售價降低還換裝1.5T 這款合資轎車不輸思域

輪轂方面使用的是美規思域Si上的18英寸旋風式輪轂,樣式非常漂亮。內飾:基本沒有變化內飾方面也僅僅進行了一些小改動,基本保持着舊款的設計。但是增加了配色,導航也終於被加入到車機裏面。依舊使用着数字显示式儀錶盤,這是一種用過都說好的設計,数字显示的時速一目瞭然,而且較高的位置真有點抬頭显示的感覺。

前言

本田思域可謂是現今緊湊型轎車的當紅辣子雞,即使需要加價也難以阻擋人們對它的喜愛之情,其中最吸引他們的就是思域上那款動力油耗表現出色的1.5T發動機以及極高的配置。而最近本田為我們帶來2017款本田傑德,這輛大兩廂汽車更換了十代思域上的1.5T發動機,功率雖然有所降低,但是配置更為豐富,那麼它具體又有着怎樣的升級呢?又是否值得購買呢?

傑德定位非常獨特,你可以將它當做一輛旅行車,也可以將其當做是MpV。而本田定位它是一輛緊湊型汽車,在筆者的眼中它是一輛大兩廂車,首先來自老款思域的平台讓它有着轎車般的駕駛感受,其次6座的布局實在和7座MpV有差異。這次2017款傑德的定價為12.99-17.99萬,購買門檻比起之前更低了。

外觀:更換了LED光源以及新增配色,更為精神

2017款傑德增加了新翠綠、波爾多紅等車身顏色,更為個性以及好看。在前臉方面進氣格柵增加了少許鍍鉻裝飾,看着更為時尚,而這種家族史設計使得傑德前臉更像是雅閣。

而集成了LED日間行車燈的LED大燈非常醒目,亮度相當高,不過這是1.5T中高配車型的專利,1.8L以及1.5T最低配都與這LED大燈無緣。

尾燈也同樣使用了LED燈源,辨識率更高,關鍵的一點是視覺效果更佳。除此以外,傑德使用了雙邊共雙出的排氣管設計,看着有一點性能味。

離地間隙提升了10mm,舊款托底的毛病減弱不少,但是有一點需要注意,傑德是一輛兩廂車,對於兩廂車而言這個離地間隙已經是很大的了。輪轂方面使用的是美規思域Si上的18英寸旋風式輪轂,樣式非常漂亮。

內飾:基本沒有變化

內飾方面也僅僅進行了一些小改動,基本保持着舊款的設計。但是增加了配色,導航也終於被加入到車機裏面。

依舊使用着数字显示式儀錶盤,這是一種用過都說好的設計,数字显示的時速一目瞭然,而且較高的位置真有點抬頭显示的感覺。

配置:提升不少,但尚未完美

這次配置上的提升是比較明顯的,讓傑德競爭力一下子提升不少,增加了LED大燈、日間行車燈、LED尾燈、18英寸輪轂、併線輔助系統、車道偏離系統、主動剎車、自適應巡航系統等,而且1.5T車型全系標配了全景天窗。

傑德似乎是想要向思域看齊,但傑德卻沒有思域上的自動駐車以及电子手剎,稍顯遺憾,而且作為最低配車型並沒有配備ESp車身穩定系統。總的而言,傑德在配置上以及性價比上提升不少。

動力:最大的亮點

一直以來,傑德都是被當做是老款思域的重造產物,使用了同樣的平台,同樣的動力總成。底盤表現非常出色,但1.8L自然吸氣發動機卻難以挑起重擔,油耗以及耐用度優秀,卻給人白開水的感覺。這次加入的1.5T發動機,就像是辣椒一樣一下子把這款大兩廂車變得勁爆刺激味蕾。最大功率115千瓦,最大扭矩203牛米,比起思域上略有下降,依然是優秀水平,可以理解為思域上的1.5T低功率版本。

和這款1.5T渦輪增壓發動機相配合的依然是CVT變速箱,和思域一樣,根據思域上平均8.5L的實測油耗,我們有理由相信使用同款發動機的傑德油耗也能同樣優秀。

這一次2017款本田傑德的上市把之前的一些小問題幾乎都解決了,更低的價格、換上了更強悍的動力總成、加入了導航、更豐富的舒適性配置以及安全配置,這樣看來傑德是越趨完美。加上同價位並沒有相似的競爭對手,傑德可能會成為東本的第二個思域。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

好開、省油還有科技感,教授把這輛車推薦給挑剔的朋友們

聽了的介紹,三位老同學都下定決心要買17款新蒙迪歐了,我想既然他們都將是17款新蒙迪歐的車主了,所以約了他們提車三個月之後回來探討一些用車的經驗。三個月後,和三位老同學再次相聚,討論一下蒙迪歐的用車心得。智行第一位同學,也就是那位駕齡不長的女司機,她說讓她印象最深刻的還是17款新蒙迪歐的LKA車道保持輔助系統,一旦車輛在無意識情況下偏離車道,系統將通過震動方向盤和儀錶盤信息提醒,並輔助她重回當前行駛車道,這讓她在高速公路上行駛時避免了偏離車道所造成的危險。

身邊的朋友知道喜歡玩車

於是經常能收到他們的諮詢

什麼十萬買啥車、二十萬買啥車

諸如此類的問題

其實都可以輕鬆應對的

然而就像廣東話說的

“上得山多終遇虎”

也會有天被問題難住

三個月前的同學聚會

有三位老同學說他們感情好

想買同一輛車,都要二十萬出頭而且夠大氣的

首先女同學是一名女司機

作為職業女性的她,要經常跑高速

而且她還是新手

所以她要我推薦一輛好開、安全的車給她

第二位同學則是環保主義者

什麼都不管,就要油耗低

(大哥,外觀大氣的車好難找油耗低的吧)

第三位同學是一名技術宅

典型的IT男

需要一輛有技術含量的車子

聽了他們的要求后,第一時間是吐槽根本沒有這樣的車。但兢兢業業鍥而不舍的我在網上做了大量的搜索之後,發現有一輛車非常適合這三位老同學,那就是長安福特的17款新蒙迪歐。

17款新蒙迪歐指導價17.98萬起,首先在價格上是符合要求的,然後在外觀上,馬丁式的家族前臉、LED大燈、2850mm的長軸距、以及肌肉感十足的車身線條,時尚個性又不失沉穩的外觀能讓不同年齡層的人接受。

內飾方面,採用了對稱式的布局,整體的內飾簡潔明了,多媒體系統也已經升級到第三代的SYNC操作系統,操作更加流暢,功能也更加完善。除此以外,旋鈕式換擋的設計也讓車內的檔次感提升不少。

行駛方面,拿2.0T車型來說,最大馬力達到245ps,搭配調校成熟的6AT手自一體變速箱,動力表現充沛且平順,絲毫不用擔心有小馬拉大車的感覺;底盤方面採用了多連桿獨立后懸架,整體偏向於舒適的調校,對細碎顛簸的過濾十分徹底。可以看出,實際的行駛表現也很符合蒙迪歐的定位。

聽了的介紹,三位老同學都下定決心要買17款新蒙迪歐了,我想既然他們都將是17款新蒙迪歐的車主了,所以約了他們提車三個月之後回來探討一些用車的經驗。

三個月後,和三位老同學再次相聚,討論一下蒙迪歐的用車心得。

智行

第一位同學,也就是那位駕齡不長的女司機,她說讓她印象最深刻的還是17款新蒙迪歐的LKA車道保持輔助系統,一旦車輛在無意識情況下偏離車道,系統將通過震動方向盤和儀錶盤信息提醒,並輔助她重回當前行駛車道,這讓她在高速公路上行駛時避免了偏離車道所造成的危險。

除此以外,對於這位新手司機來說,帶行人識別功能的智能感應制動保護系統、以及只需控制油門和剎車,即可輕鬆把車停在理想位置的主動泊車輔助系統和ACC全速智能自適應巡航控制系統都讓她駕駛蒙迪歐的過程更加輕鬆。另外,車廂內的10個安全氣囊的環繞式防護也讓她駕駛的過程中更加安心。

智擎

至於那位作為環保主義者的老同學,關注油耗的他對17款新蒙迪歐的EcoBoost雙渦流渦輪增壓直噴發動機贊不絕口,因為先進的雙渦流渦輪增壓技術能有效緩解低速時的遲滯性,使峰值扭矩提早爆發,帶來更佳的燃油經濟性。

而為了達到更好的節能環保效果,他選擇了Hybrid車型,17款新蒙迪歐的混合動力版本採用阿特金森循環發動機配合電動機,並搭載eCVT電控無級變速器,讓燃油與電力良好融合,高效環保,帶來煥然一新的駕駛體驗與樂趣。

智聯

最後一位老同學就是做IT男的技術宅,他最滿意的就是17款新蒙迪歐搭載的福特派TM互聯技術,只需要在手機中下載福特派TM應用,就可以遠程控制車輛並且通過手機來查看燃油剩餘量等車輛概況,十分方面。

在多媒體功能方面,車主可以通過藍牙或者USB數據線將手機和車輛相連,來拓展多媒體的功能,同時,17款新蒙迪歐的多媒體系統支持智能聲控以及智能中文手寫輸入,支持多字連寫,讓人機之間的交互變得更加簡單。

總結

其實早就知道17款新蒙迪歐的三大亮點能夠讓我的這三位老同學滿足,“智行”讓駕駛更加輕鬆而且安全、“智擎”在提供充沛動力的同時又保證了良好的燃油經濟性、“智聯”則結合了時下流行的智能手機,既提供了用車的便利,又豐富了多媒體功能。三管齊下,相信不只是我的這三位老同學,廣大的消費者都會被17款新蒙迪歐吸引到。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

不要小看這些買車細節,去4S店裝老司機全靠這幾招…

總結:買車畢竟是一件大事,最怕就是花了大價錢結果掉進了別人一早挖好的陷阱里,起不來活不起,當了冤大頭還要替別人數錢,所以在我們買車的時候還是多加留意,多留個心眼,將主動權抓在自己的手上。

本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※產品缺大量曝光嗎?你需要的是一流包裝設計!

大發現!國3汽車的PM2.5居然比國5低?

接下來是國三的車。所以僅從pM2。5的指數上來看,老車的成績不一定差,新車的成績也不一定好,反而車況才是更重要的因素。而那些所謂的專家。其實之前其他網友測試汽車尾氣pM2。5指數得出驚人的結果。(汽車尾氣比空氣乾淨,聽着很扯,畢竟你不可能整天去吸尾氣),就有專家出來表明,汽車的排放導致的污染並非只是直接的pM排放污染,而是“二次反應”。

