《“ID串行化”保证群消息顺序性》提到,可以通过连接池的改造,实现ID串行化,本篇讲讲连接池的原理,以及实现细节。
工程架构中有很多访问下游的需求,下游包括但不限于服务/数据库/缓存,其通讯步骤是为:
(1)与下游建立一个连接;
(2)通过这个连接,收发请求;
(3)交互结束,关闭连接,释放资源;
不管是服务/数据库/缓存,官方会提供不同语言的Driver、Document、DemoCode来指导使用方建立连接与调用接口。
以MongoDB的C++官方DriverAPI为例:
DBClientConnection* c = new DBClientConnection();
c->connect(“127.0.0.1:8888”);
c->insert(“db.s”, BSON(”shenjian”));
c->close();
画外音:建立连接、发送请求、关闭连接,都非常清晰。
这个DBClientConnection就是一个与MongoDB的连接,官方Driver通过它提供了若干API,让用户可以对MongoDB进行连接,增删查改,关闭的操作,从而实现不同的业务逻辑。
当并发量很低的时候,连接可以临时建立,但当服务吞吐量达到几百、几千的时候,建立连接connect和销毁连接close就会成为瓶颈,此时该如何优化呢?
(1)当服务启动的时候,先建立好若干连接Array[DBClientConnection];
(2)当请求到达的时候,再从Array中取出一个,执行下游操作,执行完放回;
从而避免反复的建立和销毁连接,以提升性能。
而这个对Array[DBClientConnection]进行维护的数据结构,就是连接池。
有了连接池之后,数据库操作的伪代码变为:
DBClientConnection* c = ConnectionPool::GetConnection();
c->insert(“db.s”, BSON(”shenjian”));
ConnectionPool::FreeConnection(c);
画外音:取出连接、发送请求、放回连接,也非常清晰。
可以看到连接池ConnectionPool主要有三个核心接口:
(1)Init:初始化Array[DBClientConnection],这个接口只在服务启动时调用一次;
(2)GetConnection:请求每次需要访问数据库时,不connect一个新连接,而是通过连接池的这个接口来拿连接;
(3)FreeConnection:请求每次访问完数据库时,不是close一个连接,而是把这个连接放回连接池;
连接池至少包含两个核心数据结构:
(1)连接数组Array DBClientConnection[N];
(2)互斥锁数组Array lock[N];
Init(){
for i = 1 to N {
Array DBClientConnection [i] = new();
Array DBClientConnection [i]->connect();
Array lock[i] = 0;
}
}
画外音:把所有连接和互斥锁初始化。
GetConnection()
for i = 1 to N {
if(Array lock[i] == 0){
Array lock[i] = 1;
return Array DBClientConnection[i];
}
}
}
画外音:找一个可用的连接,锁住,并返回连接。
FreeConnection(c)
for i = 1 to N {
if(Array DBClientConnection [i] == c){
Array lock[i] = 0;
}
}
}
画外音:找到连接,把锁释放。
(1)需要实施连接可用性检测,如果有连接失效,需要重建连接;
(2)通过freeArray,connectionMap等数据结构,可以让取出连接和放回连接都达到O(1)时间复杂度;
(3)可以通过hash取连接,实现id串行化;
(4)每条连接被取到的概率必须相同,以实现负载均衡;
(5)如果有下游故障,失效连接必须剔除,以实现故障自动转移;
(6)如果有下游新增,需要动态扩充连接池,以实现服务自动发现;