加州野火燒掉6個紐約市 氣候轉涼有助控制

摘錄自2020年8月25日世界日報、26日中央社報導

加州全境過去一周遭逢650場野火侵襲,許多火災源自15日以來至少1萬2000次閃電。美國有線電視新聞網(CNN)報導,加州境內野火已燒掉50萬公頃土地,比六個紐約市還大,摧毀1400棟建築物。加州23日出現數百場林火,近25萬人收到撤離令和警告,死亡人數增至七人。

規模最大的兩處野火分別為聖荷西以東的「SCU閃電綜合大火」(SCU Lightning Complex Fire),以及灣區北部的「LNU閃電綜合大火」(LNU Lightning Complex Fire),各燒掉14萬6900公頃和14萬2813公頃土地,名列有紀錄以來前三大野火。

聖他克魯兹大火是三場複合大火之一,這三起群火全都因閃電而起,發生在舊金山灣區地帶。而受到強風影響,消防人員救災進度緩慢,甚至一度因為不尋常溫暖天氣暫停。今天(26日)天氣轉涼,有助消防人員對抗州內野火。

路透社報導,較高的相對濕度和較為和緩的風勢,讓逾1萬4000名打火弟兄得以闢出防火線。加州森林防火廳救火隊長布倫頓(Mark Brunton)針對聖克魯斯(Santa Cruz)以北一處火場表示:「天氣真的很配合。我們正在穩定取得大量新資源。」

以往不受野火影響的沿海雨林地區,如今也不尋常地爆出大火,部分當局將矛頭指向氣候變遷。州內史上五大野火,有四場是在過去三年發生,最大一場是2018年的「門多西諾複合大火」(Mendocino Complex fire),燒焦了18萬5800公頃的土地。

氣候變遷
國際新聞
加州
美國
森林火災

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

最低7.98萬起 本周的新款SUV都高端的嚇人!

最後才是產品收益相較過去應有更大的改善。在採訪環節的開頭,李春榮說:“今年是東風乘用車九年了,明年翻過去就是十年,我們經常說十年磨一劍,這個用在我們東風風神身上非常貼切。”可以看出在過去近十年的時間里,東風風神為產品質量打下了紮實穩固的基礎。

2017年第一周,不少廠家選擇了在這周時間內搶先發布新款車型。這一周新上市的車型僅有幾款,但都是值得考慮的貨色!其中最賣力的要數東風本田!

12月29日,武漢正寒風凜凜,而此時東風風神卻熱情絲毫不減。當天受邀參加國家信息中心、搜狐汽車共同舉辦的“中國本土品牌創新之旅”,來到了位於武漢的東風風神廠區,共同見證“2016年度第15萬台整車發運暨第10萬台發動機下線”儀式。而這,也可視為東風乘用車公司為“十三五”開局之年畫上了一個圓滿的句號。

在過去的11個月,東風乘用車公司累計銷售超13萬輛,而現今第15萬台整車發運,意味着東風風神將邁上年銷15萬輛台階,同比增長速近50%。從銷量數據上看,東風風神AX7、AX5、AX3等組成的SUV軍團貢獻最大,合計銷量超過十萬輛,其中明星車型東風風神AX7穩步站上月銷7000輛台階,並向月銷過萬挺進。另外在海外市場也作出了好成績,年銷量超1.5萬輛,同比增長近4倍,成績斐然。

隨着東風風神在技術研發以及生產製造體系的不斷投入,東風風神已經形成完善的發動機製造價值鏈。如今,東風風神的發動機銷量佔東風公司自主發動機整體銷量近50%,成為公司最大的自主品牌發動機製造陣地。目前,東風風神研發生產的發動機主要分為A、B、C三個系列平台,其中A系列包含A16、A14T等型號,而A14T正是東風風神目前主打的1.4T渦輪增壓發動機,在未來的規劃中A系列平台也將會以1.4L為主進行研發。

李春榮表示,要當中國品牌的主力軍,得有幾個方面,首先產品質量要好,質量不好就是“游擊隊”,這一點在AX5的誕生地東風風神常州工廠有所體現,具備國際一流水平的自動化生產線和智能生產管理系統,工廠的生產標準與設備均向合資品牌東風日產看齊。其次是銷量規模,目前東風乘用車在中國汽車市場排名第31位,自主品牌排名第17位,李春榮坦言道明年銷量要做到20萬輛。再者是產品構架,東風風神要成為主力軍,明年將要在轎車、SUV、高端轎車、新能源車、海外市場5個方面均衡發展。最後才是產品收益相較過去應有更大的改善。

在採訪環節的開頭,李春榮說:“今年是東風乘用車九年了,明年翻過去就是十年,我們經常說十年磨一劍,這個用在我們東風風神身上非常貼切。”可以看出在過去近十年的時間里,東風風神為產品質量打下了紮實穩固的基礎。這裏用“博觀而約取,厚積而薄發”這句話來形容目前的東風乘用車是最合適不過了。

俗話說,40不惑,50知天命。作為成立48年的大型汽車企業,東風就像一個成熟穩重的中年人,沒有更多刻意追求數據的漂亮,而是着重穩定可靠和節油。對汽車企業來說,質量是最本質的東西。“我們已經是中國品牌的生力軍,接下來明年我們希望成為中國品牌的主力軍。”東風乘用車總經理李春榮說道。

久違的重逢,路虎發現神行安吉之旅

“遙憐十景試春遊,東嶺迢迢一徑幽。記得碧門村口去,籃輿輕度到杭州”所形容正是此時此刻的場景,駕駛全新路虎發現神行踏上了前往安吉的路上到處都是鬱郁蔥蔥的竹林、陽光透過竹恭弘=叶 恭弘灑在馬路上、竹恭弘=叶 恭弘沙沙作響。

“發現神行”作為路虎在國內投放的一款重磅中型SUV,自上市之初便飽受關注。無奈當時進口版過高的售價使得許多喜歡它的消費者都望而卻步,而在與奇瑞捷豹路虎“聯婚”后價格比起進口版直接下降十多萬,這可讓很多崇尚路虎品牌的車友們對它產生了極大的興趣。那麼國產之後的路虎又可否保持與進口版同樣的出色品質呢,讓來帶你們一探究竟。

身為一台路虎,與生俱來的越野能力毋庸置疑,但更多的城市用戶購買SUV只是為了更好的都市駕乘,在鋪裝路面的駕駛神行同樣為我們交上了一份滿意的答卷。全時四驅系統,能根據道路的實際狀況,在兩驅以及四驅之間自由切換,當檢測到到前輪打滑時它能迅速將動力分配到後輪,讓車輛在雨天濕滑路面擁有更好的循跡性。

而更值得一提的當屬是路虎家族名聲在外的全地形反饋適應系統了,只需簡單點擊按鈕即可在普通、草地-砂礫-雪地、泥濘-車轍、沙土、岩石五大駕駛模式中任意切換,讓駕駛員不必具有專業技術,也可以輕鬆應對各種路況。

“發現神行”是奇瑞捷豹路虎的第二款車型,一輛擁有優異的公路性和同級最強的四驅系統的七座中型SUV,通過大量的高科技配置和先進的技術應用,為此次安吉之旅保駕護航。

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

【其他文章推薦】

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

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

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

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

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

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

參加北汽幻樂會周年慶典,論優秀的品牌文化如何誕生?

對於這些原本素未謀面的車主,通過一次有趣的幻樂會活動之後大家便成了好朋友,一起玩耍一起交流幻速帶來的歡樂。而在活動晚宴上,廠家眾多大人物齊聚現場也是讓車主們驚訝,而北汽幻速也正式公布了幻樂會粉絲新名稱:“可樂”,譜寫了幻樂會的全新篇章。

就在2017年元旦的時候,小編受邀參加北汽幻速幻樂會的活動,說白了這就是一個由廠家組織的讓一些車主與幻速粉絲等人一起慶祝元旦並且參与遊戲娛樂的活動,有的人可能會疑惑,不去老實試車,參加車主聚會幹什麼?

其實對於一個優秀的品牌來說,用戶與企業之間的溝通十分重要,就像國內某車企總是說他們的產品是廣泛徵集廣大基盤用戶的意見后打造的一樣,其實對於幻速來說,也是一樣的道理,聽取用戶的聲音才是正確的發展方向。

因此除了賣車之外,車企要做的更多,比如建立一個優秀的品牌文化,這也是難度不遜色於造出大賣產品的工程,寶馬費盡了力氣也沒能改變它在國內就是暴發戶用車的形象,而親民的幻速在品牌文化打造上顯然更加偏向於互動性質。

就拿這次的幻樂會周年慶典來說,來自全國各地的將近上百位的車主粉絲聚集在珠海,元旦當天車主們分成7隊,加上1隊媒體組的朋友們一起參加團隊挑戰項目,刺激的過山車、垂直極限等項目需要隊友們齊心協力完成,在經過了幾個小時的挑戰之後,車主們順利完成挑戰,贏得豐厚獎品;對於這些原本素未謀面的車主,通過一次有趣的幻樂會活動之後大家便成了好朋友,一起玩耍一起交流幻速帶來的歡樂。

而在活動晚宴上,廠家眾多大人物齊聚現場也是讓車主們驚訝,而北汽幻速也正式公布了幻樂會粉絲新名稱:“可樂”,譜寫了幻樂會的全新篇章。

北汽幻速擅長製造熱銷產品是大家有目共睹的,幻速品牌推出幾年的時間累計銷量直逼60萬,2015年12月31日幻樂會成立,短短的一年內幻樂會成為了國內十分少見的優秀官方車友會,而眾多企業高層出現在幻樂會也證明了幻樂會在幻速的地位。

縱觀國內車市,大家幾乎都沉浸在努力賣車的工作中,真的願意去花費這麼大的精力打造一個粉絲圈子,組織粉絲車友自駕/遊玩等活動的少之又少,而幻速就是其中一個,從長遠的角度看這樣的活動是十分有助於建立良好的品牌形象的,這也是十分利於企業發展的一個舉措,只有幻速這種高瞻遠矚的戰略眼光才能夠做出這樣的抉擇。因此有什麼不看好幻速的理由呢?本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

