摘要:背景基于負載均衡的服務調用基于負載均衡的服務相互調用指的是通過基于等負載均衡軟件來構建一個負載均衡服務,所有的服務調用都通過負載均衡器從負載均衡的這種模式下其實有兩個主要的問題一是中心化,整個系統都基于負載均衡器,負載均衡就相當于整個業
背景 基于負載均衡的服務調用基于負載均衡的服務相互調用指的是通過基于Lvs、Haproxy、Nginx等負載均衡軟件來構建一個負載均衡服務,所有的服務調用都通過負載均衡器
從負載均衡的這種模式下其實有兩個主要的問題: 一是中心化,整個系統都基于負載均衡器,負載均衡就相當于整個業務的中心,雖然我們可以通過一些高可用手段來保證,但其實內部流量通常是巨大的,很容易出現性能瓶頸 二是增加了一次TCP交互
當然也有很多好處,比如可以做一些負載均衡、長鏈接維護、分布式跟蹤等,這不是本文重點
基于注冊中心的服務調用所有的服務都啟動后都通過注冊中心來注冊自己,同時把注冊中心里面的服務信息拉回本地,后續調用,就直接檢查本地的服務和節點信息來進行服務節點的調用
注冊中心中的注冊表
每個服務節點都會來注冊中心進行服務注冊,那數據如何在服務端進行保存呢,其實就是注冊表,其實等同于windows 里面的注冊表,每個服務都來注冊,把自己的信息上報上來,然后注冊中心吧注冊表,返回給client端,那服務之間就知道要調用服務的節點啦
注冊中心事件隊列
微服務注冊注冊中心通常會大量的服務注冊, 那不能每次客戶端來請求的時候,服務端都返回全量的數據,在數據傳輸的設計中,通常會有一種增量同步,其實在注冊中心中也類似 注冊中心通過將最近的服務變更事件保存在一個事件隊列中,后續每次客戶端拉取只返回增量數據,這樣服務端的忘了壓力就會小很多
注冊中心hashcode
增量數據有一個問題就是,如果客戶端錯過啦某些事件,比如事件隊列滿了,則客戶端與注冊中心的注冊表就會不一致, 所以eureka里面引入了一個hashcode的概念,通過比對hashcode是否相同, 如果不同則客戶端需要重新全量拉取
代碼實現 系統架構
系統整體上分為兩個端:客戶端(Client)和注冊中心(Server) Server: 提供服務注冊和獲取注冊表的接口, 同時本地把保存服務和節點的對應信息,變更事件寫入eventQueue Client: 調用server接口進行服務注冊, 同時調用注冊表拉取接口進行注冊表拉取,保存懂啊LocalRegistry
應用與節點信息
Server端的服務注冊表里面的服務和節點的信息,我通過Application和lease來維護 Application: 代表一個應用,里面會包含服務對應的節點信息 Lease: 維護一個節點的信息,比如心跳信息
服務端注冊表 注冊表結構體
服務端注冊表結構體Registry主要包含三部分信息: lock(讀寫鎖)、apps(應用對應信息)、eventQueue(事件隊列) Lock: 注冊中心是典型的讀多寫少的應用,server端注冊表可能同時提供給N個服務進行讀取,所以這里采用讀寫鎖 apps: 保存應用對應的信息, 其實后面寫完發現,沒必要使用,只使用基礎的map就可以搞定 eventQueue: 每次注冊表變更都寫入事件到里面
// Registry 注冊表
type Registry struct {
lock sync.RWMutex
apps sync.Map
duration time.Duration
eventQueue *EventQueue
}
注冊表服務注冊
注冊流程主要分為下面幾部分:
從注冊表獲取對應的應用Application
調用Application的add接口添加節點
為節點創建一個Lease
保存節點信息到Application.Node里
將事件寫入到eventQueue
// Registr 注冊服務
func (r *Registry) Registr(name, node string) bool {
r.lock.Lock()
defer r.lock.Unlock()
app := r.getApp(name)
if app == nil {
app = NewApplication(name)
r.apps.Store(name, app)
}
if lease, ok := app.add(node, r.duration); ok {
r.eventQueue.Push(&Event{lease: lease, action: ADD})
return true
}
return false
}
注冊表拉取
全量拉取通過all接口拉取全量的返回的是服務對應的節點切片 增量拉取通過details接口返回增量的變更事件和服務端注冊表的hashcode
// all 全量拉取
func (r *Registry) all() map[string][]string {
r.lock.RLock()
defer r.lock.RUnlock()
apps := make(map[string][]string)
r.apps.Range(func(k, v interface{}) bool {
name, app := k.(string), v.(*Application)
nodes := []string{}
for key := range app.Node {
nodes = append(nodes, key)
}
apps[name] = nodes
return true
})
return apps
}
// details 增量拉取
func (r *Registry) details() []*Event {
r.lock.RLock()
defer r.lock.RUnlock()
events := []*Event{}
for {
event := r.eventQueue.Pop()
if event == nil {
break
}
events = append(events, event)
}
return events
}
hashcode
hashcode是一個一致性的保證,eureka里面主要是通過拼接所有的服務名稱和節點的個數來生成的一個字符串,這里我們也采用這種方式,
func (r *Registry) hashCode() string {
r.lock.RLock()
defer r.lock.RUnlock()
hashCodes := []string{}
r.apps.Range(func(_, value interface{}) bool {
app := value.(*Application)
hashCodes = append(hashCodes, app.HashCode())
return true
})
sort.Sort(sort.StringSlice(hashCodes))
return strings.Join(hashCodes, "|")
}
客戶端注冊表
數據結構
客戶端本地注冊表其實就比較簡單了,只需要存儲服務和節點的對應信息即可
// LocalRegistry 本地注冊表
type LocalRegistry struct {
lock sync.RWMutex
apps map[string][]string
}
客戶端邏輯架構
啟動流程: 啟動時客戶端首先調用注冊接口進行自我注冊,然后調用poll拉取全量注冊表
func (c *Client) start() {
c.wg.Add(1)
c.registr()
c.poll()
go c.loop()
}
主循環
func (c *Client) loop() {
timer := time.NewTimer(time.Second)
for {
// 從服務的拉取增量事件,details內部會直接應用,然后返回服務端返回的注冊表的hashcode
respHashCode := c.details()
localHashCode := c.registry.hashCode()
// 如果發現本地和服務的的注冊表的hashcode不同,則全量拉取
if respHashCode != localHashCode {
fmt.Printf("client app %s node %s poll hashcode: %s
", c.App, c.Name, respHashCode)
c.poll()
}
select {
case <-timer.C:
timer.Reset(time.Second)
case <-c.done:
c.wg.Done()
return
}
}
}
驗證邏輯
func main() {
// 生成服務端
server := NewServer("aliyun", time.Second)
// 注冊兩個test服務的節點
clientOne := NewClient("test", "1.1.1.1:9090", server)
clientOne.start()
clientTwo := NewClient("test", "1.1.1.2:9090", server)
clientTwo.start()
// 注冊兩個hello服務的節點
clientThree := NewClient("hello", "1.1.1.3:9090", server)
clientThree.start()
clientFour := NewClient("hello", "1.1.1.4:9090", server)
clientFour.start()
time.Sleep(time.Second * 3)
// 驗證每個服務節點的注冊表的hashcode是否一致
println(clientOne.details())
println(clientTwo.details())
println(clientThree.details())
println(clientFour.details())
println(clientTwo.details() == clientOne.details())
println(clientThree.details() == clientFour.details())
println(clientOne.details() == clientFour.details())
clientOne.stop()
clientTwo.stop()
clientThree.stop()
clientFour.stop()
}
通過結果我們可以看出,節點增量拉取了注冊表,同時如果發現與本地的hashcode不同就進行全量拉取,并最終達成一致
lr event add 1.1.1.3:9090 hello
lr event add 1.1.1.4:9090 hello
lr event add client app hello node 1.1.1.4:9090 poll hashcode: hello_2|test_2
1.1.1.1:9090 test
lr event add 1.1.1.2:9090 test
client app test node 1.1.1.1:9090 poll hashcode: hello_2|test_2
client app test node 1.1.1.2:9090 poll hashcode: hello_2|test_2
client app hello node 1.1.1.3:9090 poll hashcode: hello_2|test_2
hello_2|test_2
hello_2|test_2
hello_2|test_2
hello_2|test_2
true
true
true
總結
微服務注冊中心注冊表的這種實現機制,到這基本上就明白了,注冊中心 通過增量、全量、hashcode三種機制來保證客戶端與注冊中心的注冊表的同步
其實一個工業級的注冊中心還是很麻煩的,比如注冊表中那個事件隊列,我現在的實現只有一個節點能獲取增量,其他的都會通過hashcode來觸發全量拉取,后續文章里面會相信介紹下,這塊緩存和定時器來實現增量數據的打包
其實在go里面大家注冊中心都是基于etcd、consul直接watch去做的,基本上可以完成eureka服務的8/9十的功能,但是當需要與公司現有的java做集成,可能就需要eureaka這種注冊中心了
未完待續 關注公共號: 布衣碼農
更多精彩內容可以查看www.sreguide.com
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/7194.html
摘要:先來說第種類型,即遠程通信的,看的源碼,調用過程大致內容就是將通過遠程通信將信息傳遞給服務器端,服務器端接收到該信息后,找到對應的本地,然后通過反射執行相應的方法,將方法的返回值再通過遠程通信將結果傳遞給客戶端。 showImg(https://segmentfault.com/img/remote/1460000015285374?w=640&h=363); 上圖是服務消費的主過程:...
摘要:總體介紹在互聯網金融行業一百多億其實也算不上大平臺,也就是二級陣營吧,其實每次的架構升級都是隨著業務重大推進而伴隨的,在前一代系統架構上遇到的問題,業務開發過程中積累一些優秀的開發案例,在下一代系統開發中就會大力推進架構升級。 回想起從公司成立敲出的第一行代碼算起到現在也快三年了,平臺的技術架構,技術體系也算是經歷了四次比較重大的升級轉化(目前第四代架構體系正在進行中),臨近年底也想抽...
摘要:總體介紹在互聯網金融行業一百多億其實也算不上大平臺,也就是二級陣營吧,其實每次的架構升級都是隨著業務重大推進而伴隨的,在前一代系統架構上遇到的問題,業務開發過程中積累一些優秀的開發案例,在下一代系統開發中就會大力推進架構升級。 回想起從公司成立敲出的第一行代碼算起到現在也快三年了,平臺的技術架構,技術體系也算是經歷了四次比較重大的升級轉化(目前第四代架構體系正在進行中),臨近年底也想抽...
摘要:為了解決這一系列問題,微博從年開發了語言的框架,并基于此完成了服務化改造。這些經歷之下微博也積累了一套服務治理型的服務化體系。的版,所要解決的是微博平臺內部服務之間的調用,因此協議時,其實并沒有考慮到跨語言的問題,用的是對比較友好的。 showImg(https://segmentfault.com/img/remote/1460000012601596?w=1080&h=606); ...
閱讀 540·2021-08-31 09:45
閱讀 1647·2021-08-11 11:19
閱讀 883·2019-08-30 15:55
閱讀 821·2019-08-30 10:52
閱讀 2845·2019-08-29 13:11
閱讀 2924·2019-08-23 17:08
閱讀 2833·2019-08-23 15:11
閱讀 3066·2019-08-23 14:33