華爾街見聞app:從php單體式應用到微服務架構的演進

華爾街見聞app每天全平臺為近200萬用戶提供資訊、數據、研究等服務。長期位居各應用市場財經資訊類客戶端第1位。由於將重大事件、市場的基本面變化和100多種全球資產價格緊密關聯,在金融領域具有極高滲透率。首創的7x24快訊模式已經成為在中文世界理解全球市場的最快來源。也因此,該產品有技術架構複雜,需要高併發承載能力等特性。

一.背景

  • 老系統日益臃腫
  • 原先的系統是PHP monolithic架構,功能按模塊劃分,日積月累,最後模塊達到60+個,新人接手項目會拿到一整個系統的代碼,以及需要整個系統的配置,而他可能只需要專注開發不到1/10的業務。
  • 伸縮性
  • 我們的主要業務是即時資訊,資訊具有時效性,網站的訪問量會呈現鋸齒形分佈。當遇到特大新聞如英國退歐、美國大選、法國大選等,我們要能彈性地通過增加服務資源,提高服務的容量。
  • 容錯性
  • 我們希望一個低優先級服務出現問題之後,不影響主要服務;一個主要服務能保證更高的可用性,就算出現問題,也要保證優雅降級。
  • 比如在重大事件發生的時候,我們希望文章 API 保證不會受到影響。
  • 單體應用
  • PHP單體應用在生產環境服務的時候,所有業務都跑在一個程序裡,增加了系統的脆弱性,一個隱藏的性能問題,能在服務量激增的時候成為壓垮駱駝的一根稻草。
  • 雲服務商成本
  • 由於架構落後於需要,我們不得不用硬件彌補性能上的問題,導致雲服務器成本不斷增加。
  • 線上運維
  • 由於沒有方便的監控和運維工具,導致排查問題的效率低,使得在系統遇到問題的時候排查困難,耗時過長。
  • 開發新功能
  • 開發新任務的同時,我們需要修復原有系統的性能問題。
  • PHP monolithic 架構圖
華爾街見聞app:從php單體式應用到微服務架構的演進

每臺服務器部署相同的服務端PHP代碼,由PHP-fpm解釋執行,通過Nginx進行反向代理。

二.華爾街見聞微服務架構設計

因此,我們採用微服務架構啟動重構,嘗試解決一部分上述問題,在伸縮性上能以服務為單位進行拓容,同時,這一設計會在某些方面增大我們的開發成本和運維成本。

  • 錯誤排查複雜
  • 很顯然,以前在單體應用中能直接登錄服務器,查看出錯日誌,現在錯誤散落在不同的服務中,為我們的錯誤排查帶來了困難。
  • 日誌源增加
  • 如何把服務的日誌收集並分析。
  • 基礎設施增加
  • 每個服務有互相獨立的 MySQL 、Redis ,公共服務方面需要高可用的服務發現,調用鏈路分析,日誌收集儲存設施等。

1.技術選型

微服務架構圖:

華爾街見聞app:從php單體式應用到微服務架構的演進

每臺服務器上均衡地部署服務,LB 接受用戶的請求,將請求轉發到API gateway,API gateway向服務發現查詢具體服務的IP和端口,服務執行完業務邏輯後向上返回數據。

2.服務框架

我們選擇golang作為我們的後端開發語言。

  • golang在性能和開發效率上有很好的平衡,語法上很簡單,併發編程簡單高效,基礎庫健全。
  • 調試工具強大
  • 自帶一些pprof包可以 profile 當前程序的 CPU 消耗、內存佔用、鎖狀態、channel阻塞等,非常便利我們定位問題。
  • 有一些優秀的微服務框架
  • 我們選用go-micro作為開發框架,裡面包含幾乎所有微服務組件,並且支持非常好的拓展性,通過接口的設計方式,讓我們可以拓展一些自己的組件,如服務發現、傳輸協議等。
  • golang在華爾街見聞已經有過比較多的應用,工程師使用golang開發幾乎0學習成本。

3.服務拆分

拆分的原則是通過服務功能劃分,儘量避免雙向依賴。經過拆分我們目前有13個服務,包括用戶、內容、實時新聞、評論、搜索、商城、支付、三方代理等服務。

