分類
發燒車訊

復聯3上映的深夜,一群男人卻鑽進地庫嗨翻了天

對於性能向改裝玩家來說,不管平時駕駛時到底會不會時刻關注如此豐富的數據,像這樣的三聯表是必須裝上的。十代思域顯然也是如今性能向改裝的主力車型,1。5T渦輪增壓發動機有着相當不錯的改裝潛力。“網紅車”五菱宏光現身。

相信大部分車迷都會對改裝車感興趣,甚至自己本身就是一位改裝車玩家,而對於廣大改裝車愛好者來說,靜態車聚就是一種讓大家能夠零距離接觸各類改裝案例,同時大量車友交流改裝心得的活動之一。很多改裝團體會選擇地下停車場這種封閉性公共場所作為大型改裝車聚的舉辦地,而在廣州,卻從未有過此類車聚。日前,趁着熱度極高的漫威電影《復讎者聯盟3》的國內上映,一個名為Aibo par的“地庫車聚”在廣州某商場舉行,自己作為一個車迷,筆者當然不會錯過這場被稱為“廣州首次”的地庫車聚。

與珠三角地區常見的賽道日活動相比,地庫車聚更多的還是驚喜改裝車的靜態展示,因此在這裏集結的改裝車大多以外觀、內飾改裝為核心,每一台改裝車都显示出鮮明的個性。

低姿態

“姿態”,這是外觀向改裝一個非常常用的詞語,是一台車改裝水平的重要評定標準,而所謂姿態,涉及到離地間隙、車輪傾角、輪轂選擇等多個方面,但在國內的改裝愛好者群體中,“低趴”就是最受歡迎的改裝風格之一。

就在會場簽到處,主辦方就擺出了兩台霸氣的VIp風格改裝作品,巨大的傾角、嵌入翼子板的大尺寸輪轂,讓這台奔馳S級和豐田皇冠看起來霸氣十足。

豐田皇冠是國內的VIp風格主流改裝用車,其中以第12代皇冠最為常見。

除了多基於中大型甚至大型轎車的VIp風格之外,在這裏還要提到一個改裝風格的專用名詞:“Hella Flush”。這種非常流行的外觀改裝風格要求輪轂邊緣與輪眉平齊,大部分玩家會選擇換裝氣動避震來實現最大幅度降低車身的效果,而在行駛時又可將離地間隙提升,保證一定的實用性。

克萊斯勒300C是國內難得的純種美式轎車,粗獷的線條搭配巨大的鍍鉻輪轂,盡顯美式風範。

什麼?國內竟有豐田bB?淡定,這是被改造成豐田bB外觀的長城酷熊。

外觀向改裝玩家可不只是會對外觀進行改造,他們還會用一些小物件來裝飾愛車的內部,模型車、玩偶、汽車主題貼紙等各式各樣的裝飾物看得筆者眼花繚亂。

這位車主直接將自己的酷熊打造成了移動的日式工藝品專賣店。

由於靜態車聚中常會有車友打開前蓋展示精心改造的發動機艙,為了讓愛車美得更全面,發動機蓋的背面也是不能放過的。

對於趴地玩家來說,前後包圍被蹭壞甚至掉落是一項必修課程,包圍還沒掉下來?用黃黑膠布貼上“假裝”掉了也可以。

“鑽桿”是低趴改裝玩家在車聚中最喜愛的活動,車友們設法降低車身離地高度,開車鑽過一根限定高度的杆子,以評比出車身最低的車型。

既然是漫威電影的首映車聚,怎能少得了鋼鐵俠的身影?

猜猜這台有着哆啦A夢塗裝的是什麼車?記得在下方留言框把你猜到的答案告訴我們哦!

性能派

當然,雖說靜態車聚是外觀向改裝的主場主場,但這不代表性能派就不能出現了。

豐田86顯然是最受歡迎的日系跑車之一,也是各類賽道日活動的常見車型;只可惜這款超級實惠的后驅小跑車如今已經退出中國內地市場了。

素有“平民超跑”之稱的飛度GK5,是改裝界最火爆的車型之一,能夠駕馭從低趴到性能的多種改裝風格。

比亞迪F0被降低車身,換上競技輪轂之後,突然有了一種性能小鋼炮的感覺。

這台狀態上佳的第八代STI是全場性能車的焦點之一。

對於性能向改裝玩家來說,不管平時駕駛時到底會不會時刻關注如此豐富的數據,像這樣的三聯表是必須裝上的。

十代思域顯然也是如今性能向改裝的主力車型,1.5T渦輪增壓發動機有着相當不錯的改裝潛力。

“網紅車”五菱宏光現身!

這個巨大的碳纖維進氣風箱是必須秀出來的。

市售兩千多元的電動渦輪增壓器,它的增壓值能有0.1個Bar么?

本田高性能圖騰思域Type R一現身,大批車友上前圍觀拍照,這人氣,恐怕是法拉利的旗艦跑車都無法比擬。

經典派

現場還能見到許多經典車的身影,比如近年流行起來的桑塔納,以及老皇冠、虎頭奔、Spura等車型都看得筆者口水直流。

近年老款桑塔納在國內再次流行起來,這次車聚就出現了不少桑塔納的身影。

曾幾何時,斯巴魯在中國和貴州雲雀合作,以合資生產的方式推出了雲雀小公主,也就是斯巴魯Vivio;而圖中這台可不是國內最常見的雲雀小公主,而是一台正兒八經的斯巴魯Vivio!

W140“虎頭奔”登場,哪怕這隻是一台S320,但是氣場也是足夠強大的。

斯巴魯Vivio的Bistro版本,而這台則是非常少見的雙門車型。

喲!豐田Supra出租車!請問起步價多少?

寶馬推出的第二款Z系列跑車:Z3。

這台皇冠155的車主看來頗有美國情懷啊,但是這樣的國旗搭配,不應該用在經典的紅旗或者林肯、凱迪拉克身上嗎?

豐田在90年代推出的緊湊型轎車Altezza,在國內以雷克薩斯第一代IS的身份銷售,但國內車主更願意用它的日本名字:Altezza,或者親切地叫它“咬地鯊”。

大跑車

在這一次地庫車聚中,吸引眼球的可不止是那些低姿態、性能車和經典車,現場更有大批跑車現身,對於車迷來說,這些又帥又快的高性能跑車就是心中的Dream Cars。

國內罕見的阿爾法羅密歐4C!也許很快大家就能在玩車TV的推送里見到它哦!

保時捷911 GT3 RS,只可惜國內已買不到手動變速箱版本。

法拉利,作為世界超級跑車標杆的地位也阻止不了車主給它換上氣動避震,玩起了姿態。

氣動避震的儲氣罐就隱藏在車頭的狹小行李箱中。

結語

這場看似簡單的地庫車聚,當晚共吸引了數百台改裝車前來參与,用盡了活動區域的每一個車位;地庫的悶熱和難以消散的廢氣都無法阻擋改裝車愛好者的熱情。拋開電影上映這個活動主題不說,實際上絕大部分前來參与車聚的人都是被改裝車本身所吸引,能夠讓過千人在一個擁擠的地下停車場玩得如此盡興,也許就只有改裝車聚這一種活動能夠做到了。

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

【其他文章推薦】

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

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

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

※超省錢租車方案

FB行銷專家,教你從零開始的技巧

分類
發燒車訊

8萬就夠,近期最多人關注的3款6座車都這麼給力,買誰好?

而寶駿360第三排的空間表現不錯,美中不足的是其坐墊對於大腿的承托稍顯不足。比亞迪宋MAX 6座版表現要比7座版本更為靈活和更為寬鬆,不管前排、中排還是後排都能夠找到舒適輕鬆的坐姿。扒一扒內飾寶駿360:中控檯布局與寶駿510相似,整體內飾層次感較為豐富,配合懸浮式中控屏以及液晶儀錶盤,整個內飾顯得科技感十足。

什麼車廠最了解國內消費者的需要?答案必定是本土車廠。想必大家也知道2018車輛年檢時間有新規定,非營運轎車享有6年內免上線檢測政策,可尷尬的是現在許多MpV車型是7座的布局,因此許多MpV被迫要一年一檢。對此本土車廠紛紛為消費者排憂解難,相繼推出了6座MpV車型,讓新車可以滿足新規從而6年內免上線檢測。接下來我們看看最近新上市的3款6座MpV吧。

寶駿作為一個善於打造爆款車型的品牌,不僅能打造出高性價比的車型,更重要的是它清晰當今消費者需求,上周剛推出了旗下第二款MpV車型——寶駿360,該車瞄準的就是6座MpV這一細分市場。

全新的歐尚A600是長安繼歐尚A800后又一力作,從命名來看,其定位是在A800之下。從外觀跟內飾看,歐尚A600可作為歐尚的升級改款。相比於原本的歐尚,多了一種動力組成,也多了自動擋車型的選擇,這樣的變化讓歐尚A600更加的親民。

作為爆款車型的比亞迪宋MAX也推出了六座車型,可見比亞迪對於六座MpV這一細分市場的重視程度。新車在尺寸上保持與7座車型一致,減少了一個座位,換來的是更寬敞的中排空間以及能更方便進出第三排的中央通道。

比一比尺寸

通過三車尺寸數據的對比可以看出比亞迪宋MAX佔據一定的優勢,尤其是在軸距以及寬度方面。由此可以推斷出比亞迪宋MAX的車內空間表現應為三車之最。寶駿360在尺寸上比歐尚A600稍微大一些,可是軸距上的差距是明顯的,其軸距已貼近宋MAX,換句話說寶駿360在車廂內的空間不比宋MAX差太多。

經過實測也驗證了前文的觀點,可以看到,三台車前排空間是足夠寬敞。受限於軸距,長安歐尚A600在第二排以及第三排在腿部空間上稍顯局促,應付日常短途出行問題不大。而寶駿360第三排的空間表現不錯,美中不足的是其坐墊對於大腿的承托稍顯不足。比亞迪宋MAX 6座版表現要比7座版本更為靈活和更為寬鬆,不管前排、中排還是後排都能夠找到舒適輕鬆的坐姿。

扒一扒內飾

寶駿360:中控檯布局與寶駿510相似,整體內飾層次感較為豐富,配合懸浮式中控屏以及液晶儀錶盤,整個內飾顯得科技感十足。

長安歐尚A600:內飾與歐尚相似,連貫式儀錶和中控台是一討喜的設計,細節部分歐尚A600與歐尚有着細微的差別,比如空調出風口飾板的微調以及空調控制區域按鈕的布局,這些微調使歐尚A600較於歐尚更為精緻。

比亞迪宋MAX:6座版本內飾布局與7座完全一致,在內飾的配色上新增了黑+棕色的版本,使整個內飾看上去不再沉悶。

瞧一瞧動力

動力方面,寶駿360搭載與寶駿310系列相同的1.5L自然吸氣發動機,最大輸出功率82kW,和發動機相匹配是6速手動和自動擋。長安歐尚A600動力配置上較為豐富,提供了1.5L和1.6L兩種動力選擇,此外,新車在1.6L車型上均匹配了六速自動變速箱,從而提高了車型的競爭力。比亞迪宋MAX則是熟悉的1.5T渦輪增壓發動機匹配6速手動變速箱或6速雙離合變速箱。

總結:以上三台車都是本土車廠為滿足國內消費者需要而交出的答卷。可以看出歐尚A600與寶駿360均走高性價比路線,歐尚A600自動變速箱是其一個優勢,同樣的價格區間內寶駿360有空間的優勢。相比之下,比亞迪宋MAX6座版整體配置是跟7座版本看齊,更多的可以將其看作為順應新政策而在座椅數上作出的妥協,不過也因為少了一個座,車內的空間表現將比7座版本更好。以上就是最近新上市的三款六座MpV至於要怎麼選,相信各位心中自有答案。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※超省錢租車方案

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

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

分類
發燒車訊

