從負載均衡的這種模式下其實有兩個主要的問題: 一是中心化,整個系統都基於負載均衡器,負載均衡就至關於整個業務的中心,雖然咱們能夠經過一些高可用手段來保證,但其實內部流量一般是巨大的,很容易出現性能瓶頸 二是增長了一次TCP交互java
固然也有不少好處,好比能夠作一些負載均衡、長連接維護、分佈式跟蹤等,這不是本文重點node
事件隊列
中,後續每次客戶端拉取只返回增量數據,這樣服務端的忘了壓力就會小不少
服務端註冊表結構體Registry主要包含三部分信息: lock(讀寫鎖)、apps(應用對應信息)、eventQueue(事件隊列) Lock: 註冊中心是典型的讀多寫少的應用,server端註冊表可能同時提供給N個服務進行讀取,因此這裏採用讀寫鎖 apps: 保存應用對應的信息, 其實後面寫完發現,不必使用,只使用基礎的map就能夠搞定 eventQueue: 每次註冊表變動都寫入事件到裏面windows
// Registry 註冊表
type Registry struct {
lock sync.RWMutex
apps sync.Map
duration time.Duration
eventQueue *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是一個一致性的保證,eureka裏面主要是經過拼接全部的服務名稱和節點的個數來生成的一個字符串,這裏咱們也採用這種方式,bash
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
}
複製代碼
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\n", 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三種機制來保證客戶端與註冊中心的註冊表的同步app
其實一個工業級的註冊中心仍是很麻煩的,好比註冊表中那個事件隊列,我如今的實現只有一個節點能獲取增量,其餘的都會經過hashcode來觸發全量拉取,後續文章裏面會相信介紹下,這塊緩存和定時器來實現增量數據的打包負載均衡
其實在go裏面你們註冊中心都是基於etcd、consul直接watch去作的,基本上能夠完成eureka服務的8/9十的功能,可是當須要與公司現有的java作集成,可能就須要eureaka這種註冊中心了分佈式
未完待續 關注公共號: 布衣碼農
更多精彩內容能夠查看www.sreguide.com