一到冬季,pM指數和霧霾就成了熱門話題,這個時候汽車就會慣例被推上輿論焦點,近期國六排放標準的出台也進一步表明了相關部門的觀點—“霧霾的很大成因就是因為汽車的排放”。每次說到空氣污染提得最多的就是pM2.5指數。

pM指數那麼多,為什麼偏偏是pM2.5?

pM的英文全稱為particulate Matter (顆粒物),而数字2.5是指這種顆粒物的空氣動力學直徑(aerodynamic diameter)為2.5微米( pM10則是指粒徑等於、小於10微米的顆粒物)。粒徑越小、密度越低,顆粒沉降得越慢。如果再有一些外力引起空氣微小的擾動,這些細小的顆粒即使在無風的條件下,最終也很難沉降到地面上,它們會一直在空中“遊盪”,成為危害人體健康的罪魁禍首。

而我們常說的pM2.5的濃度或其他污染氣體的濃度是指每立方米空氣中這種污染物的質量含量,它反映了空氣的污染程度,這個值越高,就代表空氣污染越嚴重。由於空氣中污染物的種類很多,如:可吸入顆粒物、二氧化硫、氮氧化物、一氧化碳及臭氧等,為了統一評估,我國的環保部門,將每一種污染物的濃度都換算成統一的空氣污染指數,然後對外發布。俗話說病從口入,那麼我們今天就從源頭-用儀器來測試一下汽車尾氣的pM2.5指數。

在此之前我們先看下香煙的pM指數,直接爆表,所以能戒煙朋友趕緊戒煙了。

接着測試一下國4的車。(所測汽車均為公司同事車輛)

然後是國5的車!

接下來是國三的車。

所以僅從pM2.5的指數上來看,老車的成績不一定差,新車的成績也不一定好,反而車況才是更重要的因素。

而那些所謂的專家。。。

其實之前其他網友測試汽車尾氣pM2.5指數得出驚人的結果。。。

(汽車尾氣比空氣乾淨,聽着很扯,畢竟你不可能整天去吸尾氣),就有專家出來表明,汽車的排放導致的污染並非只是直接的pM排放污染,而是“二次反應”。

這樣一群吃瓜群眾就懵逼了,你是博士后,你說什麼都對,就像一直聽說F1產生的下壓力足以讓其貼在牆上跑,但是從來沒有見過,不相信這件事的人也找不到反駁的理由。

不管汽車的排放是不是元兇(因為現在霧霾這個鍋汽車已經背定了),相關部門對新車排放的要求越來越高並沒錯,畢竟排放再少也是排放。假設之前國三標準滿分是100分,當年生產的汽車再優秀,也只能得100。

現在國五標準出來了,直接就說滿分是150,100分不及格,也不給你重考的機會(限制進京、限制遷入等),這種把能夠達到要求的老車也一刀切那就有點想不通了,對於車迷來說最大的損失就是中國就永遠不會有老爺車文化,因為在相關部門眼中只有報廢車。

最後通過數據計算可以知道廣州的車輛密度比北京還要高,但是空氣質量卻要好不少,所以愛吃烤鴨的小夥伴趕緊把烤鴨吃個夠,說不定哪天烤鴨也分黃標鴨!!!

本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

※產品缺大量曝光嗎?你需要的是一流包裝設計!

為什麼朋友說他夢想的車是艾母雞?看完本文後終於懂了

說到奔馳許多人最初的印象都是寬、大、貴外加後排坐着一個人肥,牙黃,地中海的老闆似乎奔馳一向跟年輕、運動、操控這些詞扯不上什麼關係所以坊間也流傳這麼一個說法——甚至有一些“自信狂人”遇上大奔時會抑制不住腎上腺

說到奔馳

許多人最初的印象都是

寬、大、貴

外加後排坐着一個

人肥,牙黃,地中海的老闆

似乎奔馳一向跟

年輕、運動、操控這些詞

扯不上什麼關係

所以坊間也流傳這麼一個說法——

甚至有一些“自信狂人”

遇上大奔時

會抑制不住腎上腺本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

我很驚訝!十幾萬的國產SUV竟有人幹得過眾泰?

對於我們汽車媒體來說,這是個很方面的功能。榮威RX5配備了車身穩定系統、定速巡航、一鍵啟動、電動駐車、前撞預警系統、陡坡緩降、電動後備廂、車道保持、道路限速識別、四驅系統鎖止等豐富配置。動力系統方面,RX5將搭載1。

國內緊湊型SUV的熱度只增不減,2016年,榮威帶來了他們潛心打造,並得到馬雲的阿里技術支持的榮威RX5,主打互聯網概念。

榮威RX5外觀走大氣沉穩范,中規中矩但又精緻的造型給人不錯的印象。前臉“展翼格柵”將兩側前大燈融貫一體,前大燈為矩陣式LED大燈,其由24顆LED光源組成,燈組內部還集成了“如意形”LED日間行車燈。矩陣式全LED大燈更顯科技感。

一進車內最吸睛的當屬中控10.4英寸大屏。由於大屏幕的使用,一些傳統按鍵被取消,整體設計簡潔時尚,僅在屏幕下方保留了五個實體按鍵。同時RX5在內飾用料上也多處使用了軟質皮革包裹,凸顯質感。

RX5配備了榮威和阿里聯合開發的Yun OS,功能較為強大,可以通過大數據為用戶提供個性化服務。還可以支持綁定航拍機和運動相機,旅途中拍攝的畫面可以呈現在中控的大屏幕上。對於我們汽車媒體來說,這是個很方面的功能。

榮威RX5配備了車身穩定系統、定速巡航、一鍵啟動、電動駐車、前撞預警系統、陡坡緩降、電動後備廂、車道保持、道路限速識別、四驅系統鎖止等豐富配置。

動力系統方面,RX5將搭載1.5T和2.0T兩款渦輪增壓發動機,其中1.5T發動機最大功率為169馬,峰值扭矩250N·m,與之匹配的是手動/TST 7速雙離合變速箱;2.0T發動機最大功率為220馬力,峰值扭矩350N·m,與之匹配的是TST 6速濕式雙離合變速箱。

整車開起來動力表現不錯,車輛的轉向是比較精準的,底盤偏向柔軟和舒適,但是又保留了一些路感。很符合家用SUV得定位。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

環境篇:嘔心瀝血@CDH線上調優

環境篇:嘔心瀝血@CDH線上調優

  • 為什麼出這篇文章?

近期有很多公司開始引入大數據,由於各方資源有限,並不能合理分配服務器資源,和服務器選型,小恭弘=叶 恭弘這裏將工作中的總結出來,給新入行的小夥伴帶個方向,不敢說一定對,但是本人親自測試,發現集群使用率穩定提高了3分之1,最高可達到2分之1,有不對的地方歡迎留言指出。

注:可能有些服務沒有設計,使用到的小夥伴可以參照這種方式去規劃。

0 資源:集群服務安排

服務名稱 子服務 CM-64G ZK-Kafka(3台)-12G DataNode(3台)-64G NameNode1-64G NameNode2-64G Resourcemanager1-32G Resourcemanager2-32G hive-hbase-16G hive-hbase-16G
MySQL MySQL
CM Activity Monitor
Alert Publisher
Event Server
Host Monitor
Service Monitor




HDFS NameNode
DataNode
Failover Controller
JournalNode
X

X
X

X


X

X
X
X
Yarn NodeManager
Resourcemanager
JobHisoryServer

X
X
X

X

Zookeeper Zookeeper Server
Kafka Kafka Broker
Hive Hive Metastore Server
HiveServer2
Gateway(安裝對應應用服務器)
X


X

X
Hbase HMaster
HRegionServer
Thrift Server
X


X

X
Oozie Oozie Server
Hue Hue Server
Load Balancer
X

X
Spark History Server
Gateway(安裝對應應用服務器)

X
Flume Flume Agent (安裝對應應用服務器)
Sqoop Sqoop(安裝對應應用服務器)

1 優化:Cloudera Management

1.1 Cloudera Management Service

這些服務主要是提供監控功能,目前的調整主要集中在內存放,以便有足夠的資源 完成集群管理。

服務 選項 配置值
Activity Monitor Java Heap Size 2G
Alert Publisher Java Heap Size 2G
Event Server Java Heap Size 2G
Host Monitor Java Heap Size 4G
Service Monitor Java Heap Size 4G
Reports Manager Java Heap Size 2G
Navigator Metadata Server Java Heap Size 8G

2 優化:Zookeeper

服務 選項 配置值
Zookeeper Java Heap Size (堆棧大小) 4G
Zookeeper maxClientCnxns (最大客戶端連接數) 1024
Zookeeper dataDir (數據文件目錄+數據持久化路徑) /hadoop/zookeeper (建議獨立目錄)
Zookeeper dataLogDir (事務日誌目錄) /hadoop/zookeeper_log (建議獨立目錄)

3 優化:HDFS

3.1 磁盤測試

3.1.1 讀測試

hdparm 用於查看硬盤的相關信息或對硬盤進行測速、優化、修改硬盤相關參數設定

#安裝hdparm
yum install hdparm
#獲取硬盤符
fdisk -l
#讀測試(讀取上一步獲取硬盤符)
hdparm -t /dev/vda

三次測試結果:

Timing buffered disk reads: 500 MB in 0.84 seconds = 593.64 MB/sec

Timing buffered disk reads: 500 MB in 0.93 seconds = 538.80 MB/sec

Timing buffered disk reads: 500 MB in 0.74 seconds = 672.95 MB/sec

說明:接近1s秒讀取了500MB磁盤,讀速度約 500 MB/秒

3.1.2 寫測試

dd 這裏使用 time + dd 簡單測試寫速度,不要求很精確

查看內存緩存情況
free -m
 
清除緩存
sync; echo 3 > /proc/sys/vm/drop_caches
 
查block size
blockdev --getbsz /dev/vda
 
寫測試
echo 3 > /proc/sys/vm/drop_caches; time dd if=/dev/zero of=/testdd bs=4k count=100000

三次測試結果:

記錄了100000+0 的讀入
記錄了100000+0 的寫出

409600000 bytes (410 MB) copied, 0.574066 s, 714 MB/s –410MB複製,用時0.57秒,評估714M/s

409600000 bytes (410 MB) copied, 1.84421 s, 222 MB/s –410MB複製,用時1.84秒,評估222 M/s

