Discovery:B站服務註冊與發現
時間是河流
如果時間是一條河流,那麼歷史就是無數的浪花,佇立在河岸回眸,常常有意識的想要改變某一朵浪花。

2015年中,一群少年為愛發電來到B站,建立了一個專案,並寫下了第一行Go程式碼。這個專案後來成為了B站微服務、中介軟體及各類平臺的孵化器,服務註冊與發現模組也應運而生。
時間回溯到2010年11月,zookeeper正式成為Apache的頂級專案,標誌著它是工業級的成熟穩定產品。再到2015年,etcd剛剛釋出了2.0版本,consul才初出茅廬,我們自然而然的選擇了zookeeper作為我們的服務註冊與發現元件。
魔改net/rpc,我們實現了 效能優化
鏈路追蹤trace
鑑權
超時傳遞
資料採集監控
等功能,再加上zookeeper,我們還實現了 net/rpc server服務註冊
net/rpc client服務發現
負載均衡
等功能。
這套框架支撐我們度過了業務快速迭代和頻繁改造的階段,但時間這條河流奔騰不息,轉眼進入了2018年。
當我們佇立河畔,回首日新月異的新潮技術變更,凝視一路跌跌撞撞濺起的浪花時,在服務註冊與發現領域,我們已經落後了。

落後就要捱打
伴隨B站業務的快速發展,微服務的節點數量幾乎指數級增長。zookeeper逐漸在下面的場景不堪重負:
大量長連線及session探活,已經支撐不了辣麼高TPS
CP系統,當機房間腦裂後不可用
沒有專家,出問題全靠運維三寶:重啟、重灌、換機器
於是,我們調研了已經成熟的etcd、consul、eureka等流行的服務註冊與發現系統,經過一番橫向對比之後,遵循 註冊中心不能因為自身的任何原因破壞服務之間本身的可連通性
,我們選擇了AP系統eureka。
但我們團隊整體都是Go技術棧,eureka在部署及維護上讓我們覺得有些不夠得心應手,並且以下幾點在eureka1.0版本中也是已知存在的問題:
靠輪詢拉取節點,無法及時通知
客戶端拉取全量節點,無法按需獲取
eureka服務間資料同步隨著業務節點數增加而成倍增加
沒有完善的日誌支撐節點變更過程查詢
沒有管理面板管理節點
針對以上問題,eureka官方也推出了 Netflix/eureka/wiki/Eureka-2.0-Motivations" target="_blank" rel="nofollow,noindex">2.0版本 計劃,但不幸的是 停止開發了 (幸虧我們選擇了自研,不然就被坑了
所以,我們決定基於eureka的機制,打造屬於B站的服務註冊與發現系統: Discovery

Discovery
時間進入2018,我們也順應技術的大潮,打造了基於k8s的PAAS平臺,大量的業務在準備和正在遷入。我們制定准入規範,將業務標識 appid
、容器啟動行為 entrypoint
、服務的 healthcheck
等等進行了統一。
最關鍵的,我們需要統一 服務註冊
!
Discovery在這個大背景下應運而生,設計之初,我們與運維童鞋討論了很多細節,最終拍定以下設計目標:
實現AP服務註冊與發現系統,保證資料最終一致性
與PAAS平臺結合,多種釋出方式的自動服務註冊
網路閃斷時服務可開啟自我保護,保證健康的服務可用
實現各個語言sdk,基於HTTP協議保證互動簡易
基本抽象
在Discovery中我們以 appid
作為服務的標識,以 hostname
定位例項 instance
。定義了三種角色 server
provider
consumer
,分別代表:
角色 | 功能 |
---|---|
server | Discovery服務節點,提供儲存例項資訊、檢查和剔除無效節點、自我保護等功能 |
provider | 服務提供者,提供包括註冊 register 、30s週期心跳 renew 、取消註冊 cancel 等功能 |
consumer | 服務消費者,基於 appid 獲取所需服務的節點資訊,並可選30s週期的長輪詢監聽服務及時變更狀態通知 |
instance | 儲存在discovery內的例項資訊抽象物件,包含 appid hostname addrs metadata 等資訊 |
架構圖

