中國特供不能買?14萬起這幾款SUV比途觀、漢蘭達更值!

98萬將空間玩到了極致本田聞名於世的除了讓人津津樂道的性能與紅頭髮動機之外,對於車內空間的營造有着極為深度的造詣也是鍾愛本田品牌的家用車車主們非常欣賞的一點品牌特性。而這一點在冠道上被發揮得淋漓盡致。當所有主機廠都在用相似的尺寸做7座SUV的時候,冠道依舊是以5座布局應對市場競爭,而也正是這5座的座椅布局讓它成為了合資品牌中型SUV陣列里後排空間鮮有對手的存在,寬敞的後排甚至可以用誇張來形容,這對於後排有着執念的中國消費者來說,冠道的乘坐空間是非常具有誘惑力的存在。

提到“中國特供”,似乎很多人對這個字眼都頗有微詞;很大一部分原因都是因為中國汽車消費市場上銷量高的車型有不少的身份是“中國特供”,而某些車型,由於技術平台較老的緣故讓部分消費者將它們扣上了一些“高價低能”的帽子。

其實特供車型是每一個成型的汽車消費市場中都存在的一個現象;例如日本的K-car,小尺寸低排量的設定與方盒子的造型,便是為了日本那寸土寸金的地方量身打造的代步工具。

又例如美國的皮卡和大尺寸、大排量的SUV以及轎車,廣袤的美利堅平原土地上的牧場主們更需要一款能載貨的車型為自己的畜牧事業運輸物料,加之他們的汽車文化發展就是讓多缸數大排量的車型唱着主調,所以美系車自然成了“大尺寸”的汽車代名詞。

所以不同品牌的特供車不過是主機廠為了可以更好的開拓一方水土,鞏固自己品牌效應的一種商業手段,其實某種情況上看,做好一款特供車並不簡單,畢竟需要花大量的時間進行市場調研和消費需求的考究,才可以做出一款能幫助主機廠在某個特定的市場環境下能帶來豐厚利潤的車型。

近年來中國市場上的特供新車越來越多,而以下這些中國特供的SUV可以說是每一台都非常值得推薦的車型,它們在不同的產品定位下保持了品牌在各自擅長領域中的優勢,極大程度的滿足了中國消費者的用車需求。

寶馬X1

指導售價:28.38-43.90萬

空間與品牌信仰為它贏得了市場

寶馬X1現如今的銷量能有個平均月銷量6000輛以上的水平,豪華品牌緊湊型SUV有這個數據已經相當不錯,而且現款的X1比奧迪Q3賣得要更好,也側面印證了它的市場效應已經成型,消費者對於這款UKL前置前驅平台下的SUV已經有了極大的包容度。

起初,UKL這三個字母幾乎讓寶馬成為了眾矢之的,認為寶馬標的汽車就不應該出現前置前驅這種喪失操控源泉的驅動形式,但其實大多數人對於寶馬的定義或許更多的是停留在寶馬的品牌效應上,其次,UKL前驅平台的寶馬X1不僅做出了比老款X1更大的尺寸,更挖掘出了相當寬敞的後排空間,讓X1的形象更接近一個買菜家用的角色定位,對於大多數人來說,這樣的寶馬X1其實更實在。

馬自達CX-4

指導售價:14.08-21.58萬

年輕人的需求得以很好的滿足

嚴格意義上說,CX-4應該是一款中國首發的SUV;這款運動風格濃郁的跨界車型讓它看上去並不像傳統意義上的SUV那般厚重,靈動的造型和飄逸的魂動設計線條讓它在年輕人心目中有着極高的認同度。

在產品力層面,CX-4保持了馬自達品牌一貫出色的操控性,雖然空間並不是強項,但是作為一般年輕家庭來說空間實用性已然足夠,而且在市場終端显示出了一個接受度非常高的售價,這讓想要購買一款高顏值SUV的年輕人看到了希望,也讓不少在轎車與SUV中找不到平衡點的消費者們有了一個十分合適的折中選擇。

冠道

指導售價:22.00-32.98萬

將空間玩到了極致

本田聞名於世的除了讓人津津樂道的性能與紅頭髮動機之外,對於車內空間的營造有着極為深度的造詣也是鍾愛本田品牌的家用車車主們非常欣賞的一點品牌特性;而這一點在冠道上被發揮得淋漓盡致。

當所有主機廠都在用相似的尺寸做7座SUV的時候,冠道依舊是以5座布局應對市場競爭,而也正是這5座的座椅布局讓它成為了合資品牌中型SUV陣列里後排空間鮮有對手的存在,寬敞的後排甚至可以用誇張來形容,這對於後排有着執念的中國消費者來說,冠道的乘坐空間是非常具有誘惑力的存在。

昂科威

指導售價:21.99-31.99萬

將豪華融進了平民層次

二十來萬要買合資SUV買出豪華質感?除了昂科威之外可能能叫得上號的車型並不多,別克品牌近年來在內飾檔次感的提升工程上可謂是下足了功夫,而昂科威這款誕生於中國返銷至美國的SUV更是如此,內飾氛圍無論是在設計或是用料層面都在同價位車型中處於上乘水準,並且擁有主動降噪技術以進一步提升昂科威在行駛狀況下的高檔質感。

將豪華融進二三十萬級別車型,這一舉措無疑是成功的,昂科威上市以後便獲得了非常可觀的銷量數據,敦實厚重的外觀設計符合大多數人對於家用SUV的認知,而實際使用體驗越級的檔次感又能讓消費者感受到相對優越的用車氛圍,也正是昂科威出色的產品力體現,所以它一直處於十分值得推薦購買的SUV名錄里。

大指揮官

指導售價:27.98-40.98萬

極大程度上滿足了國人喜好

大指揮官剛剛上市,從理性角度上說,並不應該這麼快就下定論它是一款值得推薦的車型,但是它呈現出來的市場決心非常明顯,上市之前便已經放出“我便是為中國市場而來”的消息,直擊漢蘭達,銳界,途昂的細分定位。

大指揮官的產品力幾乎滿足了國人對於一款35萬左右能買到的合資SUV應該呈現出來的產品力該有的認知:接近5米的車長,魁梧的身軀,平實近人的外觀設計,7座的布局,以及幾乎全面的配置水平,這些都符合一个中國家庭用戶對於中型SUV的嚴苛需求,起碼從產品力上看,大指揮官交出的答卷絕對值高分。

結語:就普通消費者而言,一輛車是否特供並不是衡量這輛車好與壞的標準;而是要看綜合產品力的體現是否符合它所特供的市場環境;中國汽車市場對於SUV的空間與配置需求看得更重,而對動力、操控等机械品質的要求所佔比重則要低於前者。

以上車型除了馬自達CX-4是一款更講究操控性能的SUV以外,其餘車型幾乎都是將空間與配置放在了車型研發時的首位,從關注度和銷量上看,這些車型無疑是成功的代表;所以特供車不值得買嗎?我看未必。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

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

想買性價比高的7座車?這兩款20來萬的SUV買到就賺

0T領先版,經過加長的寶馬X1擁有非常變態的空間表現,經過實測,以身高180cm的體驗者為例,後排腿部空間接近三拳,頭部空間為四指,在同級別中可以說是傲視群雄了。而榮放雖然空間表現也不錯,後排空間為一拳四指,不過相比於寶馬X1來說還是太“嫩”了,同時寶馬X1 2。

福特銳界更大的外觀尺寸帶來的是相對比較可觀的乘坐空間,採用的2.0T和2.7T搭配6速手自一體變速器具有很強的爆發力,同時銳界還是SUV陣營中不可多得的操控好手,轉向比較靈活精準,較硬的懸挂能夠給車身提供足夠的支撐性,不過其乘坐舒適性會打折扣,並且強動力帶來的油耗也是比較高的。

5008相比於兇猛的銳界來說更加溫柔,發動機輸出不緊不慢,同時油門的調校也比較慵懶,需要踩得比較深才會感受到動力的輸出,同時懸挂調校完全偏向舒適性,能夠很好地過濾路上的振動,方向盤的指向性不如銳界。內飾設計是5008的亮點之一,非常具有視覺衝擊力。

總的來說,追求動力操控並且不是很在乎油耗的話就選銳界吧,開起來比較有樂趣,而5008是完完全全的家用舒適型SUV,能夠給家人更好的乘坐體驗,而且價格更低,想買純家用SUV的話就5008吧。

國家目前雖然大力推廣新能源汽車,但是在短期內對傳統燃油車的影響並不會很大,燃油車還是可以放心購買的,插電式混合動力目前主要問題還是充電樁較少,使用不是很方便,如果想買新能源車型的話推薦混合動力車型,技術相對成熟。13萬左右的燃油SUV車型比較推薦勁客,空間、動力都能滿足日常家用,同時日產的可靠性口碑還是非常不錯的。

更加推薦寶馬X1 2.0T領先版,經過加長的寶馬X1擁有非常變態的空間表現,經過實測,以身高180cm的體驗者為例,後排腿部空間接近三拳,頭部空間為四指,在同級別中可以說是傲視群雄了。而榮放雖然空間表現也不錯,後排空間為一拳四指,不過相比於寶馬X1來說還是太“嫩”了,同時寶馬X1 2.0T發動機爆發力更強,駕駛更有樂趣,所以更加推薦X1。