409600000 bytes (410 MB) copied, 1.06969 s, 383 MB/s –410MB複製,用時1.06秒,評估383M/s

3.1.3 網絡帶寬

iperf3測量一個網絡最大帶寬

#安裝iperf3
yum -y install iperf3
 
#服務端
iperf3 -s
 
#客戶端
iperf3 -c 上調命令執行的服務機器IP

測試結果:

[ ID]–>線程id Interva–>傳輸時間 Transfer–>接收數據大小 Bandwidth–>帶寬每秒大小 Retr 角色
[ 4] 0.00-10.00 sec 17.0 GBytes 14.6 Gbits/sec 0 sender–>發送
[ 4] 0.00-10.00 sec 17.0 GBytes 14.6 Gbits/sec receiver–>接收

3.2 官方壓測

3.2.1 用戶準備

由於只能使用yarn配置了允許用戶,故這裏選擇hive用戶,如果su hive不能進入,則需要配置該步驟

usermod -s /bin/bash  hive
su hive

3.2.2 HDFS 寫性能測試

  • 測試內容:HDFS集群寫入10個128M文件(-D指定文件存儲目錄)
hadoop  jar /opt/cloudera/parcels/CDH-6.2.0-1.cdh6.2.0.p0.967373/jars/hadoop-mapreduce-client-jobclient-3.0.0-cdh6.2.0-tests.jar  TestDFSIO  -D test.build.data=/test/benchmark -write -nrFiles 10 -fileSize 128

INFO fs.TestDFSIO: —– TestDFSIO —– : write
INFO fs.TestDFSIO: Date & time: Thu Jun 11 10:30:36 CST 2020
INFO fs.TestDFSIO: Number of files: 10 –十個文件
INFO fs.TestDFSIO: Total MBytes processed: 1280 –總大小1280M
INFO fs.TestDFSIO: Throughput mb/sec: 16.96 –吞吐量 每秒16.96M
INFO fs.TestDFSIO: Average IO rate mb/sec: 17.89 –平均IO情況17.89M
INFO fs.TestDFSIO: IO rate std deviation: 4.74 –IO速率標準偏差
INFO fs.TestDFSIO: Test exec time sec: 46.33 –總運行時間

3.2.3 HDFS 讀性能測試

  • 測試內容:HDFS集群讀取10個128M文件
hadoop jar /opt/cloudera/parcels/CDH-6.2.0-1.cdh6.2.0.p0.967373/jars/hadoop-mapreduce-client-jobclient-3.0.0-cdh6.2.0-tests.jar TestDFSIO  -D test.build.data=/test/benchmark -read -nrFiles 10 -fileSize 128

INFO fs.TestDFSIO: —– TestDFSIO —– : read
INFO fs.TestDFSIO: Date & time: Thu Jun 11 10:41:19 CST 2020
INFO fs.TestDFSIO: Number of files: 10 –文件數
INFO fs.TestDFSIO: Total MBytes processed: 1280 –總大小
INFO fs.TestDFSIO: Throughput mb/sec: 321.53 –吞吐量 每秒321.53M
INFO fs.TestDFSIO: Average IO rate mb/sec: 385.43 –平均IO情況385.43M
INFO fs.TestDFSIO: IO rate std deviation: 107.67 –IO速率標準偏差
INFO fs.TestDFSIO: Test exec time sec: 20.81 –總運行時間

3.2.4 刪除測試數據

hadoop jar /opt/cloudera/parcels/CDH-6.2.0-1.cdh6.2.0.p0.967373/jars/hadoop-mapreduce-client-jobclient-3.0.0-cdh6.2.0-tests.jar TestDFSIO  -D test.build.data=/test/benchmark -clean

3.3 參數調優

服務 選項 配置值
NameNode Java Heap Size (堆棧大小) 56G
NameNode dfs.namenode.handler.count (詳見3.3.2) 80
NameNode dfs.namenode.service.handler.count (詳見3.3.2) 80
NameNode fs.permissions.umask-mode (使用默認值022) 027(使用默認值022)
DataNode Java Heap Size (堆棧大小) 8G
DataNode dfs.datanode.failed.volumes.tolerated (詳見3.3.3) 1
DataNode dfs.datanode.balance.bandwidthPerSec (DataNode 平衡帶寬) 100M
DataNode dfs.datanode.handler.count (服務器線程數) 64
DataNode dfs.datanode.max.transfer.threads (最大傳輸線程數) 20480
JournalNode Java Heap Size (堆棧大小) 1G

3.3.1 數據塊優化

dfs.blocksize = 128M

  • 文件以塊為單位進行切分存儲,塊通常設置的比較大(最小6M,默認128M),根據網絡帶寬計算最佳值。
  • 塊越大,尋址越快,讀取效率越高,但同時由於MapReduce任務也是以塊為最小單位來處理,所以太大的塊不利於於對數據的并行處理。
  • 一個文件至少佔用一個塊(如果一個1KB文件,佔用一個塊,但是佔用空間還是1KB)
  • 我們在讀取HDFS上文件的時候,NameNode會去尋找block地址,尋址時間為傳輸時間的1%時,則為最佳狀態。
    • 目前磁盤的傳輸速度普遍為100MB/S
    • 如果尋址時間約為10ms,則傳輸時間=10ms/0.01=1000ms=1s
    • 如果傳輸時間為1S,傳輸速度為100MB/S,那麼一秒鐘我們就可以向HDFS傳送100MB文件,設置塊大小128M比較合適。
    • 如果帶寬為200MB/S,那麼可以將block塊大小設置為256M比較合適。

3.3.2 NameNode 的服務器線程的數量

  • dfs.namenode.handler.count=20*log2(Cluster Size),比如集群規模為16 ,8以2為底的對數是4,故此參數設置為80
  • dfs.namenode.service.handler.count=20*log2(Cluster Size),比如集群規模為16 ,8以2為底的對數是4,故此參數設置為80

NameNode有一個工作線程池,用來處理不同DataNode的併發心跳以及客戶端併發的元數據操作。該值需要設置為集群大小的自然對數乘以20,。

3.3.3 DataNode 停止提供服務前允許失敗的卷的數量

DN多少塊盤損壞后停止服務,默認為0,即一旦任何磁盤故障DN即關閉。 對盤較多的集群(例如DN有超過2塊盤),磁盤故障是常態,通常可以將該值設置為1或2,避免頻繁有DN下線。

4 優化:YARN + MapReduce

服務 選項 配置值 參數說明
ResourceManager Java Heap Size (堆棧大小) 4G
ResourceManager yarn.scheduler.minimum-allocation-mb (最小容器內存) 2G 給應用程序 Container 分配的最小內存
ResourceManager yarn.scheduler.increment-allocation-mb (容器內存增量) 512M 如果使用 Fair Scheduler,容器內存允許增量
ResourceManager yarn.scheduler.maximum-allocation-mb (最大容器內存) 32G 給應用程序 Container 分配的最大內存
ResourceManager yarn.scheduler.minimum-allocation-vcores (最小容器虛擬 CPU 內核數量) 1 每個 Container 申請的最小 CPU 核數
ResourceManager yarn.scheduler.increment-allocation-vcores (容器虛擬 CPU 內核增量) 1 如果使用 Fair Scheduler,虛擬 CPU 內核允許增量
ResourceManager yarn.scheduler.maximum-allocation-vcores (最大容器虛擬 CPU 內核數量) 16 每個 Container 申請的最大 CPU 核數
ResourceManager yarn.resourcemanager.recovery.enabled true 啟用后,ResourceManager 中止時在群集上運行的任何應用程序將在 ResourceManager 下次啟動時恢復,備註:如果啟用 RM-HA,則始終啟用該配置。
NodeManager Java Heap Size (堆棧大小) 4G
NodeManager yarn.nodemanager.resource.memory-mb 40G 可分配給容器的物理內存數量,參照資源池內存90%左右
NodeManager yarn.nodemanager.resource.cpu-vcores 32 可以為容器分配的虛擬 CPU 內核的數量,參照資源池內存90%左右
ApplicationMaster yarn.app.mapreduce.am.command-opts 右紅 傳遞到 MapReduce ApplicationMaster 的 Java 命令行參數 “-Djava.net.preferIPv4Stack=true
ApplicationMaster yarn.app.mapreduce.am.resource.mb (ApplicationMaster 內存) 4G
JobHistory Java Heap Size (堆棧大小) 2G
MapReduce mapreduce.map.memory.mb (Map 任務內存) 4G 一個MapTask可使用的資源上限。如果MapTask實際使用的資源量超過該值,則會被強制殺死。
MapReduce mapreduce.reduce.memory.mb (Reduce 任務內存) 8G 一個 ReduceTask 可使用的資源上限。如果 ReduceTask 實際使用的資源量超過該值,則會被強制殺死
MapReduce mapreduce.map.cpu.vcores 2 每個 MapTask 可使用的最多 cpu core 數目
MapReduce mapreduce.reduce.cpu.vcores 4 每個 ReduceTask 可使用的最多 cpu core 數目
MapReduce mapreduce.reduce.shuffle.parallelcopies 20 每個 Reduce 去 Map 中取數據的并行數。
MapReduce mapreduce.task.io.sort.mb(Shuffle 的環形緩衝區大小) 512M 當排序文件時要使用的內存緩衝總量。注意:此內存由 JVM 堆棧大小產生(也就是:總用戶 JVM 堆棧 – 這些內存 = 總用戶可用堆棧空間)
MapReduce mapreduce.map.sort.spill.percent 80% 環形緩衝區溢出的閾值
MapReduce mapreduce.task.timeout 10分鐘 Task 超時時間,經常需要設置的一個參數,該參數表 達的意思為:如果一個 Task 在一定時間內沒有任何進 入,即不會讀取新的數據,也沒有輸出數據,則認為 該 Task 處於 Block 狀態,可能是卡住了,也許永遠會 卡住,為了防止因為用戶程序永遠 Block 住不退出, 則強制設置了一個該超時時間。如果你的程序對每條輸入數據的處理時間過長(比如會訪問數據庫,通過網絡拉取數據等),建議將該參數調大,該參數過小常出現的錯誤提示是 :AttemptID:attempt_12267239451721_123456_m_00 0335_0 Timed out after 600 secsContainer killed by the ApplicationMaster。

5 優化:Impala

