2016全年轎車銷量前十齣爐 國產轎車榮譽上榜!

第四名:大眾捷達全年銷量:348,437輛捷達在中國還真是“常青樹”般的存在,一直以來銷量居高不下,而且相對於桑塔納更受駕校、政府機關以及企業所喜愛,這讓它獲得了不少的企業訂單。而選擇它的原因就是其多年以來良好的口碑以及不過時的外觀。

前言

在學校中,學生們最為重視的就是考試成績了,這反映的就是他們自己的素質。而對於汽車來說,全年銷量就是最終成績了,那麼在2016中有哪些車型賣得最為火爆呢?

第十名:吉利帝豪EC7

全年銷量:223,781輛

相信熟悉吉利帝豪的肯定知道它為什麼能熱火那麼多年了,不過也需要換代了不是嗎?

第九名:現代朗動

全年銷量:253,804輛

在伊蘭特、悅動、朗動、領動“四代同堂”的情況 ,朗動的銷量在12月還是能達到3萬級別,證明了朗動的競爭實力依然不可忽視。

第八名:大眾新桑塔納

全年銷量:282,815輛

新桑塔納在顏值上已經不會以前那種很low的感覺,加上親民的價格以及不俗的耐用度,成為第八是毫無意外的,但是令人費解的是為什麼會落後同門的捷達那麼多呢?

第七名:福特福睿斯

全年銷量:296,867輛

福特終於懂得中國人的消費心理,大空間低價格才是中國人需要的,這款價格更低的中國特供車要比正統軍——福克斯要高,所以車型在外國賣得再好,也是先要適合國人的需求。

第六名:豐田卡羅拉

全年銷量:307,360輛

卡羅拉的存在說明了中國喜愛的還是中庸,平淡的外觀內飾以及動力表現是卡羅拉的性能特點,但大空間以及低油耗、低故障率卻是我們最注重的。它的銷量一直都不錯,相對於外觀年輕激進的雷凌它的迎合面更廣,所以它比雷凌高銷量是正常的事。

第五名:大眾速騰

全年銷量:341,331輛

在銷量前十的車型中,平均售價是比較高的一款車型。那句戲謔“這車比較高級”雖然搞笑,但也證明了速騰又或者說是德系車在我國消費者心中的形象,事實上它渦輪增壓+雙離合車型性能表現也是不錯。

第四名:大眾捷達

全年銷量:348,437輛

捷達在中國還真是“常青樹”般的存在,一直以來銷量居高不下,而且相對於桑塔納更受駕校、政府機關以及企業所喜愛,這讓它獲得了不少的企業訂單。而選擇它的原因就是其多年以來良好的口碑以及不過時的外觀。

第三名:日產軒逸

全年銷量:367,979輛

軒逸是這次榜單的正統生力軍,來自日本的它和我們需求有着不少相似的地方。輕巧的駕駛風格、低油耗、優秀的乘坐空間以及舒適的乘坐體驗使得它非常適合中國人的口味。而且相對於卡羅拉,內飾也更為時尚好看,成為這個榜單的前三可謂是實至名歸。

第二名:全新別克英朗

全年銷量:370,370輛

再次說明了明白中國人需求就能打造一輛熱銷的車型,別克上有威朗下有英朗,而根據國人喜好打造的全新英朗也是如此。夠用的空間加上美系車擅長的NVH,使得它有着超越該價位價值的感覺。

第一名:大眾朗逸

全年銷量:478,699輛

這才是中國特供車的高手,藉著速騰的威名,使用成熟的平台降低了研發成本,從而降低了車價。和速騰差不多的外觀,但是價格比起速騰低了不少,所以第一的寶座非它莫屬,全年銷量比起第二的全新英朗要高出十萬輛。

編者總結:

這個榜單中除朗動這已經換代的車型,年銷量都有着不同程度的提升,其中增幅最大的就是全新別克英朗以及福特福睿斯,加上同樣增幅不少的大眾朗逸,證明了我國需要的就是價格親民,性能夠用以及故障率低的車型。這些“中國特供車”才能那麼暢銷,所以“中國特供”絕非是一個貶義詞。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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

未上市先來征服冰雪,也就只有這款插電式混動SUV敢玩!

而從廣告圖上大大的100符號、以及“唐100”的官方命名來看,傳言的“純電續航里程100公里”也是沒跑了——比亞迪又刷新了國內混動新能源車的純電續航里程紀錄。當然,像4。9秒的百公里加速、全時電四驅等核心技術配置在唐100這款比亞迪旗艦級SUV上預計會保留。

看看這張比亞迪的官方宣傳海報。

如果你第一眼看到的是帥氣的小李子,那麼你一定是他的影迷;

如果你第一眼看到的是“冰雪”,那麼你一定很嚮往一次說走就走的冰雪之旅;

如果你的目光已經被小李子身後那輛SUV捕捉,並牢牢盯着畫面上“唐100”的小字不放,那你一定和小編一樣,是一個看到新車就興奮的愛車之人。

這值得興奮。2月下旬,以“馭見冰雪 歷史由你創造”為主題,比亞迪將在海拉爾舉辦一款未上市全新車型的冰雪試駕活動,並向全社會招募該新車的第一批體驗者。

而迎戰冰雪的,則是在新能源SUV領域一直呼聲很高、讓眾多粉絲守候已久的比亞迪新車唐100,這也是唐100首度在比亞迪的官方平台亮相登場。小李子背後的SUV,是唐100產品形象的第一次亮相,而“唐100”的產品命名也隨着這張海報的披露塵埃落定。

無論你是期待唐100已久的粉絲,還是想在新年開上新車的持幣待購人士,或是希望在這兩款車上市前搶先感受一把的愛車之人,都可以關注比亞迪本次活動的官網http://wechat.xudankeji.com/he/biyadi

反正有比亞迪包機票食宿,有專業老司機貼身護駕,現場還有現金大獎可以贏取,何樂不為?

重磅新車搶先看:唐100值得現場一試

唐100自被曝光之日起,就飽受關注,其上市日期也因新能源車補貼目錄的原因一再推遲。這次高調亮相,即意味着它的上市時間已經進入倒計時——小編押今年的春節紅包打賭絕不晚於第一季度。

唐100的現款在售車型——比亞迪唐,是國內首個正式銷售的插電式混動SUV,上市近兩年來始終佔據細分市場銷量冠軍的地位,而車迷和消費者們對它的後續改款車型也一直保持密切關注。儘管早有偷拍黨們樂此不疲地泄密出唐100的信息,但官方始終三緘其口,吊足了消費者的胃口。這次正式亮相海拉爾,並邀請社會公眾搶先體驗,也算是回應了市場對唐100沉甸甸的期待。

(此前曝光的唐100諜照)

依據此前曝光的諜照,和小李子身後的唐100造型上來看。新車在輪眉、發動機艙蓋、車頂線條上都有所變化。最顯眼的車前LOGO採用了傳聞已久的“唐”小篆體字,瞬間帶感了起來。