發動機橫置即為發動機的曲軸、變速器輸入軸等都是與前車橋平行的,一般前置前驅的車型都採用這種發動機布局,能夠大大減少動力的傳輸距離,提高傳動效率,還能減少發動機艙的縱向空間,從而增加車內的乘坐空間。但是橫置發動機驅動軸是一長一短的,會使兩個前輪產生輪速差,急加速時會產生扭力轉向(車頭左右搖擺不定)現象。

發動機縱置即為發動機的曲軸、變速器的輸入軸等與前橋垂直,一般應用於後驅車型,縱置發動機可以讓變速器盡量往後布置,使得動力總成的重心位於前橋之後,擁有更加平衡的前後重量,但是由於縱置發動機會使得發動機艙的縱向空間增大,因此在乘坐空間上會有所損失。

威朗GS這款車在外觀上的視覺衝擊還是比較強的,車身多處採用了凌厲的線條,“小鋼炮”味道十足,採用的1.5T發動機搭配七速雙離合擁有令人滿意的動力輸出,不過其雙離合變速器還是存在頓挫,乘坐空間中規中矩,方向盤的手感偏沉,指向性精準,底盤設定有韌性,路感比較清晰。假如喜歡駕駛的話威朗GS是一個非常不錯的選擇。

以上就是本期網友問答欄目的全部內容,假如你也想上牆的話,點擊下方留言留下你的問題並且點個贊,就有機會在下期欄目看見你的身影,我們下期再見!本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

7萬塊就能買到的合資家轎,還是自動擋,這3款車靠譜嗎?

由圖表數據我們可以看出賽歐的後排座墊比較長,因而支持性及舒適性會好過其他兩車。瑞納則是中規中矩,表現還不錯。汽車當然不止滿足自己的駕駛慾望,還要考慮家人的乘坐體驗,在看來,這三輛車在空間的表現上是合格的。

不知道你是否有這樣一種感覺,“如果我有一輛車,那麼我將得到整個世界。”雖然是自我感覺良好,但不可否認的是擁有一輛車真的帶給我們很多的便利,至少讓我們打破距離的阻礙,去到我們想去的地方。有了車,自信心是爆棚的,就看到過一個沉默寡言的人買車之後整天開着他那輛奧拓在小區裏面晃來晃去。

買車的好處多,但是在動輒10萬以上的汽車市場面前,不少人看了一眼自己的存摺,默默地把它放回原處;仍然有一部分迫切買車但是預算不夠的人把目光放在了10萬以下的汽車市場,那麼一起跟來看看這幾款比較多人選擇的低價位合資車是否值得選擇。

辣評:很多選擇瑞納的車主不僅是因為瑞納有着超低的價格(乞丐版裸車5萬左右),比較重要的一點是它有着耐看的顏值,車頭微微向下傾斜,呈一個低趴的狀態,外觀上有着超出這個價位的設計。畢竟價格擺在那裡,瑞納的內飾塑料感比較強,但是看上去不會顯得老氣,以簡約的風格為主。“皮薄”是很多人對瑞納的質疑,這個價位日韓車確實沒有國產車用料讓人放心,想說的是任何時候都要遵守交通規則,沒有任何車是絕對安全的。

辣評:賽歐也是中國人的老朋友了,現在街上還看到兩廂版的賽歐,小巧可愛。我們今天要介紹的是三廂版的賽歐,外觀上雪佛蘭家族的辨識度挺高的。內飾同樣的簡潔,比較奇葩的一點是賽歐的車窗升降按鍵設置在了中控下的位置,使用起來不是那麼方便。不過相對於其較低的價格及油耗,這點小瑕疵也就算不得什麼了吧。

辣評:近幾年威馳的銷量和口碑都還不錯,如果你有8萬的預算的話推薦買威馳,除了外觀沒有其他兩車好看外,威馳全系都標配了車身穩定系統,安全性能有了不小的提升,加之日系車是省油的典範,威馳在油耗上不會讓人失望。

雖然價格低,但是在乘坐空間上比較符合心意。三車在乘坐空間上差異並不是很大,只不過威馳後排頭部空間上會有劣勢,還好威馳後排中間地板是全平的,對於坐中間的人來說絕對是福音;由圖表數據我們可以看出賽歐的後排座墊比較長,因而支持性及舒適性會好過其他兩車;瑞納則是中規中矩,表現還不錯。汽車當然不止滿足自己的駕駛慾望,還要考慮家人的乘坐體驗,在看來,這三輛車在空間的表現上是合格的。

7萬左右的價格入手一輛合資車,對於配置就不要抱有太大的期望了,不過一些安全配置必須得有。威馳比較有誠意,全系都標配了車身穩定系統,CVT銳行版上坡輔助、剎車輔助這些功能都有;同樣的價位,賽歐已經可以買到頂配版本-幸福天窗版了,雖然沒有配置車身穩定系統,但是前排側氣囊讓人放心;就7萬這個價格來說,瑞納同樣可以上頂配了,盤式后制動器比另外兩車都要有誠意,同樣帶有前排側氣囊,車身穩定系統,總算在安全配置上扳回一城。

油耗一直是車主比較擔心的問題,一輛車油耗低的話,在日常養車中能夠省下一大筆錢。三車的平均油耗都在7L/100Km左右,當然不同路況會有不同的油耗,跑高速的話油耗會更低。

除了配置和油耗令人滿意之外,這三輛車低廉的價格是讓很多預算不多的人心動的主要原因。賽歐的價格已經下探到了4.7萬左右,瑞納的價格已經低的不能再低了,所以目前沒有優惠,就連銷售勢頭良好的威馳都有一萬左右的優惠,可見這個價位競爭的激烈,對於想買車人士來說是個好消息。

一直沒有提到動力是因為認為在這個價位,絕大多數車子在動力上表現都不怎麼樣,滿足家用足以,起碼高速超車是可以做到的。如果你迫切地希望擁有一輛車,在預算不是很多的情況下,這些車都可以在考慮的範圍內,況且7萬左右的價格就可以提自動擋車型,操控難度下降了不少。特別是對於新手司機,人生中第一輛車用來磨鍊一下自己的駕駛技術,無疑是很好的選擇。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

java併發編程系列原理篇–JDK中的通信工具類Semaphore

前言

java多線程之間進行通信時,JDK主要提供了以下幾種通信工具類。主要有Semaphore、CountDownLatch、CyclicBarrier、exchanger、Phaser這幾個通訊類。下面我們來詳細介紹每個工具類的作用、原理及用法。

Semaphore介紹

Semaphore翻譯過來是信號的意思。顧名思義,這個工具類提供的功能就是多個線程彼此“打信號”。而這個“信號”是一個int類型的數據,也可以看成是一種“資源”,用來限定線程訪問該資源的數量。
它的構造函數有兩個,一個參數的用來指定線程訪問資源的數量;兩個參數的一個用來指定線程訪問資源的數量,一個用來指定是否為公平鎖。關於公平鎖非公平鎖的概念請參照文章java併發編程系列之原理篇-synchronized與鎖。構造函數代碼如下:


  // 默認情況下使用非公平
  public Semaphore(int permits) {
        sync = new NonfairSync(permits);
  }
  public Semaphore(int permits, boolean fair) {
      sync = fair ? new FairSync(permits) : new NonfairSync(permits);
  }

它的最主要的兩個方法是acquire()和release()。acquire()方法會申請一個permit,而release方法會釋放一個permit。當然,你也可以申請多個acquire(int permits)或者釋放多個release(int permits)。每次acquire,permits就會減少一個或者多個。如果減少到了0,再有其他線程來acquire,那就要阻塞這個線程直到有其它線程release permit為止。

Semaphore的使用

Semaphore主要用來控制線程訪問資源的數量的場景。舉個例子,在併發條件下,我只想讓3個線程來執行某一任務。請看示例代碼:

public class SemaphoreDemo {

    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);
        for (int i = 0; i < 10; i++) {
            new Thread(new MyThread(i,semaphore)).start();
        }
    }

     static class  MyThread implements Runnable{

        private int id;//線程的ID號
        private Semaphore semaphore;

        public MyThread(int id, Semaphore semaphore){
            this.id = id;
            this.semaphore = semaphore;
        }

        @Override
        public void run() {
            try {
                //獲取信號量permit許可
                semaphore.acquire();
                //接下來可以用來執行具體的線程任務
                System.out.println(String.format("當前的線程是%d,還剩有%d個線程資源可以使用,有%d個線程處於等待中。",
                        id,semaphore.availablePermits(),semaphore.getQueueLength()));
                Random random = new Random();
                //隨機睡眠時間,打亂釋放順序
                Thread.sleep(random.nextInt(1000));
                System.out.println(String.format("線程%d釋放了資源",id));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                //任務結束,釋放資源
                semaphore.release();
            }
        }
    }
}

輸出結果:

當前的線程是1,還剩有1個線程資源可以使用,有0個線程處於等待中。
當前的線程是0,還剩有2個線程資源可以使用,有0個線程處於等待中。
當前的線程是2,還剩有0個線程資源可以使用,有0個線程處於等待中。
線程2釋放了資源
當前的線程是3,還剩有0個線程資源可以使用,有6個線程處於等待中。
線程1釋放了資源
當前的線程是4,還剩有0個線程資源可以使用,有5個線程處於等待中。
線程0釋放了資源
當前的線程是5,還剩有0個線程資源可以使用,有4個線程處於等待中。
線程3釋放了資源
當前的線程是6,還剩有0個線程資源可以使用,有3個線程處於等待中。
線程4釋放了資源
當前的線程是7,還剩有0個線程資源可以使用,有2個線程處於等待中。
線程5釋放了資源
當前的線程是8,還剩有0個線程資源可以使用,有1個線程處於等待中。
線程8釋放了資源
當前的線程是9,還剩有0個線程資源可以使用,有0個線程處於等待中。
線程7釋放了資源
線程6釋放了資源
線程9釋放了資源

