1. 基于B/S架構(gòu)的HTTP協(xié)議三級(jí)緩存設(shè)計(jì)
1.1. 騷話連篇
程序設(shè)計(jì)無(wú)非就是時(shí)間問(wèn)題和空間問(wèn)題,但是現(xiàn)在本質(zhì)已經(jīng)變了現(xiàn)在的軟件工程師已經(jīng)不在乎軟件質(zhì)量以及代碼質(zhì)量,以增加系統(tǒng)復(fù)雜度的來(lái)解決并發(fā)問(wèn)題,軟件變成變成了PPT編程。
其實(shí)大家都知道很多緩存方案 Redis讀寫(xiě)緩存呀,MongoDB文檔緩存呀,網(wǎng)頁(yè)靜態(tài)化呀,,其實(shí)玩來(lái)玩去也就那樣子這么多年了也沒(méi)有什么創(chuàng)新的點(diǎn)子出現(xiàn)。既然大家都知道緩存方案為啥我還要專門寫(xiě)一篇文章去解釋這個(gè)緩存方案設(shè)計(jì)哪,主要原因就是因?yàn)槲覜](méi)有找到這個(gè)方案的輪子,我是真的沒(méi)有輪子,Spring和其他Java社區(qū)都沒(méi)有提供。
難道服務(wù)端就不能去控制緩存嘛?我在思考這個(gè)問(wèn)題,服務(wù)端控制代理服務(wù)器和瀏覽器的緩存,并且做好緩存協(xié)商和緩存過(guò)期,利用好http緩存可以極大的減小服務(wù)端壓力,無(wú)論是網(wǎng)絡(luò)傳輸還是servlet容器對(duì)http的解析。
秉承著百度和Google都不能解決解決你的問(wèn)題甚至沒(méi)有答案,那么恭喜你了,你要自己去解決問(wèn)題并且發(fā)布解決方案了,所以我要造輪子以及寫(xiě)博客了。
1.2. 設(shè)計(jì)初衷
HTTP協(xié)議作為B/S架構(gòu)最通用的協(xié)議,但是大家都沒(méi)有很好的利用HTTP協(xié)議。(中國(guó)開(kāi)發(fā)工程師就那點(diǎn)水平吧,紙上談兵之士,我也是紙上談兵,哈哈哈)
提升網(wǎng)站并發(fā)首先就是要縮短距離和減小體積以加快響應(yīng)(我稱之為“短小快”)
HTTP協(xié)議的緩存策略可以縮短網(wǎng)頁(yè)請(qǐng)求資源的距離,減少延遲,節(jié)省網(wǎng)絡(luò)流量,并且由于緩存文件可以重復(fù)利用,降低網(wǎng)絡(luò)負(fù)荷,加快客戶端響應(yīng)。
其實(shí)一個(gè)高并發(fā)網(wǎng)站的流量95%都是讀流量,寫(xiě)流量甚至都達(dá)不到5%,Java工程師引用Redis將大量數(shù)據(jù)放入Redis來(lái)解決讀流量其實(shí)是錯(cuò)誤甚至是致命的,在重后端輕前端的軟件開(kāi)發(fā)環(huán)境下大家都是優(yōu)先從后端入手去解決讀流量很少?gòu)木W(wǎng)絡(luò)傳輸協(xié)議入手去解決問(wèn)題。運(yùn)維工程師對(duì)于HTT議緩存也僅僅是Nginx對(duì)靜態(tài)資源設(shè)置一個(gè)Cache-Control: max-age=31536000。
其實(shí)運(yùn)維工程師也很難判斷緩存什么時(shí)候過(guò)期,過(guò)期時(shí)間設(shè)置多大,畢竟誰(shuí)知道后端工程師什么時(shí)候更新數(shù)據(jù)吶。(這里就沒(méi)有前端什么事情了,瀏覽器都已經(jīng)維護(hù)好HTTP緩存了)
1.3. 解決思路
哼,感覺(jué)Spring對(duì)HTTP協(xié)議支持太少了(有點(diǎn)郁悶),以至于我覺(jué)得他們是不是故意不開(kāi)源部分代碼用于商業(yè)支持。
以后端方式來(lái)控制HTTP緩存(當(dāng)然不是后端返回?cái)?shù)據(jù)加一個(gè)Cache-Control: max-age=31536000),后端來(lái)決定HTTP緩存的時(shí)間,其中涉及到 HTTP協(xié)議緩存失效機(jī)制、反向代理緩存、后端緩存控制。
HTTP協(xié)議大家很熟悉吧,廢話少說(shuō)寫(xiě)文章打字很累,直接上鏈接吧
https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching
https://web.dev/i18n/en/http-cache/
這兩篇文章看完大概心里對(duì)HTTP緩存就心里有數(shù)了,其目的是盡可能的把數(shù)據(jù)緩存在瀏覽器中減少網(wǎng)絡(luò)請(qǐng)求(用戶的電腦性能不會(huì)那么拉胯的啦)。
畫(huà)個(gè)時(shí)序圖來(lái)看看吧
點(diǎn)擊放大
通過(guò)流程圖就可以發(fā)現(xiàn)只要ETag一直有效,緩存協(xié)商返回304充分利用HTTP緩存就可實(shí)現(xiàn), 至于如何高效的維護(hù)ETag的過(guò)期這里放到第三章手撕框架說(shuō)吧
2. 短小精悍
短小精悍對(duì)標(biāo)膀大腰圓,大家都是玩編程的靠提升系統(tǒng)復(fù)雜度來(lái)體現(xiàn)自己的技術(shù)的人真的沒(méi)有勁(PPT編程那群人啦)。
有點(diǎn)不想寫(xiě)了,有點(diǎn)累了,邊抽煙邊寫(xiě)這些東西很傷身體,寫(xiě)出來(lái)有沒(méi)有人用都不知道。
睡了一夜,起床喝了一杯速溶咖啡接著碼字吧。
說(shuō)一個(gè)微服務(wù)小故事臥槽!Nginx崩了。臥槽!系統(tǒng)崩了。領(lǐng)導(dǎo):在這樣子搞下去不行呀,臉上掛不住呀,把系統(tǒng)拆成微服務(wù)吧,整點(diǎn)高大上的東西進(jìn)去應(yīng)該就不會(huì)崩潰了吧
一個(gè)系統(tǒng)拆成微服務(wù)中。改造中...注冊(cè)中心來(lái)一發(fā),PRC來(lái)一發(fā),gateway來(lái)一發(fā)。。。臥槽!系統(tǒng)還是老樣子崩潰了。
運(yùn)維仔憋出大招喊出了:Nginx雙活來(lái)一發(fā),Spring Cloud Gateway集群搞起來(lái),給我活,活起來(lái)。。。運(yùn)維仔:臥槽!版本升級(jí)好麻煩呀,來(lái)一發(fā)docker順帶一發(fā)k8s。后端仔:臥槽!配置不能同步呀,來(lái)一發(fā)配置中心吧。后端仔:臥槽!這個(gè)接口涉及到多個(gè)服務(wù),來(lái)一發(fā)seata吧。后端仔:臥槽?。?!出bug了看日志好麻煩呀 整一發(fā)ELK吧。
客戶:臥槽!??!升級(jí)微服務(wù)了好牛逼plus呀。臥槽!??!微服務(wù)化了穩(wěn)定上上來(lái)了,這性能怎么這么拉胯呀。后端仔:我給你來(lái)一發(fā)zipkin做性能追蹤看看。
懶得寫(xiě)了,故事就是這樣子啦,現(xiàn)在的性能優(yōu)化就是做加法而不是做減法
一套組合拳下來(lái),發(fā)現(xiàn)什么都沒(méi)有解決甚至加速熵增,軟件開(kāi)發(fā)真有趣
2.1. 網(wǎng)關(guān)之短
來(lái)看看微服務(wù)最流行的網(wǎng)關(guān)設(shè)計(jì)吧
網(wǎng)關(guān)設(shè)計(jì)
其實(shí)LB在中小企業(yè)很少會(huì)用到, 基本上都是傳統(tǒng)網(wǎng)關(guān)模式,進(jìn)階版本看到這種部署方式的就更少了。
一個(gè)請(qǐng)求在進(jìn)入實(shí)際的微服務(wù)之前需要經(jīng)過(guò)兩層網(wǎng)關(guān),Nginx作為流量網(wǎng)關(guān),Spring Cloud Gateway作為業(yè)務(wù)網(wǎng)關(guān),經(jīng)過(guò)兩層網(wǎng)關(guān)之后性能網(wǎng)絡(luò)延遲會(huì)增加5%-10%,這些損耗還沒(méi)有加上一些人會(huì)往Spring Cloud Gateway之中增加一些奇特的功能(報(bào)文校驗(yàn),報(bào)文加解密,token校驗(yàn)。。),以至于Spring Cloud Gateway的性能下降的更厲害以至于要擴(kuò)充Spring Cloud Gateway集群。
心中其實(shí)一直有一個(gè)疑問(wèn),有Spring Cloud Gateway還需要Nginx嘛,雖然Java的性能比不上C但是也沒(méi)有必要用多層網(wǎng)關(guān)設(shè)計(jì)吧。就算上了LB,阿里云那邊其實(shí)也是一臺(tái)Nginx在哪里跑著,進(jìn)階版本和集群版本其實(shí)沒(méi)有多大差別。
19年的時(shí)候就想著Spring Cloud Gateway的性能比不上Nginx,Java天生劣勢(shì),但是Nginx又沒(méi)有微服務(wù)生態(tài),經(jīng)過(guò)一段時(shí)間的苦思冥想后已經(jīng)詢問(wèn)眾多好友后,給我了一個(gè)OpenResty讓我去玩,但是OpenResty并沒(méi)有接入Spring Cloud Alibaba體系之中,而且OpenResty需要寫(xiě)Lua,我不會(huì)Lua呀,最終22年的時(shí)候在和朋友瞎扯淡后拿到了 Apache APISIX。
Apache APISIX官網(wǎng):
https://apisix.apache.org/docs/apisix/getting-started/
性能對(duì)比:
https://baijiahao.baidu.com/s?id=1673615010327758104&wfr=spider&for=pc
經(jīng)過(guò)一段時(shí)間研究Apache APISIX后,發(fā)現(xiàn)Apache APISIX 可以完美替代 Spring Cloud Gateway和Nginx這種多層網(wǎng)關(guān)模式。
Apache APISIX基于Nginx 無(wú)論如何封裝 性能上都可吊打Spring Cloud Gateway。
列舉一下目前用到的幾個(gè)有趣的特性, 更多特性請(qǐng)看官網(wǎng)
動(dòng)態(tài)routes
動(dòng)態(tài)Upstream
服務(wù)發(fā)現(xiàn)
那么網(wǎng)關(guān)升級(jí)一下吧。
流量網(wǎng)關(guān)+業(yè)務(wù)網(wǎng)關(guān)的模式再也不存在了,現(xiàn)在只有一層流量網(wǎng)關(guān),因?yàn)橄薙pring Cloud Gateway的存在 性能上略有提升,性能上面大概會(huì)提升2%-8%吧。
盡量不要讓網(wǎng)關(guān) 去和業(yè)務(wù)扯上關(guān)系,讓網(wǎng)關(guān)做好反向代理和負(fù)載均衡將可 (Spring Cloud Gateway上面一大堆業(yè)務(wù),能不慢嘛)
別小看這種小小的性能提升,失之毫厘差之千里,流量上來(lái)了效果就不一樣了。
2.2. 數(shù)據(jù)之小
上面已經(jīng)把網(wǎng)關(guān)的距離縮短了,如何讓數(shù)據(jù)變得小一點(diǎn)。
其實(shí)從架構(gòu)較多來(lái)說(shuō),JSON和XML這些傳統(tǒng)的通訊格式很通用,換成其他的Kryo,dubbo這些之類的也只是適合服務(wù)和服務(wù)之間傳遞,并不適合前后端之間交互,就是JSON解析框架從Jackson換成了FastJSON(溫少出了FastJSON2)也只是從在解析速度上的提升
后前端其實(shí)沒(méi)有多少可以說(shuō)的,HTTP協(xié)議很通用,JSON通訊格式也方便解析稍微主意一下幾個(gè)問(wèn)題就好了
后端不需要把null值給前端,前端仔自行處理undefined,null,NaN的問(wèn)題
后端仔別一股腦 select * 返回全部數(shù)據(jù)了,和前端確定好字段后定制化的返回所需要的字段
JSON的key其實(shí)壓縮一下,apple可以用a替代,user這個(gè)可以用u來(lái)替代,@JsonProperty這些注解之類的壓縮一下key
數(shù)據(jù)庫(kù)字段盡可能使用小字段,少用寬表
微服務(wù),微服務(wù)嘛看PRC框架了,Spring Cloud Open Feign或者dubbo這些都有自己的壓縮算法的
Spring Boot其實(shí)有g(shù)zip的開(kāi)關(guān),可以考慮開(kāi)起來(lái)壓縮一下response body,但是開(kāi)啟后CPU會(huì)上來(lái),沒(méi)有辦法時(shí)間和空間問(wèn)題。
TCP協(xié)議有三次握手能快到那里吶,UDP可靠性問(wèn)題也是一個(gè)很大的問(wèn)題,其實(shí)有打算使用http2.0的,但是http2.0似乎只是提升了client的性能沒(méi)有提升server性能, 還不如用http緩存方案來(lái)解決這個(gè)問(wèn)題好一點(diǎn)。
http緩存上面介紹過(guò)了,各個(gè)節(jié)點(diǎn)之間的緩存做好緩存和緩存協(xié)商接本上就能解決這些個(gè)問(wèn)題了。
呃呃呃, 上面都是沒(méi)有營(yíng)養(yǎng)的東西,寫(xiě)偏題了(理科生啦沒(méi)有辦法就是文筆差勁)
來(lái)看看如何讓數(shù)據(jù)在變的小一點(diǎn)吧
那么網(wǎng)關(guān)升級(jí)一下吧。
oss是干啥的存儲(chǔ)文件的,前端工程都是靜態(tài)文件,把它扔到oss上面讓流量都走oss哪里好了呀。
剝離了前端工程靜態(tài)文件的流量,你就發(fā)現(xiàn)一件奇特的事情,按照一個(gè)前端工程流量5M來(lái)計(jì)算,網(wǎng)關(guān)的壓力又降低了呀。
2.3. 架構(gòu)之精
每單你引入一個(gè)中間件,在與中間件交互的時(shí)候必定會(huì)帶來(lái)性能損耗
其實(shí)不難發(fā)現(xiàn)現(xiàn)在的Java架構(gòu)生態(tài),架構(gòu)師都是往大了做了引入各種花哨的框架進(jìn)來(lái)。
商品庫(kù)數(shù)量都沒(méi)有達(dá)到百萬(wàn)級(jí)別想著上ES來(lái)提升一下商品檢索,就20臺(tái)服務(wù)器就想著上docker和k8s,為了防止服務(wù)崩潰而防止服務(wù)奔潰。
不談金錢預(yù)算的架構(gòu)師,架構(gòu)的東西真的合理嘛。
架構(gòu)不往大的做就體現(xiàn)不了自己的技術(shù),更有甚者拿公司成本試錯(cuò),加入一些自己不了解的技術(shù)已方便自己吹得高大上。
就按老東家cnool說(shuō)吧(寧波人叫他東方熱線),領(lǐng)導(dǎo)叫我上微服務(wù),我是瘋了么(是的我當(dāng)時(shí)為了工作的確上了微服務(wù)哈哈哈),Nginx,gateway,服務(wù),數(shù)據(jù)庫(kù)雙備等等,10臺(tái)服務(wù)器能干啥呀,20多個(gè)微服務(wù)都不夠分的。
架構(gòu)真正的精髓在于如何設(shè)計(jì)出一個(gè)適合業(yè)務(wù)場(chǎng)景下最優(yōu)的方案(最討厭面試的時(shí)候讓我去哈牛逼的面試官了,完全就不知道他的業(yè)務(wù)場(chǎng)景是什么,上來(lái)就說(shuō)高并發(fā)如何設(shè)計(jì)那種。。。)
架構(gòu)的精髓在于如何用最小的技術(shù)成本做出實(shí)現(xiàn)困難的東西,憑什么說(shuō)只有上了Elasticsearch才能加速商品檢索,難道dbms就不能實(shí)現(xiàn)搜索引擎了嘛,當(dāng)年Google都已經(jīng)用dbsm實(shí)踐出了dbms可以作為搜索引擎的的可能性(人家當(dāng)年都上線用了好幾年吶,只是數(shù)據(jù)量到了PB級(jí)別撐不住了),簡(jiǎn)單的技術(shù)+數(shù)據(jù)結(jié)構(gòu)即可實(shí)現(xiàn)。
電商系統(tǒng)開(kāi)發(fā)呵呵,并發(fā)上來(lái)就阻塞了撐不住了,后端仔也許可以做一次精神小妹呀,來(lái)來(lái)下面來(lái)玩一下
GET?github.com/goods/info 這個(gè)接口很常見(jiàn)吧,獲取商品詳情,網(wǎng)站一半以上的流量都是這個(gè)接口在作妖,下面來(lái)看看如何優(yōu)化啦。 goods/info就是獲取商品詳情嘛,里面的數(shù)據(jù)除了商品數(shù)量,其他的數(shù)據(jù)一年半載基本上都是不會(huì)變化的,所以?數(shù)據(jù)動(dòng)靜分離一下 第一步吶:我們保留原有接口,但是 goods/info 這個(gè)加緩存 GET?github.com/goods/info 第二步吶:增加一個(gè)接口,這個(gè)接口獲取商品數(shù)量,加不加緩存都可以,加了緩存就做好緩存協(xié)商 GET?github.com/goods/quantity 搞定,這樣子info接口都是走緩存了,都是大字段現(xiàn)在都已經(jīng)放在了緩存中基本上都不需要走服務(wù)端了,瀏覽器緩存和網(wǎng)關(guān)緩存基本上就攔截住了。 quantity接口因?yàn)樽兓俣忍炝?,也不知道什么時(shí)候變化,但是都是小字段,傳輸和數(shù)據(jù)庫(kù)檢索基本上沒(méi)有什么壓力。 對(duì)了,數(shù)據(jù)庫(kù)也可以這樣子拆分一下,?goods表弄成兩張,加一張quantity表和goods表做關(guān)聯(lián),完事了世界就是你的了
當(dāng)然上面是對(duì)API進(jìn)行優(yōu)化當(dāng)然還有一個(gè)大殺器,那就是網(wǎng)頁(yè)靜態(tài)化,這個(gè)沒(méi)有專門研究過(guò),針對(duì)app和小程序無(wú)效只能用在web網(wǎng)頁(yè)上面。
2.4. 性能之悍
物理計(jì)算機(jī)性能如何強(qiáng)悍,其實(shí)沒(méi)有沒(méi)有以上的短小精悍都不可能做到真正的強(qiáng)悍的。為什么想要強(qiáng)悍在設(shè)計(jì)及編碼之時(shí)必須摳門,是的摳門。
做過(guò)一個(gè)很有趣的功能,Java對(duì)300M的csv文件進(jìn)行關(guān)鍵字檢索,開(kāi)發(fā)環(huán)境硬件是 MacMini2018頂配,生產(chǎn)環(huán)境是 2C8G的服務(wù)器,開(kāi)發(fā)環(huán)境各種測(cè)試都沒(méi)有問(wèn)題,但是到了生成環(huán)境性能未達(dá)標(biāo),差了20%。
硬盤IO,CPU頻率,內(nèi)存帶寬等等因素造成性能未達(dá)標(biāo),在無(wú)法改變硬件的情況下,只能在low逼硬件上面進(jìn)行設(shè)計(jì),經(jīng)過(guò)多天的設(shè)計(jì)終于彌補(bǔ)那20%,可以在low逼硬件上面性能達(dá)標(biāo)。把數(shù)據(jù)和程序放在開(kāi)發(fā)環(huán)境上面運(yùn)行性能居然提升了50%。
用最拉胯的硬件寫(xiě)出高性能的軟件,這才是性能強(qiáng)悍。
2.5. 短板效應(yīng)
軟件開(kāi)發(fā)的性能問(wèn)題永遠(yuǎn)是短板效應(yīng),IO阻塞,cpu線程競(jìng)爭(zhēng),網(wǎng)卡帶寬等等,性能取決于最low的那個(gè),但是為什么現(xiàn)在硬件如此強(qiáng)度的情況下為什么還是不能提升性能吶,這是一個(gè)很有趣的問(wèn)題。
短板效應(yīng)漸漸的不是出現(xiàn)在硬件上面了,而是代碼,短板效應(yīng)居然是代碼,其次是架構(gòu)。再優(yōu)秀的架構(gòu)都抵不垃圾代碼帶來(lái)的性能損耗。
不注重代碼管理的何須談性能吶,
3. 手撕http緩存框架
感覺(jué)說(shuō)沒(méi)有意思,框架請(qǐng)自取,目前沒(méi)有發(fā)布到中央倉(cāng)庫(kù),應(yīng)該會(huì)等到22年6月7日發(fā)布吧
項(xiàng)目源碼:
https://github.com/galaxy-sea/heifer/tree/main/heifer/heifer-common/heifer-common-http
http-example:
https://github.com/galaxy-sea/heifer/tree/main/heifer/heifer-examples/heifer-common-http-example
在線預(yù)覽,開(kāi)啟F12, 注意看
@GetMapping("cache/{id}") @HttpCacheControl(key?=?"#id",?maxAge?=?10) public?String?getCache(@PathVariable?String?id)?{ ????return?ResponseEntity.ok() ?????????????????????????.body(?new?SimpleDateFormat("yyyy-MM-dd?HHss").format(new?Date()) ?????????????????????????}}) ????????????; }
@HttpCacheControl 會(huì)在 Response Header 上返回 Cache-Control: max-age=10 和 Etag: "403710060904730625"
@PostMapping("cache/{id}") @HttpETag(key?=?"#id") public?ResponseEntity
@HttpETag主要用于刷新Etag標(biāo)簽
呃呃呃,懶得寫(xiě)了,要去修復(fù)http模塊的bug了,還有好多功能和配置沒(méi)有加上去吶。
考慮要不要把模塊獨(dú)立出來(lái),作為一個(gè)單獨(dú)的項(xiàng)目發(fā)展吶
編輯:黃飛
?
評(píng)論
查看更多