而從廣告圖上大大的100符號、以及“唐100”的官方命名來看,傳言的“純電續航里程100公里”也是沒跑了——比亞迪又刷新了國內混動新能源車的純電續航里程紀錄。當然,像4.9秒的百公里加速、全時電四驅等核心技術配置在唐100這款比亞迪旗艦級SUV上預計會保留。

至於更多細節和駕駛體驗,也許在這次冰雪試駕后“體驗官”們會給我們一個真實的答覆。

未上市先搞冰雪試駕?也就比亞迪敢玩!

值得一提的是,比亞迪這次為唐100上市預熱貼身打造的冰雪試駕,放在業內可謂前無古人。正如其試駕主題 “歷史由你創造”,比亞迪和參加活動的體驗者們,即將創造一個“國內首次未上市新能源車舉辦公眾冰雪試駕”的歷史。

當然,比亞迪如此“敢玩”的底氣來自於他們一以貫之的技術實力,其2014年就發布的“542”新能源車技術標準放在今天依然沒有被超越:“5”代表0-100km/h加速在5秒內,“4”代表全時電四驅,“2”則是百公里油耗兩升。

(比亞迪展示的542技術)

其中,全時電四驅將是唐100駕馭冰雪的核心法寶。與通過傳動軸連接前後驅動的傳統机械四驅不同,全時電四驅可以將動力直接傳遞到前後軸,不需要分動箱、傳動軸等複雜結構,在冰雪路況下可靠性和作用效率更高。

資料显示,比亞迪全時電四驅控制響應時間僅為20毫秒,是机械四驅響應速度的10倍(傳統机械四驅需200毫秒),能夠非常短促、高頻地分配四輪動力,確保車輪在冰面上的抓地力並及時調整車身平衡,讓駕駛者在冰面暢快漂移的同時也能感到穩如泰山的安全感。

(現款唐在冰雪路面上駕駛毫無壓力)

事實上,在現款唐銷售期間,比亞迪也開展過諸如“冰上拔河”、“雪地越野”等活動,唐的表現非常出色,展示出了電四驅在安全性和操控性上的與傳統四驅完全不同的另類體驗。因此,如果有網友能親臨海拉爾冰雪試駕活動現場,不妨重點感受一下比亞迪的電四驅黑科技魅力。

最後,小編也想請內幕八卦人士私信報料:比亞迪放在活動圖片上的小李子,也是現場的“標配”嗎?圖片上的“100”符號,為什麼看上去像是“LEO”啊?活動現場會原地繪製“LEO”魔法陣召喚比亞迪新能源車的代言人小李子嗎?

(比亞迪和小李子喊你來冰雪試駕啦!)

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

【其他文章推薦】

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

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

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

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

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

這年頭養車和養娃一樣貴…這些便宜好養的車你可別錯過!

6L自然吸氣車型還要低,加上非常運動犀利的外觀設計,值得入手,最起碼三四年都不會過時。2016款的起亞 k3對外觀進行了一次小手術,拋棄了以往的圓潤線條,燈組、中網、保險杠都進行了修改,總體風格更加硬朗、幹練,有些時候不得不佩服,家族式設計真是一個神奇的東西,換一下設計又像是一輛新車一樣。

韓系車

韓系車一向給人前衛、時尚、富有活力的形象,韓系車的零整比表現還是不錯的,平均都是200%左右,優於行業整體平均水平,在我國,韓系車的市場佔有率還是挺高的,這主要歸根於“性價比”這一塊,同樣的價錢選擇韓系車往往能夠得到更加時尚的設計與更加豐富的配置。

就在剛開幕不久的2017年北美車展上,起亞就展示了一款非常重磅的產品,起亞 Stinger,一輛定位於轎跑的車型,其搭配了一台3.3T發動機,百公里加速5.1秒,是起亞歷史上性能最強的量產車型。

不好意思,跑太遠了,還是老老實實看一下現在有什麼韓系車值得購買吧,首先是現代領動,它的1.4T發動機獲得了今年沃德十佳發動機獎項,雖然不是有什麼強暴的性能,但是油耗比自己的1.6L自然吸氣車型還要低,加上非常運動犀利的外觀設計,值得入手,最起碼三四年都不會過時。

2016款的起亞 k3對外觀進行了一次小手術,拋棄了以往的圓潤線條,燈組、中網、保險杠都進行了修改,總體風格更加硬朗、幹練,有些時候不得不佩服,家族式設計真是一個神奇的東西,換一下設計又像是一輛新車一樣。

國產車

國產車講究的就是性價比,維修保養不在話下,隨便修,總覺得好像修車不用錢一樣,在修理廠的每個角落都能看見其身影,對,多年前國產車的確是這樣,不過在今天,雖說和國際一線大廠還有差距,但已經擺脫舊況,實力還是有的。

雖然最近長安的SUV非常火爆,CS15、CS35和CS95的關注度都相當高,但還是想說一下長安逸動這輛車,不知不覺中它已經走過了將近5個年頭,2011年年底面世的它讓我們看見國產車的進步,能夠與同價位合資車相媲美的行駛質感,它雖然沒有什麼值得炫耀的豐富配置,但是卻擁有着國際范的設計和做工水準。

要說國產車中誰的維修保養最便宜,覺得應該是王者中的王者——五菱,無論是五菱宏光還是五菱之光,亦或者是五菱榮光保有量都是超級巨大的,配件的價格自然就要比其它車型要實惠很多,隨便在路邊的小維修廠都能夠很容易找到配件。

即將上市的寶馬1系三廂就是一個很好的例子,中國消費者對於轎車的看法非常獨特,三廂才是轎車,這也是為什麼兩廂版本的寶馬1系總是得不到很好的銷量,寶馬終於意識到要迎合消費者的口味,萬眾期待的寶馬1系三廂只要在價格上多一點誠意,要把奧迪A3同台對飈不是什麼難事。

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

【其他文章推薦】

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

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

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

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

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

差點就翻車!用生命試駕中國最屌電動車