從結果可以看出來,最初搶到這3個資源的線程是1,0,2,而其他線程進入了等待隊列。之後每當有一個線程釋放了該資源,才會有其他在等待隊列的線程搶到資源。Semaphore默認的acquire方法是會讓線程進入等待隊列,且會拋出中斷異常。但它還有一些方法可以忽略中斷或不進入阻塞隊列:

 // 忽略中斷
 public void acquireUninterruptibly()
 public void acquireUninterruptibly(int permits)

 // 不進入等待隊列,底層使用CAS
 public boolean tryAcquire
 public boolean tryAcquire(int permits)
 public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException
 public boolean tryAcquire(long timeout, TimeUnit unit)

Semaphore的原理

Semaphore內部有一個繼承了AQS的同步器Sync成員變量,重寫了tryAcquireShared方法。在這個方法里,會去嘗試獲取資源。如果獲取失敗(想要的資源數量小於目前已有的資源數量),就會返回一個負數(代表嘗試獲取資源失敗)。然後當前線程就會進入AQS的等待隊列。具體的代碼邏輯請查看JDK1.8中java.util.concurrent包下的Semaphore類。

參考鏈接

在這裏很感謝能夠有幸看到來自各個大廠大神們的開源項目深入淺出Java多線程,讓我對多線程的知識有一個更深層次的了解。文中如有地方寫的不妥當或者有疑問,請大家留言,大家相互學習。感謝!

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

【其他文章推薦】

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

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

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

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

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

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

一起玩轉微服務(2)——框架與工具

一、微服務架構有哪些優勢?

  • 獨立開發 – 所有微服務都可以根據各自的功能輕鬆開發·
  • 獨立部署 – 基於其服務,可以在任何應用程序中單獨部署它們·
  • 故障隔離 – 即使應用程序的一項服務不起作用,系統仍可繼續運行· 混合技術堆棧 – 可以使用不同的語言和技術來構建同一應用程序的不同服務·
  • 粒度縮放 – 單個組件可根據需要進行縮放,無需將所有組件縮放在一起

二、微服務有哪些特點?

  • 解耦 – 系統內的服務很大程度上是分離的。因此,整個應用程序可以輕鬆構建,更改和擴展·
  • 組件化 – 微服務被視為可以輕鬆更換和升級的獨立組件·
  • 業務能力 – 微服務非常簡單,專註於單一功能·
  • 自治 – 開發人員和團隊可以彼此獨立工作,從而提高速度·
  • 持續交付 – 通過軟件創建,測試和批準的系統自動化,允許頻繁發布軟件·
  • 責任 – 微服務不關注應用程序作為項目。相反,他們將應用程序視為他們負責的產品·
  • 分散治理 – 重點是使用正確的工具來做正確的工作。這意味着沒有標準化模式或任何技術模式。開發人員可以自由選擇最有用的工具來解決他們的問題·
  • 敏捷 – 微服務支持敏捷開發。任何新功能都可以快速開發並再次丟棄

三、微服務架構如何運作?

微服務架構具有以下組件:

 

 

 

  • 客戶端 – 來自不同設備的不同用戶發送請求。·
  • 身份提供商 – 驗證用戶或客戶身份並頒發安全令牌。·
  • API 網關 – 處理客戶端請求。·
  • 靜態內容 – 容納系統的所有內容。·
  • 管理 – 在節點上平衡服務並識別故障。·
  • 服務發現 – 查找微服務之間通信路徑的指南。·
  • 內容交付網絡 – 代理服務器及其數據中心的分佈式網絡。·
  • 遠程服務 – 啟用駐留在 IT 設備網絡上的遠程訪問信息。

四、什麼是 REST / RESTful 以及它的用途是什麼?

Representational State Transfer(REST)/ RESTful Web 服務是一種幫助計算機系統通過 Internet 進行通信的架構風格。這使得微服務更容易理解和實現。微服務可以使用或不使用 RESTful API 實現,但使用 RESTful API 構建鬆散耦合的微服務總是更容易。

五、什麼是不同類型的微服務測試?

在使用微服務時,由於有多個微服務協同工作,測試變得非常複雜。因此,測試分為不同的級別。·

  • 在底層,我們有面向技術的測試,如單元測試和性能測試。這些是完全自動化的。·
  • 在中間層面,我們進行了諸如壓力測試和可用性測試之類的探索性測試。·
  • 在頂層, 我們的 驗收測試數量很少。這些驗收測試有助於利益相關者理解和驗證軟件功能。

六、常用的微服務框架

各種語言都有自己的微服務框架,主要包括:

  • 主流微服務框架:SpringCloud、Dubbo、spring cloud alibaba
  • 新銳微服務框架:Istio

下面一起看一下常用的微服務框架體系。

1. Spring 頂級項目

首先,我們一起來看一下Spring的頂級項目都包括哪些:

  • Spring IO platform:用於系統部署,是可集成的,構建現代化應用的版本平台,具體來說當你使用maven dependency引入Spring jar包時它就在工作了。
  • Spring Boot:旨在簡化創建產品級的 Spring 應用和服務,簡化了配置文件,使用嵌入式web服務器,含有諸多開箱即用微服務功能,可以和Spring Cloud聯合部署。
  • Spring Framework:即通常所說的Spring 框架,是一個開源的Java/Java EE全功能棧應用程序框架,其它Spring項目如Spring Boot也依賴於此框架。
  • Spring Cloud:微服務工具包,為開發者提供了在分佈式系統的配置管理、服務發現、斷路器、智能路由、微代理、控制總線等開發工具包。
  • Spring XD:是一種運行時環境(服務器軟件,非開發框架),組合Spring技術,如Spring batch、Spring Boot、Spring data,採集大數據並處理。
  • Spring Data:是一個數據訪問及操作的工具包,封裝了很多種數據及數據庫的訪問相關技術,包括:jdbc、Redis、MongoDB、Neo4j等。
  • Spring Batch:批處理框架,或說是批量任務執行管理器,功能包括任務調度、日誌記錄/跟蹤等。
  • Spring Security:是一個能夠為基於Spring的企業應用系統提供聲明式的安全訪問控制解決方案的安全框架。
  • Spring Integration:面向企業應用集成(EAI/ESB)的編程框架,支持的通信方式包括HTTP、FTP、TCP/UDP、JMS、RabbitMQ、Email等。
  • Spring Social:一組工具包,一組連接社交服務API,如Twitter、Facebook、LinkedIn、GitHub等,有幾十個。
  • Spring AMQP:消息隊列操作的工具包,主要是封裝了RabbitMQ的操作。
  • Spring HATEOAS:是一個用於支持實現超文本驅動的 REST Web 服務的開發庫。
  • Spring Mobile:是Spring MVC的擴展,用來簡化手機上的Web應用開發。
  • Spring for Android:是Spring框架的一個擴展,其主要目的在乎簡化Android本地應用的開發,提供RestTemplate來訪問Rest服務。
  • Spring Web Flow:目標是成為管理Web應用頁面流程的最佳方案,將頁面跳轉流程單獨管理,並可配置。
  • Spring LDAP:是一個用於操作LDAP的Java工具包,基於Spring的JdbcTemplate模式,簡化LDAP訪問。
  • Spring Session:session管理的開發工具包,讓你可以把session保存到redis等,進行集群化session管理。
  • Spring Web Services:是基於Spring的Web服務框架,提供SOAP服務開發,允許通過多種方式創建Web服務。
  • Spring Shell:提供交互式的Shell可讓你使用簡單的基於Spring的編程模型來開發命令,比如Spring Roo命令。
  • Spring Roo:是一種Spring開發的輔助工具,使用命令行操作來生成自動化項目,操作非常類似於Rails。
  • Spring Scala:為Scala語言編程提供的Spring框架的封裝(新的編程語言,Java平台的Scala於2003年底/2004年初發布)。
  • Spring BlazeDS Integration:一個開發RIA工具包,可以集成Adobe Flex、BlazeDS、Spring以及Java技術創建RIA。
  • Spring Loaded:用於實現java程序和web應用的熱部署的開源工具。
  • Spring REST Shell:可以調用Rest服務的命令行工具,敲命令行操作Rest服務。

2. 背景

對於使用java技術更多的團隊來說,dubbo,Spring Cloud的知名度相對較高,而且在業界的影響力非常高。那麼我們就來一起討論一下這兩種框架,做一個綜合的評比。
Dubbo
Dubbo是阿里出品的服務化組件,應用於多個部門,後來開源之後,也衍生出像dubbox這樣的框架,對其進行了進一步的增強。Dubbo 是一個分佈式服務框架,是國內互聯網公司開源做的比較不錯的阿里開放的微服務化治理框架,致力於提供高性能和透明化的RPC遠程服務調用方案,以及SOA服務治理方案。目前Dubbo已經正式進入Apache孵化器。

現在可以再apache 下面找到dubbo

http://dubbo.apache.org/zh-cn/

 