淑女?劈蕉佬?這些雷人車型名字你了解幾個?

有誰想到這款車當年竟然是名棄嬰,四處找人收留。Jimny的型號的原本來自一間叫希望汽車的生產廠家,該公司研製了一部希望之星ON360,但鑒於公司規模,老闆小野定良計劃向三菱汽車推銷代工制權,但三菱礙於希望汽車的往績,並沒有接受這單生意,最後只能硬着頭皮自行生產,並於1967年底上市,可惜銷量不如預期。

每部車都有着自己的型號名,一般來說就是字母加数字的組合,或單純以文字作為型號名,在當中有的更隱藏別具意義的故事,今天就和大家分享幾個有趣的故事。

淑女的由來

“惡魔Z”、“大魔王”這些都是我們對Fairlady的尊稱,日產經典跑車系列Fairlady這個名字的靈感來源於百老匯歌舞劇My Fair Lady, 1961年時任社長川又克二正在美國公幹,機緣巧合下觀看了這套長壽歌舞劇,同期美國即將推出新款跑車SLp213,一下子就決定新車冠以Fairlady之名,希望它像該歌劇一樣受歡迎,並成為經典。

380萬中挑一

Sunny(陽光)於1966年推出,曾經是日產主力家庭用車,當年日產在報紙廣告上預告推出一升的新車,並進行募集車名活動,在緊接一輪廣告攻勢逐步發布新車細節后,接連一個月宣傳吸引了848萬人參與,收到共380萬個名字推薦,最後選定了切合車型概念的Sunny為名。

南美洲“劈蕉佬”

不少越野發燒友及工程人員鍾情的三菱pajero(帕傑羅),因其英文發音和粵語里的“劈蕉佬”有着99%的相似度,瞬間顯得親民又剛強。至於pajero名稱的起源,則是取自於生活在阿根廷南部高原地帶的一種山貓,它活躍於崎嶇山間,正好吻合pajero的強勁野外走破性能,更讓這隻猛獸衝出南美洲,穿梭於全球各地山野。

人棄我取,終成經典

硬派越野車一直給人一種高大上的感覺,但也有例外,鈴木Jimny(吉姆尼)就是這樣一款小巧見稱的越野車,即使到了今天仍然是不少越野愛好者的寵兒。有誰想到這款車當年竟然是名棄嬰,四處找人收留。Jimny的型號的原本來自一間叫希望汽車的生產廠家,該公司研製了一部希望之星ON360,但鑒於公司規模,老闆小野定良計劃向三菱汽車推銷代工制權,但三菱礙於希望汽車的往績,並沒有接受這單生意,最後只能硬着頭皮自行生產,並於1967年底上市,可惜銷量不如預期。於是小野定良改去向鈴木汽車負責人鈴木修推銷此車,鈴木修對此車一見鍾情,即使公司上下都反對仍堅持一己之見,最終以1,200萬日元向希望汽車買斷整個設計,並於1970年4月推出初代Jimny,造就這部鈴木的招牌車款。

買回來的名字

在Integra未面世之前,prelude(廣東及港澳地區稱“披露”)就是本田coupe車款的代表,也是展現本田各種黑科技的試驗田。DOHC VTEC、雙搖臂懸架及四輪轉向系統等配置早早就出現在prelude之上,中文意謂“前奏曲”的prelude名字原為豐田擁有,當本田於70年代末期決定以此為新車型號后才發現早已被豐田註冊了名字。本來最簡單的方法就是改名,但當年本田執意鍾情於prelude這個名字,於是主動向豐田提出轉讓要求,結果一輪商議后prelude終於歸入本田名下。雖然雙方未有提到當中細節,不過相信離不開用金錢來解決,而這個買回來的名字就一直使用到2001年第五代prelude停產為止,歷時23年。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

分類
發燒車訊

近5米長,舒適度同級無敵,這款18.99萬起的車值不值?

雪鐵龍C6上配備的1。8T發動機是pSA集團在中國最高階的發動機。這台發動機的賬面數據和實際表現當然比不上對手最高階的發動機,但是比起他們中階發動機,倒也不至於落後。傳動系統是來自愛信的6AT變速箱,這台變速箱可以說拖了後腿,競爭對手大多數用上了8、9AT或者7速雙離合的產品。

在2008年,C6曾經以中大型車的身份引進中國。當時的雪鐵龍C6是C級車的身份,售價高達64萬。時過境遷,當C6再次回到國人視線里時,已經是B級車的身份,而且售價也僅需18.99-27.99萬。

這一代的C6是一款中國特供車型,專為國人設計。中國人喜歡的元素,超大後排空間、肉眼可見的豪華感、豐富高端的配置、運動流暢的車身造型,在這台C6上一樣不缺。然而,C6的銷量非常慘淡。好在,今天我的任務不是解答為什麼C6銷量慘淡,而是要告訴你這台車到底有多棒。

雪鐵龍C6的外觀以沉穩為主,前臉的雪鐵龍雙人標識延伸到整個車頭,車頭的線條以水平為主,搭配上大面積的鍍鉻格柵,鑽晶型日間行車燈,彰顯了C6行政級轎車的定位。尾部造型飽滿,銀色的鍍鉻裝飾條連接了兩旁的回字形尾燈,盡顯格調。C6側麵線條流暢動感,缺少了一絲行政級轎車應有的厚重感,而且影響了頭部空間的表現。

雪鐵龍C6的車身尺寸比市面上大多數B級車都要大,甚至可以列入C級車的行列。

上帝在製造法國人的時候,只給了他們優秀的設計能力,卻沒有把將設計付諸現實的能力一併安裝上去。C6車身漆面左右厚薄不一,而且這款白色車漆容易沾灰且難以清潔。

雪鐵龍C6的內飾比外觀做得還好。C6的內飾採用了最新家族式對稱式設計,造型設計更加註重檔次感。材質方面啊,C6的內飾採用了大量的Nappa真皮,實木飾板,同級再也找不到更好的。全液晶儀錶盤的显示效果清晰,可調節的選項豐富,UI設計出色。

C6座椅方面採用了Nappa真皮包裹,座椅寬大,填充物支撐性足。這台C6的頂配車型,前後座椅均有座椅通風/加熱/按摩功能,這種級別的配置也是同級所不具備的。

C6的乘坐空間只有一個字——大。1米73的乘客在前排調好位置后,在後排能夠獲得3拳2指左右的腿部空間。可惜,受流線型車身的影響,頭部空間不容樂觀,前排和後排分別只有3指和4指的表現。

C6的後備廂空間非常大,有523L的容積。不過後排座椅不能放倒,所以沒有擴充的空間。

雪鐵龍C6上配備的1.8T發動機是pSA集團在中國最高階的發動機。這台發動機的賬面數據和實際表現當然比不上對手最高階的發動機,但是比起他們中階發動機,倒也不至於落後。傳動系統是來自愛信的6AT變速箱,這台變速箱可以說拖了後腿,競爭對手大多數用上了8、9AT或者7速雙離合的產品。這款6AT的擋位數就顯得落後了。從匹配上來說,整套動力系統也不夠線性,起步時需要很小心地控制油門才不會突然被“踢”一腳。如果C6能來個ECO模式,相信會好很多。

雪鐵龍C6的0-100km/h實測百公里加速時間為9.4秒。當天有小雨,地面略微有積水,但是加速的過程245mm寬的輪胎幾乎沒有打滑,很平順地就衝出去了。這數據雖然不能跟競爭對手的高階動力系統對比,但是跟他們的1.5T或1.8T的動力對比,還是有競爭力的。

既然雪鐵龍C6作為一款行政級定位的轎車,底盤肯定是以舒適為主的。雪鐵龍C6的減振器和避震彈簧採用斜置的布置方式,而且減振器和避震彈簧另一端連接的是副車架,而不是車身。這麼做的好處,就是減少減振器上垂直方向的力和避免力直接作用於車身,最終達到乘坐舒適的效果。實際體驗下來,雪鐵龍C6確實能很好地將路面顛簸過濾掉,比起同級對手單薄的濾震能力要好上不少,甚至可以說,這是C級車上會有的濾震能力。

雪鐵龍C6的方向盤採用電動液壓助力,路感非常清晰,沒有什麼虛位,回正的力度也線性,只是手感比較重,更加適合男性駕駛。

C6的隔音水平也是行政級水準的。C6四門車窗都使用了雙層隔音玻璃。C6在怠速時幾乎聽不到發動機的聲音,高速跑起來的時候輪胎噪音會相對較大,而風噪不明顯。

從市場定位上來說,與C6有相似定位的B級車,只有別克君越這一款。他們都有舒適的底盤表現,寬敞的乘坐空間,高檔的內飾氛圍。兩款拿出來比較,C6的配置更高,後期保養更便宜,而君越的動力會更完善。如果不考慮品牌知名度,C6會更加合適。當然,如果雪鐵龍的品牌知名度能夠打出來,C6肯定不會是現在這個定價。所以,各位觀眾朋友,要撿便宜趁現在了。

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

FB行銷專家,教你從零開始的技巧

分類
發燒車訊

mingw32 exception在sjlj與dwarf差別-反彙編分析

sjlj (setjump/longjump)與dwarf-2為mingw32兩種異常處理模型的實現。sjlj有着開銷,而隨linux發行的mingw32開發庫包都是用sjlj版編譯的,而Qt卻採用dwarf-2版,那麼兩者之間有多少差異,本文就這問題對兩版的異常代碼的反彙編進行分析比較。

我使用mingw-w65-i686-810的sjlj與dwarf-2兩個版本對下面異常代碼編譯。

__attribute__((dllimport)) int dllfunc();
int main()
{
    dllfunc();
    //_create_locale(LC_ALL, "C");
    printf("abc");
    //return 0;

    try
    {
        try
        {
            throw std::exception();
        }
        catch(std::exception&)
      {
            std::rethrow_exception(std::current_exception());
      }
        
    }
    catch(int)
    {
        
    }
    catch(std::exception& e)
    {
        std::cout << e.what() << std::endl;
    }
    catch(...)
    {
        std::cout << "unknown" << std::endl;
    }
    return 0;
}

代碼邏輯:

兩層 try/catch,

1. 裡層 try/catch

1.1 try塊, throw 異常

1.2 catch塊, rethrow

2. 外層 try/catch

2.1 有三catch分支。

 

開刷前,先定義一下。

如果將 try/catch 去除 c++語言特性后,基本就是一種由c++庫還有c++編譯器共同管理的 goto。

throw相當於goto, catch相當於label(一種以類型區分的)。

那麼c++編譯器與c++庫為我們提供了什麼樣的管理呢?

c++編譯器

0. 利用c++支持對象析構進行try塊保護。

1. 將 throw 關鍵字生成彙編 call __cxa_throw,調用 c++庫的函數。

2. 為每個catch塊生成代碼片斷,只能通過jmp跳轉進來。

2.1 開頭 call __cxa_begin_catch。

2.2 結尾 call __cxa_end_catch。

2.3 最後跳出到 try/catch塊邏輯代碼的下條執行指令。

3. 為同一try/catch塊的所有catch塊產生分支控制代碼。

4. 為try塊的析構代碼產生跳轉入口。

5. 為每一層try/catch塊生成 uncaught 代碼塊,調用 _Unwind_Resume。

c++庫:

1. __cxa_throw,馬上_Unwind_RaiseException。跳轉到當前最裏面一層 try/catch的支路控制代碼片斷。

2. _Unwind_Resume,向上繼續展開。

3. std::rethrow_exception,調用 __gcclibcxx_demangle_callback,

3.1 要麼有 catch可達跳回到原來代碼的控制流,直接離開std::rethrow_exception的調用上下文。

3.2 要麼從__gcclibcxx_demangle_callback返回,執行terminate結束進程。

 

sjlj 版的反彙編代碼比 dwarf-2 版的多了50行。

