羅輯思惟首席架構師:Go微服務改造實踐

轉自:http://www.infoq.com/cn/news/2018/05/luojisiweiphp

方圓

曾前後在 Cisco,新浪微博從事基礎架構研發工做。十多年一直專一於後端技術的研發,在消息通訊,分佈式存儲等方向有着豐富的經驗。我的技術興趣普遍,主要專一 Go/Java/Python 等編程語言的發展,尤爲是在雲計算等前沿領域的應用。python

 

1、改造的背景

獲得最先的APP就是一個單體的PHP的應用,就是圖中最大的黃色塊,中間藍色塊表明不一樣模塊。下面的黃色部分表明passport 和支付系統,這個是在作獲得以前就存在的系統,由於公司早期有微信裏的電商業務。程序員

image

後來發現有一些業務邏輯並不須要從獲得走,還有一些數據格式轉換的工做也不須要跟業務徹底耦合,因此加了一層PHP的網關就是下圖看到的V3那部分。可是這樣作也有一些問題,PHP後端是FPM,一旦後端的接口響應較慢,就須要啓動大量FPM保證併發訪問,從而致使操做系統負載較高,從這一點上來講,使用PHP作這部分工做並不合適。redis

image

屋漏偏逢連夜雨

案例一:8/31大故障:2017年8月31日的時候,老闆作活動,致使流量超過預期不少,系統掛了兩個小時。

案例二:羅老師要跨年

每一年羅老師都要跨年演講,第一年是在優酷,有200多萬人的在線觀看,第二年是同時和優酷等視頻網站再加上深圳衛視一塊兒合做直播,2016年深圳衛視的收視率是地方第一。2017年的老闆當時想要送東西,送東西的這個場景比較恐怖,二維碼一放出來,就會有大量用戶同時請求。算法

最恐怖的事情是,老闆要送的東西8月31日的時候尚未,要在後面2個月期間把東西開發出來。一方面業務迭代不能停,一方面須要扛過跨年,因此就須要咱們對業務系統進行改造。spring

改造目標

  • 高性能:首先是性能要高,若是你單臺機器跑幾十QPS,那麼堆機器也很難知足要求。

  • 服務化:服務化實際上在故障以前就已經開始了,而且因爲咱們不一樣的業務團隊已經在負責不一樣的業務,實際上也是須要服務化繼續作下去。

  • 資源拆分隔離:隨着服務化過程,就須要對資源進行拆分,須要每一個服務提供相應的接口,服務之間不能直接訪問其餘服務的數據庫或者緩存。

  • 高可用:當時定的目標是99.9的可用性。

Go的好處不少,最重要的仍是對PHP程序員來講,上手更容易,並且性能好不少數據庫

 

2、改造的過程

首先有一個系統架構圖

image

對於系統改造來講,首先須要知道,系統須要改爲什麼樣子。所以咱們須要一個架構的藍圖。上面就是咱們的架構藍圖。首先須要的是一個統一對外的API GATEWAY,圖中最上層的黃色部分。 中間淡紫色的部分是對外的業務服務。淺綠色部分是基礎資源服務,好比音頻文稿信息,加密服務。下面紅色部分是支付和passport等公用服務,最右側是一些通用的框架和中間件。最下層是一些基礎設施。編程

咱們的框架跟基礎設施的完善和系統重構是交織進行的,不是說一開始就有一個徹底沒問題的設計,隨着業務的改造,會有不少新的功能加進來。json

框架和基礎設施完善

我不講應用系統怎麼拆分,由於每一個公司業務系統都不同,我講一下咱們在框架和中間件這部分事情。後端

API gateway

API gateway是咱們和陳皓(著名的左耳朵耗子)團隊合做研發的。他們團隊對於咱們成功跨年幫助很大,在此先感謝一下。