其核心部分包含:

  • 遠程通訊: 提供對多種基於長連接的NIO框架抽象封裝,包括多種線程模型,序列化,以及“請求-響應”模式的信息交換方式;
  • 集群容錯: 提供基於接口方法的透明遠程過程調用,包括多協議支持,以及軟負載均衡,失敗容錯,地址路由,動態配置等集群支持;
  • 自動發現: 基於註冊中心目錄服務,使服務消費方能動態的查找服務提供方,使地址透明,使服務提供方可以平滑增加或減少機器。

 

Spring Cloud
Spring Cloud從其命名中就可以知道,是Spring Source的產物,可以說Spring的出現整個改變了企業級開發,Spring Cloud,除了具有 Spring 社區的強大背景外,還有 Netflix 強大的後盾與技術輸出。Netflix 作為一家成功實踐微服務架構的互聯網公司,在幾年前就把幾乎整個微服務框架棧開源貢獻給了社區,這些框架開源的整套微服務架構套件是 Spring Cloud 的核心。

  • Eureka: 服務註冊發現框架;
  • Zuul: 服務網關;
  • Karyon: 服務端框架;
  • Ribbon: 客戶端框架;
  • Hystrix: 服務容錯組件;
  • Archaius: 服務配置組件;
  • Servo: Metrics組件;
  • Blitz4j: 日誌組件。

從背景上來看,前者在國內的影響力比較大,後者在國外的影響力比較大,使用的公司也更多一些。

spring-cloud-alibaba

地址:

https://spring.io/projects/spring-cloud-alibaba

服務限流降級:默認支持 WebServlet、WebFlux, OpenFeign、RestTemplate、Spring Cloud Gateway, Zuul, Dubbo 和 RocketMQ 限流降級功能的接入,可以在運行時通過控制台實時修改限流降級規則,還支持查看限流降級 Metrics 監控。
服務註冊與發現:適配 Spring Cloud 服務註冊與發現標準,默認集成了 Ribbon 的支持。
分佈式配置管理:支持分佈式系統中的外部化配置,配置更改時自動刷新。
消息驅動能力:基於 Spring Cloud Stream 為微服務應用構建消息驅動能力。
分佈式事務:使用 @GlobalTransactional 註解, 高效並且對業務零侵入地解決分佈式事務問題。。
阿里雲對象存儲:阿里雲提供的海量、安全、低成本、高可靠的雲存儲服務。支持在任何應用、任何時間、任何地點存儲和訪問任意類型的數據。
分佈式任務調度:提供秒級、精準、高可靠、高可用的定時(基於 Cron 表達式)任務調度服務。同時提供分佈式的任務執行模型,如網格任務。網格任務支持海量子任務均勻分配到所有 Worker(schedulerx-client)上執行。
阿里雲短信服務:覆蓋全球的短信服務,友好、高效、智能的互聯化通訊能力,幫助企業迅速搭建客戶觸達通道。

Istio
Istio 作為用於微服務服務聚合層管理的新銳項目,是 Google、IBM、Lyft(海外共享出行公司、Uber勁敵) 首個共同聯合開源的項S目,提供了統一的連接,安全,管理和監控微服務的方案。
目前首個測試版是針對 Kubernetes 環境的,社區宣稱在未來幾個月內會為虛擬機和 Cloud Foundry 等其他環境增加支持。 Istio 將流量管理添加到微服務中,併為增值功能(如安全性,監控,路由,連接管理和策略)創造了基礎。

  • HTTP、gRPC 和 TCP 網絡流量的自動負載均衡;
  • 提供了豐富的路由規則,實現細粒度的網絡流量行為控制;
  • 流量加密、服務間認證,以及強身份聲明;
  • 全範圍(Fleet-wide)的策略執行;
  • 深度遙測和報告。

3. 社區活躍度

我們選擇一個開源框架,社區的活躍度是我們極為關注的一個要點。社區越活躍,解決問題的速度越快,框架也會越來越完善,不然當我們碰到問題,就不得不自己解決。
先看一下dubbo和dubbox,
下圖是dubbo目前的情況,在2017年,阿里已經宣布有團隊重新維護dubbo項目,可以看到dubbo項目已經重新開始修復裡邊的一些bug,並且進入不斷的完善的過程中了。Dubbo 目前在 GitHub 上有超過 16000 個 star 和超過 12000 的 fork 數,是國內影響力最大的開源項目之一。

 

 

反倒是繼承而來的dubbox,已經有很長時間沒有進行維護了。

 

 

再看Spring Cloud,沿襲着Spring一慣的風格,更新非常及時,響應的速度也非常快,社區非常活躍。

4. 架構完整度

根據微服務架構在各方面的要素,看看Spring Cloud和Dubbo都提供了哪些支持。表N展示了Dubbo和Spring Cloud的區別。

 

其實單純從提供的數量上來比較,有一些不公平,因為dubbo是幾年前出來的,極大的改變了人們對於分佈式系統的認知。而上邊寫無的部分其實也可以整合其它的框架,所以,對於這一點的評比上來說,選用何種框架取決於團隊目前的情況,而不是一下子就全部轉型,要階梯式的實現整個應用架構。

5. 總結

使用Dubbo構建的微服務架構就像組裝電腦,各環節我們的選擇自由度很高,但是最終結果很有可能因為一條內存質量不行就點不亮了,總是讓人不怎麼放心,但是如果你是一名高手,那這些都不是問題;而Spring Cloud就像品牌機,在Spring Source的整合下,做了大量的兼容性測試,保證了機器擁有更高的穩定性,但是如果要在使用非原裝組件外的東西,就需要對其基礎有足夠的了解。

最後呢,說一下工具,個人偏好是eclipse派!
https://spring.io/tools

 

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

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

※台北網頁設計公司全省服務真心推薦

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

※推薦評價好的iphone維修中心

讓人又愛又恨的Lombok,到底該不該用

1 簡介

Lombok,印尼的一個島嶼,龍目島。但在Java的世界里,它是一個方便的類庫,能提供很多便利,因此得到許多人的青睞。但也有不少反對聲音。這是為什麼呢?

之前去龍目島拍的日落。

2 Lombok提供的便利

一般我們在Java中用到POJO時,就很容易想到要用Lombok,如VO、DTO、DO等。使用Lombok需要安裝對應IDE的插件,同時需要引入依賴:

<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.18.10</version>
  <scope>provided</scope>
</dependency>

舉個例子,如果不用Lombok,實現getter/setter、equals、hashCode、toString代碼量非常大,如下所示:

package com.pkslow.basic.lombok;

import java.util.Objects;

public class Book {
    private String name;
    private int id;
    private double price;
    private String author;
    private String desc;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Book book = (Book) o;
        return id == book.id &&
                Double.compare(book.price, price) == 0 &&
                Objects.equals(name, book.name) &&
                Objects.equals(author, book.author) &&
                Objects.equals(desc, book.desc);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, id, price, author, desc);
    }

    @Override
    public String toString() {
        return "Book{" +
                "name='" + name + '\'' +
                ", id=" + id +
                ", price=" + price +
                ", author='" + author + '\'' +
                ", desc='" + desc + '\'' +
                '}';
    }
}

而且大部分是樣板代碼,沒有太多實際邏輯。

如果使用Lombok則非常簡單,一個註解@Data就可以完成以上工作(安裝了Lombok插件后才能显示右邊的方法):

Lombok的常用註解有:

@NonNull:用於做空指針異常檢測;

@Cleanup:自動資源關閉;

@Getter/@Setter:自動生成get/set方法;

@ToString:生成toString方法,方便打印調試;

@EqualsAndHashCode:生成equals和hashCode方法,注意這兩個應該同時去實現;

@NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor:構造方法;

@Data:相當於 @ToString+ @EqualsAndHashCode+@Getter+ @Setter +@RequiredArgsConstructor。

@Builder:生成builder方法;

@Log:日誌相關:

@CommonsLog:org.apache.commons.logging.LogFactory.getLog(LogExample.class);
@Flogger:com.google.common.flogger.FluentLogger.forEnclosingClass();
@JBossLog:org.jboss.logging.Logger.getLogger(LogExample.class);
@Log:java.util.logging.Logger.getLogger(LogExample.class.getName());
@Log4j:org.apache.log4j.Logger.getLogger(LogExample.class);
@Log4j2:org.apache.logging.log4j.LogManager.getLogger(LogExample.class);
@Slf4j:org.slf4j.LoggerFactory.getLogger(LogExample.class);
@XSlf4j:org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);
@CustomLog:com.foo.your.LoggerFactory.createYourLogger(LogExample.class);

所以Lombok確實能給我們帶來極大的便利,減少大量重複、無業務邏輯的代碼,代碼顯得比較乾淨;修改了field的名字,不用多處修改;提高開發效率。

3 Lombok所帶來的問題

當然,也有人提出了不同意見:

調試時沒有具體代碼,不方便Debug;

需要強制別人安裝第三方插件,不然會显示報錯;

在檢查測試覆蓋率的時候,無法直觀显示哪些代碼已覆蓋和未覆蓋;

無法按自己意願實現,比如toString方法,有時需要輸出不一樣的String格式,並不一定是按Lombok的實現;

對於一些常用的方法,IDE已經可以自動生成,不需要Lombok一樣可以高效開發。

IDEA沒有提供Builder,但可以通過安裝插件方式使用。

成功安裝后,就能生成Builder代碼了:

4 總結