先來看dwarf-2的反彙編代碼 

  1  <+0>:    lea    0x4(%esp),%ecx
  2  <+4>:    and    $0xfffffff0,%esp
  3  <+7>:    pushl  -0x4(%ecx)
  4  <+10>:    push   %ebp
  5  <+11>:    mov    %esp,%ebp
  6  <+13>:    push   %esi
  7  <+14>:    push   %ebx
  8  <+15>:    push   %ecx
  9  <+16>:    sub    $0x2c,%esp
 10  <+19>:    call   0x401890 <__main>
 11  <+24>:    mov    0x4071a4,%eax
 12  <+29>:    call   *%eax
 13  <+31>:    movl   $0x404045,(%esp)
 14  <+38>:    call   0x4027c4 <printf>
 15  <+43>:    movl   $0x4,(%esp)
 16  <+50>:    call   0x4017ac <__cxa_allocate_exception>
 17  <+55>:    mov    %eax,%ebx
 18  <+57>:    mov    %ebx,%ecx
 19  <+59>:    call   0x402890 <std::exception::exception()>
 20  <+64>:    movl   $0x4017d4,0x8(%esp)
 21  <+72>:    movl   $0x4042a8,0x4(%esp)
 22  <+80>:    mov    %ebx,(%esp)
 23  <+83>:    call   0x401794 <__cxa_throw>
 24  <+88>:    mov    $0x0,%eax
 25  <+93>:    jmp    0x401723 <main()+355>
 26  <+98>:    mov    %edx,%ecx
 27  <+100>:    cmp    $0x2,%ecx
 28  <+103>:    je     0x40162b <main()+107>
 29  <+105>:    jmp    0x401663 <main()+163>
 30  <+107>:    mov    %eax,(%esp)
 31  <+110>:    call   0x4017a4 <__cxa_begin_catch>
 32  <+115>:    mov    %eax,-0x1c(%ebp)
 33  <+118>:    lea    -0x28(%ebp),%eax
 34  <+121>:    mov    %eax,(%esp)
 35  <+124>:    call   0x4017cc <_ZSt17current_exceptionv>
 36  <+129>:    lea    -0x28(%ebp),%eax
 37  <+132>:    mov    %eax,(%esp)
 38  <+135>:    call   0x4017c4 <_ZSt17rethrow_exceptionNSt15__exception_ptr13exception_ptrE>
 39  <+140>:    mov    %eax,%esi
 40  <+142>:    mov    %edx,%ebx
 41  <+144>:    lea    -0x28(%ebp),%eax
 42  <+147>:    mov    %eax,%ecx
 43  <+149>:    call   0x4017ec <_ZNSt15__exception_ptr13exception_ptrD1Ev>
 44  <+154>:    call   0x40179c <__cxa_end_catch>
 45  <+159>:    mov    %esi,%eax
 46  <+161>:    mov    %ebx,%edx
 47  <+163>:    cmp    $0x1,%edx
 48  <+166>:    je     0x40166f <main()+175>
 49  <+168>:    cmp    $0x2,%edx
 50  <+171>:    je     0x401683 <main()+195>
 51  <+173>:    jmp    0x4016ca <main()+266>
 52  <+175>:    mov    %eax,(%esp)
 53  <+178>:    call   0x4017a4 <__cxa_begin_catch>
 54  <+183>:    mov    (%eax),%eax
 55  <+185>:    mov    %eax,-0x24(%ebp)
 56  <+188>:    call   0x40179c <__cxa_end_catch>
 57  <+193>:    jmp    0x401618 <main()+88>
 58  <+195>:    mov    %eax,(%esp)
 59  <+198>:    call   0x4017a4 <__cxa_begin_catch>
 60  <+203>:    mov    %eax,-0x20(%ebp)
 61  <+206>:    mov    -0x20(%ebp),%eax
 62  <+209>:    mov    (%eax),%eax
 63  <+211>:    add    $0x8,%eax
 64  <+214>:    mov    (%eax),%eax
 65  <+216>:    mov    -0x20(%ebp),%edx
 66  <+219>:    mov    %edx,%ecx
 67  <+221>:    call   *%eax
 68  <+223>:    mov    %eax,0x4(%esp)
 69  <+227>:    movl   $0x6ff07a00,(%esp)
 70  <+234>:    call   0x4017b4 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc>
 71  <+239>:    movl   $0x4017bc,(%esp)
 72  <+246>:    mov    %eax,%ecx
 73  <+248>:    call   0x4017f4 <_ZNSolsEPFRSoS_E>
 74  <+253>:    sub    $0x4,%esp
 75  <+256>:    call   0x40179c <__cxa_end_catch>
 76  <+261>:    jmp    0x401618 <main()+88>
 77  <+266>:    mov    %eax,(%esp)
 78  <+269>:    call   0x4017a4 <__cxa_begin_catch>
 79  <+274>:    movl   $0x404049,0x4(%esp)
 80  <+282>:    movl   $0x6ff07a00,(%esp)
 81  <+289>:    call   0x4017b4 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc>
 82  <+294>:    movl   $0x4017bc,(%esp)
 83  <+301>:    mov    %eax,%ecx
 84  <+303>:    call   0x4017f4 <_ZNSolsEPFRSoS_E>
 85  <+308>:    sub    $0x4,%esp
 86  <+311>:    call   0x40179c <__cxa_end_catch>
 87  <+316>:    jmp    0x401618 <main()+88>
 88  <+321>:    mov    %eax,%ebx
 89  <+323>:    call   0x40179c <__cxa_end_catch>
 90  <+328>:    mov    %ebx,%eax
 91  <+330>:    mov    %eax,(%esp)
 92  <+333>:    call   0x402770 <_Unwind_Resume>
 93  <+338>:    mov    %eax,%ebx
 94  <+340>:    call   0x40179c <__cxa_end_catch>
 95  <+345>:    mov    %ebx,%eax
 96  <+347>:    mov    %eax,(%esp)
 97  <+350>:    call   0x402770 <_Unwind_Resume>
 98  <+355>:    lea    -0xc(%ebp),%esp
 99  <+358>:    pop    %ecx
100  <+359>:    pop    %ebx
101  <+360>:    pop    %esi
102  <+361>:    pop    %ebp
103  <+362>:    lea    -0x4(%ecx),%esp
104  <+365>:    ret    

我們的主要代碼邏輯只有20-30條指令

 

 當 throw時,__cxa_throw函數是不會返回的, 如同goto最後是跳轉到他處,若被本層catch處理完才會跳轉回來<+88>。

然後看c++編譯器為我們生成的異常代碼 。

 

 

 

 

 

 對於沒有發生異常時,代碼執行路徑基本不會去涉及到異常代碼支路,開銷幾近為0,只是代碼量增大。

下面來看 sjlj 版的彙編代碼,

  1 function main():
  2  <+0>:    lea    0x4(%esp),%ecx
  3  <+4>:    and    $0xfffffff0,%esp
  4  <+7>:    pushl  -0x4(%ecx)
  5  <+10>:    push   %ebp
  6  <+11>:    mov    %esp,%ebp
  7  <+13>:    push   %edi
  8  <+14>:    push   %esi
  9  <+15>:    push   %ebx
 10  <+16>:    push   %ecx
 11  <+17>:    sub    $0x68,%esp
 12  <+20>:    movl   $0x4017ac,-0x44(%ebp)
 13  <+27>:    movl   $0x402958,-0x40(%ebp)
 14  <+34>:    lea    -0x3c(%ebp),%eax
 15  <+37>:    lea    -0x18(%ebp),%ebx
 16  <+40>:    mov    %ebx,(%eax)
 17  <+42>:    mov    $0x4015b4,%edx
 18  <+47>:    mov    %edx,0x4(%eax)
 19  <+50>:    mov    %esp,0x8(%eax)
 20  <+53>:    lea    -0x5c(%ebp),%eax
 21  <+56>:    mov    %eax,(%esp)
 22  <+59>:    call   0x402790 <_Unwind_SjLj_Register>
 23  <+64>:    call   0x4018b0 <__main>
 24  <+69>:    mov    0x406194,%eax
 25  <+74>:    movl   $0xffffffff,-0x58(%ebp)
 26  <+81>:    call   *%eax
 27  <+83>:    movl   $0x404001,(%esp)
 28  <+90>:    call   0x4027e4 <printf>
 29  <+95>:    movl   $0x4,(%esp)
 30  <+102>:    call   0x4017cc <__cxa_allocate_exception>
 31  <+107>:    mov    %eax,-0x60(%ebp)
 32  <+110>:    mov    %eax,%ecx
 33  <+112>:    call   0x4028b0 <std::exception::exception()>
 34  <+117>:    movl   $0x4017f4,0x8(%esp)
 35  <+125>:    movl   $0x404264,0x4(%esp)
 36  <+133>:    mov    -0x60(%ebp),%eax
 37  <+136>:    mov    %eax,(%esp)
 38  <+139>:    movl   $0x1,-0x58(%ebp)
 39  <+146>:    call   0x4017b4 <__cxa_throw>
 40  <+151>:    mov    $0x0,%eax
 41  <+156>:    mov    %eax,-0x60(%ebp)
 42  <+159>:    jmp    0x401733 <main()+547>
 43  <+164>:    lea    0x18(%ebp),%ebp
 44  <+167>:    mov    -0x54(%ebp),%edx
 45  <+170>:    mov    -0x50(%ebp),%ecx
 46  <+173>:    mov    -0x58(%ebp),%eax
 47  <+176>:    test   %eax,%eax
 48  <+178>:    je     0x4015e6 <main()+214>
 49  <+180>:    sub    $0x1,%eax
 50  <+183>:    test   %eax,%eax
 51  <+185>:    je     0x40161b <main()+267>
 52  <+187>:    sub    $0x1,%eax
 53  <+190>:    test   %eax,%eax
 54  <+192>:    je     0x4016f8 <main()+488>
 55  <+198>:    sub    $0x1,%eax
 56  <+201>:    test   %eax,%eax
 57  <+203>:    je     0x401712 <main()+514>
 58  <+209>:    sub    $0x1,%eax
 59  <+212>:    ud2    
 60  <+214>:    mov    %edx,%eax
 61  <+216>:    mov    %ecx,%edx
 62  <+218>:    mov    %edx,%ecx
 63  <+220>:    cmp    $0x2,%ecx
 64  <+223>:    je     0x4015f3 <main()+227>
 65  <+225>:    jmp    0x401642 <main()+306>
 66  <+227>:    mov    %eax,(%esp)
 67  <+230>:    call   0x4017c4 <__cxa_begin_catch>
 68  <+235>:    mov    %eax,-0x1c(%ebp)
 69  <+238>:    lea    -0x28(%ebp),%eax
 70  <+241>:    mov    %eax,(%esp)
 71  <+244>:    call   0x4017ec <_ZSt17current_exceptionv>
 72  <+249>:    lea    -0x28(%ebp),%eax
 73  <+252>:    mov    %eax,(%esp)
 74  <+255>:    movl   $0x2,-0x58(%ebp)
 75  <+262>:    call   0x4017e4 <_ZSt17rethrow_exceptionNSt15__exception_ptr13exception_ptrE>
 76  <+267>:    mov    %edx,-0x60(%ebp)
 77  <+270>:    mov    %ecx,-0x64(%ebp)
 78  <+273>:    lea    -0x28(%ebp),%eax
 79  <+276>:    mov    %eax,%ecx
 80  <+278>:    call   0x40180c <_ZNSt15__exception_ptr13exception_ptrD1Ev>
 81  <+283>:    mov    -0x60(%ebp),%eax
 82  <+286>:    mov    %eax,-0x60(%ebp)
 83  <+289>:    mov    -0x64(%ebp),%esi
 84  <+292>:    mov    %esi,-0x64(%ebp)
 85  <+295>:    call   0x4017bc <__cxa_end_catch>
 86  <+300>:    mov    -0x60(%ebp),%eax
 87  <+303>:    mov    -0x64(%ebp),%edx
 88  <+306>:    cmp    $0x1,%edx
 89  <+309>:    je     0x40164e <main()+318>
 90  <+311>:    cmp    $0x2,%edx
 91  <+314>:    je     0x401665 <main()+341>
 92  <+316>:    jmp    0x4016b3 <main()+419>
 93  <+318>:    mov    %eax,(%esp)
 94  <+321>:    call   0x4017c4 <__cxa_begin_catch>
 95  <+326>:    mov    (%eax),%eax
 96  <+328>:    mov    %eax,-0x20(%ebp)
 97  <+331>:    call   0x4017bc <__cxa_end_catch>
 98  <+336>:    jmp    0x4015a7 <main()+151>
 99  <+341>:    mov    %eax,(%esp)
