標籤: 租車

  • 前瞻布局 穩健經營 東風日產第800萬輛整車下線

    前瞻布局 穩健經營 東風日產第800萬輛整車下線

    建立經銷商能力診斷體系並擴大經銷商經營範圍,進行二手車、汽車保險、汽車金融、汽車租貸等業務的擴充發展,在2016年1-10月達到12。21%的置換率,為東風日產歷史新高,進一步提升了經銷商收益力、服務能力和渠道效率,從而實現更加便捷高效的服務。

    2016年12月26日,東風日產第800萬整車下線儀式在花都二工廠舉行。100餘名媒體記者以及車主代表共同參与和見證這一盛事。

    東風日產副總經理周先鵬在下線儀式上表示:“東風日產的經營理念始終伴隨着中國經濟發展以及汽車產業的升級而轉型,堅持用前瞻性的眼光探索行業發展的態勢,對未來的發展方向提前布局。13年來,東風日產穩健經營、用心發展,從容迎來第八百萬輛整車下線。”

    伴隨着第800萬輛整車下線,東風日產提前完成2016年度銷售目標,截至12月25日,全年銷量達到110萬輛,比去年同期增長13%,再次穩健跨越百萬。

    客戶至上 體系能力全面升級

    品牌順應時代,不斷成長,是企業穩健經營的前提。在購車者年齡越來越年輕的新汽車消費時代,東風日產2016年繼續深化YOUNG NISSAN 戰略,通過一系列“創新、走心、用心”的營銷活動,全面彰顯品牌年輕化心態,極大提升了品牌知名度與好感度。無論是攜手NBA、歐冠等頂級賽事,還是邀約頂級明星易建聯代言新生代TIIDA,都讓消費者近距離感受體育運動的激情與活力;產品營銷方面,新樓蘭、新奇駿、全新軒逸、藍鳥等產品圍繞文化、越野、音樂等不同主題,通過創新的活動形式,不僅讓消費者體驗到各具特色的產品魅力,更展現出不同產品和目標消費者的情懷與個性。據悉,2016年東風日產品牌好感度相較於2015年提升3.8%,首次超越豐田,躋身合資品牌前三。

    客戶服務是企業穩健經營的基礎。2016年,東風日產圍繞“客戶年”的主題,開展“擁抱客戶,用心服務”主題實踐活動,強化全員客戶意識;通過成立地區支持辦公室,以更扁平化的運作架構貼近客戶;同時,在全國77家店開展了一系列的呼叫制培訓方式,使受訓店服務投訴率降幅達到38%。此外,易誠認證車首推兩年四萬公里保修升級政策,此舉為行業首創,深度保障消費者利益。

    渠道健康是企業穩健經營的保障。2016年,東風日產落實p20大城市戰略,優化專營店的數量及效率,經銷商整體收益得到提升;建立經銷商能力診斷體系並擴大經銷商經營範圍,進行二手車、汽車保險、汽車金融、汽車租貸等業務的擴充發展,在2016年1-10月達到12.21%的置換率,為東風日產歷史新高,進一步提升了經銷商收益力、服務能力和渠道效率,從而實現更加便捷高效的服務。

    不僅如此,東風日產更在提升企業體系力方面,未雨綢繆,坐言起行。2016年,秉承“穩健經營”的理念,東風日產腳踏實地、強調客戶服務、渠道和品牌健康成長。價值鏈前端建設也初見成果,先進工程技術中心、啟辰造型中心及東風日產大學,全面投入使用,從產品、研發設計、製造、人才培養等多個緯度鍛造企業內功,提升綜合實力,為東風日產未來新中期事業提供有力支撐。

    智能驅動未來 I³計劃全面展開

    隨着社會及技術層面信息化、智能化的發展,以及國家“智能製造”戰略藍圖的提出,汽車企業面臨着新的的機遇及挑戰。汽車行業已進入了智能時代,順應消費者需求智能化發展的趨勢,東風日產聚焦智能時代,進入以智能技術為驅動的YOUNG NISSAN 3.0時代,發布了“I³計劃”。以全價值鏈智能升級為核心,從智能出行(Intelligent Mobility Technology)、智造品質(Intelligent Manufacture Quality)、智享體驗(Intelligent Customer Experience)三大維度布局未來。

    在智能出行方面,以“零碰撞、零排放、零距離”作為終極目標,開啟汽車技術的智能化升級,東風日產將成為率先導入中國的量產電動車的首個合資品牌;在智造品質方面,構建國內首創“整建制”先進工程技術中心,以数字化開發平台、智能化精工製造和信息化品質管理,實現製造技術的智能化升級;在智享體驗等方面,依託國內首個合資汽車公司自建電商平台車巴巴、率先將VR技術應用於新車體驗的沉浸式產品数字體驗平台、車載智能信息服務的應用,進行顧客全觸點的智能化升級。

    2017年是東風日產再次跨越百萬之後的重要一年,800萬輛整車下線,對東風日產來說是一個歷史性的里程碑,更是一個新的起點。東風日產將以“I³計劃”為基礎,助推品牌年輕化戰略再升級,進入以智能技術為驅動的YOUNG NISSAN 3.0時代。同時,東風日產還將以“客戶年2.0”作為2017年發展的整體指導方向,從消費者需求出發,持續提升品牌力和客戶滿意度,保證主力車型的銷量及新車上市,同時整合網絡安全,強化經銷商基礎,為客戶帶來更加精彩的智能化汽車生活。

    周先鵬表示,“前瞻性的戰略思考,以及穩健高效的執行力,為東風日產更快速響應市場,決勝未來奠定了堅實基礎。在800萬份信賴之上,東風日產砥礪前行,以智能化的未來驅動力,助力東風日產引領行業趨勢,穩健前行。”本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

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

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

    ※超省錢租車方案

  • 2016重磅轎車盤點 自主雄起/合資緊張?

    2016重磅轎車盤點 自主雄起/合資緊張?

    無論如何,2016年都將要過去,而2016年我們看到了帝豪GL與艾瑞澤5這兩款代表自主品牌實力的力作,尤其是帝豪GL對於整個自主品牌都是意義重大,帝豪GL可以說開拓了一個新的細分市場,尺寸介於A級車與B級車之間,價格卻比緊湊型車型高不了多少,這樣的產品力表現值得讚歎。

    這裏小編團隊特地舉行了一次盛大的年度討論,目的就是選出2016年最值得推薦的年度轎車/年度SUV以及2016年自主品牌的新技術。而經過了長時間的激烈討論之後我們終於確定了5款年度轎車,它們都具有強大的產品力,可以說對車市有不小的影響,雖然有些車型的銷量不那麼好看但是實力無需否認,那麼我們一起看看是什麼車型能夠成為年度推薦轎車吧。

    無論如何,2016年都將要過去,而2016年我們看到了帝豪GL與艾瑞澤5這兩款代表自主品牌實力的力作,尤其是帝豪GL對於整個自主品牌都是意義重大,帝豪GL可以說開拓了一個新的細分市場,尺寸介於A級車與B級車之間,價格卻比緊湊型車型高不了多少,這樣的產品力表現值得讚歎。

    而在合資車方面科沃茲的出現可以說是給合資三廂入門車型帶來了新鮮血液,不錯的產品力表現以及定位能夠給自主車帶來不小的衝擊,科沃茲上市開始就成為爆款車型也是實力的印證,混動雅閣的出現攪動了新能源市場,可以說是混動市場的一顆重磅炸彈,加之漂亮的外觀優秀的內飾,混動雅閣讓人難以拒絕。

    最後就是沃爾沃S90了,這款車型依靠漂亮的設計吸引了不少人的目光,而最終價格也是讓人震驚,相信離大賣也不遠了,雖然2016年整个中國車市的重心全都放在了SUV方面,無論是開發的新車型數量還是現有SUV車型的銷量都是節節拔高,但是依然不少人選擇緊湊型轎車,緊湊型轎車的銷量佔比也是十分高,自主轎車在A到B級的產品力補充得不錯,但是B級以上還需要品牌力等更多的補充,2017年即將到來,轎車市場能否迎來更大的輝煌呢?本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

    ※超省錢租車方案

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

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

  • 奔馳最便宜的小轎車到來,價格可能比2.4雅閣還便宜?

    奔馳最便宜的小轎車到來,價格可能比2.4雅閣還便宜?

    奔馳Z級定位小型車,競爭對手鎖定奧迪A1以及MINI,從造型上來看奔馳Z級還是比較的有奔馳家族風格的,大尺寸的輪轂和小巧的車身形成鮮明對比,運動風格比較強烈。從前臉造型上看Z級的造型和奔馳GLC有着比較高的相似度,全LED大燈的造型也和奔馳高端車型幾乎一致,大燈尺寸和進氣格柵都十分大,加上GLC元素的使用使得前臉還是比較有氣勢的。

    奔馳這個品牌在中國市場深耕多年早已深入人心,一說起豪車大家都會想到奔馳寶馬,但是奔馳給大多數人的印象一直是價格昂貴高高在上的,比如售價過百萬的奔馳S、全尺寸SUV奔馳GLS等,但奔馳不止有這些車。

    ↑↑↑目前能夠買到的最便宜的三廂轎車CLA指導價為26.60-37.80萬

    ↑↑↑目前能夠買到最便宜奔馳兩廂轎車奔馳A級指導價為指導價:23.40-36.00萬

    你以為這就是奔馳最便宜車型的價格了嗎?當然不是,外媒繪製了一張奔馳Z級的假想圖,目前奔馳A級以及奔馳CLA都屬於緊湊型車型,而奔馳目前並沒有小型車,而Z級的出現即將填補奔馳在這一市場的空白。

    奔馳Z級定位小型車,競爭對手鎖定奧迪A1以及MINI,從造型上來看奔馳Z級還是比較的有奔馳家族風格的,大尺寸的輪轂和小巧的車身形成鮮明對比,運動風格比較強烈。

    從前臉造型上看Z級的造型和奔馳GLC有着比較高的相似度,全LED大燈的造型也和奔馳高端車型幾乎一致,大燈尺寸和進氣格柵都十分大,加上GLC元素的使用使得前臉還是比較有氣勢的。

    來到尾部,層次豐富的尾部造型也頗有幾分GLC的味道,排氣管的造型十分有運動感,只是尺寸偏小,尾燈的造型也和奔馳現今的SUV車型設計比較相似,Z級在外觀上和奔馳SUV車系比較接近,因此小編預測未來Z級會衍生出SUV車型或者跨界版,名字就叫GLZ?到時候就是小號的GLC了。

    從假想圖看來車頂高度在後排位置下降比較多,小編對於Z級的頭部空間表現表示擔憂。

    Z級的出現拉低了奔馳車型的入門門檻,而和奧迪A1以及寶馬MINI對標的話,小編預計Z級的售價在18萬起,這樣的售價也算是對得起觀眾了,當然由於這類車型比較小眾,因此即使上市也會以進口身份銷售,因此希望售價過低還是不太可能。

    競爭對手:

    奔馳Z級上市后競爭對手主要是奧迪A1、寶馬MINI以及雪鐵龍DS3,相比之下A1有着奧迪的科技感以及龐大的受眾,DS3比較的怪異能夠贏得一些消費者的喜愛,MINI則是哪個經典造型,十分有個性,與它們相比奔馳Z級的道路還比較長。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

    ※超省錢租車方案

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

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

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

  • 個性轎跑SUV不只有合資車型 自主品牌照樣玩兒得溜

    個性轎跑SUV不只有合資車型 自主品牌照樣玩兒得溜

    77萬的頂配售價着實比較實惠,但個人覺得,可以等待它1。4T+6DCT的動力總成組合上市以後再做考慮。啟辰T90指導價格:10。98-15。48萬啟辰T90的關注度自今年早些時候曝光以來就一直不減,這台側面看上去很像本田歌詩圖的SUV尺寸也比以上兩款車型更大,定價也更高。

    很多車企在SUV車型上玩起了跨界,高端的車型有寶馬的X4、X6,中端一些的也有今年大熱的馬自達CX-4、跨界造型的SUV有着轎跑般的外觀和符合SUV的離地間隙,在個性化和車輛通過實用性方面做出了權衡,滿足了很多追求車輛個性人群的需求。

    然而,寶馬X4、X6之流對於普羅大眾來說畢竟還是太遙遠,CX-4作為合資中端跨界SUV,售價對於不少人來說還是高了,那麼就可以看看自主品牌車型,也有不少把個性與跨界玩兒得溜的代表車型。

    吉利帝豪GS

    指導價格:7.78-10.88萬

    帝豪GS的名氣已經非常大啦,從上市以來就已經為自己圈了非常龐大的粉絲團,流線型的轎跑設計相當緊湊美觀,憑藉著吉利品牌近年來優秀的品質做工,帝豪GS從內到外的質感都做到了不輸於合資品牌一貫擁有的水準,十萬出頭的頂配指導價格,配置也是極其豐富,作為年輕人第一台車是非常值得考慮的選擇。

    東風風神AX3

    指導價格:6.97-8.77萬

    AX3是風神旗下定位偏向年輕化的一款跨界型SUV,雖然車身線條勾勒方式趨於平緩紮實,沒有什麼太多的亮點可言,但整車給人的感覺還是相當提氣與精神,內飾層面也以簡潔實用的風格為主,作為一台家用小車來說,8.77萬的頂配售價着實比較實惠,但個人覺得,可以等待它1.4T+6DCT的動力總成組合上市以後再做考慮。

    啟辰T90

    指導價格:10.98-15.48萬

    啟辰T90的關注度自今年早些時候曝光以來就一直不減,這台側面看上去很像本田歌詩圖的SUV尺寸也比以上兩款車型更大,定價也更高。外觀設計官方稱之為“風雕美學”,從視覺效果上看,溜背造型的車身舒展秀氣,而細節處的肌肉線條也體現出一台SUV該有的力量感。更大的車身尺寸也有着更大的車內空間,個人認為,啟辰T90更適合作為家庭的第二輛車購入。

    總結:以上三款車型比較優秀的是在於各自品牌的質量控制方面可以說是當下自主品牌當中做的比較優秀的典型,而在終端售價表現上,十萬左右的價格也是更多人可以接受的範圍,如果是作為第一輛車,小編推薦的是帝豪GS,雖然從動力表現和机械性能層面或許還有提升空間,但是從外觀的顏值和內飾的質感上,GS可以說是非常不錯的選擇。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

    ※回頭車貨運收費標準

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

    ※超省錢租車方案

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

  • 個性外觀動力不凡的德系轎跑 車主們如何評價它?

    個性外觀動力不凡的德系轎跑 車主們如何評價它?

    40萬車主點評:相信大部分購買CC就是看中其外觀,雖然改款后沒以前那麼個性,卻多了幾分沉穩,外觀設計也見仁見智把。DSG雙離合變速箱平順性以及換擋邏輯做到相當出色,大眾可以說是造雙離合變速箱最早一個廠商,所以在技術性上大眾雙離合變速箱更成熟。

    對於中級車市場來說,競爭仍然還是很激烈。雖然中級車非常普遍,但擁有一輛充滿個性運動且能兼顧到家用的,卻是少之又少,然後大眾CC就是其中一款造型非常獨特,有格調,那我們一起來看看已購買這款車的車主如何評價他們。

    大眾CC

    指導價:25.28-34.28萬

    車主一:不忘初心

    購買車型:大眾CC 2016款 1.8TSI 豪華型

    裸車價格:25.28萬

    車主點評:當時相中大眾CC無疑是被其外觀所吸引,轎跑外觀,無框車門,流暢車身線條,呈現出非常運動拉風的一面,個人也認為CC是史上最美的大眾車型。空間表現上,屬於中級車應有的水準。1.8T市內上下班足夠用,提速很輕快,高顏值動力強!是一款非常值得買的車型。

    目前行駛里程:CC目前行駛3680公里,綜合油耗在11L/100km,由於走市區較多,且道路擁堵,這油耗表現我也挺滿意。

    車主二:奮鬥ing

    購買車型:大眾 2016款 2.0TSI 豪華型

    裸車價格:26.40萬

    車主點評:相信大部分購買CC就是看中其外觀,雖然改款后沒以前那麼個性,卻多了幾分沉穩,外觀設計也見仁見智把。DSG雙離合變速箱平順性以及換擋邏輯做到相當出色,大眾可以說是造雙離合變速箱最早一個廠商,所以在技術性上大眾雙離合變速箱更成熟。

    目前行駛里程:目前CC跑了3800公里,綜合油耗在11.5L/100km,還是能接受!

    車主三:透心涼

    購買車型:大眾CC 2016款 2.0TSI 至尊型

    裸車價格:28.08萬

    車主點評:當初不買BBA就是因為之前很早就喜歡CC,也可以說是一種情懷。由於買的是2.0T頂配車型,配置非常豐富,還配備丹拿音響,也算是世界級音響,音質特別棒。其次就是在動力方面,2.0T動力輸出很強勁,高速上超車和加速都是輕鬆事情,轉向也很精準,在操控性上有一定的樂趣。

    目前行駛里程:目前開了7200公里,綜合油耗在12L/100km,追求動力,油耗也必須高。

    編者點評:

    大眾CC可以說在同級別外觀造型最拉風,無框車門吸引不少消費者對其追捧。發動機與變速箱的搭配,動力輸出足夠強勁,急加速直觀感受非常不錯。具有一定操控樂趣和高顏值外觀,你還有什麼理由不買?本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※超省錢租車方案

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

    ※回頭車貨運收費標準

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

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

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

  • 小菜成長之路,警惕淪為 API 調用俠

    小菜成長之路,警惕淪為 API 調用俠

    小菜(化名)在某互聯網公司擔任運維工程師,負責公司後台業務的運維保障工作。由於自己編程經驗不多,平時有不少工作需要開發協助。

    聽說 Python 很火,能快速開發一些運維腳本,小菜也加入 Python 大軍學起來。 Python 語言確實簡單,小菜很快就上手了,覺得自己應對運維開發工作已經綽綽有餘,便不再深入研究。

    背景

    這天老闆給小菜派了一個數據採集任務,要實時統計服務器 TCP 連接數。需求背景是這樣的:開發同事需要知道服務的連接數以及不同狀態連接的比例,以便判斷服務狀態。

    因此,小菜需要開發一個腳本,定期採集並報告 TCP 連接數,提交數據格式定為 json :

    {
      "LISTEN": 4,
      "ESTABLISHED": 100,
      "TIME_WAIT": 10
    }
    

    作為運維工程師,小菜當然知道怎麼查看系統 TCP 連接。
    Linux 系統中有兩個命令可以辦到, netstat 和 ss :

    $ netstat -nat
    Active Internet connections (servers and established)
    Proto Recv-Q Send-Q Local Address           Foreign Address         State
    tcp        0      0 127.0.0.1:8388          0.0.0.0:*               LISTEN
    tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN
    tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN
    tcp        0      0 192.168.56.3:22         192.168.56.1:54983      ESTABLISHED
    tcp6       0      0 :::22                   :::*                    LISTEN
    
    $ ss -nat
    State                    Recv-Q                    Send-Q                                         Local Address:Port                                         Peer Address:Port
    LISTEN                   0                         128                                                127.0.0.1:8388                                              0.0.0.0:*
    LISTEN                   0                         128                                            127.0.0.53%lo:53                                                0.0.0.0:*
    LISTEN                   0                         128                                                  0.0.0.0:22                                                0.0.0.0:*
    ESTAB                    0                         0                                               192.168.56.3:22                                           192.168.56.1:54983
    LISTEN                   0                         128                                                     [::]:22                                                   [::]:*
    

    小菜還知道 ss 命令比 netstat 命令要快,但至於為什麼,小菜就不知道了。

    小菜很快找到老闆,提出了自己的解決方案:寫一個 Python 程序,調用 ss 命令採集 TCP 連接信息,然後再逐條統計。

    老闆告訴小菜,線上服務器很多都是最小化安裝,並不能保證每台機器上都有 ss 或者 netstat 命令。

    老闆還告訴小菜,程序開發要學會 站在巨人的肩膀上 。動手寫代碼前,先調研一番,看是否有現成的解決方案。 切忌重複造輪子 ,浪費時間不說,可能代碼質量還差,效果也不好。

    最後老闆給小菜指了條明路,讓他回去再看看 psutil 。 psutil 是一個 Python 第三方包,用於採集系統性能數據,包括: CPU 、內存、磁盤、網卡以及進程等等。臨走前,老闆還叮囑小菜,完成工作后花點時間研究下這個庫。

    psutil 方案

    小菜搜索 psutil 發現,原來有這麼順手的第三方庫,喜出望外!他立馬裝好 psutil ,準備開干:

    $ pip install psutil
    

    導入 psutil 后,一個函數調用就可以拿到系統所有連接,連接信息非常豐富:

    >>> import psutil
    >>> for conn in psutil.net_connections('tcp'):
    ...     print(conn)
    ...
    sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='192.168.56.3', port=22), raddr=addr(ip='192.168.56.1', port=54983), status='ESTABLISHED', pid=None)
    sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='127.0.0.1', port=8388), raddr=(), status='LISTEN', pid=None)
    sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='0.0.0.0', port=22), raddr=(), status='LISTEN', pid=None)
    sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='127.0.0.53', port=53), raddr=(), status='LISTEN', pid=None)
    sconn(fd=-1, family=<AddressFamily.AF_INET6: 10>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='::', port=22), raddr=(), status='LISTEN', pid=None)
    

    小菜很滿意,感覺不用花多少時間就可搞定數據採集需求了,準時下班有望!噼里啪啦,很快小菜就寫下這段代碼:

    import psutil
    from collections import defaultdict
    
    # 遍歷每個連接,按連接狀態累加
    stats = defaultdict(int)
    for conn in psutil.net_connections('tcp'):
        stats[conn.status] += 1
    
    # 遍歷每種狀態,輸出連接數
    for status, count in stats.items():
        print(status, count)
    

    小菜接着在服務器上測試這段代碼,功能完全正常:

    ESTABLISHED 1
    LISTEN 4
    

    小菜將數據採集腳本提交,並按既定節奏逐步發布到生產服務器上。開發同事很快就看到小菜採集的數據,都誇小菜能力不錯,需求完成得很及時。小菜也很高興,感覺 Python 沒白學。如果用其他語言開發,說不定現在還在加班加點呢!Life is short, use Python! 果然沒錯!

    小菜愈發自信,早就把老闆的話拋到腦後了。 psutil 這個庫這麼好上手,有啥好深入研究的?

    內存悲劇

    突然有一天,其他同事緊急告訴小菜,他開發的採集腳本佔用很多內存, CPU 也跑到了 100% ,已經開始影響線上服務了。小菜還沉浸在成功的喜悅中,收到這個反饋如同晴天霹靂,有點举手無措。

    業務同事告訴小菜,受影響的機器系統連接數非常大,質疑小菜是不是腳本存在性能問題。小菜覺得很背,腳本只是調用 psutil 並統計數據,怎麼就攤上性能故障?腳本影響線上服務,小菜壓力很大,但不知道如何是好,只能跑去找老闆尋求幫助。

    老闆要小菜第一時間停止數據採集,降低影響。復盤故障時,老闆很敏銳地問小菜,是不是用容器保存所有連接了?小菜自己並沒有,但是 psutil 這麼做了:

    >>> psutil.net_connections()
    [sconn(fd=-1, family=<AddressFamily.AF_INET6: 10>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='::', port=22), raddr=(), status='LISTEN', pid=None), sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='0.0.0.0', port=22), raddr=(), status='LISTEN', pid=None), sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='127.0.0.53', port=53), raddr=(), status='LISTEN', pid=None), sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_DGRAM: 2>, laddr=addr(ip='10.0.2.15', port=68), raddr=(), status='NONE', pid=None), sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_DGRAM: 2>, laddr=addr(ip='127.0.0.1', port=8388), raddr=(), status='NONE', pid=None), sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='192.168.56.3', port=22), raddr=addr(ip='192.168.56.1', port=54983), status='ESTABLISHED', pid=None), sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_DGRAM: 2>, laddr=addr(ip='127.0.0.53', port=53), raddr=(), status='NONE', pid=None), sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='127.0.0.1', port=8388), raddr=(), status='LISTEN', pid=None)]
    

    psutil 將採集到的所有 TCP 連接放在一個列表裡返回。如果服務器上有十萬個 TCP 連接,那麼列表裡將有十萬個連接對象。難怪採集腳本吃了那麼多內存!

    老闆告訴小菜,可以用生成器加以解決。與列表不同,生成器逐個返回數據,因此不會佔用太多內存。Python2 中 range 和 xrange 函數的區別也是一樣的道理。

    小菜從 pstuil  fork 了一個分支,並將 net_connections 函數改造成 生成器 :

    def net_connections():
        while True:
            if done:
                break
    
            # 解析一個TCP連接
            conn = xxx
    
            yield conn
    

    代碼上線后,採集腳本內存佔用量果然下降了! 生成器 將統計算法的空間複雜度由原來的 O(n) 優化為 O(1) 。經過這次教訓,小菜不敢再盲目自信了,他決定抽時間好好看看 psutil 的源碼。

    源碼體會

    深入學習源碼后,小菜發現原來 psutil 採集 TCP 連接數的秘笈是:從 /proc/net/tcp 以及 /proc/net/tcp6 讀取連接信息。

    由此,他還進一步了解到 procfs ,這是一個偽文件系統,將內核空間信息以文件方式暴露到用戶空間。 /proc/net/tcp 文件則是提供內核 TCP 連接信息:

    $ cat /proc/net/tcp
      sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode
       0: 0100007F:20C4 00000000:0000 0A 00000000:00000000 00:00000000 00000000 65534        0 18183 1 0000000000000000 100 0 0 10 0
       1: 3500007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000   101        0 16624 1 0000000000000000 100 0 0 10 0
       2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 18967 1 0000000000000000 100 0 0 10 0
       3: 0338A8C0:0016 0138A8C0:D6C7 01 00000000:00000000 02:00023B11 00000000     0        0 22284 4 0000000000000000 20 13 23 10 20
    

    小菜還注意到,連接信息看起來像個自定義類對象,但其實是一個 nametuple :

    # psutil.net_connections()
    sconn = namedtuple('sconn', ['fd', 'family', 'type', 'laddr', 'raddr',
                                 'status', 'pid'])
    

    小菜一開始並不知道作者為啥要這麼做。後來,小菜開始研究 Python 源碼,學習了 Python 類機制后他恍然大悟。

    Python 自定義類的每個實例對象均需要一個 dict 來保存對象屬性,這也就是對象的 屬性空間 。

    如果用自定義類來實現,每個連接都需要創建一個字典,而字典又是 散列表 實現的。如果系統存在成千上萬的連接,開銷可想而知。

    小菜將學到的知識總結起來:對於 數量大 而 屬性固定 的實體,沒有必要用自定義類來實現,用 nametuple 更合適,開銷更小。由此,小菜不經由衷佩服 psutil 的作者。

    CPU悲劇

    後來小菜又收到業務反饋,採集腳本在高併發的服務器上, CPU 使用率很高,需要再優化一下。

    小菜回憶 psutil 源碼,很快就找到了性能瓶頸處: psutil 將連接信息所有字段都解析了,而採集腳本只需要其中的 狀態 字段而已。

    跟老闆商量后,小菜決定自行讀取 procfs 來實現採集腳本,只解析狀態字段,避免不必要的計算開銷。

    procfs 方案

    直接讀取 /proc/net/tcp ,可以得到完整的 TCP 連接信息:

    >>> with open('/proc/net/tcp') as f:
    ...     for line in f:
    ...         print(line.rstrip())
    ...
      sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode
       0: 0100007F:20C4 00000000:0000 0A 00000000:00000000 00:00000000 00000000 65534        0 18183 1 0000000000000000 100 0 0 10 0
       1: 3500007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000   101        0 16624 1 0000000000000000 100 0 0 10 0
       2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 18967 1 0000000000000000 100 0 0 10 0
       3: 0338A8C0:0016 0138A8C0:D6C7 01 00000000:00000000 02:0007169E 00000000     0        0 22284 3 0000000000000000 20 20 33 10 20
    

    其中, IP 、端口、狀態等字段都是以十六進制編碼的。例如, st 列表示狀態,狀態碼 0A 表示 LISTEN 。很快小菜就寫下這段代碼:

    from collections import defaultdict
    
    stat_names = {
        '0A': 'LISTEN',
        '01': 'ESTABLISHED',
        # ...
    }
    
    # 遍歷每個連接,按連接狀態累加
    stats = defaultdict(int)
    
    with open('/proc/net/tcp') as f:
        # 跳過表頭行
        f.readline()
    
        for line in f:
            st = line.strip().split()[3]
            stats[st] += 1
    
    for st, count in stats.items():
        print(stat_names[st], count)
    

    現在,小菜寫代碼比之前講究多了。在統計連接數時,他並不急於將狀態碼解析成名字,而是按原樣統計。等統計完成,他再一次性轉換,這樣狀態碼轉換開銷便降到最低: O(1)  而不是 O(n) 。

    這次改進符合業務同事預期,但小菜決定好好做一遍性能測試,不打無準備之仗。他找業務同事要了一個連接數最大的 /proc/net/tcp 樣本,拉到本地測試。測試結果還算符合預期,採集腳本能夠扛住十萬連接採集壓力。

    性能測試中,小菜發現了一個比較奇怪的問題。同樣的連接規模,把 /proc/net/tcp 拉到本地跑比直接在服務器上跑要快,而本地電腦性能肯定比不上服務器。

    他百思不得其解,又去找老闆幫忙。老闆很快指出到其中的區別,將 /proc/net/tcp 拉到本地就成為普通 磁盤文件 ,而 procfs 是內核映射出來的 偽文件 ,並不是磁盤文件。

    他讓小菜研究一下 Python 文件 IO 以及內核 IO 子系統在處理這兩種文件時有什麼區別,還讓小菜特別留意 IO 緩衝區大小。

    IO緩衝

    小菜打開一個普通的磁盤文件,發現 Python 選的默認緩衝區大小是 4K (讀緩存對象頭 152 字節):

    >>> f = open('test.py')
    >>> f.buffer.__sizeof__()
    4248
    

    但是如果打開的是 procfs 文件, Python 選的緩衝區卻只有 1K ,相差了 4 倍呢!

    >>> f = open('/proc/net/tcp')
    >>> f.buffer.__sizeof__()
    1176
    

    因此,理論上 Python 默認讀取 procfs 發生的上下文切換次數是普通磁盤文件的 4 倍,怪不得會慢。

    雖然小菜還不知道這種現象背後的原因,但是他已經知道怎麼進行優化了。隨即他決定將緩衝區設置為 1M 以上,盡量避免 IO 上下文切換,以空間換時間:

    with open('/proc/net/tcp', buffering=1*1024*1024) as f:
        # ...
    

    經過這次優化,採集腳本在大部分服務器上運行良好,基本可以高枕無憂了。而小菜也意識到 編程語言 以及 操作系統 等底層基礎知識的重要性,他開始制定學習計劃補全計算機基礎知識。

    netlink 方案

    後來負載均衡團隊找到小菜,他們也想統計服務器上的連接信息。由於負載均衡服務器作為入口轉發流量,連接數規模特別大,達到幾十萬,將近百萬的規模。小菜決定好好進行性能測試,再視情況上線。

    測試結果並不樂觀,採集腳本要跑幾十秒鐘才完成, CPU 跑到 100% 。小菜再次調高 IO 緩衝區,但效果不明顯。小菜又測試了 ss 命令,發現 ss 命令要快很多。由於之前嘗到了閱讀源碼的甜頭,小菜很想到 ss 源碼中尋找秘密。

    由於項目時間較緊,老闆提醒小菜先用 strace 命令追蹤 ss 命令的系統調用,便可快速獲悉 ss 的實現方式。老闆演示了 strace 命令的用法,很快就找到了 ss 的秘密 —— Netlink :

    $ strace ss -nat
    ...
    socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_SOCK_DIAG) = 3
    ...
    

    Netlink 套接字是 Linux 提供通訊機制,可用於內核與進程間、進程與進程間通訊。 Netlink 下的 sock_diag 子系統,提供了一種從內核獲取套接字信息的新方式。

    procfs 不同,sock_diag 採用網絡通訊的方式,內核作為服務端接收客戶端進程查詢請求,並以二進制數據包響應查詢結果,效率更高。

    這就是 ss 比 netstat 更快的原因, ss 採用 Netlink 機制,而 netstat 採用 procfs 機制。

    很不幸 Python 並沒有提供 Netlink API ,一般人可能又要干著急了。好在小菜先前有意識地研究了部分 Python 源碼,對 Python 的運行機制有所了解。

    他知道可以用 C 寫一個 Python 擴展模塊,在 C 語言中調用原生系統調用。

    編寫 Python C 擴展模塊可不簡單,對編程功底要求很高,必須全面掌握 Python 運行機制,特別是對象內存管理。

    一朝不慎可能導致程序異常退出、內存泄露等棘手問題。好在小菜已經不是當年的小菜了,他經受住了考驗。

    小菜的擴展模塊上線后,效果非常好,頂住了百萬級連接的採集壓力。

    一個看似簡單得不能再簡單的數據採集需求,背後涉及的知識可真不少,沒有一定的水平還真搞不定。好在小菜成長很快,他最終還是徹底地解決了性能問題,找回了久違的信心。

    內核模塊方案

    雖然性能問題已經徹底解決,小菜還是沒有將其淡忘。

    他時常想:如果可以將統計邏輯放在內核空間做,就不用在內核和進程之間傳遞大量連接信息了,效率應該是最高的!受限於當時的知識水平,小菜還沒有能力實現這個設想。

    後來小菜在研究 Linux 內核時,發現可以用內核模塊來擴展內核的功能,結合 procfs 的工作原理,他找到了技術方案!他順着 /proc/net/tcp 在內核中的實現源碼,依樣畫葫蘆寫了這個內核模塊:

    #include <linux/module.h>
    #include <linux/proc_fs.h>
    #include <linux/seq_file.h>
    #include <net/tcp.h>
    
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("Xiaocai");
    MODULE_DESCRIPTION("TCP state statistics");
    MODULE_VERSION("1.0");
    
    // 狀態名列表
    static char *state_names[] = {
        NULL,
        "ESTABLISHED",
        "SYN_SENT",
        "SYN_RECV",
        "FIN_WAIT1",
        "FIN_WAIT2",
        "TIME_WAIT",
        "CLOSE",
        "CLOSE_WAIT",
        "LAST_ACK",
        "LISTEN",
        "CLOSING",
        NULL
    };
    
    
    static void stat_sock_list(struct hlist_nulls_head *head, spinlock_t *lock,
        unsigned int state_counters[])
    {
        // 套接字節點指針(用於遍歷)
        struct sock *sk;
        struct hlist_nulls_node *node;
    
        // 鏈表為空直接返回
        if (hlist_nulls_empty(head)) {
            return;
        }
    
        // 自旋鎖鎖定
        spin_lock_bh(lock);
    
        // 遍歷套接字鏈表
        sk = sk_nulls_head(head);
        sk_nulls_for_each_from(sk, node) {
            if (sk->sk_state < TCP_MAX_STATES) {
                // 自增狀態計數器
                state_counters[sk->sk_state]++;
            }
        }
    
        // 自旋鎖解鎖
        spin_unlock_bh(lock);
    }
    
    
    static int tcpstat_seq_show(struct seq_file *seq, void *v)
    {
        // 狀態計數器
        unsigned int state_counters[TCP_MAX_STATES] = { 0 };
        unsigned int state;
    
        // TCP套接字哈希槽序號
        unsigned int bucket;
    
        // 先遍歷Listen狀態
        for (bucket = 0; bucket < INET_LHTABLE_SIZE; bucket++) {
            struct inet_listen_hashbucket *ilb;
    
            // 哈希槽
            ilb = &tcp_hashinfo.listening_hash[bucket];
    
            // 遍歷鏈表並統計
            stat_sock_list(&ilb->head, &ilb->lock, state_counters);
        }
    
        // 遍歷其他狀態
        for (bucket = 0; bucket < tcp_hashinfo.ehash_mask; bucket++) {
            struct inet_ehash_bucket *ilb;
            spinlock_t *lock;
    
            // 哈希槽鏈表
            ilb = &tcp_hashinfo.ehash[bucket];
            // 保護鎖
            lock = inet_ehash_lockp(&tcp_hashinfo, bucket);
    
            // 遍歷鏈表並統計
            stat_sock_list(&ilb->chain, lock, state_counters);
        }
    
        // 遍歷狀態輸出統計值
        for (state = TCP_ESTABLISHED; state < TCP_MAX_STATES; state++) {
            seq_printf(seq, "%-12s: %d\n", state_names[state], state_counters[state]);
        }
    
        return 0;
    }
    
    
    static int tcpstat_seq_open(struct inode *inode, struct file *file)
    {
        return single_open(file, tcpstat_seq_show, NULL);
    }
    
    
    static const struct file_operations tcpstat_file_ops = {
        .owner   = THIS_MODULE,
        .open    = tcpstat_seq_open,
        .read    = seq_read,
        .llseek  = seq_lseek,
        .release = single_release
    };
    
    
    static __init int tcpstat_init(void)
    {
        proc_create("tcpstat", 0, NULL, &tcpstat_file_ops);
        return 0;
    }
    
    
    static __exit void tcpstat_exit(void)
    {
        remove_proc_entry("tcpstat", NULL);
    }
    
    module_init(tcpstat_init);
    module_exit(tcpstat_exit);
    

    內核模塊編譯好並加載到內核后, procfs 文件系統提供了一個新文件 /proc/tcpstat ,內容為統計結果:

    $ cat /proc/tcpstat
    ESTABLISHED : 5
    SYN_SENT    : 0
    SYN_RECV    : 0
    FIN_WAIT1   : 0
    FIN_WAIT2   : 0
    TIME_WAIT   : 1
    CLOSE       : 0
    CLOSE_WAIT  : 0
    LAST_ACK    : 0
    LISTEN      : 14
    CLOSING     : 0
    

    當用戶程序讀取這個文件時,內核虛擬文件系統( VFS )調用小菜在內核模塊中寫的處理函數:遍歷內核 TCP 套接字完成統計並格式化統計結果。內核模塊、 VFS 以及套接字等知識超出專欄範圍,不再贅述。

    小菜在服務器上試驗這個內核模塊,真的快得飛起!

    經驗總結

    小菜開始總結這次腳本開發工作中的經驗教訓,他列出了以下關鍵節點:

    1. 依靠 psutil 採集,沒有關注 psutil 實現導致性能問題;
    2. 用生成器代替列表返回連接信息,解決內存瓶頸;
    3. 直接讀取 procfs 文件系統,部分解決 CPU 性能瓶頸;
    4. 通過調節 IO 緩衝區大小,進一步降低 CPU 開銷;
    5. Netlink 代替 procfs ,徹底解決性能問題;
    6. 實驗內核模塊思路,終極解決方案快得飛起;

    這些問題節點,一個比一個深入,沒有一定功底是搞不定的。小菜從剛開始跌跌撞撞,到後來獨當一面,快速成長的關鍵在於善於在問題中總結經驗教訓:

    • 程序開發完一定要做性能測試,看能夠扛住多大的壓力;
    • 使用任何工具,需要準確理解其背後的原理,避免誤用;
    • 對編程語言以及操作系統源碼要保持好奇心;
    • 計算機基礎知識很重要,需要及時補全才能達到新高度;
    • 學會問題發散,舉一反三;

    更多章節

    洞悉 Python 虛擬機運行機制,探索高效程序設計之道!

    到底如何才能提升我的 Python 開發水平,向更高一級的崗位邁進? 如果你有這些問題或者疑惑,請訂閱我們的專欄,閱讀更多章節:

    • 內建對象
    • 虛擬機
    • 函數機制
    • 類機制
    • 生成器與協程
    • 內存管理機制

    附錄

    更多 Python 技術文章請訪問:小菜學Python,轉至 原文 可獲得最佳閱讀體驗。

    訂閱更新,獲取更多學習資料,請關注 小菜學編程 :

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

    【其他文章推薦】

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

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

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

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

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

    ※超省錢租車方案

  • 自己動手寫SQL執行引擎

    自己動手寫SQL執行引擎

    自己動手寫SQL執行引擎

    前言

    在閱讀了大量關於數據庫的資料后,筆者情不自禁產生了一個造數據庫輪子的想法。來驗證一下自己對於數據庫底層原理的掌握是否牢靠。在筆者的github中給這個database起名為Freedom。

    整體結構

    既然造輪子,那當然得從前端的網絡協議交互到後端的文件存儲全部給擼一遍。下面是Freedom實現的整體結構,裡面包含了實現的大致模塊:

    最終存儲結構當然是使用經典的B+樹結構。當然在B+樹和文件系統block塊之間的轉換則通過Buffer(Page) Manager來進行。當然了,為了完成事務,還必須要用WAL協議,其通過Log Manager來操作。
    Freedom採用的是索引組織表,通過DruidSQL Parse來將sql翻譯為對應的索引操作符進而進行對應的語義操作。

    MySQL Protocol結構

    client/server之間的交互採用的是MySQL協議,這樣很容易就可以和mysql client以及jdbc進行交互了。

    query packet

    mysql通過3byte的定長包頭去進行分包,進而解決tcp流的讀取問題。再通過一個sequenceId來再應用層判斷packet是否連續。

    result set packet

    mysql協議部分最複雜的內容是其對於result set的讀取,在NIO的方式下加重了複雜性。
    Freedom通過設置一系列的讀取狀態可以比較好的在Netty框架下解決這一問題。

    row packet

    還有一個較簡單的是對row格式進行讀取,如上圖所示,只需要按部就班的解析即可。

    由於協議解析部分較為簡單,在這裏就不再贅述。
    關注筆者公眾號,獲取更多乾貨文章

    SQL Parse

    Freedom採用成熟好用的Druid SQL Parse作為解析器。事實上,解析sql就是將用文本表示
    的sql語義表示為一系列操作符(這裏限於篇幅原因,僅僅給出select中where過濾的原理)。

    對where的處理

    例如where後面的謂詞就可以表示為一系列的以樹狀結構組織的SQL表達式,如下圖所示:

    當access層通過游標提供一系列row后,就可以通過這個樹狀表達式來過濾出符合where要求的數據。Druid採用了Parse中常用的visitor很方便的處理上面的表達式計算操作。

    對join的處理

    對join最簡單處理方案就是對兩張表進行笛卡爾積,然後通過上面的where condition進行過濾,如下圖所示:

    Freedom對於縮小笛卡爾積的處理

    由於Freedom採用的是B+樹作為底層存儲結構,所以可以通過where謂詞來界定B+樹scan(搜索)的範圍(也即最大搜索key和最小搜索key在B+樹種中的位置)。考慮sql

    select a.*,b.* from t_archer as a join t_rider as b where a.id>=3 and a.id<=11 b.id and b.id>=19 b.id<=31
    

    那麼就可以界定出在id這個索引上,a的scan範圍為[3,11],如下圖所示:

    b的scan範圍為[19,31],如下圖所示(假設兩張表數據一樣,便於繪圖):

    scan少了從原來的15*15(一共15個元素)次循環減少到4*4次循環,即循環次數減少到7.1%

    當然如果存在join condition的話,那麼Freedom在底層cursor遞歸處理的過程中會預先過濾掉一部分數據,進一步減少上層的過濾。

    B+Tree的磁盤結構

    leaf磁盤結構

    Freedom的B+Tree是存儲到磁盤裡的。考慮到存儲的限制以及不定長的key值,所以會變得非常複雜。Freedom以page為單位來和磁盤進行交互。恭弘=叶 恭弘子節點和非恭弘=叶 恭弘子節點都由page承載並刷入磁盤。結構如下所示:

    一個元組(tuple/item)在一個page中分為定長的ItemPointer和不定長的Item兩部分。
    其中ItemPointer裏面存儲了對應item的起始偏移和長度。同時ItemPointer和Item如圖所示是向著中心方向進行伸張,這種結構很有效的組織了非定長Item。

    leaf和node節點在Page中的不同

    雖然leaf和node在page中組織結構一致,但其item包含的項確有區別。由於Freedom採用的是索引組織表,所以對於leaf在聚簇索引(clusterIndex)和二級索引(secondaryIndex)中對item的表示也有區別,如下圖所示:

    其中在二級索引搜索時通過secondaryIndex通過index-key找到對應的clusterId,再通過
    clusterId在clusterIndex中找到對應的row記錄。
    由於要落盤,所以Freedom在node節點中的item裏面寫入了index-key對應的pageno,
    這樣就可以容易的從磁盤恢復所有的索引結構了。

    B+Tree在文件中的組織

    有了Page結構,我們就可以將數據承載在一個個page大小的內存裏面,同時還可以將page刷新到對應的文件里。有了node.item中的pageno,我們就可以較容易的進行文件和內存結構之間的互相映射了。
    B+樹在磁盤文件中的組織如下圖所示:

    B+樹在內存中相對應的映射結構如下圖所示:

    文件page和內存page中的內容基本是一致的,除了一些內存page中特有的字段,例如dirty等。

    每個索引一個B+樹

    在Freedom中,每個索引都是一顆B+樹,對記錄的插入和修改都要對所有的B+樹進行操作。

    B+Tree的測試

    筆者通過一系列測試case,例如隨機變長記錄對B+樹進行插入並落盤,修復了其中若干個非常詭異的corner case。

    B+Tree的todo

    筆者這裏只是完成了最簡單的B+樹結構,沒有給其添加併發修改的鎖機制,也沒有在B+樹做操作的時候記錄log來保證B+樹在宕機等災難性情況下的一致性,所以就算完成了這麼多的工作量,距離一個高併發高可用的bptree還有非常大的距離。

    Meta Data

    table的元信息由create table所創建。創建之後會將元信息落盤,以便Freedom在重啟的時候加載表信息。每張表的元信息只佔用一頁的空間,依舊復用page結構,主要保存的是聚簇索引和二級索引的信息。元信息對應的Item如下圖所示:

    如果想讓mybatis可以自動生成關於Freedom的代碼,還需實現一些特定的sql來展現Freedom的元信息。這個在筆者另一個項目rider中有這樣的實現。原理如下圖所示:

    實現了上述4類SQL之後,mybatis-generator就可以通過jdbc從Freedom獲取元信息進而自動生成代碼了。

    事務支持

    由於當前Freedom並沒有保證併發,所以對於事務的支持只做了最簡單的WAL協議。通過記錄redo/undolog從而實現原子性。

    redo/undo log協議格式

    Freedom在每做一個修改操作時,都會生成一條日誌,其中記錄了修改前(undo)和修改后(redo)的行信息,undo用來回滾,redo用來宕機recover。結構如下圖所示:

    WAL協議

    WAL協議很好理解,就是在事務commit前將當前事務中所產生的的所有log記錄刷入磁盤。
    Freedom自然也做了這個操作,使得可以在宕機后通過log恢復出所有的數據。

    回滾的實現

    由於日誌中記錄了undo,所以對於一個事務的回滾直接通過日誌進行undo即可。如下圖所示:

    宕機恢復

    Freedom如果在page全部刷盤之後關機,則可以由通過加載page的方式獲取原來的數據。
    但如果突然宕機,例如kill -9之後,則可以通過WAL協議中記錄的redo/undo log來重新
    恢復所有的數據。由於時間和精力所限,筆者並沒有實現基於LSN的檢查點機制。

    Freedom運行

    git clone https://github.com/alchemystar/Freedom.git
    // 並沒有做打包部署的工作,所以最簡單的方法是在java編輯器裏面
    run alchemystar.freedom.engine.server.main
    

    以下是筆者實際運行Freedom的例子:

    join查詢

    delete回滾

    Freedom todo

    Freedom還有很多工作沒有完成,例如有層次的鎖機制和MVCC等,由於工作忙起來就耽擱了。
    於是筆者就看了看MySQL源碼的實現理解了一下鎖和MVCC實現原理,並寫了兩篇博客。比起
    自己動手擼實在是輕鬆太多了_

    MVCC

    https://my.oschina.net/alchemystar/blog/1927425

    二階段鎖

    https://my.oschina.net/alchemystar/blog/1438839

    尾聲

    在造輪子的過程中一開始是非常有激情非常快樂的。但隨着系統越來越龐大,複雜性越來越高,進度就會越來越慢,還時不時要推翻自己原來的設想並重新設計,然後再協同修改關聯的所有代碼,就如同泥沼,越陷越深。至此,筆者才領悟了軟件工程最重要的其實是控制複雜度!始終保持簡潔的接口和優雅的設計是實現一個大型系統的必要條件。

    收穫與遺憾

    這次造輪子的過程基本滿足了筆者的初衷,通過寫一個數據庫來學習數據庫。不僅僅是加深了理解,最重要的是筆者在寫的過程中終於明白了數據庫為什麼要這麼設計,為什麼不那樣設計,僅僅對書本的閱讀可能並不會有這些思考與領悟。
    當然,還是有很多遺憾的,Freedom並沒有實現鎖機制和MVCC。由於只能在工作閑暇時間寫,所以斷斷續續寫了一兩個月,工作一忙就將這個項目閑置了。現在將Freedom的設計寫出來,希望大家能有所收穫。
    更多乾貨,盡在解Bug之路:

    github鏈接

    https://github.com/alchemystar/Freedom

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

    【其他文章推薦】

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

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

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

    ※超省錢租車方案

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

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

  • 阻塞隊列一——java中的阻塞隊列

    阻塞隊列一——java中的阻塞隊列

    目錄

    • 阻塞隊列簡介:介紹阻塞隊列的特性與應用場景
    • java中的阻塞隊列:介紹java中實現的供開發者使用的阻塞隊列
    • BlockQueue中方法:介紹阻塞隊列的API接口
    • 阻塞隊列的實現原理:具體的例子說明阻塞隊列的實現原理
    • 總結

    阻塞隊列簡介

    阻塞隊列(BlockingQueue)首先是一個支持先進先出的隊列,與普通的隊列完全相同;
    其次是一個支持阻塞操作的隊列,即:

    • 當隊列滿時,會阻塞執行插入操作的線程,直到隊列不滿。
    • 當隊列為空時,會阻塞執行獲取操作的線程,直到隊列不為空。

    阻塞隊列用在多線程的場景下,因此阻塞隊列使用了鎖機制來保證同步,這裏使用的可重入鎖;
    而對於阻塞與喚醒機制則有與鎖綁定的Condition實現

    應用場景:生產者消費者模式

    java中的阻塞隊列

    java中的阻塞隊列根據容量可以分為有界隊列和無界隊列:

    • 有界隊列:隊列中只能存儲有限個元素,超出后存放元素線程會被阻塞或者失敗。
    • 無界隊列:隊列中可以存儲無限個元素。

    java8中提供了7種阻塞隊列阻塞隊列供開發者使用,如下錶:

    類名 描述
    ArrayBlockingQueue 一個由數組結構組成的有界阻塞隊列
    LinkedBlockingQueue 由鏈表結構組成的有界阻塞隊列(默認大小Integer.MAX_VALUE)
    PriorityBlockingQueue 支持優先級排序的無界阻塞隊列
    DelayQueue 使用優先級隊列實現的延遲無界阻塞隊列
    SynchronousQueue 不存儲元素的阻塞隊列,即單個元素的隊列
    LinkedTransferQueue 由鏈表結構組成的無界阻塞隊列
    LinkedBlockingDeque 由鏈表結構組成的雙向阻塞隊列

    另外還有一個在ScheduledThreadPoolExecutor中實現的DelayedWorkQueue阻塞隊列,
    但這個阻塞隊列開發者不能使用。它們之間的UML類圖如下圖:

    BlockingQueue接口是阻塞隊列對外的訪問接口,所有的阻塞隊列都實現了BlockQueue中的方法

    BlockQueue中方法

    作為一個隊列的核心方法就是入隊和出隊。由於存在阻塞策略,BlockQueue將出隊入隊的情況分為了四組,每組提供不同的方法:

    • 拋出異常:當隊列滿時,如果再往隊列中插入元素,則拋出IllegalStateException異常;
      當隊列為空時,從隊列中獲取元素則拋出NoSuchElementException異常。

    • 返回特定值(布爾值):當隊列滿時,如果再往隊列中插入元素,則返回false;當隊列為空時,從隊列中獲取元素則返回null。

    • 一直阻塞:當隊列滿時,如果再往隊列中插入元素,阻塞當前線程直到隊列中至少一個被移除或者響應中斷退出;
      當隊列為空時,則阻塞當前線程直到至少一個元素元素入隊或者響應中斷退出。

    • 超時退出:當隊列滿時,如果再往隊列中插入元素,阻塞當前線程直到隊列中至少一個被移除或者達到指定的等待時間退出或者響應中斷退出;
      當隊列為空時,則阻塞當前線程直到至少一個元素元素入隊或者達到指定的等待時間退出或者響應中斷退出。

    對於每種情況BlockingQueue提供的方法如下錶:

    方法\處理方式 拋出異常 返回特定值(布爾值) 一直阻塞 超時退出
    插入 add(e) offer(e) put(e) offer(e,time,unit)
    移除 remove() poll() take() poll(time.unit)
    檢查 element() peek() 不可用 不可用

    上述方法一般用於生產者-消費者模型中,是其中的生產和消費操作隊列的核心方法。
    除了這些方法,BlockingQueue還提供了一些其他的方法如下錶:

    方法名稱 描述
    remove(Object o) 從隊列中移除一個指定值
    size() 獲取隊列中元素的個數
    contains(Object o) 判斷隊列是否包含指定的元素,但是這個元素在這次判斷完可能就會被消費
    drainTo(Collection<? super E> c) 將隊列中元素放在給定的集合中,並返回添加的元素個數
    drainTo(Collection<? super E> c, int maxElements) 將隊列中元素取maxElements(不超過隊列中元素個數)個放在給定的集合中,並返回添加的元素個數
    remainingCapacity() 計算隊列中還可以存放的元素個數
    toArray() 以objetc數組的形式獲取隊列中所有的元素
    toArray(T[] a) 以給定類型數組的方式獲取隊列中所有的元素
    clear() 清空隊列,危險的操作

    阻塞隊列的實現原理

    阻塞隊列的實現依靠通知模式實現:當生產者向滿了的隊列中添加元素時,會阻塞住生產者,
    直到消費者消費了一個隊列中的元素後會通知消費者隊列可用,此時再由生產者向隊列中添加元素。反之亦然。

    阻塞隊列的阻塞喚醒依靠Condition——條件隊列來實現。

    ArrayBlockingQueue為例說明:

    ArrayBlockingQueue的定義:

    public class ArrayBlockingQueue<E> extends AbstractQueue<E>
            implements BlockingQueue<E>, java.io.Serializable {
       
        /** The queued items */
        //以數組的結構存儲隊列的元素,採用的是循環數組
        final Object[] items;
    
        /** items index for next take, poll, peek or remove */
        //隊列的隊頭索引
        int takeIndex;
    
        /** items index for next put, offer, or add */
        //隊列的隊尾索引
        int putIndex;
    
        /** Number of elements in the queue */
        //隊列中元素的個數
        int count;
    
        /** Main lock guarding all access */
        //對於ArrayBlockingQueue所有的操作都需要加鎖,
        final ReentrantLock lock;
    
        /** Condition for waiting takes */
        //條件隊列,當隊列為空時阻塞消費者並在生產者生產後喚醒消費者
        private final Condition notEmpty;
    
        /** Condition for waiting puts */
        //條件隊列,當隊列滿時阻塞生產者,並在消費者消費隊列后喚醒生產者
        private final Condition notFull;
    }
    

    根據類的定義字段可以看到,有兩個Condition條件隊列,猜測以下過程

    • 當隊列為空,消費者試圖消費時應該調用notEmpty.await()方法阻塞,並在生產者生產後調用notEmpty.single()方法
    • 當隊列已滿,生產者試圖放入元素應調用notFull.await()方法阻塞,並在消費者消費隊列后調用notFull.single()方法

    向隊列中添加元素put()方法的添加過程。

        /**
        * 向隊列中添加元素
        * 當隊列已滿時需要阻塞當前線程
        * 放入元素后喚醒因隊列為空阻塞的消費者
        */
        public void put(E e) throws InterruptedException {
            checkNotNull(e);
            final ReentrantLock lock = this.lock;
            lock.lockInterruptibly();
            try {
                //當隊列已滿時需要notFull.await()阻塞當前線程
                //offer(e,time,unit)方法就是阻塞的時候加了超時設定
                while (count == items.length)
                    notFull.await();
                //放入元素的過程
                enqueue(e);
            } finally {
                lock.unlock();
            }
        }
        
        /**enqueue實際添加元素的方法*/
        private void enqueue(E x) {
            // assert lock.getHoldCount() == 1;
            // assert items[putIndex] == null;
            final Object[] items = this.items;
            items[putIndex] = x;
            if (++putIndex == items.length)
                putIndex = 0;
            count++;
            //如果條件隊列中存在等待的線程
            //喚醒
            notEmpty.signal();
        }
    

    從隊列中獲取元素take()方法的獲取過程。

        /**
        * 從隊列中獲取元素
        * 當隊列已空時阻塞當前線程
        * 從隊列中消費元素后喚醒等待的生產線程
        */
        public E take() throws InterruptedException {
            final ReentrantLock lock = this.lock;
            lock.lockInterruptibly();
            try {
                //隊列為空需要阻塞當前線程
                while (count == 0)
                    notEmpty.await();
                //獲取元素的過程
                return dequeue();
            } finally {
                lock.unlock();
            }
        }
        
        /**dequeue實際消費元素的方法*/
        private E dequeue() {
           // assert lock.getHoldCount() == 1;
           // assert items[takeIndex] != null;
           final Object[] items = this.items;
           @SuppressWarnings("unchecked")
           E x = (E) items[takeIndex];
           items[takeIndex] = null;
           if (++takeIndex == items.length)
               takeIndex = 0;
           count--;
           if (itrs != null)
               itrs.elementDequeued();
           //消費元素后從喚醒阻塞的生產者線程
           notFull.signal();
           return x;
        }
    

    總結

    阻塞隊列提供了不同於普通隊列的增加、刪除元素的方法,核心在與隊列滿時阻塞生產者和隊列空時阻塞消費者。
    這一阻塞過程依靠與鎖綁定的Condition對象實現。Condition接口的實現在AQS中實現,具體的實現類是
    ConditionObject

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

    【其他文章推薦】

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

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

    ※超省錢租車方案

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

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

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

  • Python面試進階問題,__init__和__new__的區別是什麼?

    Python面試進階問題,__init__和__new__的區別是什麼?

    本文始發於個人公眾號:TechFlow,原創不易,求個關注

    今天這篇是Python專題的第17篇文章,我們來聊聊Python當中一個新的默認函數__new__。

    上一篇當中我們講了如何使用type函數來動態創建Python當中的類,除了type可以完成這一點之外,還有另外一種用法叫做metaclass。原本這一篇應該是繼續元類的內容,講解metaclass的使用。但是metaclass當中用到了一個新的默認函數__new__,關於這個函數大家可能會比較陌生,所以在我們研究metaclass之前,我們先來看看__new__這個函數的用法。

    真假構造函數

    如果你去面試Python工程師的崗位,面試官問你,請問Python當中的類的構造函數是什麼?

    你不假思索,當然是__init__啦!如果你這麼回答,很有可能你就和offer無緣了。因為在Python當中__init__並不是構造函數,__new__才是。是不是有點蒙,多西得(日語:為什麼)?我們不是一直將__init__方法當做構造函數來用的嗎?怎麼又冒出來一個__new__,如果__new__才是構造函數,那麼為什麼我們創建類的時候從來不用它呢?

    別著急,我們慢慢來看。首先我們回顧一下__init__的用法,我們隨便寫一段代碼:

    class Student:
        def __init__(self, name, gender):
            self.name = name
            self.gender = gender

    我們一直都是這麼用的,對不對,毫無問題。但是我們換一個問題,我們在Python當中怎麼實現單例(Singleton)的設計模式呢?怎麼樣實現工廠呢?

    從這個問題出發,你會發現只使用__init__函數是不可能完成的,因為__init__並不是構造函數,它只是初始化方法。也就是說在調用__init__之前,我們的實例就已經被創建好了,__init__只是為這個實例賦上了一些值。如果我們把創建實例的過程比喻成做一個蛋糕,__init__方法並不是烘焙蛋糕的,只是點綴蛋糕的。那麼顯然,在點綴之前必須先烘焙出一個蛋糕來才行,那麼這個烘焙蛋糕的函數就是__new__。

    __new__函數

    我們來看下__new__這個函數的定義,我們在使用Python面向對象的時候,一般都不會重構這個函數,而是使用Python提供的默認構造函數,Python默認構造函數的邏輯大概是這樣的:

    def __new__(cls, *args, **kwargs):
        return super().__new__(cls, *args, **kwargs)

    從代碼可以看得出來,函數當中基本上什麼也沒做,就原封不動地調用了父類的構造函數。這裏隱藏着Python當中類的創建邏輯,是根據繼承關係一級一級創建的。根據邏輯關係,我們可以知道,當我們創建一個實例的時候,實際上是先調用的__new__函數創建實例,然後再調用__init__對實例進行的初始化。我們可以簡單做個實驗:

    class Test:
        def __new__(cls):
            print('__new__')
            return object().__new__(cls)
        def __init__(self):
            print('__init__')

    當我們創建Test這個類的時候,通過輸出的順序就可以知道Python內部的調用順序。

    從結果上來看,和我們的推測完全一樣。

    單例模式

    那麼我們重載__new__函數可以做什麼呢?一般都是用來完成__init__無法完成的事情,比如前面說的單例模式,通過__new__函數就可以實現。我們來簡單實現一下:

    class SingletonObject:
        def __new__(cls, *args, **kwargs):
            if not hasattr(SingletonObject, "_instance"):
                SingletonObject._instance = object.__new__(cls)
            return SingletonObject._instance
        
        def __init__(self):
            pass

    當然,如果是在併發場景當中使用,還需要加上線程鎖防止併發問題,但邏輯是一樣的。

    除了可以實現一些功能之外,還可以控制實例的創建。因為Python當中是先調用的__new__再調用的__init__,所以如果當調用__new__的時候返回了None,那麼最後得到的結果也是None。通過這個特性,我們可以控制類的創建。比如設置條件,只有在滿足條件的時候才能正確創建實例,否則會返回一個None。

    比如我們想要創建一個類,它是一個int,但是不能為0值,我們就可以利用__new__的這個特性來實現:

    class NonZero(int):
        def __new__(cls, value):
            return super().__new__(cls, value) if value != 0 else None

    那麼當我們用0值來創建它的時候就會得到一個None,而不是一個實例。

    工廠模式

    理解了__new__函數的特性之後,我們就可以靈活運用了。我們可以用它來實現許多其他的設計模式,比如大名鼎鼎經常使用的工廠模式

    所謂的工廠模式是指通過一個接口,根據參數的取值來創建不同的實例。創建過程的邏輯對外封閉,用戶不必關係實現的邏輯。就好比一個工廠可以生產多種零件,用戶並不關心生產的過程,只需要告知需要零件的種類。也因此稱為工廠模式。

    比如說我們來創建一系列遊戲的類:

    class Last_of_us:
        def play(self):
            print('the Last Of Us is really funny')
            
            
    class Uncharted:
        def play(self):
            print('the Uncharted is really funny')
            

    class PSGame:
        def play(self):
            print('PS has many games')

    然後這個時候我們希望可以通過一個接口根據參數的不同返回不同的遊戲,如果不通過__new__,這段邏輯就只能寫成函數而不能通過面向對象來實現。通過重載__new__我們就可以很方便地用參數來獲取不同類的實例:

    class GameFactory:
        games = {'last_of_us': Last_Of_us, 'uncharted': Uncharted}
        def __new__(cls, name):
            if name in cls.games:
                return cls.games[name]()
            else:
                return PSGame()
            

    uncharted = GameFactory('uncharted')
    last_of_us = GameFactory('last_of_us')

    總結

    相信看到這裏,關於__new__這個函數的用法應該都能理解了。一般情況下我們是用不到這個函數的,只會在一些特殊的場景下使用。雖然如此,我們學會它並不只是用來實現設計模式,更重要的是可以加深我們對於Python面向對象的理解。

    除此之外,另一個經常使用__new__場景是元類。所以今天的這篇文章其實也是為了後面介紹元類的其他用法打基礎。

    如果喜歡本文,可以的話,請點個關注,給我一點鼓勵,也方便獲取更多文章。

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

    【其他文章推薦】

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

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

    ※回頭車貨運收費標準

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

    ※超省錢租車方案

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

  • 超強教程!在樹莓派上構建多節點K8S集群!

    在很長一段時間里,我對於在樹莓派上搭建Kubernetes集群極為感興趣。在網絡上找到一些教程並且跟着實操,我已經能夠將Kubernetes安裝在樹莓派上,並在三個Pi集群中工作。然而,在master節點上對於RAM和CPU的要求已經超過了我的樹莓派所能提供的,因此在執行Kubernetes任務時性能並不優異。這也使得就地升級Kubernetes成為不可能。

    所以,我看到業界應用最為廣泛的Kubernetes管理平台創建者Rancher Labs推出輕量級Kubernetes發行版K3s時,十分興奮,它專為資源受限場景而設計,還針對ARM處理器進行了優化,這使得在樹莓派上運行Kubernetes更加可行。在本文中,我將使用K3s和樹莓派創建一個Kubernetes集群。

    前期準備

    要創建本文中的Kubernetes集群,我們需要準備:

    • 至少一個樹莓派(帶有SD卡和電源適配器)

    • 以太網電纜

    • 將我們所有的樹莓派連接在一起的交換機或路由器

    我將從網絡上安裝K3s,所以需要通過路由器訪問互聯網。

    集群架構

    對於這一集群,我們將使用3個樹莓派。第一個樹莓派我把它命名為kmaster,並分配一個靜態IP 192.168.0.50(因為我的本地網絡是192.168.0.0/24)。第一個worker節點(也就是第二個Pi),我們稱它為knode1並分配IP 192.168.0.51。最後一個worker節點,我們稱它為knode2並分配IP 192.168.0.52。

    當然如果你的網絡和我不一樣,可以使用你能夠獲得網絡IP。只要在本文使用IP的任何地方替換你自己的值即可。

    為了不必再通過IP引用每個節點,我們將其主機名添加到PC上的/ etc / hosts文件中。

    echo -e "192.168.0.50\tkmaster" | sudo tee -a /etc/hosts
    echo -e "192.168.0.51\tknode1" | sudo tee -a /etc/hosts
    echo -e "192.168.0.52\tknode2" | sudo tee -a /etc/hosts
    

    安裝master節點

    現在我們已經準備好,可以開始安裝master節點。第一步,安裝最新的Raspbian鏡像。我之前寫過一篇詳細的文章介紹為什麼需要最新的鏡像,感興趣的朋友可以在訪問鏈接查看:

    https://carpie.net/articles/headless-pi-with-static-ip-wired-edition

    接下來,開始安裝Raspbian,啟用SSH server,為kmaster設置主機名稱並分配靜態IP 192.168.0.50。

    既然Raspbian已經在master節點上安裝完畢,讓我們啟用我們的master Pi並通過ssh進入它:

    ssh pi@kmaster
    

    現在我們要準備安裝K3s。在master Pi上運行:

    curl -sfL https://get.k3s.io | sh -
    

    命令執行完畢之後,我們就有了一個已經設置好的單節點集群並且正在運行中!讓我們檢查一下。依舊是在這個Pi上,運行:

    sudo kubectl get nodes
    

    你應該看到類似以下內容:

    NAME     STATUS   ROLES    AGE    VERSION
    kmaster  Ready    master   2m13s  v1.14.3-k3s.1
    

    提取join token

    我們想要添加一對worker節點。在這些節點上安裝K3s,我們需要一個join token。Join token存在於master節點的文件系統上。讓我們複製並將它保存在某個地方,稍後我們可以獲取它:

    sudo cat /var/lib/rancher/k3s/server/node-token
    

    安裝worker節點

    為兩個worker節點獲取一些SD卡,並在每個節點上安裝Raspbian。對於其中一個,將主機名設置為knode1並分配IP 192.168.0.51。對於另一個,將主機名設置為knode2並分配IP 192.168.0.52。現在,讓我們安裝K3s。

    啟動你的第一個worker節點,並通過ssh進入它:

    ssh pi@knode1
    

    在這個Pi上,我們將像之前一樣安裝K3s,但我們將給安裝程序額外的參數,讓它了解我們正在安裝一個worker節點並且要加入一個現有集群:

    curl -sfL http://get.k3s.io | K3S_URL=https://192.168.0.50:6443 \
    K3S_TOKEN=join_token_we_copied_earlier sh -
    

    使用從上個部分提取出來的join token替換join_token_we_copied_earlier。為knode2重複這些步驟。

    從我們的PC訪問集群

    每當我們要檢查或修改集群時,都必須通過SSH到master節點來運行kubectl,這很煩人。因此,我們像將kubectl放在我們的PC上,但是首先讓我們從master節點獲取所需的配置信息。通過SSH進入kmaster,並運行:

    sudo cat /etc/rancher/k3s/k3s.yaml
    

    複製配置信息並返回到你的PC。為配置創建一個目錄:

    mkdir ~/.kube
    

    保存複製的配置為~/.kube/config。現在編輯文件並更改:

    server: https://localhost:6443
    

    改為:

    server: https://kmaster:6443
    

    為了安全起見,請將文件的讀/寫權限限製為你自己:

    chmod 600 ~/.kube/config
    

    現在讓我們在我們的PC上安裝kubectl(如果你還沒有)。Kubernetes網站上有針對各種平台執行此操作的說明。由於我正在運行Linux Mint(一個Ubuntu衍生版本),因此我將在此處显示Ubuntu的說明:

    sudo apt update && sudo apt install -y apt-transport-https
    curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
    echo "deb https://apt.kubernetes.io/ kubernetes-xenial main" | \
    sudo tee -a /etc/apt/sources.list.d/kubernetes.list
    sudo apt update && sudo apt install kubectl
    

    如果你還不熟悉,上述命令為Kubernetes添加了一個Debian倉庫,獲取其GPG密鑰以確保安全,然後更新軟件包列表並安裝kubectl。現在,我將通過標準軟件更新機制獲得有關kubectl更新的通知。

    現在我們可以從我們的PC檢查我們的集群,運行:

    kubectl get nodes
    

    你應該看到類似以下內容:

    NAME     STATUS  ROLES   AGE   VERSION
    kmaster  Ready   master  12m   v1.14.3-k3s.1
    knode1   Ready   worker  103s  v1.14.3-k3s.1
    knode1   Ready   worker  103s  v1.14.3-k3s.1
    

    Congratulations!你現在已經有一個正在工作的3個節點的Kubernetes集群!

    使用K3s的附加 bonus

    如果你運行kubectl get pods –all-namespaces,你將看到一些Traefik的額外pod。Treafik是一個反向代理和負載均衡器,我們可以使用它從單個入口點將流量引導到我們的集群中。Kubernetes當然也可以安裝Traefik,但不是默認提供的。所以K3s中默認提供Traefik是一個非常棒的設計!

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

    【其他文章推薦】

    ※超省錢租車方案

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

    ※回頭車貨運收費標準

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

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

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