這款中型SUV樣子“值”幾十萬 卻只賣10.49萬起

內飾的按鍵比較少,檔把區域質感出色,整體還是蠻不錯的。前期上市的車型只有1。8T發動機+5擋手動變速箱的動力組合,喜歡自動擋的可以再等等,這台1。8T發動機來頭可不小了,最大功率130千瓦,最大扭矩245牛米。這台1。8T發動機代號TN4G18T,來自銅陵銳展科技有限公司,銅陵銳展科技有限公司由鐵牛集團控股,鐵牛集團正是眾泰的大股東。

又是周一,又是美好的一星期開始,我愛工作,工作使我充實((ง •̀_•́)ง)

話說小編美美一大早看新聞就看到一對奇葩,兩夫妻去迪拜淘金,付出6萬賺回37塊,小編美美想說,咱們國家這麼好,何必去國外淘金?我們有12萬買得到的的“路虎極光/保時捷卡宴”,還有7萬塊的“途觀”8萬塊的“奧迪Q3”,其它國家有嗎?

這不,今天又有一件大事發生了,那就是大邁X7預售價公布,廢話不多說,先看看售價↓↓↓

1.8T+5MT 精英型 10.49萬

1.8T+5MT 豪華型 11.69萬

1.8T+5MT 尊貴型 12.49萬

1.8T+5MT 至尊型 13.09萬

這時候有人就要說了,大邁X7是什麼車?

大邁X7是眾泰推出的一款中型SUV,它最大的特點便是:外觀和大眾Cross Coupe GTE十分相似,好吧,真相是這樣的。

大眾在2015年1月的北美車展上推出Cross Coupe GTE概念車,並計劃於2016年實現量產,當然2016年我們沒有見到它,卻被手拿皮尺的眾泰搶了先機,眾泰於2016年8月發布大邁X7,同年10月份大邁X7正式下線,還沒來得及量產的大眾SUV卻被眾泰山寨並且量產,大眾表示哭暈在廁所。

既然是山寨大眾Cross Coupe GTE的造型,那麼造型上大邁X7肯定是十分有特點了。大邁X7的長寬高尺寸為4736/1942/1672mm,軸距為2850mm,4736mm的長度卻有着2850mm的軸距,2850mm的軸距在SUV中屬於非常長的類型,保證了內部空間,而寬度1942mm的大邁X7相信在橫向空間上也不錯。

外觀設計上大邁X7和大眾概念車的相似度達到90%,整車設計上趨向平直,高窗線小側窗的設計比較有動感,但是這個外觀比較偏向於大叔一點,鍍鉻裝飾使用比較多。大邁X7全系標配19英寸輪圈,這也是比較下本了~

在內飾上大邁X7和大眾Cross Coupe GTE也十分相似,大邁X7在內飾上也是平直設計,木紋的使用量非常大,由此看來大邁X7定位和外觀一樣更加偏向年長一些的消費者。內飾的按鍵比較少,檔把區域質感出色,整體還是蠻不錯的。

前期上市的車型只有1.8T發動機+5擋手動變速箱的動力組合,喜歡自動擋的可以再等等,這台1.8T發動機來頭可不小了,最大功率130千瓦,最大扭矩245牛米。這台1.8T發動機代號TN4G18T,來自銅陵銳展科技有限公司,銅陵銳展科技有限公司由鐵牛集團控股,鐵牛集團正是眾泰的大股東。這台發動機也在眾泰Z700上使用,不過在眾泰的銷量擔當SUV上使用,這在小編美美印象中還是第一次。

其實說這麼多只是告訴你,相比之前購買上汽和三菱的發動機,現在眾泰用上自己的發動機了。而前期沒有自動擋,估計也是自動擋的匹配還沒做好~

作為一台眾泰,配置不多怎麼行,大邁X7標配車內氛圍燈、一鍵啟動/無鑰匙進入、电子手剎、後排出風口等配置,當然10.49萬的手動精英型沒有配備ESp這種事我也不會隨便告訴你的。

最後小編美美來回答幾個大家最關心的問題:

1、大邁X7是一款什麼樣的車?

一款中型SUV,定位偏向年長消費者,外觀靚麗尺寸大空間大配置高的車。

2、大邁X7預售價貴嗎?

手動車型10.49萬還沒有配備ESp,小編美美覺得略貴,不過要是你覺得買了個大眾概念車,那也不錯哇~

3、推出自動擋的話,估計多少錢?

預售價來看,大邁X7是比眾泰SR9便宜一丟丟的,加上發動機的規格比SR9略低,所以自動車型的售價肯定也比SR9略低,因此小編美美估計自動擋的大邁X7會12.89萬起。

總結:

大邁X7其實是眾泰車系中定位比較高的車型了,目前眾泰SUV主要有入門的大邁X5和SR7,中端的眾泰T600以及高端的SR9和大邁X7,SR9主打年輕人的市場,而大邁X7主打年長的消費者也就順理成章了,動力規格不高,缺乏自動擋的大邁X7在動態上的表現讓小編美美沒什麼期待,但是想想配置高空間大外觀靚麗,大邁X7的銷量估計也不成為題。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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

合資小型SUV中的銷量冠軍 顏值高油耗低車主們滿意嗎

28萬車主點評:外觀給人非常時尚前衛的感覺,圓潤飽滿緊湊感十足,配置上將將夠用吧,自動駐車和上坡輔助還是蠻好用的,然後就是令人驚訝的乘坐空間了,堪比一些緊湊級SUV了,家裡人對這車的空間都好評,1。8L的動力很足,隨叫隨到,加上CVT變速箱很平順,駕駛感受很舒服,就是懸挂有點硬。

前言

最近很多粉絲後台都有留言,其頻率最高的為15萬買什麼SUV好,的確這個價位是目前很多消費者所最能接受的價位,而且要求就是省油、耐用、空間大,那麼綜合考慮就只有本田家的繽智和XR-V了,雖然出自同一平台,但是XR-V的銷量一直要比繽智要好,那車主們當初為什麼選擇它呢!

東風本田-本田XR-V

指導價:12.78-16.28萬

車主:扳手小歐歐

購買車型:2015款 1.5L LXi CVT經典版

裸車價格:13.20萬

車主點評:一眼望去最滿意的首先是它的外觀,顏值比較高,看着很順眼,但進到裏面的內飾的塑料感就比較強了,而且配置比較低;但是日常代步很滿意的一點是它的油耗和動力,非常的省心,還有就是空間真的沒話說,坐上去感覺非常寬敞,比較完美的一輛家用車。

我是7月份提的車,目前行駛里程4500公里,市區油耗7.5L,高速油耗6L左右,用車成本相當低

車主:愛吃才會贏

購買車型:2015款 1.8L EXi CVT舒適版

裸車價格:14.28萬

車主點評:外觀給人非常時尚前衛的感覺,圓潤飽滿緊湊感十足,配置上將將夠用吧,自動駐車和上坡輔助還是蠻好用的,然後就是令人驚訝的乘坐空間了,堪比一些緊湊級SUV了,家裡人對這車的空間都好評,1.8L的動力很足,隨叫隨到,加上CVT變速箱很平順,駕駛感受很舒服,就是懸挂有點硬。

9月份提的車,現在4100公里,多數是市區開,油耗7L左右,還是很滿意的,我是開車比較溫柔的那種。

車主:我在東北玩泥沙

購買車型:2015款 1.8L VTi CVT豪華版

裸車價格:15.38萬

車主點評:對於XRV這輛車各方面都很滿意的,外觀很耐看,之所以上頂配,是為了發動機啟停、自動空調、無鑰匙啟動/進入、定速巡航等配置,空間上簡直就是無敵,1.8米的大胖子坐在後排也表示沒壓力,加速很線性,油門反應很靈敏,轉向也足夠精準,儲物空間夠豐富,日常使用很方便。

6月份提的車,跑了1萬公里了,平均油耗8L,表現還是相當不錯的,動力和油耗比較均衡。

總結:說起本田,除了發動機牛逼以外,還有就是它在空間處理上值得稱道了,乘坐空間非常寬敞,後排魔術座椅在空間擴展性上增加了不少分數,1.5L地球夢發動機動力強,油耗低,用車成本方面很有優勢。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

依舊高級感十足,為什麼教授會更喜歡這款11萬的手動擋RX5?

親自感受了一番榮威RX5的6MT之後,想用一個字去形容——爽,首先是換擋行程短,換擋時候手臂動作不大,其次是掛擋手感非常乾脆,不會有那種油膩的黏黏糊糊的感覺。離合器的腳感很輕,不會是很彈腳的那種,這種設定對於經常在市區走走停停的路況會比較好,確實能有效減輕左腳的負擔。

有些事情總是在不知不覺中發生,就在幾年前,大多數消費者的購車預算還停留在十萬元內,不管是國產車還是合資車,大家選擇的大多都是手動擋車型,然而到了2017的今天,不知不覺中手動擋已經淡出我們的視線。

應該有不少人都覺得現在買個手動擋就是掉檔次,和親戚朋友聊天時問起都不好意思說出來,但是試駕完這台SUV界網紅——榮威RX5 20T 手動旗艦版,真心覺得手動擋也很有面子。

外觀與高配車型差別不大,很良心

大家先不要着急,好戲在後頭,雖然大家都已經對榮威RX5非常熟悉,但還是要先來回顧一下它的外觀內飾,因為前不久剛剛試過了榮威RX5的30T車型,所以對這次試駕的榮威RX5 20T 手動旗艦版有一些獨特的見解。

先來看看外觀上最大的“亮點”,這次試駕的20T 手動旗艦版配備了遠近光一體帶透鏡鹵素大燈,大燈燈組內集成了燈帶式的LED日間行車燈,造型相當好看,非常亮眼,順帶提一句,20T 手動旗艦版可以選裝全LED大燈,效果非常驚艷,強烈推薦准車主選裝這個配置。