100  <+344>:    call   0x4017c4 <__cxa_begin_catch>
101  <+349>:    mov    %eax,-0x24(%ebp)
102  <+352>:    mov    -0x24(%ebp),%eax
103  <+355>:    mov    (%eax),%eax
104  <+357>:    add    $0x8,%eax
105  <+360>:    mov    (%eax),%eax
106  <+362>:    mov    -0x24(%ebp),%edx
107  <+365>:    mov    %edx,%ecx
108  <+367>:    call   *%eax
109  <+369>:    mov    %eax,0x4(%esp)
110  <+373>:    movl   $0x6ff29a00,(%esp)
111  <+380>:    movl   $0x3,-0x58(%ebp)
112  <+387>:    call   0x4017d4 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc>
113  <+392>:    movl   $0x4017dc,(%esp)
114  <+399>:    mov    %eax,%ecx
115  <+401>:    call   0x401814 <_ZNSolsEPFRSoS_E>
116  <+406>:    sub    $0x4,%esp
117  <+409>:    call   0x4017bc <__cxa_end_catch>
118  <+414>:    jmp    0x4015a7 <main()+151>
119  <+419>:    mov    %eax,(%esp)
120  <+422>:    call   0x4017c4 <__cxa_begin_catch>
121  <+427>:    movl   $0x404005,0x4(%esp)
122  <+435>:    movl   $0x6ff29a00,(%esp)
123  <+442>:    movl   $0x4,-0x58(%ebp)
124  <+449>:    call   0x4017d4 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc>
125  <+454>:    movl   $0x4017dc,(%esp)
126  <+461>:    mov    %eax,%ecx
127  <+463>:    call   0x401814 <_ZNSolsEPFRSoS_E>
128  <+468>:    sub    $0x4,%esp
129  <+471>:    movl   $0xffffffff,-0x58(%ebp)
130  <+478>:    call   0x4017bc <__cxa_end_catch>
131  <+483>:    jmp    0x4015a7 <main()+151>
132  <+488>:    mov    %edx,-0x60(%ebp)
133  <+491>:    call   0x4017bc <__cxa_end_catch>
134  <+496>:    mov    -0x60(%ebp),%eax
135  <+499>:    mov    %eax,(%esp)
136  <+502>:    movl   $0xffffffff,-0x58(%ebp)
137  <+509>:    call   0x402788 <_Unwind_SjLj_Resume>
138  <+514>:    mov    %edx,-0x60(%ebp)
139  <+517>:    movl   $0x0,-0x58(%ebp)
140  <+524>:    call   0x4017bc <__cxa_end_catch>
141  <+529>:    mov    -0x60(%ebp),%eax
142  <+532>:    mov    %eax,(%esp)
143  <+535>:    movl   $0xffffffff,-0x58(%ebp)
144  <+542>:    call   0x402788 <_Unwind_SjLj_Resume>
145  <+547>:    lea    -0x5c(%ebp),%eax
146  <+550>:    mov    %eax,(%esp)
147  <+553>:    call   0x402780 <_Unwind_SjLj_Unregister>
148  <+558>:    mov    -0x60(%ebp),%eax
149  <+561>:    lea    -0x10(%ebp),%esp
150  <+564>:    pop    %ecx
151  <+565>:    pop    %ebx
152  <+566>:    pop    %esi
153  <+567>:    pop    %edi
154  <+568>:    pop    %ebp
155  <+569>:    lea    -0x4(%ecx),%esp
156  <+572>:    ret    

下面的分析只列出不同的地方 

 上圖的註釋有誤沒有勘誤過,lea是不訪問內存,通常代替add指令做加法,應該是6條指令要訪問內存。

支路控制代碼:

 

 

 

 

 可以看出,支路選路控制指令多而且複雜,還有就是跳轉多。

最後是函數結束前。

 

 

 

 可以看出在 sjlj 版本中,即使代碼不發生異常,函數在進入與離開時都要為登記維護付出一此成本,當涉及異常代碼時,支路選路控制更加複雜更多跳轉。這裡有一個成本比例,你的函數邏輯簡單,上面的開銷比重就越大,如果是頻繁調用的輕量函數就要考慮不用exception這樣的error handle。

還有就是當發生異常時,需要交給c++庫去管理,不同異常處理模型的實現,有着不同的開銷,本文並沒有涉及到。只是單純從c++庫以外的代碼進行分析,也足夠看出他們之間有着一定的差別。

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

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

分類
發燒車訊

[源碼解析] GroupReduce,GroupCombine 和 Flink SQL group by

[源碼解析] GroupReduce,GroupCombine和Flink SQL group by

目錄

  • [源碼解析] GroupReduce,GroupCombine和Flink SQL group by
    • 0x00 摘要
    • 0x01 緣由
    • 0x02 概念
      • 2.1 GroupReduce
      • 2.2 GroupCombine
      • 2.3 例子
    • 0x03 代碼
    • 0x04 Flink SQL內部翻譯
    • 0x05 JobGraph
    • 0x06 Runtime
      • 6.1 ChainedFlatMapDriver
      • 6.2 GroupReduceCombineDriver
      • 6.3 GroupReduceDriver & ChainedFlatMapDriver
    • 0x07 總結
    • 0x08 參考

0x00 摘要

本文從源碼和實例入手,為大家解析 Flink 中 GroupReduce 和 GroupCombine 的用途。也涉及到了 Flink SQL group by 的內部實現。

0x01 緣由

在前文[源碼解析] Flink的Groupby和reduce究竟做了什麼中,我們剖析了Group和reduce都做了些什麼,也對combine有了一些了解。但是總感覺意猶未盡,因為在Flink還提出了若干新算子,比如GroupReduce和GroupCombine。這幾個算子不搞定,總覺得如鯁在喉,但沒有找到一個良好的例子來進行剖析說明。

本文是筆者在探究Flink SQL UDF問題的一個副產品。起初是為了調試一段sql代碼,結果發現Flink本身給出了一個GroupReduce和GroupCombine使用的完美例子。於是就拿出來和大家共享,一起分析看看究竟如何使用這兩個算子。

請注意:這個例子是Flink SQL,所以本文中將涉及Flink SQL goup by內部實現的知識。

0x02 概念

Flink官方對於這兩個算子的使用說明如下:

2.1 GroupReduce

GroupReduce算子應用在一個已經分組了的DataSet上,其會對每個分組都調用到用戶定義的group-reduce函數。它與Reduce的區別在於用戶定義的函數會立即獲得整個組。

Flink將在組的所有元素上使用Iterable調用用戶自定義函數,並且可以返回任意數量的結果元素。

2.2 GroupCombine

GroupCombine轉換是可組合GroupReduceFunction中組合步驟的通用形式。它在某種意義上被概括為允許將輸入類型 I 組合到任意輸出類型O。與此相對的是,GroupReduce中的組合步驟僅允許從輸入類型 I 到輸出類型 I 的組合。這是因為GroupReduceFunction的 “reduce步驟” 期望自己的輸入類型為 I。

在一些應用中,我們期望在執行附加變換(例如,減小數據大小)之前將DataSet組合成中間格式。這可以通過CombineGroup轉換能以非常低的成本實現。

注意:分組數據集上的GroupCombine在內存中使用貪婪策略執行,該策略可能不會一次處理所有數據,而是以多個步驟處理。它也可以在各個分區上執行,而無需像GroupReduce轉換那樣進行數據交換。這可能會導致輸出的是部分結果,所以GroupCombine是不能替代GroupReduce操作的,儘管它們的操作內容可能看起來都一樣。

2.3 例子

是不是有點暈?還是直接讓代碼來說話吧。以下官方示例演示了如何將CombineGroup和GroupReduce轉換用於WordCount實現。即通過combine操作先對單詞數目進行初步排序,然後通過reduceGroup對combine產生的結果進行最終排序。因為combine進行了初步排序,所以在算子之間傳輸的數據量就少多了

DataSet<String> input = [..] // The words received as input

// 這裏通過combine操作先對單詞數目進行初步排序,其優勢在於用戶定義的combine函數只調用一次,因為runtime已經把輸入數據一次性都提供給了自定義函數。  
DataSet<Tuple2<String, Integer>> combinedWords = input
  .groupBy(0) // group identical words
  .combineGroup(new GroupCombineFunction<String, Tuple2<String, Integer>() {

    public void combine(Iterable<String> words, Collector<Tuple2<String, Integer>>) { // combine
        String key = null;
        int count = 0;

        for (String word : words) {
            key = word;
            count++;
        }
        // emit tuple with word and count
        out.collect(new Tuple2(key, count));
    }
});

// 這裏對combine的結果進行第二次排序,其優勢在於用戶定義的reduce函數只調用一次,因為runtime已經把輸入數據一次性都提供給了自定義函數。  
DataSet<Tuple2<String, Integer>> output = combinedWords
  .groupBy(0)                              // group by words again
  .reduceGroup(new GroupReduceFunction() { // group reduce with full data exchange

    public void reduce(Iterable<Tuple2<String, Integer>>, Collector<Tuple2<String, Integer>>) {
        String key = null;
        int count = 0;

        for (Tuple2<String, Integer> word : words) {
            key = word;
            count++;
        }
        // emit tuple with word and count
        out.collect(new Tuple2(key, count));
    }
});

看到這裏,有的兄弟已經明白了,這和mapPartition很類似啊,都是runtime做了大量工作。為了讓大家這兩個算子的使用情形有深刻的認識,我們再通過一個sql的例子,向大家展示Flink內部是怎麼應用這兩個算子的,也能看出來他們的強大之處

0x03 代碼

下面代碼主要參考自 flink 使用問題匯總。我們可以看到這裏通過groupby進行了聚合操作。其中collect方法,類似於mysql的group_concat。

public class UdfExample {
    public static class MapToString extends ScalarFunction {

        public String eval(Map<String, Integer> map) {
            if(map==null || map.size()==0) {
                return "";
            }
            StringBuffer sb=new StringBuffer();
            for(Map.Entry<String, Integer> entity : map.entrySet()) {
                sb.append(entity.getKey()+",");
            }
            String result=sb.toString();
            return result.substring(0, result.length()-1);
        }
    }

    public static void main(String[] args) throws Exception {
        MemSourceBatchOp src = new MemSourceBatchOp(new Object[][]{
                new Object[]{"1", "a", 1L},
                new Object[]{"2", "b33", 2L},
                new Object[]{"2", "CCC", 2L},
                new Object[]{"2", "xyz", 2L},
                new Object[]{"1", "u", 1L}
        }, new String[]{"f0", "f1", "f2"});

        BatchTableEnvironment environment = MLEnvironmentFactory.getDefault().getBatchTableEnvironment();
        Table table = environment.fromDataSet(src.getDataSet());
        environment.registerTable("myTable", table);
        BatchOperator.registerFunction("MapToString",  new MapToString());
        BatchOperator.sqlQuery("select f0, mapToString(collect(f1)) as type from myTable group by f0").print();
    }
}

程序輸出是

f0|type
--|----
1|a,u
2|CCC,b33,xyz

0x04 Flink SQL內部翻譯

