Jmeter與壓測相關概念

3{icon} {views}

相關概念

RT(response time)

什麼是RT? RT就是指系統在接收到請求和做出相應這段時間跨度

但是值得一提的是RT的值越高,並不真的就能說明我們的系統的吞吐量就很高, 比如說,如果存在數據在網絡中傳輸時出現了丟包的現象,傳輸的數據少了,速度就會增加, 但是這是事故,不是說我們的系統吞吐量高

此外,對不同的系統來說衡量的標準也不一樣,對一個遊戲軟件來說,響應時間也就是常說的延遲. 在100ms以內,遊戲體驗感會很好,飆升到幾秒鐘,乾脆就不要玩了, 但是對一個編譯系統來說,編譯一套源代碼可能好幾十分鐘,人們也願意等

java應用程序中的GC也會導致系統的總體RT飆升

Throughput 吞吐量

什麼是吐吞量呢? 百度百科: 廣義的吞吐量是: 單位時間內成功的傳輸數據的數量, 在我們進行壓力測試得出的結果中, 可以將吞吐量理解成系統在單位時間內成功處理的請求的數量

一個系統的吞吐量和request對CPU的消耗,外部的接口以及IO等因素緊密相關,

比如一個web應用代碼寫的再漂亮,但是每次請求都會去查詢數據庫,併發數上來后,數據庫就會佔用大量的CPU負載,系統的IO飆升,甚至可能導致其他軟件不得不等待mysql先執行完才能搶到CPU的時間分片. 系統的瓶頸積壓到mysql這裏,這個web應用的吞吐量一定不會很高

此外,一般我們都是這樣描述吞吐量: 在併發數為xxx時,系統的吐吞量為yyy

併發用戶數

它指的是系統可以承載的, 可以同時正常使用網站的用戶數量, 這個指標似乎看起來可以比吞吐量更加直觀反應系統的性能, 但是往往系統中的用戶又有不同的行為, 比如未註冊的, 在線的, 同時發送請求的等等, 簡而言之, 可以考慮用在線的用戶和同時發送請求的用戶數作為性能指標, 把在線的用戶當成性能指標更直觀, 把同時發送請求的用戶數量當成性能指標更準確

QPS (query per seconds)

每秒的查詢率, 用來權衡服務器在規定的時間內處理的流量數

計算公式: QPS = req / sec , 即平均每秒的請求量

TPS (transition per seconds)

TPS (transaction per second)代表每秒執行的事務數量,可基於測試周期內完成的事務數量計算得出。例如,用戶每分鐘執行6個事務,TPS為6 / 60s = 0.10 TPS。同時我們會知道事務的響應時間(或節拍),以此例,60秒完成6個事務也同時代表每個事務的響應時間或節拍為10秒

PV和UV

PV訪問量 (Page View) 每打開一次頁面或者刷新一次頁面 pv+1, 它反應的的網站的頁面被訪問的次數

UV訪問數(Unique Visitor) 即, 獨立訪客的訪問數, 換句話就是一台電腦算一個訪客,

  • 通過QPS估算PV

公式1 : pv = QPS*3600*6

公式2: pv = QPS*3600*8

  • 根據QPS,PV估算服務器的數量

服務器的數量= 每天的總PV / 單台服務器的PV

原理: 每天80%的訪問集中在了當前的20%的時間段, 這20%的時間就叫做峰值時間

峰值時間段 QPS = 總pv數*0.8 / 每天的秒數*0.2

Jmeter

jmeter能做什麼?

Jmeter也是apache的頂級項目, 通過jmeter我們可以測試靜態和動態資源,web動態應用程序的性能, 還可以用來模擬服務器,服務器中,網絡或者對象上的高負載, 測試他們的強度,以及不同負載下的總體性能

使用環境: Windows

使用的思路: 在jmeter提供的可視化頁面中,添加一個一個的指定的模塊. 對我們的web項目中指定的接口進行壓力測試,當然它不僅僅使用於發送http請求測試web項目的資源接口

添加什麼模塊呢?

  • 線程組: 也就是啟動多少條線程對系統壓測, 線程是否併發的啟動
  • http的默認參數: 比如protocol host port 等等
  • 添加請求的參數/請求體/cookie等信息
  • 添加監聽器來显示壓測結果

實例:

添加線程組,每輪同時啟動1000條線程對系統進行壓測,重複1輪

添加默認使用的請求參數, 這個默認的請求參數並不要求強制添加,只是為了以後在具體的某個http請求壓測模塊不寫請求參數時,使用這裏面的默認參數

配置信息如下:

添加一個sample, 這個sample其實就是http請求, 一個具體的http請求, 用大白話說 ,我們想壓測系統中的登錄方法,就添加一個登錄sample, 想壓測系統中拉取個人信息的方法, 就添加一個 個人信息sample

sample詳細配置

可以看到上圖中我們可以添加request Param request body, 上傳文件, 甚至可以添加一個模塊添加請求的cookie模塊

添加Listenner, 選擇聚合報告 ,開始壓測得到下面的結果,我們關注圖中的吞吐量

支持使用配置文件中的數據當成壓測的參數, 使用場景: 通過這個配置文件的配置,我們可以模擬出多用戶併發訪問系統的場景,下面配置中的每一行都是一個用戶的信息,jmeter會自動讀取這個配置文件中的每一行,使用這裏面的信息進行壓力測試,直到達到了用戶指定的請求量才結束

編寫配置文件

導入配置文件

使用配置文件,詳細的配置細節我都標在了圖片中

發送請求

使用環境: linux

第一步: 在window上錄好 XXX.jmx 文件

第二步: 啟動

sh jmeter.sh -n -t XXX.jmx -l result.jtl

第三步:將result.jtl導回到window上的jmeter中在可視化頁面中查看結果

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

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

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

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

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

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

電腦組裝之硬件選擇,電腦組裝知識

7{icon} {views}

作者:凌逆戰

地址:

電腦主要配件:主板、CPU、顯卡、显示器、電源、機箱、內存條、硬盤。CPU、顯卡、內存條、硬盤是插在主板上的,電源用來給主板上的部件進行供電,CPU,主板,顯卡,內存條、硬盤、電源這幾個放在機箱中就構成了我們通常所說的主機。摩爾定律,硬件的性能每隔18~20個月就會提升一倍。

主板

主板主要接口:CPU插槽;內存條插槽;SATA硬盤接口;M.2接口(用於M.2接口的固態硬盤);背板I/O接口;PCLE-X16插槽(用來接顯卡);PCLE-X1插槽(用來接聲卡、網卡等);供電插槽

主板性能指標:芯片組、供電項數目(供電項越多,平分下來每一路的電流就會減小了,減少主板發熱 超頻時更加穩定)、做工、擴展能力(是否支持USB3.0或者USB3.1,是否支持超頻)。

對於主板最重要的就是其穩定能力,其次才是芯片組

因為芯片組有Intel和AMD,所以主板可以根據芯片組分類,英特爾和AMD這兩家公司每次發布新的CPU的時候,都會先各大主板廠家發布適配自己CPU的芯片組,意思就是說:“我這邊CPU已經做出來了,你們主板廠得按照我的這個標準,做出來的主板才能用我的這個CPU”

如何區分intel主板和AMD主板:

  • intel主板會有亮閃閃的金屬扣,且主板的安裝位置是一個一個針腳,均勻分佈,因為intel處理器是觸點式接口
  • AMD主板沒有金屬扣,且主板的安裝位置是一個一個小凹槽,因為AMD處理器是針腳式接口

Intel芯片組主板

芯片組是主板的核心芯片,選對芯片組,主板和CPU才能兼容。

型號字母

目前英特爾的芯片組有4個等級,HBZX分別適配不同的用戶,下面只是一個大概的規律,不能以偏概全,比如:H370>B360

H中低端、入門不支持超頻。價格在300元左右。

B中低端、主流這類主板幾乎不能超頻(有特殊幾個主板能通過人工破解來進行超頻),而且僅支持最高DDR4 2666GHz的內存條頻率,所以如果你買的CPU是不帶K的如 i3-8100;i5-8500這類不支持超頻的CPU,或者本身沒有超頻需求,B系主板是非常划算和有性價比的選擇。價格在500元左右。

Z高端這類主板天生支持超頻功能(需要CPU支持,英特爾CPU後面帶K的都支持超頻,如i3-8350K;I5-8600K;I7-8700K),同時芯片組支持更多原生的擴展卡槽的接口,而且這類主板通常也支持非常高的內存頻率,價格也會貴一些,一般在1000元以上。

X特殊、頂級,這類主板有2066個針腳,也就是只能用英特爾後面帶X的CPU。如i9-7960X、i7-7800X。當然這類主板和CPU價格都高的嚇人,市場很小眾。

H、B、Z芯片組的主板/CPU接口都是 LGA 1151的,X系列的主板/CPU接口是 LGA 2066

字母後面的数字

100系列支持英特爾6代CPU,如B150芯片組支持i3-6100、i5-6500、i7 6700等。(intel的100系列主板刷新bios是可以上7代cpu的)

200系列支持英特爾6、7代CPU,如:B250芯片組支持i3-7100、i5-7500、i7-7700k等。

300系列支持英特爾89代CPU,如:B360芯片組支持i3-8100、i5-8500、i7-8700k、i7-9700k、i9-9900k等。

至於後面個位和十位數上的数字,越高代表主板在同等級中越高端。

AMD芯片組主板

芯片組是主板的核心芯片,選對芯片組,主板和CPU才能兼容。

型號字母

AMD平台的主板芯片組,有ABX三個檔次

A低端,這類芯片組的主板不能超頻,普通用戶使用也足夠了。價格大多在500元以下。

B中端,這類芯片組的主板也能超頻,高性能的接口數量要少一些,價格在500元左右,是普通裝機用戶的性價比選擇。

X高端,芯片組支持更多可以擴展的接口,可以超頻

AMD芯片組的主板CPU接口大都是AM4的,像高端的X399CPU接口是TR4的。

字母後面的数字

300系列支持 一代銳龍,需要更新BIOS才能支持二代銳龍

400系列支持 一代、二代銳龍

銳龍二代或一代的用戶買300 400系列芯片組的主板都可以

主板尺寸

常見的主板板型分為:E-ATX(加強型)、ATX(標準型)、M-ATX(緊湊型)、MINI-ITX(迷你型)。

 

常見的主板板型對比

E-ATX型主板高性能主板,一般會有8跟內存插槽,芯片組也多為X等級的,也就是說適用於英特爾以X結尾的CPU,如i9-7960X。價格非常高

ATX型主板用的最多的主板,俗稱“大板”。這類主板由於體型稍大,需要搭配中塔以上大機箱,做工用料較好,擴展接口比較豐富,不過價位略貴

M-ATX型主板比ATX要短了一些,俗稱“小板”,也叫緊湊型主板,其結構為方形。M-ATX主板主要用於小機箱電腦中,如今裝機非常主流的主板板型。

MINI-ITX型主板俗稱迷你型主板,結構是方形,主板尺寸小,適合一些迷你小機箱電腦,這種主板通常作為小巧的HTPC電腦,因此這類主板大部分都內置了Wifi模塊。

主板小板和大板的區別是什麼?主板小板和大板優缺點分析:

1、同芯片的主板大板和小板性能沒有任何提升,主板尺寸並不會影響到電腦的整體性能

2、大板相比小板的擴展接口也許會更加豐富,比如大板標配4個內存插槽,而小板有可能是2個或則4個,大板的PCI-E顯卡插槽擁有兩個,而小板只有一個。此外,PCI插槽方面,大板也更豐富一些,USB方面,大板通常也多出兩個;

3、大主板做工用料相對充足,不過價位上也會更加貴一些;

4、大板需要中塔以上大機箱支持,而小主板既能夠兼容大機箱,還能夠兼容一些迷你機箱,價位上也相對可能實惠一些;

  對於買主板是選擇小板還是大板,關於這個問題,關鍵看個人需求。通常來說,組裝入門級電腦,選用小巧的M-ATX機箱,那麼肯定需要選用M-ATX主板或者迷你主板,對於注重散熱並定位中高端或者高端,選用大機箱的裝機用戶,建議搭配ATX大主板,其擴展、做工更加有優勢,一般用戶,建議選擇M-ATX小主板,價格相對實惠一些,擴展接口也基本足夠使用了,屬於大眾型主板。

關於品牌

華碩:三大板廠之一,主板的BIOS界面做的超一流,簡單易懂,好用,而且功能很多。高端主板非常強,中低端主板與其他牌子拉不開差距。ROG玩家國度系列主板用料豪華,超頻能力非常強,是最高端主板的象徵。

技嘉:三大板廠之一,技嘉的主板一般比較耐用。AROUS系列主板定位高端,類ROG玩家國度。

微星:三大板廠之一,微星的板子跟華碩技嘉都差不多,BIOS比華碩稍差一些,價格也低一點點。

華擎:華擎是華碩的子公司,是華碩面對中低端市場與二三線品牌競爭的品牌,在低價位中用料算是不錯的,性價比高,價格比三大廠要低一兩百塊,被稱為價格屠夫。

映泰:映泰也是台灣的主板大廠,主板相對來說稍微低端一些,穩定性可以,價格也很低。

七彩虹:七彩虹的品牌宣傳做的好,線下電腦城賣的好,但線上銷量有點凄慘,不建議購買。

高端板子最好選擇華碩的,低中端主板最好選擇技嘉、華擎的

CPU

CPU有幾個重要的參數:架構、主頻(頻率)、核心數、線程數、緩存、最大睿頻。

關鍵參數

主頻:CPU的工作頻率,我們可以直接理解為運算速度,主頻越高,計算能力越強,現在CPU主頻都以GHz為單位。

最大睿頻:睿頻是指當啟動一個運行程序后,處理器會自動加速到合適的頻率,而原來的運行速度會提升 10%~20% 以保證程序流暢運行的一種技術。

核心數線程數:核心數就相當於有多少只手,線程數就相當於你能同時干幾樣活,核心數*線程數=馬路寬度,頻率等於車速

架構:架構對性能的影響最大。一般來說,每一代CPU的架構都是一樣的,

緩存:位於CPU與內存之間的臨時存儲器,它的容量比內存小但交換速度快。有一級緩存二級緩存,一級緩存的容量基本在4KB~64KB,二級緩存的容量則分為128KB/256KB/512KB/1MB/2MB等,一級緩衝容量相差不大,二級緩存容量則是提高CPU內存的關鍵。

如果玩遊戲,就需要選主頻高的,簡單粗暴的進行計算。如果做圖形渲染,建議多核心多線程的

CPU有兩家廠商在做Intel和AMD,處理器有兩種接口接口類型,AMD的CPU是針腳式Intel的CPU是觸點式,由於intelCPU採用了觸點式所以intel主板就必須有針腳,而AMD則是與之相反,CPU使用針腳,主板採用觸點式。雖然方式不同,但是從性能上完全沒有區別,主要AMD的處理器針腳特別怕彎,彎了或者斷了處理器基本就要涼涼了。而intel處理器就不會這樣,不過intel主板的針腳也極易容易彎折。現在AMD也推出了自己的觸點式處理器——線程撕裂者,以後AMD處理器也會朝着觸點式發展。

Intel CPU:穩定性強,功耗低(發熱量少,常規散熱系統),兼容性強

AMD CPU:爆發力強,多線程(適合畫圖)、功耗高(發熱量大,散熱系統需要規劃)

Intel CPU和AMD CPU在相同性能下,價格往往AMD會更加實惠,換句話說相同價位的CPU,AMD的性能普遍更強,說雖然這麼說,但是還是要看個人需求,畢竟這兩款CPU的針對性不同,當然對於AMD的高端CPU散熱量和Intel差不多,不用很擔心AMD處理器散熱問題。

Intel CPU後面的数字

  以i5 3450U為例

第一位数字 是i 後面的数字代表的是家族名稱。

第二位数字 “3”代表它是第幾代架構的產品,後面的三位数字是CPU型號,一般越高越好,

