# IT明星不是梦 #分布式负载均衡算法之亲和性轮询

无论是在早期的负载均衡器中,还是当前微服务基于客户端的负载均衡中,都有一个最基础的轮询算法,即将请求平均分布给多台机器,今天聊聊在此基础上, kube proxy是如何实现亲和性轮询的核心数据结构. 了解亲和性策略实现,失败重试等机制

1. 基础筑基

1.1 Service与Endpoints


Service和Endpoint是kubernetes中的概念,其中Service代表一个服务,后面通常会对应一堆pod,因为pod的ip并不是固定的,用Servicel来提供后端一组pod的统一访问入口, 而Endpoints则是一组后端提供相同服务的IP和端口集合
在这节内容中大家知道这些就可以来,

1.2 轮询算法


轮询算法可能是最简单的算法了,在go里面大多数实现都是通过一个slice存储当前可以访问的后端所有地址,而通过index来保存下一次请求分配的主机在slice中的索引

1.3 亲和性


亲和性实现上也相对简单,所谓亲和性其实就是当某个IP重复调用后端某个服务,则将其转发到之前转发的机器上即可

2. 核心数据结构实现

2.1 亲和性实现

2.1.1 亲和性之亲和性策略

亲和性策略设计上主要是分为三个部分实现:
affinityPolicy:亲和性类型,即根据客户端的什么信息来做亲和性依据,现在是基于clientip
affinityMap:根据Policy中定义的亲和性的类型作为hash的key, 存储clientip的亲和性信息
ttlSeconds: 存储亲和性的过期时间, 即当超过该时间则会重新进行RR轮询算法选择

type affinityPolicy struct {
    affinityType v1.ServiceAffinity // Type字段只是一个字符串不需要深究
    affinityMap  map[string]*affinityState // map client IP -> affinity info
    ttlSeconds   int
}

2.1.2 亲和性之亲和性状态

上面提到会通过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
}

2.2 Service数据结构之负载均衡状态


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
}

2.3 负载均衡轮询数据结构


核心数据结构主要通过services字段来保存服务对应的负载均衡状态,并通过读写锁来进行service map进行保护

type LoadBalancerRR struct {
    lock     sync.RWMutex
    services map[proxy.ServicePortName]*balancerState
}

2.4 负载均衡算法实现

我们只关注负载均衡进行轮询与亲和性分配的相关实现,对于感知service与endpoints部分代码,省略更新删除等逻辑, 下面章节是NextEndpoint实现

2.4.1 加锁与合法性效验

合法性效验主要是检测对应的服务是否存在,并且检查对应的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)

2.4.2 亲和性类型支持检测

通过检测亲和性类型,确定当前是否支持亲和性,即通过检查对应的字段是否设置

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
}

2.4.3 亲和性匹配与最后访问更新

亲和性匹配则会优先返回对应的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
            }
        }
    }

2.4.4 根据clientIP构建亲和性状态

    // 获取一个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

好了,今天的分析就到这里,希望能帮组到大家,了解亲和性轮询算法的实现, 学习到核心的数据结构设计,以及在产生中应对故障的一些设计,就到这里,感谢大家分享关注,谢谢大家

k8s源码阅读电子书地址: https://www.yuque.com/baxiaoshi/tyado3

原文地址:https://blog.51cto.com/srexin/2471036

时间: 2024-07-31 11:57:55

# IT明星不是梦 #分布式负载均衡算法之亲和性轮询的相关文章

Nginx 做负载均衡的几种轮询策略