目的

  • 限流

    API gateway主要的目的就是限流,改造過程中,咱們線上有400多個接口,常常加新功能。咱們能夠保證新接口的性能,可是總有在改造過程當中疏忽的老接口,經過API gateway限流能夠保證在流量大的時候,老接口也有部分用戶可用。

  • 升級API

    大部分的API升級都是跟客戶端解決的,可是咱們不太強制用戶升級,致使線上老接口存在很長時間,咱們須要在API gateway這一層作一些把新接口數據格式轉成老接口數據格式的工做。

  • 鑑權

    在拆分服務以後,須要統一對接口進行鑑權和訪問控制,業界的作法一般都是在網關這一層來作,咱們也不例外。

接下來看一下API gateway的架構

image

API gateway由一個write節點和多個read節點,節點之間經過gossip協議通訊。每一個節點最上層有一個CLI的命令行,能夠用來調用Gateway的API。下層的HTTPServer等都是一個plugin,由多個plugin組成不一樣的pipeline來處理不一樣的請求。在後面我會介紹這個的設計。每一個節點都有一個統計模塊來作一些統計信息,這個統計信息主要是接口平均響應時間,QPS等。修改配置以後,write節點會把配置信息同步到read節點上,而且經過model模塊持久化到本地磁盤上。

image

請求通過了兩段pipeline,第一段pipeline基於請求的url。能夠在不一樣的pipeline上面組合不一樣的plugin。假設一個接口不須要限流,只須要在接口的配置裏頭不加limiter plugin就能夠了。第二段pipeline基於後端的Server配置,作一些負載均衡的工做。

接下來看整個API gateway啓動的流程和調度方面

image

啓動是比較簡單的,去加載plugin,而後再去加載相應的配置文件,根據配置文件把plugin和pipeline作對應。右上角的這個調度器分爲靜態調度和動態調度。靜態調度是假設分配5個go routine來作處理,始終都有5個go routine來處理對應的請求。動態調度器是根據請求繁忙程度,在一個go routine最大值和最小值之間變化。

image

API gateway鑑權方面比較簡單,客戶端調用登陸接口,passport會把token和userid,傳到API gateway,API gateway再把相應的token傳到這個APP端。客戶端下次請求就拿token請求,若是token驗證不過,就返回客戶端。若是驗證經過再調用後端不一樣的服務獲取結果,最後返回結果給客戶端。

最後再強調一下API gateway如何進行

image

咱們在API gateway裏面引入兩種限流的策略

  • 滑動窗口限流

    爲何會根據滑動窗口限流呢?由於線上接口太多,咱們也不知道究竟是限100好200好仍是限10000好,除非每個都進行壓測。用滑動窗口來統計一個時間窗口以內,響應時間,成功和失敗的數量,根絕這個統計數據對下一個時間窗口是否要進行限流作判斷。

  • QPS的限流

    爲何還會留一個QPS的限流呢?由於要作活動,滑動窗口是一個時間窗口,作活動的時候,客戶拿起手機掃二維碼,流量瞬間就進來了,滑動窗口在這種狀況下很難起到做用。

服務框架

目的

  • 簡化應用開發

  • 服務註冊發現

  • 方便配置管理

服務框架的經常使用架構

image

第一種方式是作成一個庫,把相關功能編譯進服務自己。這裏有兩個問題,第一個是咱們兼容好幾種語言,開發量比較大。還有一個是一旦客戶端跟隨服務調用方發佈到生產環境中,後續若是要對客戶庫進行升級,勢必要求服務調用方修改代碼並從新發布,因此該方案的升級推廣有不小的阻力。在業界來講,spring cloud,dubbo,motan都是用這樣的機制。

image

還有一種方案是把Lord Balancing的功能拿出來作成一個agent,跟consumer單獨跑,每次consumer請求的時候是經過agent拿到Service Provder的地址,而後再調用Service Provder。

  • 好處是簡化了服務調用方,不須要爲不一樣語言開發客戶庫,LB的升級不須要服務調用方改代碼。

  • 缺點也很明顯,部署比較複雜;還有可用性檢測會更麻煩一點,這個agent也可能會掛。若是agent掛掉,整個服務也要摘下來。