第三位数字 “4”代表的是處理器等級。

第四位和第五位数字 “50”代表處理器的頻率。如果後面還有@ 3.0GHz  @ 3.0GHz,前一個是主頻后一個是睿頻

Intel CPU後面的字母

K代表不鎖倍頻的處理器;也就是可以超頻;

M代表標準電壓cpu,可以拆卸的;

U代表低電壓節能的,可以拆卸的;

X代表高性能,可拆卸的;

Q代表至高性能級別;

H是高電壓的,是焊接的,不能拆卸;

Y代表超低電壓的,除了省電,沒別的優點的了,是不能拆卸的;

F代表不帶內置顯卡(核顯)

兩個字母的,屬於上面這些特性的組合

相同性能的CPU,Intel的CPU價格要比AMD貴,且Intel的CPU功耗比AMD處理器功耗低。如果手頭預算不多,建議選擇AMD的CPU,而手頭寬裕的話,建議選擇Intel的CPU。相比而言AMD 的CPU性價比較高。

AMD CPU後面的数字

  以R5 1500X為例

第一位数字 是R 後面的数字代表的是家族名稱。

第二位数字 “1”代表它是第幾代架構的產品,

後面的三位数字是CPU型號,一般越高越好,

X代表支持完整的XFR技術(帶X的超頻能力更強,而且超頻是全自動智能的)。

CPU天梯圖

想要看更詳細的CPU性能天梯圖請移步:

中關村CPU性能天梯圖:

注意事項:選購CPU、主板的時候要特別注意接口時候匹配

CPU的主要廠商

  • Intel:主要有賽揚(Celeron)、奔騰(Pentinum)、酷睿2(Core2)、酷睿i(Core i)這三個系列,賽揚系列以低端產品為主,奔騰系列以低端和低中端產品為主,Core2以低端和中低端產品為主,Core i系列以中端和高端產品為主,酷睿i3 i5 i7 是英特爾主流的處理器家族。總的來說,性能強弱為賽揚<奔騰<酷睿2<酷睿 i。但是不是絕對的,還要看該系列的產品是第幾代的,比如酷睿2系列的CPU不一定就絕對地比奔騰系列的CPU性能強,從天梯排行中就可以看出來,Pentinum E5700>Core2 duo E4300.
  • AMD:主要有銳龍(Ryzen,性價比)、AMD FX(推土機,高端)、APU(以四核為主)、速龍(Athlon,性價比)、閃龍(Sempron),這幾個系列,閃龍系列主要以低端產品為主,速龍以低端和低中端產品為主,弈龍主要以中端和高端產品為主,A系列(A系列集成了顯卡芯片)主要以低中端和中低端產品為主,FX系列以中高端和高端產品為主。總的來說,性能強弱為:閃龍<速龍<A系列<弈龍<FX。但是這個也不是絕對的。比如弈龍1代的很多產品性能就比不上速龍2代的產品。AMD現在只有銳龍系列的CPU值得買

顯卡 

顯卡的性能指標:架構、流處理器個數、GPU頻率、核心頻率、顯存帶寬、顯存大小(按重要程度排序)

架構:越新越好

流處理器:簡稱SP,也叫渲染管,是顯卡最重要的參數,流處理器的數量直接影響顯卡的性能,流處理器越多,顯卡畫圖能力越強,速度越快(同一代的顯卡比較流處理器才有意義)。

GPU頻率:GPU頻率越高,性能越強,發熱也越大,功耗越高;頻率低,性能弱,發熱也越小。

顯存帶寬:顯存帶寬=顯存位寬×顯存頻率,顯存位寬就相當於公路路寬,顯存頻率就像汽車的速度,所以,顯卡位寬和顯存頻率對顯卡的性能影響很大。

顯存容量:顯存能提供臨時的存儲功能。很多奸商會把顯存當做顯卡的賣點來忽悠小白,這裏要說的是,大顯存有用,但不是那麼的重要。舉個例子:顯存是停車場,如果停車場的馬路不夠寬(位寬 bit),汽車的速度也不夠快(顯存頻率MHz),那麼這個停車場的吞吐量就很小,修一個超大的停車場純屬浪費資源。

顯卡品牌:N卡和A卡

Nvida Geforce——N卡

N卡有GT、GTS、GTX這幾個系列。

GT是普通系列,而GTS和GTX是中高端系列的,如果後面跟了 Ti 表示是該型號的的加強版。

在選擇N卡來說,一般看第一位数字和第二位数字,

第一位数字 第幾代顯卡

第二位数字 表示是該系列的低端、中端還是高端。1,2,3的話表示是該系列的低端產品;4,5,6表示是中端產品,7,8,9表示是高端產品,比如GT610就是第六代的低端顯卡,還有一點就是產品低一代的話,而第二個数字高一點的話,兩個顯卡的性能基本很接近的,比如GT640和GT550,GT640是第六代的顯卡,GT550是第五代的顯卡,而GT550第二位数字是5,GT640第二位数字是4,通常來說,像這樣的情況下,兩個顯卡的性能是很接近的。

第三位数字 “0”表示常規顯卡,“5”表示升級版

N卡優缺點

  • 設計側重點:3D性能和速度,
  • 性能運算:執行效率高,但運行能力較低
  • 視頻處理:色彩稍淡,需要手動調節分辨率

ATI——A卡

ATI(已經被AMD收購),A卡;N卡有鐳、HD、R、X系列。

第一位数字 表示是第幾代,

第二位数字 表示該系列的低端、中端還是高端。4,5的話表示是低端;6,7表示是中端;8,9表示是高端。

第三位数字 “0”表示常規顯卡,“5”表示升級版

A卡優缺點

  • 設計側重點:2D平面畫質
  • 性能運算:運算能力強大,但執行效率不高,對於複雜的任務適應性不強,需要軟件的支持
  • 視頻處理:自動調節分辨率,色彩還原度高

娛樂一下

  相同性能情況下N卡價格比A卡貴,A卡的性價比相對來說要高一點。現在來說,N卡和A卡區別不大,普通用戶根本用不出來區別。但是A卡較N卡性價比高是真的。有時候我們看到一個顯卡相同的型號,但是卻買不同的價格,其主要原因是因為顯卡的核心頻率、散熱設計等不同。

關於品牌

目前較大的顯卡品牌有:華碩、技嘉、微星、藍寶石、索泰、映眾、七彩虹、影馳等。

華碩:三大顯卡廠之一,顯卡用料好、散熱猛、售後好、價格高。ROG玩家國度系列在性能、散熱、外觀等各個方面都幾乎完美,但是就是貴。

技嘉:三大顯卡廠之一,顯卡做工用料都很好。旗下有AORUS系列,定位類似ROG玩家國度,價格和玩家國度也差不多。

微星:三大顯卡廠之一,是三大廠中性價比最高的。微星紅龍系列顯卡性能和用料還是很強的,而且價格要便宜一些。

藍寶石:是AMD最忠實的合作夥伴,A卡最強廠家

索泰:索泰和映眾都是香港柏能旗下的品牌,索泰被稱為“堆料狂魔”,顯卡性能極強,核心頻率調的很高,性能甚至超越三大廠,價格還便宜,但是高頻帶來的散熱問題較大,可能會比較吵。

映眾:映眾跟索泰的做法剛好相反,映眾在顯卡的核心頻率上沒索泰那麼激進,為了散熱和靜音將顯卡頻率調低了一點點,犧牲了小部分性能。映眾冰龍系列散熱極佳,風扇非常靜音,價格也較低。

影馳:影馳的顯卡其實也是非常不錯的,名人堂系列顯卡用料足,外觀也很好看,但是價格非常非常高,性價比很低,感覺不如買三大廠的旗艦型號。

七彩虹:七彩虹也被稱為“凄慘紅”,七彩虹在十年前的名聲並不好,但是現在七彩虹的igame系列顯卡做的還可以,價格和性能都中規中矩。如果遇上打折力度較大,也是可以選擇的

核心顯卡、主板集成顯卡和獨立顯卡的區別

核心顯卡:處理器集成顯卡就是指集成在cpu內部的顯卡

集成顯卡:集成在主板北橋中的顯卡,目前處理器核心顯卡性能已經領先於主板集成的顯卡,並且將顯卡集成在CPU中比集成在主板中更有優勢。新的主板廠商已經不會在主板上集成顯卡了。

獨立顯卡:我們自己加的獨立的显示芯片,採用PCI接口插槽。

注意:下圖左邊NVIDIA卡的支持光追改成RTX了,不支持光追的還是GTX

想要看更詳細的顯卡性能天梯圖請移步:

中關村顯卡性能天梯圖:

內存條

  DDR內存,目前分為4代。每到新一代的內存的開發,都是為了解決上一代內存速度極限的問題,DDR2到了1066就不能再高了,而DDR3是從1066起步的,DDR3內存條基本上到了2400的頻率,就上不去了。而DDR4代,從2400起步,同之前的內存是一樣的。所以,這個其實是根本區別。再高的頻率,意味着更大的帶寬,就可以更好的適應新的CPU的要求。

DDR3 支持頻率有6種:800Mhz(DDR2和DDR3換代時候的產品)、1066Mhz(DDR2和DDR3換代時候的產品)、1333Mhz(常見)、1600Mhz(常見)、1866Mhz(高端電腦配備)、2400Mhz(高端電腦配備)

DDR4 支持頻率有6種:2133MHZ(常見)、2400MHz(常見)、2666MHz2800MHz3000MHz3200MHZ3300MHZ,

內存最高頻率 < 主板最大支持頻率

  內存是CPU與硬盤之間的傳輸的中間設備,以DDR4為例,內存條的讀寫速度相比於硬盤快很多很多很多。

內存的主要參數:頻率、時延、顆粒、容量、下行速度(按重要程度排序)

頻率和時序:我們經常看到的2133MHz、2400MHz、3200MHz就是內存條的頻率,它可以看成是內存條數據的傳輸速度,超高頻率的內存條固然能給遊戲帶來一點性能提升,但是需要更高端的主板和CPU的支持,普通用戶選擇2400MHz的也已經足夠用了,頻率是內存條最重要的參數。

  還有一個重要參數就是時延,一般用CL表示,時延就是尋址所需的時鐘周期。同一頻率的內存條時延越低越好。

現在普通的DDR4代內存條一般為頻率2400MHz,時序CL15-17左右。但是一些使用極品顆粒的超頻內存條如三星的B-die顆粒就可以輕鬆做到頻率3200MHz,而且時序只有CL12。這類極品內存條可以做到保證時序不超標的情況下,超頻上4000MHz以上。

幾乎所有的DDR4代的內存條默認的頻率只有2133MHz,所以即使你買的是高頻內存條,也需要在主板BIOS設置中打開XMP(自動超頻)或手動設置超頻后才能達到商家所給出的頻率,而且,很多主板並不支持超過2666MHz以上的頻率,所以即使你的內存條是4000MHz的神條,也會自動降頻到2666MHz使用,這個需要用戶去看主板上的說明。

內存條顆粒:顆粒就是內存條的存儲數據的東西,現在主流的顆粒生產商就是 三星海力士鎂光這三家。由於顆粒在生產時候會有質量參差不齊的情況,所以一些成色極品的顆粒會被挑選出來做成高端超頻內存條,而一些成色普通但合格的顆粒會被拿去做成普通內存條。至於怎麼看顆粒的好壞,我們可以從內存條的頻率和時序來做一個購買前的初步判斷。

內存條容量:容量越大,存儲的數據越多。每打開一個軟件,這些軟件的數據都會被保存到內存條中,如果內存條被塞滿,我們繼續打開其他軟件的時候,CPU就只能從速度超慢的硬盤調取數據了

單通道與雙通道

CPU與內存條之間需要交換數據,就有上行數據(內存條數據傳入CPU)和下行數據(CPU數據傳入內存條)  ,如果只有一根內存條,那麼上行數據和下行數據的傳輸就需要佔用一條通道,爭搶通道的使用權限和帶寬。如果有兩根內存條組雙通道,那麼上行數據和下行數據的傳輸就可以分別在兩根內存條之間分別傳輸,互不影響。

  一般來說,兩根相同規格的內存條插在主板對應的位置上就可以組成雙通道了(看B站UP主說的)。

雙通道能夠給計算機帶來5%左右的性能提升,雙通道最大受益者是集顯/核顯,因為CPU要同時負責程序數據和显示數據的處理,需要的數據流量更大,所以雙通道帶來的雙倍帶寬才能滿足這麼大的數據流量的需求。英特核顯可以提高20%性能,amd核顯可以提升30%-50%。

內存條選擇注意事項:

  1. 對於普通用戶,選擇內存條的時候,一般來說4g就可以了,最多8g,16g的內存條看需求,如果自己也不知道自己需要多少容量的,可以先買一根8GB的使用,發現不夠可以再買一根8GB組雙通道。
  2. 如果有兩根或多根不同頻率的內存條同時使用,會以頻率最低的來統一頻率。比如有一根2133MHz、一根2400MHz、一根3200MHz的內存條同時使用的話,所有內存條都是按照2133MHz來使用。所以一般組雙通道建議購買兩根相同規格的內存條插在主板對應的位置上。所以如果是升級內存條的用戶,一定要看看已有的是多少頻率的,不要盲目購買高頻內存條。
  3. 有4根內存插槽的主板,你把內存條插滿它依然是雙通道的
  4. 關於內存條PCB板的層數,層數越多,電路板內部的電路走線層數增加,每一層的電路走線就不會那麼擁擠了,這樣會有更好的電氣性能,使得超頻更加穩定。
  5. AMD銳龍平台用戶可以選擇芝奇、英睿達等牌子的內存條,可以兼容。
  6. 關於品牌:各個主流品牌之間內存條價格差距不大,普通用戶建議在 芝奇英睿達海盜船影馳金士頓威剛阿斯加特十銓宇瞻等這幾個品牌中對比挑選一款頻率、時序、價格都不錯的內存條,然後認準官方自營旗艦店、官方旗艦店,因為內存條官方都會終身質保。

硬盤

  我們都知道木桶的短板效應,就電腦的速度來說,CPU緩存、顯卡緩存、甚至是內存條都是以至少十倍百倍以上的速度差距遠遠的超過了所有的机械硬盤的,這就一定會給電腦的性能帶來一些瓶頸。固態硬盤的存儲速度是机械硬盤的5-10倍左右,多少能彌補一些硬盤在速度上的短板,所以如果你覺得你的舊電腦有些卡,反應慢,換一塊固態硬盤絕對能給你的舊電腦帶來新的生命

机械硬盤

  机械硬盤是利用磁性來記錄信息數據,原理類似與小時候聽歌用的磁帶,如果我們需要找到某個數據,磁盤就會轉動到記錄這個信息的部位,然後由磁頭感應磁性來讀取數據。

注意事項:硬盤建議選擇西部數據和希捷的,我個人傾向於西部數據,相比而言穩定一點,硬盤的轉速有7200轉和5400轉的,最好選擇7200轉的,西部數據硬盤有三種:黑盤,藍盤和綠盤,在買硬盤的時候注意千萬不要買綠盤,最好選擇藍盤或者黑盤(價格有點貴)。好像現在又出了紅盤,具體不是很清楚。

固態硬盤

  固態硬盤(SSD)是利用電流來記錄信息數據,原理類似與MP3,如果我們要找到某個數據,直接去找到存放數據的區域,就可以直接讀取了。

  

 

固態硬盤的性能參數:固態硬盤的顆粒、主控、緩存、3D NAND堆棧技術、接口\總線\協議、(按重要程度排序)

顆粒

閃存顆粒是固態硬盤用來存儲數據的東西,分為SLCMLCTLC三種,是挑選固態最重要的參數。

SLC:每個存儲單元存儲1bit的數據。這種存儲方式穩定性最強,讀寫速度很快,而且不會出錯,壽命長,但是成本高。