作為整個RX5車系中的中低配車型,全LED大燈需要選裝其實很正常,如果你覺得稍有遺憾的話,不要緊,因為那個吸取了小提琴元素,點亮后辨識度非常高的尾燈還是在的,每次看到RX5都會忍不住看多兩眼。

榮威RX5能夠在短時間內突破兩萬輛的銷量,其“律於形而動於心”的律動設計理念有很大的功勞,車身多處使用了0.618的黃金分割比例,最終呈現出一個非常精悍的造型,這也是榮威RX5和競爭對手們的區別,一點都不顯得臃腫,而是精神秀氣。

內飾簡約大氣,中控操作便利

一坐進車內,那個互聯網車型獨有,並且讓無數消費者津津樂道的10.4英寸中控大屏不見了,取而代之的是一塊8英寸的屏幕,不要以為屏幕大就是好用,只能說大家都有各自的特點,20T 手動旗艦版配備的這個8英寸屏幕,功能齊全,導航、藍牙電話、Apple Carplay和百度CarLife等等一應俱全。

看到中控屏幕下方的空調操作面板,非常欣慰,如果你和一樣覺得在觸控屏上調節空調不方便的話,那麼你一定更加喜歡這個實體操作面板,熟悉之後能夠在行車當中進行盲操作,除此之外,整个中控台按鍵、旋鈕的裝配工藝和阻尼感都非常出色,在同級別當中處於領先水準。

好開順手,懸架支撐性強

終於到了最激動人心的部分,到底這個6擋手動變速箱的表現如何了,到底值不值得買呢?親自感受了一番榮威RX5的6MT之後,想用一個字去形容——爽,首先是換擋行程短,換擋時候手臂動作不大,其次是掛擋手感非常乾脆,不會有那種油膩的黏黏糊糊的感覺。

離合器的腳感很輕,不會是很彈腳的那種,這種設定對於經常在市區走走停停的路況會比較好,確實能有效減輕左腳的負擔。因為配備的1.5T發動機扭矩平台足夠低和寬廣,離合器和變速箱的結合匹配寬容度很高,都很容易上手,各個擋位間切換順滑。缺點也是有的,美中不足的地方是離合器的結合點模糊,起步時空擋掛入1擋鬆開離合過程中結合點不好找,一不留神就容易憋熄火,還好憑藉多年老司機臨床經驗,基本上靜下心來試個三兩次就好了。1.5T渦輪增壓發動機的表現也是很盡職,如果你第一次接觸它,不跟你說它是渦輪增壓發動機的話,你一定會以為是自然吸氣發動機。

發動機與渦輪的噪音控制讓感到驚喜,發動機轉速在2700轉內,發動機艙傳來的聲音不會打擾到乘客。另外發動機動力輸出平順,日常行車狀態下絕對夠用,得益於6擋手動變速箱,高速巡航時的燃油經濟性表現很好,100km/h時速下發動機轉速為2000轉,120km/h的時候是2500轉,儀錶盤還會根據當前擋位和踩油門的深度給出ECO節油指示。

榮威RX5的底盤懸架調校也是比較喜歡的,過彎時候的支撐性很好,側傾控制出色,讓駕駛員很有信心,與此同時,還能夠做到對路面的坑窪顛簸進行有效過濾,保證良好的舒適性,再加上方向盤轉向手感適中,整個駕駛體驗比的預想要好不少,感覺它的實力已經超出了它11.98萬的指導價,性價比高。

有話說

其實喜歡手動擋的大有人在,但是為什麼大家最後都選擇了自動擋車型,估計不是因為不顯檔次就是因為開起來不順手,雖然這次試駕的榮威RX5的20T 兩驅手動旗艦版還沒有達到完美,但是並不妨礙它成為一輛好車,一輛值得推薦的車。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

過年回家好多人買了哈弗和眾泰,到底該不該也買一輛?

再舉個生動栗子,的朋友陳二狗是一個下水道維修人員,經過幾年的艱苦工作,終於存夠錢買車了,但是不知道買什麼車。然鵝看到了隔壁的老王買了一台新車,外觀挺好看的,坐進去看起來配置也不錯,問一下價格竟然還不貴。

春節回家過年,是我們中國人傳統的習俗。過年除了回家探望親人,最大的目的還是在“炫耀”上,中國人都存在一種攀比的心理,比誰混得更好是春節回家的一個必備項目。汽車是一件“奢飾品”,作為身份的象徵,不少人都會在過年前買一台小汽車,開回老家彰顯身份。

也正是有這樣的“攀比”心理,導致中國人買車變得極度不理性。說實話“沒主見”是最主要的因素之一。因為購車所需要考慮的因素很多,不管你對車輛熟不熟悉,到了真正購車時,購車前所有的準備都煙消雲散,到了4S店你也只會 一味地跟着銷售的套路走下去。

為什麼消費者的購車思想都不理性?

影響消費者買車的因素有很多,像車型的品牌、車型的顏值、車型的配置、汽車的售價/優惠幅度、提車時間等,另外消費者們還有一個最為致命的共通點:就是跟風消費。

舉個栗子,就像哈弗H6那樣,每個月都有幾萬的銷量,一年能賣上幾十萬台的車型,難道就真的這麼多人買?那是肯定的,就好比國家在發展,人們的生活質量日益提高,所以不少家庭都開始將購車的計劃提上了議程。相對於一線大城市,在汽車保有量方面已經趨向於飽和甚至是過剩的狀態,所以目前銷售主力主要集中在二三線甚至是更偏的城市,而其中絕大部分的人都是首次購車的消費者。

其實大部分人都是和一樣的普通人,如果不是專業做汽車,也不知道買什麼車才是最好的選擇。對於自己本身來說,大多都是靠自己的努力辛辛苦苦存錢買車,很重視汽車的價格以及優惠幅度。也可以這樣說,如果是比較極端的消費者,在15萬以內的購車預算,兩個相差不大的競品車型,也許是因為某個車型有更多的優惠、價格更低。於是選擇了這台車。

再舉個生動栗子,的朋友陳二狗是一個下水道維修人員,經過幾年的艱苦工作,終於存夠錢買車了,但是不知道買什麼車。然鵝看到了隔壁的老王買了一台新車,外觀挺好看的,坐進去看起來配置也不錯,問一下價格竟然還不貴。再過了两天同村的老李又買了輛一模一樣的車,哇!同村這麼多人都買這車,質量肯定可以了,於是二狗高高興興地又買了一輛,於是他們愉快地創辦了二貨村殺馬特車友會。

其實大家都一樣,往往容易被表面迷惑,買車大多是看外觀數配置,這也是目前眾多車型品牌在追求的一個東西,外觀越來越大氣、配置越來越高,價格也越來越低了。這也是為什麼能夠火了部分喜歡“山寨”的品牌,譬如最近新出的保時泰SR9,10多萬的售價竟然有百萬級SUV的外觀,不僅如此連內飾的設計都有極高的相似度。當然消費者對於保時泰SR9的熱情是非常高的,關鍵是逼格夠高。還得說一下,能不能開上蘭博基尼就得看眾泰了。

只不過買這些車的人在選擇的時候壓根就沒有想過這車的質量到底行不行,至於眾泰這個品牌的質量能不能過關?還是建議去看一下車主的口碑,不是說質量不行,只是車型整體故障率還是比較高的。不過這類山寨車型還有一個好處呀,我還能剩下一大筆的設計費呀!

看得見的地方給到你逼格十足的感覺,但是在看不到不知道的地方就給你各方面的減配,你覺得這樣真的好嗎?為了降低成本,同一個部位的零件,哪個供應商便宜選哪個,就算質量差一點也是沒所謂,這樣的行為見得太多了!當然,這樣的行為不僅出現在國產品牌中,更有出現在合資品牌上。不過厚道的汽車廠家不少,只是消費者們不懂得識別,這也是目前我國消費者在購車方面最大的囧況。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

UniRx精講(一):UniRx簡介&定時功能實現

1.UniRx 簡介

UniRx 是一個 Unity3D 的編程框架。它專註於解決時間上異步的邏輯,使得異步邏輯的實現更加簡潔和優雅。

簡潔優雅如何體現?

比如,實現一個“只處理第一次鼠標點擊事件”這個功能,使用 UniRx 實現如下:

