負載均衡算法之二 - 以 Golang 方式

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"過門","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"經過","attrs":{}},{"type":"link","attrs":{"href":"https://hedzr.com/golang/algorithm/go-load-balancer-1","title":"","type":null},"content":[{"type":"text","text":"上一篇","attrs":{}}]},{"type":"text","text":"對基本算法的列舉之後,我們注意到基本算法的堆疊是個比較重要的特性。此外,怎麼樣對 factor 做約束也是一個比較重要的特性,因爲它可以幫助決定堆疊後的 LB 如何完成第二級選擇。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"建設類庫","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以最終,我建立了一個 golang 的 lb 類庫:","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/hedzr/lb","title":"","type":null},"content":[{"type":"text","text":"這裏","attrs":{}}]},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其實 lb 類庫真的是多的要命了,不過我也有特定的目的(前面、上一篇也都有提到過):","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"線程安全","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"可堆疊的設計","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"可以對 factor 進行約束","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所謂線程安全無外乎解決 go routines data racing 問題。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可堆疊則是指基本算法應該能被堆疊起來,形成複合的定製的 LB 算法。比如說將 wrr 和 random 堆疊起來可以形成加權隨機算法;或者爲一致性hash增加權重也不是不可能。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對 factor 進行約束是我們的設計目標之一,這是爲了讓調用者能夠傳入特定的二級算法的解釋器,它將會解釋滿足了什麼樣的約束條件之後就可以抽出什麼樣的結果。它也是爲了完整的可堆疊的組成成分之一。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此外,算法是要可擴充的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後,由於算法研究是一種非正式的研究行爲,所以這次不打算針對高頻交易進行優化,因爲那樣做了之後往往都會面目全非了。儘管如此,由於對每種算法有所選擇的原因,所以性能還是很不錯的,爲免競爭,不做性能測試了。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"可嵌套的設計","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"怎麼樣設計我們的 LB 庫,使得基本算法,特別是加權這樣的特性能夠嵌套在別的基本算法之上,形成複合的 LB 算法呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"例如加權的隨機算法,它和加權的輪詢算法確確實實還是有區別的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"基本上來說,嵌套套娃的實作已經涉及到強人工智能的領域,也就是我如何創建我自己的終極命題。所以呢,WTF,我只是胡說在八道,其實其關鍵就在於讓 child interface 包含一個 到 parent interface 的嵌入,從而達到隱含的等同性的目的。我這就是傳授了 Golang 中的架構設計之終極大法了哦。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"說得有點繞。具體到我們的 LB 設計體系中呢,注意到我們認爲你會給 Balancer 添加一堆 Peer(s),然後在它們上做均衡性計算,計算的依據之一爲 Factor 因素,當然,對於輪詢和隨機這些算法來說並不需要 factor 的參與。所以,當我們想要在 Random Balancer 上疊加 Weighted 算法時,我們需要一個承上啓下的 A2B interface,它是 Weighted 算法的 Factor,又是 Random 算法的 Peer,這就解決問題了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後在我們的每一個 Next 中會鑑別 next-picked peer 是不是也是一個 BalancerLite,如果是的話就遞歸進去,從而達到套娃的目的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"比如說,我們的最終 random 算法是這樣子的(wrr 中的 Next 也是相似的):","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func (s *randomS) Next(factor lbapi.Factor) (next lbapi.Peer, c lbapi.Constraintenable) {\n next = s.miniNext()\n if fc, ok := factor.(lbapi.FactorComparable); ok {\n next, c, ok = fc.ConstrainedBy(next)\n } else if nested, ok := next.(lbapi.BalancerLite); ok {\n next, c = nested.Next(factor)\n }\n return\n}\n\nfunc (s *randomS) miniNext() (next lbapi.Peer) {\n s.rw.RLock()\n defer s.rw.RUnlock()\n\n l := int64(len(s.peers))\n ni := atomic.AddInt64(&s.count, inRange(0, l)) % l\n next = s.peers[ni]\n return\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"它一方面做了 BalancerLite 類型診斷然後遞歸到 child.Next,所以當你在 Balancer.Add(peers) 的時候如果添加對是一個 Balancer 耦合的 Peer 對象的話,嵌套堆疊就能夠順利地級聯下去。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另一方面,Next 還針對可被約束的 Factor(即 FactorComparable)做一個約束限定操作(即 ConstrainedBy),這個約束限定操作可以被你用於增強的 LB 目的上。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面就會介紹如何在我們這套 LB 類庫上做擴展和定製。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"額外的算法實現","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在這裏呢,我們挨個介紹一下兩種擴展思路的實例。它們都包含在正式的 LB 庫 release 中,一面是做示範,一面是免去你可能的重複編碼的無謂浪費——無須擔心,實際的生產中各種變態的需求我都有遇到過,你完全不必擔心沒得代碼可寫,通用類庫怎麼也不能替你幹完一切事的。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"加權隨機算法:對 random 做 weighted 堆疊","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"既然 wrr 能夠加權,random 能夠隨機,那麼藉助於已經建設好的基礎設施我們就可以堆疊兩者。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在這個需求裏,需要用到 Next 中的這個分支所提供的能力:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":" if nested, ok := next.(lbapi.BalancerLite); ok {\n next, c = nested.Next(factor)\n }\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們的實現方法是,製作一個新的 Peer 實現,讓它是一個 random lb,並且能夠提供一個權重值,因爲首先來講 wrr 需要我們添加給它的都是 WeightedPeer,這就是代碼中的 wpPeer struct,它實現了 WBPeer interface。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"package wrandom\n\nimport (\n \"github.com/hedzr/lb/lbapi\"\n \"github.com/hedzr/lb/wrr\"\n)\n\nfunc New(opts ...lbapi.Opt) lbapi.Balancer { return wrr.New(opts...) }\n\nfunc WithWeightedBalancedPeers(peers ...WBPeer) lbapi.Opt {\n return func(balancer lbapi.Balancer) {\n for _, b := range peers {\n if bp, ok := b.(lbapi.WeightedPeer); ok {\n balancer.Add(bp)\n }\n }\n }\n}\n\nfunc NewPeer(weight int, gen func(opts ...lbapi.Opt) lbapi.Balancer, opts ...lbapi.Opt) WBPeer {\n return &wpPeer{\n weight: weight,\n lb: gen(opts...),\n }\n}\n\n// WBPeer is a weighted, balanced peer.\ntype WBPeer interface {\n lbapi.WeightedPeer\n lbapi.Balancer\n}\n\ntype wpPeer struct {\n weight int\n lb lbapi.Balancer\n}\n\nfunc (w *wpPeer) String() string { return \"wpBeer\" }\n\nfunc (w *wpPeer) Weight() int { return w.weight }\n\nfunc (w *wpPeer) Next(factor lbapi.Factor) (next lbapi.Peer, c lbapi.Constrainable) {\n return w.lb.Next(factor)\n}\nfunc (w *wpPeer) Count() int { return w.lb.Count() }\nfunc (w *wpPeer) Add(peers ...lbapi.Peer) { w.lb.Add(peers...) }\nfunc (w *wpPeer) Remove(peer lbapi.Peer) { w.lb.Remove(peer) }\nfunc (w *wpPeer) Clear() { w.lb.Clear() }\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"按照上一篇中提供的 lbapi 的 interfaces 定義,在示例中我們構造了一個 wbPeer 的 lbapi.Peer 實現。L38 實現了 Peer,L40 幫助實現了 WeightedPeer,而 L42 則完成了 Balancer 的實現。注意到 wpPeer 結構中包含一個 random balancer 的實例 w.lb。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"WithWeightedBalancedPeers(peers ...WBPeer)","attrs":{}}],"attrs":{}},{"type":"text","text":" 需要一組通過 NewPeer 所創建的 Peer 對象,這些對象最後被 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"New","attrs":{}}],"attrs":{}},{"type":"text","text":" 用來構造一個 wrr LB。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以我們可以這樣使用它:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"package wrandom_test\n\nimport (\n \"github.com/hedzr/lb/lbapi\"\n \"github.com/hedzr/lb/random\"\n \"github.com/hedzr/lb/wrandom\"\n \"testing\"\n)\n\ntype exP string\n\nfunc (s exP) String() string { return string(s) }\n\nfunc withPeers(peers ...lbapi.Peer) lbapi.Opt {\n return func(balancer lbapi.Balancer) {\n for _, p := range peers {\n balancer.Add(p)\n }\n }\n}\n\nfunc TestWR1(t *testing.T) {\n peer1, peer2, peer3 := exP(\"172.16.0.7:3500\"), exP(\"172.16.0.8:3500\"), exP(\"172.16.0.9:3500\")\n peer4, peer5 := exP(\"172.16.0.2:3500\"), exP(\"172.16.0.3:3500\")\n\n lb := wrandom.New(\n wrandom.WithWeightedBalancedPeers(\n wrandom.NewPeer(3, random.New, withPeers(peer1, peer2, peer3)),\n wrandom.NewPeer(2, random.New, withPeers(peer4, peer5)),\n ),\n )\n\n sum := make(map[lbapi.Peer]int)\n\n for i := 0; i < 5000; i++ {\n p, _ := lb.Next(lbapi.DummyFactor)\n sum[p]++\n }\n\n // results\n for k, v := range sum {\n t.Logf(\"%v: %v\", k, v)\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當然,這裏的代碼是爲了測試使用的。在業務代碼中你可以將上述的一體化初始化語句拆分爲多步驟的,在運行時刻對 wpPeer 對象做 Add/Remove 來增減其從屬的後端而不是直接加入 peer1, peer2, ..., peer5。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一個參考結果是:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"bash"},"content":[{"type":"text","text":" wr_test.go:42: 172.16.0.8:3500: 1018\n wr_test.go:42: 172.16.0.3:3500: 1012\n wr_test.go:42: 172.16.0.9:3500: 1026\n wr_test.go:42: 172.16.0.2:3500: 988\n wr_test.go:42: 172.16.0.7:3500: 956\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"5 個後端節點最終","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"均分","attrs":{}},{"type":"text","text":"了全部請求,這就是我們想要的結果。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同樣的道理,隨機算法不必有絕對的均分,允許一定的統計學上的偏差。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"加權的版本比較算法:對 factor 做約束","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"任意組合兩種已有的 balancer 算法,上一節已經作出了示範,應該說還算是比較容易實作的吧。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在來製作一個全新的加權算法 WV。它包含一個 semver 比較器集合,我們認爲調用者將會傳入一組帶有具體版本號的 host+port 集合作爲 factor ,WV 要做的就是:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"按權重比例抽出某一個 semver 比較器 X","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"將 factor 傳入 X,從而挑選出一個 host+port+version 使得其 version 滿足 X 所約束的條件。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們使用了 \"github.com/Masterminds/semver\" 來做 semver 相關操作。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"New","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先需要一個 wrr 及其 peers 的專屬構建器:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"package version\n\nfunc New(opts ...lbapi.Opt) lbapi.Balancer { return wrr.New(opts...) }\n\nfunc WithConstrainedPeers(cs ...lbapi.Constrainable) lbapi.Opt {\n return func(balancer lbapi.Balancer) {\n for _, vp := range cs {\n balancer.Add(vp)\n }\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"它看起來平平無奇,除了使用一個 Constrainable 接口的 peer 輸入之外。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以我們將會爲 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"version.New(version.WithConstrainedPeers(...))","attrs":{}}],"attrs":{}},{"type":"text","text":" 傳入一堆 Constrainable 這樣的 peers。回顧一下 Constrainable 的定義,它本身就是滿足 Peer 接口的。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"constrainablePeer","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在來實現這樣的特殊 peer:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"package version\n\nimport (\n \"fmt\"\n \"github.com/Masterminds/semver\"\n \"github.com/hedzr/lb/lbapi\"\n)\n\nfunc NewConstrainablePeer(constraints string, weight int) (peer lbapi.Constrainable) {\n p := &constrainablePeer{\n constraints: constraints,\n weight: weight,\n constraintsObj: nil,\n }\n vc, err := semver.NewConstraint(constraints)\n if err == nil {\n p.constraintsObj = vc\n peer = p\n }\n return\n}\n\ntype constrainablePeer struct {\n constraints string\n weight int\n constraintsObj *semver.Constraints\n}\n\nfunc (s *constrainablePeer) String() string { return s.constraints }\nfunc (s *constrainablePeer) Weight() int { return s.weight }\nfunc (s *constrainablePeer) Constraints() *semver.Constraints { return s.constraintsObj }\nfunc (s *constrainablePeer) CanConstrain(o interface{}) (yes bool) {\n _, yes = o.(*semver.Version)\n return\n}\nfunc (s *constrainablePeer) Check(factor interface{}) (satisfied bool) {\n if s.constraintsObj == nil {\n var err error\n s.constraintsObj, err = semver.NewConstraint(s.constraints)\n if err != nil {\n fmt.Printf(\"illegal constraints: %q. %v\\n\", s.constraints, err)\n }\n }\n\n if v, ok := factor.(*semver.Version); ok {\n satisfied = s.constraintsObj.Check(v)\n } else if v, ok := factor.(interface{ Version() *semver.Version }); ok {\n satisfied = s.constraintsObj.Check(v.Version())\n }\n return\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"constrainablePeer","attrs":{}}],"attrs":{}},{"type":"text","text":" 的關鍵之處在於它的 Check 函數,在這裏它會試圖解開 factor 中包含的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"*semver.Version","attrs":{}}],"attrs":{}},{"type":"text","text":" 值 V,然後運用約束器 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"s.constraintsObj","attrs":{}}],"attrs":{}},{"type":"text","text":" 來檢查 V 是不是滿足約束條件。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"翻譯成直白的話就是:我是版本號 1.3,我是不是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"< 1.3.x","attrs":{}}],"attrs":{}},{"type":"text","text":" 啊?","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"constrainablePeer","attrs":{}}],"attrs":{}},{"type":"text","text":" 就會說,是的。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"NewConstrainablePeer","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"constrainablePeer","attrs":{}}],"attrs":{}},{"type":"text","text":" 準備了一個公開函數 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"NewConstrainablePeer","attrs":{}}],"attrs":{}},{"type":"text","text":",所以:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"var testConstraints = []lbapi.Constrainable{\n version.NewConstrainablePeer(\"<= 1.1.x\", 2),\n version.NewConstrainablePeer(\"^1.2.x\", 4),\n version.NewConstrainablePeer(\"^2.x\", 11),\n version.NewConstrainablePeer(\"^3.x\", 3),\n}\n\n lb := version.New(version.WithConstrainedPeers(testConstraints...))\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"TestCase","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在需要的是準備好 factor,然後來做測試:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func initFactors() version.BackendsFactor {\n fa := version.NewBackendsFactor(rr.New)\n fa.AddPeers(\n version.NewBackendFactor(\"1.1\", \"172.16.0.6:3500\"),\n version.NewBackendFactor(\"1.3\", \"172.16.0.7:3500\"),\n version.NewBackendFactor(\"2.0\", \"172.16.0.8:3500\"),\n version.NewBackendFactor(\"3.13\", \"172.16.0.9:3500\"),\n )\n return fa\n}\n\nfactor := initFactors()\npeer, c := lb.Next(factor)\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在這裏我們略過了 NewBackendsFactor 以及 NewBackendFactor 的大部分實現細節,其中值得特別提及的是 fs 也就是 backendsFactor 的 ConstrainedBy:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func (fa *backendsFactor) ConstrainedBy(constraints interface{}) (peer lbapi.Peer, c lbapi.Constrainable, satisfied bool) {\n if cc, ok := constraints.(lbapi.Constrainable); ok {\n var lb lbapi.Balancer\n \n // for this object cc, build a lb and associate with it\n fa.crw.RLock()\n if _, ok := fa.constraints[cc]; !ok {\n fa.crw.RUnlock()\n\n lb = fa.generator()\n fa.crw.Lock()\n fa.constraints[cc] = lb\n fa.crw.Unlock()\n } else {\n lb = fa.constraints[cc]\n fa.crw.RUnlock()\n lb.Clear()\n }\n\n // find all satisfied backends/peers and add them into lb\n for _, f := range fa.backends {\n if cc.Check(f) {\n satisfied = true\n lb.Add(f)\n }\n }\n\n // now, pick up the next peer of them\n peer, c = lb.Next(lbapi.DummyFactor)\n if c == nil {\n c = cc\n }\n }\n return\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一般地講,在這裏 fa.generator 應該是一個 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"rr.New","attrs":{}}],"attrs":{}},{"type":"text","text":"(通過 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"fa := version.NewBackendsFactor(rr.New)","attrs":{}}],"attrs":{}},{"type":"text","text":"),也即 lb 會被創建爲一個輪詢算法的 LB,或者別的亦可。然後全部滿足約束條件(通過 Check)的後端將被當作是一個 peers 集合讓 lb 進行選擇。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"感興趣的朋友可以去到源碼查閱。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"出於代碼邏輯清晰的緣故,上面採用了代價較高的運算,也就是每次都對全部 fa.backends 做一次 Check 比較。可想而知對於大規模的後端它不是好的方式。以後將會在此增加 cache 並做增量式添加刪除,以便儘可能地消除 Check all。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"小小結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有何用意?這個加權的版本範圍比較算法,其實是我以前做 api gw 時的一個念頭,其目的在於做到整個自研框架的灰度上線測試。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當然,作爲一個完整的框架要達成灰度上線目標,需要的是全方位的適配。至少會包含這些:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"可以按照版本範圍的約束表達式來進行權重分配","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"能夠透明地完成 incoming requests 的 dispatch","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"利用 cmdr 鎖提供的配置文件自動監控、自動載入和合並、自動觸發變更機制,以便在 microservice 在線的狀態下實時地調整權重分配設定","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"如果沒有配置文件合併能力,則需要考慮採用諸如 redis 緩存的配置項等手段來保證實時權重分配。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"需要能夠綜合多種負載均衡算法,特別是正確地抽取請求上下文中的 Properties 來完成分發決策","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以,真正全功能的 API GW,已知的能開箱即用的大概還沒有,能夠通過代碼調整來適配的可能都有限,或者是需要在系統的各種角落做不知名的 hack 纔行。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"最後","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後,把所有的 LB 算法收歸到上一級 package 中,我們用一個 map,這個 map 配備了一顆 RWMutex 來防止你會在多線程的環境中使用它。但是實際上我們認爲你應該事先建立一個 lb 的實例,之後動態地 add/remove 它的 peers 就好了,此外,我們也認爲你應該是在 app 一開始的時候就已經註冊了自行定製的 balancer 算法及其 generator 的——也就是說,其實這顆鎖意思有限。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"好,在上一級的總領中,代碼片段是這樣:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func New(algorithm string, opts ...lbapi.Opt) lbapi.Balancer {\n kbs.RLock()\n defer kbs.RUnlock()\n if g, ok := knownBalancers[algorithm]; ok {\n return g(opts...)\n }\n log.Fatalf(\"unknown/unregistered balancer and generator: %q\", algorithm)\n return nil // unreachable\n}\n\n// WithPeers adds the initial peers.\nfunc WithPeers(peers ...lbapi.Peer) lbapi.Opt {\n return func(balancer lbapi.Balancer) {\n for _, p := range peers {\n balancer.Add(p)\n }\n }\n}\n\n// Register assign a (algorithm, generator) pair.\nfunc Register(algorithm string, generator func(opts ...lbapi.Opt) lbapi.Balancer) {\n kbs.Lock()\n defer kbs.Unlock()\n knownBalancers[algorithm] = generator\n}\n\n// Unregister revoke a (algorithm, generator) pair.\nfunc Unregister(algorithm string) {\n kbs.Lock()\n defer kbs.Unlock()\n delete(knownBalancers, algorithm)\n}\n\nconst (\n // Random algorithm\n Random = \"random\"\n // RoundRobin algorithm\n RoundRobin = \"round-robin\"\n // WeightedRoundRobin algorithm\n WeightedRoundRobin = \"weighted-round-robin\"\n // ConsistentHash algorithm\n ConsistentHash = \"consistent-hash\"\n // WeightedRandom algorithm\n WeightedRandom = \"weighted-random\"\n // VersioningWRR algorithm\n VersioningWRR = \"versioning-wrr\"\n)\n\nfunc init() {\n kbs.Lock()\n defer kbs.Unlock()\n\n knownBalancers = make(map[string]func(opts ...lbapi.Opt) lbapi.Balancer)\n\n knownBalancers[Random] = random.New\n knownBalancers[RoundRobin] = rr.New\n knownBalancers[WeightedRoundRobin] = wrr.New\n knownBalancers[ConsistentHash] = hash.New\n\n knownBalancers[WeightedRandom] = wrandom.New\n\n knownBalancers[VersioningWRR] = version.New\n}\n\nvar knownBalancers map[string]func(opts ...lbapi.Opt) lbapi.Balancer\nvar kbs sync.RWMutex\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"用法","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個類庫允許你 register 自己的 lb 算法進去,目的在於讓調用者能夠有更統一的界面。至於使用某個 lb 算法是很簡單的:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"package main\n\nimport (\n \"fmt\"\n lb \"github.com/hedzr/lb\"\n \"github.com/hedzr/lb/lbapi\"\n)\n\nfunc main() {\n b := lb.New(lb.RoundRobin)\n\n b.Add(exP(\"172.16.0.7:3500\"), exP(\"172.16.0.8:3500\"), exP(\"172.16.0.9:3500\"))\n sum := make(map[lbapi.Peer]int)\n for i := 0; i < 300; i++ {\n p, _ := b.Next(lbapi.DummyFactor)\n sum[p]++\n }\n \n for k, v := range sum {\n fmt.Printf(\"%v: %v\\n\", k, v)\n }\n}\n\ntype exP string\n\nfunc (s exP) String() string { return string(s) }\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根據你的實際場景,你需要自己的 exP 實現(它需要實現 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"lbapi.Peer","attrs":{}}],"attrs":{}},{"type":"text","text":"),但爲了便利於你的使用,一個 Peer 只需要實現了 String() string 接口就夠了。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"結束","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以,在這次放出的 LB 類庫中,我們提供了一堆基本算法以及複合算法:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"隨機","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"輪詢","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"del","attrs":{}}],"text":"最少連接數","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"hashing","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"加權輪詢","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"加權隨機","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":7,"align":null,"origin":null},"content":[{"type":"text","text":"加權的版本範圍比較","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而且,我們認爲已經提供了充足的基礎設施以便利於你在一個 unified 的框架下面拓展負載均衡算法達成你的實際需要。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"話說昨天刷 github,發現 profile page 大變樣了,有 explore,trending 之類的 pages。今天現在去看又恢復老樣子了。我難不成是幻覺了,還是說是昨晚想着這第二篇該怎麼寫就睡着了在 dreaming?","attrs":{}}]}],"attrs":{}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章