一開始我是支持使用Lombok的,經過一段時間使用及出現了一些問題后,我還是覺得通過IDE自動生成代碼的方式更適合。畢竟強制要求別人安裝插件這種實在是太野蠻了,而通過IDEA生成代碼,別人不安裝插件也不會有報錯。

另外,多幾行代碼還能體現工作量,哈哈哈哈哈哈……

歡迎訪問南瓜慢說 www.pkslow.com獲取更多精彩文章!

歡迎關注微信公眾號<南瓜慢說>,將持續為你更新…

多讀書,多分享;多寫作,多整理。

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

深度解密 Go 語言之 sync.map

工作中,經常會碰到併發讀寫 map 而造成 panic 的情況,為什麼在併發讀寫的時候,會 panic 呢?因為在併發讀寫的情況下,map 里的數據會被寫亂,之後就是 Garbage in, garbage out,還不如直接 panic 了。

目錄

  • 是什麼
  • 有什麼用
  • 如何使用
  • 源碼分析
    • 數據結構
    • Store
    • Load
    • Delete
    • LoadOrStore
    • Range
  • 其他
  • 總結
  • 參考資料

是什麼

Go 語言原生 map 並不是線程安全的,對它進行併發讀寫操作的時候,需要加鎖。而 sync.map 則是一種併發安全的 map,在 Go 1.9 引入。

sync.map 是線程安全的,讀取,插入,刪除也都保持着常數級的時間複雜度。
sync.map 的零值是有效的,並且零值是一個空的 map。在第一次使用之後,不允許被拷貝。

有什麼用

一般情況下解決併發讀寫 map 的思路是加一把大鎖,或者把一個 map 分成若干個小 map,對 key 進行哈希,只操作相應的小 map。前者鎖的粒度比較大,影響效率;後者實現起來比較複雜,容易出錯。

而使用 sync.map 之後,對 map 的讀寫,不需要加鎖。並且它通過空間換時間的方式,使用 read 和 dirty 兩個 map 來進行讀寫分離,降低鎖時間來提高效率。

如何使用

使用非常簡單,和普通 map 相比,僅遍歷的方式略有區別:

package main

import (
	"fmt"
	"sync"
)

func main()  {
	var m sync.Map
	// 1. 寫入
	m.Store("qcrao", 18)
	m.Store("stefno", 20)

	// 2. 讀取
	age, _ := m.Load("qcrao")
	fmt.Println(age.(int))

	// 3. 遍歷
	m.Range(func(key, value interface{}) bool {
		name := key.(string)
		age := value.(int)
		fmt.Println(name, age)
		return true
	})

	// 4. 刪除
	m.Delete("qcrao")
	age, ok := m.Load("qcrao")
	fmt.Println(age, ok)

	// 5. 讀取或寫入
	m.LoadOrStore("stefno", 100)
	age, _ = m.Load("stefno")
	fmt.Println(age)
}

第 1 步,寫入兩個 k-v 對;

第 2 步,使用 Load 方法讀取其中的一個 key;

第 3 步,遍歷所有的 k-v 對,並打印出來;

第 4 步,刪除其中的一個 key,再讀這個 key,得到的就是 nil;

第 5 步,使用 LoadOrStore,嘗試讀取或寫入 “Stefno”,因為這個 key 已經存在,因此寫入不成功,並且讀出原值。

程序輸出:

18
stefno 20
qcrao 18
<nil> false
20

sync.map 適用於讀多寫少的場景。對於寫多的場景,會導致 read map 緩存失效,需要加鎖,導致衝突變多;而且由於未命中 read map 次數過多,導致 dirty map 提升為 read map,這是一個 O(N) 的操作,會進一步降低性能。

源碼分析

數據結構

先來看下 map 的數據結構。去掉大段的註釋后:

type Map struct {
	mu Mutex
	read atomic.Value // readOnly
	dirty map[interface{}]*entry
	misses int
}

互斥量 mu 保護 read 和 dirty。

read 是 atomic.Value 類型,可以併發地讀。但如果需要更新 read,則需要加鎖保護。對於 read 中存儲的 entry 字段,可能會被併發地 CAS 更新。但是如果要更新一個之前已被刪除的 entry,則需要先將其狀態從 expunged 改為 nil,再拷貝到 dirty 中,然後再更新。

dirty 是一個非線程安全的原始 map。包含新寫入的 key,並且包含 read 中的所有未被刪除的 key。這樣,可以快速地將 dirty 提升為 read 對外提供服務。如果 dirty 為 nil,那麼下一次寫入時,會新建一個新的 dirty,這個初始的 dirty 是 read 的一個拷貝,但除掉了其中已被刪除的 key。

每當從 read 中讀取失敗,都會將 misses 的計數值加 1,當加到一定閾值以後,需要將 dirty 提升為 read,以期減少 miss 的情形。

read map 和 dirty map 的存儲方式是不一致的。
前者使用 atomic.Value,後者只是單純的使用 map。
原因是 read map 使用 lock free 操作,必須保證 load/store 的原子性;而 dirty map 的 load+store 操作是由 lock(就是 mu)來保護的。

真正存儲 key/value 的是 read 和 dirty 字段。read 使用 atomic.Value,這是 lock-free 的基礎,保證 load/store 的原子性。dirty 則直接用了一個原始的 map,對於它的 load/store 操作需要加鎖。

read 字段里實際上是存儲的是:

// readOnly is an immutable struct stored atomically in the Map.read field.
type readOnly struct {
	m       map[interface{}]*entry
	amended bool // true if the dirty map contains some key not in m.
}

注意到 read 和 dirty 里存儲的東西都包含 entry,來看一下:

type entry struct {
	p unsafe.Pointer // *interface{}
}

很簡單,它是一個指針,指向 value。看來,read 和 dirty 各自維護一套 key,key 指向的都是同一個 value。也就是說,只要修改了這個 entry,對 read 和 dirty 都是可見的。這個指針的狀態有三種:

當 p == nil 時,說明這個鍵值對已被刪除,並且 m.dirty == nil,或 m.dirty[k] 指向該 entry。

當 p == expunged 時,說明這條鍵值對已被刪除,並且 m.dirty != nil,且 m.dirty 中沒有這個 key。

其他情況,p 指向一個正常的值,表示實際 interface{} 的地址,並且被記錄在 m.read.m[key] 中。如果這時 m.dirty 不為 nil,那麼它也被記錄在 m.dirty[key] 中。兩者實際上指向的是同一個值。

當刪除 key 時,並不實際刪除。一個 entry 可以通過原子地(CAS 操作)設置 p 為 nil 被刪除。如果之後創建 m.dirty,nil 又會被原子地設置為 expunged,且不會拷貝到 dirty 中。

如果 p 不為 expunged,和 entry 相關聯的這個 value 可以被原子地更新;如果 p == expunged,那麼僅當它初次被設置到 m.dirty 之後,才可以被更新。

整體用一張圖來表示:

Store

先來看 expunged:

var expunged = unsafe.Pointer(new(interface{}))

它是一個指向任意類型的指針,用來標記從 dirty map 中刪除的 entry。

// Store sets the value for a key.
func (m *Map) Store(key, value interface{}) {
	// 如果 read map 中存在該 key  則嘗試直接更改(由於修改的是 entry 內部的 pointer,因此 dirty map 也可見)
	read, _ := m.read.Load().(readOnly)
	if e, ok := read.m[key]; ok && e.tryStore(&value) {
		return
	}

	m.mu.Lock()
	read, _ = m.read.Load().(readOnly)
	if e, ok := read.m[key]; ok {
		if e.unexpungeLocked() {
			// 如果 read map 中存在該 key,但 p == expunged,則說明 m.dirty != nil 並且 m.dirty 中不存在該 key 值 此時:
			//    a. 將 p 的狀態由 expunged  更改為 nil
			//    b. dirty map 插入 key
			m.dirty[key] = e
		}
		// 更新 entry.p = value (read map 和 dirty map 指向同一個 entry)
		e.storeLocked(&value)
	} else if e, ok := m.dirty[key]; ok {
		// 如果 read map 中不存在該 key,但 dirty map 中存在該 key,直接寫入更新 entry(read map 中仍然沒有這個 key)
		e.storeLocked(&value)
	} else {
		// 如果 read map 和 dirty map 中都不存在該 key,則:
		//	  a. 如果 dirty map 為空,則需要創建 dirty map,並從 read map 中拷貝未刪除的元素到新創建的 dirty map
		//    b. 更新 amended 字段,標識 dirty map 中存在 read map 中沒有的 key
		//    c. 將 kv 寫入 dirty map 中,read 不變
		if !read.amended {
		    // 到這裏就意味着,當前的 key 是第一次被加到 dirty map 中。
			// store 之前先判斷一下 dirty map 是否為空,如果為空,就把 read map 淺拷貝一次。
			m.dirtyLocked()
			m.read.Store(readOnly{m: read.m, amended: true})
		}
		// 寫入新 key,在 dirty 中存儲 value
		m.dirty[key] = newEntry(value)
	}
	m.mu.Unlock()
}