MLC:每個存儲單元存儲2bit的數據,速度會比SLC慢一點,穩定性較強,壽命還好,但是價格貴。

TLC:每個存儲單元存儲3bit的數據,效率低、速度慢、還容易出錯、壽命相對於上面兩種短一些,,說雖這麼說,但是現在使用最多的還是TLC,重度使用5年是沒有問題的,且價格相對便宜。

現在世界上能自主生產顆粒的廠家有:intel、三星、閃迪、東芝、鎂光(英睿達)、海力士。所有正規的固態硬盤使用的都是這幾家的檢驗合格的原廠顆粒。如使用自家顆粒的intel、三星、閃迪、鎂光(英睿達)、東芝等;還有雖然自己不會生產顆粒,但是使用從原廠購買顆粒的浦科特、海盜船、建興等。

主控

  主控很重要,就相當於顆粒的管理員。比較好的主控品牌有:馬牌(Marvell)、SandForce、三星、intel、東芝等

緩存

  和前面一樣,緩存就是CPU和固態硬盤之間的緩衝區,方便傳輸,緩存越大越好

總線

  總線有SATA總線、PCI-E總線(PCIE×1、PCIE×2、PCIE×4、PCIE×8、PCIE×16 数字越大,速度越快)目前固態硬盤用的都是PCIEx2和PCIEx4的總線,顯卡是PCIEx16總線

傳輸協議

  傳輸協議有:IDE(老的机械硬盤)、AHCI(机械硬盤)、SATA(目前主流硬盤協議)和NVMe(高速度低延遲)。越後面傳輸速度越快,擁有NVME協議的固態硬盤速度比無NVME協議的固態硬盤快很多。

接口

目前固態硬盤接口有:SATA、mSATA、M.2、PCI-E

SATA接口:屬於老式的接口,分SATA 3GB和SATA6 GB,我們的机械硬盤使用的也是這種接口。這種接口速度稍慢,延遲稍高,最大速度不會超過600MB/s,

mSATA接口:這種接口不多,一般用在筆記本上

M.2接口有兩種:B key和M key。

B key M.2接口:又稱“SOCKET 2”,豁口在左邊,比較老,支持PCI-Ex2總線和SATA總線,速度比較慢

M key M.2接口:又稱“SOCKET 3”,豁口在右邊,目前主流,支持PCI-Ex4總線,速度比較快

B&M型接口的固態硬盤兼容性好,兩種M.2的插槽都能用,性能和B key差不多。

PCI-E接口:一般這個插槽是給顯卡用的,但也可以用它來插固態硬盤,這個長得跟顯卡一樣的固態硬盤也是 PCI-E ×4的接口,支持PCI-E ×4的總線。但是現在的主板大多數是沒有PCIE-4的插槽的。所以一般都是接在顯卡的插槽里使用的。

4KB隨機讀寫:固態硬盤雖然順序讀寫速度超快,但是那是只有在讀寫一整個大文件(如一部電影)時才能體會到它的優勢,而影響我們日常使用的是硬盤的4KB 隨機讀寫速度

排名

固態硬盤推薦:

  • 120GB裝個系統,裝幾個日常軟件、裝一個大型遊戲就差不多滿了,建議首選240GB容量。
  • 新配電腦的用戶建議選擇M.2接口的硬盤,因為這是未來的主流。老電腦升級的用戶要檢查主板是否有M.2插槽。
  • 4k讀寫性能才是影響日常體驗的重中之重,購買前需要重點關注。 

電源

  電源的選擇是最容易被我們忽略,又非常重要的一項,我們的核心部件如:CPU、固態硬盤、內存條都是由額定電壓的。如果如果供電不穩定忽高忽低,很容易造成元器件的損壞與老化。理想情況下,供電12V就是12V,但是市面上幾乎沒有一款真正能做到0偏差,intel CPU對電壓的要求是偏差不超過$\pm5%$,電源的好壞和品牌沒有絕對的關係,即便是品牌好的牌子如海盜船,也依然有差的電源,如:VS系列。

功率的選擇

直流輸出中+12V的,是給CPU和顯卡供電的電壓和電流。最好買單路12V的,因為雙路12V的會限制CPU和顯卡的使用功率,單路的直接給他們,誰需求多誰就取得多。買電源的時候,+12V的直流電源功率大於顯卡和CPU的額定功率就不會出問題。

80Plus認證

80Plus認證是一個電源轉換率標準,轉化率越高,也就越省電。就是說,如果你的電源額定功率是500W的白牌電源(滿載轉化率為80%),那麼當你的電源滿載時,你家電錶實際用電為:500÷80%=625W

注意:80plus認證僅僅是轉化率標準,不能直接反應電源的好壞,所以這個指標在購買時僅作為參考就行了。

電源用料:日系電容>台系電容>國產電容(大概排名,不絕對)

模組選擇

非模組:非模組電源價格便宜,但是全部線材固定,多餘線頭不能拆卸,理線稍微麻煩一些。

半模組:比非模組貴一點,重要線材固定,部分線材可拆卸,可以去除多餘線材。

全模組:價格最貴,全部線材可拆卸,美觀,方便理線。

電源尺寸

一般來說選擇ATX的電源就夠了,SFX尺寸的電源一般用在MINI型機箱上面。

關於品牌

振華(super flower):台企,振華電源有戰碟、金蝶、金蝶GX、Leadex這幾個系列,建議從金蝶開始買起,

海韻:台企,海韻的電源清一色日系電容,用料很不錯。

酷冷至尊:台企,全日系電容,全模組,性價比高,穩定性很好。

美商海盜船:美企,海盜船的電源一定要買高端的,也就是800元起步,低端的VS系列價格高,配置低,臭名在外;

EVGA:美企,EVGA的電源500元以下的都不建議買,性價比太低了。高端的如G、P、T系列都是全日系電容,主流的LLC半橋+ DC-DC 結構。質保7-10年,性價比稍低。

訊景(XFX):訊景低端的XT2電源(價格在300左右)不建議買,因為雖然價格不低,但是電源方案很落後,高端點的XTR、XTS系列價格在500元以上,方案主流,可以購買,但性價比稍低。

台達:台企,台達是世界最大的電源生產商,主要是做服務器電源和高端專業的電源解決方案,在零售商做的不是很多。有幾款在售的電源如NX系列和VX靜音王系列價格便宜,全日系電容用料很不錯,很耐用。

航嘉:深圳企業,國內老牌電源廠商,型號多,價格從100-1000都有。航嘉的電源是國內做工非常好的,低端電源也比國內雜牌好的很多,安全有保障,300元以內還是推薦買的,超過300的還是建議去買酷媽和振華

鑫谷:東莞企業,國內老牌電源廠商,比較推薦的是GP 白金版,使用日系和台系電容,而且是白金牌認證,電壓波形很穩,價格也很便宜,性價比很高

注意事項:

  1. 不要不捨得為電源花錢,多花100塊錢有時就能拯救你好幾塊兒机械硬盤。
  2. 功率選擇不要看廠家標多少瓦,主要看+12V,把顯卡和CPU滿載功耗加起來還有幾十瓦余量就夠了。
  3. 80PLUS金銀銅牌認證僅代表電源轉化效率,高轉化率更加省電,但是不能完全評判電源的好壞。
  4. 建議購買使用 LLC半橋+ DC-DC方案,使用日系、台系電容的電源。
  5. 盡量購買大品牌的電源,雖然貴一點點,安全有保障,不至於翻車燒壞主板。

:航嘉電源功率計算器 輸入你要買的主板、CPU、顯卡型號 它就會自動計算出你電源的待機功率和最大功耗,然後推薦出適合你的電源型號

散熱器

  如果電腦散熱不良,CPU的溫度過高,CPU為了保護自己不被燒壞,首先會自動降低頻率來減少發熱,這會導致電腦性能下降,其次降頻之後如果溫度還是過高,CPU就會自動觸發電腦死機來保護自己。所以保證良好的散熱還是很有必要的。

散熱器的工作原理

傳熱底座與CPU緊密接觸,通過導熱裝置,將CPU產生的熱量傳導至散熱鰭片,然後由風扇吹走鰭片上的熱量。

導熱裝置有三種

純銅(純鋁)導熱:這種方式導熱效率比較低,但是結構簡單,價格便宜,很多原裝散熱器都是這種方式

導熱銅管:這是現在最常用的方式,它的銅管是中空的,裏面注有一種導熱液,溫度升高時銅管底部的液體蒸發吸收熱量,將熱量傳遞給散熱鰭片后溫度降低凝結成液體,流回銅管底部,如此循環,導熱效率很高。所以現在的大部分散熱器都是這種方式

水冷散熱:嚴格來說它並不是水,是一種導熱率很高的液體。它是通過水將CPU的熱量帶走,然後高溫的水在通過曲曲折折的冷排(結構跟家裡的暖氣片差不多)的時候被風扇吹走熱量,變為涼水再次循環。

影響散熱效果的因素(風冷)

熱量傳遞的效率

熱量的傳遞效率是散熱的關鍵,影響熱量傳遞效率的因素有以下4點。

熱管的數量以及粗細

熱管根數越多越好,一般2根湊合,4根夠用,6根及以上就是高端散熱器了;熱管越粗越好(大部分為6mm,也有8mm的)。

傳熱底座的工藝:

熱管直觸:這種方案的底座非常普遍,一般的百元及以下散熱器都是這種的。這種方案為了保證與CPU接觸面的平整,會把銅管拍扁、打磨,這使得本來就很薄的銅管更薄了,時間久了就會出現凹凸不平的現象,影響導熱效率。正規大廠都會把銅管打磨的非常平整,這樣就與CPU的接觸面積更大,導熱效率高。一些山寨廠家的銅管凹凸不平,導致有些銅管工作時根本接觸不到CPU,所以再多銅管也只是花架子。

銅底焊接(鏡面打磨):這種方案的底座價格稍微貴一些,因為把傳熱底座直接做成鏡面,接觸面積更高,導熱效果也更好。所以中高端的風冷散熱器都是用這種方案。

均熱板:這是一種很少很少見到的方案,原理類似於熱管,也是通過液體遇熱蒸發,然後遇冷液化來傳遞熱量,這種方案導熱均勻效率高,但是成本很高,所以很少見。

導熱硅脂

  由於製造工藝問題,散熱器底座與CPU之間不可能有完全平整的接觸面(即使你看上去很平整,但是在放大鏡下是能看到凹凸不平的),所以就需要塗一層導熱係數較高的硅脂來填補這些凹凸不平的地方幫助導熱。硅脂的導熱係數比銅低很多,所以只要均勻的塗上薄薄的一層就好了,如果塗太厚,反倒影響散熱了。一般好的硅脂的導熱係數在5-8之間,也有非常昂貴的導熱係數在10-15。

散熱鰭片與熱管交接處的工藝

熱管是穿插在鰭片之間的,要把熱量傳遞到鰭片上,所以他們交接地方的處理工藝也會影響導熱性,目前的處理工藝有兩種

①迴流焊:顧名思義就是將兩者焊接到一起。這種方案成本較高,但是導熱性能好,而且很牢固,不容易出現鰭片鬆動的現象。

②穿fin:也叫“穿片”工藝。顧名思義就是在鰭片上開孔,然後藉助外力將導熱銅管插進鰭片里。這種工藝成本較低,雖然簡單,但是要想做好卻並不容易,因為要考慮接觸不良、鰭片鬆動等問題(如果你隨手一撥,鰭片就在熱管上滑動,導熱效果可想而知了)。

鰭片與空氣的接觸面積大小

鰭片承擔著散熱的重任,它的任務是將熱管送來的熱量散發到空氣中,所以鰭片必須盡可能多的與空氣進行接觸,有些廠家會細心的設計一些凸點來盡可能大的增加鰭片的表面積。

風量

風量表示每分鐘風扇能送出風的總體積,一般用CFM表示。風量越大,散熱也就越好。關於風扇的參數還有:轉速、風壓、扇恭弘=叶 恭弘尺寸、噪音等。現在大多數風扇都支持PWM智能調速,我們需要關注的也就是風量、噪音等

風冷散熱器的類型

風冷散熱器有三種類型:被動式散熱(無風扇設計)、塔式、下壓式。

1、被動式散熱:它其實就是一個無風扇版的散熱器,全靠空氣流通帶走鰭片上的熱量。

  • 優點:完全沒有噪音。
  • 缺點:散熱性能差,適合發熱量特別小的平台(我們的手機幾乎都是被動式散熱,甚至不如被動式散熱)。

2、下壓式散熱:這種散熱器風扇是朝下吹的,所以在兼顧CPU散熱的同時也可以惠顧到主板和內存條散熱。但是散熱效果稍差,而且會擾亂機箱風道,所以適合發熱小的平台,同時由於體積小不佔空間,所以是小機箱的福音。

3、塔式散熱:這種散熱器高高聳立如高塔一般,故名塔式散熱。這種散熱器單向吹風,不會擾亂風道,而且鰭片和風扇可以做的比較大,因此散熱性能最好。但是不能兼顧主板和內存散熱,因此需要機箱上的風扇輔助才行。

關於品牌

貓頭鷹:來自奧地利的品牌,貓頭鷹主要以靜音風扇久負盛名。貓頭鷹的散熱器最主要的特點是靜音,而且設計絕對沒有光污染,堅持使用迴流焊工藝,但是價格昂貴。如果只是追求靜音,可以買稍微便宜的型號,但如果還想要一個不錯的散熱效果,那就只能放血了。

大鐮刀:日本品牌,鐮刀的散熱器做工優秀,設計很人性化,扭曲式銅管設計可以避開內存條,所以不擋內存條

利民:中國台灣品牌,利民的散熱器主要還是面對高端的超頻用戶

九州風神:國內品牌,九州風神最出名的就是玄冰400和大塔霜了。但是99元的玄冰400不推薦,因為特別難安裝。229元的6熱管的大塔霜性價比非常高,但是可能會出現擋一根內存的情況。

酷冷至尊:台灣品牌,129元的T400,4熱管,安裝方便,比玄冰400要好,適合絕大多數人的裝機需求

ID-COOLING:深圳品牌,產品設計很棒,而且性價比高主力產品是下壓式散熱器,ITX機箱用戶可以選擇199元的IS-VC45,採用均熱板設計,厚度僅為45mm;159元的is-60,採用6熱管設計,也是一款性價比很高的產品。

超頻三:深圳品牌,超頻三的散熱器性價比很高,最出名的就是紅海mini了,不到40元的售價,2熱管設計,不玩遊戲的話足夠了。東海X5和東海X6也都是很有性價比的選擇。

總結:

  1. 雖然熱管數量很重要,但是吸熱底座做工必須要足夠好才行,如果做工不平整,再多的熱管都沒用,因此散熱器拼的更多是做工。
  2. 普通的i3、i5後面不帶K的CPU和銳龍不帶X的CPU,如i3 8100、i5 8500、銳龍1500 1600等選擇4熱管的散熱器就足夠了,比如99元的大鐮刀STB120或雙風扇的STB120 plus都能輕鬆應對。
  3. i3 i5 i7後面帶K的CPU和銳龍帶X的CPU,如i3-8350k、i5 8600k、i7 8700K、銳龍1600X等選擇6熱管就可以了,如大鐮刀的千石船、九州風神的大塔霜、利民的TS140P等。
  4. 選擇散熱器的時候一定要注意:①機箱限高和散熱器的大小;②擋不擋內存條;③支不支持你的主板平台(大部分散熱器都支持英特爾和AMD多平台)。
  5. 硅脂也很重要,盡量選擇導熱係數在5以上的硅脂。這東西很便宜,確是散熱很重要的一環,均勻的塗上薄薄的一層就可以了。
  6. 不要迷信水冷。風冷才是最安全,最具性價比的選擇。

显示器

不要以為显示器不重要,显示器總結影響用戶體驗呀,

