不管是在早期的負載均衡器中,仍是當前微服務基於客戶端的負載均衡中,都有一個最基礎的輪詢算法,即將請求平均分佈給多臺機器,今天聊聊在此基礎上, kube proxy是如何實現親和性輪詢的核心數據結構. 瞭解親和性策略實現,失敗重試等機制算法
Service和Endpoint是kubernetes中的概念,其中Service表明一個服務,後面一般會對應一堆pod,由於pod的ip並非固定的,用Servicel來提供後端一組pod的統一訪問入口, 而Endpoints則是一組後端提供相同服務的IP和端口集合在這節內容中你們知道這些就能夠來,後端
輪詢算法多是最簡單的算法了,在go裏面大多數實現都是經過一個slice存儲當前能夠訪問的後端全部地址,而經過index來保存下一次請求分配的主機在slice中的索引api
親和性實現上也相對簡單,所謂親和性其實就是當某個IP重複調用後端某個服務,則將其轉發到以前轉發的機器上便可微信
親和性策略設計上主要是分爲三個部分實現:affinityPolicy:親和性類型,即根據客戶端的什麼信息來作親和性依據,如今是基於clientipaffinityMap:根據Policy中定義的親和性的類型做爲hash的key, 存儲clientip的親和性信息ttlSeconds: 存儲親和性的過時時間, 即當超過該時間則會從新進行RR輪詢算法選擇session
type affinityPolicy struct {
affinityType v1.ServiceAffinity // Type字段只是一個字符串不須要深究
affinityMap map[string]*affinityState // map client IP -> affinity info
ttlSeconds int
}複製代碼
上面提到會經過affinityMap存儲親和性狀態, 其實親和性狀態裏面關鍵信息有兩個endpoint(後端要訪問的endpoint)和lastUsed(親和性最後被訪問的時間)數據結構
type affinityState struct {
clientIP string
//clientProtocol api.Protocol //not yet used
//sessionCookie string //not yet used
endpoint string
lastUsed time.Time
}複製代碼
balancerState存儲當前Service的負載均衡狀態數據,其中endpoints存儲後端pod的ip:port集合, index則是實現RR輪詢算法的節點索引, affinity存儲對應的親和性策略數據負載均衡
type balancerState struct {
endpoints []string // a list of "ip:port" style strings
index int // current index into endpoints
affinity affinityPolicy
}複製代碼
核心數據結構主要經過services字段來保存服務對應的負載均衡狀態,並經過讀寫鎖來進行service map進行保護ide
type LoadBalancerRR struct {
lock sync.RWMutex
services map[proxy.ServicePortName]*balancerState
}複製代碼
咱們只關注負載均衡進行輪詢與親和性分配的相關實現,對於感知service與endpoints部分代碼,省略更新刪除等邏輯, 下面章節是NextEndpoint實現微服務
合法性效驗主要是檢測對應的服務是否存在,而且檢查對應的endpoint是否存在源碼分析
lb.lock.Lock()
defer lb.lock.Unlock() // 加鎖
// 進行服務是否存在檢測
state, exists := lb.services[svcPort]
if !exists || state == nil {
return "", ErrMissingServiceEntry
}
// 檢查服務是否有服務的endpoint
if len(state.endpoints) == 0 {
return "", ErrMissingEndpoints
}
klog.V(4).Infof("NextEndpoint for service %q, srcAddr=%v: endpoints: %+v", svcPort, srcAddr, state.endpoints)複製代碼
經過檢測親和性類型,肯定當前是否支持親和性,即經過檢查對應的字段是否設置
sessionAffinityEnabled := isSessionAffinity(&state.affinity)
func isSessionAffinity(affinity *affinityPolicy) bool {
// Should never be empty string, but checking for it to be safe.
if affinity.affinityType == "" || affinity.affinityType == v1.ServiceAffinityNone {
return false
}
return true
}複製代碼
親和性匹配則會優先返回對應的endpoint,可是若是此時該endpoint已經訪問失敗了,則就須要從新選擇節點,就須要重置親和性
var ipaddr string
if sessionAffinityEnabled {
// Caution: don't shadow ipaddr
var err error
// 獲取對應的srcIP當前是根據客戶端的ip進行匹配
ipaddr, _, err = net.SplitHostPort(srcAddr.String())
if err != nil {
return "", fmt.Errorf("malformed source address %q: %v", srcAddr.String(), err)
}
// 親和性重置,默認狀況下是false, 可是若是當前的endpoint訪問出錯,則須要重置
// 由於已經鏈接出錯了,確定要從新選擇一臺機器,當前的親和性就不能繼續使用了
if !sessionAffinityReset {
// 若是發現親和性存在,則返回對應的endpoint
sessionAffinity, exists := state.affinity.affinityMap[ipaddr]
if exists && int(time.Since(sessionAffinity.lastUsed).Seconds()) < state.affinity.ttlSeconds {
// Affinity wins.
endpoint := sessionAffinity.endpoint
sessionAffinity.lastUsed = time.Now()
klog.V(4).Infof("NextEndpoint for service %q from IP %s with sessionAffinity %#v: %s", svcPort, ipaddr, sessionAffinity, endpoint)
return endpoint, nil
}
}
}複製代碼
// 獲取一個endpoint, 並更新索引
endpoint := state.endpoints[state.index]
state.index = (state.index + 1) % len(state.endpoints)
if sessionAffinityEnabled {
// 保存親和性狀態
var affinity *affinityState
affinity = state.affinity.affinityMap[ipaddr]
if affinity == nil {
affinity = new(affinityState) //&affinityState{ipaddr, "TCP", "", endpoint, time.Now()}
state.affinity.affinityMap[ipaddr] = affinity
}
affinity.lastUsed = time.Now()
affinity.endpoint = endpoint
affinity.clientIP = ipaddr
klog.V(4).Infof("Updated affinity key %s: %#v", ipaddr, state.affinity.affinityMap[ipaddr])
}
return endpoint, nil複製代碼
好了,今天的分析就到這裏,但願能幫組到你們,瞭解親和性輪詢算法的實現, 學習到核心的數據結構設計,以及在產生中應對故障的一些設計,就到這裏,感謝你們分享關注,謝謝你們
微信號:baxiaoshi2020
關注公告號閱讀更多源碼分析文章
更多文章關注 www.sreguide.com
本文由博客一文多發平臺 OpenWrite 發佈