Observable.EveryUpdate()
			.Where(_ => Input.GetMouseButtonUp(0))
			.First()
			.Subscribe(_ => { // do something   });

代碼做的事情很簡單:

  1. 開啟一個 Update 的事件監聽。
  2. 每次 Update 事件被調用時,進行鼠標是否抬起的判斷。
  3. 如果判斷通過,則進行計數,並且只獲取第一次點擊事件。
  4. 訂閱/處理事件。

如果在 Unity 中,使用傳統的方式實現如上功能,首先要創建一個成員變量來記錄點擊次數/是否點擊過,然後在腳本中創建一個 Update 方法來監聽鼠標抬起的事件。

如果在 Update 方法中,除了實現鼠標事件監聽這個功能之外,還要實現其他的功能。那麼 Update 里就會充斥着大量的狀態判斷等邏輯。代碼非常不容易閱讀。

而 UniRx 提供了一種編程思維,使得平時一些比較難以實現的異步邏輯(比如以上這種),使用 UniRx 輕鬆搞定,並且不失代碼的可讀性。

當然 UniRx 的強大不僅僅如此。

它還可以:

  • 優雅實現 MVP(MVC)架構模式。
  • 對 UGUI/Unity API 提供了增強,很多需要寫大量代碼的 UI 邏輯,使用 UniRx 優雅實現。
  • 輕鬆完成非常複雜的異步任務處理。
  • ……

最最重要的是,它可以提高我們的編碼效率,同時還給我們的大腦提供一個強有力的編程模型。而 UniRx 本身的源碼非常值得研究學習,就連大名鼎鼎的 uFrame 框架,在 1.6 版本之後,使用 UniRx 進行了大幅重構,其事件/數據綁定層使用 UniRx 強力驅動。

筆者的 QFramework 也同樣引入了 UniRx,有一大批的框架用戶都是因為支持了 UniRx 慕名而來。

為什麼要用 UniRx ?

UniRx 就是 Unity 版本的 Reactive Extensions,Reactive Extensions 中文意思是:響應式擴展,響應式指的是觀察者和定時器,擴展指的是 LINQ 的操作符。Reactive Extensions 以擅長處理時間上異步的邏輯、以及極簡的 API 風格而聞名。

我們都知道,遊戲很多的系統都是在時間上異步的,所以 Unity 開發者所需要實現的異步邏輯是非常多的。這也是為什麼 Unity 官方在引擎層實現了 Coroutine(協程)這樣的概念。

在遊戲中,像動畫的播放、聲音的播放、網絡請求、資源加載/卸載、Tween 動畫、場景過渡等都是在時間上異步的邏輯。甚至是遊戲循環(Every Update、OnCollisionEnter 等)、傳感器數據(Kinect、Leap Motion、VR Input 等)都是時間上異步的邏輯(事件)。

當我們在項目中實現以上的邏輯的時候,往往使用的方式是用大量的回調實現,最終隨着項目的擴張會導致傳說中的”回調地獄”。

相對較好的方法則是使用消息/事件進行實現,結果導致“消息滿天飛”,導致代碼非常難以閱讀。

以上的任務使用 Coroutine 也是非常不錯的,但是 Coroutine 在 Unity 使用的時候,需要定義一個方法。寫起來是非常面向過程的。當邏輯稍微複雜一點,就很容易造成 Coroutine 嵌套 Coroutine,代碼是非常不容易閱讀的(強耦合)。

而 UniRx 的出現剛好解決了這個問題,它介於回調和事件之間。

它有事件的概念,只不過它的事件是像流水一樣流過來,而我們要做的則是簡單地對這些事件進行組織、變換、過濾、合併就可以得到我們想要的結果了。

它也用掉了回調,只不過它的回調是在事件經過組織之後,只需要調用一次就可以進行事件處理了。

它的原理和 Coroutine (迭代器模式)、LINQ 非常相似,但是比 Coroutine 強大得多。

UniRx 將時間上異步的事件轉化為響應式的事件序列,通過 LINQ操作可以很簡單地組合起來。

為什麼要用 UniRx? 答案就是遊戲本身有大量的在時間上異步的邏輯,而 UniRx 恰好擅長處理這類邏輯,使用 UniRx 可以節省我們的時間,同時讓代碼更簡潔易讀。

Rx 只是一套標準,其他的語言也有實現,如果在 Unity 中熟悉了這套標準,那麼在未來,大家在做別的語言的項目的時候,很容易獲得 Rx 的能力。

專欄內容:

  1. 實踐並講解開發中最常用的 UniRx API。
  2. 對 UniRx 進行一個全方面的簡介。
  3. 在每個階段結束后就會結合所學的知識進行項目實踐。
  4. UniRx 操作符大全。
  5. UniRx 源碼閱讀。
  6. UniRx 背後的設計原理及設計模式學習。
  7. LINQ、Coroutine 底層原理剖析。
  8. BindingsRx、uFrame 源碼閱讀。

OK,讓我們從下一篇開始,感受一下 UniRx 的魅力吧。

知識地圖

2.定時功能實現

在 Unity 開發中,延時是我們經常要實現的功能,這個功能對於有點經驗的開發者來說不難。

最常見的實現方式如下:

using UnityEngine;

public class CommonDelayExample : MonoBehaviour
{
	private float mStartTime;

	void Start()
	{
		mStartTime = Time.time;
	}

	void Update()
	{
		if (Time.time - mStartTime > 5)
		{
			DoSomething();
			// 避免再次執行
			mStartTime = float.MaxValue;
		}
	}

	void DoSomething()
	{
		Debug.Log("DoSomething");
	}
}

這是很多初學者剛入門時候的實現方式,而初學者們隨着深入學習後來接觸到了 Coroutine(協程),使用 Coroutine 實現定時功能會更容易,而且也是更好的選擇,實現如下:

using System;
using System.Collections;
using UnityEngine;

public class CoroutineDelayExample : MonoBehaviour
{
	void Start()
	{
		StartCoroutine(Timer(5, DoSomething));
	}

	IEnumerator Timer(float seconds, Action callback)
	{
		yield return new WaitForSeconds(seconds);
		callback();
	}

	void DoSomething()
	{
		Debug.Log("DoSomething");
	}
}

協程已經把代碼精簡了很多,不過接下來有更厲害的,使用 UniRx。

代碼如下:

Observable.Timer(TimeSpan.FromSeconds(5)).Subscribe(_ => { /* do something */ });

當然以上代碼是沒有和 MonoBehaviour 進行生命周期綁定的,也就是說當 MonoBehaviour 銷毀了之後,這個定時邏輯可能還在執行,這樣就會有造成空指針異常的風險。

要解決也很簡單,代碼如下:

Observable.Timer(TimeSpan.FromSeconds(5))
		.Subscribe(_ => { /* do something */ })
		.AddTo(this);

只要加上一個 AddTo(this) 就可以了。
這樣,當 this(MonoBehaviour) Destroy 的時候,這個延時邏輯也會銷毀掉,從而避免造成空指針異常。

三行代碼,寫下來大約 20 秒的時間,就搞定了一個實現起來比較麻煩的邏輯。

今天的內容就這些。

知識地圖

更多內容

  • QFramework 地址:https://github.com/liangxiegame/QFramework
  • QQ 交流群:623597263
  • 涼鞋的主頁:https://liangxiegame.com/zhuanlan
  • 關注公眾號:liangxiegame 獲取第一時間更新通知及更多的免費內容。

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

【其他文章推薦】

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

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

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

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

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

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

akka-typed(7) – cluster:sharding, 集群分片

  在使用akka-typed的過程中發現有很多地方都簡化了不少,變得更方便了,包括:Supervision,只要用Behaviors.supervise()把Behavior包住,很容易就可以實現這個actor的SupervisorStrategy.restartWithBackoff策略了。然後集群化的group router使用起來也很方便,再就是集群分片cluster-sharding了。下面我們就通過一個例子來介紹cluster-sharding的具體使用方法。

首先,分片的意思是指在集群中多個節點上部署某種actor,即entity,的構建機制。entity的構建是動態的,ClusterSharding系統根據各節點的負載情況決定到底在哪個節點構建entity,然後返回ShardRegion:一個該類entity具體的構建工具及消息中介。也就是說我們可以把同樣的一種運算通過entityId指定給任何一個entity,但具體這個entity生存在集群哪個節點上人工是無法確定的,完全靠ClusterSharding引導。先設計一個簡單功能的actor,測試它作為一個entity的工作細節:

 

object Counter { sealed trait Command extends CborSerializable case object Increment extends Command final case class GetValue(replyTo: ActorRef[Response]) extends Command case object StopCounter extends Command private case object Idle extends Command sealed trait Response extends CborSerializable case class SubTtl(entityId: String, ttl: Int) extends Response val TypeKey = EntityTypeKey[Command]("Counter") def apply(nodeAddress: String, entityContext: EntityContext[Command]): Behavior[Command] = { Behaviors.setup { ctx => def updated(value: Int): Behavior[Command] = { Behaviors.receiveMessage[Command] { case Increment => ctx.log.info("******************{} counting at {},{}",ctx.self.path,nodeAddress,entityContext.entityId) updated(value + 1) case GetValue(replyTo) => ctx.log.info("******************{} get value at {},{}",ctx.self.path,nodeAddress,entityContext.entityId) replyTo ! SubTtl(entityContext.entityId,value) Behaviors.same case Idle => entityContext.shard ! ClusterSharding.Passivate(ctx.self) Behaviors.same case StopCounter => Behaviors.stopped(() => ctx.log.info("************{} stopping ... passivated for idling.", entityContext.entityId)) } } ctx.setReceiveTimeout(30.seconds, Idle) updated(0) } } }

 

cluster-sharding的機制是這樣的:在每個(或指定的)節點上構建部署一個某種EntityType的ShardRegion。這樣系統可以在任何部署了ShardRegion的節點上構建這種entity。然後ClusterSharding系統會根據entityId來引導消息至正確的接收對象。我們再看看ShardRegion的部署是如何實現的吧:

 

object EntityManager { sealed trait Command case class AddOne(counterId: String) extends Command case class GetSum(counterId: String ) extends Command case class WrappedTotal(res: Counter.Response) extends Command def apply(): Behavior[Command] = Behaviors.setup { ctx => val cluster = Cluster(ctx.system) val sharding = ClusterSharding(ctx.system) val entityType = Entity(Counter.TypeKey) { entityContext => Counter(cluster.selfMember.address.toString,entityContext) }.withStopMessage(Counter.StopCounter) sharding.init(entityType) val counterRef: ActorRef[Counter.Response] = ctx.messageAdapter(ref => WrappedTotal(ref)) Behaviors.receiveMessage[Command] { case AddOne(cid) => val entityRef: EntityRef[Counter.Command] = sharding.entityRefFor(Counter.TypeKey, cid) entityRef ! Counter.Increment Behaviors.same case GetSum(cid) => val entityRef: EntityRef[Counter.Command] = sharding.entityRefFor(Counter.TypeKey, cid) entityRef ! Counter.GetValue(counterRef) Behaviors.same case WrappedTotal(ttl) => ttl match { case Counter.SubTtl(eid,subttl) => ctx.log.info("***********************{} total: {} ",eid,subttl) } Behaviors.same } } }

太簡單了, sharding.ini(entityType)一個函數完成了一個節點分片部署。系統通過sharding.init(entityType)來實現ShardRegion構建。這個entityType代表某種特殊actor模版,看看它的構建函數:

object Entity { /** * Defines how the entity should be created. Used in [[ClusterSharding#init]]. More optional * settings can be defined using the `with` methods of the returned [[Entity]]. * * @param typeKey A key that uniquely identifies the type of entity in this cluster * @param createBehavior Create the behavior for an entity given a [[EntityContext]] (includes entityId) * @tparam M The type of message the entity accepts */ def apply[M](typeKey: EntityTypeKey[M])( createBehavior: EntityContext[M] => Behavior[M]): Entity[M, ShardingEnvelope[M]] =
    new Entity(createBehavior, typeKey, None, Props.empty, None, None, None, None, None) }

這個函數需要一個EntityTyeKey和一個構建Behavior的函數createBehavior,產生一個Entity類型。Entity類型定義如下:

final class Entity[M, E] private[akka] ( val createBehavior: EntityContext[M] => Behavior[M], val typeKey: EntityTypeKey[M], val stopMessage: Option[M], val entityProps: Props, val settings: Option[ClusterShardingSettings], val messageExtractor: Option[ShardingMessageExtractor[E, M]], val allocationStrategy: Option[ShardAllocationStrategy], val role: Option[String], val dataCenter: Option[DataCenter]) { /** * [[akka.actor.typed.Props]] of the entity actors, such as dispatcher settings. */ def withEntityProps(newEntityProps: Props): Entity[M, E] = copy(entityProps = newEntityProps) /** * Additional settings, typically loaded from configuration. */ def withSettings(newSettings: ClusterShardingSettings): Entity[M, E] = copy(settings = Option(newSettings)) /** * Message sent to an entity to tell it to stop, e.g. when rebalanced or passivated. * If this is not defined it will be stopped automatically. * It can be useful to define a custom stop message if the entity needs to perform * some asynchronous cleanup or interactions before stopping. */ def withStopMessage(newStopMessage: M): Entity[M, E] = copy(stopMessage = Option(newStopMessage)) /** * * If a `messageExtractor` is not specified the messages are sent to the entities by wrapping * them in [[ShardingEnvelope]] with the entityId of the recipient actor. That envelope * is used by the [[HashCodeMessageExtractor]] for extracting entityId and shardId. The number of * shards is then defined by `numberOfShards` in `ClusterShardingSettings`, which by default * is configured with `akka.cluster.sharding.number-of-shards`. */ def withMessageExtractor[Envelope](newExtractor: ShardingMessageExtractor[Envelope, M]): Entity[M, Envelope] =
    new Entity( createBehavior, typeKey, stopMessage, entityProps, settings, Option(newExtractor), allocationStrategy, role, dataCenter) /** * Allocation strategy which decides on which nodes to allocate new shards, * [[ClusterSharding#defaultShardAllocationStrategy]] is used if this is not specified. */ def withAllocationStrategy(newAllocationStrategy: ShardAllocationStrategy): Entity[M, E] = copy(allocationStrategy = Option(newAllocationStrategy)) /** * Run the Entity actors on nodes with the given role. */ def withRole(newRole: String): Entity[M, E] = copy(role = Some(newRole)) /** * The data center of the cluster nodes where the cluster sharding is running. * If the dataCenter is not specified then the same data center as current node. If the given * dataCenter does not match the data center of the current node the `ShardRegion` will be started * in proxy mode. */ def withDataCenter(newDataCenter: DataCenter): Entity[M, E] = copy(dataCenter = Some(newDataCenter)) private def copy( createBehavior: EntityContext[M] => Behavior[M] = createBehavior, typeKey: EntityTypeKey[M] = typeKey, stopMessage: Option[M] = stopMessage, entityProps: Props = entityProps, settings: Option[ClusterShardingSettings] = settings, allocationStrategy: Option[ShardAllocationStrategy] = allocationStrategy, role: Option[String] = role, dataCenter: Option[DataCenter] = dataCenter): Entity[M, E] = { new Entity( createBehavior, typeKey, stopMessage, entityProps, settings, messageExtractor, allocationStrategy, role, dataCenter) } }

這裏面有許多方法用來控制Entity的構建和作業。

然後我們把這個EntityManager當作RootBehavior部署到多個節點上去:

object ClusterShardingApp { def main(args: Array[String]): Unit = { if (args.isEmpty) { startup("shard", 25251) startup("shard", 25252) startup("shard", 25253) startup("front", 25254) } else { require(args.size == 2, "Usage: role port") startup(args(0), args(1).toInt) } } def startup(role: String, port: Int): Unit = { // Override the configuration of the port when specified as program argument
    val config = ConfigFactory .parseString(s"""       akka.remote.artery.canonical.port=$port akka.cluster.roles = [$role] """)
      .withFallback(ConfigFactory.load("cluster")) val entityManager = ActorSystem[EntityManager.Command](EntityManager(), "ClusterSystem", config) ... }

一共設定了3個role=shard節點和1個front節點。

在front節點上對entityId分別為9013,9014,9015,9016幾個entity發送消息:

 def startup(role: String, port: Int): Unit = { // Override the configuration of the port when specified as program argument
    val config = ConfigFactory .parseString(s"""       akka.remote.artery.canonical.port=$port akka.cluster.roles = [$role] """)
      .withFallback(ConfigFactory.load("cluster")) val entityManager = ActorSystem[EntityManager.Command](EntityManager(), "ClusterSystem", config) if (role == "front") { entityManager ! EntityManager.AddOne("9013") entityManager ! EntityManager.AddOne("9014") entityManager ! EntityManager.AddOne("9013") entityManager ! EntityManager.AddOne("9015") entityManager ! EntityManager.AddOne("9013") entityManager ! EntityManager.AddOne("9014") entityManager ! EntityManager.AddOne("9014") entityManager ! EntityManager.AddOne("9013") entityManager ! EntityManager.AddOne("9015") entityManager ! EntityManager.AddOne("9015") entityManager ! EntityManager.AddOne("9016") entityManager ! EntityManager.GetSum("9014") entityManager ! EntityManager.GetSum("9015") entityManager ! EntityManager.GetSum("9013") entityManager ! EntityManager.GetSum("9016") }

以下是部分運算結果显示:

15:12:10.073 [ClusterSystem-akka.actor.default-dispatcher-15] INFO com.learn.akka.Counter$ - ******************akka://ClusterSystem/system/sharding/Counter/786/9014 counting at akka://ClusterSystem@127.0.0.1:25253,9014
15:12:10.106 [ClusterSystem-akka.actor.default-dispatcher-15] INFO com.learn.akka.Counter$ - ******************akka://ClusterSystem/system/sharding/Counter/786/9014 counting at akka://ClusterSystem@127.0.0.1:25253,9014
15:12:10.106 [ClusterSystem-akka.actor.default-dispatcher-15] INFO com.learn.akka.Counter$ - ******************akka://ClusterSystem/system/sharding/Counter/786/9014 counting at akka://ClusterSystem@127.0.0.1:25253,9014
15:12:10.106 [ClusterSystem-akka.actor.default-dispatcher-3] INFO com.learn.akka.Counter$ - ******************akka://ClusterSystem/system/sharding/Counter/785/9013 counting at akka://ClusterSystem@127.0.0.1:25251,9013
15:12:10.107 [ClusterSystem-akka.actor.default-dispatcher-3] INFO com.learn.akka.Counter$ - ******************akka://ClusterSystem/system/sharding/Counter/785/9013 counting at akka://ClusterSystem@127.0.0.1:25251,9013
15:12:10.107 [ClusterSystem-akka.actor.default-dispatcher-3] INFO com.learn.akka.Counter$ - ******************akka://ClusterSystem/system/sharding/Counter/785/9013 counting at akka://ClusterSystem@127.0.0.1:25251,9013
15:12:10.107 [ClusterSystem-akka.actor.default-dispatcher-3] INFO com.learn.akka.Counter$ - ******************akka://ClusterSystem/system/sharding/Counter/785/9013 counting at akka://ClusterSystem@127.0.0.1:25251,9013
15:12:10.109 [ClusterSystem-akka.actor.default-dispatcher-19] INFO com.learn.akka.Counter$ - ******************akka://ClusterSystem/system/sharding/Counter/787/9015 counting at akka://ClusterSystem@127.0.0.1:25254,9015
15:12:10.110 [ClusterSystem-akka.actor.default-dispatcher-19] INFO com.learn.akka.Counter$ - ******************akka://ClusterSystem/system/sharding/Counter/787/9015 counting at akka://ClusterSystem@127.0.0.1:25254,9015
15:12:10.110 [ClusterSystem-akka.actor.default-dispatcher-19] INFO com.learn.akka.Counter$ - ******************akka://ClusterSystem/system/sharding/Counter/787/9015 counting at akka://ClusterSystem@127.0.0.1:25254,9015
15:12:10.110 [ClusterSystem-akka.actor.default-dispatcher-19] INFO com.learn.akka.Counter$ - ******************akka://ClusterSystem/system/sharding/Counter/787/9015 get value at akka://ClusterSystem@127.0.0.1:25254,9015
15:12:10.112 [ClusterSystem-akka.actor.default-dispatcher-18] INFO com.learn.akka.EntityManager$ - ***********************9015 total: 3
15:12:10.149 [ClusterSystem-akka.actor.default-dispatcher-15] INFO com.learn.akka.Counter$ - ******************akka://ClusterSystem/system/sharding/Counter/786/9014 get value at akka://ClusterSystem@127.0.0.1:25253,9014
15:12:10.149 [ClusterSystem-akka.actor.default-dispatcher-3] INFO com.learn.akka.Counter$ - ******************akka://ClusterSystem/system/sharding/Counter/785/9013 get value at akka://ClusterSystem@127.0.0.1:25251,9013
15:12:10.169 [ClusterSystem-akka.actor.default-dispatcher-18] INFO com.learn.akka.EntityManager$ - ***********************9014 total: 3
15:12:10.169 [ClusterSystem-akka.actor.default-dispatcher-18] INFO com.learn.akka.EntityManager$ - ***********************9013 total: 4
15:12:10.171 [ClusterSystem-akka.actor.default-dispatcher-3] INFO com.learn.akka.Counter$ - ******************akka://ClusterSystem/system/sharding/Counter/788/9016 counting at akka://ClusterSystem@127.0.0.1:25251,9016
15:12:10.171 [ClusterSystem-akka.actor.default-dispatcher-3] INFO com.learn.akka.Counter$ - ******************akka://ClusterSystem/system/sharding/Counter/788/9016 get value at akka://ClusterSystem@127.0.0.1:25251,9016
15:12:10.172 [ClusterSystem-akka.actor.default-dispatcher-18] INFO com.learn.akka.EntityManager$ - ***********************9016 total: 1

15:19:32.176 [ClusterSystem-akka.actor.default-dispatcher-3] INFO com.learn.akka.Counter$ - ************9013 stopping ... passivated for idling.
15:19:52.529 [ClusterSystem-akka.actor.default-dispatcher-3] INFO com.learn.akka.Counter$ - ************9014 stopping ... passivated for idling.
15:19:52.658 [ClusterSystem-akka.actor.default-dispatcher-3] INFO com.learn.akka.Counter$ - ************9016 stopping ... passivated for idling.
15:19:52.662 [ClusterSystem-akka.actor.default-dispatcher-14] INFO com.learn.akka.Counter$ - ************9015 stopping ... passivated for idling.

下面是本次示範的完整源代碼:

ClusterSharding.scala

package com.learn.akka
import scala.concurrent.duration._
import akka.actor.typed._
import akka.actor.typed.scaladsl._
import akka.cluster.sharding.typed.scaladsl.EntityContext
import akka.cluster.sharding.typed.scaladsl.Entity
import akka.persistence.typed.PersistenceId
//#sharding-extension
import akka.cluster.sharding.typed.ShardingEnvelope
import akka.cluster.sharding.typed.scaladsl.ClusterSharding
import akka.cluster.sharding.typed.scaladsl.EntityTypeKey
import akka.cluster.sharding.typed.scaladsl.EntityRef
import com.typesafe.config.ConfigFactory
import akka.cluster.typed.Cluster
//#counter
object Counter {
  sealed trait Command extends CborSerializable
  case object Increment extends Command
  final case class GetValue(replyTo: ActorRef[Response]) extends Command
  case object StopCounter extends Command
  private case object Idle extends Command

  sealed trait Response extends CborSerializable
  case class SubTtl(entityId: String, ttl: Int) extends Response


  val TypeKey = EntityTypeKey[Command]("Counter")

  def apply(nodeAddress: String, entityContext: EntityContext[Command]): Behavior[Command] = {
    Behaviors.setup { ctx =>
      def updated(value: Int): Behavior[Command] = {
        Behaviors.receiveMessage[Command] {
          case Increment =>
            ctx.log.info("******************{} counting at {},{}",ctx.self.path,nodeAddress,entityContext.entityId)
            updated(value + 1)
          case GetValue(replyTo) =>
            ctx.log.info("******************{} get value at {},{}",ctx.self.path,nodeAddress,entityContext.entityId)
            replyTo ! SubTtl(entityContext.entityId,value)
            Behaviors.same
          case Idle =>
            entityContext.shard ! ClusterSharding.Passivate(ctx.self)
            Behaviors.same
          case StopCounter =>
            Behaviors.stopped(() => ctx.log.info("************{} stopping ... passivated for idling.", entityContext.entityId))
        }
      }
      ctx.setReceiveTimeout(30.seconds, Idle)
      updated(0)
    }
  }
}
object EntityManager {
  sealed trait Command
  case class AddOne(counterId: String) extends Command
  case class GetSum(counterId: String ) extends Command
  case class WrappedTotal(res: Counter.Response) extends Command


  def apply(): Behavior[Command] = Behaviors.setup { ctx =>
    val cluster = Cluster(ctx.system)
    val sharding = ClusterSharding(ctx.system)
    val entityType = Entity(Counter.TypeKey) { entityContext =>
      Counter(cluster.selfMember.address.toString,entityContext)
    }.withStopMessage(Counter.StopCounter)
    sharding.init(entityType)

    val counterRef: ActorRef[Counter.Response] = ctx.messageAdapter(ref => WrappedTotal(ref))

     Behaviors.receiveMessage[Command] {
      case AddOne(cid) =>
        val entityRef: EntityRef[Counter.Command] = sharding.entityRefFor(Counter.TypeKey, cid)
        entityRef ! Counter.Increment
        Behaviors.same
      case GetSum(cid) =>
         val entityRef: EntityRef[Counter.Command] = sharding.entityRefFor(Counter.TypeKey, cid)
         entityRef ! Counter.GetValue(counterRef)
         Behaviors.same
      case WrappedTotal(ttl) => ttl match {
        case Counter.SubTtl(eid,subttl) =>
          ctx.log.info("***********************{} total: {} ",eid,subttl)
      }
      Behaviors.same
    }
  }

}

object ClusterShardingApp  {
  def main(args: Array[String]): Unit = {
    if (args.isEmpty) {
      startup("shard", 25251)
      startup("shard", 25252)
      startup("shard", 25253)
      startup("front", 25254)
    } else {
      require(args.size == 2, "Usage: role port")
      startup(args(0), args(1).toInt)
    }
  }

  def startup(role: String, port: Int): Unit = {
    // Override the configuration of the port when specified as program argument
    val config = ConfigFactory
      .parseString(s"""
      akka.remote.artery.canonical.port=$port
      akka.cluster.roles = [$role]
      """)
      .withFallback(ConfigFactory.load("cluster"))

    val entityManager = ActorSystem[EntityManager.Command](EntityManager(), "ClusterSystem", config)
    if (role == "front") {
      entityManager ! EntityManager.AddOne("9013")
      entityManager ! EntityManager.AddOne("9014")
      entityManager ! EntityManager.AddOne("9013")
      entityManager ! EntityManager.AddOne("9015")
      entityManager ! EntityManager.AddOne("9013")
      entityManager ! EntityManager.AddOne("9014")
      entityManager ! EntityManager.AddOne("9014")
      entityManager ! EntityManager.AddOne("9013")
      entityManager ! EntityManager.AddOne("9015")
      entityManager ! EntityManager.AddOne("9015")
      entityManager ! EntityManager.AddOne("9016")
      entityManager ! EntityManager.GetSum("9014")
      entityManager ! EntityManager.GetSum("9015")
      entityManager ! EntityManager.GetSum("9013")
      entityManager ! EntityManager.GetSum("9016")
    }

  }

}

cluster.conf

akka {
  actor {
    provider = cluster

    serialization-bindings {
      "com.learn.akka.CborSerializable" = jackson-cbor
    }
  }
  remote {
    artery {
      canonical.hostname = "127.0.0.1"
      canonical.port = 0
    }
  }
  cluster {
    seed-nodes = [
      "akka://ClusterSystem@127.0.0.1:25251",
      "akka://ClusterSystem@127.0.0.1:25252"]
  }
}

 

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

【其他文章推薦】

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

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

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

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

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

Netty 中的內存分配淺析

Netty 出發點作為一款高性能的 RPC 框架必然涉及到頻繁的內存分配銷毀操作,如果是在堆上分配內存空間將會觸發頻繁的GC,JDK 在1.4之後提供的 NIO 也已經提供了直接直接分配堆外內存空間的能力,但是也僅僅是提供了基本的能力,創建、回收相關的功能和效率都很簡陋。基於此,在堆外內存使用方面,Netty 自己實現了一套創建、回收堆外內存池的相關功能。基於此我們一起來看一下 Netty 是如何實現內存分配的。

1. Netty 中的數據容器分類

談到數據保存肯定要說到內存分配,按照存儲空間來劃分,可以分為 堆內存 和 堆外內存;按照內存區域連貫性來劃分可以分為池化內存和非池化內存。這些劃分在 Netty 中的實現接口分別是:

按照底層存儲空間劃分:

  • 堆緩衝區:HeapBuffer;
  • 直接緩衝區:DirectBuffer。

按照是否池化劃分:

  • 池化:PooledBuffer;
  • 非池化:UnPooledBuffer。

默認使用 PoolDireBuf 類型的內存, 這些內存主要由 PoolArea 管理。另外 Netty 並不是直接對外暴露這些 API,提供了 Unsafe 類作為出口暴露數據分配的相關操作。

小知識:

什麼是池化?

一般申請內存是檢查當前內存哪裡有適合當前數據塊大小的空閑內存塊,如果有就將數據保存在當前內存塊中。

那麼池化想做的事情是:既然每次來數據都要去找內存地址來存,我就先申請一塊內存地址,這一塊就是我的專用空間,內存分配、回收我全權管理。

池化解決的問題:

內存碎片:

內碎片

內碎片就是申請的地址空間大於真正數據使用的內存空間。比如固定申請1M的空間作為某個線程的使用內存,但是該線程每次最多只佔用0.5M,那麼每次都有0.5M的碎片。如果該空間不被有效回收時間一長必然存在內存空洞。

外碎片

外碎片是指多個內存空間合併的時候發現不夠分配給待使用的空間大小。比如有一個 20byte,13byte 的連續內存空間可以被回收,現在有一個 48byte 的數據塊需要存儲,而這兩個加起來也只有 33byte 的空間,必然不會被使用到。

如何實現內存池?

  1. 鏈表維護空閑內存地址

    最簡單的就是弄一個鏈表來維護當前空閑的內存空間地址。如果有使用就從鏈表刪除,有釋放就加入鏈表對應位置。這種方式實現簡單,但是搜索和釋放內存維護的難度還是比較大,不太適合。

  2. 定長內存空間分配

    維護兩個列表,一個是未分配內存列表,一個是已分配內存列表。每個內存塊都是一樣大小,分配時如果不夠就將多個塊合併到一起。這種方式的缺點就是會浪費一定的內存空間,如果有特定的場景還是沒有問題。

  3. 多段定長池分配

    在上面的定長分配基礎上,由原來的固定一個長度分配空間變為按照不同對象大小(8,16,32,64,128,256,512,1k…64K),的方式分配多個固定大小的內存池。每次要申請內存的時候按照當前對象大小去對應的池中查找是否有剩餘空間。

    Linux 本身支持動態內存分配和釋放,對應的命令為:malloc/free。malloc 的全稱是 memory allocation,中文叫動態內存分配,用於申請一塊連續的指定大小的內存塊區域以void*類型返回分配的內存區域地址。

    malloc / free的實現過程:

    1. 空閑存儲空間以空閑鏈表的方式組織(地址遞增),每個塊包含一個長度、一個指向下一塊的指針以及一個指向自身存儲空間的指針。( 因為程序中的某些地方可能不通過 malloc 調用申請,因此 malloc 管理的空間不一定連續)
    2. 當有申請請求時,malloc 會掃描空閑鏈表,直到找到一個足夠大的塊為止(首次適應)(因此每次調用malloc 時並不是花費了完全相同的時間)
    3. 如果該塊恰好與請求的大小相符,則將其從鏈表中移走並返回給用戶。如果該塊太大,則將其分為兩部分,尾部的部分分給用戶,剩下的部分留在空閑鏈表中(更改頭部信息)。因此 malloc 分配的是一塊連續的內存。
    4. 釋放時首先搜索空閑鏈表,找到可以插入被釋放塊的合適位置。如果與被釋放塊相鄰的任一邊是一個空閑塊,則將這兩個塊合為一個更大的塊,以減少內存碎片。

2. Netty 中的內存分配

Netty 採用了 jemalloc 的思想,這是 FreeBSD 實現的一種併發 malloc 的算法。jemalloc 依賴多個 Arena(分配器) 來分配內存,運行中的應用都有固定數量的多個 Arena,默認的數量與處理器的個數有關。系統中有多個 Arena 的原因是由於各個線程進行內存分配時競爭不可避免,這可能會極大的影響內存分配的效率,為了緩解高併發時的線程競爭,Netty 允許使用者創建多個分配器(Arena)來分離鎖,提高內存分配效率。

線程首次分配/回收內存時,首先會為其分配一個固定的 Arena。線程選擇 Arena 時使用 round-robin 的方式,也就是順序輪流選取。

每個線程各種保存 Arena 和緩存池信息,這樣可以減少競爭並提高訪問效率。Arena 將內存分為很多 Chunk 進行管理,Chunk 內部保存 Page,以頁為單位申請。申請內存分配時,會將分配的規格分為幾類:TINY,SAMLL,NORMAL 和 HUGE,分別對應不同的範圍,處理過程也不相同。

tiny 代表了大小在 0-512B 的內存塊;

small 代表了大小在 512B-8K 的內存塊;

normal 代表了大小在 8K-16M 的內存塊;

huge 代表了大於 16M 的內存塊。

每個塊裏面又定義了更細粒度的單位來分配數據:

  • Chunk:一個 Chunk 的大小是 16M,Chunk 是 Netty 對操作系統進行內存申請的單位,後續所有的內存分配都是在 Chunk 裏面進行操作。
  • Page:Chunk 內部以 Page 為單位分配內存,一個 Page 大小為 8K。當我們需要 16K 的空間時,Netty 就會從一個 Chunk 中找到兩個 Page 進行分配。
  • Subpage 和 element:element 是比 Page 更小的單位,當我們申請小於 8K 的內存時,Netty 會以 element 為單位進行內存分配。element 沒有固定大小,具體由用戶的需求決定。Netty 通過 Subpage 管理 element,Subpage 是由 Page 轉變過來的。當我們需要 1K 的空間時,Netty 會把一個 Page 變成 Subpage,然後把 Subpage 分成 8 個 1K 的 element 進行分配。

Chunk 中的內存分配

線程分配內存主要從兩個地方分配: PoolThreadCache 和 Arena。其中 PoolThreadCache 線程獨享, Arena 為幾個線程共享。

初次申請內存的時候,Netty 會從一整塊內存(Chunk)中分出一部分來給用戶使用,這部分工作是由 Arena 來完成。而當用戶使用完畢釋放內存的時候,這些被分出來的內存會按不同規格大小放在 PoolThreadCache 中緩存起來。當下次要申請內存的時候,就會先從 PoolThreadCache 中找。

Chunk、Page、Subpage 和 element 都是 Arena 中的概念,Arena 的工作就是從一整塊內存中分出合適大小的內存塊。Arena 中最大的內存單位是 Chunk,這是 Netty 向操作系統申請內存的單位。而一塊 Chunk(16M) 申請下來之後,內部會被分成 2048 個 Page(8K),當用戶向 Netty 申請超過 8K 內存的時候,Netty 會以 Page 的形式分配內存。

Chunk 內部通過夥伴算法管理 Page,具體實現為一棵完全平衡二叉樹:

二叉樹中所有子節點管理的內存也屬於其父節點。當我們要申請大小為 16K 的內存時,我們會從根節點開始不斷尋找可用的節點,一直到第 10 層。那麼如何判斷一個節點是否可用呢?Netty 會在每個節點內部保存一個值,這個值代表這個節點之下的第幾層還存在未分配的節點。比如第 9 層的節點的值如果為 9,就代表這個節點本身到下面所有的子節點都未分配;如果第 9 層的節點的值為 10,代表它本身不可被分配,但第 10 層有子節點可以被分配;如果第 9 層的節點的值為 12,此時可分配節點的深度大於了總深度,代表這個節點及其下面的所有子節點都不可被分配。下圖描述了分配的過程:

對於小內存(小於4096)的分配還會將 Page 細化成更小的單位 Subpage。Subpage 按大小分有兩大類:

  1. Tiny:小於 512 的情況,最小空間為 16,對齊大小為 16,區間為[16,512),所以共有 32 種情況。
  2. Small:大於等於 512 的情況,總共有四種,512,1024,2048,4096。

PoolSubpage 中直接採用位圖管理空閑空間(因為不存在申請 k 個連續的空間),所以申請釋放非常簡單。

第一次申請小內存空間的時候,需要先申請一個空閑頁,然後將該頁轉成 PoolSubpage,再將該頁設為已被佔用,最後再把這個 PoolSubpage 存到 PoolSubpage 池中。這樣下次就不需要再去申請空閑頁了,直接去池中找就好了。Netty 中有 36 種 PoolSubpage,所以用 36 個 PoolSubpage 鏈表表示 PoolSubpage 池。

因為單個 PoolChunk 只有 16M,這遠遠不夠用,所以會很很多很多 PoolChunk,這些 PoolChunk 組成一個鏈表,然後用 PoolChunkList 持有這個鏈表。

我們先從內存分配器 PoolArena 來分析 Netty 中的內存是如何分配的,Area 的工作就是從一整塊內存中協調如何分配合適大小的內存給當前數據使用。PoolArena 是 Netty 的內存池實現抽象類,其內部子類為 HeapArena 和 DirectArena,HeapArena 對應堆內存(heap buffer),DirectArena 對應堆外直接內存(direct buffer),兩者除了操作的內存(byte[]和ByteBuffer)不同外其餘完全一致。

從結構上來看,PoolArena 中主要包含三部分子內存池:

tinySubpagePools;

smallSubpagePools;

一系列的 PoolChunkList。

tinySubpagePools 和 smallSubpagePools 都是 PoolSubpage 的數組,數組長度分別為 32 和 4。

PoolChunkList 則主要是一個容器,其內部可以保存一系列的 PoolChunk 對象,並且,Netty 會根據內存使用率的不同,將 PoolChunkList 分為不同等級的容器。

abstract class PoolArena<T> implements PoolArenaMetric {

   enum SizeClass {
        Tiny,
        Small,
        Normal
    }
  // 該參數指定了tinySubpagePools數組的長度,由於tinySubpagePools每一個元素的內存塊差值為16,
	// 因而數組長度是512/16,也即這裏的512 >>> 4
  static final int numTinySubpagePools = 512 >>> 4;
	//表示該PoolArena的allocator
  final PooledByteBufAllocator parent;
  //表示PoolChunk中由Page節點構成的二叉樹的最大高度,默認11
  private final int maxOrder;
  //page的大小,默認8K
  final int pageSize;
  // 指定了恭弘=叶 恭弘節點大小8KB是2的多少次冪,默認為13,該字段的主要作用是,在計算目標內存屬於二叉樹的
	// 第幾層的時候,可以藉助於其內存大小相對於pageShifts的差值,從而快速計算其所在層數
  final int pageShifts;
  //默認16MB
  final int chunkSize;
  // 由於PoolSubpage的大小為8KB=8196,因而該字段的值為
	// -8192=>=> 1111 1111 1111 1111 1110 0000 0000 0000
	// 這樣在判斷目標內存是否小於8KB時,只需要將目標內存與該数字進行與操作,只要操作結果等於0,
	// 就說明目標內存是小於8KB的,這樣就可以判斷其是應該首先在tinySubpagePools或smallSubpagePools
	// 中進行內存申請
  final int subpageOverflowMask;
  // 該參數指定了smallSubpagePools數組的長度,默認為4
  final int numSmallSubpagePools;
  //tinySubpagePools用來分配小於512 byte的Page
  private final PoolSubpage<T>[] tinySubpagePools;
  //smallSubpagePools用來分配大於等於512 byte且小於pageSize內存的Page
  private final PoolSubpage<T>[] smallSubpagePools;
  //用來存儲用來分配給大於等於pageSize大小內存的PoolChunk
  //存儲內存利用率50-100%的chunk
  private final PoolChunkList<T> q050;
  //存儲內存利用率25-75%的chunk
  private final PoolChunkList<T> q025;
  //存儲內存利用率1-50%的chunk
  private final PoolChunkList<T> q000;
  //存儲內存利用率0-25%的chunk
  private final PoolChunkList<T> qInit;
  //存儲內存利用率75-100%的chunk
  private final PoolChunkList<T> q075;
  //存儲內存利用率100%的chunk
  private final PoolChunkList<T> q100;
	//堆內存(heap buffer)
  static final class HeapArena extends PoolArena<byte[]> {
  
  }
   //堆外直接內存(direct buffer)
  static final class DirectArena extends PoolArena<ByteBuffer> {
    
  }
  
  
}

如上所示,PoolArena 是由多個 PoolChunk 組成的大塊內存區域,而每個 PoolChun k則由多個 Page 組成。當需要分配的內存小於 Page 的時候,為了節約內存採用 PoolSubpage 實現小於 Page 大小內存的分配。在PoolArena 中為了保證 PoolChunk 空間的最大利用化,按照 PoolArena 中各 個PoolChunk 已使用的空間大小將其劃分為 6 類:

  1. qInit:存儲內存利用率 0-25% 的 chunk;
  2. q000:存儲內存利用率 1-50% 的 chunk;
  3. q025:存儲內存利用率 25-75% 的 chunk;
  4. q050:存儲內存利用率 50-100% 的 chunk;
  5. q075:存儲內存利用率 75-100%的 chunk;
  6. q100:存儲內存利用率 100%的 chunk。

PoolArena 維護了一個 PoolChunkList 組成的雙向鏈表,每個 PoolChunkList 內部維護了一個 PoolChunk 雙向鏈表。分配內存時,PoolArena 通過在 PoolChunkList 找到一個合適的 PoolChunk,然後從 PoolChunk 中分配一塊內存。

下面來看 PoolArena 是如何分配內存的:

private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) {
  // 將需要申請的容量格式為 2^N
  final int normCapacity = normalizeCapacity(reqCapacity);
  // 判斷目標容量是否小於8KB,小於8KB則使用tiny或small的方式申請內存
  if (isTinyOrSmall(normCapacity)) { // capacity < pageSize
    int tableIdx;
    PoolSubpage<T>[] table;
    boolean tiny = isTiny(normCapacity);
    // 判斷目標容量是否小於512字節,小於512字節的為tiny類型的
    if (tiny) { // < 512
      // 將分配區域轉移到 tinySubpagePools 中
      if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) {
        // was able to allocate out of the cache so move on
        return;
      }
      // 如果無法從當前線程緩存中申請到內存,則嘗試從tinySubpagePools中申請,這裏tinyIdx()方法
      // 就是計算目標內存是在tinySubpagePools數組中的第幾號元素中的
      tableIdx = tinyIdx(normCapacity);
      table = tinySubpagePools;
    } else {
      // 如果目標內存在512byte~8KB之間,則嘗試從smallSubpagePools中申請內存。這裏首先從
      // 當前線程的緩存中申請small級別的內存,如果申請到了,則直接返回
      if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) {
        // was able to allocate out of the cache so move on
        return;
      }
      tableIdx = smallIdx(normCapacity);
      table = smallSubpagePools;
    }
		// 獲取目標元素的頭結點
    final PoolSubpage<T> head = table[tableIdx];

    // 這裏需要注意的是,由於對head進行了加鎖,而在同步代碼塊中判斷了s != head,
    // 也就是說PoolSubpage鏈表中是存在未使用的PoolSubpage的,因為如果該節點已經用完了,
    // 其是會被移除當前鏈表的。也就是說只要s != head,那麼這裏的allocate()方法
    // 就一定能夠申請到所需要的內存塊
    synchronized (head) {
      // s != head就證明當前PoolSubpage鏈表中存在可用的PoolSubpage,並且一定能夠申請到內存,
      // 因為已經耗盡的PoolSubpage是會從鏈表中移除的
      final PoolSubpage<T> s = head.next;
      // 如果此時 subpage 已經被分配過內存了執行下文,如果只是初始化過,則跳過該分支
      if (s != head) {
        // 從PoolSubpage中申請內存
        assert s.doNotDestroy && s.elemSize == normCapacity;
        // 通過申請的內存對ByteBuf進行初始化
        long handle = s.allocate();
        assert handle >= 0;
        // 初始化 PoolByteBuf 說明其位置被分配到該區域,但此時尚未分配內存
        s.chunk.initBufWithSubpage(buf, handle, reqCapacity);
				// 對tiny類型的申請數進行更新
        if (tiny) {
          allocationsTiny.increment();
        } else {
          allocationsSmall.increment();
        }
        return;
      }
    }
    // 走到這裏,說明目標PoolSubpage鏈表中無法申請到目標內存塊,因而就嘗試從PoolChunk中申請
    allocateNormal(buf, reqCapacity, normCapacity);
    return;
  }
   // 走到這裏說明目標內存是大於8KB的,那麼就判斷目標內存是否大於16M,如果大於16M,
  // 則不使用內存池對其進行管理,如果小於16M,則到PoolChunkList中進行內存申請
  if (normCapacity <= chunkSize) {
    // 小於16M,首先到當前線程的緩存中申請,如果申請到了則直接返回,如果沒有申請到,
    // 則到PoolChunkList中進行申請
    if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) {
      // was able to allocate out of the cache so move on
      return;
    }
    allocateNormal(buf, reqCapacity, normCapacity);
  } else {
    // 對於大於16M的內存,Netty不會對其進行維護,而是直接申請,然後返回給用戶使用
    allocateHuge(buf, reqCapacity);
  }
}