主要參數:色域(能显示的色彩範圍)、色深(bit)(色彩的精細程度)、色差(色彩還原的準確性)、對比度(對比度越高,越清晰)、分辨率(表示圖像的清晰程度)、刷新率Hz(表示显示器1秒能刷新多少幀)、灰階響應時間(畫面延遲)

显示器接口

DVI:只支持視頻

DVI接口有兩個標準,25針和29針,如下圖所示。直觀來說,這兩種接口沒有區別。DVI接口傳輸的是数字信號,可以傳輸大分辨率的視頻信號。DVI連接計算機顯卡和显示器時不用發生轉換,所以信號沒有損失

VGA:只支持視頻

針數為15的視頻接口,主要用於老式的電腦輸出。VGA輸出和傳遞的是模擬信號。大家都知道計算機顯卡產生的是数字信號,显示器使用的也是数字信號。所以使用VGA的視頻接口相當於是經歷了一個數模轉換和一次模數轉換。信號損失,显示較為模糊

HDMI接口 支持音視頻

HDMI既能傳輸高清圖形畫面信號,也能夠傳輸音頻信號,一般來說家裡會接電視,而且抗干擾強。如數碼相機的體積小,需要小的接口,可以使用micro HDMI。

 

DP接口 支持音視頻

DP即DisplayPort,是一種高清数字显示接口標準,可以連接電腦和显示器,也可以連接連接電腦和家庭影院。DP接口可以理解是HDMI的加強版,在音頻和視頻傳輸方面更加強悍。目前情況下,DP與HDMI在性能上沒有多大區別。如果你使用3840*2160分辨率(4K),HDMI由於帶寬不足,最大隻能傳送30幀,DP就沒有問題。

 

VGA和DVI互轉:模擬信號和数字信號的轉換,視頻信號損失,造成失真。最好不要這樣轉換。

DVI和HDMI互轉:都是数字信號,轉換不會發生是真。可以轉換。但是從HDMI轉換成DVI時會自動捨去音頻信號。

液晶面板

TN面板

TN面板的優點是:液晶分子偏轉速度非常快,所以灰階響應時間很短缺點是:色域窄,色彩差,畫面色彩蒼白,可視角度很小,有條件的可以用手機屏幕對比一下老式的便宜的筆記本電腦屏幕。這種屏幕本來快被市場淘汰了,但是隨着電競的火熱,TN面板藉著刷新率高,灰階響應時間短的優點又重新回到市場,散發第二春。

IPS面板

IPS面板的優點:色彩显示效果好,可視角度大,色彩准。缺點是:容易漏光,黑色不夠純正。這類显示器由於色彩好,可視角度大,所以也是現在應用最廣的显示器面板。

VA面板

VA面板有兩種:MVA面板和PVA面板,PVA是三星改良的MVA面板。這類面板算是TN面板和IPS的折中方案,優點是色彩準確,對比度高,可視角度較大,漏光少,黑色純正。缺點是響應時間比IPS還要長。

PLS面板

PLS面板是三星獨家研製的面板,類似IPS面板

帶魚屏

帶魚屏指屏幕比列為21:9或以上的显示器,特點是非常的長,跟帶魚一樣,所以被調侃為“帶魚屏”。這種屏幕由於較長,所以一屏能显示更多的內容。

優點:

1.多開網頁或者軟件、遊戲時,同屏能显示更多的內容,因此很適合用來工作

2.如果能找到21:9的電影片源,看電影會非常爽。(這種片源很少

3.支持市面主流網絡和單機遊戲。LOL和絕地求生、cs go等都能有效擴寬左右視野

缺點:

1.由於直播、電視劇、綜藝節目等片源大多是傳統的16:9的,所以看這些內容,屏幕兩邊會有很寬的黑邊。

2.由於分辨率高,所以玩遊戲時對顯卡要求更高

3.雖然視野變寬了,但是左右看時需要左右扭頭,累脖子,適合單機遊戲娛樂,不適合電競。

所以,對於有多開需求的用戶來說,帶魚屏或許是個能提高效率的選擇。

總結:

  1. 144hz/1ms的電競显示器對CF、CS GO、絕地求生、守望先鋒等PFS射擊遊戲來說,效果區別非常大,是那種用了之後眼睛就再也受不了60hz显示器的那種,當然前提是你的顯卡得支持這麼高的幀數
  2. 一兩千塊錢的144hz/1ms電競显示器都是TN面板的屏幕,色彩會很差,四五千的會好一些,但色彩依然比不過2千塊的IPS。
  3. IPS屏幕色彩很好,可視角度也很大,但漏光是IPS的通病,你買到的显示器漏光嚴重與否,很大程度看運氣。而且輕微漏光日常使用看不出區別,所以不用太糾結。
  4. 普通玩家選擇IPS或者三星VA面板的显示器就夠了,不是PFS射擊類遊戲玩家不用盲目追求144hz/1ms,效果不明顯
  5. 帶魚屏細長,能同時显示多個窗口,對於多開多任務需求的用戶來說幫助很大。遊戲時雖然能提供更寬的視野,但是需要左右扭頭,累脖子,適合單機遊戲娛樂,不適合電競

機箱

散熱器的選擇也應該挑有品牌保障的產品,切勿貪圖便宜,品牌產品不僅有售後保障,而且在設計時為了品牌發展也會下較大的成本,而非品牌產品則更加註重於散熱器的外觀,實際的散熱性能會因為壓縮成本而大打折扣。

搭配選擇

家用:對配置要求不高,看電影、上上網之類的,選擇入門級CPU和顯卡即可

打遊戲:看遊戲此配置情況選擇顯卡,遊戲對顯卡要求特別高,同時對CPU也有一定的要求。

辦公:辦公時常需要多線程切換,因此選擇一款多線程處理能力好的CPU,

特殊領域:比如工程繪圖或者圖形渲染,深度學習等等,建議CPU和顯卡都買比較好的

參考來自中關村:

推薦兩個個自助裝機的網站和,用戶可以根據自己需要選擇配置,而且會標出價格,配完之後可以在線諮詢客服,更方面用戶進行選擇。

 

 

如果因為資金原因,那麼哪些電腦配件可以選擇二手的,哪些不能呢?

可以買二手的電腦硬件部分

處理器(CPU):處理器做工非常精細,一般都是在機箱內部,多層維護,輕易是不會損壞的,除非是人為把針腳搞壞或者是其它損壞,如果CPU正常點亮的話,買二手的話,是可以的。

內存條:內存條也是如此,做工都是非常精細的,也是在機箱內部,不是人為損壞的話,除非你用力掰扯損壞或者是其它損壞。一般情況下正常用,買二手的都是沒有問題的。

機箱:機箱對電腦的整體性能不大,只要能保證完整,可以用,那麼機箱可以考慮買二手的。

显示器:显示器新的和舊的差別不太大,显示器保證正常,買二手的對於使用者來說完全沒有問題。

不可以買二手的電腦硬件部分

顯卡(GPU):顯卡的性能和使用的程度是密切相關的,尤其是別人挖礦的顯卡,長時間負載,元器件極易容易老化,影響較大,所以顯卡最好買新的。

主板:主板上的電容 ,接口,線路較多,很容易損壞,舊的很容易老化有問題,建議買新的。

硬盤:無論是机械硬盤還是固態硬盤,它們都有一定的使用壽命與做擦寫次數的問題,建議大家硬盤要買新的。

電源:電源是維持電腦穩定工作的必備之一,二手電源會有一定的老化損耗,電壓容易不穩定,建議買新的而且要買額定功率大的電源,用來保證電腦正常工作。

散熱器:二手的很多都是沒有經過保養的,二手散熱器經過長時間的使用會有磨損,使用時有明顯的噪音,散熱對電腦散熱方面也是一個不可忽視的部分,所以建議大家散熱,也要買新的。散熱器的選擇也應該挑有品牌保障的產品,切勿貪圖便宜,品牌產品不僅有售後保障,而且在設計時為了品牌發展也會下較大的成本,而非品牌產品則更加註重於散熱器的外觀,實際的散熱性能會因為壓縮成本而大打折扣。

參考

硬件知識

嗶哩嗶哩 古宇胡:

嗶哩嗶哩 顯卡吧DIY電腦團:

B站UP主“”的專欄(強烈建議大家可以關注他,很多內容轉載他的文章,對本文幫助很大)

J.Feng 博客園博客——

組裝電腦

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

SpringBoot 源碼解析 (二)—– Spring Boot精髓:啟動流程源碼分析

8{icon} {views}

本文從源代碼的角度來看看Spring Boot的啟動過程到底是怎麼樣的,為何以往紛繁複雜的配置到如今可以這麼簡便。

入口類

@SpringBootApplication public class HelloWorldMainApplication {

    public static void main(String[] args) {
        SpringApplication.run(HelloWorldMainApplication.class, args);
    }
    
}

@SpringBootApplication我們上一篇文章中大概的講過了,有興趣的可以看看我第一篇關於SpringBoot的文章,本篇文章主要關注SpringApplication.run(HelloWorldMainApplication.class, args);,我們跟進去看看

// 調用靜態類,參數對應的就是HelloWorldMainApplication.class以及main方法中的args
public static ConfigurableApplicationContext run(Class<?> primarySource,String... args) {
    return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
    return (new SpringApplication(sources)).run(args);
}

它實際上會構造一個SpringApplication的實例,並把我們的啟動類HelloWorldMainApplication.class作為參數傳進去,然後運行它的run方法

SpringApplication構造器

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    //把HelloWorldMainApplication.class設置為屬性存儲起來
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); //設置應用類型是Standard還是Web
    this.webApplicationType = deduceWebApplicationType(); //設置初始化器(Initializer),最後會調用這些初始化器
    setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); //設置監聽器(Listener)
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass();
}

先將HelloWorldMainApplication.class存儲在this.primarySources屬性中

設置應用類型

private WebApplicationType deduceWebApplicationType() {
    if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
            && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
        return WebApplicationType.REACTIVE;
    }
    for (String className : WEB_ENVIRONMENT_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }
    return WebApplicationType.SERVLET;
}

// 相關常量
private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
        + "web.reactive.DispatcherHandler";
private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."
        + "web.servlet.DispatcherServlet";
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
        "org.springframework.web.context.ConfigurableWebApplicationContext" };

這裏主要是通過類加載器判斷REACTIVE相關的Class是否存在,如果不存在,則web環境即為SERVLET類型。這裏設置好web環境類型,在後面會根據類型初始化對應環境。大家還記得我們第一篇文章中引入的依賴嗎?

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

spring-boot-starter-web 的pom又會引入Tomcat和spring-webmvc,如下

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webmvc</artifactId>
  <version>5.0.5.RELEASE</version>
  <scope>compile</scope>
</dependency>

 我們來看看spring-webmvc這個jar包

很明顯spring-webmvc中存在DispatcherServlet這個類,也就是我們以前SpringMvc的核心Servlet,通過類加載能加載DispatcherServlet這個類,那麼我們的應用類型自然就是WebApplicationType.SERVLET

public enum WebApplicationType {
    NONE,
  SERVLET,
    REACTIVE;

    private WebApplicationType() {
    }
}

設置初始化器(Initializer)

//設置初始化器(Initializer),最後會調用這些初始化器
setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class));

我們先來看看getSpringFactoriesInstances( ApplicationContextInitializer.class)

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    return getSpringFactoriesInstances(type, new Class<?>[] {});
}

// 這裏的入參type就是ApplicationContextInitializer.class
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    // 使用Set保存names來避免重複元素
    Set<String> names = new LinkedHashSet<>(
            SpringFactoriesLoader.loadFactoryNames(type, classLoader)); // 根據names來進行實例化
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); // 對實例進行排序
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

這裏面首先會根據入參type讀取所有的names(是一個String集合),然後根據這個集合來完成對應的實例化操作:

// 入參就是ApplicationContextInitializer.class
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
  String factoryClassName = factoryClass.getName();

  try {
      //從類路徑的META-INF/spring.factories中加載所有默認的自動配置類
      Enumeration<URL> urls = classLoader != null?classLoader.getResources("META-INF/spring.factories"):ClassLoader.getSystemResources("META-INF/spring.factories");
      ArrayList result = new ArrayList();

      while(urls.hasMoreElements()) {
          URL url = (URL)urls.nextElement();
          Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
          //獲取ApplicationContextInitializer.class的所有值
          String factoryClassNames = properties.getProperty(factoryClassName);
          result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
      }

      return result;
  } catch (IOException var8) {
      throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
  }
}

這個方法會嘗試從類路徑的META-INF/spring.factories處讀取相應配置文件,然後進行遍歷,讀取配置文件中Key為:org.springframework.context.ApplicationContextInitializer的value。以spring-boot-autoconfigure這個包為例,它的META-INF/spring.factories部分定義如下所示:

org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer

這兩個類名會被讀取出來,然後放入到Set<String>集合中,準備開始下面的實例化操作:

// parameterTypes: 上一步得到的names集合
private <T> List<T> createSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
        Set<String> names) {
    List<T> instances = new ArrayList<T>(names.size());
    for (String name : names) {
        try {
            Class<?> instanceClass = ClassUtils.forName(name, classLoader);
            //確認被加載類是ApplicationContextInitializer的子類
 Assert.isAssignable(type, instanceClass);
            Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
            //反射實例化對象
            T instance = (T) BeanUtils.instantiateClass(constructor, args); //加入List集合中
 instances.add(instance);
        }
        catch (Throwable ex) {
            throw new IllegalArgumentException(
                    "Cannot instantiate " + type + " : " + name, ex);
        }
    }
    return instances;
}

確認被加載的類確實是org.springframework.context.ApplicationContextInitializer的子類,然後就是得到構造器進行初始化,最後放入到實例列表中。

因此,所謂的初始化器就是org.springframework.context.ApplicationContextInitializer的實現類,這個接口是這樣定義的:

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

    void initialize(C applicationContext);

}

在Spring上下文被刷新之前進行初始化的操作。典型地比如在Web應用中,註冊Property Sources或者是激活Profiles。Property Sources比較好理解,就是配置文件。Profiles是Spring為了在不同環境下(如DEV,TEST,PRODUCTION等),加載不同的配置項而抽象出來的一個實體。

設置監聽器(Listener)

下面開始設置監聽器:

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

我們還是跟進代碼看看getSpringFactoriesInstances

// 這裏的入參type是:org.springframework.context.ApplicationListener.class
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {
    return getSpringFactoriesInstances(type, new Class<?>[] {});
}