百度內部的BNS和Airbnb的SmartStack服務發現框架也是這種作法。因爲咱們內部語言較多,所以選擇了第二種作法。

image

在Consul集羣中,每一個提供服務的節點上都要部署和運行Consul的agent,全部運行Consul agent節點的集合構成Consul Cluster。Consul agent有兩種運行模式:

  • Server

  • Client

這裏的Server和Client只是Consul集羣層面的區分,與搭建在Cluster之上 的應用服務無關。以Server模式運行的Consul agent節點用於維護Consul集羣的狀態,官方建議每一個Consul Cluster至少有3個或以上的運行在Server mode的Agent,Client節點不限。

Client和Server的角色在DDNS是沒有嚴格區分的,請求服務時該服務就是Client,提供服務時候就是Server。

NNDS提供出來的是一個SDK能夠很容易的集成和擴展爲一個獨立的服務而且集成更多的功能。採用agent方式,將在每個服務器部署安裝獲得的agent,支持使用HTTP和grpc進行請求。

image

服務完成啓動並能夠能夠對外提供服務以後,請求agent的接口v1/service/register將其註冊的進入DDNS;

  • 註冊成功則其餘客戶端能夠經過DDNS發現接口獲取到該APP節點信息;

  • 若是註冊失敗,APP會重複嘗試從新註冊,重試三次失敗則報警;

image

假設服務A須要請求服務B,服務名稱爲bbb,直接請求本機的agent接口v1/service/getservice,獲取到bbb的服務節點信息。

對於agent而言,若是服務bbb是第一次被請求,則會請求Consul集羣,獲取到服務bbb的數據以後進行本地從cache並對服務bbb的節點進行watch監控,並定時更新本地的service信息;

若是獲取失敗,給出緣由,若是是系統錯誤則報警;

這是服務框架基本的接口

image

這個就是客戶端調用的封裝,能夠同時支持HTTP和JRTC,在這個以後咱們還作了RBAC的權限控制,咱們但願能調哪些服務都是能夠作權限控制的。

多級緩存

image

client請求到server,server先在緩存裏找,找到就返回,沒有就數據庫找,若是找到就回設到緩存而後返回客戶端。這裏是一個比較簡單的模型。只有一級cache,可是一級cache有可能不夠用,好比說壓測的時候咱們發現,一個redis在咱們的業務狀況下支撐到接口的QPS就是一萬左右,QPS高一點怎麼辦呢?咱們引入多級緩存。

image

越靠近上面的緩存就越小,一級就是服務local cache,若是命中就返回數據,若是沒有就去L1查,若是查到就更新local cache,而且返回數據。若是L1級也沒有就去

L2級查,若是查到數據就更新L1 cache/local cache,並返回數據

image

咱們上面看到的是針對單條內容自己的緩存,在整個棧上來看,gateway也能夠緩存一部分數據,不用請求透穿。這個5的虛線是什麼意思呢?由於數據修改後須要更新,在應用層作有時候會有失敗,因此讀取數據庫binlog來補漏,減小數據不一致的狀況。

image

我一直以爲若是有泛型代碼好寫不少,沒有泛型框架裏面就要大量的反射來代替泛型。

image

多級緩存開始加了以後整個性能的對比,最先PHP是一兩百,改爲Go以後,也不強多少,後面Go和big cache的大概到兩千左右的,可是有一些問題,後面會講當問題。後面基於對象的cache,把對象緩存起來,咱們跑測試的機器是在八核,達到這樣的結果還能夠接受。

熔斷降級

image

接口同時請求內部服務,service七、八、9不同,service5是掛掉的狀態,可是對外的服務還在每次調用,咱們須要減小調用,讓service5恢復過來。

image

打開的狀態下,失敗達到必定的閾值就關起來,等熔斷的窗口結束,達到一個半開的狀態接受一部分的請求。若是失敗的閾值很高就回到關閉的狀態。這個統計的作法就是咱們以前提到的滑動窗口算法。

image

這裏是移植了JAVA hystrix的庫,JAVA裏面有不少作得很不錯的框架和庫,值得咱們借鑑。