所有內存分配的 size 都會經過 normalizeCapacity() 進行處理,申請的容量總是會被格式為 2^N。主要規則如下:

  1. 如果目標容量小於 16 字節,則返回 16;
  2. 如果目標容量大於 16 字節,小於 512 字節,則以 16 字節為單位,返回大於目標字節數的第一個 16 字節的倍數。比如申請的 100 字節,那麼大於 100 的 16 整數倍最低為: 16 * 7 = 112,因而返回 112;
  3. 如果目標容量大於 512 字節,則返回大於目標容量的第一個 2 的指數冪。比如申請的 1000 字節,那麼返回的將是:2^10 = 1024。

PoolArena 提供了兩種方式進行內存分配:

  1. PoolSubpage 用於分配小於 8k 的內存

    • tinySubpagePools:用於分配小於 512 字節的內存,默認長度為 32,因為內存分配最小為 16,每次增加16,直到512,區間[16,512)一共有 32 個不同值;
    • smallSubpagePools:用於分配大於等於 512 字節的內存,默認長度為 4;
  • tinySubpagePools 和 smallSubpagePools 中的元素默認都是 subpage。
  1. poolChunkList 用於分配大於 8k 的內存

    上面已經解釋了 q 開頭的幾個變量用於保存大於 8k 的數據。

