基于OpenResty的單機(jī)10萬TPS網(wǎng)關(guān)在物流業(yè)務(wù)中的應(yīng)用
引言
OpenResty® 是一個(gè)基于 Nginx 與 Lua 的高性能 Web 平臺(tái),其內(nèi)部集成了大量精良的 Lua 庫、第三方模塊以及大多數(shù)的依賴項(xiàng)。用于方便地搭建能夠處理超高并發(fā)、擴(kuò)展性極高的動(dòng)態(tài) Web 應(yīng)用、Web 服務(wù)和動(dòng)態(tài)網(wǎng)關(guān)。
物流網(wǎng)關(guān)就是基于OpenResty構(gòu)建的,今天就跟大家聊聊 OpenResty 在物流網(wǎng)關(guān)的故事。
為什么選擇OpenResty
物流網(wǎng)關(guān)在建設(shè)之初就重點(diǎn)關(guān)注性能、穩(wěn)定性、擴(kuò)展性以及可持續(xù)性。
在技術(shù)選型階段重點(diǎn)關(guān)注三個(gè)方面:
- 在網(wǎng)絡(luò) I/O 模型方面,出于性能的考慮,需要非阻塞的 I/O 模型;
- 由于物流網(wǎng)關(guān)對(duì)外提供的是 Http/s 協(xié)議,所以需要成熟的支持 Http/s 協(xié)議的技術(shù);
- 這個(gè)世界變化很快,只有擁抱好的生態(tài)才能促進(jìn)持續(xù)發(fā)展。
綜合這三方面的需求,發(fā)現(xiàn) OpenResty 是一個(gè)很好地選擇。
首先,OpenResty 利用協(xié)程實(shí)現(xiàn)了同步非阻塞的 cosocket,利用 cosocket 既可以享受同步編程的簡單,又可以享受非阻塞IO的性能優(yōu)勢。
其次,Nginx 處理 Http/s 請(qǐng)求,目前在業(yè)界無人能出其右,性能和穩(wěn)定性有目共睹。
同時(shí),期望利用插件機(jī)制擴(kuò)展功能。這方面 Kong 這個(gè)網(wǎng)關(guān)項(xiàng)目(這個(gè)項(xiàng)目基于 OpenResty)給出了優(yōu)秀的參考方案。
插件化擴(kuò)展方法
物流網(wǎng)關(guān)的功能紛繁復(fù)雜,核心的組件有安防、認(rèn)證、限流、協(xié)議轉(zhuǎn)換、日志,網(wǎng)關(guān)的這些核心功能***都是插件化的,這些插件能夠根據(jù)不同的商家動(dòng)態(tài)加載和卸載,這樣才能滿足不同商家的需求。
物流網(wǎng)關(guān)的插件機(jī)制依賴于 Nginx 處理請(qǐng)求的生命周期模型,安防、認(rèn)證、限流這三個(gè)插件在 Rewrite / Access 階段動(dòng)態(tài)加載執(zhí)行,協(xié)議轉(zhuǎn)換、負(fù)載均衡在 Content 階段動(dòng)態(tài)加載執(zhí)行,而日志在 Log 階段異步處理。
每一個(gè)請(qǐng)求都需要根據(jù)業(yè)務(wù)配置動(dòng)態(tài)加載,這些配置存儲(chǔ)在 MySQL 數(shù)據(jù)庫中,在高并發(fā)場景下,如果每次請(qǐng)求都要訪問 MySQL 數(shù)據(jù)庫,那 MySQL 數(shù)據(jù)庫一定會(huì)成為瓶頸直至宕機(jī),因此引入多級(jí)緩存。
緩存的設(shè)計(jì)
物流網(wǎng)關(guān)采用了多級(jí)緩存,首先是利用 ngx.shared.DICT 實(shí)現(xiàn)的本地緩存,集中式緩存使用的是 Redis,物流網(wǎng)關(guān)并不直接訪問數(shù)據(jù)庫,而是通過調(diào)用 RPC 服務(wù)來訪問數(shù)據(jù)庫。
Redis 中的緩存是長期有效的,Redis 和 MySQL 之間的數(shù)據(jù)同步依賴雙寫機(jī)制,本地緩存和 Redis 的同步同時(shí)采用了兩種方法,一種是利用Redis實(shí)現(xiàn)了一個(gè)簡單MQ,網(wǎng)關(guān)集群節(jié)點(diǎn)訂閱元數(shù)據(jù)變更的消息,當(dāng)有變更時(shí),清空相關(guān)的本地緩存;為了容錯(cuò),本地緩存設(shè)置了失效期,這樣能夠保證數(shù)據(jù)總是有機(jī)會(huì)同步到本地緩存。
負(fù)載均衡器的設(shè)計(jì)
物流網(wǎng)關(guān)自研了支持 RPC 協(xié)議的 Lua 客戶端,功能與 Java 版的客戶端類似,值得一提的是負(fù)載均衡器的設(shè)計(jì)更加智能,在壓測階段發(fā)現(xiàn),同樣規(guī)格的 Docker,性能差異非常大,這個(gè)差異很可能和宿主機(jī)的網(wǎng)絡(luò)、CPU 負(fù)載、內(nèi)存使用率有關(guān),這個(gè)影響因素是動(dòng)態(tài)變化的,因此靜態(tài)的負(fù)載均衡配置(例如輪訓(xùn)、隨機(jī)、權(quán)重等負(fù)載均衡策略)難以滿足需求,理想的負(fù)載均衡器應(yīng)該能夠根據(jù) RPC 服務(wù)負(fù)載來動(dòng)態(tài)調(diào)整流量分發(fā)。
物流網(wǎng)關(guān)的調(diào)度算法選用的是最小連接數(shù)調(diào)度算法,類似于大家去超市排隊(duì)結(jié)賬,總是選取長度最少的隊(duì)伍。連接數(shù)的計(jì)算是這樣的:發(fā)送請(qǐng)求的時(shí)候連接數(shù)+1,響應(yīng)返回或者異常的時(shí)候連接數(shù)-1。
json 跨語言的坑
Json 作為一種成熟的序列化方案,已經(jīng)存在很久了,但是在跨語言方面 Json 并不成熟,A == json.decode(A).encode 在跨語言的時(shí)候并不是總能成立。例如對(duì)于二進(jìn)制的序列化,在 Java 里都是將它轉(zhuǎn)換成 base64 的字符串,例如 0X3F 會(huì)被序列化成”/”,OpenResty 自帶的 cjson 會(huì)把“/”反序列化成字符串“/”,至此都沒有問題,但是 cjson 序列化字符串“/”時(shí),得到的卻是“\/”,因?yàn)榘凑?json 規(guī)范“/”是需要被轉(zhuǎn)義的。最終結(jié)果就是網(wǎng)關(guān)的輸入是“/”輸出卻是“\/”。
所以物流網(wǎng)關(guān)自研了無損的 json 序列化組件,完全在字符串基本上操作 json,這樣就避免了類型轉(zhuǎn)換帶來的問題。下圖是一個(gè) json 字符的解析過程。
性能優(yōu)化
OpenResty 提供了優(yōu)秀的性能分析工具,可以在運(yùn)行時(shí)對(duì)系統(tǒng)采樣,并生成火焰圖,通過火焰圖可以快速定位性能瓶頸出現(xiàn)在哪行代碼。物流網(wǎng)關(guān)在單機(jī)全鏈路壓測中 TPS 能夠到達(dá)10萬,將硬件性能發(fā)揮到了***。
總結(jié)
目前,物流網(wǎng)關(guān)作為京東物流開放技術(shù)平臺(tái)的核心服務(wù),支撐了所有 Http/s 協(xié)議的開放業(yè)務(wù),已經(jīng)平穩(wěn)度過2018年的618全球年中購物節(jié)以及11.11全球好物節(jié)。借助 Lua 優(yōu)秀的表達(dá)能力,以及插件化機(jī)制,物流網(wǎng)關(guān)近一年實(shí)現(xiàn)了功能的快速演進(jìn),真正做到了快速響應(yīng)業(yè)務(wù)發(fā)展。
【本文來自51CTO專欄作者張開濤的微信公眾號(hào)(開濤的博客),公眾號(hào)id: kaitao-1234567】