private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    // Use names and ensure unique to protect against duplicates
    Set<String> names = new LinkedHashSet<String>(
            SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
            classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

可以發現,這個加載相應的類名,然後完成實例化的過程和上面在設置初始化器時如出一轍,同樣,還是以spring-boot-autoconfigure這個包中的spring.factories為例,看看相應的Key-Value:

org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

這10個監聽器會貫穿springBoot整個生命周期。至此,對於SpringApplication實例的初始化過程就結束了。

SpringApplication.run方法

完成了SpringApplication實例化,下面開始調用run方法:

public ConfigurableApplicationContext run(String... args) {
    // 計時工具
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();

    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();

    configureHeadlessProperty();

    // 第一步:獲取並啟動監聽器
    SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

        // 第二步:根據SpringApplicationRunListeners以及參數來準備環境
        ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
        configureIgnoreBeanInfo(environment);

        // 準備Banner打印器 - 就是啟動Spring Boot的時候打印在console上的ASCII藝術字體
        Banner printedBanner = printBanner(environment);

        // 第三步:創建Spring容器
        context = createApplicationContext();

        exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);

        // 第四步:Spring容器前置處理
 prepareContext(context, environment, listeners, applicationArguments,printedBanner); // 第五步:刷新容器
 refreshContext(context);
    
// 第六步:Spring容器後置處理 afterRefresh(context, applicationArguments);     // 第七步:發出結束執行的事件 listeners.started(context); // 第八步:執行Runners this.callRunners(context, applicationArguments); stopWatch.stop(); // 返回容器 return context; } catch (Throwable ex) { handleRunFailure(context, listeners, exceptionReporters, ex); throw new IllegalStateException(ex); } }
  • 第一步:獲取並啟動監聽器
  • 第二步:根據SpringApplicationRunListeners以及參數來準備環境
  • 第三步:創建Spring容器
  • 第四步:Spring容器前置處理
  • 第五步:刷新容器
  • 第六步:Spring容器後置處理
  • 第七步:發出結束執行的事件
  • 第八步:執行Runners

 下面具體分析。

第一步:獲取並啟動監聽器

獲取監聽器

跟進getRunListeners方法:

private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

這裏仍然利用了getSpringFactoriesInstances方法來獲取實例,大家可以看看前面的這個方法分析,從META-INF/spring.factories中讀取Key為org.springframework.boot.SpringApplicationRunListener的Values:

org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

getSpringFactoriesInstances中反射獲取實例時會觸發EventPublishingRunListener的構造函數,我們來看看EventPublishingRunListener的構造函數:

 1 public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
 2     private final SpringApplication application;
 3     private final String[] args;
 4     //廣播器
 5     private final SimpleApplicationEventMulticaster initialMulticaster;  6 
 7     public EventPublishingRunListener(SpringApplication application, String[] args) {
 8         this.application = application;
 9         this.args = args;
10         this.initialMulticaster = new SimpleApplicationEventMulticaster();
11         Iterator var3 = application.getListeners().iterator(); 12 
13         while(var3.hasNext()) {
14             ApplicationListener<?> listener = (ApplicationListener)var3.next();
15             //將上面設置到SpringApplication的十一個監聽器全部添加到SimpleApplicationEventMulticaster這個廣播器中
16             this.initialMulticaster.addApplicationListener(listener); 17         }
18 
19     }
20     //略...
21 }

我們看到EventPublishingRunListener裏面有一個廣播器,EventPublishingRunListener 的構造方法將SpringApplication的十一個監聽器全部添加到SimpleApplicationEventMulticaster這個廣播器中,我們來看看是如何添加到廣播器:

 1 public abstract class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {
 2     //廣播器的父類中存放保存監聽器的內部內
 3     private final AbstractApplicationEventMulticaster.ListenerRetriever defaultRetriever = new AbstractApplicationEventMulticaster.ListenerRetriever(false);
 4 
 5     @Override
 6     public void addApplicationListener(ApplicationListener<?> listener) {
 7         synchronized (this.retrievalMutex) {
 8             Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
 9             if (singletonTarget instanceof ApplicationListener) {
10                 this.defaultRetriever.applicationListeners.remove(singletonTarget);
11             }
12             //內部類對象
13             this.defaultRetriever.applicationListeners.add(listener); 14             this.retrieverCache.clear();
15         }
16     }
17 
18     private class ListenerRetriever {
19         //保存所有的監聽器
20         public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet(); 21         public final Set<String> applicationListenerBeans = new LinkedHashSet();
22         private final boolean preFiltered;
23 
24         public ListenerRetriever(boolean preFiltered) {
25             this.preFiltered = preFiltered;
26         }
27 
28         public Collection<ApplicationListener<?>> getApplicationListeners() {
29             LinkedList<ApplicationListener<?>> allListeners = new LinkedList();
30             Iterator var2 = this.applicationListeners.iterator();
31 
32             while(var2.hasNext()) {
33                 ApplicationListener<?> listener = (ApplicationListener)var2.next();
34                 allListeners.add(listener);
35             }
36 
37             if (!this.applicationListenerBeans.isEmpty()) {
38                 BeanFactory beanFactory = AbstractApplicationEventMulticaster.this.getBeanFactory();
39                 Iterator var8 = this.applicationListenerBeans.iterator();
40 
41                 while(var8.hasNext()) {
42                     String listenerBeanName = (String)var8.next();
43 
44                     try {
45                         ApplicationListener<?> listenerx = (ApplicationListener)beanFactory.getBean(listenerBeanName, ApplicationListener.class);
46                         if (this.preFiltered || !allListeners.contains(listenerx)) {
47                             allListeners.add(listenerx);
48                         }
49                     } catch (NoSuchBeanDefinitionException var6) {
50                         ;
51                     }
52                 }
53             }
54 
55             AnnotationAwareOrderComparator.sort(allListeners);
56             return allListeners;
57         }
58     }
59     //略...
60 }

上述方法定義在SimpleApplicationEventMulticaster父類AbstractApplicationEventMulticaster中。關鍵代碼為this.defaultRetriever.applicationListeners.add(listener);,這是一個內部類,用來保存所有的監聽器。也就是在這一步,將spring.factories中的監聽器傳遞到SimpleApplicationEventMulticaster中。我們現在知道EventPublishingRunListener中有一個廣播器SimpleApplicationEventMulticaster,SimpleApplicationEventMulticaster廣播器中又存放所有的監聽器。

啟動監聽器

我們上面一步通過getRunListeners方法獲取的監聽器為EventPublishingRunListener,從名字可以看出是啟動事件發布監聽器,主要用來發布啟動事件。

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
    private final SpringApplication application;
    private final String[] args;
    private final SimpleApplicationEventMulticaster initialMulticaster;

我們先來看看SpringApplicationRunListener這個接口

package org.springframework.boot;
public interface SpringApplicationRunListener {

    // 在run()方法開始執行時,該方法就立即被調用,可用於在初始化最早期時做一些工作
    void starting();
    // 當environment構建完成,ApplicationContext創建之前,該方法被調用
    void environmentPrepared(ConfigurableEnvironment environment);
    // 當ApplicationContext構建完成時,該方法被調用
    void contextPrepared(ConfigurableApplicationContext context);
    // 在ApplicationContext完成加載,但沒有被刷新前,該方法被調用
    void contextLoaded(ConfigurableApplicationContext context);
    // 在ApplicationContext刷新並啟動后,CommandLineRunners和ApplicationRunner未被調用前,該方法被調用
    void started(ConfigurableApplicationContext context);
    // 在run()方法執行完成前該方法被調用
    void running(ConfigurableApplicationContext context);
    // 當應用運行出錯時該方法被調用
    void failed(ConfigurableApplicationContext context, Throwable exception);
}

SpringApplicationRunListener接口在Spring Boot 啟動初始化的過程中各種狀態時執行,我們也可以添加自己的監聽器,在SpringBoot初始化時監聽事件執行自定義邏輯,我們先來看看SpringBoot啟動時第一個啟動事件listeners.starting():

@Override
public void starting() {
    //關鍵代碼,先創建application啟動事件`ApplicationStartingEvent`
    this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}

這裏先創建了一個啟動事件ApplicationStartingEvent,我們繼續跟進SimpleApplicationEventMulticaster,有個核心方法:

@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    //通過事件類型ApplicationStartingEvent獲取對應的監聽器
    for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) { //獲取線程池,如果為空則同步處理。這裏線程池為空,還未沒初始化。
        Executor executor = getTaskExecutor();
        if (executor != null) {
            //異步發送事件
            executor.execute(() -> invokeListener(listener, event));
        }
        else {
            //同步發送事件
 invokeListener(listener, event);
        }
    }
}

這裡會根據事件類型ApplicationStartingEvent獲取對應的監聽器,在容器啟動之後執行響應的動作,有如下4種監聽器:

我們選擇springBoot 的日誌監聽器來進行講解,核心代碼如下:

@Override
public void onApplicationEvent(ApplicationEvent event) {
    //在springboot啟動的時候
    if (event instanceof ApplicationStartedEvent) {
    onApplicationStartedEvent((ApplicationStartedEvent) event); } //springboot的Environment環境準備完成的時候
    else if (event instanceof ApplicationEnvironmentPreparedEvent) {
        onApplicationEnvironmentPreparedEvent(
                (ApplicationEnvironmentPreparedEvent) event);
    }
    //在springboot容器的環境設置完成以後
    else if (event instanceof ApplicationPreparedEvent) {
        onApplicationPreparedEvent((ApplicationPreparedEvent) event);
    }
    //容器關閉的時候
    else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event)
            .getApplicationContext().getParent() == null) {
        onContextClosedEvent();
    }
    //容器啟動失敗的時候
    else if (event instanceof ApplicationFailedEvent) {
        onApplicationFailedEvent();
    }
}

因為我們的事件類型為ApplicationEvent,所以會執行onApplicationStartedEvent((ApplicationStartedEvent) event);。springBoot會在運行過程中的不同階段,發送各種事件,來執行對應監聽器的對應方法。

第二步:環境構建

ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);

跟進去該方法:

private ConfigurableEnvironment prepareEnvironment(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    //獲取對應的ConfigurableEnvironment
    ConfigurableEnvironment environment = getOrCreateEnvironment(); //配置
 configureEnvironment(environment, applicationArguments.getSourceArgs()); //發布環境已準備事件,這是第二次發布事件
 listeners.environmentPrepared(environment);
    bindToSpringApplication(environment);
    ConfigurationPropertySources.attach(environment);
    return environment;
}

來看一下getOrCreateEnvironment()方法,前面已經提到,environment已經被設置了servlet類型,所以這裏創建的是環境對象是StandardServletEnvironment

private ConfigurableEnvironment getOrCreateEnvironment() {
    if (this.environment != null) {
        return this.environment;
    }
    if (this.webApplicationType == WebApplicationType.SERVLET) { return new StandardServletEnvironment(); } return new StandardEnvironment();
}

接下來看一下listeners.environmentPrepared(environment);,上面已經提到了,這裡是第二次發布事件。什麼事件呢?來看一下根據事件類型獲取到的監聽器:

主要來看一下ConfigFileApplicationListener,該監聽器非常核心,主要用來處理項目配置。項目中的 properties 和yml文件都是其內部類所加載。具體來看一下:

首先還是會去讀spring.factories 文件,List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();獲取的處理類有以下四種:

# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor

在執行完上述三個監聽器流程后,ConfigFileApplicationListener會執行該類本身的邏輯。由其內部類Loader加載項目制定路徑下的配置文件:

private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";

至此,項目的變量配置已全部加載完畢,來一起看一下:

這裏一共6個配置文件,取值順序由上到下。也就是說前面的配置變量會覆蓋後面同名的配置變量。項目配置變量的時候需要注意這點。

第三步:創建容器

context = createApplicationContext();

繼續跟進該方法:

public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch (this.webApplicationType) {
            case SERVLET:
                contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
                break;
            case REACTIVE:
                contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                break;
            default:
                contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
            }
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable create a default ApplicationContext, "
                            + "please specify an ApplicationContextClass",
                    ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

這裏創建容器的類型 還是根據webApplicationType進行判斷的,該類型為SERVLET類型,所以會通過反射裝載對應的字節碼,也就是AnnotationConfigServletWebServerApplicationContext

第四步:Spring容器前置處理

這一步主要是在容器刷新之前的準備動作。包含一個非常關鍵的操作:將啟動類注入容器,為後續開啟自動化配置奠定基礎。

prepareContext(context, environment, listeners, applicationArguments,printedBanner);

繼續跟進該方法:

private void prepareContext(ConfigurableApplicationContext context,
        ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments, Banner printedBanner) {
    //設置容器環境,包括各種變量
 context.setEnvironment(environment); //執行容器後置處理
    postProcessApplicationContext(context);
    //執行容器中的ApplicationContextInitializer(包括 spring.factories和自定義的實例)
 applyInitializers(context);   //發送容器已經準備好的事件,通知各監聽器
 listeners.contextPrepared(context); //註冊啟動參數bean,這裏將容器指定的參數封裝成bean,注入容器
    context.getBeanFactory().registerSingleton("springApplicationArguments",
            applicationArguments);
    //設置banner
    if (printedBanner != null) {
        context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
    }
    //獲取我們的啟動類指定的參數,可以是多個
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    //加載我們的啟動類,將啟動類注入容器
    load(context, sources.toArray(new Object[0])); //發布容器已加載事件。
    listeners.contextLoaded(context);
}

調用初始化器

protected void applyInitializers(ConfigurableApplicationContext context) {
    // 1. 從SpringApplication類中的initializers集合獲取所有的ApplicationContextInitializer
    for (ApplicationContextInitializer initializer : getInitializers()) { // 2. 循環調用ApplicationContextInitializer中的initialize方法
        Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
                initializer.getClass(), ApplicationContextInitializer.class);
        Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
       initializer.initialize(context);
    }
}

這裏終於用到了在創建SpringApplication實例時設置的初始化器了,依次對它們進行遍歷,並調用initialize方法。我們也可以自定義初始化器,並實現initialize方法,然後放入META-INF/spring.factories配置文件中Key為:org.springframework.context.ApplicationContextInitializer的value中,這裏我們自定義的初始化器就會被調用,是我們項目初始化的一種方式

加載啟動指定類(重點)

大家先回到文章最開始看看,在創建SpringApplication實例時,先將HelloWorldMainApplication.class存儲在this.primarySources屬性中,現在就是用到這個屬性的時候了,我們來看看getAllSources()

public Set<Object> getAllSources() {
    Set<Object> allSources = new LinkedHashSet();
    if (!CollectionUtils.isEmpty(this.primarySources)) {
        //獲取primarySources屬性,也就是之前存儲的HelloWorldMainApplication.class
        allSources.addAll(this.primarySources);
    }

    if (!CollectionUtils.isEmpty(this.sources)) {
        allSources.addAll(this.sources);
    }

    return Collections.unmodifiableSet(allSources);
}

很明顯,獲取了this.primarySources屬性,也就是我們的啟動類HelloWorldMainApplication.class,我們接着看load(context, sources.toArray(new Object[0]));

protected void load(ApplicationContext context, Object[] sources) {
    BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
    if (this.beanNameGenerator != null) {
        loader.setBeanNameGenerator(this.beanNameGenerator);
    }
    if (this.resourceLoader != null) {
        loader.setResourceLoader(this.resourceLoader);
    }
    if (this.environment != null) {
        loader.setEnvironment(this.environment);
    }
    loader.load();
}

private int load(Class<?> source) {
    if (isGroovyPresent()
            && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
        // Any GroovyLoaders added in beans{} DSL can contribute beans here
        GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source,
                GroovyBeanDefinitionSource.class);
        load(loader);
    }
    if (isComponent(source)) {
        //以註解的方式,將啟動類bean信息存入beanDefinitionMap,也就是將HelloWorldMainApplication.class存入了beanDefinitionMap
        this.annotatedReader.register(source); return 1;
    }
    return 0;
}

啟動類HelloWorldMainApplication.class被加載到 beanDefinitionMap中,後續該啟動類將作為開啟自動化配置的入口,後面一篇文章我會詳細的分析,啟動類是如何加載,以及自動化配置開啟的詳細流程。

通知監聽器,容器已準備就緒

listeners.contextLoaded(context);

主還是針對一些日誌等監聽器的響應處理。

第五步:刷新容器

執行到這裏,springBoot相關的處理工作已經結束,接下的工作就交給了spring。我們來看看refreshContext(context);