网上看见nginx的upstream目前支持的5种方式的分配,摘录备忘. 1.轮询(默认)每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除.upstream backserver {server 192.168.0.14;server 192.168.0.15;} 2.weight指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况.upstream backserver {server 192.168.0.14 weight=10;serv

负载均衡手段之DNS轮询

大多数域名注册商都支持对统一主机添加多条A记录,这就是DNS轮询,DNS服务器将解析请求按照A记录的顺序,随机分配到不同的IP上,这样就完成了简单的负载均衡.下图的例子是:有3台联通服务器.3台电信服务器,要实现"联通用户流量分摊到3台联通服务器.其他用户流量分摊到电信服务器"这个效果的设置. DNS由于成本较低,所以一般在小型的网站用的比较多.但是大型的网站一般也会将用它和其他负载均衡的方式结合起来一起使用,DNS轮询方式提供的IP地址,在大型网站中往往是一个集群的地址,可能是均衡交

LVS负载均衡地址转换使用轮询算法实验(结尾代码随记)

地址转换模式:调度服务器有双网卡,做NAT映射,请求经过调度服务器, 也是各个节点服务器回应客户机的出口,可作用于私网,较安全. 实验思路及环境 一.调度服务器配置双网卡,做网关,请求的流量以及转发的流量走调度服务器走 二.资源服务器两台都提供网页服务,为了测试,两台的内容不相同ip:192.168.10.10/24 httpd服务ip:192.168.10.20/24 httpd服务 三.远程存储设备使用NFS做远程存储,给资源服务器提供内存支持 图解如下 1.安装ipvsadm包 yum i

nginx(负载均衡算法)

1.nginx负载均衡算法1)轮询(默认)每个请求按照时间顺序逐一分配到不同的后端服务,如果后端某台服务器宕机,自动剔除故障主机,使用户访问不受影响.2)weight(轮询权值)weight的值越大,访问概率越高,主要用于后端每台服务器性能不均衡的情况下.或者仅仅为在主从的情况下设置不同的权值,达到合理有效的利用主机资源.3)ip_hash每个请求按照访问IP的哈希结果分配,使来自同一个IP的访客固定访问一台后端服务器,并且可以有效解决动态网页存在的session共享问题.4)fair比weig

Spring Cloud Ribbon 源码分析---负载均衡算法

上一篇分析了Ribbon如何发送出去一个自带负载均衡效果的HTTP请求,本节就重点分析各个算法都是如何实现. 负载均衡整体是从IRule进去的: public interface IRule{ /* * choose one alive server from lb.allServers or * lb.upServers according to key * * @return choosen Server object. NULL is returned if none * server i

Citrix Netscaler负载均衡算法

众所周知,作为新一代应用交付产品的Citrix Netscaler具有业内领先的数据控制.应用交付的能力,然而作为根本内容之一的ADC功能,如果不具备强大的.多元化的均衡算法是不可能适应如此众多的应用场景,更无法做到好的应用交付产品.因此我们在此讨论一下比较常用的负载均衡算法就很有必要. 目前最新版本的Netscaler支持17种均衡算法,目前先讨论最常用的12种 1.轮询算法(Round Robin) 当NetScaler 使用轮询的负载均衡算法时,它会将来自客户端的请求轮流分配给后台中的服务

F5负载均衡算法及基本原理

原文:Intro to Load Balancing for Developers – The Algorithms 转载:http://blog.gesha.net/archives/205/ posted on Tuesday, March 31, 2009 11:02 PM Random: This load balancing method randomly distributes load across the servers available, picking one via ra

负载均衡算法及手段

负载均衡器 可以是专用设备,也可以是在通用服务器上运行的应用程序. 分散请求到拥有相同内容或提供相同服务的服务器. 专用设备一般只有以太网接口,可以说是多层交换机的一种. 负载均衡器一般会被分配虚拟IP地址,所有来自客户端的请求都是针对虚拟IP地址完成的.负载均衡器通过负载均衡算法将来自客户端的请求转发到服务器的实际IP地址上. 负载均衡算法 private Map<String,Integer> serverMap = new HashMap<String,Integer>(){

几种简单的负载均衡算法及其Java代码实现

什么是负载均衡 负载均衡,英文名称为Load Balance,指由多台服务器以对称的方式组成一个服务器集合,每台服务器都具有等价的地位,都可以单独对外提供服务而无须其他服务器的辅助.通过某种负载分担技术,将外部发送来的请求均匀分配到对称结构中的某一台服务器上,而接收到请求的服务器独立地回应客户的请求.负载均衡能够平均分配客户请求到服务器阵列,借此提供快速获取重要数据,解决大量并发访问服务问题,这种集群技术可以用最少的投资获得接近于大型主机的性能. 负载均衡分为软件负载均衡和硬件负载均衡,前者的代