基本邏輯
provider
啟動後會請求Discovery的 register
介面進行例項資訊註冊,註冊成功後要進行30s週期一次的 renew
心跳,用於維護Discovery內線上狀態。
consumer
啟動後請求Discovery的 fetch
介面,根據 appid
獲取所有的例項資訊。如果有實時接收 appid
變更的需要,可以請求 poll
介面進行長輪詢,首次請求會拿到 server
節點下發的 latestTimestamp
( 表示 appid
的最後變更時間,該時間為 server
自身時間且不 server
間同步 )。當再有變更發生時Discovery更新自身 latestTimestamp
,與 consumer
請求時攜帶的 latestTimestamp
對比,如超過則下發最新例項資訊,否則維持長輪詢連線直到30s超時或有變更發生。
重要邏輯
- 複製(Peer to Peer),資料一致性的保障:
-
appid
註冊時根據當前時間生成dirtyTimestamp
,Discovery的serverA
向serverB
同步(register
)時,serverB
可能有以下兩種情況:- 返回
-404
則serverA
攜帶dirtyTimestamp
向serverB
發起註冊請求,把最新資訊同步:-
serverB
中不存在例項 -
serverB
中dirtyTimestamp
較小
-
- 返回
-409
serverB
不同意採納serverA
資訊,且返回自身資訊,serverA
使用該資訊更新自身
- 返回
-
appid
註冊成功後,provider
每30s發起一次renew
請求,處理流程同上
-
-
instance
管理- 正常檢測模式,隨機分批踢掉無心跳
instance
節點,儘量避免單應用節點被一次全踢 - 網路閃斷和分割槽時自我保護模式
- 60s內丟失大量(小於
instance
總數*2*0.85)心跳數,“好”“壞”instance
資訊都保留 - 所有
server
都會持續提供服務,單個server
的註冊和發現功能不受影響 - 最大保護時間,防止分割槽恢復後大量原先
instance
真的已經不存在時,一直處於保護模式
- 60s內丟失大量(小於
- 正常檢測模式,隨機分批踢掉無心跳
-
consumer
客戶端- 長輪詢+
server
推送,服務發現準實時 - 訂閱式,只需要關注想要關注的
appid
的instance
列表變化 - 快取例項
instance
列表資訊,保證與server
網路不通等無法訪問到server
情況時原先的instance
可用
- 長輪詢+
特別注意
server
間同步複製是需要時間的,那如何保證 consumer
請求 serverB
時,因為攜帶的 latestTimestamp
是來自serverA,但 serverB
晚於該次請求才收到同步事件,而導致獲取的節點資訊不一致?
我們通過 consumer
啟動後,從 nodes
介面獲取到Discovery的所有 server
節點後,隨機選取一個 serverA
進行 fetch
poll
等請求,保證在 consumer
生命週期內,例項資訊和時間資訊始終來自同一個 serverA
。除非遇到網路等錯誤才切換節點到 serverB
並清空 latestTimestamp
,再當做 首次 請求重新拉取 appid
的全部例項資訊和時間資訊。
多註冊中心
Discovery的同步複製機制天生好支援多註冊中心。

我們用 zone
來表示機房,假設 zoneA
和 zoneB
的Discovery叢集之間要相互同步,那我們只需要將 zoneA
當做 zoneB
的特殊 server
節點,同理將 zoneB
當做 zoneA
的 特殊server
節點。
當 zoneA
的 serverA
收到 appid1
的註冊請求,並同步給內部的其他 server
後,再同步給 server-zoneB
, zoneB
即可複製到 appid1
的例項資訊。
但 zoneB
內部 server
間同步後不再需要同步回 zoneA
,所以 特殊server
就是指在傳送同步請求時,判斷該請求是否來自相同的 zone
,是的話就像 zoneA
同步給 zoneB
,否的話就像 zoneB
內部同步後不再向其他 zone
同步。
注: zoneA
與 zoneB
間,建議使用SLB進行負載均衡
與PAAS在一起

我們的PAAS平臺已經集成了Discovery的服務註冊,也就是 provider
能力。業務只需要正常釋出就可以直接註冊到Discovery,並依賴pod的生命週期進行 renew
心跳請求管理。
如果服務需要提供RPC、叢集、權重等自定義資訊,則只需要暴露 /register
介面並返回 map[string]string
格式的 json
資料,PAAS在啟動例項後和註冊資訊前,通過回撥該介面獲取資訊,將資訊作為 metedata
同時註冊到Discovery。
基於此,依賴服務( consumer
)就可以獲取到例項資訊,並對服務進行訪問。
管理節點

我們還實現了簡單的管理能力,可以基於 appid
和 環境資訊
獲取到所有例項資訊。並基於此擴充套件了 查詢依賴服務
生成CPU和記憶體profile圖 火焰圖
等功能。
奔騰不息的河流
當我們再一次佇立在河岸回眸,發現時光的浪花翻騰,但總有那麼幾朵浪花醜陋,讓人想要在今後扔石子時,扔的漂亮~
結語
Discovery已經服務於B站幾萬+的例項規模,通過藉此總結我們在服務註冊與發現領域的實踐經驗,希望對業界閱讀此文的童鞋能夠有所幫助和啟發。同時,我們也希望收到大家的反饋意見,詳情請看Discovery開源專案【 點我到Github 】。
本文作者:冠冠愛看書