這個SQL語句的重點是group by。這個是程序猿經常使用的操作。但是大家有沒有想過這個group by在真實運行起來時候是怎麼操作的呢?針對大數據環境有沒有做了什麼優化呢?其實,Flink正是使用了GroupReduce和GroupCombine來實現並且優化了group by的功能。優化之處在於:

  • GroupReduce和GroupCombine的函數調用次數要遠低於正常的reduce算子,如果reduce操作中涉及到頻繁創建額外的對象或者外部資源操作,則會相當省時間。
  • 因為combine進行了初步排序,所以在算子之間傳輸的數據量就少多了。

SQL生成Flink的過程十分錯綜複雜,所以我們只能找最關鍵處。其是在 DataSetAggregate.translateToPlan 完成的。我們可以看到,對於SQL語句 “select f0, mapToString(collect(f1)) as type from myTable group by f0”,Flink系統把它翻譯成如下階段,即

  • pre-aggregation :排序 + combine;
  • final aggregation :排序 + reduce;

從之前的文章我們可以知道,groupBy這個其實不是一個算子,它只是排序過程中的一個輔助步驟而已,所以我們重點還是要看combineGroup和reduceGroup。這恰恰是我們想要的完美例子。

input ----> (groupBy + combineGroup) ----> (groupBy + reduceGroup) ----> output

SQL生成的Scala代碼如下,其中 combineGroup在後續中將生成GroupCombineOperator,reduceGroup將生成GroupReduceOperator。

  override def translateToPlan(
      tableEnv: BatchTableEnvImpl,
      queryConfig: BatchQueryConfig): DataSet[Row] = {

    if (grouping.length > 0) {
      // grouped aggregation
      ...... 
      if (preAgg.isDefined) { // 我們的例子是在這裏
        inputDS          
          // pre-aggregation
          .groupBy(grouping: _*)
          .combineGroup(preAgg.get) // 將生成GroupCombineOperator算子
          .returns(preAggType.get)
          .name(aggOpName)
          // final aggregation
          .groupBy(grouping.indices: _*) //將生成GroupReduceOperator算子。
          .reduceGroup(finalAgg.right.get)
          .returns(rowTypeInfo)
          .name(aggOpName)
      } else {
        ......
      }
    }
    else {
      ......
    }
  }
}

// 程序變量打印如下
this = {DataSetAggregate@5207} "Aggregate(groupBy: (f0), select: (f0, COLLECT(f1) AS $f1))"
 cluster = {RelOptCluster@5220} 

0x05 JobGraph

LocalExecutor.execute中會生成JobGraph。JobGraph是提交給 JobManager 的數據結構,是唯一被Flink的數據流引擎所識別的表述作業的數據結構,也正是這一共同的抽象體現了流處理和批處理在運行時的統一。

在生成JobGraph時候,系統得到如下JobVertex。

jobGraph = {JobGraph@5652} "JobGraph(jobId: 6aae8b5e5ad32f588136bef26f8b65f6)"
 taskVertices = {LinkedHashMap@5655}  size = 4

{JobVertexID@5677} "c625209bb7fb9a098807551840aeaa99" -> {InputOutputFormatVertex@5678} "CHAIN DataSource (at initializeDataSource(MemSourceBatchOp.java:98) (org.apache.flink.api.java.io.CollectionInputFormat)) -> FlatMap (select: (f0, f1)) (org.apache.flink.runtime.operators.DataSourceTask)"

{JobVertexID@5679} "b56ace4acd7a2f69ea110a9f262ff80a" -> {JobVertex@5680} "CHAIN GroupReduce (groupBy: (f0), select: (f0, COLLECT(f1) AS $f1)) -> FlatMap (select: (f0, mapToString($f1) AS type)) -> Map (Map at linkFrom(MapBatchOp.java:35)) (org.apache.flink.runtime.operators.BatchTask)"
 
{JobVertexID@5681} "3f5e2a0f700421d80ce85e02a6d9db73" -> {InputOutputFormatVertex@5682} "DataSink (collect()) (org.apache.flink.runtime.operators.DataSinkTask)"
 
{JobVertexID@5683} "ad29dc5b2e0a39ad2cd1d164b6f859f7" -> {JobVertex@5684} "GroupCombine (groupBy: (f0), select: (f0, COLLECT(f1) AS $f1)) (org.apache.flink.runtime.operators.BatchTask)"

我們可以看到,在JobGraph中就生成了對應的兩個算子。其中這裏的FlatMap就是用戶的UDF函數MapToString的映射生成。

GroupCombine (groupBy: (f0), select: (f0, COLLECT(f1) AS $f1))  
  
CHAIN GroupReduce (groupBy: (f0), select: (f0, COLLECT(f1) AS $f1)) -> FlatMap (select: (f0, mapToString($f1) AS type)) -> Map 

0x06 Runtime

最後,讓我們看看runtime會如何處理這兩個算子。

6.1 ChainedFlatMapDriver

首先,Flink會在ChainedFlatMapDriver.collect中對record進行處理,這是從Table中提取數據所必須經歷的,與後續的group by關係不大。

@Override
public void collect(IT record) {
   try {
      this.numRecordsIn.inc();
      this.mapper.flatMap(record, this.outputCollector);
   } catch (Exception ex) {
      throw new ExceptionInChainedStubException(this.taskName, ex);
   }
}

// 這裡能夠看出來,我們獲取了第一列記錄
record = {Row@9317} "1,a,1"
 fields = {Object[3]@9330} 
this.taskName = "FlatMap (select: (f0, f1))"

// 程序堆棧打印如下
collect:80, ChainedFlatMapDriver (org.apache.flink.runtime.operators.chaining)
collect:35, CountingCollector (org.apache.flink.runtime.operators.util.metrics)
invoke:196, DataSourceTask (org.apache.flink.runtime.operators)
doRun:707, Task (org.apache.flink.runtime.taskmanager)
run:532, Task (org.apache.flink.runtime.taskmanager)
run:748, Thread (java.lang)

6.2 GroupReduceCombineDriver

其次,GroupReduceCombineDriver.run()中會進行combine操作。

  1. 會通過this.sorter.write(value)把數據寫到排序緩衝區。
  2. 會通過sortAndCombineAndRetryWrite(value)進行實際的排序,合併。

因為是系統實現,所以Combine的用戶自定義函數就是由Table API提供的,比如org.apache.flink.table.functions.aggfunctions.CollectAccumulator.accumulate

@Override
public void run() throws Exception {
   final MutableObjectIterator<IN> in = this.taskContext.getInput(0);
   final TypeSerializer<IN> serializer = this.serializer;

   if (objectReuseEnabled) {
    .....
   }
   else {
      IN value;
      while (running && (value = in.next()) != null) {
         // try writing to the sorter first
         if (this.sorter.write(value)) {
            continue;
         }

         // do the actual sorting, combining, and data writing
         sortAndCombineAndRetryWrite(value);
      }
   }

   // sort, combine, and send the final batch
   if (running) {
      sortAndCombine();
   }
}

// 程序變量如下
value = {Row@9494} "1,a"
 fields = {Object[2]@9503} 

sortAndCombine是具體排序/合併的過程。

  1. 排序是通過 org.apache.flink.runtime.operators.sort.QuickSort 完成的。
  2. 合併是通過 org.apache.flink.table.functions.aggfunctions.CollectAccumulator.accumulate 完成的。
  3. 給下游是由 org.apache.flink.table.runtime.aggregate.DataSetPreAggFunction.combine 調用 out.collect(output) 完成的。
private void sortAndCombine() throws Exception {
   final InMemorySorter<IN> sorter = this.sorter;
   // 這裏進行實際的排序
   this.sortAlgo.sort(sorter);
   final GroupCombineFunction<IN, OUT> combiner = this.combiner;
   final Collector<OUT> output = this.output;

   // iterate over key groups
   if (objectReuseEnabled) {
			......		
   } else {
      final NonReusingKeyGroupedIterator<IN> keyIter = 
            new NonReusingKeyGroupedIterator<IN>(sorter.getIterator(), this.groupingComparator);
      // 這裡是歸併操作
      while (this.running && keyIter.nextKey()) {
         // combiner.combiner 是用戶定義操作,runtime把某key對應的數據一次性傳給它
         combiner.combine(keyIter.getValues(), output);
      }
   }
}

具體調用棧如下:

accumulate:57, CollectAggFunction (org.apache.flink.table.functions.aggfunctions)
accumulate:-1, DataSetAggregatePrepareMapHelper$5
combine:71, DataSetPreAggFunction (org.apache.flink.table.runtime.aggregate)
sortAndCombine:213, GroupReduceCombineDriver (org.apache.flink.runtime.operators)
run:188, GroupReduceCombineDriver (org.apache.flink.runtime.operators)
run:504, BatchTask (org.apache.flink.runtime.operators)
invoke:369, BatchTask (org.apache.flink.runtime.operators)
doRun:707, Task (org.apache.flink.runtime.taskmanager)
run:532, Task (org.apache.flink.runtime.taskmanager)
run:748, Thread (java.lang)

6.3 GroupReduceDriver & ChainedFlatMapDriver

這兩個放在一起,是因為他們組成了Operator Chain。

GroupReduceDriver.run中完成了reduce。具體reduce 操作是在 org.apache.flink.table.runtime.aggregate.DataSetFinalAggFunction.reduce 完成的,然後在其中直接發送給下游 out.collect(output)

@Override
public void run() throws Exception {
   // cache references on the stack
   final GroupReduceFunction<IT, OT> stub = this.taskContext.getStub();
 
   if (objectReuseEnabled) {
       ......	
   }
   else {
      final NonReusingKeyGroupedIterator<IT> iter = new NonReusingKeyGroupedIterator<IT>(this.input, this.comparator);
      // run stub implementation
      while (this.running && iter.nextKey()) {
         // stub.reduce 是用戶定義操作,runtime把某key對應的數據一次性傳給它
         stub.reduce(iter.getValues(), output);
      }
   }
}

從前文我們可以,這裏已經配置成了Operator Chain,所以out.collect(output)會調用到CountingCollector。CountingCollector的成員變量collector已經配置成了ChainedFlatMapDriver。

public void collect(OUT record) {
   this.numRecordsOut.inc();
   this.collector.collect(record);
}

this.collector = {ChainedFlatMapDriver@9643} 
 mapper = {FlatMapRunner@9610} 
 config = {TaskConfig@9655} 
 taskName = "FlatMap (select: (f0, mapToString($f1) AS type))"

於是程序就調用到了 ChainedFlatMapDriver.collect

public void collect(IT record) {
   try {
      this.numRecordsIn.inc();
      this.mapper.flatMap(record, this.outputCollector);
   } catch (Exception ex) {
      throw new ExceptionInChainedStubException(this.taskName, ex);
   }
}

最終調用棧如如下:

eval:21, UdfExample$MapToString (com.alibaba.alink)
flatMap:-1, DataSetCalcRule$14
flatMap:52, FlatMapRunner (org.apache.flink.table.runtime)
flatMap:31, FlatMapRunner (org.apache.flink.table.runtime)
collect:80, ChainedFlatMapDriver (org.apache.flink.runtime.operators.chaining)
collect:35, CountingCollector (org.apache.flink.runtime.operators.util.metrics)
reduce:80, DataSetFinalAggFunction (org.apache.flink.table.runtime.aggregate)
run:131, GroupReduceDriver (org.apache.flink.runtime.operators)
run:504, BatchTask (org.apache.flink.runtime.operators)
invoke:369, BatchTask (org.apache.flink.runtime.operators)
doRun:707, Task (org.apache.flink.runtime.taskmanager)
run:532, Task (org.apache.flink.runtime.taskmanager)
run:748, Thread (java.lang)

0x07 總結

