Redis source code 版本: 5.0.7
Redis 启动后,main thread 中启动三个bio线程,分别执行三种任务:
#define BIO_CLOSE_FILE 0 /* Deferred close(2) syscall. */
#define BIO_AOF_FSYNC 1 /* Deferred AOF fsync. */
#define BIO_LAZY_FREE 2 /* Deferred objects freeing. */
1. BIO_CLOSE_FILE: pthread callback task, 任务便是直接执行: close(fd); fd 通过 bioJob 对象的 arg1 域传入。
2. BIO_AOF_FSYNC: 任务是调用 linux: fdatasync(fd); fd 通过 bioJob 对象的 arg1 域传入。
3. BIO_LAZY_FREE: 主要是为了 free Redis server对象中16个redisDb中的的两张 hash table。通过 job 的 arg2, arg3 域传入。
三个线程各自维护一张 redis的 double list。在main thread中 通过 createBioJob 接口封装 job,push如该double list。而bio thread作为消费者,通过生产者消费者模式,pop 内部的 joblist,三个类型的thread 分别执行三种类型的 job。
这是典型的简单生产者消费者模式+jobQueue 的多线程模型,很容易理解。
关键是redis自身对bio线程的一些设置:
1. 设置线程栈大小:
/* Set the stack size as by default it may be small in some system */
pthread_attr_init(&attr);
pthread_attr_getstacksize(&attr,&stacksize);
if (!stacksize) stacksize = 1; /* The world is full of Solaris Fixes */
// bio background job thread neead 4 MB stack size at least
while (stacksize < REDIS_THREAD_STACK_SIZE) {
stacksize *= 2;
}
pthread_attr_setstacksize(&attr, stacksize);
2. set cancel 请求可用。可自行查询linux pthread cancel的内部机制,在 cond wait的情况下,cancel请求一般是可以被正确处理的。
/* Make the thread killable at any time, so that bioKillThreads()
* can work reliably. */
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
3. 处理了 SIGALRM 信号。
/* Block SIGALRM so we are sure that only the main thread will
* receive the watchdog signal. */
sigemptyset(&sigset);
sigaddset(&sigset, SIGALRM);
if (pthread_sigmask(SIG_BLOCK, &sigset, NULL))
{
serverLog(LL_WARNING,
"Warning: can't mask SIGALRM in bio.c thread: %s", strerror(errno));
}
作用就是将 SIGALRM 信号,添加到 屏蔽字 中。该线程,便不再响应处理该信号了,只会被添加到pending que中一直阻塞,导致该信号一直被处于一个悬而未决的状态,也不允许系统采用默认处理方式处理。当然,只是该线程在active 情况下不处理,那么其他线程收到还是可以处理的。
为什么?
第一点,很好理解。
第三点,对于某个特殊信号,自己不处理,让其他线程统一处理,也可以理解。当然,SIGALRM信号有没有作为 watch dog信号在主线程里面被处理,还需进一步源码确认。
第二点,外部主动调用 bio 提供的 bioKillThreads() unclean stop thread, 而bio不提供 bioDeinit 去主动 join gracefully exit,有点奇怪,不过 bio 提供了bioWaitStepOfType 函数,去阻塞检测 不同任务的类型余量,当任务que为空,再 bioKill其实也可以。不同任务,其实除了第二种任务,进程退出可能有影响外,其他的进程,都是释放 fd 以及内存资源,可以让进程自动回收。
Redis 的实际情况是:
所以 redis-serve作为服务程序启动后,期望的是一直运行。虽然代码暂未通读,但是,某节点 dump之后,数据的丢失看来也是不可避免的。