經驗總結

通用基礎庫很是重要

剛纔講的性能提高部分,QPS 從600提高到12000,咱們只用了一天,主要緣由就在於咱們經過基礎庫作了大量優化,並且基礎庫作的提高,全部服務都會受益。

善用工具

• generate + framework提高開發效率

• pprof+trace+go-torch肯定性能問題

好比說咱們大量的用generate + framework,經過generate和模板生成不少代碼。查性能的時候,pprof+trace+go-torch能夠幫你節省不少工做。Go-torch是作火焰圖的,Go新版本已經內置了火焰圖的功能。

image

這是根據咱們的表結構生成相應的數據庫訪問代碼,多級緩存是把全部的訪問都要抽象成K-V,K-LIST等訪問模式,每次這麼作的時候手動去寫太繁瑣,咱們就作了一個工具,你用哪個表,工具就生成好,你只須要把它組裝一下。

定位性能問題的時候,火焰圖必定要用

image

好比說定位性能問題就要看最長的地方在哪裏,着力優化這個熱點的code,壓測的時候發現,你們600、900的火火焰圖這裏有問題,優化完成後以下圖

image

其餘經驗總結

例如:

  • 針對熱點代碼作優化

  • 合理複用對象

  • 儘可能避免反射

  • 合理的序列化和反序列化方式

接下來重點講幾個操做:

GC開銷

舉例來講咱們以前有一個服務會從緩存裏面拿到不少ID的list,數據是存成json格式[1,2,3]這樣,發現json的序列化和反序列化性能開銷很是大,基本上會佔到50%以上的開銷。

滴滴講他們的json庫,能夠提高10倍性能,實際上在咱們的場景下提高不了那麼多,大概只能提高一倍,固然提高一倍也是很大的提高(由於你只用改一行代碼就能提高這麼多)。

其次json飯序列化致使的GC的問題也很厲害,最猛的時候可以達到20%CPU,即便是在Go的算法也作得很不錯的狀況下。

最終解決的辦法就是在這裏引入PB替代json。PB反序列化性能(在咱們的狀況下)確實比json好10倍,而且分配的臨時對象少多了,從而也下降了GC開銷。

爲何要避免反射呢?咱們在本地建了local cache,緩存整個對象就要求你不能在緩存以外修改這個對象,可是實際業務上有這個需求。咱們出現過這樣的狀況後就用反射來作deep copy。JAVA反射還能夠用,緣由是jvm會將反射代碼生成JAVA代碼,實際上調用的是生成的代碼。

可是在Go裏面不是,原本Go的性能是和C接近的,大量用了反射以後,性能就跟python接近額。後來咱們就定義一個cloneable的接口,讓程序員手動來作這個clone工做。

壓力測試

咱們主要用的就是ab和Siege,這兩個一般是針對單個系統的壓力測試。實際上用戶在使用的過程中,調用鏈上每個地方均可能出現問題。因此在微服務的狀況下,單個系統的壓力測試,雖然很重要,可是不足以徹底消除咱們系統的全部問題。

舉一個例子,跨年的時候羅老闆要送東西,首先要領東西,領東西是一個接口,接下來一般用戶會再刷一下已購列表看看在不在,最後再確認一下他領到的東西對不對。所以你須要對整個鏈路進行壓測,不能只壓測一下領取接口,這樣多是有問題的。假設你已購列表接口比較慢,用戶領了之後就再刷一下看一看有沒有,沒有的狀況下,通常用戶會持續的刷,致使越慢的接口越容易成爲瓶頸。所以須要合理的規劃訪問路徑,對鏈路上的全部服務進行壓測,不能只關注一個服務。

咱們直接買了阿里雲PTS的服務,他們作法就是在CDN節點上模擬請求,能夠對整個訪問路徑進行模擬。

正在作什麼

分庫分表和分佈式事務