整體流程:

  1. 如果在 read 里能夠找到待存儲的 key,並且對應的 entry 的 p 值不為 expunged,也就是沒被刪除時,直接更新對應的 entry 即可。
  2. 第一步沒有成功:要麼 read 中沒有這個 key,要麼 key 被標記為刪除。則先加鎖,再進行後續的操作。
  3. 再次在 read 中查找是否存在這個 key,也就是 double check 一下,這也是 lock-free 編程里的常見套路。如果 read 中存在該 key,但 p == expunged,說明 m.dirty != nil 並且 m.dirty 中不存在該 key 值 此時: a. 將 p 的狀態由 expunged 更改為 nil;b. dirty map 插入 key。然後,直接更新對應的 value。
  4. 如果 read 中沒有此 key,那就查看 dirty 中是否有此 key,如果有,則直接更新對應的 value,這時 read 中還是沒有此 key。
  5. 最後一步,如果 read 和 dirty 中都不存在該 key,則:a. 如果 dirty 為空,則需要創建 dirty,並從 read 中拷貝未被刪除的元素;b. 更新 amended 字段,標識 dirty map 中存在 read map 中沒有的 key;c. 將 k-v 寫入 dirty map 中,read.m 不變。最後,更新此 key 對應的 value。

再來看一些子函數:

// 如果 entry 沒被刪,tryStore 存儲值到 entry 中。如果 p == expunged,即 entry 被刪,那麼返回 false。
func (e *entry) tryStore(i *interface{}) bool {
	for {
		p := atomic.LoadPointer(&e.p)
		if p == expunged {
			return false
		}
		if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i)) {
			return true
		}
	}
}

tryStore 在 Store 函數最開始的時候就會調用,是比較常見的 for 循環加 CAS 操作,嘗試更新 entry,讓 p 指向新的值。

unexpungeLocked 函數確保了 entry 沒有被標記成已被清除:

// unexpungeLocked 函數確保了 entry 沒有被標記成已被清除。
// 如果 entry 先前被清除過了,那麼在 mutex 解鎖之前,它一定要被加入到 dirty map 中
func (e *entry) unexpungeLocked() (wasExpunged bool) {
	return atomic.CompareAndSwapPointer(&e.p, expunged, nil)
}

Load

func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
	read, _ := m.read.Load().(readOnly)
	e, ok := read.m[key]
	// 如果沒在 read 中找到,並且 amended 為 true,即 dirty 中存在 read 中沒有的 key
	if !ok && read.amended {
		m.mu.Lock() // dirty map 不是線程安全的,所以需要加上互斥鎖
		// double check。避免在上鎖的過程中 dirty map 提升為 read map。
		read, _ = m.read.Load().(readOnly)
		e, ok = read.m[key]
		// 仍然沒有在 read 中找到這個 key,並且 amended 為 true
		if !ok && read.amended {
			e, ok = m.dirty[key] // 從 dirty 中找
			// 不管 dirty 中有沒有找到,都要"記一筆",因為在 dirty 提升為 read 之前,都會進入這條路徑
			m.missLocked()
		}
		m.mu.Unlock()
	}
	if !ok { // 如果沒找到,返回空,false
		return nil, false
	}
	return e.load()
}

處理路徑分為 fast path 和 slow path,整體流程如下:

  1. 首先是 fast path,直接在 read 中找,如果找到了直接調用 entry 的 load 方法,取出其中的值。
  2. 如果 read 中沒有這個 key,且 amended 為 fase,說明 dirty 為空,那直接返回 空和 false。
  3. 如果 read 中沒有這個 key,且 amended 為 true,說明 dirty 中可能存在我們要找的 key。當然要先上鎖,再嘗試去 dirty 中查找。在這之前,仍然有一個 double check 的操作。若還是沒有在 read 中找到,那麼就從 dirty 中找。不管 dirty 中有沒有找到,都要”記一筆”,因為在 dirty 被提升為 read 之前,都會進入這條路徑

這裏主要看下 missLocked 的函數的實現:

func (m *Map) missLocked() {
	m.misses++
	if m.misses < len(m.dirty) {
		return
	}
	// dirty map 晉陞
	m.read.Store(readOnly{m: m.dirty})
	m.dirty = nil
	m.misses = 0
}

直接將 misses 的值加 1,表示一次未命中,如果 misses 值小於 m.dirty 的長度,就直接返回。否則,將 m.dirty 晉陞為 read,並清空 dirty,清空 misses 計數值。這樣,之前一段時間新加入的 key 都會進入到 read 中,從而能夠提升 read 的命中率。

再來看下 entry 的 load 方法:

func (e *entry) load() (value interface{}, ok bool) {
	p := atomic.LoadPointer(&e.p)
	if p == nil || p == expunged {
		return nil, false
	}
	return *(*interface{})(p), true
}

對於 nil 和 expunged 狀態的 entry,直接返回 ok=false;否則,將 p 轉成 interface{} 返回。

Delete

// Delete deletes the value for a key.
func (m *Map) Delete(key interface{}) {
	read, _ := m.read.Load().(readOnly)
	e, ok := read.m[key]
	// 如果 read 中沒有這個 key,且 dirty map 不為空
	if !ok && read.amended {
		m.mu.Lock()
		read, _ = m.read.Load().(readOnly)
		e, ok = read.m[key]
		if !ok && read.amended {
			delete(m.dirty, key) // 直接從 dirty 中刪除這個 key
		}
		m.mu.Unlock()
	}
	if ok {
		e.delete() // 如果在 read 中找到了這個 key,將 p 置為 nil
	}
}

可以看到,基本套路還是和 Load,Store 類似,都是先從 read 里查是否有這個 key,如果有則執行 entry.delete 方法,將 p 置為 nil,這樣 read 和 dirty 都能看到這個變化。

如果沒在 read 中找到這個 key,並且 dirty 不為空,那麼就要操作 dirty 了,操作之前,還是要先上鎖。然後進行 double check,如果仍然沒有在 read 里找到此 key,則從 dirty 中刪掉這個 key。但不是真正地從 dirty 中刪除,而是更新 entry 的狀態。

來看下 entry.delete 方法:

func (e *entry) delete() (hadValue bool) {
	for {
		p := atomic.LoadPointer(&e.p)
		if p == nil || p == expunged {
			return false
		}
		if atomic.CompareAndSwapPointer(&e.p, p, nil) {
			return true
		}
	}
}

它真正做的事情是將正常狀態(指向一個 interface{})的 p 設置成 nil。沒有設置成 expunged 的原因是,當 p 為 expunged 時,表示它已經不在 dirty 中了。這是 p 的狀態機決定的,在 tryExpungeLocked 函數中,會將 nil 原子地設置成 expunged。

tryExpungeLocked 是在新創建 dirty 時調用的,會將已被刪除的 entry.p 從 nil 改成 expunged,這個 entry 就不會寫入 dirty 了。

func (e *entry) tryExpungeLocked() (isExpunged bool) {
	p := atomic.LoadPointer(&e.p)
	for p == nil {
		// 如果原來是 nil,說明原 key 已被刪除,則將其轉為 expunged。
		if atomic.CompareAndSwapPointer(&e.p, nil, expunged) {
			return true
		}
		p = atomic.LoadPointer(&e.p)
	}
	return p == expunged
}

注意到如果 key 同時存在於 read 和 dirty 中時,刪除只是做了一個標記,將 p 置為 nil;而如果僅在 dirty 中含有這個 key 時,會直接刪除這個 key。原因在於,若兩者都存在這個 key,僅做標記刪除,可以在下次查找這個 key 時,命中 read,提升效率。若只有在 dirty 中存在時,read 起不到“緩存”的作用,直接刪除。

LoadOrStore

這個函數結合了 Load 和 Store 的功能,如果 map 中存在這個 key,那麼返回這個 key 對應的 value;否則,將 key-value 存入 map。這在需要先執行 Load 查看某個 key 是否存在,之後再更新此 key 對應的 value 時很有效,因為 LoadOrStore 可以併發執行。

具體的過程不再一一分析了,可參考 Load 和 Store 的源碼分析。

Range

Range 的參數是一個函數:

f func(key, value interface{}) bool

由使用者提供實現,Range 將遍歷調用時刻 map 中的所有 k-v 對,將它們傳給 f 函數,如果 f 返回 false,將停止遍歷。

func (m *Map) Range(f func(key, value interface{}) bool) {
	read, _ := m.read.Load().(readOnly)
	if read.amended {
		m.mu.Lock()
		read, _ = m.read.Load().(readOnly)
		if read.amended {
			read = readOnly{m: m.dirty}
			m.read.Store(read)
			m.dirty = nil
			m.misses = 0
		}
		m.mu.Unlock()
	}

	for k, e := range read.m {
		v, ok := e.load()
		if !ok {
			continue
		}
		if !f(k, v) {
			break
		}
	}
}

當 amended 為 true 時,說明 dirty 中含有 read 中沒有的 key,因為 Range 會遍歷所有的 key,是一個 O(n) 操作。將 dirty 提升為 read,會將開銷分攤開來,所以這裏直接就提升了。

之後,遍歷 read,取出 entry 中的值,調用 f(k, v)。

其他

關於為何 sync.map 沒有 Len 方法,參考資料里給出了 issue,bcmills 認為對於併發的數據結構和非併發的數據結構並不一定要有相同的方法。例如,map 有 Len 方法,sync.map 卻不一定要有。就像 sync.map 有 LoadOrStore 方法,map 就沒有一樣。

有些實現增加了一個計數器,並原子地增加或減少它,以此來表示 sync.map 中元素的個數。但 bcmills 提出這會引入競爭:atomic 並不是 contention-free 的,它只是把競爭下沉到了 CPU 層級。這會給其他不需要 Len 方法的場景帶來負擔。

