由于沒(méi)有方便的監(jiān)控和運(yùn)維工具,導(dǎo)致排查問(wèn)題的效率低,使得在系統(tǒng)遇到問(wèn)題的時(shí)候排查困難,耗時(shí)過(guò)長(zhǎng)。

??? ?開(kāi)發(fā)新功能

開(kāi)發(fā)新任務(wù)的同時(shí),我們需要修復(fù)原有系統(tǒng)的性能問(wèn)題。

?PHP monolithic架構(gòu)圖

每臺(tái)服務(wù)器部署相同的服務(wù)端PHP代碼,由PHP-fpm解釋執(zhí)行,通過(guò)Nginx進(jìn)行反向代理。

華爾街見(jiàn)聞微服務(wù)架構(gòu)設(shè)計(jì)

因此,在2016年11月至2017年3月,我們采用微服務(wù)架構(gòu)啟動(dòng)重構(gòu),嘗試解決一部分上述問(wèn)題,在伸縮性上能以服務(wù)為單位進(jìn)行拓容,同時(shí),這一設(shè)計(jì)會(huì)在某些方面增大我們的開(kāi)發(fā)成本和運(yùn)維成本。

??? ?錯(cuò)誤排查復(fù)雜

很顯然,以前在單體應(yīng)用中能直接登錄服務(wù)器,查看出錯(cuò)日志,現(xiàn)在錯(cuò)誤散落在不同的服務(wù)中,為我們的錯(cuò)誤排查帶來(lái)了困難。

??? ?日志源增加

如何把服務(wù)的日志收集并分析。

??? ?基礎(chǔ)設(shè)施增加

每個(gè)服務(wù)有互相獨(dú)立的MySQL、Redis,公共服務(wù)方面需要高可用的服務(wù)發(fā)現(xiàn),調(diào)用鏈路分析,日志收集儲(chǔ)存設(shè)施等。

技術(shù)選型

微服務(wù)架構(gòu)圖

每臺(tái)服務(wù)器上均衡地部署服務(wù),LB接受用戶(hù)的請(qǐng)求,將請(qǐng)求轉(zhuǎn)發(fā)到API gateway,API gateway向服務(wù)發(fā)現(xiàn)查詢(xún)具體服務(wù)的IP和端口,服務(wù)執(zhí)行完業(yè)務(wù)邏輯后向上返回?cái)?shù)據(jù)。

服務(wù)框架

我們選擇golang作為我們的后端開(kāi)發(fā)語(yǔ)言。

??? ?golang在性能和開(kāi)發(fā)效率上有很好的平衡,語(yǔ)法上很簡(jiǎn)單,并發(fā)編程簡(jiǎn)單高效,基礎(chǔ)庫(kù)健全。

??? ?調(diào)試工具強(qiáng)大

自帶一些pprof包可以profile當(dāng)前程序的CPU消耗、內(nèi)存占用、鎖狀態(tài)、channel阻塞等,非常便利我們定位問(wèn)題。

??? ?有一些優(yōu)秀的微服務(wù)框架

我們選用go-micro作為開(kāi)發(fā)框架,里面包含幾乎所有微服務(wù)組件,并且支持非常好的拓展性,通過(guò)接口的設(shè)計(jì)方式,讓我們可以拓展一些自己的組件,如服務(wù)發(fā)現(xiàn)、傳輸協(xié)議等。

??? ?golang在華爾街見(jiàn)聞已經(jīng)有過(guò)比較多的應(yīng)用,工程師使用golang開(kāi)發(fā)幾乎0學(xué)習(xí)成本。

服務(wù)拆分

拆分的原則是通過(guò)服務(wù)功能劃分,盡量避免雙向依賴(lài)。我們拆分出了13個(gè)服務(wù),包括用戶(hù)、內(nèi)容、實(shí)時(shí)新聞、評(píng)論、搜索、商城、支付、三方代理等服務(wù)。

服務(wù)間通信

服務(wù)間使用protobuf協(xié)議對(duì)數(shù)據(jù)進(jìn)行編碼,使用UDP作為傳輸協(xié)議。

服務(wù)發(fā)現(xiàn)

Etcd搭建多節(jié)點(diǎn)高可用的服務(wù)發(fā)現(xiàn)。

服務(wù)保護(hù)