服務 選項 配置值 參數說明
Impala Daemon mem_limit (內存限制) 50G 由守護程序本身強制執行的 Impala Daemon 的內存限制。
如果達到該限制,Impalad Daemon 上運行的查詢可能會被停止
Impala Daemon Impala Daemon JVM Heap 512M 守護進程堆棧大小
Impala Daemon scratch_dirs 節點上多塊獨立磁盤(目錄) Impala Daemon 將溢出信息等數據寫入磁盤以釋放內存所在的目錄。這可能是大量數據
Impala Catalog Server Java Heap Size 8G 堆棧大小

6 優化:Kafka

6.1 官方壓測

6.1.1 Kafka Producer 壓力測試

  • record-size 是一條信息有多大,單位是字節。
  • num-records 是總共發送多少條信息。
  • throughput 是每秒多少條信息,設成-1,表示不限流,可測出生產者最大吞吐量。
bash /opt/cloudera/parcels/CDH-6.2.0-1.cdh6.2.0.p0.967373/lib/kafka//bin/kafka-producer-perf-test.sh --topic test --record-size 100 --num-records 100000 --throughput -1 --producer-props bootstrap.servers=cdh01.cm:9092,cdh02.cm:9092,cdh03.cm:9092

100000 records sent, 225733.634312 records/sec (21.53 MB/sec),

8.20 ms avg latency, 66.00 ms max latency,

3 ms 50th, 28 ms 95th, 30 ms 99th, 30 ms 99.9th.

參數解析:一共寫入 10w 條消息,吞吐量為 21.53 MB/sec,每次寫入的平均延遲

為 8.20 毫秒,最大的延遲為 66.00 毫秒。

6.1.2 Kafka Consumer 壓力測試

  • zookeeper 指定 zookeeper 的鏈接信息
  • topic 指定 topic 的名稱
  • fetch-size 指定每次 fetch 的數據的大小
  • messages 總共要消費的消息個數
bash /opt/cloudera/parcels/CDH-6.2.0-1.cdh6.2.0.p0.967373/lib/kafka//bin/kafka-consumer-perf-test.sh --broker-list cdh01.cm:9092,cdh02.cm:9092,cdh03.cm:9092 --topic test --fetch-size 10000 --messages 10000000 --threads 1

start.time, end.time, data.consumed.in.MB, MB.sec, data.consumed.in.nMsg, nMsg.sec, rebalance.time.ms, fetch.time.ms, fetch.MB.sec, fetch.nMsg.sec

2020-06-11 17:53:48:179, 2020-06-11 17:54:04:525, 57.2205, 3.5006, 600000, 36706.2278, 3051, 13295, 4.3039, 45129.7480

start.time:2020-06-11 17:53:48:179 開始時間

end.time:2020-06-11 17:54:04:525 結束時間(用時16秒)

data.consumed.in.MB:57.2205 消費57M數據

MB.sec:3.5006 3.5M/S

data.consumed.in.nMsg:600000 消費60萬消息

nMsg.sec:36706.2278 36706條消息/S

rebalance.time.ms:3051 平衡時間3S

fetch.time.ms:13295 抓取時間13S

fetch.MB.sec:4.3039 一秒抓取4.3M

fetch.nMsg.sec:45129.7480 一秒抓取45129條消息

開始測試時間,測試結束數據,共消費數據57.2205MB,吞吐量 3.5M/S,共消費600000條,平均每秒消費36706.2278條。

6.1.3 Kafka 機器數量計算

Kafka 機器數量(經驗公式)= 2 X(峰值生產速度 X 副本數 /100)+ 1

先拿到峰值生產速度,再根據設定的副本數,就能預估出需要部署 Kafka 的數量。

比如我們的峰值生產速度是 50M/s。副本數為 2。

Kafka 機器數量 = 2 X( 50 X 2 / 100 )+ 1 = 3 台

6.2 參數調優

服務 選項 配置值 參數說明
Kafka Broker Java Heap Size of Broker 2G Broker堆棧大小
Kafka Broker Data Directories 多塊獨立磁盤
Kafka 服務 Maximum Message Size 10M 服務器可以接收的消息的最大大小。此屬性必須與使用者使用的最大提取大小同步。否則,不守規矩的生產者可能會發布太大而無法消費的消息
Kafka 服務 Replica Maximum Fetch Size 20M 副本發送給leader的獲取請求中每個分區要獲取的最大字節數。此值應大於message.max.bytes。
Kafka 服務 Number of Replica Fetchers 6 用於複製來自領導者的消息的線程數。增大此值將增加跟隨者代理中I / O并行度。

7 優化:HBase

服務 選項 配置值 參數說明
HBase Java Heap Size 18G 客戶端 Java 堆大小(字節)主要作用來緩存Table數據,但是flush時會GC,不要太大,根據集群資源,一般分配整個Hbase集群內存的70%,16->48G就可以了
HBase hbase.client.write.buffer 512M 寫入緩衝區大小,調高該值,可以減少RPC調用次數,單數會消耗更多內存,較大緩衝區需要客戶端和服務器中有較大內存,因為服務器將實例化已通過的寫入緩衝區並進行處理,這會降低遠程過程調用 (RPC) 的數量。
HBase Master Java Heap Size 8G HBase Master 的 Java 堆棧大小
HBase Master hbase.master.handler.count 300 HBase Master 中啟動的 RPC 服務器實例數量。
HBase RegionServer Java Heap Size 31G HBase RegionServer 的 Java 堆棧大小
HBase RegionServer hbase.regionserver.handler.count 100 RegionServer 中啟動的 RPC 服務器實例數量,根據集群情況,可以適當增加該值,主要決定是客戶端的請求數
HBase RegionServer hbase.regionserver.metahandler.count 60 用於處理 RegionServer 中的優先級請求的處理程序的數量
HBase RegionServer zookeeper.session.timeout 180000ms ZooKeeper 會話延遲(以毫秒為單位)。HBase 將此作為建議的最長會話時間傳遞給 ZooKeeper 仲裁
HBase RegionServer hbase.hregion.memstore.flush.size 1G 如 memstore 大小超過此值,Memstore 將刷新到磁盤。通過運行由 hbase.server.thread.wakefrequency 指定的頻率的線程檢查此值。
HBase RegionServer hbase.hregion.majorcompaction 0 合併周期,在合格節點下,Region下所有的HFile會進行合併,非常消耗資源,在空閑時手動觸發
HBase RegionServer hbase.hregion.majorcompaction.jitter 0 抖動比率,根據上面的合併周期,有一個抖動比率,也不靠譜,還是手動好
HBase RegionServer hbase.hstore.compactionThreshold 6 如在任意一個 HStore 中有超過此數量的 HStoreFiles,則將運行壓縮以將所有 HStoreFiles 文件作為一個 HStoreFile 重新寫入。(每次 memstore 刷新寫入一個 HStoreFile)您可通過指定更大數量延長壓縮,但壓縮將運行更長時間。在壓縮期間,更新無法刷新到磁盤。長時間壓縮需要足夠的內存,以在壓縮的持續時間內記錄所有更新。如太大,壓縮期間客戶端會超時。
HBase RegionServer hbase.client.scanner.caching 1000 內存未提供數據的情況下掃描儀下次調用時所提取的行數。較高緩存值需啟用較快速度的掃描儀,但這需要更多的內存且當緩存為空時某些下一次調用會運行較長時間
HBase RegionServer hbase.hregion.max.filesize 50G HStoreFile 最大大小。如果列組的任意一個 HStoreFile 超過此值,則託管 HRegion 將分割成兩個
HBase Master hbase.master.logcleaner.plugins 日誌清除器插件 org.apache.hadoop.hbase.master.cleaner.TimeToLiveLogCleaner
HBase hbase.replication false 禁用複製
HBase hbase.master.logcleaner.ttl 10min 保留 HLogs 的最長時間,加上如上兩條解決oldWALs增長問題

8 優化:Hive

服務 選項 配置值 參數說明
HiveServer2 Java Heap Size 4G
Hive MetaStore Java Heap Size 8G
Hive Gateway Java Heap Size 2G
Hive hive.execution.engine Spark 執行引擎切換
Hive hive.fetch.task.conversion more Fetch抓取修改為more,可以使全局查找,字段查找,limit查找等都不走計算引擎,而是直接讀取表對應儲存目錄下的文件,大大普通查詢速度
Hive hive.exec.mode.local.auto(hive-site.xml 服務高級配置,客戶端高級配置) true 開啟本地模式,在單台機器上處理所有的任務,對於小的數據集,執行時間可以明顯被縮短
Hive hive.exec.mode.local.auto.inputbytes.max(hive-site.xml 服務高級配置,客戶端高級配置) 50000000 文件不超過50M
Hive hive.exec.mode.local.auto.input.files.max(hive-site.xml 服務高級配置,客戶端高級配置) 10 個數不超過10個
Hive hive.auto.convert.join 開啟 在join問題上,讓小表放在左邊 去左鏈接(left join)大表,這樣可以有效的減少內存溢出錯誤發生的幾率
Hive hive.mapjoin.smalltable.filesize(hive-site.xml 服務高級配置,客戶端高級配置) 50000000 50M以下認為是小表
Hive hive.map.aggr 開啟 默認情況下map階段同一個key發送給一個reduce,當一個key數據過大時就發生數據傾斜。
Hive hive.groupby.mapaggr.checkinterval(hive-site.xml 服務高級配置,客戶端高級配置) 200000 在map端進行聚合操作的條目數目
Hive hive.groupby.skewindata(hive-site.xml 服務高級配置,客戶端高級配置) true 有數據傾斜時進行負載均衡,生成的查詢計劃會有兩個MR Job,第一個MR Job會將key加隨機數均勻的分佈到Reduce中,做部分聚合操作(預處理),第二個MR Job在根據預處理結果還原原始key,按照Group By Key分佈到Reduce中進行聚合運算,完成最終操作
Hive hive.exec.parallel(hive-site.xml 服務高級配置,客戶端高級配置) true 開啟并行計算
Hive hive.exec.parallel.thread.number(hive-site.xml 服務高級配置,客戶端高級配置) 16G 同一個sql允許的最大并行度,針對集群資源適當增加

9 優化:Oozie、Hue

服務 選項 配置值 參數說明
Oozie Java Heap Size 1G 堆棧大小
Hue Java Heap Size 4G 堆棧大小

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

.net core3.1 abp動態菜單和動態權限(思路) (二)

ps:本文需要先把abp的源碼下載一份來下,跟着一起找實現,更容易懂

在abp中,對於權限和菜單使用靜態來管理,菜單的加載是在登陸頁面的地方(具體是怎麼知道的,瀏覽器按F12,然後去sources中去找)