4.服務間通信

服務間使用protobuf協議對數據進行編碼,使用UDP作為傳輸協議。

5.服務發現

Etcd 搭建多節點高可用的服務發現。

6.服務保護

我們選擇Hystrix作為服務保護以及服務降級的方案。

每個服務開發者,需要定義自己服務接口的併發量、超時時間以及 fallback 方法。

7.部署方案

我們選擇了Kubernetes。

  • Docker Swarm
  • 這是我們最先選擇的方案,因為Docker 1.12之後已經將Swarm功能集成到Docker Engine,能以最少的配置啟動Docker集群。經過簡化和設計的控制檯API,方便地管理集群、調整服務如控制服務的數量、CPU、內存限制等。往集群內加入機器非常簡單,只需要運行一條命令即可。使用manager-worker架構,manager作為調度節點,支持高可用。
  • 但遇到了非常致命的問題,比如頻繁更新服務的時候會出現服務訪問不到,某服務的負載均衡後掛載的服務IP是其它服務的,服務之間的通信有機率出現超時問題,歸根結底,還是社區正在不斷完善 swarm,有很多不穩定的地方,網絡方面沒有進行優化。
  • Kubernetes
  • 這是谷歌主導的服務編排工具,它支持Docker,相比Docker Swarm來說,它的概念更多,分層更細。功能方面多於Docker Swarm,支持一些高級功能如祕鑰管理、配置管理、自動拓容等。在生產環境的應用比較廣泛,穩定性更高。
  • 裸機部署
  • 裸機部署是我們的一個備案,考慮到以上兩個方案在當時沒有具體線上實施的經驗,所以如果Docker Swarm和Kubernetes都沒有成功,我們直接裸機部署。

裸機部署的需要解決單機端口衝突,如果一個服務在一個服務器上最多隻部署一個,那麼可以通過寫腳本,並劃分服務器角色的方式進行部署,利用ansible可以定義user服務集群、content服務集群、comment服務集群等,通過分發二進制文件的方式讓服務啟動,這樣的方案要考慮到服務更新、服務重啟、服務刪除等邏輯,同一時間只有部分節點更新,在服務未更新成功的時候流量暫時不能打到正在更新的節點。

三.準備工作

1.代碼託管

由於之前使用github開發人員的代碼提交在有翻牆工具的幫助下速度依然不是很理想,我們自建了Gitlab倉庫,自此開發過上了幸福的生活。

2.容器化

swarm和kubernetes是基於docker快速創建刪除服務,通過增加容器為服務拓容,縮減容器為服務縮小規模,所以所有項目必須要構建docker鏡像。按項目類型劃分,我們遇到3種鏡像打包情況。

  1. 後端項目

後端服務90%是golang項目,針對golang的鏡像,我們採取將golang項目編譯成可執行文件,基於最小的alpine鏡像打包入docker,這裡遇到過一個問題,就是alpine裡缺少openssl的證書,無法支持https,我們自定義了新的基礎鏡像,不僅將證書文件打入鏡像,同時為了線上調試方便,增加了tcpdump、strace、bash等工具,在初期調試容器間通信問題時發揮重要的作用。

  1. 前端靜態文件

見聞的後臺以及m站基於Vue,編譯後生成的靜態文件打入鏡像,通過nginx訪問。

為了支持HTTP2,我們打入nginx鏡像缺少的證書。

  1. 服務端渲染

主站PC站基於nodejs、Vue實現服務端渲染,所以不僅需要依賴nodejs,而且需要利用pm2進行nodejs生命週期的管理。為了加速線上鏡像構建的速度,我們利用taobao源https://registry.npm.taobao.org進行加速, 並且將一些常見的npm依賴打入了基礎鏡像,避免每次都需要重新下載,鏡像打包從開始的3分鐘縮減到1.5分鐘

三類鏡像結構

華爾街見聞app:從php單體式應用到微服務架構的演進

3.持續集成