我們選擇Hystrix作為服務(wù)保護(hù)以及服務(wù)降級(jí)的方案。

每個(gè)服務(wù)開(kāi)發(fā)者,需要定義自己服務(wù)接口的并發(fā)量、超時(shí)時(shí)間以及fallback方法。

部署方案

選擇Kubernetes

* Docker Swarm

這是我們最先選擇的方案,因?yàn)镈ocker 1.12之后已經(jīng)將Swarm功能集成到Docker En-gine,能以最少的配置啟動(dòng)Docker集群。經(jīng)過(guò)簡(jiǎn)化和設(shè)計(jì)的控制臺(tái)API,方便地管理集群、調(diào)整服務(wù)如控制服務(wù)的數(shù)量、CPU、內(nèi)存限制等。往集群內(nèi)加入機(jī)器非常簡(jiǎn)單,只需要運(yùn)行一條命令即可。使用manager-worker架構(gòu),manager作為調(diào)度節(jié)點(diǎn),支持高可用。

但遇到了非常致命的問(wèn)題,比如頻繁更新服務(wù)的時(shí)候會(huì)出現(xiàn)服務(wù)訪問(wèn)不到,某服務(wù)的負(fù)載均衡后掛載的服務(wù)IP是其它服務(wù)的,服務(wù)之間的通信有幾率出現(xiàn)超時(shí)問(wèn)題,歸根結(jié)底,還是社區(qū)正在不斷完善swarm,有很多不穩(wěn)定的地方,網(wǎng)絡(luò)方面沒(méi)有進(jìn)行優(yōu)化。

* Kubernetes

這是谷歌主導(dǎo)的服務(wù)編排工具,它支持Docker,相比Docker Swarm來(lái)說(shuō),它的概念更多,分層更細(xì)。功能方面多于Docker Swarm,支持一些高級(jí)功能如秘鑰管理、配置管理、自動(dòng)拓容等。在生產(chǎn)環(huán)境的應(yīng)用比較廣泛,穩(wěn)定性更高。

* 裸機(jī)部署

裸機(jī)部署是我們的一個(gè)備案,考慮到以上兩個(gè)方案在當(dāng)時(shí)沒(méi)有具體線上實(shí)施的經(jīng)驗(yàn),所以如果Docker Swarm和Kubernetes都沒(méi)有成功,我們直接裸機(jī)部署。

裸機(jī)部署的需要解決單機(jī)端口沖突,如果一個(gè)服務(wù)在一個(gè)服務(wù)器上最多只部署一個(gè),那么可以通過(guò)寫(xiě)腳本,并劃分服務(wù)器角色的方式進(jìn)行部署,利用ansible可以定義user服務(wù)集群、content服務(wù)集群、comment服務(wù)集群等,通過(guò)分發(fā)二進(jìn)制文件的方式讓服務(wù)啟動(dòng),這樣的方案要考慮到服務(wù)更新、服務(wù)重啟、服務(wù)刪除等邏輯,同一時(shí)間只有部分節(jié)點(diǎn)更新,在服務(wù)未更新成功的時(shí)候流量暫時(shí)不能打到正在更新的節(jié)點(diǎn)。

準(zhǔn)備工作

代碼托管
由于之前使用github開(kāi)發(fā)人員的代碼提交在有翻墻工具的幫助下速度依然不是很理想,我們自建了Gitlab倉(cāng)庫(kù),自此開(kāi)發(fā)過(guò)上了幸福的生活。

容器化

swarm和kubernetes是基于docker快速創(chuàng)建刪除服務(wù),通過(guò)增加容器為服務(wù)拓容,縮減容器為服務(wù)縮小規(guī)模,所以所有項(xiàng)目必須要構(gòu)建docker鏡像。按項(xiàng)目類(lèi)型劃分,我們遇到3種鏡像打包情況。

1.?? ?后端項(xiàng)目

后端服務(wù)90%是golang項(xiàng)目,針對(duì)golang的鏡像,我們采取將golang項(xiàng)目編譯成可執(zhí)行文件,基于最小的alpine鏡像打包入docker,這里遇到過(guò)一個(gè)問(wèn)題,就是alpine里缺少openssl的證書(shū),無(wú)法支持https,我們自定義了新的基礎(chǔ)鏡像,不僅將證書(shū)文件打入鏡像,同時(shí)為了線上調(diào)試方便,增加了tcpdump、strace、bash等工具,在初期調(diào)試容器間通信問(wèn)題時(shí)發(fā)揮重要的作用。

