连接池
什么是连接?四元组:<source ip,source port,target ip,target port>。 本质上是个tcp的结构,tcp socket。
为了降低建立连接的时间,HTTP 1.1引入了长连接的概念,并把它搞成了默认的连接方式。啥叫长连接?就是当完成一个业务之后,socket结构并不回收。这样,只要在socket结构还存在的时候,客户端发送的任何数据,服务器都可以收到,这就是所谓的长连接。
相比短连接而言,长连接并没有什么特别的新的技术,只是维护socket结构时间长了。因为,说http长连接更不如说是tcp长连接。
为什么要有连接池? 资源复用
前言
在分布式高并发服务器中,client到server以及server中的多个节点之间的连接往往使用连接池来管理。简单来说就是将提前创建好的连接保存在池中,当有请求到来时,直接使用连接池中的连接对server端访问,省去了创建连接和销毁连接的开销(TCP建立连接时的三次握手和释放连接时的四次挥手),从而提高了性能。
设计原则
- 连接池的扩缩容
- 空闲连接的超时与保活
- 池满的处理机制
连接池的扩缩容
通常连接池属性包含最大空闲连接数和最大活跃连接数。
最大空闲连接数:连接池一直保持的连接数,无论这些连接被使用与否都会被保持。如果客户端对连接池的使用量不大,便会造成服务端连接资源的浪费。
最大活跃连接数:连接池最多保持的连接数,如果客户端请求超过次数,便要根据池满的处理机制来处理没有得到连接的请求。
扩容:当请求到来时,如果连接池中没有空闲的连接,同时连接数也没有达到最大活跃连接数,便会按照特定的增长策略创建新的连接服务该请求,同时用完之后归还到池中,而不是关闭连接。
缩容:当连接池一段时间没有被使用,同时池中的连接数超过了最大空闲连接数,那么便会关闭一部分连接,使池中的连接数始终维持在最大空闲连接数。
空闲连接的超时与保活
超时 如果连接没有被客户端使用的话,便会成为空闲连接,在一段时间后,服务端可能会根据自己的超时策略关闭空闲连接,此时空闲连接已经失效,如果客户端再使用失效的连接,便会通信失败。为了避免这种情况发生,通常连接池中的连接设有最大空闲超时时间(最好略小于服务器的空闲连接超时时间),在从池中获取连接时,判断是否空闲超时,如果超时则关闭,没有超时则可以继续使用。
保活 如果服务器发生重启,那么连接池中的连接便会全部失效,如果此时再从池中获取连接,不论获取到哪一个,都将通信失败。因此,连接池必须考虑连接的保活问题,有两种解决方法:
1、连接池设置一个Ping函数,专门用来做连接的保活。在从池中获取连接的时候,Ping一下服务器,如果得到响应,则连接依然有效,便可继续使用,如果超时无响应,则关闭该连接,生成新的连接,由于每次都要Ping一下,必然会增加延迟。也可以后台用一个线程或者协程定期的执行Ping函数,进行连接的保活,缺点是感知连接的失效会有一定的延迟,从池中仍然有可能获取到失效的连接。
2、客户端加入相应的重试机制。比如重试3次,前两次从池中获取连接执行,如果报的错是失效的连接等有关连接问题的错误,那么第3次从池中获取的时候带上参数,指定获取新建的连接,同时连接池移除前两次获取的失效的连接。
池满的处理机制
连接池不可能无限的容纳连接,当池满时,有两种处理机制:
1、池新建连接,并返回给客户端,当客户端用完时,如果池满则关闭连接,否则放入池中。
2、设置一定的超时时间来等待空闲连接。需要客户端加入重试机制,避免因超时之后获取不到空闲连接产生的错误。
基本原理
- 服务启动时建立连接池。
- 初始化连接池,建立最大空闲连接数个连接。
- 请求到来时,从池中获取一个连接。如果没有空闲连接且连接数没有达到最大活跃连接数,则新建连接;如果达到最大活跃连接数,设置一定的超时时间,等待获取空闲连接。
- 获取到连接后进行通信服务。
- 释放连接,此时是将连接放回连接池,如果池满则关闭连接。
- 释放连接池,关闭所有连接。
golang实现
实现上可以用数组当空闲连接的队列,每次left pop一个空闲连接使用。
该队列的访问更新要加锁。
获取连接操作:
- 当连接池有连接时,取个连接,判断是否已经过期。
- 过期了就新建个。
- 当没空闲连接时,加入等待队列。
- 当连接用完时,判断等待队列里 是否有等待的,有等待的,分配给它。
- 否则直接放入空闲连接池。
- 也应该有个定时任务定期清理空闲连接池过期的连接。