我們利用Gitlab CI配置了測試、鏡像構建、鏡像發佈、自動部署等流程,後端服務從提交代碼到測試分支到測試環境自動部署完成花費1.5分鐘,前端服務平均為2.5分鐘

CI任務中的test->build->docker->deploy流程

華爾街見聞app:從php單體式應用到微服務架構的演進

四.雲平臺的選擇

最終我們選擇了騰訊雲的容器服務,主要基於以下幾點考慮:

  • 騰訊雲的容器服務是在騰訊雲的iaas上為每個用戶構建容器集群,他們提供的微服務架構和持續集成與交付的應用場景基本滿足了我們的述求。
  • 騰訊雲的容器服務是基於Kubernetes實現的,支持完全的kubernetes能力。
  • 騰訊雲在Kubernetes上實現了他們的存儲、負載均衡等產品的插件、複用了他們平臺的監控、日誌等能力。減少了我們接入和開發的成本。

五.服務在騰訊雲的應用

我們將我們的應用重構成微服務的架構,每個微服務部署成騰訊雲容器服務上的一個服務,前端接入通過一個負載均衡。後端服務間可互相訪問。

通過VPC進行網絡隔離,將網絡劃分為生產環境、測試環境,在生產環境中又劃分backend子網和data子網,設定子網之間的訪問規則,增加服務端的安全性。

六.性能對比

利用locust模擬線上請求的比例,利用2臺16核的壓測機在內網對10臺16C32G的機器上的服務進行壓測,達到1w/s QPS以上,並且服務的負載並沒達到極限,這已經是之前PHP生產環境20+臺16C32G服務器能達到的QPS。

七.線上調用追蹤

通過追蹤API調用鏈的流向與耗時,我們可以找出性能的瓶頸。我們通過zipkin實際優化了幾種情況:

  • 服務調用冗餘
  • 當拉取文章列表的時候,我們需要拉取文章對應的作者信息,開始的時候我們使用拉取單個作者信息的方式,後來性能調優階段,我們將其改為批量拉取作者列表,減少RPC的冗餘。
  • 服務耗時長
  • 對於有些本身就比較耗時並且對即時性不是那麼苛刻的計算服務,我們為了保證服務的響應時間,會適量地加上緩存。

八.監控與報警

由從外部系統表徵到內部日誌,我們將監控分為API健康,程序錯誤報警,以及服務器/容器負載。

排查問題的流程一般有兩種情況,一種是用戶發現問題,申報問題,開發人員跟進問題;一種是我們的監控優先發現問題,開發人員在用戶反饋前跟進並修復。在報警方面,我們通過為監控系統謹慎設置報警閾值,當觸發報警時,開發人員會收到郵件。

這裡我們在報警的定義上有過思考,即什麼樣的報警算是有意義的?我們遇到過每天10幾條重複的報警,通常開發人員開始時會對報警非常重視,當重複的報警一再出現,漸漸失去了對報警的關注。所以我們有解除一些不必要的報警,並且對剩餘一些報警進行調查,甚至有些警報是因為監控工具本身的不準確引起的。

1.API健康

我們設置默認的時間區間是5分鐘

  • 統計API五分鐘內平均 QPS
  • API 95%以內的延遲分佈
  • QPS 最高的前10的API
  • API 的返回碼的分佈

2.程序錯誤報警

後端程序內接入Sentry日誌報警系統,golang程序捕獲panic日誌以及error日誌,併發送報警郵件。

3.服務器/容器負載

通過在服務器上運行telegraf daemon進程,收集服務器metrics併發送給influxdb,使用Grafana作為前端面板,對服務器負載以及容器的平均 CPU 、內存佔用率進行監控。

九.結束語

本文介紹了華爾街見聞微服務的實踐情況。經過幾個月的開發測試,已經完成了線上服務從PHP到Golang的轉型。

在服務的穩定性上經歷了考驗,支撐了幾次重大新聞的高流量。

在開發流程上,搭建了完善的自動化工具,減少了人工操作的重複性和誤操作概率。

在運維方面,由於監控系統對系統完整的監控,與Kubernetes健全的上線、下線、回滾、拓容功能配合,能以極快的速度處理線上問題。

相關推薦

推薦中...