2.?? ?前端靜態(tài)文件

見(jiàn)聞的后臺(tái)以及m站基于Vue,編譯后生成的靜態(tài)文件打入鏡像,通過(guò)nginx訪問(wèn)。 為了支持HTTP2,我們打入nginx鏡像缺少的證書(shū)。

3.?? ?服務(wù)端渲染

主站PC站基于nodejs、Vue實(shí)現(xiàn)服務(wù)端渲染,所以不僅需要依賴(lài)nodejs,而且需要利用pm2進(jìn)行nodejs生命周期的管理。為了加速線上鏡像構(gòu)建的速度,我們利用taobao源https://registry.npm.taobao.org進(jìn)行加速, 并且將一些常見(jiàn)的npm依賴(lài)打入了基礎(chǔ)鏡像,避免每次都需要重新下載,鏡像打包從開(kāi)始的3分鐘縮減到1.5分鐘。

三類(lèi)鏡像結(jié)構(gòu)

持續(xù)集成

我們利用Gitlab CI配置了測(cè)試、鏡像構(gòu)建、鏡像發(fā)布、自動(dòng)部署等流程,后端服務(wù)從提交代碼到測(cè)試分支到測(cè)試環(huán)境自動(dòng)部署完成花費(fèi)1.5分鐘,前端服務(wù)平均為2.5分鐘。

CI任務(wù)中的test->build->docker->deploy流程

云平臺(tái)的選擇

最終,我們選擇了騰訊云的容器服務(wù),主要基于以下幾點(diǎn)考慮:

??? ?騰訊云的容器服務(wù)是在騰訊云的Iaas上為每個(gè)用戶(hù)構(gòu)建容器集群,騰訊云提供的微服務(wù)架構(gòu)和持續(xù)集成與交付的應(yīng)用場(chǎng)景基本滿(mǎn)足了我們的述求。

??? ?騰訊云的容器服務(wù)是基于Kubernetes實(shí)現(xiàn)的,支持完全的kubernetes能力。

??? ?騰訊云在Kubernetes上實(shí)現(xiàn)了他們的存儲(chǔ)、負(fù)載均衡等產(chǎn)品的插件、復(fù)用了騰訊云本身平臺(tái)的監(jiān)控、日志等能力。減少了我們接入和開(kāi)發(fā)的成本。

服務(wù)在騰訊云的應(yīng)用

我們將我們的應(yīng)用重構(gòu)成微服務(wù)的架構(gòu),每個(gè)微服務(wù)部署成騰訊云容器服務(wù)上的一個(gè)服務(wù),前端接入通過(guò)一個(gè)負(fù)載均衡。后端服務(wù)間可互相訪問(wèn)。

服務(wù)器安全方面,內(nèi)部服務(wù)器通過(guò)VPC進(jìn)行網(wǎng)絡(luò)隔離,將網(wǎng)絡(luò)劃分為生產(chǎn)環(huán)境、測(cè)試環(huán)境,在生產(chǎn)環(huán)境中又劃分backend子網(wǎng)和data子網(wǎng),設(shè)定子網(wǎng)之間的訪問(wèn)規(guī)則。

為了禁止內(nèi)部服務(wù)器的外網(wǎng)訪問(wèn),不給內(nèi)部服務(wù)器分配外網(wǎng)IP,僅通過(guò)跳板機(jī)訪問(wèn)。

性能對(duì)比

利用locust模擬線上請(qǐng)求的比例,利用2臺(tái)16核的壓測(cè)機(jī)在內(nèi)網(wǎng)對(duì)10臺(tái)16C32G的機(jī)器上的服務(wù)進(jìn)行壓測(cè),達(dá)到1w/s QPS以上,并且服務(wù)的負(fù)載并沒(méi)達(dá)到極限,這已經(jīng)是之前PHP生產(chǎn)環(huán)境20+臺(tái)16C32G服務(wù)器能達(dá)到的QPS。

