内容简介:最近在做一个项目,项目中用golang 写了一个网关gateway,gateway接受来自外部的请求,并转发到后端的容器中。gateway和应用的容器都部署在同一个K8S集群当中。流程如下图gateway到pod的请求,是通过K8S的dns机制来访问service,使用的是service的endpoint的负载均衡机制。当gateway得到一个请求之后,通过解析对应的参数,然后可以判断需要转发到哪个host,例如:请求转发到service.namespace.svc.cluster.local:8080,然
最近在做一个项目,项目中用golang 写了一个网关gateway,gateway接受来自外部的请求,并转发到后端的容器中。gateway和应用的容器都部署在同一个K8S集群当中。流程如下图
gateway到pod的请求,是通过K8S的dns机制来访问service,使用的是service的endpoint的负载均衡机制。当gateway得到一个请求之后,通过解析对应的参数,然后可以判断需要转发到哪个host,例如:请求转发到service.namespace.svc.cluster.local:8080,然后DNS解析会解析出对应service的clusterIp,通过service转发请求到后端的pod上(具体转发原理可以了解一下kube-proxy的原理),gateway到service的请求通过golang的 fasthttp实现,并且为了提高效率,采用的是长连接的形式。
我们现在为了实现自动化扩缩容,引入了HPA扩缩容机制,也就是说service对应的pod会根据访问量和CPU的变化进行自动的扩缩容。现在的问题是,这种方案能否在扩容之后实现负载均衡吗?答案是不能,或者说负载均衡的效果并不好(如果采用RoundRobin的负载均衡策略,多个pod并不能均匀的接受到请求),下面说一下我的分析:
我们知道,使用fasthttp作为客户端并采用长连接的时候,TPC的连接存在一个连接池,而这个连接池是如何管理的至关重要。看代码: client.go
func (c *Client) Do(req *Request, resp *Response) error { uri := req.URI() host := uri.Host() isTLS := false scheme := uri.Scheme() if bytes.Equal(scheme, strHTTPS) { isTLS = true } else if !bytes.Equal(scheme, strHTTP) { return fmt.Errorf("unsupported protocol %q. http and https are supported", scheme) } startCleaner := false c.mLock.Lock() m := c.m if isTLS { m = c.ms } if m == nil { m = make(map[string]*HostClient) if isTLS { c.ms = m } else { c.m = m } } hc := m[string(host)] if hc == nil { hc = &HostClient{ Addr: addMissingPort(string(host), isTLS), Name: c.Name, NoDefaultUserAgentHeader: c.NoDefaultUserAgentHeader, Dial: c.Dial, DialDualStack: c.DialDualStack, IsTLS: isTLS, TLSConfig: c.TLSConfig, MaxConns: c.MaxConnsPerHost, MaxIdleConnDuration: c.MaxIdleConnDuration, MaxIdemponentCallAttempts: c.MaxIdemponentCallAttempts, ReadBufferSize: c.ReadBufferSize, WriteBufferSize: c.WriteBufferSize, ReadTimeout: c.ReadTimeout, WriteTimeout: c.WriteTimeout, MaxResponseBodySize: c.MaxResponseBodySize, DisableHeaderNamesNormalizing: c.DisableHeaderNamesNormalizing, } m[string(host)] = hc if len(m) == 1 { startCleaner = true } } c.mLock.Unlock() if startCleaner { go c.mCleaner(m) } return hc.Do(req, resp) }
其中
hc := m[string(host)]
这一行代码就是关键。大概解释一下,httpclient当中维护了一个 map[string]*HostClient ,其中key即为host,value为hostClient对象。那这个host,即为我们请求的host。在本例中就是service.namespace.svc.cluster.local:8080,而每一个hostClient,又维护了一个TCP的连接池,这个连接池中,真正维护着TCP连接。每次进行http请求时,先通过请求的host找到对应的hostClient,再从hostClient的连接池中取一个连接来发送http请求。问题的关键就在于,map中的key,用的是域名+端口还是ip+端口的形式。如果是域名+端口,那么对应的hostClient中的连接,就会可能包含到该域名对应的各个ip的连接,而这些连接的数量无法保证均匀。但如果key是ip+端口,那么对应hostClient中的连接池只有到该ip+端口的连接。如下图:
图中每一个方框代表一个hostclient的连接池,框1指的就是本例中的情况,而框2和框3指的是通过ip+端口建立连接的情况。在K8S中,service的负载均衡指的是建立连接时,会均衡的和pod建立连接,但是,由于我们pod的创建顺序有先后区别(初始的时候只有一个pod,后面通过hpa扩容起来),导致框1中的连接肯定无法做到均匀分配,因此扩容起来之后的pod,无法做到真正意义的严格的负载均衡。
那么有什么办法改进呢:
1.gateway到后端的请求是通过host(K8S的域名)通过service进行请求的,如果改成直接通过podIP进行访问,那么就可以自己实现负载均衡方案,但是这样的复杂度在于必须要自己做服务发现机制,即不能依赖K8S的service服务发现。
2.采用短连接,短连接显然没有任何问题,完全取决于service的负载均衡。但是短连接必然会影响转发效率,所以,可以采用一种长短连接结合的方式,即每个连接设置最大的请求次数或连接持续时间。这样能在一定程度上解决负载分配不均匀的问题。
以上是个人的一些理解和看法,因笔者水平有限,难免有理解错误或不足的地方,欢迎大家指出,也欢迎大家留言讨论。
------------------------------------------------------------------------------------2019.11.11更新------------------------------------------------------------------------------------------
以前的faasthttp的client里,没有 MaxConnDuration 字段,我也在github上提出了一个issue,希望作者能加上该字段,很高兴,作者已经更新,加上了该字段。参考 https://github.com/valyala/fasthttp/issues/692
这个字段的意思是连接持续最大多长时间后就会关闭,用该参数,实现了长短连接结合的方式,兼顾了效率和负载均衡的问题。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
程序员面试金典(第5版)
[美] Gayle Laakmann McDowell / 李琳骁、漆 犇 / 人民邮电出版社 / 2013-11 / 59.00
本书是原谷歌资深面试官的经验之作,层层紧扣程序员面试的每一个环节,全面而详尽地介绍了程序员应当如何应对面试,才能在面试中脱颖而出。第1~7 章主要涉及面试流程解析、面试官的幕后决策及可能提出的问题、面试前的准备工作、对面试结果的处理等内容;第8~9 章从数据结构、概念与算法、知识类问题和附加面试题4 个方面,为读者呈现了出自微软、苹果、谷歌等多家知名公司的150 道编程面试题,并针对每一道面试题目......一起来看看 《程序员面试金典(第5版)》 这本书的介绍吧!