你TM就拿補貼造了這麼一個&^%$&%*^$(&出來。只要你開着它溜出個幾米,跨過減速帶,那種直接生硬的震動會讓你的香臀不知所措。對於知豆我覺得不需要說太多了,因為我直接建議大家去租一輛好好體驗一把。而奇瑞EQ則像是一輛升級再升級版的知豆。

相信近些年大家對新能源車是有了不少的了解,不論是國內還是國外的車企都着手推出新能源車型。而在國內車企推出新能源車型還能獲得國家或地方政府補貼,消費者也能選擇更多不同的車型。

然而,並不是每家車企都會出良心之作。於是,就有了這麼三輛新能源車,知豆D2,北汽EV160和奇瑞eQ,這三輛車的官方售價分別為15.88-18.88萬元、17.78-18.99萬和15.99-16.49萬元。如果沒有補貼按這價格來賣,估計排隊噴它們的人能繞地球三圈。

不過,今天我們不討論錢的問題。單從產品出發,就聊它們到底是不是一輛“車”。

上一年我們就試駕過了知豆D2,這輛雙門雙座的小車,簡直就是一輛升級版的老年代步車。什麼用料,質感,品牌統統要拋到一邊,因為不值得去說了。不過相比於其它兩者,知豆D2擁有一个中控大屏,不得不說麻雀雖小但逼格還是得有。

不知道有多少朋友體驗過坐進知豆D2里的感覺,但那處濃烈的塑料感絕對會讓你開懷的笑出聲來。你TM就拿補貼造了這麼一個&^%$&%*^$(&出來?只要你開着它溜出個幾米,跨過減速帶,那種直接生硬的震動會讓你的香臀不知所措。對於知豆我覺得不需要說太多了,因為我直接建議大家去租一輛好好體驗一把。

而奇瑞EQ則像是一輛升級再升級版的知豆。在租車點清一色雪白的車體,搭載暗紫色的封閉輪轂,還沒進去之前你會有點懷疑,但只要你坐進去,扣好安全帶,試了N次將車子啟動之後,你還是會覺得它至少還像輛車。用手輕輕一扭,將旋鈕式擋把切換至D擋,你就可以開始屬於你的傳奇之旅了。如果奇瑞的工程師們能把旋鈕做成啟動后能緩緩升上來的話,絕對會是新能源界的一段佳話。

踩住剎車,右腳油門到底,上車不來一發溫柔的彈射起步絕對不能算開過這車。在大長直的公路上稍稍變線,可以感受到強有力的側翻臨界體驗。更可貴的是,這車能開到100km/h。別問我怎麼知道的,肯定是工程師們為了讓你好受些將液晶時速表的数字調大了。

相比之下,北汽EV160就可以算是一輛車了。它不像知豆那麼簡陋,也不像奇瑞eQ那樣發飄,開完前兩輛車之後再開北汽EV160,你會感覺世界的秩序會稍稍正常一點。如果你哪天因為不可描述的原因買了這麼一輛車,那我覺得你需要上某寶收一個奔馳的標,貼在前進氣一點違和感都沒有。

除了洋氣的外觀之外,北汽EV160開起來就沒有像前兩輛車那麼充滿戲劇性。按照The Grand Tour的主持人Jeremy Clarkson的理論,一輛能讓人充滿笑容的車才是好車,那麼北汽EV160可能就有點達不到要求了。

不過這些都不重要了,如果今天的這條片子看完沒能讓你笑出聲來,那可能就說明了連我們的聰明才智都無法讓這三輛車挽回那一點點“車”的尊嚴。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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

過年買車套路深…怎樣才能用合理的價錢買到合適的車?

免息≠免手續費什麼“一年免息”、“無息貸款2年愛車開回家”這些廣告你們真敢信。等你帶好了身份證、銀行流水甚至房產證去店裡準備簽字畫押的時候,你就會明白即使減免或優惠了所謂的利息,但手續費、服務費等等費會讓你瞬間感覺上當,不過此時的你騎虎難下,買還是不買。

眼瞅着公曆新年正式開始,同時也意味着“購置稅5折優惠大酬賓”活動徹底結束,不知道大家有沒有趕在2017年之前買到自己喜愛的車呢?

叫獸在這裏對已經提了新車的朋友們說聲恭喜,你們算是“撿到了大便宜”。還沒買的也不要着急,畢竟今年購置稅還可以享受“75折優惠”(1.6L及以下排量乘用車)。更重要的是,如果您恰好在買車之前看到今天這篇文章保你買車不被坑,可以少花冤枉錢,這同樣是“真金白銀”一樣的福利,快收下吧。

叫獸是個實誠人,就單刀直入告訴大家我們在買車的時候會遇到哪些常見的套路。

一.網上低價陷阱

如今網絡信息這麼發達,大部分人買車之前都會先在網上做好功課。除了查詢車輛的配置差異外,還會通過媒體試駕評測(比如時刻關注叫獸)以及網友評價等消息對車輛做一個初步的了解和判斷,而報價更是大家的關注的重點。

來到敏感的價格部分,這時候就需要擦亮你的雙眼。網上都常會以特別顯眼(尤其是紅色)的顏色展示出一個非常有誘惑力的價格,不懂行的“小白”們會以為撿着大便宜了,趕緊點進鏈接一看,通常會遇到幾種情況:

1.需要在店內上牌、辦理保險甚至購買裝飾(所謂的優惠也就形同虛設)

2.大幅優惠只限於頂配車型(並且這個優惠通常來說一樣也辦不到),要買低配?不是沒車就是做不了那樣的優本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

除了家用致炫還能這麼玩,看完包你流口水…

5L車型前往午飯目的地,人生地不熟的在原廠車載導航的指引下,只用半個多小時便順利到達午飯地點——濱江大公館惠食佳。飲飽食醉后,馬不停蹄前往場地挑戰地點,媒體老師們駕馭着致炫,各顯神通、爭奪積分,無論是短小狹窄的緊湊型賽道,抑或是蜿蜒小徑,致炫都能游刃有餘,樂趣不言而喻。

常言道:“唯汽車與美食不可辜負”,每天沉浸在汽車與美食帶來的歡悅中,樂此不疲。無獨有偶,廣汽豐田邀請參与廠商悉心舉辦的“食貨大玩咖的美食探尋之旅”,正中下懷。

兵馬未動,糧草先行,首個行程便是前往遠近馳名的“食在廣州第一家”——廣州酒家,品味嶺南“一盅兩件”的早茶文化。

苦澀中夾雜一絲甘甜,被貪婪的口腔霸佔着,茶的蒸汽朦朧了雙眼,勾勒出朦朧的回憶,但記憶卻不再朦朧。(好了,這逼裝不下去了)

早茶過後,便駕馭豐田致炫1.5L車型前往午飯目的地,人生地不熟的在原廠車載導航的指引下,只用半個多小時便順利到達午飯地點——濱江大公館惠食佳。

飲飽食醉后,馬不停蹄前往場地挑戰地點,媒體老師們駕馭着致炫,各顯神通、爭奪積分,無論是短小狹窄的緊湊型賽道,抑或是蜿蜒小徑,致炫都能游刃有餘,樂趣不言而喻。

這歸功於致炫搭載S-CVT智能無級變速器(模擬8速),優化了動力系統的效率和加速響應性,使車輛的動力輸出更平順,燃油經濟性理想,只可惜變速箱並未配備S擋。

來到位於佛山北滘的晚飯地點——蚝專家,自不然要一嘗新鮮地道的海鮮盛宴。

這次致炫讓我們收穫了廣佛兩城的獨特味道,在快節奏的城市生活中與致炫偷得一絲閑逸,體驗新款致炫的多項升級。未來,致炫將憑藉其獨有的產品魅力,俘獲更多年輕人。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

明明買了奔馳寶馬卻被人嘲笑 我到底做錯了什麼

雖然面對他們的嘲笑我表示不解,但是車還是要買,聽說買了奔馳,妹子就會自動上車。於是我又買了輛最新款的SUV。結果還是被他們嘲笑。後來我又換了寶馬。路虎。但是結果都是一樣,我覺得不要買那麼好的車了,太容易買到假貨,車要那麼好的品牌幹嘛,能夠遮風擋雨代步就夠了,所以下定決心買一輛吉利熊貓,結果買回去他們卻說,我買車的這段時間,他們的臉上都長肌肉了。

眼看年關將至,為了過年回家的時候能在親戚朋友面前,展示一下自己一年的成績,決定去提輛車。一直都有聽朋友說本田的超跑不錯,還可以爆VTEC,所以決定買輛GK5。

不知怎麼買回來就被朋友取笑,難道在他們眼中Type R才是本田的超跑嗎?後來想了想。

於是我決定換個選擇。雖然超跑很刺激,但是過年車多路滑,安全也很重要,聽說奧迪的quattro四驅很厲害,所以決定去買輛奧迪的SUV。

結果買回來又被他們取笑,難道現在的奧迪是quattro嗎?

雖然面對他們的嘲笑我表示不解,但是車還是要買,聽說買了奔馳,妹子就會自動上車。於是我又買了輛最新款的SUV。

結果還是被他們嘲笑。

後來我又換了寶馬。

路虎。

但是結果都是一樣,我覺得不要買那麼好的車了,太容易買到假貨,車要那麼好的品牌幹嘛,能夠遮風擋雨代步就夠了,所以下定決心買一輛吉利熊貓,結果買回去他們卻說,我買車的這段時間,他們的臉上都長肌肉了。

我的人生觀彷彿在這一刻崩塌。

其實並不是笑話這些車是假車,段子聽聽就完了,畢竟他們除了換了個Logo之外知道自己是老年代步車,並沒有說自己是汽車,不想其他品牌。

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

Zookeeper分佈式鎖

分佈式解決方案源碼,請幫我點個star哦!
原文地址為https://www.cnblogs.com/haixiang/p/13112710.html,轉載請註明出處!

zookeeper客戶端選型

  • 原生zookeeper客戶端,有watcher一次性、無超時重連機制等一系列問題
  • ZkClient,解決了原生客戶端一些問題,一些存量老系統中還在使用
  • curator,提供了各種應用場景(封裝了分佈式鎖,計數器等),新項目首選

分佈式鎖使用場景

在單體項目中jvm中的鎖即可完成需要,但是微服務、分佈式環境下,同一個服務可能部署在多台服務器上,多個jvm之間無法通過常用的jvm鎖來完成同步操作,需要借用分佈式鎖來完成上鎖、釋放鎖。例如在訂單服務中,我們需要根據日期來生成訂單號流水,就有可能產生相同的時間日期,從而出現重複訂單號。(jdk8使用LocalDateTime線程安全,不會存在這樣的問題)

zookeeper分佈式鎖實現原理

  • zookeeper中規定,在同一時刻,不能有多個客戶端創建同一個節點,我們可以利用這個特性實現分佈式鎖。zookeeper臨時節點只在session生命周期存在,session一結束會自動銷毀。
  • watcher機制,在代表鎖資源的節點被刪除,即可以觸發watcher解除阻塞重新去獲取鎖,這也是zookeeper分佈式鎖較其他分佈式鎖方案的一大優勢。

基於臨時節點方案

第一種方案實現較為簡單,邏輯就是誰創建成功該節點,誰就持有鎖,創建失敗的自己進行阻塞,A線程先持有鎖,B線程獲取失敗就會阻塞,同時對/lockPath設置監聽,A線程執行完操作后刪除節點,觸發監聽器,B線程此時解除阻塞,重新去獲取鎖。

我們模仿原生jdk的lock接口設計,採用模板方法設計模式來編寫分佈式鎖,這樣的好處是擴展性強,我們可以快速切換到redis分佈式鎖、數據庫分佈式鎖等實現方式。

創建Lock接口

public interface Lock {
    /**
     * 獲取鎖
     */
    void getLock() throws Exception;

    /**
     * 釋放鎖
     */
    void unlock() throws Exception;
}

AbstractTemplateLock抽象類

public abstract class AbstractTemplateLock implements Lock {
    @Override
    public void getLock() {
        if (tryLock()) {
            System.out.println(Thread.currentThread().getName() + "獲取鎖成功");
        } else {
            //等待
            waitLock();//事件監聽 如果節點被刪除則可以重新獲取
            //重新獲取
            getLock();
        }
    }
    protected abstract void waitLock();
    protected abstract boolean tryLock();
    protected abstract void releaseLock();
    @Override
    public void unlock() {
        releaseLock();
    }
}

zookeeper分佈式鎖邏輯

@Slf4j
public class ZkTemplateLock extends AbstractTemplateLock {
    private static final String zkServers = "127.0.0.1:2181";
    private static final int sessionTimeout = 8000;
    private static final int connectionTimeout = 5000;

    private static final String lockPath = "/lockPath";


    private ZkClient client;

    public ZkTemplateLock() {
        client = new ZkClient(zkServers, sessionTimeout, connectionTimeout);
        log.info("zk client 連接成功:{}",zkServers);
    }

    @Override
    protected void waitLock() {
        CountDownLatch latch = new CountDownLatch(1);

        IZkDataListener listener = new IZkDataListener() {
            @Override
            public void handleDataDeleted(String dataPath) throws Exception {
                System.out.println("監聽到節點被刪除");
                latch.countDown();
            }
            @Override
            public void handleDataChange(String dataPath, Object data) throws Exception {}
        };
        //完成 watcher 註冊
        client.subscribeDataChanges(lockPath, listener);

        //阻塞自己
        if (client.exists(lockPath)) {
            try {
                latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //取消watcher註冊
        client.unsubscribeDataChanges(lockPath, listener);
    }

    @Override
    protected boolean tryLock() {
        try {
            client.createEphemeral(lockPath);
            System.out.println(Thread.currentThread().getName()+"獲取到鎖");
        } catch (Exception e) {
            log.error("創建失敗");
            return false;
        }
        return true;
    }

    @Override
    public void releaseLock() {
       client.delete(this.lockPath);
    }
}

缺點

每次去競爭鎖,都只會有一個線程拿到鎖,當線程數龐大時會發生“驚群”現象,zookeeper節點可能會運行緩慢甚至宕機。這是因為其他線程沒獲取到鎖時都會監聽/lockPath節點,當A線程釋放完畢,海量的線程都同時停止阻塞,去爭搶鎖,這種操作十分耗費資源,且性能大打折扣。

基於臨時順序節點方案

臨時順序節點與臨時節點不同的是產生的節點是有序的,我們可以利用這一特點,只讓當前線程監聽上一序號的線程,每次獲取鎖的時候判斷自己的序號是否為最小,最小即獲取到鎖,執行完畢就刪除當前節點繼續判斷誰為最小序號的節點。

臨時順序節點操作源碼

@Slf4j
public class ZkSequenTemplateLock extends AbstractTemplateLock {
    private static final String zkServers = "127.0.0.1:2181";
    private static final int sessionTimeout = 8000;
    private static final int connectionTimeout = 5000;
    private static final String lockPath = "/lockPath";
    private String beforePath;
    private String currentPath;
    private ZkClient client;

    public ZkSequenTemplateLock() {
        client = new ZkClient(zkServers);
        if (!client.exists(lockPath)) {
            client.createPersistent(lockPath);

        }
        log.info("zk client 連接成功:{}",zkServers);

    }

    @Override
    protected void waitLock() {
        CountDownLatch latch = new CountDownLatch(1);
        IZkDataListener listener = new IZkDataListener() {
            @Override
            public void handleDataDeleted(String dataPath) throws Exception {
                System.out.println("監聽到節點被刪除");
                latch.countDown();
            }
            @Override
            public void handleDataChange(String dataPath, Object data) throws Exception {}
        };
        //給排在前面的節點增加數據刪除的watcher,本質是啟動另一個線程去監聽上一個節點
        client.subscribeDataChanges(beforePath, listener);

        //阻塞自己
        if (client.exists(beforePath)) {
            try {
                System.out.println("阻塞"+currentPath);
                latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //取消watcher註冊
        client.unsubscribeDataChanges(beforePath, listener);
    }

    @Override
    protected boolean tryLock() {
        if (currentPath == null) {
            //創建一個臨時順序節點
            currentPath = client.createEphemeralSequential(lockPath + "/", "lock-data");
            System.out.println("current:" + currentPath);
        }

        //獲得所有的子節點並排序。臨時節點名稱為自增長的字符串
        List<String> childrens = client.getChildren(lockPath);
        //排序list,按自然順序排序
        Collections.sort(childrens);
        if (currentPath.equals(lockPath + "/" + childrens.get(0))) {
            return true;
        } else {
            //如果當前節點不是排第一,則獲取前面一個節點信息,賦值給beforePath
            int curIndex = childrens.indexOf(currentPath.substring(lockPath.length() + 1));
            beforePath = lockPath + "/" + childrens.get(curIndex - 1);
        }
        System.out.println("beforePath"+beforePath);
        return false;
    }

    @Override
    public void releaseLock() {
        System.out.println("delete:" + currentPath);
        client.delete(currentPath);
    }
}

Curator分佈式鎖工具

curator提供了以下種類的鎖:

  • 共享可重入鎖(Shared Reentrant Lock):全局同步鎖,同一時間不會有兩個客戶端持有一個鎖
  • 共享鎖:與共享可重入鎖類似,但是不可重入(有時候會因為這個原因造成死鎖)
  • 共享可重入讀寫鎖
  • 共享信號量
  • Multi Shared Lock:管理多種鎖的容器實體

我們採用第一種Shared Reentrant Lock中的InterProcessMutex來完成上鎖、釋放鎖的的操作

public class ZkLockWithCuratorTemplate implements Lock {
    // zk host地址
    private String host = "localhost";

    // zk自增存儲node
    private String lockPath = "/curatorLock";

    // 重試休眠時間
    private static final int SLEEP_TIME_MS = 1000;
    // 最大重試1000次
    private static final int MAX_RETRIES = 1000;
    //會話超時時間
    private static final int SESSION_TIMEOUT = 30 * 1000;
    //連接超時時間
    private static final int CONNECTION_TIMEOUT = 3 * 1000;
		//curator核心操作類
    private CuratorFramework curatorFramework;

    InterProcessMutex lock;

   public ZkLockWithCuratorTemplate() {
       curatorFramework = CuratorFrameworkFactory.builder()
               .connectString(host)
               .connectionTimeoutMs(CONNECTION_TIMEOUT)
               .sessionTimeoutMs(SESSION_TIMEOUT)
               .retryPolicy(new ExponentialBackoffRetry(SLEEP_TIME_MS, MAX_RETRIES))
               .build();
       curatorFramework.start();
       lock = new InterProcessMutex (curatorFramework, lockPath);
    }

    @Override
    public void getLock() throws Exception {
        //5s后超時釋放鎖
         lock.acquire(5, TimeUnit.SECONDS);
    }

    @Override
    public void unlock() throws Exception {
        lock.release();
    }
}

源碼以及測試類地址

https://github.com/Motianshi/distribute-tool

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

【其他文章推薦】

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

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

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

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

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

通過Nginx、Consul、Upsync實現動態負載均衡和服務平滑發布

前提

前段時間順利地把整個服務集群和中間件全部從UCloud遷移到阿里雲,筆者擔任了架構和半個運維的角色。這裏詳細記錄一下通過NginxConsulUpsync實現動態負載均衡和服務平滑發布的核心知識點和操作步驟,整個體系已經在生產環境中平穩運行。編寫本文使用的虛擬機系統為CentOS7.x,虛擬機的內網IP192.168.56.200

動態負載均衡的基本原理

一般會通過upstream配置Nginx的反向代理池:

http {
    
    upstream upstream_server{
        server 127.0.0.1:8081;
        server 127.0.0.1:8082;
    }

    server {
        listen       80;
        server_name localhost;

        location / {
            proxy_pass http://upstream_server;
        }
    }
}

現在假如8081端口的服務實例掛了需要剔除,那麼需要修改upstream為:

upstream upstream_server{
    # 添加down標記該端口的服務實例不參与負載
    server 127.0.0.1:8081 down;
    server 127.0.0.1:8082;
}

並且通過nginx -s reload重新加載配置,該upstream配置才會生效。我們知道,服務發布時候重啟過程中是處於不可用狀態,正確的服務發布過程應該是:

  • 把該服務從對應的upstream剔除,一般是置為down,告知Nginx服務upstream配置變更,需要通過nginx -s reload進行重載。
  • 服務構建、部署和重啟。
  • 通過探活腳本感知服務對應的端口能夠訪問,把該服務從對應的upstream中拉起,一般是把down去掉,告知Nginx服務upstream配置變更,需要通過nginx -s reload進行重載。

上面的步驟一則涉及到upstream配置,二則需要Nginx重新加載配置(nginx -s reload),顯得比較笨重,在高負載的情況下重新啟動Nginx並重新加載配置會進一步增加系統的負載並可能暫時降低性能。

所以,可以考慮使用分佈式緩存把upstream配置存放在緩存服務中,然後Nginx直接從這個緩存服務中讀取upstream的配置,這樣如果有upstream的配置變更就可以直接修改緩存服務中對應的屬性,而Nginx服務也不需要reload。在實戰中,這裏提到的緩存服務就選用了ConsulNginx讀取緩存中的配置屬性選用了新浪微博提供的NginxC語言模塊nginx-upsync-module。示意圖大致如下:

Consul安裝和集群搭建

ConsulHashicorp公司的一個使用Golang開發的開源項目,它是一個用於服務發現和配置的工具,具備分佈式和高度可用特性,並且具有極高的可伸縮性。Consul主要提供下面的功能:

  • 服務發現。
  • 運行狀況檢查。
  • 服務分塊/服務網格(Service Segmentation/Service Mesh)。
  • 密鑰/值存儲。
  • 多數據中心。

下面是安裝過程:

mkdir /data/consul
cd /data/consul
wget https://releases.hashicorp.com/consul/1.7.3/consul_1.7.3_linux_amd64.zip
# 注意解壓后只有一個consul執行文件
unzip consul_1.7.3_linux_amd64.zip

解壓完成后,使用命令nohup /data/consul/consul agent -server -data-dir=/tmp/consul -bootstrap -ui -advertise=192.168.56.200 -client=192.168.56.200 > /dev/null 2>&1 &即可後台啟動單機的Consul服務。啟動Consul實例后,訪問http://192.168.56.200:8500/即可打開其後台管理UI

下面基於單台虛擬機搭建一個偽集群,關於集群的一些配置屬性的含義和命令參數的解釋暫時不進行展開

# 創建集群數據目錄
mkdir /data/consul/node1 /data/consul/node2 /data/consul/node3
# 創建集群日誌目錄
mkdir /data/consul/node1/logs /data/consul/node2/logs /data/consul/node3/logs

/data/consul/node1目錄添加consul_conf.json文件,內容如下:

{
  "datacenter": "es8-dc",
  "data_dir": "/data/consul/node1",
  "log_file": "/data/consul/node1/consul.log",
  "log_level": "INFO",
  "server": true,
  "node_name": "node1",
  "ui": true,
  "bind_addr": "192.168.56.200",
  "client_addr": "192.168.56.200",
  "advertise_addr": "192.168.56.200",
  "bootstrap_expect": 3,
  "ports":{
    "http": 8510,
    "dns": 8610,
    "server": 8310,
    "serf_lan": 8311,
    "serf_wan": 8312
    }
}

/data/consul/node2目錄添加consul_conf.json文件,內容如下:

{
  "datacenter": "es8-dc",
  "data_dir": "/data/consul/node2",
  "log_file": "/data/consul/node2/consul.log",
  "log_level": "INFO",
  "server": true,
  "node_name": "node2",
  "ui": true,
  "bind_addr": "192.168.56.200",
  "client_addr": "192.168.56.200",
  "advertise_addr": "192.168.56.200",
  "bootstrap_expect": 3,
  "ports":{
    "http": 8520,
    "dns": 8620,
    "server": 8320,
    "serf_lan": 8321,
    "serf_wan": 8322
    }
}

/data/consul/node3目錄添加consul_conf.json文件,內容如下:

{
  "datacenter": "es8-dc",
  "data_dir": "/data/consul/node3",
  "log_file": "/data/consul/node3/consul.log",
  "log_level": "INFO",
  "server": true,
  "node_name": "node3",
  "ui": true,
  "bind_addr": "192.168.56.200",
  "client_addr": "192.168.56.200",
  "advertise_addr": "192.168.56.200",
  "bootstrap_expect": 3,
  "ports":{
    "http": 8530,
    "dns": 8630,
    "server": 8330,
    "serf_lan": 8331,
    "serf_wan": 8332
    }
}

新建一個集群啟動腳本:

cd /data/consul
touch service.sh
# /data/consul/service.sh內容如下:
nohup /data/consul/consul agent -config-file=/data/consul/node1/consul_conf.json > /dev/null 2>&1 &
sleep 10
nohup /data/consul/consul agent -config-file=/data/consul/node2/consul_conf.json -retry-join=192.168.56.200:8311 > /dev/null 2>&1 &
sleep 10
nohup /data/consul/consul agent -config-file=/data/consul/node3/consul_conf.json -retry-join=192.168.56.200:8311 > /dev/null 2>&1 &

如果集群啟動成功,觀察節點1中的日誌如下:

通過節點1的HTTP端點訪問後台管理頁面如下(可見當前的節點1被標記了一顆紅色的星星,說明當前節點1是Leader節點):

至此,Consul單機偽集群搭建完成(其實分佈式集群的搭建大同小異,注意集群節點所在的機器需要開放使用到的端口的訪問權限),由於Consul使用Raft作為共識算法,該算法是強領導者模型,也就是只有Leader節點可以進行寫操作,因此接下來的操作都需要使用節點1的HTTP端點,就是192.168.56.200:8510

重點筆記:如果Consul集群重啟或者重新選舉,Leader節點有可能發生更變,外部使用的時候建議把Leader節點的HTTP端點抽離到可動態更新的配置項中或者動態獲取Leader節點的IP和端口。

Nginx編譯安裝

直接從官網下載二進制的安裝包並且解壓:

mkdir /data/nginx
cd /data/nginx
wget http://nginx.org/download/nginx-1.18.0.tar.gz
tar -zxvf nginx-1.18.0.tar.gz

解壓后的所有源文件在/data/nginx/nginx-1.18.0目錄下,編譯之前需要安裝pcre-develzlib-devel依賴:

yum -y install pcre-devel
yum install -y zlib-devel

編譯命令如下:

cd /data/nginx/nginx-1.18.0
./configure --prefix=/data/nginx

如果./configure執行過程不出現問題,那麼結果如下:

接着執行make

cd /data/nginx/nginx-1.18.0
make

如果make執行過程不出現問題,那麼結果如下:

最後,如果是首次安裝,可以執行make install進行安裝(實際上只是拷貝編譯好的文件到--prefix指定的路徑下):

cd /data/nginx/nginx-1.18.0
make install

make install執行完畢后,/data/nginx目錄下新增了數個文件夾:

其中,Nginx啟動程序在sbin目錄下,logs是其日誌目錄,conf是其配置文件所在的目錄。嘗試啟動一下Nginx

/data/nginx/sbin/nginx

然後訪問虛擬機的80端口,從而驗證Nginx已經正常啟動:

通過nginx-upsync-module和nginx_upstream_check_module模塊進行編譯

上面做了一個Nginx極簡的編譯過程,實際上,在做動態負載均衡的時候需要添加nginx-upsync-modulenginx_upstream_check_module兩個模塊,兩個模塊必須提前下載源碼,並且在編譯Nginx過程中需要指定兩個模塊的物理路徑:

mkdir /data/nginx/modules
cd /data/nginx/modules
# 這裡是Github的資源,不能用wget下載,具體是:
nginx-upsync-module需要下載release裏面的最新版本:v2.1.2
nginx_upstream_check_module需要下載整個項目的源碼,主要用到靠近當前版本的補丁,使用patch命令進行補丁升級

下載完成後分別(解壓)放在/data/nginx/modules目錄下:

ll /data/nginx/modules
drwxr-xr-x. 6 root root   4096 Nov  3  2019 nginx_upstream_check_module-master
drwxrwxr-x. 5 root root     93 Dec 18 00:56 nginx-upsync-module-2.1.2

編譯前,還要先安裝一些前置依賴組件:

yum -y install libpcre3 libpcre3-dev ruby zlib1g-dev patch

接下來開始編譯安裝Nginx

cd /data/nginx/nginx-1.18.0
patch -p1 < /data/nginx/modules/nginx_upstream_check_module-master/check_1.16.1+.patch
./configure --prefix=/data/nginx --add-module=/data/nginx/modules/nginx_upstream_check_module-master --add-module=/data/nginx/modules/nginx-upsync-module-2.1.2
make
make install

上面的編譯和安裝過程無論怎麼調整,都會出現部分依賴缺失導致make異常,估計是這兩個模塊並不支持太高版本的Nginx。(生產上用了一個版本比較低的OpenResty,這裏想復原一下使用相對新版本Nginx的踩坑過程)於是嘗試降級進行編譯,下面是參考多個Issue后得到的相對比較新的可用版本組合:

  • nginx-1.14.2.tar.gz
  • xiaokai-wang/nginx_upstream_check_module,使用補丁check_1.12.1+.patch
  • nginx-upsync-module:release:v2.1.2
# 提前把/data/nginx下除了之前下載過的modules目錄外的所有文件刪除
cd /data/nginx
wget http://nginx.org/download/nginx-1.14.2.tar.gz
tar -zxvf nginx-1.14.2.tar.gz

開始編譯安裝:

cd /data/nginx/nginx-1.14.2
patch -p1 < /data/nginx/modules/nginx_upstream_check_module-master/check_1.12.1+.patch
./configure --prefix=/data/nginx --add-module=/data/nginx/modules/nginx_upstream_check_module-master --add-module=/data/nginx/modules/nginx-upsync-module-2.1.2
make && make install

安裝完成后通過/data/nginx/sbin/nginx命令啟動即可。

啟用動態負載均和健康檢查

首先編寫一個簡易的HTTP服務,因為Java比較重量級,這裏選用Golang,代碼如下:

package main

import (
	"flag"
	"fmt"
	"net/http"
)

func main() {
    var host string
    var port int
    flag.StringVar(&host, "h", "127.0.0.1", "IP地址")
    flag.IntVar(&port, "p", 9000, "端口")
    flag.Parse()
    address := fmt.Sprintf("%s:%d", host, port)
    http.HandleFunc("/ping", func(writer http.ResponseWriter, request *http.Request) {
        _, _ = fmt.Fprintln(writer, fmt.Sprintf("%s by %s", "pong", address))
    })
    http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
        _, _ = fmt.Fprintln(writer, fmt.Sprintf("%s by %s", "hello world", address))
    })
    err := http.ListenAndServe(address, nil)
    if nil != err {
        panic(err)
    }
}

編譯:

cd src
set GOARCH=amd64
set GOOS=linux
go build -o ../bin/app app.go

這樣子在項目的bin目錄下就得到一個Linux下可執行的二進制文件app,分別在端口90009001啟動兩個服務實例:

# 記得先給app文件的執行權限chmod 773 app
nohup ./app -p 9000 >/dev/null 2>&1 &
nohup ./app -p 9001 >/dev/null 2>&1 &

修改一下Nginx的配置,添加upstream

# /data/nginx/conf/nginx.conf部分片段
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;

    upstream app {
       # 這裡是consul的leader節點的HTTP端點
       upsync 192.168.56.200:8510/v1/kv/upstreams/app/ upsync_timeout=6m upsync_interval=500ms upsync_type=consul strong_dependency=off;
       # consul訪問不了的時候的備用配置
       upsync_dump_path /data/nginx/app.conf;
       # 這裡是為了兼容Nginx的語法檢查
       include /data/nginx/app.conf;
       # 下面三個配置是健康檢查的配置
       check interval=1000 rise=2 fall=2 timeout=3000 type=http default_down=false;
       check_http_send "HEAD / HTTP/1.0\r\n\r\n";
       check_http_expect_alive http_2xx http_3xx;
    }

    server {
        listen       80;
        server_name  localhost;
        location / {
            proxy_pass http://app;
        }
        # 健康檢查 - 查看負載均衡的列表
        location /upstream_list {
            upstream_show;
        }
        # 健康檢查 - 查看負載均衡的狀態
        location /upstream_status {
            check_status;
            access_log off;
        }
    }
}

# /data/nginx/app.conf
server 127.0.0.1:9000 weight=1 fail_timeout=10 max_fails=3;
server 127.0.0.1:9001 weight=1 fail_timeout=10 max_fails=3;

手動添加兩個HTTP服務進去Consul中:

curl -X PUT -d '{"weight":1, "max_fails":2, "fail_timeout":10}' http://192.168.56.200:8510/v1/kv/upstreams/app/127.0.0.1:9000
curl -X PUT -d '{"weight":1, "max_fails":2, "fail_timeout":10}' http://192.168.56.200:8510/v1/kv/upstreams/app/127.0.0.1:9001

最後重新加載Nginx的配置即可。

動態負載均衡測試

前置工作準備好,現在嘗試動態負載均衡,先從Consul下線9000端口的服務實例:

curl -X PUT -d '{"weight":1, "max_fails":2, "fail_timeout":10, "down":1}' http://192.168.56.200:8510/v1/kv/upstreams/app/127.0.0.1:9000

可見負載均衡的列表中,9000端口的服務實例已經置為down,此時瘋狂請求http://192.168.56.200,只輸出hello world by 127.0.0.1:9001,可見9000端口的服務實例已經不再參与負載。重新上線9000端口的服務實例:

curl -X PUT -d '{"weight":1, "max_fails":2, "fail_timeout":10, "down":0}' http://192.168.56.200:8510/v1/kv/upstreams/app/127.0.0.1:9000

再瘋狂請求http://192.168.56.200,發現hello world by 127.0.0.1:9000hello world by 127.0.0.1:9001交替輸出。到此可以驗證動態負載均衡是成功的。此時再測試一下服務健康監測,通過kill -9隨機殺掉其中一個服務實例,然後觀察/upstream_status端點:

瘋狂請求http://192.168.56.200,只輸出hello world by 127.0.0.1:9001,可見9000端口的服務實例已經不再參与負載,但是查看Consul9000端口的服務實例的配置,並沒有標記為down,可見是nginx_upstream_check_module為我們過濾了異常的節點,讓這些節點不再參与負載。

總的來說,這個相對完善的動態負載均衡功能需要nginx_upstream_check_modulenginx-upsync-module共同協作才能完成。

服務平滑發布

服務平滑發布依賴於前面花大量時間分析的動態負載均衡功能。筆者所在的團隊比較小,所以選用了阿里雲的雲效作為產研管理平台,通過裏面的流水線功能實現了服務平滑發布,下面是其中一個服務的生產環境部署的流水線:

其實平滑發布和平台的關係不大,整體的步驟大概如下:

步驟比較多,並且涉及到大量的shell腳本,這裏不把詳細的腳本內容列出,簡單列出一下每一步的操作(注意某些步驟之間可以插入合理的sleep n保證前一步執行完畢):

  • 代碼掃描、單元測試等等。
  • 代碼構建,生成構建后的壓縮包。
  • 壓縮包上傳到服務器X中,解壓到對應的目錄。
  • Consul發送指令,把當前發布的X_IP:PORT的負載配置更新為down=1
  • stop服務X_IP:PORT
  • start服務X_IP:PORT
  • 檢查服務X_IP:PORT的健康狀態(可以設定一個時間周期例如120秒內每10秒檢查一次),如果啟動失敗,則直接中斷返回,確保還有另一個正常的舊節點參与負載,並且人工介入處理。
  • Consul發送指令,把當前發布的X_IP:PORT的負載配置更新為down=0

上面的流程是通過hard code完成,對於不同的服務器,只需要添加一個發布流程節點並且改動一個IP的佔位符即可,不需要對Nginx進行配置重新加載。筆者所在的平台流量不大,目前每個服務部署兩個節點就能滿足生產需要,試想一下,如果要實現動態擴容,應該怎麼構建流水線?

小結

服務平滑發布是CI/CD中比較重要的一個環節,而動態負載均衡則是服務平滑發布的基礎。雖然現在很多雲平台都提供了十分便捷的持續集成工具,但是在使用這些工具和配置流程的時候,最好能夠理解背後的基本原理,這樣才能在工具不適用的時候或者出現問題的時時候,迅速地作出判斷和響應。

參考資料:

  • nginx-upsync-module
  • Nginx docs
  • Consul docs

(本文完 c-7-d e-a-20200613 感謝廣州某金融科技公司運維大佬昊哥提供的支持)

技術公眾號(《Throwable文摘》),不定期推送筆者原創技術文章(絕不抄襲或者轉載):

娛樂公眾號(《天天沙雕》),甄選奇趣沙雕圖文和視頻不定期推送,緩解生活工作壓力:

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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

(數據科學學習手札87)利用adjustText解決matplotlib文字標籤遮擋問題

本文示例代碼、數據已上傳至我的Github倉庫https://github.com/CNFeffery/DataScienceStudyNotes

1 簡介

  在進行數據可視化時我們常常需要在可視化作品上進行一些文字標註,譬如對散點圖我們可以將每個散點對應的屬性信息標註在每個散點旁邊,但隨着散點量的增多,或圖像上的某個區域聚集了較多的散點時,疊加上的文字標註會擠在一起相互疊置,出現如圖1所示的情況:

圖1

  出現這種情況非常影響數據可視化作品的呈現效果,而我們下面要介紹的adjustText是一個輔助matplotlib所繪製的圖像自動調整文字位置以緩解遮擋現象的庫,其靈感來源於R中非常著名的輔助ggplot2解決文字遮擋問題的ggrepel

圖2

  它通過算法迭代,在一輪輪的迭代過程中逐漸消除文字遮擋現象:

圖3

  下面我們就來學習如何使用adjustText解決matplotlib圖像文字遮擋問題。

2 使用adjustText解決文字遮擋問題

2.1 從一個簡單的例子出發

  使用pip install adjustTextconda install -c conda-forge adjusttext 來安裝adjustText。安裝成功之後,首先生成隨機示例數據以方便之後的演示:

import matplotlib.pyplot as plt
from adjustText import adjust_text
import numpy as np

#解決中文显示問題
plt.rcParams['font.sans-serif'] = ['SimHei']

seed = np.random.RandomState(42) # 固定隨機數水平
x, y = seed.uniform(0, 1, [2, 100]) # 產生固定的均勻分佈隨機數
texts = [f'文字{i}' for i in range(x.__len__())]

  接着我們先不使用adjustText調整圖像,直接繪製出原始的散點+文字標籤

fig, ax = plt.subplots(figsize=(8, 8))
ax.scatter(x, y, c='SeaGreen', s=10) # 繪製散點

# 繪製所有點對應的文字標籤
for x_, y_, text in zip(x, y, texts):
    plt.text(x_, y_, text, fontsize=12)

# 美觀起見隱藏頂部與右側邊框線
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)

fig.savefig('圖4.png', dpi=300, bbox_inches='tight', pad_inches=0) # 保存圖像

圖4

  可以看到,在通常的情況下,散點聚集的區域內文字標籤非常容易重疊在一起,接下來我們使用adjustText的基礎功能來消除文字重疊現象:

圖5

  這時可以看到與圖4相比,圖5中的所有文字都沒有出現彼此重疊現象,adjustText幫助我們自動微調了文字的擺放位置,並且距離原始散點偏移較大的文字還貼心的加上了連接線,至此,我們就初探了adjustText的強大功能,接下來我們來學習adjustText的更多功能。

2.2 adjust_text的用法

  adjustText中的核心功能都通過調用函數adjust_text來實現,其核心參數如下:

texts:List型,每個元素都是表示單個文字標籤對應的matplotlib.text.Text對象

ax:繪製文字標籤的目標axe對象,默認為最近一次的axe對象

lim:int型,控制迭代調整文本標籤位置的次數,默認為500次

precision:float型,用於決定迭代停止的精度,默認為0.01,即所有標籤相互遮擋部分的長和寬占所有標籤自身長寬之和的比例,addjust_text會在精度達到precision和迭代次數超過lim這兩個條件中至少有一個滿足時停止迭代

only_move:字典型,用於指定文本標籤與不同對象發生遮擋時的位移策略,鍵有'points''text''objects',對應的值可選'xy''x''y',分別代表豎直和水平方向均調整、只調整水平方向以及只調整豎直方向

arrowprops:字典型,用於設置偏移后的文字標籤與原始位置之間的連線樣式,下文會作具體演示

save_steps:bool型,用於決定是否保存記錄迭代過程中各輪的幀圖像,默認為False

save_prefix:str型,當save_steps設置為True時,用於指定中間幀保存的路徑,默認為”,即當前工作路徑

  下面我們來演示一下這些參數的使用效果,首先我們來看看only_move參數的效果,在圖6的基礎上,我們設置only_move={'text': 'x'},即當文字出現遮擋時,只在水平方向上進行偏移,這裏將save_steps設置為True以直觀地查看偏移過程:

fig, ax = plt.subplots(figsize=(8, 8))
ax.scatter(x, y, c='SeaGreen', s=10) # 繪製散點

# 使用adjustText修正文字重疊現象
new_texts = [plt.text(x_, y_, text, fontsize=12) for x_, y_, text in zip(x, y, texts)]
adjust_text(new_texts, 
            only_move={'text': 'x'},
            arrowprops=dict(arrowstyle='-', color='grey'),
            save_steps=True)

# 美觀起見隱藏頂部與右側邊框線
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)

圖6

  可以看到在整個迭代微調的過程中,每個標籤只在水平方向發生位移,你可以根據自己作圖的實際需要靈活調整這裏的平移策略。接下來我們來看看arrowprops對可視化結果的影響,在之前的例子里我們設置了arrowprops={arrowstyle='-', color='grey'},其中arrowstyle用於設定連線的線型,color不用多說,接下來我們添加參數lw用於控制線的寬度,並對線型與顏色進行修改:

fig, ax = plt.subplots(figsize=(8, 8))
ax.scatter(x, y, c='SeaGreen', s=10) # 繪製散點

# 使用adjustText修正文字重疊現象
new_texts = [plt.text(x_, y_, text, fontsize=12) for x_, y_, text in zip(x, y, texts)]
adjust_text(new_texts, 
            arrowprops=dict(arrowstyle='->', 
                            color='red',
                            lw=1))

# 美觀起見隱藏頂部與右側邊框線
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)

fig.savefig('圖7.png', dpi=300, bbox_inches='tight', pad_inches=0) # 保存圖像

  這時連線隨着我們自定義的設置改變到相應的樣式:

圖7

  有關adjustText的更多參數設置信息和示例可以去官方文檔(https://adjusttext.readthedocs.io/en/latest/ )查看。

  以上就是本文的全部內容,如有疑問歡迎在評論區與我們討論。

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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