由此我們可以看到:

  • GroupReduce,GroupCombine和mapPartition十分類似,都是從系統層面對算子進行優化,把循環操作放到用戶自定義函數來處理。
  • 對於group by這個SQL語句,Flink將其翻譯成 GroupReduce + GroupCombine,採用兩階段優化的方式來完成了對大數據下的處理。

0x08 參考

flink 使用問題匯總

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

【其他文章推薦】

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

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

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

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

※超省錢租車方案

分類
發燒車訊

VSCode + WSL 2 + Ruby環境搭建詳解

vscode配置ruby開發環境

vscode近年來發展迅速,幾乎在3年之間就搶佔了原來vim、sublime text的很多份額,猶記得在2015-2016年的時候,ruby推薦的開發環境基本上都是vim和sublime text,然而,隨着vscode的發展,vscode下ruby的開發體驗已經非常不錯。現在基本上使用win 10 wsl2 + vscode + windows terminal的體驗已經不遜於mac + vim (sublime) + item 2的體驗了

總體步驟

使用win10專業版配置ruby開發環境大致分為以下幾步:

  1. 開啟win10 wsl功能
  2. 升級wsl2
  3. 安裝ubuntu
  4. 安裝ruby(rvm)
  5. 安裝vscode
  6. 安裝vscode wsl擴展
  7. 安裝vscode ruby相關擴展

經過以上7步就可以開始愉悅的ruby開發了,再開始之前,可以先看個效果圖。

1. 開啟win10 wsl功能

ruby對Linux和Mac比較友好,在windows下很多第三方庫要配合mingw或msys2才能安裝,不過好在windows 10提供了Linux子系統,在win10 2004版本中wsl也升級到了wsl2,速度更快,功能更完善。

要使用wsl2需要先在控制面板中開啟wsl功能:

  • 適用於Linux的Windows子系統
  • 虛擬機平台

2. 升級wsl2

目前wsl2還需要安裝一個內核升級包,具體可參考微軟說明:

  • wsl2安裝說明
  • wsl2 update包

更新包安裝完成后,輸入命令

wsl --set-default-version 2

3. 安裝Ubuntu

在微軟應用商店安裝Ubuntu,當前Ubuntu版本為20.04 LTS

安裝完成以後,配置Ubuntu默認為wsl2

# 查看
wsl --list --verbose

# 設置
wsl --set-version Ubuntu 2

4. 安裝ruby

在Linux下安裝ruby有多種方法,比較主流的方法是RVM,不過為了簡單起見,我直接通過ubuntu的apt工具進行了安裝。

關於RVM的安裝可參考如下網站:

  • RVM官網
  • RVM實用指南

通過APT安裝,輸入下列命令即可

sudo apt install ruby ruby-dev ri ruby-bundle

安裝完成以後需要配置gem國內鏡像,參考如下網址:

  • gem中文鏡像

輸入下列命令

# 設置gem source
gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/

# 查看gem source
gem sources -l

# 設置bundle
bundle config mirror.https://rubygems.org https://gems.ruby-china.com

5. 安裝vscode

vscode直接在官網下載安裝即可,這裏我選擇了System Installer

  • vscode官網下載頁面

6. 安裝vscode wsl擴展

vscode安裝完成以後,可以在plugin中找到Remote – WSL擴展,點擊安裝即可

7. 安裝vscode ruby相關擴展

直接在plugin中搜索ruby在wsl中安裝下列五個擴展即可

  • Peng Lv/Ruby
  • Castwide/Ruby Solargraph(Language Server)
  • misogi/ruby-rubocop(Lint)
  • Simple Ruby ERB
  • endwise

其中,ruby solargraphrubocop除了安裝擴展,還需要通過gem安裝第三方包

sudo gem install rubocop
sudo gem install solargraph

重新加載vscode-wsl就可以愉快的使用ruby language進行開發了

vscode使用

在使用上基本只要require了相應的庫,就solargraph就會對require的庫中涉及的類和模塊進行提示,非常方便。唯一有問題的地方就是require的時候沒有提示,這可能就需要自己記一下庫的名稱,不過相比於原來已經好太多了,應該說在可以接受的範圍內。

1. 如果安裝了新的第三方庫會提示嗎?

如果安裝了sinatra這樣的庫,vscode-ruby如何給出提示呢?只需要Ctrl + Shift + P,選擇solargraph: build new gem documention即可

2. rubocop如何使用?

rubocop是一個Ruby Lint工具,可以進行Ruby代碼風格檢查,並能夠自動修復,只需要Ctrl + Shift + P,選擇Ruby: autocorrect by rubocop即可

3. 常用類型註釋

ruby是動態強類型語言,由於不需要指定函數返回值類型,這導致IDE無法自動推斷一些變量的類型。目前Python、PHP、TypeScript都在不斷的強化類型以方便IDE進行靜態檢查。IDE只有在知道類型的情況下才能準確地進行智能提示。

在ruby 2當中,我們可以通過類型註釋的方式增強IDE推斷能力。常見的類型註釋可參考YARD項目

下面代碼給出了一些示例。

require 'socket'

server = TCPServer.new 2000
loop do
  # 代碼塊參數類型註釋
  # @param {TCPSocket} client
  Thread.start(server.accept) do |client|
    client.puts 'hello !'
    client.puts "Time is #{Time.now}"
    client.close
  end
end

server = TCPServer.new 2000
loop do
  # 變量註釋
  # @type {TCPSocket} client
  client = server.accept
end

# 函數參數和返回值註釋,數組類型
# @param {Array(Integer)} nums
# @param {Integer} target
# @return {Array(Integer)}
def two_sum(nums, target)
  hash_nums = {}
  result = []
  nums.each_with_index do |num, index|
    hash_nums[num] = index
  end

  nums.each_with_index do |num, index|
    another = target - num
    if hash_nums[another] && hash_nums[another] != index
      result.push(index, hash_nums[another])
      break
    end
  end

  result
end

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

【其他文章推薦】

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

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

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

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

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

分類
發燒車訊

salesforce零基礎學習(九十八)Type淺談

在Salesforce的世界,凡事皆Metadata。

先通過一句經常使用的代碼帶入一下:

Account accountItem = (Account)JSON.deserialize(accountString,Account.class);

這種代碼相信大部分開發都會寫過,前台將數據序列化,通過字符串的形參傳遞給後台,後台將數據進行反序列化,從而獲取到這個表或者這個自定義類的實例。所以問題來了,為啥第二個參數是 Account.class?我們通過官方的API描述可能更好的進行了解。

 這裏我們引出了 Type的概念,他是apex 預定的類型,包括 基礎數據類型(Integer等) , 集合, sObject類型以及 用戶定義的類。基礎數據類型等等都是 object類型,所以當我們理解salesforce裏面的類型時,可以簡單的分成兩個大類:Object & sObject。所以Type概念引入完了,它用來幹嘛?怎麼聲明?什麼時候用呢?

Type t1 = Integer.class;
Type t2 = Type.forName('Integer');
system.debug(t1.equals(t2));

上面的簡單的demo中提供了兩種聲明Type的方式,一種是根據 object | sObject使用 .class聲明,另外一種是使用 Type的方法forName來實例化變量。既然變量可以聲明出來,我們就可以看看他的方法考慮如何搞事情了。

 Type的方法非常少,所以我們想要查看其對應的方法描述以及使用很容易就看完。這裏針對幾個重要的方法進行描述:

  • forName(fullyQualifiedName):返回與指定的完全限定的類名相對應的類型。這裏的類名包括salesforce系統預製的類,自定義的類以及sObject名稱;
  • isAssignableFrom(sourceType):如果object指定類型的引用可以從子類型分配,則返回true,否則返回false。這個方法我們可能首先會先想到 instanceof,因為都是來判斷類型是否相兼容匹配,但是 instanceof必須是初始化這個類或者對象的變量,才能使用 instanceof 來進行判斷,使用 此方法可以省去了靜態編譯時的依賴,所以如果想判斷依賴性,可以優先考慮此方法。
  • newInstance():此方法用來實例化一個指定Type的對象,返回類型是一個object對象,我們可以強制轉換成我們需要的對象類型。因為apex也是面向對象的語言,封裝,繼承,多態三大特性,我們可以通過 newInstance實現父子類型的輕鬆轉換調用相關的方法從而實現動態配置。

基礎信息介紹完畢,此篇淺入淺出,介紹兩種可能用到的場景。

1.  JSON序列化與反序列化

這個我們經常使用,一筆帶過:通過字符串以及指定的 Type類型可以轉換成指定的數據類型。

Account accountItem = (Account)JSON.deserialize(accountString,Account.class);

2. 針對Custom Setting等根據配置的動態的類調用動態方法的使用

ParentClass是一個父類,有兩個變量以及一個虛擬的方法,繼承的子類可以進行實現

public abstract class ParentClass {
    public String param1 { get; set; }
    public String param2 { get; set; }

    public ParentClass() {
        this.param1 = 'value1';
        this.param2 = 'value2';
    }

    public virtual String joinParam() {
        return param1 + param2;
    }
}

SonClass1繼承了它並實現了它相關的方法

public class SonClass1 extends ParentClass {
    public SonClass1() {
        super();
        this.param1 = 'son value1';
        this.param2 = 'son value2';
    }

    public override String joinParam() {
        return super.joinParam();
    }
}

還有其他的SonClassn繼承了它並實現了它的相關的方法,我們在custom setting中配置了不同的場景應該調用的不同的子類,公共方法中,我們只需要使用以下的代碼來判斷和調用即可。

public String executeJoin(String className) {
    Type t = Type.forName(className);
    Type t2 = Type.forName('ParentClass');
    if(!t2.isAssignableFrom(t)) {
        throw new CustomException('should be son class of ParentClass');
    }
    ParentClass newObj = (ParentClass)t.newInstance();
    return newObj.joinParam();
}

總結:篇中簡單的介紹了salesforce中的 Type的使用,拋磚引玉,想要深入了解還要自行查看官方文檔。篇中有錯誤的地方歡迎指出,有不懂的歡迎留言。

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

【其他文章推薦】

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

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

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

※超省錢租車方案

FB行銷專家,教你從零開始的技巧

分類
發燒車訊

Nginx服務器的使用與反向代理負載均衡

Nginx服務器

一:什麼是Nginx?

我們生活的世界中,有的時候需要上網。我們可以瀏覽很多很多的網頁,這些網頁都是由一系列的程序組成,但是我們是否想過,這些程序存儲在什麼地方呢?沒錯,這些程序都是存儲在一種名叫服務器的硬件上,比如我們的電腦也是一種服務器,只不過我們的個人電腦作為服務器的話性能會比較低。我們的網頁程序存儲在服務器硬件上,是否可以隨意存儲呢?不是的,我們需要在服務器硬件的操作系統中搭建一個服務器軟件,那麼這樣,有服務器軟件跟服務器硬件配合,才形成一個完整的服務器。服務器軟件有非常多,比如Apache、tomcat等等都是服務器軟件,而我們今天要學習的Nginx也是一種服務器軟件之一。

Nginx是一種服務器軟件,故而其最主要、最基本的功能當然是可以與服務器硬件結合,讓程序員可以將程序放在Nginx服務器上,將程序發布出去,讓成千上萬的網民可以瀏覽。除此之外,Nginx是一種高性能的HTTP和反向代理服務器,同時也是一個代理郵件服務器。也就是說,我們Nginx上可以發布網站,也可以實現負載均衡的功能,還可以作為郵件服務器實現收發郵件等功能。所謂的負載均衡是指,當同時有N多用戶訪問我們服務器的時候,為了減少服務器壓力,我們需要將用戶分別引入各服務器,分擔服務器的壓力。

Nginx與其他服努器的性能比較

首先說IIS, IIS服務器只能在Windows上運行,Windows服務器性能不如Linux— 類服務器。其次說Tomcat,Tomcat服務器面向的是Java語言,是一種重量級的服 務器,而Nginx是輕量級服務器,Tomcat與Nginx不具備可比性。最後,我們講一 下Apache,Apache優點非常多,比如穩定、幵源、跨平台等等,但是Apache不支 持高併發。Nginx能支持處理百萬級的TCP連接,10萬以上的併發連接,並且是一 個很好的跨平台服務器。