protected void refresh(ApplicationContext applicationContext) {
    Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    //調用創建的容器applicationContext中的refresh()方法
    ((AbstractApplicationContext)applicationContext).refresh();
}
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        /**
         * 刷新上下文環境
         */
        prepareRefresh();

        /**
         * 初始化BeanFactory,解析XML,相當於之前的XmlBeanFactory的操作,
         */
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        /**
         * 為上下文準備BeanFactory,即對BeanFactory的各種功能進行填充,如常用的註解@Autowired @Qualifier等
         * 添加ApplicationContextAwareProcessor處理器
         * 在依賴注入忽略實現*Aware的接口,如EnvironmentAware、ApplicationEventPublisherAware等
         * 註冊依賴,如一個bean的屬性中含有ApplicationEventPublisher(beanFactory),則會將beanFactory的實例注入進去
         */
        prepareBeanFactory(beanFactory);

        try {
            /**
             * 提供子類覆蓋的額外處理,即子類處理自定義的BeanFactoryPostProcess
             */
            postProcessBeanFactory(beanFactory);

            /**
             * 激活各種BeanFactory處理器,包括BeanDefinitionRegistryBeanFactoryPostProcessor和普通的BeanFactoryPostProcessor
             * 執行對應的postProcessBeanDefinitionRegistry方法 和  postProcessBeanFactory方法
             */
            invokeBeanFactoryPostProcessors(beanFactory);

            /**
             * 註冊攔截Bean創建的Bean處理器,即註冊BeanPostProcessor,不是BeanFactoryPostProcessor,注意兩者的區別
             * 注意,這裏僅僅是註冊,並不會執行對應的方法,將在bean的實例化時執行對應的方法
             */
            registerBeanPostProcessors(beanFactory);

            /**
             * 初始化上下文中的資源文件,如國際化文件的處理等
             */
            initMessageSource();

            /**
             * 初始化上下文事件廣播器,並放入applicatioEventMulticaster,如ApplicationEventPublisher
             */
            initApplicationEventMulticaster();

            /**
             * 給子類擴展初始化其他Bean
             */
            onRefresh();

            /**
             * 在所有bean中查找listener bean,然後註冊到廣播器中
             */
            registerListeners();

            /**
             * 設置轉換器
             * 註冊一個默認的屬性值解析器
             * 凍結所有的bean定義,說明註冊的bean定義將不能被修改或進一步的處理
             * 初始化剩餘的非惰性的bean,即初始化非延遲加載的bean
             */
            finishBeanFactoryInitialization(beanFactory);

            /**
             * 通過spring的事件發布機制發布ContextRefreshedEvent事件,以保證對應的監聽器做進一步的處理
             * 即對那種在spring啟動后需要處理的一些類,這些類實現了ApplicationListener<ContextRefreshedEvent>,
             * 這裏就是要觸發這些類的執行(執行onApplicationEvent方法)
             * 另外,spring的內置Event有ContextClosedEvent、ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、RequestHandleEvent
             * 完成初始化,通知生命周期處理器lifeCycleProcessor刷新過程,同時發出ContextRefreshEvent通知其他人
             */
            finishRefresh();
        }

        finally {
    
            resetCommonCaches();
        }
    }
}

refresh方法在spring整個源碼體系中舉足輕重,是實現 ioc 和 aop的關鍵。我之前也有文章分析過這個過程,大家可以去看看

第六步:Spring容器後置處理

protected void afterRefresh(ConfigurableApplicationContext context,
        ApplicationArguments args) {
}

擴展接口,設計模式中的模板方法,默認為空實現。如果有自定義需求,可以重寫該方法。比如打印一些啟動結束log,或者一些其它後置處理。

第七步:發出結束執行的事件

public void started(ConfigurableApplicationContext context) {
    //這裏就是獲取的EventPublishingRunListener
    Iterator var2 = this.listeners.iterator();

    while(var2.hasNext()) {
        SpringApplicationRunListener listener = (SpringApplicationRunListener)var2.next();
        //執行EventPublishingRunListener的started方法
 listener.started(context);
    }
}

public void started(ConfigurableApplicationContext context) {
    //創建ApplicationStartedEvent事件,並且發布事件 //我們看到是執行的ConfigurableApplicationContext這個容器的publishEvent方法,和前面的starting是不同的
    context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
}

獲取EventPublishingRunListener監聽器,並執行其started方法,並且將創建的Spring容器傳進去了,創建一個ApplicationStartedEvent事件,並執行ConfigurableApplicationContext 的publishEvent方法,也就是說這裡是在Spring容器中發布事件,並不是在SpringApplication中發布事件,和前面的starting是不同的,前面的starting是直接向SpringApplication中的11個監聽器發布啟動事件。

第八步:執行Runners

我們再來看看最後一步callRunners(context, applicationArguments);

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Object> runners = new ArrayList<Object>();
    //獲取容器中所有的ApplicationRunner的Bean實例
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values()); //獲取容器中所有的CommandLineRunner的Bean實例
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    AnnotationAwareOrderComparator.sort(runners);
    for (Object runner : new LinkedHashSet<Object>(runners)) {
        if (runner instanceof ApplicationRunner) {
            //執行ApplicationRunner的run方法
 callRunner((ApplicationRunner) runner, args);
        }
        if (runner instanceof CommandLineRunner) {
            //執行CommandLineRunner的run方法
 callRunner((CommandLineRunner) runner, args);
        }
    }
}

如果是ApplicationRunner的話,則執行如下代碼:

private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
    try {
      runner.run(args);
    } catch (Exception var4) {
        throw new IllegalStateException("Failed to execute ApplicationRunner", var4);
    }
}

如果是CommandLineRunner的話,則執行如下代碼:

private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
    try {
        runner.run(args.getSourceArgs());
    } catch (Exception var4) {
        throw new IllegalStateException("Failed to execute CommandLineRunner", var4);
    }
}

我們也可以自定義一些ApplicationRunner或者CommandLineRunner,實現其run方法,並注入到Spring容器中,在SpringBoot啟動完成后,會執行所有的runner的run方法

至此,SpringApplication大概分析了一遍,還有很多細節和核心留在下面文章中講。

 

 

 

 

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

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

※評比南投搬家公司費用收費行情懶人包大公開

006.Kubernetes二進制部署ETCD

6{icon} {views}

一 部署ETCD集群

1.1 安裝ETCD


etcd 是基於 Raft 的分佈式 key-value 存儲系統,由 CoreOS 開發,常用於服務發現、共享配置以及併發控制(如 leader 選舉、分佈式鎖等)。kubernetes 使用 etcd 存儲所有運行數據。

  1 etcd 是基於 Raft 的分佈式 key-value 存儲系統,由 CoreOS 開發,常用於服務發現、共享配置以及併發控制(如 leader 選舉、分佈式鎖等)。kubernetes 使用 etcd 存儲所有運行數據。
  2 [root@k8smaster01 ~]# cd /opt/k8s/work
  3 [root@k8smaster01 work]# wget https://github.com/coreos/etcd/releases/download/v3.3.13/etcd-v3.3.13-linux-amd64.tar.gz
  4 [root@k8smaster01 work]# tar -xvf etcd-v3.3.13-linux-amd64.tar.gz


1.2 分發ETCD

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for master_ip in ${MASTER_IPS[@]}
  4   do
  5     echo ">>> ${master_ip}"
  6     scp etcd-v3.3.13-linux-amd64/etcd* root@${master_ip}:/opt/k8s/bin
  7     ssh root@${master_ip} "chmod +x /opt/k8s/bin/*"
  8   done


1.3 創建etcd證書和密鑰

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# cat > etcd-csr.json <<EOF
  3 {
  4     "CN": "etcd",
  5     "hosts": [
  6     "127.0.0.1",
  7     "172.24.8.71",
  8     "172.24.8.72",
  9     "172.24.8.73"
 10   ],
 11     "key": {
 12         "algo": "rsa",
 13         "size": 2048
 14     },
 15     "names": [
 16         {
 17             "C": "CN",
 18             "ST": "Shanghai",
 19             "L": "Shanghai",
 20             "O": "k8s",
 21             "OU": "System"
 22         }
 23     ]
 24 }
 25 EOF
 26 #創建etcd的CA證書請求文件



解釋:

hosts 字段指定授權使用該證書的 etcd 節點 IP 或域名列表,需要將 etcd 集群的三個節點 IP 都列在其中。

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# cfssl gencert -ca=/opt/k8s/work/ca.pem \
  3 -ca-key=/opt/k8s/work/ca-key.pem -config=/opt/k8s/work/ca-config.json \
  4 -profile=kubernetes etcd-csr.json | cfssljson -bare etcd	#生成CA密鑰(ca-key.pem)和證書(ca.pem)


1.4 分發證書和私鑰

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for master_ip in ${MASTER_IPS[@]}
  4   do
  5     echo ">>> ${master_ip}"
  6     ssh root@${master_ip} "mkdir -p /etc/etcd/cert"
  7     scp etcd*.pem root@${master_ip}:/etc/etcd/cert/
  8   done


1.5 創建etcd的systemd

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# cat > etcd.service.template <<EOF
  4 [Unit]
  5 Description=Etcd Server
  6 After=network.target
  7 After=network-online.target
  8 Wants=network-online.target
  9 Documentation=https://github.com/coreos
 10 
 11 [Service]
 12 Type=notify
 13 WorkingDirectory=${ETCD_DATA_DIR}
 14 ExecStart=/opt/k8s/bin/etcd \\
 15   --data-dir=${ETCD_DATA_DIR} \\
 16   --wal-dir=${ETCD_WAL_DIR} \\
 17   --name=##MASTER_NAME## \\
 18   --cert-file=/etc/etcd/cert/etcd.pem \\
 19   --key-file=/etc/etcd/cert/etcd-key.pem \\
 20   --trusted-ca-file=/etc/kubernetes/cert/ca.pem \\
 21   --peer-cert-file=/etc/etcd/cert/etcd.pem \\
 22   --peer-key-file=/etc/etcd/cert/etcd-key.pem \\
 23   --peer-trusted-ca-file=/etc/kubernetes/cert/ca.pem \\
 24   --peer-client-cert-auth \\
 25   --client-cert-auth \\
 26   --listen-peer-urls=https://##MASTER_IP##:2380 \\
 27   --initial-advertise-peer-urls=https://##MASTER_IP##:2380 \\
 28   --listen-client-urls=https://##MASTER_IP##:2379,http://127.0.0.1:2379 \\
 29   --advertise-client-urls=https://##MASTER_IP##:2379 \\
 30   --initial-cluster-token=etcd-cluster-0 \\
 31   --initial-cluster=${ETCD_NODES} \\
 32   --initial-cluster-state=new \\
 33   --auto-compaction-mode=periodic \\
 34   --auto-compaction-retention=1 \\
 35   --max-request-bytes=33554432 \\
 36   --quota-backend-bytes=6442450944 \\
 37   --heartbeat-interval=250 \\
 38   --election-timeout=2000
 39 Restart=on-failure
 40 RestartSec=5
 41 LimitNOFILE=65536
 42 
 43 [Install]
 44 WantedBy=multi-user.target
 45 EOF



解釋:

WorkingDirectory、–data-dir:指定工作目錄和數據目錄為 ${ETCD_DATA_DIR},需在啟動服務前創建這個目錄;

–wal-dir:指定 wal 目錄,為了提高性能,一般使用 SSD 或者和 –data-dir 不同的磁盤;

–name:指定節點名稱,當 –initial-cluster-state 值為 new 時,–name 的參數值必須位於 –initial-cluster 列表中;

–cert-file、–key-file:etcd server 與 client 通信時使用的證書和私鑰;

–trusted-ca-file:簽名 client 證書的 CA 證書,用於驗證 client 證書;

–peer-cert-file、–peer-key-file:etcd 與 peer 通信使用的證書和私鑰;

–peer-trusted-ca-file:簽名 peer 證書的 CA 證書,用於驗證 peer 證書。

1.6 修改systemd相應地址

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for (( i=0; i < 3; i++ ))
  4   do
  5     sed -e "s/##MASTER_NAME##/${MASTER_NAMES[i]}/" -e "s/##MASTER_IP##/${MASTER_IPS[i]}/" etcd.service.template > etcd-${MASTER_IPS[i]}.service
  6   done


1.7 分發etcd systemd

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for master_ip in ${MASTER_IPS[@]}
  4   do
  5     echo ">>> ${master_ip}"
  6     scp etcd-${master_ip}.service root@${master_ip}:/etc/systemd/system/etcd.service
  7   done


二 啟動並驗證

2.1 啟動ETCD

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for master_ip in ${MASTER_IPS[@]}
  4   do
  5     echo ">>> ${master_ip}"
  6     ssh root@${master_ip} "mkdir -p ${ETCD_DATA_DIR} ${ETCD_WAL_DIR}"
  7     ssh root@${master_ip} "systemctl daemon-reload && systemctl enable etcd && systemctl restart etcd " &
  8   done


2.2 檢查ETCD啟動

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for master_ip in ${MASTER_IPS[@]}
  4   do
  5     echo ">>> ${master_ip}"
  6     ssh root@${master_ip} "systemctl status etcd|grep Active"
  7   done


2.3 驗證服務狀態

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for master_ip in ${MASTER_IPS[@]}
  4   do
  5     echo ">>> ${master_ip}"
  6     ETCDCTL_API=3 /opt/k8s/bin/etcdctl \
  7     --endpoints=https://${master_ip}:2379 \
  8     --cacert=/etc/kubernetes/cert/ca.pem \
  9     --cert=/etc/etcd/cert/etcd.pem \
 10     --key=/etc/etcd/cert/etcd-key.pem endpoint health
 11   done



2.4 查看ETCD當前leader

  1 [root@k8smaster01 ~]# source /opt/k8s/bin/environment.sh
  2 [root@k8smaster01 ~]# ETCDCTL_API=3 /opt/k8s/bin/etcdctl \
  3   -w table --cacert=/etc/kubernetes/cert/ca.pem \
  4   --cert=/etc/etcd/cert/etcd.pem \
  5   --key=/etc/etcd/cert/etcd-key.pem \
  6   --endpoints=${ETCD_ENDPOINTS} endpoint status




如上所示,當前ETCD集群的leader為172.24.8.71。本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

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

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

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

高德客戶端及引擎技術架構演進與思考

2{icon} {views}

2019杭州雲棲大會上,高德地圖技術團隊向與會者分享了包括視覺與機器智能、路線規劃、場景化/精細化定位、時空數據應用、億級流量架構演進等多個出行技術領域的熱門話題。現場火爆,聽眾反響強烈。我們把其中的優秀演講內容整理成文並陸續發布出來,本文為其中一篇。

阿里巴巴高級無線開發專家宋照春在高德技術專場做了題為《高德客戶端及引擎技術架構演進與思考》的演講,主要分享了高德地圖客戶端技術架構沿着「上漂下沉」、「模塊化、Bundle化」的思路演進所做的一系列架構升級中的經驗和思考。

以下為宋照春演講內容的簡版實錄:

主要分享三個方面的內容:

  • 融合
  • 架構治理
  • 動態化

一、三管齊下 深度融合

高德最初有兩個端,車機版的高德導航,手機版的高德地圖,兩個團隊,一個是2B,一個是2C,分別是汽車業務和手機業務。當時在引擎/技術上,分為離線引擎和在線引擎,但兩個團隊之間交流比較少,各自有自己的研發、產品和測試,而作為一款端上的APP,兩塊業務都需要有地圖渲染、路線規劃、導航以及定位等通用能力。從公司層面看,存在較大的重複建設,整體研發效率較低。

於是我們做了一件事:利用技術手段,打通端上引擎,打造一套能同時支撐多端的APP能力。具體到執行層面,先從A團隊拉一部分人到B團隊一起建設,建設完之後再從B團隊拉到A團隊。在同時支撐好主線業務發展的情況下,通過一年左右時間,完成了引擎上的融合,做到同時支撐手機、車機以及開放平台。

這樣就從引擎的維度,實現了渲染、定位、規劃和引導的統一。具體來說,我們的各大引擎有好多套代碼,好幾個開發團隊,每個團隊有各自的開發方式和開發環境(Linux,Windows,Mac OS)。各種開發環境,工程配置文件大量重複,修改非常繁瑣。

為此,我們通過兩種方法:

1.建立了一套構建系統Abtor,通過一個配置系統實現統一構建,能夠同時支持多個子引擎,在構建集成效率上得到了很大的提升;

2.對基礎庫進行了整體重構,形成了一套涵蓋了文件I/O、KV存儲、多線程框架&異步框架、歸檔、基礎容器等一系列標準能力的基礎庫,同時也做了引擎核心架構的統一。

 

二、架構治理

通過引擎的融合同時支持多端,在研發效率上實現比較大的收益。而通過技術的抓手來實現團隊的融合,對公司發展而言,這其實是更大的收益,團隊融合的意義在於人才拉通和復用,組織效率得到了較大提升。

