前情
每個(gè)月最怕看到云賬單。
好幾臺(tái)2核4G的機(jī)器,CPU常年跑在5%以下,內(nèi)存倒是占了快2個(gè)G,一個(gè)月三四百塊錢就這么白白燒掉。我跟運(yùn)維說(shuō),要不換更便宜的機(jī)型?運(yùn)維說(shuō)換了也差不多,這點(diǎn)負(fù)載,再便宜的機(jī)器也是浪費(fèi)。
后來(lái)我想明白了,問(wèn)題根本不在機(jī)器貴不貴,在于我們養(yǎng)了一堆“閑人”。于是我開(kāi)始帶著團(tuán)隊(duì)做減法,按下面這四步走,難度從低到高,省下的錢卻一次比一次狠。
正文
第一步,先給Java進(jìn)程“節(jié)食”。
很多低流量服務(wù),我們上來(lái)就給它配2G堆內(nèi)存,純屬過(guò)度喂養(yǎng)。我挑了一個(gè)邊緣服務(wù)做實(shí)驗(yàn),直接把-Xmx從2G壓到256M,垃圾回收器換成SerialGC,順手把機(jī)器配置也降成1核1G。壓測(cè)跑了一遍,QPS沒(méi)掉,響應(yīng)時(shí)間也沒(méi)明顯變長(zhǎng)。觀察了一周,一次Full GC都沒(méi)有。
于是我讓團(tuán)隊(duì)把所有類似服務(wù)都照此處理,逐步分批的將機(jī)器配置一降,月費(fèi)直接砍半。這是代價(jià)最小、見(jiàn)效最快的一招,改幾行參數(shù)的事,但大部分人都沒(méi)想過(guò)去做。
第二步,給低流量服務(wù)“找室友”。
公司內(nèi)部的管理后臺(tái)、報(bào)表小工具、某某數(shù)據(jù)同步程序,一天撐死幾十個(gè)請(qǐng)求,居然獨(dú)占一臺(tái)機(jī)器,這跟一個(gè)人住一棟別墅有什么區(qū)別?我把它們?nèi)w到同一臺(tái)8核16G的服務(wù)器上,用Nginx按端口轉(zhuǎn)發(fā),每個(gè)服務(wù)嚴(yán)格限制-Xmx,防止一個(gè)OOM把全宿舍炸了。用systemd做進(jìn)程守護(hù),加了個(gè)簡(jiǎn)單的存活監(jiān)控腳本,完事。
這一輪下來(lái),直接關(guān)掉了五臺(tái)僵尸機(jī),賬單瞬間清爽不少。
第三步,把傳統(tǒng)Jar包搬上Serverless,低峰期不再白燒錢。
前兩步做完,機(jī)器合并得差不多了,但翻翻還有一堆定時(shí)任務(wù)和低頻接口——每天夜里跑幾分鐘的數(shù)據(jù)同步、偶爾被調(diào)一次的webhook、幾個(gè)星期才有人用的內(nèi)部小工具。就為了這點(diǎn)活,還得養(yǎng)著機(jī)器24小時(shí)待命,典型的“交著整租的錢,用著鐘點(diǎn)房的量”。
以前我對(duì)Serverless的理解就是函數(shù)計(jì)算,得把Spring Boot那套全拆了重寫(xiě),想想就勸退。后來(lái)團(tuán)隊(duì)一個(gè)小伙子在技術(shù)群里看到有人分享阿里云的Serverless SAE,說(shuō)現(xiàn)在有些云原生產(chǎn)品能直接跑原生jar包,Spring Boot應(yīng)用不用改造就能享受彈性伸縮,我才知道路子沒(méi)那么窄。
我挑了一個(gè)每天凌晨3點(diǎn)跑的數(shù)據(jù)對(duì)賬程序當(dāng)小白鼠,典型的Spring Boot單體,打成一個(gè)fat jar,原來(lái)獨(dú)占一臺(tái)2核4G的機(jī)器,實(shí)際每天只干5分鐘的活。遷移過(guò)程簡(jiǎn)單得出乎意料——jar包拖上去,選好Java運(yùn)行環(huán)境,配好內(nèi)存規(guī)格,幾分鐘就跑起來(lái)了,一行代碼沒(méi)改。
真正的改進(jìn)在于伸縮策略。以前這臺(tái)機(jī)器24小時(shí)開(kāi)著,白天完全沒(méi)人用,CPU在那兒空轉(zhuǎn)?,F(xiàn)在我把應(yīng)用配置成動(dòng)態(tài)擴(kuò)縮容——高峰期自動(dòng)彈到多個(gè)實(shí)例扛流量,低峰期只保留1個(gè)實(shí)例兜底。別小看這“保留1個(gè)”而不是“縮到零”,這是我踩過(guò)坑才總結(jié)出來(lái)的。縮到零雖然更省錢,但請(qǐng)求來(lái)的時(shí)候冷啟動(dòng)要等好幾秒,偶爾哪個(gè)業(yè)務(wù)方半夜查個(gè)數(shù)據(jù),等半天頁(yè)面打不開(kāi),第二天投訴就來(lái)了。留1個(gè)實(shí)例在那,響應(yīng)速度跟常駐機(jī)器沒(méi)區(qū)別,用戶體驗(yàn)不受影響,但跟原來(lái)開(kāi)好幾臺(tái)機(jī)器相比,成本已經(jīng)降了七八成。
這里有兩個(gè)配置細(xì)節(jié)一定要做好,不然就容易翻車。
一個(gè)是健康檢查
你得確保新彈出的實(shí)例真正就緒了才能接流量。別一啟動(dòng)就往里打請(qǐng)求,結(jié)果Spring容器還沒(méi)初始化完,接口調(diào)了半天返回500,那還不如不彈。我配了Readiness探針,等上下文加載完、數(shù)據(jù)庫(kù)連接池初始化好了,才把流量切過(guò)來(lái)。
另一個(gè)是優(yōu)雅停機(jī)
縮容的時(shí)候別直接把進(jìn)程殺掉,得給正在處理的請(qǐng)求一點(diǎn)時(shí)間收尾。我配了30秒的terminationGracePeriod,收到縮容信號(hào)后先摘掉流量,等手里這批請(qǐng)求處理完再退出,業(yè)務(wù)方完全無(wú)感。
那個(gè)對(duì)賬程序原來(lái)獨(dú)占一臺(tái)機(jī)器,一個(gè)月將近三百塊。換成彈性伸縮后,低峰期只剩1個(gè)小規(guī)格實(shí)例,高峰期自動(dòng)彈,月底一算賬,月費(fèi)掉到了幾十塊。我把類似場(chǎng)景全梳理了一遍,挨個(gè)改造成動(dòng)態(tài)擴(kuò)縮容,原來(lái)養(yǎng)這些“夜間值班員”的固定開(kāi)銷,變成了按實(shí)際用量彈性的成本。
這一步比前兩步門檻略高——需要花時(shí)間分析業(yè)務(wù)流量規(guī)律來(lái)定伸縮閾值,需要把健康檢查和優(yōu)雅停機(jī)配到位。但一旦跑通一個(gè),后面的復(fù)制成本幾乎為零,而且因?yàn)榱袅?個(gè)實(shí)例保底,業(yè)務(wù)方完全感覺(jué)不到變化,你默默把錢省了,零投訴。
第四步,敢把微服務(wù)“合回去”。
這是最需要勇氣的一步。我們有兩個(gè)服務(wù),從來(lái)都是一起改一起發(fā),QPS加起來(lái)還不到10,硬生生拆成了兩個(gè)微服務(wù),調(diào)用鏈路多了一截,排錯(cuò)麻煩了一倍。我跟團(tuán)隊(duì)說(shuō),別撐了,合。
用Maven子模塊把兩個(gè)服務(wù)變成同一個(gè)項(xiàng)目里的不同package,內(nèi)部直接調(diào)用,一下子砍掉了遠(yuǎn)程序列化開(kāi)銷和那套服務(wù)治理組件。不僅省了機(jī)器,還省下了維護(hù)這套分布式系統(tǒng)的心智負(fù)擔(dān),開(kāi)發(fā)效率反而高了。
結(jié)尾
這四步走完,我們?cè)瀑~單降了六成多。有意思的是,系統(tǒng)沒(méi)變慢,穩(wěn)定性也沒(méi)下降,反而因?yàn)榧軜?gòu)變簡(jiǎn)單了,出問(wèn)題的概率更小。
回頭想想,真是驗(yàn)證了一句老話,“合久必分分久必合”。
我們花了很多年把服務(wù)拆開(kāi),又花了不少精力把它們合回去。但這不是倒退,是知道什么東西值得拆,什么東西該老實(shí)待在一起。降本的盡頭不是往云上堆更多花活,而是敢于把不該有的東西“下”掉。