Nginx的主要優點有可以實現高併發、部署簡單、內存消耗少、成本低等等。

Nginx的主要缺點有rewrite功能不夠強大,模塊沒有Apache的多。

本文版權歸微信公眾號”代碼藝術”(ID:onblog)所有,若是轉載請務必保留本段原創聲明,違者必究。若是文章有不足之處,歡迎關注微信公眾號私信與我進行交流!

二:Linux中搭建Nginx服務器

新建壓縮包下載位置(可選)

新建目錄
mkdir /usr/local/nginx_down
切換目錄
cd /usr/local/nginx_down

下載解壓 Nginx

下載
wget http://nginx.org/download/nginx-1.14.0.tar.gz
解壓
tar -zxvf nginx-1.14.0.tar.gz
切換目錄
cd nginx-1.14.0

配置 Nginx

./configure --with-http_ssl_module
  1. 這樣會默認安裝nginx在 /usr/local/nginx 目錄,可以使用--prefix=/usr/local/nginx指定安裝位置。

  2. 如果需要HTTPS(SSL)的支持,需要指定參數--with-http_ssl_module

如果提示錯誤,那麼需要其它環境,請參考下面

安裝 make

yum -y install gcc automake autoconf libtool make

安裝 g++

yum -y install gcc gcc-c++

安裝 PCRE庫

yum -y install pcre pcre-devel

安裝 Zlib

yum -y install zlib zlib-devel

安裝 GD library

yum -y install gd-devel

安裝 openssl

yum -y install openssl openssl-devel

-y:跳過所有手動確認輸入

如果./configure安裝成功,只需要再執行兩個命令:

make
make install

查看是否安裝成功

cd /usr/local/nginx

如果安裝成功,則會出現下列目錄:

conf  html  logs  sbin

切換到sbin目錄

cd /sbin

啟動程序

./nginx

追加參數 -c 可以指定配置文件。

常見的錯誤

在Linux操作系統下搭建Nginx服務器,很多時候會出現不同的錯誤,在此,我們對搭建過程中出現的錯誤進行一些總結。主要有這些類型:

防火牆問題,缺少gc++,缺少pcre、zlib等庫。

三:Nginx的反向代理和負載均衡

什麼是反向代理

我們有時候,用自己的計算機A想訪問國外的某個網站B,但是訪問不了,此時,有一台中間服務器C可以訪問國外的網站B,那麼,我們可以用自己的電腦訪問服務器C,通過C來訪問B這個網站。那麼這個時候,服務器C稱為代理服務器,這種訪問方式叫做正向代理。正向代理有一個特點,就是我們明確知道要訪問哪個網站。再如,當我們有一個服務器集中,並且服務器集群中的每台服務器的內容一樣的時候,同樣我們要直接從個人電腦訪問到服務器集中的服務器的時候無法訪問,且此時第三方服務器能訪問集群,這個時候,我捫通過第三方服務器訪問服務器集群的內容,但是此吋我們並不知道是哪一台服務器提供的內容,此時的代理方式稱為反向代理。

正向代理

反向代理

什麼是負載均衡

當一台服務器的單位時間內的訪問量越大的時候,服務器的壓力會越大。當一台服務器壓力大得超過自身的承受能力的時候,服務器會崩潰。為了避免服務器崩潰,讓用戶有更好地體驗,我們通常通過負載均衡的方式來分擔服務器的壓力。那麼什麼是負載均衡呢?是這樣,我們可以建立很多很多個服務器,這些服務器組成一個服務器集群,然後,當用戶訪問我們網站的時候,先訪問一个中間服務器,再讓這个中間服務器在服務器集群中選擇一個壓力較小的服務器,然後將該訪問請求引入該選擇的服務器。這樣,用戶的每次訪問,都會保證服務器集群中的每個服務器的壓力趨於平衡,分擔了服務器壓力,避免了服務器崩潰的情況。

基於反向代理的原理實現負載均衡

四:Nginx負載均衡的實現

Nginx 是一款可以通過反向代理實現負載均衡的服務器,使用 Nginx 服務實現負載均衡的時候,用戶的訪問首先會訪問到 Nginx 服務器,然後 Nginx 服務器再從服務器集群表中選擇壓力較小的服務器,然後將該訪問請求引向該服務器。若服務器集群中的某個服務器崩潰,那麼從待選服務器列表中將該服務器刪除,也就是說一個服務器假如崩潰了,那麼 Nginx 就肯定不會將訪問請求引入該服務器了。那麼下面,我們通過實例來講解一下 Nginx 負載均衡的實現。

負載均衡配置文件

默認配置文件nginx.conf 很重要,我們一般是新建一個配置文件,在啟動時指定加載。

[root@hostname conf]# ls  //查看目錄
fastcgi.conf            koi-win             scgi_params
fastcgi.conf.default    mime.types          scgi_params.default
fastcgi_params          mime.types.default  uwsgi_params
fastcgi_params.default  nginx.conf          uwsgi_params.default
koi-utf                 nginx.conf.default  win-utf
[root@hostname conf]# touch fzjh.conf  //新建負載均衡配置文件

編輯fzjh.conf配置文件

vi fzjh.conf
worker_processes  1;#工作進程的個數,一般與計算機的cpu核數一致  
  
events {  
    worker_connections  1024;#單個進程最大連接數(最大連接數=連接數*進程數) 併發 
}  
  
http {  
    include       mime.types; #文件擴展名與文件類型映射表  
    default_type  application/octet-stream;#默認文件類型  
  
    sendfile        on;#開啟高效文件傳輸模式,sendfile指令指定nginx是否調用sendfile函數來輸出文件,對於普通應用設為 on,如果用來進行下載等應用磁盤IO重負載應用,可設置為off,以平衡磁盤與網絡I/O處理速度,降低系統的負載。注意:如果圖片显示不正常把這個改成off。  
      
    keepalive_timeout  65; #長連接超時時間,單位是秒  
  
    gzip  on;#啟用Gizp壓縮  
      
    #服務器的集群  
    upstream  myproject {  #服務器集群名字   
        server    220.181.111.188:80  weight=1;#服務器配置   weight是權重的意思,權重越大,分配的概率越大。  
        server    42.121.252.58:80  weight=2;  
    }     
  
    #當前的Nginx的配置  
    server {  
        listen       80;  #監聽80端口,可以改成其他端口  
        server_name  localhost; ##############   當前服務的域名  
  
        location / {  ##配置路徑/下實現負載均衡
            proxy_pass http://myproject;  ##對應哪個服務器集群
            proxy_redirect default;  
        }  
          
        error_page   500 502 503 504  /50x.html;  
        location = /50x.html {  
           root   html;  #根目錄
        }  
    }  
}  

我們在nginx目錄下執行命令,啟動負載均衡

./sbin/nginx -c ./conf/fzjh.conf

然後嘗試訪問你的服務器,多訪問幾次,你會發現它會訪問2次csdn網站,1次百度。說明我們的負載均衡已經部署完畢。

如何重啟Nginx

【本文版權歸微信公眾號”代碼藝術”(ID:onblog)所有,若是轉載請務必保留本段原創聲明,違者必究。若是文章有不足之處,歡迎關注微信公眾號私信與我進行交流!】

./sbin/nginx -s reload

如何關閉Nginx服務器

1.查看nginx進程號

ps -ef|grep nginx

2.kill掉進程即可 (1709是第二列的進程號)

kill 1709

或者

killall -9 nginx

五:HTTP Upstream 模塊

HTTP Upstream 模塊

Upstream 模塊是 Nginx 服務器的一個重要模塊。 Upstream 模塊實現在輪詢和客戶端 iP 之間實現後端的負載均衡。常用的指令有 ip_hash指令、 server 指令和 upstream 指令等,下面我們分別來講一下。

Http Upstream模塊- ip_hash指令

在負載均衡系統中,假如用戶在某台服務器上登錄,那麼如果該用戶第二次請求的時候,因為我們是負載均衡系統,每次請求都會重新定位到服務器集群中的一個服務器,那麼此時如果將已經登錄服務器A的用戶再定位到其他服務器,顯然不妥。故而,我們可以採用 ip_hash指令解決這個問題,如果客戶端請求已經訪問了服務器A並登錄,那麼第二次請求的時候,會將該請求通過哈希算法自動定位到該後端服務器中。下面我們通過實例講解。

實例

此時不應該使用weight權重。