線上調(diào)用追蹤
通過(guò)追蹤API調(diào)用鏈的流向與耗時(shí),我們可以找出性能的瓶頸。我們通過(guò)zipkin實(shí)際優(yōu)化了幾種情況:
??? ?服務(wù)調(diào)用冗余
當(dāng)拉取文章列表的時(shí)候,我們需要拉取文章對(duì)應(yīng)的作者信息,開(kāi)始的時(shí)候我們使用拉取單個(gè)作者信息的方式,后來(lái)性能調(diào)優(yōu)階段,我們將其改為批量拉取作者列表,減少RPC的冗余。
??? ?服務(wù)耗時(shí)長(zhǎng)
對(duì)于有些本身就比較耗時(shí)并且對(duì)即時(shí)性不是那么苛刻的計(jì)算服務(wù),我們?yōu)榱吮WC服務(wù)的響應(yīng)時(shí)間,會(huì)適量地加上緩存。

監(jiān)控與報(bào)警
由從外部系統(tǒng)表征到內(nèi)部日志,我們將監(jiān)控分為API健康,程序錯(cuò)誤報(bào)警,以及服務(wù)器/容器負(fù)載。
排查問(wèn)題的流程一般有兩種情況,一種是用戶(hù)發(fā)現(xiàn)問(wèn)題,申報(bào)問(wèn)題,開(kāi)發(fā)人員跟進(jìn)問(wèn)題;一種是我們的監(jiān)控優(yōu)先發(fā)現(xiàn)問(wèn)題,開(kāi)發(fā)人員在用戶(hù)反饋前跟進(jìn)并修復(fù)。在報(bào)警方面,我們通過(guò)為監(jiān)控系統(tǒng)謹(jǐn)慎設(shè)置報(bào)警閾值,當(dāng)觸發(fā)報(bào)警時(shí),開(kāi)發(fā)人員會(huì)收到郵件。
這里我們?cè)趫?bào)警的定義上有過(guò)思考,即什么樣的報(bào)警算是有意義的?我們遇到過(guò)每天10幾條重復(fù)的報(bào)警,通常開(kāi)發(fā)人員開(kāi)始時(shí)會(huì)對(duì)報(bào)警非常重視,當(dāng)重復(fù)的報(bào)警一再出現(xiàn),漸漸失去了對(duì)報(bào)警的關(guān)注。所以我們有解除一些不必要的報(bào)警,并且對(duì)剩余一些報(bào)警進(jìn)行調(diào)查,甚至有些警報(bào)是因?yàn)楸O(jiān)控工具本身的不準(zhǔn)確引起的。

API健康
我們?cè)O(shè)置默認(rèn)的時(shí)間區(qū)間是5分鐘
??? ?統(tǒng)計(jì)API五分鐘內(nèi)平均QPS
??? ?API 98%以?xún)?nèi)的延遲分布
??? ?QPS最高的前10的API
??? ?API的返回碼的分布

程序錯(cuò)誤報(bào)警
后端程序內(nèi)接入Sentry日志報(bào)警系統(tǒng),golang程序捕獲panic日志以及error日志,并發(fā)送報(bào)警郵件。

服務(wù)器/容器負(fù)載
通過(guò)在服務(wù)器上運(yùn)行telegraf daemon進(jìn)程,收集服務(wù)器metrics并發(fā)送給influxdb,使用Grafana作為前端面板,對(duì)服務(wù)器負(fù)載以及容器的平均CPU、內(nèi)存占用率進(jìn)行監(jiān)控。

結(jié)束語(yǔ)
本文介紹了華爾街見(jiàn)聞通過(guò)重構(gòu)和服務(wù)容器的重新部署,實(shí)踐微服務(wù)架構(gòu)的情況。經(jīng)過(guò)幾個(gè)月的開(kāi)發(fā)測(cè)試,我們不僅完成了線上服務(wù)從PHP到Golang的轉(zhuǎn)型,更在服務(wù)的穩(wěn)定性上經(jīng)歷了考驗(yàn),支撐了幾次重大新聞的高流量。
在開(kāi)發(fā)流程上,搭建了完善的自動(dòng)化工具,減少了人工操作的重復(fù)性和誤操作概率。
在運(yùn)維方面,由于監(jiān)控系統(tǒng)對(duì)系統(tǒng)完整的監(jiān)控,與Kubernetes健全的上線、下線、回滾、拓容功能配合,能以極快的速度處理線上問(wèn)題。

分享到

zhangnn

相關(guān)推薦