分布式负载均衡算法之亲和性轮询原理
无论是在早期的负载均衡器中,还是当前微服务基于客户端的负载均衡中,都有一个最基础的轮询算法,即将请求平均分布给多台机器,今天聊聊在此基础上, 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
好了,今天的分析就到这里,希望能帮组到大家,了解亲和性轮询算法的实现, 学习到核心的数据结构设计,以及在产生中应对故障的一些设计,就到这里,感谢大家分享关注,谢谢大家
> 微信号:baxiaoshi2020 > 关注公告号阅读更多源码分析文章
> 更多文章关注 www.sreguide.com > 本文由博客一文多发平台 OpenWrite 发布

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
使用Pyhton带你分析酒店里的针孔摄像头
/1 前言/ 最近酒店被曝出隐藏摄像头的事情屡见不鲜,搞得大家人心惶惶,有图为证,如下图所示。 今天教大家一下如何用python分析出酒店里的针孔摄像头,让你放心住酒店。 /2 原理介绍/ 我们大家都知道,针孔摄像一般都很小,所以它不太可能含有存储设备,都是通过连接酒店房间内的无线网,远程传输给作案人,我们就从这方面入手,用Python的scapy模块扫描局域网内的所有设备,并通过爬虫找到该设备生产厂商,以厂商来判断该设备是否为摄像头。 所需环境:ubuntu、pyhton3.5、pycharm /3 操作步骤/ 实现步骤依赖环境较多,操作起来可能有些复杂,不过就当了解一下也是欧克的,具体的操作步骤如下。 1、用scapy模块的scapy函数模拟构造ARP广播包,代码如下图所示。 这个表示要向网段内的所有主机都询问一下,说的通俗一点就是“请问网段内都有谁在啊”。 2、模拟在局域网内发送。用scapy模块模拟发送ARP广播,代码如下图所示。 其中p是第一步构造的广播包,wifi是本机的网卡名称,timeout是发送时限。 3、返回的两个参数,ans包含两类数据包(ARP请求包和ARP返回...
-
下一篇
从天津百货大楼 5 病例“迷局”见新冠病毒传播路径
天津某百货大楼内部相继出现 5 例新冠肺炎确诊病例,从起初的 3 个病例来看,似乎找不到任何流行病学上的关联性。在这种背景之下,作为技术人员可以通过什么技术来找寻病例之间的联系呢? 摘要 最初,nCoV 新冠病毒的扩散过程是由一个人(节点)向各其他人(节点)扩散的树状结构,但随着病毒的变异和人员交叉感染、“百家宴”、“联欢会”、“春运”等人员聚集,疫情扩散为网状结构。可以使用图数据库来存储相关人员、地理位置、感染时间等数据,本文将使用图数据库 Nebula Graph 作为工具,带大家一起探讨疫情的传播路径,并找到相关的疑似病例。 天津案例简述 下面用 Usr1、Usr2、Usr3、Usr4、Usr5 来代指这 5 例病例,看一下他们的行为轨迹: Usr1 信息: Usr1 于 1 月 24 日开始发热,在 1 月 22 日至 1 月 30 日期间在天津百货大厦 A 区工作,于 1 月 31 日确诊; Usr2 信息:Usr2 为 Usr1 丈夫,于 1 月 25 日开始出现腹泻症状,于 2 月 1 日确诊; Usr3 信息:Usr3 于 1 月 18 日接触过一个疑似病例,而后在天津...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- MySQL8.0.19开启GTID主从同步CentOS8
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- MySQL数据库在高并发下的优化方案
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS7,8上快速安装Gitea,搭建Git服务器
- SpringBoot2全家桶,快速入门学习开发网站教程
- Dcoker安装(在线仓库),最新的服务器搭配容器使用