tp {  
   ....
  upstream  myproject {    
      ip_hash; #實現會話跟蹤
      server 140.205.140.234;
      server 61.135.169.125;

  }     
 ....

Http Upstream 模塊一 upstream 指令及相關變量

upstream 指令主要是用於設置一組可以在 proxy_pass 和 fastcgi_pass 指令中使用代理服務器,默認負載均衡方式為輪詢。

六:其他負載均衡實現方式

負載均衡的實現方法除了可以使用 Nginx服務器實現外,還可以通過很多種方法來實現。負載均衡的核心就是建立一個服務器集群,然後用戶首先訪問到第三方代理服務器,然後由代理服務器選擇一個集群中的服務器,然後將請求引入選定的服務器。那麼代理服務器可以使用多種方式來充當,故而實現負載均衡的方式也是多種。總的來說,負載均衡實現的方式分為軟件實現和硬件實現兩種,如果中間的代理機構是硬件,那麼就是通過硬件設備來實現負載均衡的方式,如果中間的代理機構為軟件,就是軟件實現負載均衡的方式。而其中,軟件又可以是服務器軟件、系統軟件以及應用軟件等充當。

負載均衡實現方式小結

下面我們簡單總結一下負載均衡不同實現方式的優缺點:

假如使用硬件的方式實現負載均衡,那麼中間的轉發機構就是硬件,這個時候運行的效率非常高,但是對應的成本也非常高。如果我們採用軟件的方式來實現負載均衡,那麼中間的轉發機構就是軟件,這個時候,運行效率不如硬件,但是成本相對來說低得多。而使用Nginx服務器實現負載均衡,那麼就是通過軟件的方式來實現負載均衡,並且 Nginx本身支持高併發等。故而使用 Nginx服務器實現負載均衡,能大大節約企業的成本,並且由於 Nginx是服務器軟件,其執行效率也是非常高。

七:location匹配順序

  1. “=”前綴指令匹配,如果匹配成功,則停止其他匹配
  2. 普通字符串指令匹配,順序是從長到短,匹配成功的location如果使用^~,則停止其他匹配(正則匹配)
  3. 正則表達式指令匹配,按照配置文件里的順序,成功就停止其他匹配
  4. 如果第三步中有匹配成功,則使用該結果,否則使用第二步結果

注意點

  1. 匹配的順序是先匹配普通字符串,然後再匹配正則表達式。另外普通字符串匹配順序是根據配置中字符長度從長到短,也就是說使用普通字符串配置的location順序是無關緊要的,反正最後nginx會根據配置的長短來進行匹配,但是需要注意的是正則表達式按照配置文件里的順序測試。找到第一個比配的正則表達式將停止搜索。
  2. 一般情況下,匹配成功了普通字符串location后還會進行正則表達式location匹配。有兩種方法改變這種行為,其一就是使用“=”前綴,這時執行的是嚴格匹配,並且匹配成功后立即停止其他匹配,同時處理這個請求;另外一種就是使用“^~”前綴,如果把這個前綴用於一個常規字符串那麼告訴nginx 如果路徑匹配那麼不測試正則表達式。

匹配模式及順序

  location = /uri    =開頭表示精確匹配,只有完全匹配上才能生效。

  location ^~ /uri   ^~ 開頭對URL路徑進行前綴匹配,並且在正則之前。

  location ~ pattern  ~開頭表示區分大小寫的正則匹配。

  location ~* pattern  ~*開頭表示不區分大小寫的正則匹配。

  location /uri     不帶任何修飾符,也表示前綴匹配,但是在正則匹配之後。

  location /      通用匹配,任何未匹配到其它location的請求都會匹配到,相當於switch中的default。

八:配置HTTPS

1.獲取證書

獲得SSL證書文件 1_www.domain.com_bundle.crt 和私鑰文件 2_www.domain.com.key

2.證書安裝

將域名 www.domain.com 的證書文件1_www.domain.com_bundle.crt 、私鑰文件2_www.domain.com.key保存到同一個目錄,例如/usr/local/nginx/conf目錄下。

3.配置conf

打開nginx.conf文件,找到nginx.conf的下段配置內容:

# HTTPS server
#
#server {
#    listen       443 ssl;
#    server_name  localhost;

#    ssl_certificate      cert.pem;
#    ssl_certificate_key  cert.key;

#    ssl_session_cache    shared:SSL:1m;
#    ssl_session_timeout  5m;

#    ssl_ciphers  HIGH:!aNULL:!MD5;
#    ssl_prefer_server_ciphers  on;

#    location / {
#        root   html;
#        index  index.html index.htm;
#    }
#}

打開註釋,修改server_name為綁定證書的域名(如:www.domain.com),修改ssl_certificate 為 1_www.domain.com_bundle.crt,修改 ssl_certificate_key 為 2_www.domain.com.key 即可。

4.HTTP自動跳轉HTTPS

對於用戶不知道網站可以進行https訪問的情況下,讓服務器自動把http的請求重定向到https。 在服務器這邊的話配置的話,可以在頁面里加js腳本,也可以在後端程序里寫重定向,當然也可以在web服務器來實現跳轉。

Nginx是支持rewrite的(只要在編譯的時候沒有去掉pcre) 在http的server里增加rewrite ^(.*) https://$host$1 permanent; 這樣就可以實現80進來的請求,重定向為https了。

還是在此配置文件中,加入下面一句:

server {
        listen       80;
        server_name  localhost;
        
		rewrite ^(.*) https://$host$1 permanent;
		...

版權聲明

本文版權歸微信公眾號”代碼藝術”(ID:onblog)所有,若是轉載請務必保留本段原創聲明,違者必究。若是文章有不足之處,歡迎關注微信公眾號私信與我進行交流!

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

分類
發燒車訊

drf之框架基礎

(一)drf基礎

全稱:django-rest framework

接口:什麼是接口、restful接口規範(協議)

CBV(基於FBV的基礎上形成)、CBV生命周期源碼—-基於restful規範下的CBV接口

請求生命周期:請求組件、解析組件、響應組件

序列化組件(序列化、反序列化簡單來說就是對象轉為字符串、字符串轉為對象,目的是為傳輸數據(傳給別的語言或者存儲))

三大認證(重中之重):認證(用戶是否合法)、權限(管理員、普通用戶)、頻率(次數過多限制)

其他組件(過濾、篩選、排序、分頁、路由)

 

1.接口

概念:聯繫兩個物質的媒介,完成信息的交互。在web程序中,聯繫前台頁面後台數據庫的媒介。

web接口組成:

url:長得像返回數據的url鏈接。如api.baidu.map/search

www.baidu.com不叫接口,叫url鏈接,訪問只能拿到主頁。api.baidu.map/search是接口,返回的是json數據)

請求參數:前台按照指定的key提供數據給後台。(必須是給定的,這樣後台才能以此提取數據再到數據庫查詢返回)

響應數據:後台與數據庫交互后,將數據反饋給前台。

因此,接口就是帶着請求參數訪問能夠得到響應數據的url鏈接。

接口 = url + 請求參數 + 響應數據

 

 

2.restful接口規範

接口規範:不同後台語言,使用同樣的接口返回同樣的數據。

如何寫接口:要寫url和響應數據。如果將請求參數也加上,就是在寫接口文檔。

 

兩大部分:

1url

1)用api關鍵字標識接口url。方式1api.baidu.com;方式2:www.baudu.com/api

2)接口數據安全性決定優先選用https協議

3)如果一個接口有多版本存在,需要在url中標識體現。如下的v1v2

api.baidu.com/v1/….  api.baidu.com/v2/….

4)操作中的數據稱為資源,在url中資源一般採用複數形式,一個接口可以概括對該資源的多種操作方式。(一個url對應一個類,類裏面可以有多個請求方法)

可以對操作隱藏,並且復用性更強(寫動作了,只能適用這一個動作,不寫其他動作都可以用)如api.baidu.com/books    api.baidu.com/books/(pk)

5)請求方式有多種,用一個url處理如何讓保證不混亂——通過不同的請求方式標識不同操作資源的方式

/books      get     獲取所有

/books post   增加一個(多個)

/books/(pk) delete 刪除一個

/books/(pk)  put 整體更新一個  #改一個用戶

/books/(pk)  patch 局部更新一個 #改一個用戶的密碼

 

6)資源往往涉及數據的各種操作方式:篩選、排序、限制

api.baidu.com/books/?search=寶馬&ordering=-price&limit=3

 

2)響應數據

1http請求的響應會有響應狀態碼,接口用來返回操作的資源數據,也有自己操作數據結果的資源狀態碼status 0代表操作資源成功,1代表操作失敗,2代表操作成功,但沒匹配結果)

注:資源狀態碼和http狀態碼不一樣,為前後台的約定

2)資源狀態碼的文字提示。

status ok “賬號有誤或者密碼有誤”

3)資源本身

results

:刪除資源成功不做任何數據返回(只返回空字符串,連狀態碼、狀態信息都不返回)

4)不能直接返回的資源(子資源、圖片、視頻等資源),返回該資源的url鏈接。

 

https://api.baidu.com/v1/books?limit=3
get|post|delete|put|patch   
{
  “status” : 0,
  “msg” : “ok”,
  “results”: [
{
  “title”: “三國”,
  “price”: 78,
  “img”: “https://.....”   
    }
  ]
}

 

3.django流程

1)項目準備:

 

1.分發路由

在項目文件夾的urls複製一份到應用文件夾中。然後在項目文件夾的urls分發路由給app:導入include,然後url(r’^api/’, include(‘api.urls’))。再在app文件夾的urls.py中分發路由給CBV

2.視圖

在應用中分發路由前,先寫類視圖

from django.http import JsonResponse
from django.views import View

class Book(View):

    def get(self, request, *args, **kwargs):
        return JsonResponse('get ok', safe=False)

    def post(self, request, *args, **kwargs):
        return JsonResponse('get ok', safe=False)   #safe默認為true,要返回字典。不是字典否則拋異常。

 

3.在應用urls下分發路由

from django.conf.urls import url
from . import views    #注意在應用中導入視圖都是.   從當前應用中導入

urlpatterns = [
    url(r'^books/', views.Book.as_view()),
]

 

4.定義模型類

1models.py中定義類

from django.db import models

class Book(models.Model):

    title = models.CharField(max_length=64)
    price = models.DecimalField(max_digits=5, decimal_places=2) #整數、小數位

    class Meta:    #嵌套類(給上級類添加功能或指定標準)
        
       db_table = 'book'    #自定義數據庫表名
        verbose_name = "book"   #給模型起個可讀的名字,默認是複數
        verbose_name_plural = verbose_name   #取消上面的複數

    def __str__(self):      #显示的內容
        return '<<%s>>' % self.title    

 

 

   (2)數據庫遷移

進入djangoshell環境中:Tools—-> run manage.py task

shell環境中生成遷移文件:makemigrations。然後遷移:migrate

 

5.生成admin

1)在amin.py中註冊並且導入模型

from django.contrib import admin
port models

admin.site.register(models.Book)

 

2)創建用戶

shell環境中:createsuper創建超級用戶,然後輸入用戶密碼(郵箱不用)

 

 

 2CBV的請求生命周期

請求如何到CBV下的getpost

a.請求過來,項目文件中路由分發給應用api的路由

b.應用分發路由走as_view函數。

views.Book.as_view()  保存一系列數據(requestargs**kwargs等)給Book對象,然後都給dispatch進行路由分發。

dispatch乾的事:判斷請求方式是否支持,然後返回(通過getattr)支持的這些請求方法(getpost等,在視圖中自定義getpost的返回值)的結果。

c.通過dispatch就執行了CBV下請求方式的結果,返回結果

 

 

4.django原生的接口、序列化

  六大基礎接口:獲取一個、獲取所有、增加一個、刪除一個、整體更新一個、局部更新一個

 十大接口:6大基礎、群增、群刪、整體群改、局部群改

 

1.在應用的urls.py下分發路由

url(r’^books/$’, views.Book.as_view()),   #必須要加$,否則後面匹配不到

url(r’^books/(?P<pk>.*)/$’, views.Book.as_view()),有名分組

在視圖函數中通過kwargs.get(pk)取到匹配的值

 

2.views.py里寫邏輯

class Book(View):

 

    def get(self, request, *args, **kwargs):

        pk = kwargs.get(‘pk’)  #獲取參數

        if not pk:  #群查接口

            #操作數據庫

            book_obj_list = models.Book.objects.all()

            #序列化過程

            book_list = []

            for obj in book_obj_list:   #將查到的對象序列化

                dic = {}

                dic[‘title’] = obj.title

                dic[‘price’] = obj.price

                book_list.append(dic)

            return JsonResponse({

                ‘status’ : 0,

                “msg” : “ok”,

                “results”: book_list,

            }, json_dumps_params={‘ensure_ascii’:False})

        else:    #單查接口

            book_dic = models.Book.objects.filter(pk=pk).values(

                ‘title’, ‘price’).first()

            if book_dic:

                return JsonResponse({

                    ‘status’: 0,

                    “msg”: “ok”,

                    “results”: book_dic,

                }, json_dumps_params={‘ensure_ascii’: False})

 

            return JsonResponse({

                ‘status’: 2,

                “msg”: “no results”,

            }, json_dumps_params={‘ensure_ascii’: False})

 

 

    def post(self, request, *args, **kwargs):

        #前台通過urlencoded方式提交數據

        try:

            book_obj = models.Book.objects.create(**request.POST.dict()) #create創建對象。將request.POST中存放的提交的關鍵詞參數轉化為字典以**方式傳進去。沒傳參數,這邊會報錯。

            if book_obj:

                return JsonResponse({

                ‘status’: 0,

                “msg”: “ok”,

                “results”: {‘title’:book_obj.title, “price”:book_obj.price}

            }, json_dumps_params={‘ensure_ascii’: False})

        except:    #健壯性

            return JsonResponse({

                ‘status’: 1,

                “msg”: “wrong params”,

            }, json_dumps_params={‘ensure_ascii’: False})

        return JsonResponse({    #可能操作數據庫失敗了

                ‘status’: 2,

                “msg”: “created failed”,

            }, json_dumps_params={‘ensure_ascii’: False})

           

  

JsonResponse返回時,中文會變成unicode,要加json_dumps_params={‘ensure_ascii’:False}選項。但在linux環境下的火狐瀏覽器,加了是亂碼。

 

filter返回queryset對象,對象里是個列表(表名:對象信息(有自定義str就是自定義的信息))。first取里第一個對象(相當於print(第一個對象)values展示對應的對象里的值

<QuerySet [<Book: <<三國演義>>>]>                   #直接.filter

<<三國演義>>                                    #.first()

<QuerySet [{‘title’: ‘三國演義‘, ‘price’: Decimal(‘56.00’)}]>  #.values(‘title’,’price’)

{‘title’: ‘三國演義‘, ‘price’: Decimal(‘56.00’)}             #.values.first()  是個字典

 

 上面序列化的工作很麻煩。drf就是為了方便序列化的。

 

postman可以完成不同方式的請求:getpostput

postman發送數據包有三種方式:form-dataurlencodedjson. 原生djangourlencoded數據提交兼容。

 

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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