1. Redis的线程模型
Redis是一个单线程的应用程序,意味着所有的I/O操作都在单个线程中进行,没有阻塞线程的等待其它线程完成操作的情况。
1.1 Redis线程模型的好处
Redis单线程模型是出于性能的考虑,下面我们来一起探究一下:
1. 减少线程间上下文切换的开销,只需维护一个线程,就避免了多个线程之间频繁的上下文切换。
2. 避免了各种线程相关问题,比如同步、锁、死锁等问题。
1.2 Redis线程模型的缺点
单线程模型也有其缺点,下面我们来一起看一下:
1. 无法发挥多核CPU的能力,因为只有一个线程,无法充分利用多核处理器。
2. 在某些场景下,I/O操作会成为瓶颈,虽然单线程不会阻塞,但它会百分之百的占用CPU,导致其它请求处理速度变慢。
针对第二个缺点,Redis提出了线程I/O模型。
2. Redis的线程I/O模型
在Redis线程I/O模型中,Redis在主线程上接收socket连接请求,但是实际读写操作是在独立的I/O线程中进行。
2.1 Redis I/O线程的数量
Redis线程I/O模型中,I/O线程的数量是根据当前系统核数来确定的,通常情况下,Redis默认使用和系统核数相同的I/O线程。
int aeCreateThreadPool(serverThread *server) {
int i;
char *err = NULL;
server->io_threads = server.system_hz;
if (server->io_threads == 0) server->io_threads = 1;
if (server->io_threads > MAX_THREADS) server->io_threads = MAX_THREADS;
server->io_threads_active = 0;
server->io_threads_do_stop = 0;
for (i = 0; i < server->io_threads; i++) {
struct io_thread_data *data;
data = zmalloc(sizeof(*data));
data->server = server;
data->id = i;
data->base_idx = (i*MAX_EVENT_LOOPS)/server->io_threads;
/* Create per-thread event loop. */
server.io_threads_data[i].el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
if (server.io_threads_data[i].el == NULL) {
err = "Can't create event loop for I/O thread";
goto error;
}
/* Create an event pipe to wakeup event loop from other threads. */
if (pipe(server.io_threads_data[i].pipe_fd)) {
err = "Can't create wakeup pipe for I/O thread";
goto error;
}
anetNonBlock(NULL,server.io_threads_data[i].pipe_fd[0]);
anetNonBlock(NULL,server.io_threads_data[i].pipe_fd[1]);
server.io_threads_data[i].el->setEventLoopDoneProc(iOThreadDone,server.io_threads_data[i].pipe_fd[0]);
server.io_threads_data[i].el->setBeforeSleepProc(iOThreadBeforeSleep,NULL);
server.io_threads_active++;
/* Create the I/O thread. */
if (pthread_create(&server.io_threads_data[i].thread_id,NULL,
ioThreadMain,data) != 0)
{
close(server.io_threads_data[i].pipe_fd[0]);
close(server.io_threads_data[i].pipe_fd[1]);
server.io_threads_data[i].el->freeEventLoop();
zfree(data);
err = "Can't create thread";
goto error;
}
}
return C_OK;
error:
redisLog(REDIS_WARNING,
"Fatal error: %s. Exiting.", strerror(errno));
if (err)
redisLog(REDIS_WARNING,
"Error detail: %s", err);
exit(1);
}
从以上代码可以看出,`io_thread`的数量是通过 `server.system_hz`(系统核数)做判断得出的。
2.2 Redis I/O线程的工作模式
当主线程接收到一个socket连接请求后,会将连接请求传递给一个处于等待状态的I/O线程,然后I/O线程就会负责处理该连接的读写操作,直到连接被关闭或者出现错误。
在Redis线程I/O模型中,每个I/O线程有一个专门的事件循环,它独立于主事件循环。当需要进行I/O操作时,主线程会将操作传递给I/O线程,I/O线程会在专门的事件循环中进行I/O操作。
如果I/O线程遇到了I/O操作阻塞的情况,Redis会在主线程中重新分配该I/O线程的读写任务,让该I/O线程去处理其它未被占用的任务,从而保持I/O线程的高效执行。
2.3 Redis I/O线程模型的好处
Redis线程I/O模型将socket连接请求和读写操作分离,将连接请求交给主线程处理,将读写请求交给I/O线程处理,达到了如下几个好处:
1. Redis可以充分利用多核CPU的能力,提高Redis的并发能力。
2. 减少主线程上的I/O延迟,不会因I/O阻塞而导致主线程的阻塞。
3. 对于I/O操作,每个I/O线程都有自己的事件循环,既不会阻塞主线程,也不会占用线程在调度上的资源,因此单个I/O线程的处理速度得到保证。
2.4 Redis I/O线程模型的缺点
虽然Redis线程I/O模型优化了Redis的并发处理能力和I/O延迟问题,同时保证了单个I/O线程的高效执行,但也存在缺点:
1. Redis线程I/O模型的实现较为复杂,与单线程的实现相比,难度较大。
2. 在极高的负载下,每个I/O线程的I/O操作会堆积,导致Redis处理效率降低。
3. 总结
Redis线程I/O模型是一种优化的单线程方案,在保证单线程处理不阻塞的前提下,充分利用了多核CPU的能力,提高了Redis的性能和并发处理能力。
当然,Redis线程I/O模型也存在优缺点,需要在实际使用中根据业务场景进行选择。