默認先嘗試從 poolThreadCache 中分配內存,PoolThreadCache 利用 ThreadLocal 的特性,消除了多線程競爭,提高內存分配效率;

首次分配時,poolThreadCache 中並沒有可用內存進行分配,當上一次分配的內存使用完並釋放時,會將其加入到 poolThreadCache 中,提供該線程下次申請時使用。

如果是分配小內存,則嘗試從 tinySubpagePools 或 smallSubpagePools 中分配內存,如果沒有合適 subpage,則採用方法 allocateNormal 分配內存。

如果分配一個 page 以上的內存,直接採用方法 allocateNormal() 分配內存,allocateNormal()則會將申請動作交由 PoolChunkList 進行。

private synchronized void allocateNormal(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
  //如果在對應的PoolChunkList能申請到內存,則返回
  if (q050.allocate(buf, reqCapacity, normCapacity) || q025.allocate(buf, reqCapacity, normCapacity) ||
      q000.allocate(buf, reqCapacity, normCapacity) || qInit.allocate(buf, reqCapacity, normCapacity) ||
      q075.allocate(buf, reqCapacity, normCapacity)) {
    ++allocationsNormal;
    return;
  }

  // Add a new chunk.
  PoolChunk<T> c = newChunk(pageSize, maxOrder, pageShifts, chunkSize);
  long handle = c.allocate(normCapacity);
  ++allocationsNormal;
  assert handle > 0;
  c.initBuf(buf, handle, reqCapacity);
  qInit.add(c);
}

