不管是在早期的负载均衡器中,仍是当前微服务基于客户端的负载均衡中,都有一个最基础的轮询算法,即将请求平均分布给多台机器,今天聊聊在此基础上, kube proxy是如何实现亲和性轮询的核心数据结构. 了解亲和性策略实现,失败重试等机制算法
Service和Endpoint是kubernetes中的概念,其中Service表明一个服务,后面一般会对应一堆pod,由于pod的ip并非固定的,用Servicel来提供后端一组pod的统一访问入口, 而Endpoints则是一组后端提供相同服务的IP和端口集合 在这节内容中你们知道这些就能够来,后端
轮询算法多是最简单的算法了,在go里面大多数实现都是经过一个slice存储当前能够访问的后端全部地址,而经过index来保存下一次请求分配的主机在slice中的索引api
亲和性实现上也相对简单,所谓亲和性其实就是当某个IP重复调用后端某个服务,则将其转发到以前转发的机器上便可微信
亲和性策略设计上主要是分为三个部分实现: affinityPolicy:亲和性类型,即根据客户端的什么信息来作亲和性依据,如今是基于clientip affinityMap:根据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 发布