選擇一個數據庫跟你公司相關的運維是相關的。分佈式事務在我這裏比較重要,咱們有不少購買的環節,一旦拆了微服務以後,只要有一個地方錯,就須要對整個進行回滾。咱們如今的作法是手動控制,可是隨着你後面的業務愈來愈多,不可能全部的都手動控制,這時就須要有一個分佈式事務框架,因此咱們如今基於TCC的方式正在作本身的分佈式事務框架。

分庫分表也是一個硬性的需求,咱們在這裏暫時沒有上tidb的緣由主要是DBA團隊對tidb不熟悉。咱們以前的分庫分表也是程序員本身來處理,如今正在作一個框架能同時支持分庫和分表,同時支持hash和range兩種方式。

API gateway

API gateway上面有不少事情能夠作,咱們在熔斷和降級作了一些事情。如今一些Service mesh作的不少事情是把不少工做放在內部API gateway上,是作控制的事情,實際上不該該是業務邏輯關心的事情。咱們也在考慮怎麼把API gateway和SM作結合。

APM

拆了微服務以後,最大的問題是不方便定位具體問題在哪裏。咱們有時候出問題,我叫好幾我的看看各自負責的系統對不對,你們人肉看出問題的地方在哪,這是個比較蛋疼的作法。因入APM+tracing以後,就方便咱們來追蹤問題在哪裏。

容器化

咱們如今的線上環境,仍是在用虛擬機。仿真環境和測試環境已是容器,使用容器有不少好處,我就不一一列舉了。這也是咱們下半年要作的重點工做。

緩存服務化

咱們如今有多級緩存的實現,可是多級緩存仍是一個庫的形式來實現的。若是把緩存抽出來,使用memcached或者redis的協議,抽出來成爲一個獨立的服務。後面的業務系統迭代的時候不用關心緩存自己的擴容縮容策略。

我今天分享的內容就到這兒,謝謝你們!

提問環節

Q:經過你的描述,我知道你之前有JAVA方面的經驗,咱們Go其實沒有一個像JAVA spring cloud這樣比較成熟的微服務的開箱即用的解決方案,如今讓你從新作服務化轉型這個事情,你會怎麼選擇?

方圓:讓一羣php程序員學JAVA比較麻煩,學習Go就比較簡單。其次如今搞一個微服務框架,其實並無那麼難以接受。由於不少開源軟件已經提供了對應的功能,因此要造的輪子其實沒有那麼多。

Q:你的分庫分表框架裏面有支持水平分庫的狀況嗎?

方圓:支持。

Q:衆所周知PHP是世界上最好的語言,大家轉成Go,Go的開發效率比PHP(04:49:50)這兩種狀況。

方圓:我PHP學得不是太好,我本身寫Go確定比PHP快不少。咱們團隊來講,Go的開發效率比PHP略低,可是運行性能卻好太多。

Q:從新選擇一次技術選型,你是上來就選Go,仍是從PHP再演化到Go?

方圓:我確定是上來就選Go,我仍是認爲PHP作rest API沒有啥優點。


ArchSummit演講摘要

本次方圓在ArchSummit的分享主要基於羅輯思惟後端平臺從其餘語言轉型至Go的整個實踐過程,和你們分享轉型的經歷。涉及現有Go語言實現的服務框架技術棧選型,其中包括API Gateway,配置中心/服務註冊發現,客戶端負載均衡,分庫分表,任務調度,多級緩存,熔斷等多個方面。此外也會涉及到服務監控和容器化相關的工做,以及一些常見的痛點。最後對公司將來技術方向進行闡述。大綱以下:

爲何選擇Go語言?

  • 選擇Go語言的緣由:併發,性能

  • 微服務架構

  • 服務框架

  • API Gateway

  • 配置中心/服務註冊發現

  • client side lb

  • 自動分庫分表

  • 分佈式任務調度

  • 多級緩存

  • 熔斷器

  • 服務監控

  • 容器化

遇見的問題:

  • Error處理和缺少泛型

  • CPU使用分析

  • 內存使用分析

  • 公用代碼庫

將來須要的方向:

  • 分佈式事務

  • 分佈式追蹤

  • Service Mesh

相關文章
相關標籤/搜索