這個/AbpScripts/GetScripts是獲取需要初始化的script,源自AbpScriptsController,GetScripts方法包括

頁面加載時的鏈接是:http://localhost:62114/AbpScripts/GetScripts?v=637274153555501055

_multiTenancyScriptManager //當前租戶初始化 對應報文的 abp.multiTenancy

_sessionScriptManager //當前session初始化 對應報文的 abp.session
_localizationScriptManager  //本地化的初始化 對應報文的 abp.localization
_featuresScriptManager  //對應報文的 abp.features
_authorizationScriptManager  //權限初始化  對應報文的 abp.auth
_navigationScriptManager  //導航菜單初始化  對應報文的 abp.nav
_settingScriptManager  //設置初始化  對應報文的 abp.setting
_timingScriptManager  //對應報文的 abp.clock
_customConfigScriptManager  //對應報文的 abp.custom

 

 

 

 

 好了,現在基本算是找到菜單和權限js獲取的地方了,一般系統裏面,權限是依賴於菜單和菜單按鈕的,所以我們先不管權限,先把菜單做成動態加載的

從await _navigationScriptManager.GetScriptAsync()開始,一路F12,大概流程是

(接口)INavigationScriptManager=>(接口實現)NavigationScriptManager=>(方法)GetScriptAsync=>(調用)await _userNavigationManager.GetMenusAsync=>
(接口)IUserNavigationManager=>(接口實現)UserNavigationManager=>(方法)GetMenuAsync=>(調用)navigationManager.Menus=>
(接口)INavigationManager=>(接口實現)NavigationManager=>(非靜態構造函數為Menus屬性賦值)NavigationManager

 到這裏之後基本就到底了,我們看看NavigationManager的內容

    internal class NavigationManager : INavigationManager, ISingletonDependency
    {
        public IDictionary<string, MenuDefinition> Menus { get; private set; }  //屬性

        public MenuDefinition MainMenu //屬性
        {
            get { return Menus["MainMenu"]; }
        }

        private readonly IIocResolver _iocResolver;  
        private readonly INavigationConfiguration _configuration;

        public NavigationManager(IIocResolver iocResolver, INavigationConfiguration configuration) //非靜態構造函數
        {
            _iocResolver = iocResolver;
            _configuration = configuration;

            Menus = new Dictionary<string, MenuDefinition>
                    {
                        {"MainMenu", new MenuDefinition("MainMenu", new LocalizableString("MainMenu", AbpConsts.LocalizationSourceName))}
                    };
        }

        public void Initialize()  //初始化方法
        {
            var context = new NavigationProviderContext(this);

            foreach (var providerType in _configuration.Providers)
            {
                using (var provider = _iocResolver.ResolveAsDisposable<NavigationProvider>(providerType))
                {
                    provider.Object.SetNavigation(context);  //中式英語翻譯一下,應該是設置導航
                }
            }
        }
    }

這個類裏面就只有屬性、需要注入的接口聲明、非靜態構造函數、初始化方法,我們到這裏需要關注的是Menus這個屬性,這個屬性似乎將會包含我們需要生成的菜單內容

Menus = new Dictionary<string, MenuDefinition>
                    {
                        {"MainMenu", new MenuDefinition("MainMenu", new LocalizableString("MainMenu", AbpConsts.LocalizationSourceName))}
                    };

這裡是對Menus的賦值,實例化了一個Dictionary,前面的不用看,主要是看標紅的這句話,從new LocalizableString(“MainMenu”, AbpConsts.LocalizationSourceName)裏面獲取到值

好了現在基本找到地方了,我們不知道LocalizableString是什麼意思,但是我們可以百度一波

ILocalizableString/LocalizableString:封裝需要被本地化的string的信息,並提供Localize方法(調用ILocalizationManager的GetString方法)返回本地化的string. SourceName指定其從那個本地化資源讀取本地化文本。

  LocalizableString(“Questions”, “”) 如果本地找不到資源,會報300

大概的意思是通過new LocalizableString,我們可以在本地化來源為AbpConsts.LocalizationSourceName的string裏面尋找到Key為MainMenu的value(理解不對請噴)

 

現在需要去找到那個地方對MainMenu進行了本地化操作,一般來說這個事情都是在程序加載的時候進行的,先對MainMenu進行讀取,保存到本地,然後在_navigationScriptManager讀取,傳輸給前台

似乎不好找了,但是我們發現有一個類型MenuDefinition,F12一下,可以發現寶藏

namespace Abp.Application.Navigation
{
    /// <summary>
    /// Represents a navigation menu for an application.  //表示應用程序的導航菜單
/// </summary>
    public class MenuDefinition : IHasMenuItemDefinitions
    {
        /// <summary>
        /// Unique name of the menu in the application. Required.  //應用程序中菜單的唯一名稱。 必須
        /// </summary>
        public string Name { get; private set; }

        /// <summary>
        /// Display name of the menu. Required.  //菜單显示名稱 必須
/// </summary>
        public ILocalizableString DisplayName { get; set; }

        /// <summary>
        /// Can be used to store a custom object related to this menu. Optional.  //可用於存儲與此菜單相關的自定義對象
/// </summary>
        public object CustomData { get; set; }

        /// <summary>
        /// Menu items (first level).   //菜單項(第一級)
/// </summary>
        public List<MenuItemDefinition> Items { get; set; }

        /// <summary>
        /// Creates a new <see cref="MenuDefinition"/> object.
        /// </summary>
        /// <param name="name">Unique name of the menu</param>
        /// <param name="displayName">Display name of the menu</param>
        /// <param name="customData">Can be used to store a custom object related to this menu.</param>
        public MenuDefinition(string name, ILocalizableString displayName, object customData = null)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException("name", "Menu name can not be empty or null.");
            }

            if (displayName == null)
            {
                throw new ArgumentNullException("displayName", "Display name of the menu can not be null.");
            }

            Name = name;
            DisplayName = displayName;
            CustomData = customData;

            Items = new List<MenuItemDefinition>();
        }

        /// <summary>
        /// Adds a <see cref="MenuItemDefinition"/> to <see cref="Items"/>.
        /// </summary>
        /// <param name="menuItem"><see cref="MenuItemDefinition"/> to be added</param>
        /// <returns>This <see cref="MenuDefinition"/> object</returns>
        public MenuDefinition AddItem(MenuItemDefinition menuItem)
        {
            Items.Add(menuItem);
            return this;
        }

        /// <summary>
        /// Remove menu item with given name
        /// </summary>
        /// <param name="name"></param>
        public void RemoveItem(string name)
        {
            Items.RemoveAll(m => m.Name == name);
        }
    }
}

找到了菜單的類型了,那麼我們去找保存的地方就好找了,我們其實可以根據AddItem這個方法去找,去查看哪個地方引用了

AddItem方法添加的是MenuItemDefinition類型的變量,那我們現在退出abp源碼,去我們的AbpLearn項目中去全局搜索一下

 

 

看來是同一個AbpLearnNavigationProvider類裏面,雙擊過去看一下

 

    /// <summary>
    /// This class defines menus for the application.
    /// </summary>
    public class AbpLearnNavigationProvider : NavigationProvider
    {
        public override void SetNavigation(INavigationProviderContext context)
        {
            context.Manager.MainMenu
                .AddItem(
                    new MenuItemDefinition(
                        PageNames.Home,
                        L("HomePage"),
                        url: "",
                        icon: "fas fa-home",
                        requiresAuthentication: true
                    )
                ).AddItem(
                    new MenuItemDefinition(
                        PageNames.Tenants,
                        L("Tenants"),
                        url: "Tenants",
                        icon: "fas fa-building",
                        permissionDependency: new SimplePermissionDependency(PermissionNames.Pages_Tenants)
                    )
                ).AddItem(
                    new MenuItemDefinition(
                        PageNames.Users,
                        L("Users"),
                        url: "Users",
                        icon: "fas fa-users",
                        permissionDependency: new SimplePermissionDependency(PermissionNames.Pages_Users)
                    )
                ).AddItem(
                    new MenuItemDefinition(
                        PageNames.Roles,
                        L("Roles"),
                        url: "Roles",
                        icon: "fas fa-theater-masks",
                        permissionDependency: new SimplePermissionDependency(PermissionNames.Pages_Roles)
                            )
                )
                .AddItem(
                    new MenuItemDefinition(
                        PageNames.About,
                        L("About"),
                        url: "About",
                        icon: "fas fa-info-circle"
                    )
                ).AddItem( // Menu items below is just for demonstration!
                    new MenuItemDefinition(
                        "MultiLevelMenu",
                        L("MultiLevelMenu"),
                        icon: "fas fa-circle"
                    ).AddItem(
                        new MenuItemDefinition(
                            "AspNetBoilerplate",
                            new FixedLocalizableString("ASP.NET Boilerplate"),
                            icon: "far fa-circle"
                        ).AddItem(
                            new MenuItemDefinition(
                                "AspNetBoilerplateHome",
                                new FixedLocalizableString("Home"),
                                url: "https://aspnetboilerplate.com?ref=abptmpl",
                                icon: "far fa-dot-circle"
                            )
                        ).AddItem(
                            new MenuItemDefinition(
                                "AspNetBoilerplateTemplates",
                                new FixedLocalizableString("Templates"),
                                url: "https://aspnetboilerplate.com/Templates?ref=abptmpl",
                                icon: "far fa-dot-circle"
                            )
                        ).AddItem(
                            new MenuItemDefinition(
                                "AspNetBoilerplateSamples",
                                new FixedLocalizableString("Samples"),
                                url: "https://aspnetboilerplate.com/Samples?ref=abptmpl",
                                icon: "far fa-dot-circle"
                            )
                        ).AddItem(
                            new MenuItemDefinition(
                                "AspNetBoilerplateDocuments",
                                new FixedLocalizableString("Documents"),
                                url: "https://aspnetboilerplate.com/Pages/Documents?ref=abptmpl",
                                icon: "far fa-dot-circle"
                            )
                        )
                    ).AddItem(
                        new MenuItemDefinition(
                            "AspNetZero",
                            new FixedLocalizableString("ASP.NET Zero"),
                            icon: "far fa-circle"
                        ).AddItem(
                            new MenuItemDefinition(
                                "AspNetZeroHome",
                                new FixedLocalizableString("Home"),
                                url: "https://aspnetzero.com?ref=abptmpl",
                                icon: "far fa-dot-circle"
                            )
                        ).AddItem(
                            new MenuItemDefinition(
                                "AspNetZeroFeatures",
                                new FixedLocalizableString("Features"),
                                url: "https://aspnetzero.com/Features?ref=abptmpl",
                                icon: "far fa-dot-circle"
                            )
                        ).AddItem(
                            new MenuItemDefinition(
                                "AspNetZeroPricing",
                                new FixedLocalizableString("Pricing"),
                                url: "https://aspnetzero.com/Pricing?ref=abptmpl#pricing",
                                icon: "far fa-dot-circle"
                            )
                        ).AddItem(
                            new MenuItemDefinition(
                                "AspNetZeroFaq",
                                new FixedLocalizableString("Faq"),
                                url: "https://aspnetzero.com/Faq?ref=abptmpl",
                                icon: "far fa-dot-circle"
                            )
                        ).AddItem(
                            new MenuItemDefinition(
                                "AspNetZeroDocuments",
                                new FixedLocalizableString("Documents"),
                                url: "https://aspnetzero.com/Documents?ref=abptmpl",
                                icon: "far fa-dot-circle"
                            )
                        )
                    )
                );
        }

        private static ILocalizableString L(string name)
        {
            return new LocalizableString(name, AbpLearnConsts.LocalizationSourceName);
        }
    }