總結

  1. sync.map 是線程安全的,讀取,插入,刪除也都保持着常數級的時間複雜度。

  2. 通過讀寫分離,降低鎖時間來提高效率,適用於讀多寫少的場景。

  3. Range 操作需要提供一個函數,參數是 k,v,返回值是一個布爾值:f func(key, value interface{}) bool。

  4. 調用 Load 或 LoadOrStore 函數時,如果在 read 中沒有找到 key,則會將 misses 值原子地增加 1,當 misses 增加到和 dirty 的長度相等時,會將 dirty 提升為 read。以期減少“讀 miss”。

  5. 新寫入的 key 會保存到 dirty 中,如果這時 dirty 為 nil,就會先新創建一個 dirty,並將 read 中未被刪除的元素拷貝到 dirty。

  6. 當 dirty 為 nil 的時候,read 就代表 map 所有的數據;當 dirty 不為 nil 的時候,dirty 才代表 map 所有的數據。

參考資料

【德志大佬-設計併發安全的 map】https://halfrost.com/go_map_chapter_one/

【德志大佬-設計併發安全的 map】https://halfrost.com/go_map_chapter_two/

【關於 sync.map 為什麼沒有 len 方法的 issue】https://github.com/golang/go/issues/20680

【芮神增加了 len 方法】http://xiaorui.cc/archives/4972

【圖解 map 操作】https://wudaijun.com/2018/02/go-sync-map-implement/

【從一道面試題開始】https://segmentfault.com/a/1190000018657984

【源碼分析】https://zhuanlan.zhihu.com/p/44585993

【行文通暢,流程圖清晰】https://juejin.im/post/5d36a7cbf265da1bb47da444

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

【其他文章推薦】

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

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

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

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

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

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

使用三台雲服務器搭建真正的Redis集群

三台雲服務器搭建redis集群#

今天花了一天的時間弄集群redis;遇到了很多坑,從頭開始吧

環境講解:

兩台配置:1核2G,另一台:1核1G;
操作系統:Centos 7.6
Redis:3.2.12
Ruby:2.3.4

由於是雲服務器所以默認安裝了JDK1.8和yum以及gcc,如果不是雲服務器的用戶請試試雲服務器吧!還是很方便的

了解什麼是Redis集群(可跳過):

答:集群,即Redis Cluster,是Redis 3.0開始引入的分佈式存儲方案。
集群由多個節點(Node)組成,Redis的數據分佈在這些節點中。集群中的節點分為主節點和從節點:只有主節點負責讀寫請求和集群信息的維護;從節點只進行主節點數據和狀態信息的複製。
集群的作用,可以歸納為兩點:

  1. 數據分區:數據分區(或稱數據分片)是集群最核心的功能。
    集群將數據分散到多個節點,一方面突破了Redis單機內存大小的限制,存儲容量大大增加;另一方面每個主節點都可以對外提供讀服務和寫服務,大大提高了集群的響應能力。
    Redis單機內存大小受限問題,在介紹持久化和主從複製時都有提及;例如,如果單機內存太大,bgsave和bgrewriteaof的fork操作可能導致主進程阻塞,主從環境下主機切換時可能導致從節點長時間無法提供服務,全量複製階段主節點的複製緩衝區可能溢出……。

  2. 高可用:集群支持主從複製和主節點的自動故障轉移(與哨兵類似);當任一節點發生故障時,集群仍然可以對外提供服務。

值得一提的是這幾天的面試遇到一個很有意思的問題(本人親身經歷):

面試官:讓你設計一個搶票或者秒殺或者紅包系統你該如何實現?

我:首先應該加鎖,然後加redis的緩存和預熱。。。

面試官:沒了?

我:我只知道這麼多。

面試官:那如果是在多個集群下呢?

我:分佈式鎖!

面試官欣慰的點頭說:那你知道分佈式鎖的實現方式嗎?

我:zookeeper和redis的redisson。

面試官:redis的分佈式集群玩過嗎?

我:沒有。。。

面試官:有空去玩玩吧,既然你沒玩過那我也不必要和你啰嗦了。

到此我的奇怪的經歷又上漲了!於是今天買了兩個測試服務器來玩玩redis的集群;

這是我的使用場景,但是現實中和面試的例子差不多,都是有需求才去接觸,(可能我比較懶~haha)
當然今天並不是說面試,我們言歸正傳;

搭建,啟動redis集群#

1、安裝Redis(三台服務器都需要安裝)

方式一:

一台全新的服務器首先需要配置密碼和SSH登錄此處我跳過;
接着可以更新yum(服務器的yum都是安裝好的)

yum update

在雲服務器中的安裝則是很簡單的;

yum install redis

安裝需要提示輸入y直接輸入!

接着安裝完成后查看安裝情況:

redis-server -v

或者

redis-server --version

都可以查看當前安裝的redis版本

方式二:

直接從官網下載gz文件

redis舊版本下載地址

一般都是3.2.12版本。所以此處以3.2.12版本為例;

下載好後上傳到雲服務器,並且解壓后,make Install 安裝

redis安裝好后,有五個文件在linux文件系統的/usr/bin中

  • redis-benchmark
  • redis-check-aof
  • redis-check-rdb
  • redis-cli
  • redis-sentinel
  • redis-server

還有一個配置文件在/etc中:

  • redis.conf

現在我們在/usr/local下創建一個文件名叫redis

cd /usr/local
mkdir redis

接着創建兩個文件夾:

cd redis
mkdir redis-01
mkdir redis-02

接着將上述的文件分別複製到這兩個01和02文件內,
這是為了方便我們更好的在本機調用:

2、安裝Ruby(只需要一台安裝即可)

很多小夥伴會有疑問說為什麼需要ruby呢?

因為在redis/src中有一個文件叫做redis-trib.rb,這個文件就是作者用Ruby寫的,用來搭建redis集群(redis3.0版本時才開始支持集群),redis-trib.rb的後綴就是Ruby的簡寫,所以想要搭建redis集群需要有一個能執行.rb這種文件的運行環境,這個環境就是Ruby。

接着又會有小夥伴說:這個安裝簡單!直接 yum install ruby!
你要是這麼想的話,會浪費你將近5-10分鐘的時間(取決你的網速)因為你需要安裝后升級它!