隨着高德業務的快速發展,業務上持續擴品類,需求量激增,高德地圖從最初的駕車導航,到後來的步行、騎行、摩托車導航等等,App所承載的業務發展非常快,而原有的架構治理模式的問題也逐漸暴露出來。

首先就是App的代碼規模變得特別大。當時一個倉庫達到了10G以上,由此導致的一個典型的問題就是編譯慢,編譯出一次安裝包需要一個小時。伴隨代碼規模的另一個問題是團隊規模快速增長。代碼增長和大團隊并行開發,最終導致合版慢,每次迭代,客戶端合版需要2天。

代碼膨脹導致的架構腐化問題特別突出,所以測試質量以及線上的質量有段時間也比較差。此外,從產品提出需求到上線,平均需要45天,版本迭代周期很長。

為解決以上架構問題,我們採取了三個手段:升級Native基礎組件,搭建Native容器和頁面框架,Bundle化分拆(微應用)。

下面重點介紹下頁面框架和微應用。

頁面框架主要借鑒和融合了Android和iOS的生命期管理機制。從高德地圖App架構看,下層模塊是一套標準地圖,所有上層業務都要基於地圖模塊開發。為確保上層業務低耦合、一致性,我們設計了一個頁面框架。

如上圖,左邊的Activity是Android的系統頁面控制器,右邊的UIViewController是iOS的系統頁面控制器,通過虛線連接比較,我們發現兩端的頁面狀態設計基本相同。

所以,我們在設計自己的頁面框架時沿用了這些系統頁面狀態,同時從命名上也保持一致,這樣可以讓Android和iOS原生開發的同學更容易理解和上手。

我們吸取了雙端各自的優點。比如,Android端頁面有四種啟動模式,但是iOS 端並沒有這些,我們就把Android的四種啟動模式運用到了iOS端;iOS端有Present特性,但是Android端沒有,那麼也把這種特性融合到Android端的頁面框架中;最後,還有一些小設計,比如Android的onResult設計,也可以借鑒融合到iOS端。

此外,我們還做了微應用,所謂微應用,首先是模塊化,就是把大模塊倉庫大模塊拆成一個個小的Bundle,除了實現模塊化,還主要實現以下幾個目標:

粒度:以業務為單位,以業務線為分組

編譯:二進制級別的產物,可獨立編譯、出包時鏈接

依賴:松耦合,以“服務”為導向,不關心模塊歸屬

而Native容器層面,要實現四個核心目標:路由管理、服務管理、UI生命期管理、微應用管理。

通過一年時間的Bundle化改造,高德地圖單端App完成了300多個頁面的建設,拆分了100多個Bundle。

從收益來看,總編譯時間從原來的60分鐘降低到了8分鐘,合版周期從原來的3天降到1天,需求上線周期降到了1個月以內,線上質量和測試質量都得到了極大的提升,崩潰率從萬分之八降低到十萬分之八。

三、動態化

隨着高德地圖業務發展沿着擴品類、在垂直品類做精做細,景區、酒店、銀行商鋪、充電樁等個性化定製需求凸顯,對前端展現提出了更高的要求,對“快速應變”要求也更高了。

實際上,在2015年,高德就開始做動態化。最早的時候業內就有React Native,團隊做了技術調研,發現不能完全滿足業務上的需要,尤其是性能方面。最後我們決定自研一套動態化技術。

具體來說,就是通過一個核心C++引擎,把兩端業務(Android、iOS)用一套JavaScript代碼解決,實現雙端歸一,Android實現業務動態化發布。

架構層面,最下面是高德App核心的地圖引擎,我們在上面搭建了一套動態化應用引擎,通過C++來實現。應用引擎的作用是為了承上啟下,上面承載動態化業務,下層完成地圖引擎的直接打通。眾所周知,GUI的核心是DOM樹,所以應用引擎不但要實現和JavaScript引擎的整合,還要負責DOM樹的核心邏輯計算。

其次,動態化的技術和前端Web技術一致:樣式、布局。應用引擎負責完成樣式的布局計算、DOM樹Diff、事件生成。而GUI的繪製,通過Diff事件,交由原生的Android以及iOS去完成。這樣,所有的GUI都是原生的組件。

在之上,我們搭建了一套前端框架,前端框架採用當前前端響應式框架做,前端框架之上又搭建了一套前端的UI卡片庫和UI組件庫,讓上層業務能夠更高效的開發。

而對於一些通過動態化的技術無法實現,或者性能上存在卡點的功能,我們就通過Native擴展能力來支撐,這樣,完整的動態化的業務能夠直接運行在Android以及iOS上。

 

JS去執行代碼之後,前端框架會產生虛擬的DOM樹,最後提交到C++引擎,形成C++的DOM樹。C++引擎去完成布局、樣式計算,Diff計算,將每個節點的屬性和坐標交給Android以及iOS,由Native來完成最終UI的渲染。

總體來說,動態化的特點:首先是它與主流前端框架融合,充分融合了大前端的生態;第二,性能、擴展性較好。因為採用C++實現整個核心邏輯,靜態和動態的語言綁定技術,能夠保證地圖引擎的能力能夠直接透出到上層,或者從上層能夠直接call底層的C++能力;第三,多端歸一和動態化,充分利用Native優勢,接近原生Native體驗。

動態化技術改造完成之後,雙端不一致的問題降低了90%,開發、測試成本降低30%,發版周期從T+30到T+0。

最後,總結下高德客戶端及引擎技術架構演進的幾個重要階段:第一個階段,通過在線&離線引擎的融合拉通,讓高德最核心的導航能力提到提升;第二階段,在客戶端發展成為“巨型”APP,代碼量發展到超大規模的時候,通過架構治理,滿足業務快速增長的訴求,解決大規模業務體量下的架構合理性問題,消除架構瓶頸;第三個階段通過動態化的技術,實現多端歸一,以及動態發版能力,為業務發展提供更大的助力。

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

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

※帶您來看台北網站建置台北網頁設計,各種案例分享

淺談什麼是動態規劃以及相關的「股票」算法題:一網打盡「買賣股票的最佳時機」

1{icon} {views}

本文首發於公眾號「五分鐘學算法」,是系列文章之一。

個人網站:

動態規劃

1 概念

  動態規劃算法是通過拆分問題,定義問題狀態和狀態之間的關係,使得問題能夠以遞推(或者說分治)的方式去解決。在學習動態規劃之前需要明確掌握幾個重要概念。

  階段:對於一個完整的問題過程,適當的切分為若干個相互聯繫的子問題,每次在求解一個子問題,則對應一個階段,整個問題的求解轉化為按照階段次序去求解。

  狀態:狀態表示每個階段開始時所處的客觀條件,即在求解子問題時的已知條件。狀態描述了研究的問題過程中的狀況。

  決策:決策表示當求解過程處於某一階段的某一狀態時,可以根據當前條件作出不同的選擇,從而確定下一個階段的狀態,這種選擇稱為決策。

  策略:由所有階段的決策組成的決策序列稱為全過程策略,簡稱策略。

  最優策略:在所有的策略中,找到代價最小,性能最優的策略,此策略稱為最優策略。

  狀態轉移方程:狀態轉移方程是確定兩個相鄰階段狀態的演變過程,描述了狀態之間是如何演變的。

2 使用場景

能採用動態規劃求解的問題的一般要具有 3 個性質:

  (1)最優化:如果問題的最優解所包含的子問題的解也是最優的,就稱該問題具有最優子結構,即滿足最優化原理。子問題的局部最優將導致整個問題的全局最優。換句話說,就是問題的一個最優解中一定包含子問題的一個最優解。

  (2)無後效性:即某階段狀態一旦確定,就不受這個狀態以後決策的影響。也就是說,某狀態以後的過程不會影響以前的狀態,只與當前狀態有關,與其他階段的狀態無關,特別是與未發生的階段的狀態無關。

   (3)重疊子問題:即子問題之間是不獨立的,一個子問題在下一階段決策中可能被多次使用到。(該性質並不是動態規劃適用的必要條件,但是如果沒有這條性質,動態規劃算法同其他算法相比就不具備優勢)

3 算法流程

  (1)劃分階段:按照問題的時間或者空間特徵將問題劃分為若干個階段。
  (2)確定狀態以及狀態變量:將問題的不同階段時期的不同狀態描述出來。
  (3)確定決策並寫出狀態轉移方程:根據相鄰兩個階段的各個狀態之間的關係確定決策。
  (4)尋找邊界條件:一般而言,狀態轉移方程是遞推式,必須有一個遞推的邊界條件。
  (5)設計程序,解決問題

實戰練習

下面的三道算法題都是來源於 LeetCode 上與股票買賣相關的問題 ,我們按照 動態規劃 的算法流程來處理該類問題。

股票買賣這一類的問題,都是給一個輸入數組,裏面的每個元素表示的是每天的股價,並且你只能持有一支股票(也就是你必須在再次購買前出售掉之前的股票),一般來說有下面幾種問法:

  • 只能買賣一次
  • 可以買賣無數次
  • 可以買賣 k 次

需要你設計一個算法去獲取最大的利潤。

買賣股票的最佳時機

題目來源於 LeetCode 上第 121 號問題:買賣股票的最佳時機。題目難度為 Easy,目前通過率為 49.4% 。

題目描述

給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。

如果你最多只允許完成一筆交易(即買入和賣出一支股票),設計一個算法來計算你所能獲取的最大利潤。

注意你不能在買入股票前賣出股票。

示例 1:

輸入: [7,1,5,3,6,4]
輸出: 5
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 5 天(股票價格 = 6)的時候賣出,最大利潤 = 6-1 = 5 。
     注意利潤不能是 7-1 = 6, 因為賣出價格需要大於買入價格。

示例 2:

輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種情況下, 沒有交易完成, 所以最大利潤為 0。

題目解析

我們按照動態規劃的思想來思考這道問題。

狀態

買入(buy)賣出(sell) 這兩種狀態。

轉移方程

對於買來說,買之後可以賣出(進入賣狀態),也可以不再進行股票交易(保持買狀態)。

對於賣來說,賣出股票后不在進行股票交易(還在賣狀態)。

只有在手上的錢才算錢,手上的錢購買當天的股票后相當於虧損。也就是說當天買的話意味着損失-prices[i],當天賣的話意味着增加prices[i],當天賣出總的收益就是 buy+prices[i]

所以我們只要考慮當天買和之前買哪個收益更高,當天賣和之前賣哪個收益更高。

  • buy = max(buy, -price[i]) (注意:根據定義 buy 是負數)
  • sell = max(sell, prices[i] + buy)

邊界

第一天 buy = -prices[0], sell = 0,最後返回 sell 即可。

代碼實現

class Solution {
    public int maxProfit(int[] prices) {
        if(prices.length <= 1)
            return 0;
        int buy = -prices[0], sell = 0;
        for(int i = 1; i < prices.length; i++) {
            buy = Math.max(buy, -prices[i]);
            sell = Math.max(sell, prices[i] + buy);

        }
        return sell;
    }
}

買賣股票的最佳時機 II

題目來源於 LeetCode 上第 122 號問題:買賣股票的最佳時機 II。題目難度為 Easy,目前通過率為 53.0% 。

題目描述

給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。

設計一個算法來計算你所能獲取的最大利潤。你可以盡可能地完成更多的交易(多次買賣一支股票)。

注意:你不能同時參与多筆交易(你必須在再次購買前出售掉之前的股票)。

示例 1:

