新聞中心
微服務(wù)是否適合小團隊是個見仁見智的問題。但小團隊并不代表出品的一定是小產(chǎn)品,當(dāng)業(yè)務(wù)變得越來越復(fù)雜,如何使用微服務(wù)分而治之就成為一個不得不面對的問題。

讓客戶滿意是我們工作的目標(biāo),不斷超越客戶的期望值來自于我們對這個行業(yè)的熱愛。我們立志把好的技術(shù)通過有效、簡單的方式提供給客戶,將通過不懈努力成為客戶在信息化領(lǐng)域值得信任、有價值的長期合作伙伴,公司提供的服務(wù)項目有:域名注冊、虛擬主機、營銷軟件、網(wǎng)站建設(shè)、平房網(wǎng)站維護、網(wǎng)站推廣。
因為微服務(wù)是對整個團隊的考驗,從開發(fā)到交付,每一步都充滿了挑戰(zhàn)。經(jīng)過 1 年多的探索和實踐,本著將 DevOps 落實到產(chǎn)品中的愿景,一步步建設(shè)出適合我們的微服務(wù)平臺。
要不要微服務(wù)
我們的產(chǎn)品是 Linkflow,企業(yè)運營人員使用的客戶數(shù)據(jù)平臺(CDP)。產(chǎn)品的一個重要部分類似企業(yè)版的“捷徑",讓運營人員可以像搭樂高積木一樣創(chuàng)建企業(yè)的自動化流程,無需編程即可讓數(shù)據(jù)流動起來。
從這一點上,我們的業(yè)務(wù)特點就是聚少成多,把一個個服務(wù)連接起來就成了數(shù)據(jù)的海洋。
理念上跟微服務(wù)一致,一個個獨立的小服務(wù)最終實現(xiàn)大功能。當(dāng)然我們一開始也沒有使用微服務(wù),當(dāng)業(yè)務(wù)還未成型就開始考慮架構(gòu),那么就是“過度設(shè)計"。
另一方面需要考慮的因素就是“人",有沒有經(jīng)歷過微服務(wù)項目的人,團隊是否有 DevOps 文化等等,綜合考量是否需要微服務(wù)化。
微服務(wù)的好處是什么?
- 相比于單體應(yīng)用,每個服務(wù)的復(fù)雜度會下降,特別是數(shù)據(jù)層面(數(shù)據(jù)表關(guān)系)更清晰,不會一個應(yīng)用上百張表,新員工上手快。
- 對于穩(wěn)定的核心業(yè)務(wù)可以單獨成為一個服務(wù),降低該服務(wù)的發(fā)布頻率,也減少測試人員壓力。
- 可以將不同密集型的服務(wù)搭配著放到物理機上,或者單獨對某個服務(wù)進行擴容,實現(xiàn)硬件資源的充分利用。
- 部署靈活,在私有化項目中,如果客戶有不需要的業(yè)務(wù),那么對應(yīng)的微服務(wù)就不需要部署,節(jié)省硬件成本,就像上文提到的樂高積木理念。
微服務(wù)有什么挑戰(zhàn)?
- 一旦設(shè)計不合理,交叉調(diào)用,相互依賴頻繁,就會出現(xiàn)牽一發(fā)動全身的局面。想象單個應(yīng)用內(nèi) Service 層依賴復(fù)雜的場面就明白了。
- 項目多了,輪子需求也會變多,需要有人專注公共代碼的開發(fā)。
- 開發(fā)過程的質(zhì)量需要通過持續(xù)集成(CI)嚴格把控,提高自動化測試的比例,因為往往一個接口改動會涉及多個項目,光靠人工測試很難覆蓋所有情況。
- 發(fā)布過程會變得復(fù)雜,因為微服務(wù)要發(fā)揮全部能力需要容器化的加持,容器編排就是***的挑戰(zhàn)。
- 線上運維,當(dāng)系統(tǒng)出現(xiàn)問題需要快速定位到某個機器節(jié)點或具體服務(wù),監(jiān)控和鏈路日志分析都必不可少。
下面詳細說說我們是怎么應(yīng)對這些挑戰(zhàn)的。
開發(fā)過程的挑戰(zhàn)
持續(xù)集成
通過 CI 將開發(fā)過程規(guī)范化,串聯(lián)自動化測試和人工 Review。
我們使用 Gerrit 作為代碼&分支管理工具,在流程管理上遵循 GitLab 的工作流模型:
- 開發(fā)人員提交代碼至 Gerrit 的 Magic 分支。
- 代碼 Review 人員 Review 代碼并給出評分。
- 對應(yīng) Repo 的 Jenkins job 監(jiān)聽分支上的變動,觸發(fā) Build job。經(jīng)過 IT 和 Sonar 的靜態(tài)代碼檢查給出評分。
- Review 和 Verify 皆通過之后,相應(yīng) Repo 的負責(zé)人將代碼 Merge 到真實分支上。
- 若有一項不通過,代碼修改后重復(fù)過程。
- Gerrit 將代碼實時同步備份至兩個遠程倉庫中。
集成測試
一般來說代碼自動執(zhí)行的都是單元測試(Unit Test),即不依賴任何資源(數(shù)據(jù)庫,消息隊列)和其他服務(wù),只測試本系統(tǒng)的代碼邏輯。
但這種測試需要 Mock 的部分非常多,一是寫起來復(fù)雜,二是代碼重構(gòu)起來跟著改的測試用例也非常多,顯得不夠敏捷。而且一旦要求開發(fā)團隊要達到某個覆蓋率,就會出現(xiàn)很多造假的情況。
所以我們選擇主要針對 API 進行測試,即針對 Controller 層的測試。另外對于一些公共組件如分布式鎖,Json 序列化模塊也會有對應(yīng)的測試代碼覆蓋。
測試代碼在運行時會采用一個隨機端口拉起項目,并通過 HTTP Client 對本地 API 發(fā)起請求,測試只會對外部服務(wù)做 Mock,數(shù)據(jù)庫的讀寫,消息隊列的消費等都是真實操作,相當(dāng)于把 Jmeter 的事情在 Java 層面完成一部分。
Spring Boot 項目可以很容易的啟動這樣一個測試環(huán)境,代碼如下:
測試過程的 HTTP Client 推薦使用 io.rest-assured:rest-assured 支持 JsonPath,十分好用。
測試時需要注意的一個點是測試數(shù)據(jù)的構(gòu)造和清理。構(gòu)造又分為 Schema 的創(chuàng)建和測試數(shù)據(jù)的創(chuàng)建:
- Schema 由 Flyway 處理,在啟用測試環(huán)境前先刪除所有表,再進行表的創(chuàng)建。
- 測試數(shù)據(jù)可以通過 @Sql 讀取一個 SQL 文件進行創(chuàng)建,在一個用例結(jié)束后再清除這些數(shù)據(jù)。
順帶說一下,基于 Flyway 的 Schema Upgrade 功能我們封成了獨立的項目,每個微服務(wù)都有自己的 Upgrade 項目。
好處:一是支持 command-line 模式,可以細粒度的控制升級版本;二是也可以支持分庫分表以后的 Schema 操作。Upgrade項目也會被制作成 Docker image 提交到 Docker hub。
測試在每次提交代碼后都會執(zhí)行,Jenkins 監(jiān)聽 Gerrit 的提交,通過 docker run -rm {upgrade 項目的 image}先執(zhí)行一次 Schema Upgrade,然后 Gradle test 執(zhí)行測試。
最終會生成測試報告和覆蓋率報告,覆蓋率報告采用 JaCoCo 的 Gradle 插件生成,如下圖:
這里多提一點,除了集成測試,服務(wù)之間的接口要保證兼容,實際上還需要一種 consumer-driven testing tool。
就是說接口消費端先寫接口測試用例,然后發(fā)布到一個公共區(qū)域,接口提供方發(fā)布接口時也會執(zhí)行這個公共區(qū)域的用例,一旦測試失敗,表示接口出現(xiàn)了不兼容的情況。
比較推薦大家使用 Pact 或是 Spring Cloud Contact。我們目前的契約基于“人的信任”,畢竟服務(wù)端開發(fā)者還不多,所以沒有必要使用這樣一套工具。
集成測試的同時還會進行靜態(tài)代碼檢查,我們用的是 Sonar,當(dāng)所有檢查通過后 Jenkins 會 +1 分,再由 Reviewer 進行代碼 Review。
自動化測試
單獨拿自動化測試出來說,就是因為它是質(zhì)量保證的非常重要的一環(huán),上文能在 CI 中執(zhí)行的測試都是針對單個微服務(wù)的。
那么當(dāng)所有服務(wù)(包括前端頁面)都在一起工作的時候是否會出現(xiàn)問題,就需要一個更接近線上的環(huán)境來進行測試了。
在自動化測試環(huán)節(jié),我們結(jié)合 Docker 提高一定的工作效率并提高測試運行時環(huán)境的一致性以及可移植性。
在準(zhǔn)備好基礎(chǔ)的 Pyhton 鏡像以及 Webdriver(Selenium)之后,我們的自動化測試工作主要由以下主要步驟組成:
- 測試人員在本地調(diào)試測試代碼并提交至 Gerrit。
- Jenkins 進行測試運行時環(huán)境的鏡像制作,主要將引用的各種組件和庫打包進一個 Python 的基礎(chǔ)鏡像。
- 通過 Jenkins 定時或手動觸發(fā),調(diào)用環(huán)境部署的 Job 將專用的自動化測試環(huán)境更新,然后拉取自動化測試代碼啟動一次性的自動化測試運行時環(huán)境的 Docker 容器,將代碼和測試報告的路徑鏡像至容器內(nèi)。
- 自動化測試過程將在容器內(nèi)進行。
- 測試完成之后,不必手動清理產(chǎn)生的各種多余內(nèi)容,直接在 Jenkins 上查看發(fā)布出來的測試結(jié)果與趨勢。
關(guān)于部分性能測試的執(zhí)行,我們同樣也將其集成到 Jenkins 中,在可以直觀的通過一些結(jié)果數(shù)值來觀察版本性能變化情況的回歸測試和基礎(chǔ)場景,將會很大程度的提高效率,便捷的觀察趨勢:
- 測試人員在本地調(diào)試測試代碼并提交至 Gerrit。
- 通過 Jenkins 定時或手動觸發(fā),調(diào)用環(huán)境部署的 Job 將專用的性能測試環(huán)境更新以及可能的 Mock Server 更新。
- 拉取***的性能測試代碼,通過 Jenkins 的性能測試插件來調(diào)用測試腳本。
- 測試完成之后,直接在 Jenkins 上查看通過插件發(fā)布出來的測試結(jié)果與趨勢。
發(fā)布過程的挑戰(zhàn)
上面提到微服務(wù)一定需要結(jié)合容器化才能發(fā)揮全部優(yōu)勢,容器化就意味著線上有一套容器編排平臺。我們目前采用是 Redhat 的 OpenShift。
所以發(fā)布過程較原來只是啟動 Jar 包相比要復(fù)雜的多,需要結(jié)合容器編排平臺的特點找到合適的方法。
鏡像準(zhǔn)備
公司開發(fā)基于 GitLab 的工作流程,Git 分支為 Master,Pre-production和 Prodution 三個分支,同時生產(chǎn)版本發(fā)布都打上對應(yīng)的 Tag。
每個項目代碼里面都包含 Dockerfile 與 Jenkinsfile,通過 Jenkins 的多分支 Pipeline 來打包 Docker 鏡像并推送到 Harbor 私庫上。
Docker 鏡像的命令方式為:項目名/分支名:git_commit_id,如 funnel/production:4ee0b052fd8bd3c4f253b5c2777657424fccfbc9。
Tag 版本的 Docker 鏡像命名為:項目名 /release:tag 名,如 funnel/release:18.10.R1。
在 Jenkins 中執(zhí)行 build docker image job 時會在每次 Pull 代碼之后調(diào)用 Harbor 的 API 來判斷此版本的 Docker image 是否已經(jīng)存在,如果存在就不執(zhí)行后續(xù)編譯打包的 Stage。
在 Jenkins 的發(fā)布任務(wù)中會調(diào)用打包 Job,避免了重復(fù)打包鏡像,這樣就大大的加快了發(fā)布速度。
數(shù)據(jù)庫 Schema 升級
數(shù)據(jù)庫的升級用的是 Flyway,打包成 Docker 鏡像后,在 OpenShift 中創(chuàng)建 Job 去執(zhí)行數(shù)據(jù)庫升級。
Job 可以用最簡單的命令行的方式去創(chuàng)建:
腳本升級任務(wù)也集成在 Jenkins 中。
容器發(fā)布
OpenShift 有個特別概念叫 DeploymentConfig,原生 Kubernetes Deployment 與之相似,但 OpenShift 的 DeploymentConfig 功能更多。
DeploymentConfig 關(guān)聯(lián)了一個叫做 ImageStreamTag 的東西,而這個 ImagesStreamTag 和實際的鏡像地址做關(guān)聯(lián),當(dāng) ImageStreamTag 關(guān)聯(lián)的鏡像地址發(fā)生了變更,就會觸發(fā)相應(yīng)的 DeploymentConfig 重新部署。
我們發(fā)布是使用了 Jenkins+OpenShift 插件,只需要將項目對應(yīng)的 ImageStreamTag 指向到新生成的鏡像上,就觸發(fā)了部署。
如果是服務(wù)升級,已經(jīng)有容器在運行怎么實現(xiàn)平滑替換而不影響業(yè)務(wù)呢?
配置 Pod 的健康檢查,Health Check 只配置了 ReadinessProbe,沒有用 LivenessProbe。
因為 LivenessProbe 在健康檢查失敗之后,會將故障的 Pod 直接干掉,故障現(xiàn)場沒有保留,不利于問題的排查定位。而 ReadinessProbe 只會將故障的 Pod 從 Service 中踢除,不接受流量。
使用了 ReadinessProbe 后,可以實現(xiàn)滾動升級不中斷業(yè)務(wù),只有當(dāng) Pod 健康檢查成功之后,關(guān)聯(lián)的 Service 才會轉(zhuǎn)發(fā)流量請求給新升級的 Pod,并銷毀舊的 Pod。
線上運維的挑戰(zhàn)
服務(wù)間調(diào)用
Spring Cloud 使用 Eruka 接受服務(wù)注冊請求,并在內(nèi)存中維護服務(wù)列表。
當(dāng)一個服務(wù)作為客戶端發(fā)起跨服務(wù)調(diào)用時,會先獲取服務(wù)提供者列表,再通過某種負載均衡算法取得具體的服務(wù)提供者地址(IP + Port),即所謂的客戶端服務(wù)發(fā)現(xiàn)。在本地開發(fā)環(huán)境中我們使用這種方式。
由于 OpenShift 天然就提供服務(wù)端服務(wù)發(fā)現(xiàn),即 Service 模塊,客戶端無需關(guān)注服務(wù)發(fā)現(xiàn)具體細節(jié),只需知道服務(wù)的域名就可以發(fā)起調(diào)用。
由于我們有 Node.js 應(yīng)用,在實現(xiàn) Eureka 的注冊和去注冊的過程中都遇到過一些問題,不能達到生產(chǎn)級別。
所以決定直接使用 Service 方式替換掉 Eureka,也為以后采用 Service Mesh 做好鋪墊。
具體的做法是,配置環(huán)境變量:
- EUREKA_CLIENT_ENABLED=false,RIBBON_EUREKA_ENABLED=false
并將服務(wù)列表如:
- FOO_RIBBON_LISTOFSERVERS: '[http://foo:8080](http://foo:8080/)'
寫進 ConfigMap 中,以 envFrom: configMapRef 方式獲取環(huán)境變量列表。
如果一個服務(wù)需要暴露到外部怎么辦,比如暴露前端的 HTML 文件或者服務(wù)端的 Gateway。
OpenShift 內(nèi)置的 HAProxy Router,相當(dāng)于 Kubernetes 的 Ingress,直接在 OpenShift 的 Web 界面里面就可以很方便的配置。
我們將前端的資源也作為一個 Pod 并有對應(yīng)的 Service,當(dāng)請求進入 HAProxy 符合規(guī)則就會轉(zhuǎn)發(fā)到 UI 所在的 Service。
Router 支持 A/B test 等功能,唯一的遺憾是還不支持 URL Rewrite。
對于需要 URL Rewrite 的場景怎么辦?那么就直接將 Nginx 也作為一個服務(wù),再做一層轉(zhuǎn)發(fā)。流程變成 Router → Nginx Pod → 具體提供服務(wù)的 Pod。
鏈路跟蹤
開源的全鏈路跟蹤很多,比如 Spring Cloud Sleuth + Zipkin,國內(nèi)有美團的 CAT 等等。
其目的就是當(dāng)一個請求經(jīng)過多個服務(wù)時,可以通過一個固定值獲取整條請求鏈路的行為日志,基于此可以再進行耗時分析等,衍生出一些性能診斷的功能。
不過對于我們而言,首要目的就是 Trouble Shooting,出了問題需要快速定位異常出現(xiàn)在什么服務(wù),整個請求的鏈路是怎樣的。
為了讓解決方案輕量,我們在日志中打印 RequestId 以及 TraceId 來標(biāo)記鏈路。
RequestId 在 Gateway 生成表示唯一一次請求,TraceId 相當(dāng)于二級路徑,一開始與 RequestId 一樣,但進入線程池或者消息隊列后,TraceId 會增加標(biāo)記來標(biāo)識唯一條路徑。
舉個例子,當(dāng)一次請求向 MQ 發(fā)送一個消息,那么這個消息可能會被多個消費者消費,此時每個消費線程都會自己生成一個 TraceId 來標(biāo)記消費鏈路。加入 TraceId 的目的就是為了避免只用 RequestId 過濾出太多日志。
實現(xiàn)上,通過 ThreadLocal 存放 APIRequestContext 串聯(lián)單服務(wù)內(nèi)的所有調(diào)用。
當(dāng)跨服務(wù)調(diào)用時,將 APIRequestContext 信息轉(zhuǎn)化為 HTTP Header,被調(diào)用方獲取到 HTTP Header 后再次構(gòu)建 APIRequestContext 放入 ThreadLocal,重復(fù)循環(huán)保證 RequestId 和 TraceId 不丟失即可。
如果進入 MQ,那么 APIRequestContext 信息轉(zhuǎn)化為 Message Header 即可(基于 RabbitMQ 實現(xiàn))。
當(dāng)日志匯總到日志系統(tǒng)后,如果出現(xiàn)問題,只需要捕獲發(fā)生異常的 RequestId 或是 TraceId 即可進行問題定位。
經(jīng)過一年來的使用,基本可以滿足絕大多數(shù) Trouble Shooting 的場景,一般半小時內(nèi)即可定位到具體業(yè)務(wù)。
容器監(jiān)控
容器化前監(jiān)控用的是 Telegraf 探針,容器化后用的是 Prometheus,直接安裝了 OpenShift 自帶的 cluster-monitoring-operator。
自帶的監(jiān)控項目已經(jīng)比較全面,包括 Node,Pod 資源的監(jiān)控,在新增 Node 后也會自動添加進來。
Java 項目也添加了 Prometheus 的監(jiān)控端點,只是可惜 cluster-monitoring-operator 提供的配置是只讀的,后期將研究怎么將 Java 的 JVM 監(jiān)控這些整合進來。
總結(jié)
開源軟件是對中小團隊的一種福音,無論是 Spring Cloud 還是 Kubernetes 都大大降低了團隊在基礎(chǔ)設(shè)施建設(shè)上的時間成本。
當(dāng)然其中有更多的話題,比如服務(wù)升降級,限流熔斷,分布式任務(wù)調(diào)度,灰度發(fā)布,功能開關(guān)等等都需要更多時間來探討。
對于小團隊,要根據(jù)自身情況選擇微服務(wù)的技術(shù)方案,不可一味追新,適合自己的才是***的。
當(dāng)前名稱:一個可供小團隊參考的微服務(wù)落地實踐
標(biāo)題鏈接:http://m.5511xx.com/article/ccosiip.html


咨詢
建站咨詢