好了,現在我們找到菜單定義的地方了,那麼我們如何去做動態菜單哪?

 

首先我們想一下需要什麼樣的動態菜單?

1.從數據庫加載,不從數據庫加載怎麼叫動態

2.可以根據不同Host(管理者)和Tenant(租戶)加載不同的菜單,不可能管理者和租戶看到的菜單全是一個樣子的吧!

3.可以根據不同的角色或者用戶加載不同的菜單(這個就牽扯到權限了,比如誰可以看到什麼,不可以看到什麼)

4.權限、按鈕最好和菜單相綁定,這樣便於控制

……

 

根據以上幾點,我們可以確定

1.必須要在用戶登錄之後加載出來的菜單才能符合條件

2.菜單需要建一個表(因為abp默認沒有單獨的菜單表),來進行存放

3.字段需要包含:菜單名,菜單與權限對應的名稱(用於動態權限),菜單對應的Url,Icon,級聯父Id,是否啟用,排序,租戶Id

4.需要對菜單進行編輯時,因為牽扯到多租戶,我們需要對多租戶定義一個標準的菜單,在添加租戶時,自動將標準菜單複製保存一份到新租戶中,所以我們需要對於菜單的進行區分,一般來說Host對應的數據行TenantId(int)都為null,我們可以將標準菜單的TenantId標為-1,已經分配保存的菜單TenantId為當前租戶Id,這樣便於區分和查詢

 

好了,讓我們開始寫動態菜單吧

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

Redis的內存和實現機制

1. Reids內存的劃分

  1. 數據 內存統計在used_memory中
  2. 進程本身運行需要內存 Redis主進程本身運行需要的內存佔用,代碼、常量池等
  3. 緩衝內存,客戶端緩衝區、複製積壓緩衝區、AOF緩衝區。有jemalloc分配內存,會統計在used_memory中
  4. 內存碎片 Redis在分配、回收物理內存過程中產生的。內存碎片不會統計在used_memory中。如果Redis服務器中的內存碎片已經很大,可以通過安全重啟的方式減小內存碎片:因為重啟之後,Redis重新從備份文件中讀取數據,在內存中進行重排,為每個數據重新選擇合適的內存單元,減小內存碎片。

2. Redis的數據存儲的細節

涉及到內存分配器jemalloc, 簡單動態字符串(SDS),5種值類型對象的內部編碼,redisObject,

  1. DictEntry: Redis 是key-value數據庫,因此對每個鍵值對都會有一個dictEntry,裏面存儲了指向Key和Value的指針;next指向下一個dictEntry,與本Key-Value無關
  2. Key: 並不是以字符串存儲,而是存儲在SDS結構中
  3. RedisObject: 5種值對象不是直接以對應的類型存儲的,而是被封裝為redisObject來存儲
  4. jemalloc: 無論是DictEntry對象,還是redisObject, SDS對象,都需要內存分配器

2.1 Jemalloc

redis 在編譯時便會指定內存分配器, 內存分配器可以是libc、jemalloc、tcmalloc

jemalloc作為Redis的默認內存分配器,在減小內存碎片方面做的相對比較好。jemalloc在64位系統中,將內存空間劃分為小、大、巨大三個範圍;每個範圍內又劃分了許多小的內存塊單位;當Redis存儲數據時,會選擇大小最合適的內存塊進行存儲。

2.2 RedisObject

redis對象的類型,內部編碼,內存回收,共享對象等功能都需要RedisObject的支持

typedef struct redisObject{
    unsigned type: 4;
    unsigned encoding: 4;
    unsigned lru: REDIS_LRU_BITS; /*lru time*/
    int refcount;
    void *ptr;
} robj;
  • type 字段 佔4bit 目前有5中類型, REDIS_STRING, REDIS_LIST, REDIS_HASH, REDIS_SET, REDIS_ZSET。 當執行type命令時,便是通過讀取redisObject對象的type字段獲取對象類型

  • encoding 佔4bit (表示對象的內部編碼),對於redis支持的每種類型,都有至少兩種內部編碼。通過object encoding命令,可以查看對象採用的編碼方式

    • 對於字符串,有int, embstr, raw 三種編碼。
    • 對於列表, 有壓縮列表和雙端列表兩種編碼方式,如果列表中元素較少,redis傾向於使用壓縮列表進行存儲,因為壓縮列表內存佔用少,而且比雙端鏈表可以更快載入;當列表對象元素較多時,壓縮列表就會轉化為更適合存儲大量元素的雙端鏈表。
  • lru 不同版本佔用內存大小不一樣,4.0版本佔用24bit,2.6版本佔用22bit

    • 記錄的是對象最後一次被命令程序訪問的時間,通過對比lru時間和當前時間,可以計算某個對象的空轉時間,object idletime命令可以显示該空轉時間 秒級別,改命令並不會改變對象的lru值,lru值除了通過object idletime命令打印之外,還與Redis的內存回收有關係:如果Redis打開了maxmemory選項,且內存回收算法選擇的是volatile-lru或allkeys—lru,那麼當Redis內存佔用超過maxmemory指定的值時,Redis會優先選擇空轉時間最長的對象進行釋放。
  • refcount 共享對象 記錄對象的引用計數,協助內存回收,引用計數可以通過 object refcount命令查看

    • ​ 共享對象的具體實現
    • Redis共享對象目前只支持整數值的對象。實際上是對內存和CPU時間的衡量。共享對象雖然會降低內存消耗,但是判斷兩個對象是否相等時需要消耗時間的。,對於整數值,判斷操作複雜度為O(1);對於普通字符串,判斷複雜度為O(n);而對於哈希、列表、集合和有序集合,判斷的複雜度為O(n^2)。
    • 雖然共享對象只能是整數值的字符串對象,但是5種類型都可能使用共享對象(如哈希、列表等的元素可以使用)。reids服務器在初始化時,會創建10000個字符串對象,值分別是0-9999的整數值。10000這個数字可以通過調整參數REDIS_SHARED_INTEGERS(4.0中是OBJ_SHARED_INTEGERS)的值進行改變
  • ptr 指針指向具體的數據 如 set hello world ptr指向包含字符串world的SDS

  • RedisObject對象大小16字節 4bit+4bit+24bit+4Byte+8Byte=16Byte

3. Redis內部數據結

3.1 SDS 簡單動態字符串

結構

struct sdshdr {
	int len;  // 記錄buf數組中已使用字節的數量 等於SDS所保存字符串的長度
    int free;  // 記錄buf數組中未使用的字節數量
    char buf[];
};
  1. SDS結構 佔據的空間:free+len+buf(表示字符串結尾的空字符串), 其中buf=free+len+1. 則總長度為4+4+free+len+1=free+len+9

  2. 與C字符串的比較

    在C字符串的基礎上加入了free和len字段,優勢

    • 獲取字符串長度: SDS O(1), C字符串是O(n)
    • 緩衝區溢出:使用C字符串的API時,如果字符串長度增加(如strcat操作)而忘記重新分配內存,很容易造成緩衝區的溢出;而SDS由於記錄了長度,相應的API在可能造成緩衝區溢出時會自動重新分配內存,杜絕了緩衝區溢出。
    • 修改字符串內存的重分配:對於C字符串,如果要修改字符串,必須要重新分配內存(先釋放再申請),因為如果沒有重新分配,字符串長度增大時會造成內存溢出,字符串長度減小時會造成內存泄漏。對於SDS, 由於記錄了len和free,因此解除了字符串長度和空間數組長度之間的關聯,可以在此基礎上進行優化:空間預分配(分配內存時比實際需要的多)使得字符串長度增大時重新分配內存的概率減小。惰性空間釋放策略 惰性空間釋放用於優化 SDS 的字符串縮短操作: 當 SDS 的 API 需要縮短 SDS 保存的字符串時, 程序並不立即使用內存重分配來回收縮短后多出來的字節, 而是使用 free 屬性將這些字節的數量記錄起來, 並等待將來使用。
    • 二進制安全 C 字符串中的字符必須符合某種編碼(比如 ASCII), 並且除了字符串的末尾之外, 字符串裏面不能包含空字符, 否則最先被程序讀入的空字符將被誤認為是字符串結尾 —— 這些限制使得 C 字符串只能保存文本數據, 而不能保存像圖片、音頻、視頻、壓縮文件這樣的二進制數據。
      SDS 的 API 都是二進制安全的(binary-safe): 所有 SDS API 都會以處理二進制的方式來處理 SDS 存放在 buf 數組裡的數據, 程序不會對其中的數據做任何限制、過濾、或者假設 —— 數據在寫入時是什麼樣的, 它被讀取時就是什麼樣。

    總結:

    • Redis 的字符串表示為 sds ,而不是 C 字符串(以 \0 結尾的 char*)。

    • 對比 C 字符串,sds 有以下特性:
      – 可以高效地執行長度計算(strlen);
      – 可以高效地執行追加操作(append);
      – 二進制安全;

    • sds 會為追加 操作進行優化:加快追加操作的速度,並降低內存分配的次數,代價是多佔用了一些內存,而且這些內存不會被主動釋放。

3.3 字典