升級的教程我直接用別人的了:[Ruby升級教程](https://www.cnblogs.com/qize/p/11394841.html](https://www.cnblogs.com/qize/p/11394841.html “Ruby升級教程”) 在此也感謝這個博主所寫的經驗教程

一般雲服務器如果安裝的redis是3.2.12,那麼你的ruby使用yum安裝的話一定是2.0版本的!
這個和我們使用的ruby操作redis集群不適應!
所以人生苦短:

wget https://cache.ruby-lang.org/pub/ruby/2.4/ruby-2.4.5.tar.gz

接着解壓下載好的ruby:

tar -xzvf ruby2.4.5

接着cd ruby2.4.5執行下面的命令

./configure –-prefix=/usr/local/ruby -prefix

是將ruby安裝到指定目錄,也可以自定義

make && make install

檢查Ruby安裝情況:

ruby -v

不出意外應該是可以显示版本的,之後安裝這個gem的依賴

yum install rubygems

接着我們需要繼續安裝Redis操作工具

gem install redis

3、檢查配置文件(三台服務器都需要)

上述說到我們有兩個文件:redis-01和redis-02,應該是這樣的

現在需要修改每一個配置文件:

daemonize yes
port 6379(每台機器的端口可以指定為兩個;比如6379和6380)
dbfilename dump-此處添加你的端口.rdb
pidfile /var/run/redis-此處添加你的端口.pid
requirepass 在此輸入你的密碼
masterauth 輸入和上述密碼一樣的
cluster-enabled yes 啟用集群
cluster-config-file nodes-此處添加你的端口.conf
cluster-node-timeout 15000(默認你過期時間15s)

此處網上有很多提示說你的bind屬性為什麼不加呢?
很多小夥伴加了bind屬性反而不能運行redis,剛開始我也犯了這樣的錯誤,後來我發現,雲服務器上的redis
其實不需要加bind屬性綁定端口也可以跑集群。

現在我一共是三台機器:每台上面有兩個redis實例,都分別綁定了6379和6380端口

至此我們的配置文件就結束了。

4、放行端口(三台服務器都需要)

根據不同雲服務器供應商需要進入不同的控制台管理界面;

但是每台服務器的安全組都需要開放對應的端口:

我的三台服務器我就需要為每台服務器開啟入口6379和6380端口;
但是千萬不要以為這就結束了!

redis集群還需要一個總線端口,這個總線端口是你redis實例佔用端口+10000;

比如我一台服務器開了兩個redis實例:6379和6380,那麼他的總線端口救為16379和16380;

需要在安全組同時開放這兩個端口。

啟動集群

回到我們剛剛安裝redis的那台服務器,現在我們來試試啟動redis集群

要啟動redis;需要找到你的 redis-trib.rb 這個文件

一般雲服務器的安裝redis是沒有這個文件的,但是如果你用解壓安裝的redis是有集群的文件

所以需要你自己從官網拷貝比如我的是3.2.12就需要從官網下載。

redis舊版本下載地址

具體安裝見方式二

之後再redis/src下找到 redis-trib.rb 這個文件

把他複製到/usr/local/redis裏面

接下來重中之重的是需要清空/var/lib/redis裏面的所有rdb和conf文件!

之後就可以啟動三個服務器上的redis了

啟動完成可以用 ps -ef|grep redis 查看實例啟動詳情

一般啟動成功是這個樣子

之後回到安裝了ruby的服務器:

cd /usr/local/redis

執行下面命令

./redis-trib.rb create --replicas 1 第一台服務器公網IP地址:6379 第一台服務器公網IP地址:6380 第二台服務器公網IP地址:6379  第二台服務器公網IP地址:6380 第三台服務器公網IP地址:6379 第三台服務器公網IP地址:6380

至此服務器應該是啟動成功了。

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

【其他文章推薦】

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

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

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

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

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

五個Taurus垃圾回收compactor優化方案,減少系統資源佔用

簡介

TaurusDB是一種基於MySQL的計算與存儲分離架構的雲原生數據庫,一個集群中包含多個存儲幾點,每個存儲節點包含多塊磁盤,每塊磁盤對應一個或者多個slicestore的內存邏輯結構來管理. 在taurus的slicestore中將數據划為多個slice進行管理,每個slice的大小是10G,Taurus架構圖如下:

TauruDB的存儲層支持append-only寫和隨機讀,最小數據存儲邏輯單元為plog,每個slice中包含多個plog,默認每個plog的大小為64M。slice中的plog主要用來存放page。Plog中存放中不同版本的page,有些老page已經過期,需要刪除;有些page是新page,需要被保留下來。

Compator主要用來清理plog中過期的page,把一個plog上所有沒有過期的page搬移到一個新plog,老的plog刪除掉。

Compactor的任務需要頻繁訪問內存中索引結構和讀寫plog中的page頁,這兩部分都屬於整個系統中關鍵資源,鎖競爭的壓力比較大,會直接影響性能,所以compactor的優化方案主要圍繞減少內存訪問和磁盤IO,需要考慮以下幾個點:

A、 選取的清理的plog集最優問題,每次回收需要搬運有效page,搬運的有效page數越少,磁盤IO就越小。如何每次調度都選取到垃圾量最大的一批plog;

B、 垃圾量是否分佈均勻,如何讓垃圾集中到一起,回收垃圾集中的plog,提高回收效率;

C、 回收周期是固定的,怎麼樣保證在每個周期內,都能取到最優plog集。

當前,我們提出了幾個可以有效減少page搬運數,從而達到減少IO目的的方案。

關鍵點1:全局調度

全局調度的方案增大plog垃圾量的排序範圍,從slice的範圍增大到slicestore的範圍。因為考慮到後面需要針對單個磁盤進行“加速回收”,所以不擴大到一個存儲節點的全局範圍。

全局調度方案按照slicestore來選取回收plog,先遍歷所有的slicestore,然後在slicestore內部進行垃圾量排序,選取最多的若干個plog進行回收;

優點:有效避免一個slicestore中由於垃圾分佈不均勻引起的plog的無效搬運,減少對plog的讀寫產生的IO;

缺點:排序範圍增大后,排序算法會增加CPU的消耗;

關鍵點2:排序算法優化

原始方案中在slice內部對plog按照垃圾量排序採用C++標準庫排序(std::sort),該算法內部基於快速排序、插入排序和堆排序實現。原始方案中每個slice中最多存有160個plog,小數據量的排序或許效率影響不大,但是一個slicestore中存儲成百上千的slice,排序算法的效率問題就值得關注。

Taurus設計了一種topN算法,能夠提升該場景下的效率。假設需要在n個元素中選取m個最大的元素,兩種算法的時間複雜度和空間複雜度:

C++標準庫排序時間複雜度為O(nlogn),空間複雜度為O(nlogn);

topN算法排序時間複雜度為O(nlogm),空間複雜度為O(1);

Compactor應用場景中,n和m相差幾個數量級,topN算法在時間和空間上都更具優勢。

優點:減少時間複雜度和空間複雜度;

關鍵點3:調度數優化

公共線程池分配給compactor的線程數是固定的,每個周期調度器生成一次任務。原始方案中compactor的每次生成的任務數由slice個數決定,會導致任務隊列中的任務數過多或者過少。過多的話會失去時效性,也就是說plog的垃圾量會隨着時間改變,如果在隊列等待執行的時間太長,可能就不是當前最高垃圾量的plog,同時一次挑選的plog個數太多,會增加算法的時間和空間複雜度;過少的話,compactor線程沒有跑滿,會導致垃圾回收速度降低。

調度數優化也是基於全局調度優化,調度策略只需要保證在一個調度周期內,任務隊列中任務數剛好滿足compactor線程執行。假設有8個slicestore,分配了24個執行線程,每個線程每個調度周期完成一個plog的回收,則每個調度周期每個slicestore只需要生成3個任務。

調度數優化即記錄公共線程池中正在執行和準備執行的任務數,跟據記錄決定本輪調度生成多少個回收任務,從而保證執行線程剛好夠用,且不多不少。

優點:保證垃圾回收速度,最優回收的plog集,有效的減少page的搬運量。

關鍵點4:冷熱分區優化

在數據庫系統中,數據的更新並不是相同頻率,一些數據頁更新或者寫入會更加頻繁(例如系統頁),這部分頁面被稱作熱頁,另外一些更新不頻繁的稱作冷頁。如果把冷頁和熱頁混合放到一起,就會導致更高的寫放大。舉個簡單的例子,假設有100個熱頁和10個冷頁,冷頁基本不更新但是每次垃圾回收,都需要重複把冷頁搬運一次。如果把冷頁單獨寫入一個plog,那麼在垃圾回收階段,就可以減少重複搬運這部分冷頁,達到減少IO放大的效果。

優點:提高垃圾回收的效率和速度。

缺點:需要額外的內存記錄熱度信息。

關鍵點5:磁盤逃生優化

為了後台線程比如備份、快照、垃圾回收、頁面回放等線程有足夠的磁盤空間運行,在磁盤容量使用到達一定閾值,會置Full標誌位,同時停止前台IO。在極端情況下,磁盤使用到閾值前台IO會出現斷崖式下跌為零:

為了避免在磁盤容量達到一定閾值之後前台IO完全停止,在磁盤使用率未達到閾值時,就應該有相應的處理機制。比如說在磁盤使用率未達閾值時,增加如下處理:

A、 降低前台IO,減少磁盤的壓力;

B、 加速垃圾回收,也就是TaurusDB中的compactor機制;

A點不涉及compactor的功能,所以本文先不涉及,下面主要介紹兩種加速機制:

1.修改寫IO優先級

Compactor作為後台線程,考慮到整個系統的效率,正常運行時plog寫IO優先級默認為low。在加速回收階段,plog的IO需要修改為high。

2.提高執行速度

前文可知,公共線程池為compactor分配固定數目執行線程,而且運行過程中只支持擴容不支持恢復。如果壓縮其他後台任務的執行線程,對整個系統的影響太大,量也不易控制。所以不考慮。

考慮到過程中不能擴容,那就初始化就擴容,通過控制調度任務數控制任務執行速度。例如,假設compactor的運行線程在正常場景下為4個,加速狀態下需要增加到8個。可以在公共線程池中先分配8個線程,在正常場景下,控制任務隊列為4個,另外4個線程處於wait狀態,只會佔用文件句柄,並不影響CPU。而在加速狀態下,控制任務隊列中的任務數為8,就可以實現上述的加速邏輯。

所以,提升寫IO的優先級能夠加速page的搬運,提升垃圾回收速度。通過控制調度任務數控制任務執行速度卻很難控制很精準,上下會有一定的小波動。

最後

以上提到的垃圾回收compactor優化方案中,磁盤逃生優化能夠處理磁盤容量緊急情況。目前根據本地測試,在500G的小數據集情況下,IO放大能減少到原來的1/6。系統資源佔用減少到原來的1/3。

 

點擊關注,第一時間了解華為雲新鮮技術~

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

【其他文章推薦】

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

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

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

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

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

斯里蘭卡逾百頭鯨魚擱淺 漁民徹夜忙搶救

摘錄自2020年11月3日公視報導

斯里蘭卡首都可倫坡附近,發生有120頭鯨魚擱淺事件,當地民眾無法顧及防疫宵禁措施,徹夜協助要把鯨魚推回大海,但其中還是有兩頭鯨魚因為擱淺、受傷死亡。

警方說,當時不斷有鯨魚沖上岸,民眾根本搶救不及,後來海岸警衛隊及海軍部隊也加入搶救行列,利用小型巡邏艇將鯨魚送回深水區。斯里蘭卡海洋環境保護署則表示,這是斯里蘭卡首度有這麼多的鯨魚擱淺,情況很不尋常,懷疑可能跟澳洲日前發生大規模鯨魚擱淺事件有相似之處。

澳洲的塔斯馬尼亞在九月間,發生470頭領航鯨集體擱淺的意外。當時救援隊伍歷經五天時間,成功將其中110頭送回大海,其餘鯨魚則不幸死亡。為了避免造成二度海洋生態浩劫,當時清運鯨魚屍體也成了棘手問題。至於鯨魚為何一再被沖上岸始終成謎,科學家研判,領航鯨是高度社會化動物,擱淺原因可能跟牠們喜歡群聚生活、彼此互動密切,跟隨著一兩隻偏離航道的領航鯨而集體擱淺。

海洋
國際新聞
斯里蘭卡
鯨豚擱淺

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

【其他文章推薦】

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

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

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

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

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

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