3.1 连接管理
为了让连接变得更可靠和高效,gRPC需要对连接进行管理。
考虑这样的一种情景,由于公司规模的扩大、流量的增加,gRPC的服务端由单机扩展成了一个集群。这个时候,我们的客户端需要调用服务端中的某一个方法,那么这个客户端需要向哪台机器建立连接,发送数据呢?
如果我们把这个问题划分的更具体,那么可以需要解决的问题如下:
假设现在这个集群里面有很多台机器,那么我们该怎么告知客户端每台服务端机器的ip:port呢?
假设我们新增或减少了一些gRPC的服务端,客户端该怎么更新它所维护的ip:port列表呢?
假设客户端当前请求的服务端,存在了多个ip:port,那么这个客户端该向哪个连接发送数据呢?
这几个问题可以归结为,gRPC如何解决服务注册、服务发现、负载均衡的问题。
然而,gRPC并没有提供诸如Spring Cloud、Dubbo等框架的服务注册、服务发现的功能。
我想gRPC这么做的原因大概是为了能够提供更灵活的服务发现和负载均衡功能。
3.2 Resolver
Resolver称为解析器,能够将客户端传入的“符合某种规则的名称”解析为IP地址列表。
假设你定义了一种地址格式:aaa:///bbb-project/ccc-srv
然后Resolver会将这个地址解析成好几个ip:port,代表了提供ccc-srv服务集群的所有机器地址。
这就是Resolver的作用。
那么,Resolver是怎么进行解析的呢?换句话说,Resolver是如何做到输入某种地址,输出一串IP地址呢?
这部分的工作需要由用户自己实现。
gRPC提供的是插件式的Resolver功能,他会根据用户传入的aaa:///bbb-project/ccc-srv,选择一个能够解析aaa的Resolver,并进行解析,得到ip:port列表。
3.3 Balancer
Balancer称为负载均衡器,负责在Resolver解析出的一串地址中,选择其中的一个建立连接。
至于如何选择,也是由用户自己编写LB的逻辑。
也就是说,gRPC实现了基础的逻辑,但是也提供了很强大的插件式编程的能力,将很多操作都留给开发人员自己去做选择。
不过,很大的灵活度对应的是很复杂的代码结构,直接去看源码可能会让人摸不着头脑。所以在这一篇的文章中我们先来介绍整体的一个设计逻辑,在下一篇文章中我们再来聊具体的细节。
3.4 Wrapper
我们在上面聊到,gRPC的Resolver和Balancer都是支持自定义的。我们可以自己定义各种不同的Resolver和Balancer,来应对不同场景的需求。
这么做虽然增加了代码复杂度,但是却能够让gRPC变得更灵活,能够对各种复杂情景提供支持。
那么,要怎么才能够实现插件式的编程呢?
答案是使用装饰器模式。
装饰模式(Decorator)也叫包装器模式(Wrapper)。GOF在《设计模式》一书中给出的定义为:动态地给一个对象添加一些额外的职责。
装饰器模式是指动态地给一个对象添加一些额外的职责,就增加功能来说装饰模式比生成子类更为灵活。它通过创建一个包装对象,也就是装饰来包裹真实的对象。
https://www.cnblogs.com/hongjijun/p/14321434.html
https://www.cnblogs.com/xy-ouyang/p/10689908.html
设计原则
连接池的扩缩容
空闲连接的超时与保活
池满的处理机制
连接池的扩缩容
通常连接池属性包含最大空闲连接数和最大活跃连接数。
最大空闲连接数:连接池一直保持的连接数,无论这些连接被使用与否都会被保持。如果客户端对连接池的使用量不大,便会造成服务端连接资源的浪费。
最大活跃连接数:连接池最多保持的连接数,如果客户端请求超过次数,便要根据池满的处理机制来处理没有得到连接的请求。
扩容:当请求到来时,如果连接池中没有空闲的连接,同时连接数也没有达到最大活跃连接数,便会按照特定的增长策略创建新的连接服务该请求,同时用完之后归还到池中,而不是关闭连接。
缩容:当连接池一段时间没有被使用,同时池中的连接数超过了最大空闲连接数,那么便会关闭一部分连接,使池中的连接数始终维持在最大空闲连接数。
空闲连接的超时与保活
超时 如果连接没有被客户端使用的话,便会成为空闲连接,在一段时间后,服务端可能会根据自己的超时策略关闭空闲连接,此时空闲连接已经失效,如果客户端再使用失效的连接,便会通信失败。为了避免这种情况发生,通常连接池中的连接设有最大空闲超时时间(最好略小于服务器的空闲连接超时时间),在从池中获取连接时,判断是否空闲超时,如果超时则关闭,没有超时则可以继续使用。
保活 如果服务器发生重启,那么连接池中的连接便会全部失效,如果此时再从池中获取连接,不论获取到哪一个,都将通信失败。因此,连接池必须考虑连接的保活问题,有两种解决方法:
1、连接池设置一个Ping函数,专门用来做连接的保活。在从池中获取连接的时候,Ping一下服务器,如果得到响应,则连接依然有效,便可继续使用,如果超时无响应,则关闭该连接,生成新的连接,由于每次都要Ping一下,必然会增加延迟。也可以后台用一个线程或者协程定期的执行Ping函数,进行连接的保活,缺点是感知连接的失效会有一定的延迟,从池中仍然有可能获取到失效的连接。
2、客户端加入相应的重试机制。比如重试3次,前两次从池中获取连接执行,如果报的错是失效的连接等有关连接问题的错误,那么第3次从池中获取的时候带上参数,指定获取新建的连接,同时连接池移除前两次获取的失效的连接。
池满的处理机制
连接池不可能无限的容纳连接,当池满时,有两种处理机制:
1、池新建连接,并返回给客户端,当客户端用完时,如果池满则关闭连接,否则放入池中。
2、设置一定的超时时间来等待空闲连接。需要客户端加入重试机制,避免因超时之后获取不到空闲连接产生的错误。
基本原理
服务启动时建立连接池。
初始化连接池,建立最大空闲连接数个连接。
请求到来时,从池中获取一个连接。如果没有空闲连接且连接数没有达到最大活跃连接数,则新建连接;如果达到最大活跃连接数,设置一定的超时时间,等待获取空闲连接。
获取到连接后进行通信服务。
释放连接,此时是将连接放回连接池,如果池满则关闭连接。
释放连接池,关闭所有连接。
https://zhuanlan.zhihu.com/p/100200985?from_voters_page=true