在Redis中的應用:

  1. 實現數據庫鍵空間(key space) Redis 是一個鍵值對數據庫,數據庫中的鍵值對就由典保存:每個數據庫都有一個與之相對應的字典,這個字典被稱之為鍵空間(key space。
  2. 用作Hash類型鍵的其中一種底層實現

Redis 的 Hash 類型鍵使用以下兩種數據結構作為底層實現:

  1. 字典;
  2. 壓縮列表

3.3.1 字典的底層實現

實現字典的方法有很多種:

  • 最簡單的就是使用鏈表和數組,方式只適用於元素個數不多的情況
  • 兼顧高效和簡單性,使用哈希表
  • 追求更穩定的性能特徵,並且希望高效的實現排序操作,可以是用更為複雜的平衡樹

Reids選擇了高效且實現簡單的哈希表作為字典的底層實現。

/* dict.h/dict
* 字典
*
* 每個字典使用兩個哈希表,用於實現漸進式 rehash
*/

typedef struct dict {
    dictType *type;  // 特定於類型的處理函數
    void *privdata;  // 類型處理函數的私有數據
    dictht ht[2];   // 2個哈希表
    
    int rehashidx;  // 記錄rehash 進度的標誌, 值為-1  表示rehash未進行
    
    int iterators;   // 當前正在運作的安全迭代器數量
} dict;

注: dict類型使用了兩個指針分別指向兩個哈希表

其中,0號哈希表(ht[0])是字典主要使用的哈希表,而 1號哈希表(ht[1])則只有對0號哈希表進行rehash時才使用。

3.3.2 哈希表的實現

/*哈希表*/
typedef struct dictht {
    dictEntry **table;   // 哈希表節點指針數組(俗稱桶, bucket)
    unsigned long size;  //指針數組的大小
    unsigned long sizemask;   //指針數組的長度掩碼
    unsigned long used;   // 哈希表現有的節點數量
}dictht;
/*哈希表節點*/
typedef struct dictEntry {
    void *key;
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
    } v;
    
    // 鏈接後繼系節點
    struct dictEntry *next;
} dictEntry;

next 屬性指向另一個dictEntry結構, 多個dictEntry 可以通過next指針串連成鏈表dictht使用鏈地址法來處理鍵碰撞;當多個不同鍵擁有相同的哈希值時,哈希表用一個鏈表將這些鍵連接起來。

3.3.3 哈希碰撞

在哈希表實現中,當兩個不同的鍵擁有相同的哈希值時,我們稱這兩個鍵發生碰撞(collision),而哈希表實現必須想辦法對碰撞進行處理。字典哈希表所使用的碰撞解決方法被稱之為鏈地址法:這種方法使用鏈表將多個哈希值相同的節點串連在一起,從而解決衝突問題。

假設現在有一個帶有三個節點的哈希表:

對於一個新的鍵值對 key4 和 value4 ,如果 key4 的哈希值和 key1 的哈希值相同,那麼它們將在哈希表的 0 號索引上發生碰撞。

3.2.4 添加新鍵值對時觸發rehash操作?

對於使用鏈地址法來解決碰撞問題的哈希表 dictht 來說,哈希表的性能依賴於它的大小(size屬性)和它所保存的節點的數量(used 屬性)之間的比率:比率最好在1:1。

4. 跳躍表

跳躍表是一種隨機化數據結果,查找、添加、刪除操作都可以在對數期望時間下完成

跳躍表目前在Redis的唯一作用就是作為有序集類型的底層數據結構之一

Redis對跳躍表進行了修改包括:

  • score值可重複
  • 對比一個元素需要同時檢查它的score和member
  • 每個節點帶有高度為1層的後退指針,用於從表尾方向向表頭方向迭代

Redis 為什麼用跳錶而不用平衡樹?

4.1 skiplist與平衡樹、哈希表的比較

  • skiplist和各種平衡樹(如AVL、紅黑樹等)的元素是有序排列的,而哈希表不是有序的。因此,在哈希表上只能做單個key的查找,不適宜做範圍查找。所謂範圍查找,指的是查找那些大小在指定的兩個值之間的所有節點。
  • 在做範圍查找的時候,平衡樹比skiplist操作要複雜。在平衡樹上,我們找到指定範圍的小值之後,還需要以中序遍歷的順序繼續尋找其它不超過大值的節點。如果不對平衡樹進行一定的改造,這裏的中序遍歷並不容易實現。而在skiplist上進行範圍查找就非常簡單,只需要在找到小值之後,對第1層鏈表進行若干步的遍歷就可以實現。
  • 平衡樹的插入和刪除操作可能引發子樹的調整,邏輯複雜,而skiplist的插入和刪除只需要修改相鄰節點的指針,操作簡單又快速。
  • 從內存佔用上來說,skiplist比平衡樹更靈活一些。一般來說,平衡樹每個節點包含2個指針(分別指向左右子樹),而skiplist每個節點包含的指針數目平均為1/(1-p),具體取決於參數p的大小。如果像Redis里的實現一樣,取p=1/4,那麼平均每個節點包含1.33個指針,比平衡樹更有優勢。
  • 查找單個key,skiplist和平衡樹的時間複雜度都為O(log n),大體相當;而哈希表在保持較低的哈希值衝突概率的前提下,查找時間複雜度接近O(1),性能更高一些。所以我們平常使用的各種Map或dictionary結構,大都是基於哈希表實現的。
  • 從算法實現難度上來比較,skiplist比平衡樹要簡單得多。

Redis的對象類型和內部編碼

1. 字符串

1.1 內部編碼

  • int 8個字節的長整型。字符串值是整型時,這個值使用long整型表示
  • embstr <=39字節的字符串。embstr與raw都使用redisObject和sds保存數據,區別在於,embstr的使用只分配一次內存空間(因此redisObject和sds是連續的),而raw需要分配兩次內存空間(分別為redisObject和sds分配空間)。因此與raw相比,embstr的好處在於創建時少分配一次空間,刪除時少釋放一次空間,以及對象的所有數據連在一起,尋找方便。而embstr的壞處也很明顯,如果字符串的長度增加需要重新分配內存時,整個redisObject和sds都需要重新分配空間,因此redis中的embstr實現為只讀。
  • raw: 大於39個字節的字符串

1.2 編碼轉換

新創建的字符串默認使用 REDIS_ENCODING_RAW 編碼,在將字符串作為鍵或者值保存進數據庫時,程序會嘗試將字符串轉為 REDIS_ENCODING_INT 編碼, 字符串的長度不超過512MB

2. 列表

創建新列表時Redis默認使用REDIS_ENCODING_ZIPLIST編碼,當一下任意一個條件滿足時,列表會被轉換成REDIS_ENCODING_LINKEDLIST編碼:

  • 試圖往列表新添加一個字符串值,且這個字符串的長度超過sever.list_max_ziplist_value(默認值是64)
  • ziplist 包含的節點超過server.list_max_ziplist_entries(默認的值為512)

且編碼只可能由壓縮列錶轉化為雙端鏈表,一個列表可以存儲2^32-1個元素

2.1 壓縮列表

壓縮列表是Redis為了節約內存而開發的,由一系列特殊編碼的連續內存塊(而不是像雙端鏈表每個節點都是指針) 順序型數據結構;與雙端鏈表相比,壓縮列表可以節省內存空間,但是進行修改或增刪操作時,複雜度較高;因此當節點數量較少時,可以使用壓縮列表;但是節點數量多時,還是使用雙端鏈表划算。因為 ziplist 節約內存的性質,它被哈希鍵、列表鍵和有序集合鍵作為初始化的底層實現來使

2.2 雙端鏈表

typedef struct listNode {
    struct listNode *prev;  //前驅節點
    struct listNode *next;  // 後繼節點
    void *value;
} listNode;

typedef struct list {
    //表頭指針
    listNode *head;
    //表尾指針
    listNode *tail;
    unsigned long len; // 節點長度
    void *(*dup) (void *ptr);
    void (*freee)(void *ptr);
    int (*match) (void *ptr, void *key);
}list;

小結:

作為Reids列表的底層實現之一; 作為通用數據結構,被其他功能模塊使用。

  • 節點帶有前驅和後繼指針,訪問前驅節點和後繼節點的複雜度為 O(1) ,並且對鏈表
    的迭代可以在從表頭到表尾和從表尾到表頭兩個方向進行;
  • 鏈錶帶有指向表頭和表尾的指針,因此對錶頭和表尾進行處理的複雜度為 O(1) ;
  • 鏈錶帶有記錄節點數量的屬性,所以可以在 O(1) 複雜度內返回鏈表的節點數量(長
    度);

3. 哈希表

  • 當哈希表使用字典編碼時,程序將哈希表的鍵(key)保存為字典的鍵,將哈希表的值(value)保存為字典的值, 字典的鍵和值都是字符串對象

  • 壓縮列表編碼的哈希表

  • 編碼轉換

    默認使用ziplist編碼,當滿足以下條件時,自動切換為字典編碼

    • 哈希表中某個鍵或某個值的長度大於sever.hash_max_ziplist_value(默認值是64)
    • ziplist 包含的節點超過server.list_max_ziplist_entries(默認的值為512)

4. 集合

第一個添加到集合的元素,決定了創建集合時所使用的編碼:

  • 如果第一個元素可以表示為 long long 類型值(也即是,它是一個整數),那麼集合的初始編碼為 REDIS_ENCODING_INTSET 。
  • 否則,集合的初始編碼為 REDIS_ENCODING_HT 。

4.1 內部編碼

當使用 REDIS_ENCODING_HT 編碼時,集合將元素保存到字典的鍵裏面,而字典的值則統一設為 NULL

如果一個集合使用 REDIS_ENCODING_INTSET 編碼, 當滿足以下條件的時候會轉成字典編碼

  • intset保存的整數值個數超過server.set_max_intset_entries 默認值為512
  • 試圖往集合中添加一個新的元素,這個元素不能被表示為long, long類型,類型不一樣的時候使用字典

整數集合適用於集合所有元素都是整數且集合元素數量較小的時候,與哈希表相比,整數集合的優勢在於集中存儲,節省空間;同時,雖然對於元素的操作複雜度也由O(1)變為了O(n),但由於集合數量較少,因此操作的時間並沒有明顯劣勢。

5 .有序集合

有序集合與集合一樣,元素都不能重複;但與集合不同的是,有序集合中的元素是有順序的。與列表使用索引下標作為排序依據不同,有序集合為每個元素設置一個分數(score)作為排序依據