輸入: [7,1,5,3,6,4]
輸出: 7
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 3 天(股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。
     隨後,在第 4 天(股票價格 = 3)的時候買入,在第 5 天(股票價格 = 6)的時候賣出, 這筆交易所能獲得利潤 = 6-3 = 3 。

示例 2:

輸入: [1,2,3,4,5]
輸出: 4
解釋: 在第 1 天(股票價格 = 1)的時候買入,在第 5 天 (股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。
     注意你不能在第 1 天和第 2 天接連購買股票,之後再將它們賣出。
     因為這樣屬於同時參与了多筆交易,你必須在再次購買前出售掉之前的股票。

示例 3:

輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種情況下, 沒有交易完成, 所以最大利潤為 0。

題目解析

狀態

買入(buy)賣出(sell) 這兩種狀態。

轉移方程

對比上題,這裏可以有無限次的買入和賣出,也就是說 買入 狀態之前可擁有 賣出 狀態,所以買入的轉移方程需要變化。

  • buy = max(buy, sell – price[i])
  • sell = max(sell, buy + prices[i] )

邊界

第一天 buy = -prices[0], sell = 0,最後返回 sell 即可。

代碼實現

class Solution {
    public int maxProfit(int[] prices) {
        if(prices.length <= 1)
            return 0;
        int buy = -prices[0], sell = 0;
        for(int i = 1; i < prices.length; i++) {
            sell = Math.max(sell, prices[i] + buy);
            buy = Math.max( buy,sell - prices[i]);
        }
        return sell;
    }
}

買賣股票的最佳時機 III

題目來源於 LeetCode 上第 123 號問題:買賣股票的最佳時機 III。題目難度為 Hard,目前通過率為 36.1% 。

題目描述

給定一個數組,它的第 i 個元素是一支給定的股票在第 i 天的價格。

設計一個算法來計算你所能獲取的最大利潤。你最多可以完成 兩筆 交易。

注意: 你不能同時參与多筆交易(你必須在再次購買前出售掉之前的股票)。

示例 1:

輸入: [3,3,5,0,0,3,1,4]
輸出: 6
解釋: 在第 4 天(股票價格 = 0)的時候買入,在第 6 天(股票價格 = 3)的時候賣出,這筆交易所能獲得利潤 = 3-0 = 3 。
     隨後,在第 7 天(股票價格 = 1)的時候買入,在第 8 天 (股票價格 = 4)的時候賣出,這筆交易所能獲得利潤 = 4-1 = 3 。

示例 2:

輸入: [1,2,3,4,5]
輸出: 4
解釋: 在第 1 天(股票價格 = 1)的時候買入,在第 5 天 (股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。   
     注意你不能在第 1 天和第 2 天接連購買股票,之後再將它們賣出。   
     因為這樣屬於同時參与了多筆交易,你必須在再次購買前出售掉之前的股票。

示例 3:

輸入: [7,6,4,3,1] 
輸出: 0 
解釋: 在這個情況下, 沒有交易完成, 所以最大利潤為 0。

題目解析

這裏限制了最多兩筆交易。

狀態

第一次買入(fstBuy)第一次賣出(fstSell)第二次買入(secBuy)第二次賣出(secSell) 這四種狀態。

轉移方程

這裏最多兩次買入和兩次賣出,也就是說 買入 狀態之前可擁有 賣出 狀態,賣出 狀態之前可擁有 買入 狀態,所以買入和賣出的轉移方程都需要變化。

  • fstBuy = max(fstBuy , -price[i])
  • fstSell = max(fstSell,fstBuy + prices[i] )
  • secBuy = max(secBuy ,fstSell -price[i]) (受第一次賣出狀態的影響)
  • secSell = max(secSell ,secBuy + prices[i] )

邊界

  • 一開始 fstBuy = -prices[0]
  • 買入后直接賣出,fstSell = 0
  • 買入后再賣出再買入,secBuy - prices[0]
  • 買入后再賣出再買入再賣出,secSell = 0

最後返回 secSell 。

代碼實現

class Solution {
    public int maxProfit(int[] prices) {
        int fstBuy = Integer.MIN_VALUE, fstSell = 0;
        int secBuy = Integer.MIN_VALUE, secSell = 0;
        for(int i = 0; i < prices.length; i++) {
            fstBuy = Math.max(fstBuy, -prices[i]);
            fstSell = Math.max(fstSell, fstBuy + prices[i]);
            secBuy = Math.max(secBuy, fstSell -  prices[i]);
            secSell = Math.max(secSell, secBuy +  prices[i]); 
        }
        return secSell;

    }
}

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

【其他文章推薦】

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

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

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

南投搬家前需注意的眉眉角角,別等搬了再說!

從 DevOps 到 Serverless:通過“不用做”的方式解決“如何更高效做”的問題

5{icon} {views}

作者 | 徐進茂(羅離) JAVA 開發工程師 

導讀:近年來,Serverless 一詞越來越熱,它已經逐漸成為了一種新型的軟件設計架構。和 DevOps 概念提倡的是通過一系列工具和自動化的技術來降低運維的難度,促進研發運維一體化不同, Serverless 更像是一種 NoOps,即通過“不用做”的方式來解決“如何更高效做”的問題。

DevOps 概述

DevOps 是一組用於促進開發和運維人員之間協作的過程、方法和系統的統稱。

DevOps 提倡通過一系列的技術和工具降低開發和運維人員之間的隔閡,實現從開發到最終部署的全流程自動化,從而達到開發運維一體化。通過將 DevOps 的理念引入到整個系統的開發過程中,能夠顯著提升軟件的開發效率,縮短軟件交付的周期,更加適應當今快速發展的互聯網時代。

說到 DevOps ,就必然會提到持續集成。持續集成指的是在軟件開發過程中,軟件開發人員持續不斷地將開發出來的代碼和其他的開發人員的代碼進行合併,每次合併后自動地進行編譯、構建,並運行自動化測試進行驗證,而不是等到最後各自開發完成后才合併在一起。

持續集成能從根本上提高一個團隊的軟件開發效率。在軟件開發過程中引入持續集成,可以幫助團隊及時的發現系統中的問題,並快速做出修復,不僅可以縮短軟件開發的時間,而且可以交付更具質量的系統。

基於 Docker 實現一個 DevOps 開發環境

一個 DevOps 開發環境需要滿足以下 8 點需求。

  • 環境一致性:在本地開發出來的功能,無論在什麼環境下部署都應該能得到一致的結果;

  • 代碼自動檢查:為了儘早發現問題,每一次代碼提交后,系統都應該自動對代碼進行檢查,及早發現潛在的問題,並運行自動化測試;

  • 持續集成:每次代碼提交后系統可以自動進行代碼的編譯和打包,無需運維人員手動進行;

  • 持續部署:代碼集成完畢后,系統可以自動將運行環境中的舊版本應用更新成新版本的應用並且整個過程中不會讓系統不可用;

  • 持續反饋:在代碼自動檢查、持續集成、持續部署的過程中,一旦出現問題,要能及時將問題反饋給開發人員以及運維人員。開發和運維人員收到反饋后對問題及時進行修復;

  • 快速回滾:當發現本次部署的版本出現問題時,系統應能快速回退到上一個可用版本;

  • 彈性伸縮:當某個服務訪問量增大時,系統應可以對這個服務快速進行擴容,保證用戶的訪問。當訪問量回歸正常時,系統能將擴容的資源釋放回去,實現根據訪問情況對系統進行彈性伸縮;

  • 可視化運維:提供可視化的頁面,可實時監控應用、集群、硬件的各種狀態。

為了滿足以上 8 點要求,設計出的 DevOps 開發環境如下圖所示。

整個環境主要由 6 部分組成:

  • 代碼倉庫 Gitlab
  • 容器技術 Docker
  • 持續集成工具 Jenkins
  • 代碼質量檢測平台 SonarQube
  • 鏡像倉庫 Harbor
  • 容器集群管理系統 Kubernetes

整個環境的運行流程主要分為以下 6 步:

  • 開發人員在本地開發並驗證好功能后,將代碼提交到代碼倉庫;

  • 通過事先配置好的 Webhook 通知方式,當開發人員提交完代碼后,部署在雲端的持續集成工具 Jenkins 會實時感知,並從代碼倉庫中獲取最新的代碼;

  • 獲取到最新代碼后,Jenkins 會啟動測試平台 SonarQube 對最新的代碼進行代碼檢查以及執行單元測試,執行完成后在 SonarQube 平台上生成測試報告。如果測試沒通過,則以郵件的方式通知研發人員進行修改,終止整個流程。若測試通過,將結果反饋給 Jenkins 並進行下一步;

  • 代碼檢查以及單元測試通過後, Jenkins 會將代碼發送到持續集成服務器中,在服務器上對代碼進行編譯、構建然後打包成能在容器環境上運行的鏡像文件。如果中間有步驟出現問題,則通過郵件的方式通知開發人員和運維人員進行處理,並終止整個流程;

  • 將鏡像文件上傳到私有鏡像倉庫 Harbor 中保存;

  • 鏡像上傳完成后, Jenkins 會啟動持續交付服務器,對雲環境中運行的應用進行版本更新,整個更新過程會確保服務的訪問不中斷。持續交付服務器會將最新的鏡像文件拉取到 Kubernetes 集群中,並採用逐步替換容器的方式進行對應用進行更新,在服務不中斷的前提下完成更新。

通過上述幾步,我們就可以簡單實現一個 DevOps 開發環境,實現代碼從提交到最終部署的全流程自動化。

但是自從 2014 年 AWS 發布 ASW Lambda 以來, Serverless 的概念開始逐漸火熱起來。各大雲廠商開始紛紛開始推出各自的 Serverless 產品,如 Google 的 Cloud Functions ,阿里雲的函數計算、Serverless應用引擎(SAE)等等。究竟什麼是Serverless 無服務計算呢?

什麼是 Serverless?

根據 CNCF (雲原生計算基金會)發布的 白皮書里的定義:

Serverless computing refers to the concept of building and running applications that do not require server management. It describes a finer-grained deployment model where applications, bundled as one or more functions, are uploaded to a platform and then executed, scaled, and billed in response to the exact demand needed at the moment.

首先需要強調一點的是無服務器計算並不意味着我們不再需要使用服務器來運行代碼,代碼仍需要運行在服務器上對外提供服務。

在無服務計算時代,研發人員無需對服務器進行監控、配置、更新、擴容等運維操作。只需要將代碼上傳到雲廠商提供的無服務器計算平台上即可,雲廠商會保證代碼能正常運行,當流量突增時,自動對服務器進行擴容,流量減少時,對服務器進行縮容。

這些運維操作對研發人員來說都是黑盒的,會將開發人員從繁瑣的運維工作中解放出來,只需要按運行時長對資源進行付費即可。

和 DevOps 概念提倡的是通過一系列工具和自動化的技術來降低運維的難度,促進研發運維一體化不同, Serverless 更像是一種 NoOps,即通過“不用做”的方式來解決“如何更高效做”的問題。

阿里雲在 Serverless 上的實踐

當前阿里雲上實現 Serverless 技術的產品有 Serverless 應用引擎和函數計算 FaaS。

Serverless 應用引擎

Serverless 應用引擎是面嚮應用的 Serverless PaaS 平台,它向上抽象了應用的概念,支持 Spring Cloud、Apache Dubbo、HSF 等流行的開發框架,並通過 WAR 包、JAR 包和鏡像等多種方式部署應用。它的使用可以通過下面這張圖來了解。

函數計算

FaaS 是 Serverless 所提供的服務的另一種形態。以阿里雲函數計算為例,阿里雲函數計算的流程大致如下圖所示。

  • 開發者在本地編寫代碼;

  • 代碼開發完成后通過命令行工具 fcli、fun 或者可視化界面控制台上傳到阿里雲函數計算平台;

  • 開發者上傳完代碼后,平台會自動啟動基於 Docker 的 DevOps 流程,對代碼進行編譯、打包成鏡像文件。並上傳到鏡像倉庫;

  • 開發者在平台是配置事件觸發器,當前阿里雲已經支持 OSS、HTTP、CDN、SLS、定時任務等多種形式的觸發器形式;

  • 當觸發器被觸發后,會到達事件調度器。平台會將鏡像快速啟動成容器並執行代碼,根據流量自動對服務進行彈性伸縮。保證代碼能正常並執行完成。

伯克利對 Serverless 未來的預測

儘管 Serverless 仍存在諸多的挑戰,但是我們相信隨着市場規模的不斷擴大,這些挑戰會逐漸被解決。UC 伯克利對 Serverless 未來十年的發展趨勢做了以下幾點預測。

  • 新型的 BaSS 存儲服務會被創造出來,這樣更多類型的應用可以遷移到 Serverless 平台上。這種存儲服務的性能會和本地存儲的性能相當,並提供長期和短期的存儲。更多適用於 Serverless 平台的硬件會被使用;

  • 由於更高級別的編程抽象以及更加細粒度的資源隔離,在無服務器計算平台上運行的代碼將會比傳統的方式更加安全可靠;

  • 隨着無服務器計算收費模式的不斷髮展,幾乎任何應用遷移到無服務器計算平台都會比原先的有服務器計算的方式的成本更低;

  • 有服務器計算在未來會促進 BaaS 的發展;

  • 雖然現有的有服務器計算不會消失,但是隨着 Serverless r技術的不斷髮展,有服務器計算在雲上所佔的比例會逐年下降;

  • 無服務器計算將會成為雲時代默認的編程方式,它將大規模取代傳統的基於服務器的編程方式,並終結傳統的 C/S 架構。

總結

當前數據中心的資源利用率仍處於一個較低水平,特別是對於在線業務而言,日均資源使用率僅在 10% 左右,主要是由於當今資源都是屬於獨享型的,不管你用不用,這些資源都需要保留。

一旦大規模使用 Serverless 之後,資源的使用由平台統一調度,按需使用,整體的資源利用率會大幅提升,整個雲計算資源的使用成本無疑也會大幅降低。

隨着 Serverless 的不斷髮展,未來編程方式將會有很大的不同。無論是從成本的角度還是使用的角度,我們有理由相信下一個時代是 Serverless 的時代,並應該朝着這個方向不斷探索。

作者簡介:徐進茂(羅離) Java 開發工程師。現就職於阿里雲智能基礎設施事業部,主要負責阿里巴巴數據中心運營平台的研發工作。

“ 阿里巴巴雲原生微信公眾號(ID:Alicloudnative)關注微服務、Serverless、容器、Service Mesh等技術領域、聚焦雲原生流行技術趨勢、雲原生大規模的落地實踐,做最懂雲原生開發者的技術公眾號。”

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

分享幾套加絨蕾絲打底衫的精緻搭配

1{icon} {views}

蕾絲元素可謂是時尚界的寵兒,蕾絲加絨衫在這個微寒天氣裏面十分實用,更適合精緻的女人穿着。但是很多女性不知道蕾絲衫怎樣搭配才好看?今天小編就和大家分享幾套加絨蕾絲打底衫的精緻搭配,希望能帶給你搭配靈感。

黑色氣質蕾絲衫

網紗拼接氣質的木耳邊小立領,搭配大V領的外套,更顯優雅。雙層木耳邊的袖口設計與領口的木耳邊設計,相互映襯,一舉一動都演繹着時尚與優雅與。后領口兩粒扣水滴形門襟設計,穿脫十分方便。

半高領蕾絲加絨衫

領口裝飾花邊,整體觀感特別雍容華貴,凸顯高貴的感覺。緊身的版型,上身展現曼妙的身姿。時尚的設計,體現出你的時尚品味,時尚百搭。

百搭蕾絲衫

氣質蕾絲衫,穿着優雅名媛范。優雅甜美蕾絲邊裝飾,襯托甜美時尚范,打造完美舒適穿着。精緻手工鑲鑽,與華美刺繡領完美結合,兩者搭配,彰顯優雅高貴氣質。

洋氣內搭時尚保暖衣

精緻立領與立體蕾絲勾花,裝扮出時尚優雅風,讓清新淑味為展露無遺。胸部訂珠,高端有檔次,瞬間提升氣質。長袖的設計,簡潔大方,是當今職場女性的選擇,這種袖型設計,有效的遮住手臂的贅肉。

美體蕾絲上衣

打破傳統滾邊立領,拼接的鏤空設計,端莊古典中彰顯幾分女性的柔美。精選唯美水溶蕾絲面料,體現出精緻細膩的質感,整體非常顯瘦修身,提升高貴迷人氣息。

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

【精選推薦文章】

※聽過「電子菸」嗎?想知道與一般傳統香菸有何不同嗎?

電子煙能幫助戒菸嗎?專家學者以健康觀點帶您來了解 !

※新手該如何選擇電子菸口味及濃度呢?

※你應該要知道的電子煙懶人包!

電子煙有爭議?真相解密

※全台最大電子煙交易平台?

張子萱外穿白色的羊羔夾克,然後內搭粉色的針織裙

3{icon} {views}

張子萱是陳赫的妻子,結婚後的她也淡出娛樂圈了,和很多接不到戲的明星相比,張子萱則在家中賣賣衣服挺好的,而且,實話實說,張子萱的衣品是相當不錯呢。是大齡女生們穿搭上的教科書,這次來說下張子萱。

35歲的人怎麼穿出甜美氣息呢?看張子萱外穿白色的羊羔夾克,然後內搭粉色的針織裙,很有少女感,走在街上,大家肯定不知道原來這是一位已經當媽媽的女生呢,所以啊,人的確是得要靠打扮。

馬上就是冬天了,羽絨服也可以安排上了,看張子萱往身上穿了件粉色的長款羽絨服,在此基礎上,又內搭了一條白色的針織裙,簡簡單單的兩件單品就可以美美得過冬天了呢。這不,張子萱又曬照了,看到張子萱又穿出20歲的樣子,穿條紋針織毛衣搭鉛筆褲,可時尚了!來看下。

只見張子萱戴上了一頂牛仔藍的報童帽,在如今,這種復古風格的報童帽其實還是蠻流行的,在造型上給自己的氣質加分不少,另外,她還將自己的頭髮顏色染成了栗子色,頗為顯時髦呢。

穿着上,張子萱先是外穿了一件灰色和紅色撞色的針織毛衣,而且還是條紋風格的,這麼一來的話,這件毛衣也會時髦一點,另外,紅色是比較顯少女感的顏色,使得張子萱一下子就顯年輕了好幾歲呢。

深秋的時候,穿一件針織衫可能會比較冷,所以呢,這個時候還得多加一件外套,看張子萱在針織衫的外面多穿了件卡其色的呢大衣,卡其色的呢大衣可以說是最經典的款式了,很百搭,也很顯氣質。

在搭配的時候,張子萱選擇的褲子是一條藍色的鉛筆褲,我們張子萱的腿很細長,穿上鉛筆褲之後,那真的像是擁有一雙“筷子腿”呢。再接着,腳上也要像年輕人一樣,穿一雙紅色的帆布鞋即可。感覺美極了。

像這身裝扮,包包也一定是要搭配好的,看張子萱選擇穿上的是一個牛皮色的單肩包,不僅顯得很高級,而且很百搭,和張子萱的這套LOOK也是相當搭的,整個一身相當時尚了呢。

好了,張子萱的秋冬新私服就是這樣,其實,拆解開來的話都是一些很簡單的單品,好好打扮一番之後就完全不一樣了,這就是時髦感。那麼,看到張子萱的這一身LOOK你有啥看法?覺得張子萱的衣品好嗎?

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

【精選推薦文章】

※聽過「電子菸」嗎?想知道與一般傳統香菸有何不同嗎?

電子煙能幫助戒菸嗎?專家學者以健康觀點帶您來了解 !

※新手該如何選擇電子菸口味及濃度呢?

※你應該要知道的電子煙懶人包!

電子煙有爭議?真相解密

※全台最大電子煙交易平台?

周末出遊仿羊羔毛假兩件衛衣搭配

3{icon} {views}

1.撞色衛衣

2.星星衛衣

3.仿羊羔毛假兩件衛衣

4.荷恭弘=恭弘=叶 恭弘 恭弘邊上衣

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

【精選推薦文章】

※聽過「電子菸」嗎?想知道與一般傳統香菸有何不同嗎?

電子煙能幫助戒菸嗎?專家學者以健康觀點帶您來了解 !

※新手該如何選擇電子菸口味及濃度呢?

※你應該要知道的電子煙懶人包!

電子煙有爭議?真相解密

※全台最大電子煙交易平台?