首先將申請動作按照 q050->q025->q000->qInit->q075 的順序依次交由各個 PoolChunkList 進行處理,如果在對應的 PoolChunkList 中申請到了內存,則直接返回。

如果申請不到,那麼直接創建一個新的 PoolChunk,然後在該 PoolChunk 中申請目標內存,最後將該 PoolChunk 添加到 qInit 中。

上面說過 Chunk 是 Netty 向操作系統申請內存塊的最大單位,每個 Chunk 是16M,PoolChunk 內部通過 memoryMap 數組維護了一顆完全平衡二叉樹作為管理底層內存分佈及回收的標記位,所有的子節點管理的內存也屬於其父節點。

關於 PoolChunk 內部如何維護完全平衡二叉樹就不在這裏展開,大家有興趣可以自行看源碼。

對於內存的釋放,PoolArena 主要是分為兩種情況,即池化和非池化,如果是非池化,則會直接銷毀目標內存塊,如果是池化的,則會將其添加到當前線程的緩存中。如下是 free()方法的源碼:

public void free(PoolChunk<T> chunk, ByteBuffer nioBuffer, long handle, int normCapacity,
     PoolThreadCache cache) {
  // 如果是非池化的,則直接銷毀目標內存塊,並且更新相關的數據
  if (chunk.unpooled) {
    int size = chunk.chunkSize();
    destroyChunk(chunk);
    activeBytesHuge.add(-size);
    deallocationsHuge.increment();
  } else {
    // 如果是池化的,首先判斷其是哪種類型的,即tiny,small或者normal,
    // 然後將其交由當前線程的緩存進行處理,如果添加成功,則直接返回
    SizeClass sizeClass = sizeClass(normCapacity);
    if (cache != null && cache.add(this, chunk, nioBuffer, handle,
          normCapacity, sizeClass)) {
      return;
    }

    // 如果當前線程的緩存已滿,則將目標內存塊返還給公共內存塊進行處理
    freeChunk(chunk, handle, sizeClass, nioBuffer);
  }
}

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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