5.1 內部編碼

  • 壓縮列表

  • 跳躍表(skiplist)

    跳躍表是一種有序數據結構,通過在每個節點中維持多個指向其他節點的指針,從而達到快速訪問節點的目的。除了跳躍表,實現有序數據結構的另一種典型實現是平衡樹;大多數情況下,跳躍表的效率可以和平衡樹媲美,且跳躍表實現比平衡樹簡單很多,因此redis中選用跳躍表代替平衡樹。跳躍表支持平均O(logN)、最壞O(N)的複雜點進行節點查找,並支持順序操作。Redis的跳躍表實現由zskiplist和zskiplistNode兩個結構組成:前者用於保存跳躍表信息(如頭結點、尾節點、長度等),後者用於表示跳躍表節點

typedef struct zset {
    dict *dict;
    zskiplist *zsl;
} zset;

5.2 編碼轉換

對於一個 REDIS_ENCODING_ZIPLIST 編碼的有序集,只要滿足以下任一條件,就將它轉換為REDIS_ENCODING_SKIPLIST 編碼

  • ziplist所保存的元素數量超過服務器屬性server.zset_max_ziplist_entries值 默認值是128
  • 新添加元素的member的長度大於服務器屬性server.zset_max_ziplist_value 默認值是64

優化Redis 內存佔用

  1. 利用共享對象,可以減少對象的創建(同時減少了redisObject的創建),節省內存空間。目前redis中的共享對象只包括10000個整數(0-9999);可以通過調整REDIS_SHARED_INTEGERS參數提高共享對象的個數;例如將REDIS_SHARED_INTEGERS調整到20000,則0-19999之間的對象都可以共享。

    考慮這樣一種場景:論壇網站在redis中存儲了每個帖子的瀏覽數,而這些瀏覽數絕大多數分佈在0-20000之間,這時候通過適當增大REDIS_SHARED_INTEGERS參數,便可以利用共享對象節省內存空間

內存碎片率

mem_fragmentation_ratio=used_memory_rss (Redis進程佔據操作系統的內存(單位是字節))/ used_memory(Redis分配器分配的內存總量(單位是字節)).

如果內存碎片率過高(jemalloc在1.03左右比較正常),說明內存碎片多,內存浪費嚴重;這時便可以考慮重啟redis服務,在內存中對數據進行重排,減少內存碎片。

參考博文與書籍:

  1. 《redis設計與實現》
  2. Redis內存模型
  3. Redis 基礎操作 – 時間複雜度

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※產品缺大量曝光嗎?你需要的是一流包裝設計!

手動造輪子——基於.NetCore的RPC框架DotNetCoreRpc

前言

    一直以來對內部服務間使用RPC的方式調用都比較贊同,因為內部間沒有這麼多限制,最簡單明了的方式就是最合適的方式。個人比較喜歡類似Dubbo的那種使用方式,採用和本地方法相同的方式,把接口層獨立出來作為服務契約,為服務端提供服務,客戶端也通過此契約調用服務。.Net平台上類似Dubbo這種相對比較完善的RPC框架還是比較少的,GRPC確實是一款非常優秀的RPC框架,能跨語言調用,但是每次還得編寫proto文件,個人感覺還是比較麻煩的。如今服務拆分,微服務架構比較盛行的潮流下,一個簡單實用的RPC框架確實可以提升很多開發效率。

簡介

    隨着.Net Core逐漸成熟穩定,為我一直以來想實現的這個目標提供了便利的方式。於是利用閑暇時間本人手寫了一套基於Asp.Net Core的RPC框架,算是實現了一個自己的小目標。大致的實現方式,Server端依賴Asp.Net Core,採用的是中間件的方式攔截處理請求比較方便。Client端可以是任何可承載.Net Core的宿主程序。通信方式是HTTP協議,使用的是HttpClientFactory。至於為什麼使用HttpClientFactory,因為HttpClientFactory可以更輕鬆的實現服務發現,而且可以很好的集成Polly,很方便的實現,超時重試,熔斷降級這些,給開發過程中提供了很多便利。由於本人能力有限,基於這些便利,站在巨人的肩膀上,簡單的實現了一個RPC框架,項目託管在GitHub上https://github.com/softlgl/DotNetCoreRpc有興趣的可以自行查閱。

開發環境

  • Visual Studio 2019
  • .Net Standard 2.1
  • Asp.Net Core 3.1.x

使用方式

    打開Visual Studio先新建一個RPC契約接口層,這裏我起的名字叫IRpcService。然後新建一個Client層(可以是任何可承載.Net Core的宿主程序)叫ClientDemo,然後建立一個Server層(必須是Asp.Net Core項目)叫WebDemo,文末附本文Demo連接,建完這些之後項目結構如下:

Client端配置

Client端引入DotNetCoreRpc.Client包,並引入自定義的契約接口層

<PackageReference Include="DotNetCoreRpc.Client" Version="1.0.2" />

然後可以愉快的編碼了,大致編碼如下

class Program
{
    static void Main(string[] args)
    {
        IServiceCollection services = new ServiceCollection();
        //*註冊DotNetCoreRpcClient核心服務
        services.AddDotNetCoreRpcClient()
        //*通信是基於HTTP的,內部使用的HttpClientFactory,自行註冊即可
        .AddHttpClient("WebDemo", client => { client.BaseAddress = new Uri("http://localhost:13285/"); });

        IServiceProvider serviceProvider = services.BuildServiceProvider();
        //*獲取RpcClient使用這個類創建具體服務代理對象
        RpcClient rpcClient = serviceProvider.GetRequiredService<RpcClient>();

        //IPersonService是我引入的服務包interface,需要提供ServiceName,即AddHttpClient的名稱
        IPersonService personService = rpcClient.CreateClient<IPersonService>("WebDemo");

        PersonDto personDto = new PersonDto
        {
            Id = 1,
            Name = "yi念之間",
            Address = "中國",
            BirthDay = new DateTime(2000,12,12),
            IsMarried = true,
            Tel = 88888888888
        };

        bool addFlag = personService.Add(personDto);
        Console.WriteLine($"添加結果=[{addFlag}]");

        var person = personService.Get(personDto.Id);
        Console.WriteLine($"獲取person結果=[{person.ToJson()}]");

        var persons = personService.GetAll();
        Console.WriteLine($"獲取persons結果=[{persons.ToList().ToJson()}]");

        personService.Delete(person.Id);
        Console.WriteLine($"刪除完成");

        Console.ReadLine();
    }
}

到這裏Client端的代碼就編寫完成了

Server端配置

Client端引入DotNetCoreRpc.Client包,並引入自定義的契約接口層

<PackageReference Include="DotNetCoreRpc.Server" Version="1.0.2" />

然後編寫契約接口實現類,比如我的叫PersonService

//實現契約接口IPersonService
public class PersonService:IPersonService
{
    private readonly ConcurrentDictionary<int, PersonDto> persons = new ConcurrentDictionary<int, PersonDto>();
    public bool Add(PersonDto person)
    {
        return persons.TryAdd(person.Id, person);
    }

    public void Delete(int id)
    {
        persons.Remove(id,out PersonDto person);
    }

    //自定義Filter
    [CacheFilter(CacheTime = 500)]
    public PersonDto Get(int id)
    {
        return persons.GetValueOrDefault(id);
    }

    //自定義Filter
    [CacheFilter(CacheTime = 300)]
    public IEnumerable<PersonDto> GetAll()
    {
        foreach (var item in persons)
        {
            yield return item.Value;
        }
    }
}

通過上面的代碼可以看出,我自定義了Filter,這裏的Filter並非Asp.Net Core框架定義的Filter,而是DotNetCoreRpc框架定義的Filter,自定義Filter的方式如下

//*要繼承自抽象類RpcFilterAttribute
public class CacheFilterAttribute: RpcFilterAttribute
{
    public int CacheTime { get; set; }

    //*支持屬性注入,可以是public或者private
    //*這裏的FromServices並非Asp.Net Core命名空間下的,而是來自DotNetCoreRpc.Core命名空間
    [FromServices]
    private RedisConfigOptions RedisConfig { get; set; }

    [FromServices]
    public ILogger<CacheFilterAttribute> Logger { get; set; }

    public override async Task InvokeAsync(RpcContext context, RpcRequestDelegate next)
    {
        Logger.LogInformation($"CacheFilterAttribute Begin,CacheTime=[{CacheTime}],Class=[{context.TargetType.FullName}],Method=[{context.Method.Name}],Params=[{JsonConvert.SerializeObject(context.Parameters)}]");
        await next(context);
        Logger.LogInformation($"CacheFilterAttribute End,ReturnValue=[{JsonConvert.SerializeObject(context.ReturnValue)}]");
    }
}

以上代碼基本上完成了對服務端業務代碼的操作,接下來我們來看如何在Asp.Net Core中配置使用DotNetCoreRpc。打開Startup,配置如下代碼既可

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<IPersonService, PersonService>()
        .AddSingleton(new RedisConfigOptions { Address = "127.0.0.1:6379", Db = 10 })
        //*註冊DotNetCoreRpcServer
        .AddDotNetCoreRpcServer(options => {
            //*確保添加的契約服務接口事先已經被註冊到DI容器中

            //添加契約接口
            //options.AddService<IPersonService>();

            //或添加契約接口名稱以xxx為結尾的
            //options.AddService("*Service");

            //或添加具體名稱為xxx的契約接口
            //options.AddService("IPersonService");

            //或掃描具體命名空間下的契約接口
            options.AddNameSpace("IRpcService");

            //可以添加全局過濾器,實現方式和CacheFilterAttribute一致
            options.AddFilter<LoggerFilterAttribute>();
        });
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        //這一堆可以不要+1
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        //添加DotNetCoreRpc中間件既可
        app.UseDotNetCoreRpc();

        //這一堆可以不要+2
        app.UseRouting();

        //這一堆可以不要+3
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapGet("/", async context =>
            {
                await context.Response.WriteAsync("Server Start!");
            });
        });
    }
}

以上就是Server端簡單的使用和配置,是不是感覺非常的Easy。附上可運行的Demo地址,具體編碼可查看Demo.

總結

    能自己實現一套RPC框架是我近期以來的一個願望,現在可以說實現了。雖然看起來沒這麼高大上,但是整體還是符合RPC的思想。主要還是想自身實地的實踐一下,順便也希望能給大家提供一些簡單的思路。不是說我說得一定是對的,我講得可能很多是不對的,但是我說的東西都是我自身的體驗和思考,也許能給你帶來一秒鐘、半秒鐘的思考,亦或是哪怕你覺得我哪一句話說的有點道理,能引發你內心的感觸,這就是我做這件事的意義。最後,歡迎大家評論區或本項目GitHub下批評指導。

歡迎掃碼關注 本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

※產品缺大量曝光嗎?你需要的是一流包裝設計!