几乎每一种数据库都会有连接池, 来减少频繁的创建删除连接的开销, 在MongoDB里面是通过信号量线程同步方式来对创建、销毁进行管理。
信号量基础
int sem_init(sem_t *sem, int pshared, unsigned int value)
sem是要初始化的信号量,pshared表示此信号量是在进程间共享(=1)还是线程间共享(=0),value是信号量的初始值。
int sem_destroy(sem_t *sem);
其中sem是要销毁的信号量。只有用sem_init初始化的信号量才能用sem_destroy销毁。
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
sem_wait:等待信号量,如果信号量的值大于0, 将信号量的值减1,立即返回。如果信号量的值为0,则线程阻塞。相当于P操作。成功返回0,失败返回-1。
sem_trywait:与sem_wait类似, 只是如果递减不能立即执行,调用将返回错误(errno 设置为EAGAIN)而不是阻塞。
sem_timewait:sem_wait() 类似,只不过 abs_timeout 指定一个阻塞的时间上限,如果调用因不能立即执行递减而要阻塞。
int sem_post(sem_t *sem);
释放信号量,让信号量的值加1。相当于V操作。
连接池的用法
在大部分的MongoDB driver下, 都有实现了如下的URI 选项, 通过指定相关的连接池控制项, 可以指定连接池的具体行为:
连接项 | 说明 |
maxPoolSize | 连接池的最大连接数, 默认值是100 |
minPoolSize | 连接池的最小连接数, 默认值是0, 注意不是所有driver支持该选项 |
maxIdleTimeMS | 一个空闲连接在被删除或者关闭之前存在的最大时间, 单位是毫秒,注意,不是所有driver支持该选线 |
waitQueueMultiple | driver强行限制线程同时等待连接的个数。 这个限制了连接池的倍数 |
waitQueueTimeoutMS | 在超时之前,线程等待连接生效的总时间。如果连接池到达最大并且所有的连接都在使用,这个参数就生效了 |
URI连接的实例:
如下URI代表write_concern的设定是majority, 写入等待majority的超时时间是2000ms, 连接池的最大连接数是200, 最小的连接数是10.
mongodb://USER:PASSWD@host1,host2,host3/?w=majority&wtimeoutMS=2000&maxPoolSize=200&minPoolSize=10
连接池的代码实现
使用TicketHoder来封装类线程控制信号量函数:
TicketHolder::TicketHolder(int num) : _outof(num) {
_check(sem_init(&_sem, 0, num));
}
TicketHolder::~TicketHolder() {
_check(sem_destroy(&_sem));
}
bool TicketHolder::tryAcquire() {
while (0 != sem_trywait(&_sem)) {
switch (errno) {
case EAGAIN:
return false;
case EINTR:
break;
default:
_check(-1);
}
}
return true;
}
void TicketHolder::waitForTicket() {
while (0 != sem_wait(&_sem)) {
switch (errno) {
case EINTR:
break;
default:
_check(-1);
}
}
}
void TicketHolder::release() {
_check(sem_post(&_sem));
}
在MongoDB启动的时候, 会先创建一个PortMessageServer,并在里面指定一个有配置参数或者命令行参数指定的最大的连接数, 然后通过setupSockets创建一个socket并绑定, 并将其加入到Listener里面的std::vector _socks;
static void _initAndListen(int listenPort) {
...
auto handler = std::make_shared<MyMessageHandler>();
MessageServer* server = createServer(options, std::move(handler));
server->setAsTimeTracker();
if (!server->setupSockets()) {
error() << "Failed to set up sockets during startup.";
return;
}
...
server->run();
}
MessagePortServer里面, 通过Listener::initAndListen函数,最终在 PortMessageServer::accepted里面来创建新的线程处理本次的操作。
这里可以看到, 每一次新的操作, TicketHoder::tryAcquire会试图进入信号量的代码区, 如果信号量的连接数大于1, 就会获得信号量锁, 并且将连接数减少1; 如果此时的连接数为0 , tryAcquire会返回失败, 表明连接数已满, 无法连接。
获得信号量锁之后, 会建立一下新的处理线程, 指定其处理函数为:
PortMessageServer::handleIncomingMsg.
class PortMessageServer : public MessageServer, public Listener {
virtual void accepted(std::shared_ptr<Socket> psocket, long long connectionId) {
ScopeGuard sleepAfterClosingPort = MakeGuard(sleepmillis, 2);
std::unique_ptr<MessagingPortWithHandler> portWithHandler(
new MessagingPortWithHandler(psocket, _handler, connectionId));
if (!Listener::globalTicketHolder.tryAcquire()) {
log() << "connection refused because too many open connections: "
<< Listener::globalTicketHolder.used() << endl;
return;
}
{
stdx::thread thr(stdx::bind(&handleIncomingMsg, portWithHandler.get()));
thr.detach();
}
}