Redis 单线程真的是单线程吗?源码角度全面解析
Redis 是单线程的——这句话流传太广了以至于很多人真的以为 Redis 就一个线程在跑。但实际上如果你ps -ef或者top看一眼正在运行的 Redis 进程会发现线程数不止一个。到底怎么回事这篇文章从源码角度把这个问题彻底说清楚。先说结论Redis 的单线程指的是命令处理的主逻辑是单线程的。但 Redis 进程里实际上有主线程处理网络请求、执行命令、事件循环3 个后台线程异步处理关闭文件、AOF fsync、惰性释放子进程RDB 持久化、AOF 重写时 fork 出来的所以 Redis 不是严格意义上的单线程而是命令处理单线程。这个设计非常聪明后面会解释为什么。后台线程bio.c打开bio.c文件开头的注释写得很清楚This file implements operations that we need to perform in the background. Currently there is a single operation, that is a background close(2) system call.说currently a single operation是早期版本现在已经扩展了。看bio.h的定义#defineBIO_CLOSE_FILE0// 异步关闭文件#defineBIO_AOF_FSYNC1// 异步 AOF fsync#defineBIO_LAZY_FREE2// 异步释放内存#defineBIO_NUM_OPS3// 共 3 种后台任务Redis 启动时会创建 3 个后台线程voidbioInit(void){// 初始化锁、条件变量、任务队列for(j0;jBIO_NUM_OPS;j){pthread_mutex_init(bio_mutex[j],NULL);pthread_cond_init(bio_newjob_cond[j],NULL);pthread_cond_init(bio_step_cond[j],NULL);bio_jobs[j]listCreate();bio_pending[j]0;}// 创建 3 个线程for(j0;jBIO_NUM_OPS;j){if(pthread_create(thread,attr,bioProcessBackgroundJobs,arg)!0){serverLog(LL_WARNING,Fatal: Cant initialize Background Jobs.);exit(1);}bio_threads[j]thread;}}每个线程负责一种任务类型有自己的任务队列。主线程通过bioCreateBackgroundJob提交任务voidbioCreateBackgroundJob(inttype,void*arg1,void*arg2,void*arg3){structbio_job*jobzmalloc(sizeof(*job));job-timetime(NULL);job-arg1arg1;job-arg2arg2;job-arg3arg3;pthread_mutex_lock(bio_mutex[type]);listAddNodeTail(bio_jobs[type],job);bio_pending[type];pthread_cond_signal(bio_newjob_cond[type]);// 唤醒对应线程pthread_mutex_unlock(bio_mutex[type]);}后台线程的工作循环void*bioProcessBackgroundJobs(void*arg){unsignedlongtype(unsignedlong)arg;while(1){pthread_mutex_lock(bio_mutex[type]);// 没任务就等着if(listLength(bio_jobs[type])0){pthread_cond_wait(bio_newjob_cond[type],bio_mutex[type]);continue;}// 取任务listNode*lnlistFirst(bio_jobs[type]);jobln-value;pthread_mutex_unlock(bio_mutex[type]);// 执行任务if(typeBIO_CLOSE_FILE){close((long)job-arg1);}elseif(typeBIO_AOF_FSYNC){redis_fsync((long)job-arg1);}elseif(typeBIO_LAZY_FREE){if(job-arg1)lazyfreeFreeObjectFromBioThread(job-arg1);elseif(job-arg2job-arg3)lazyfreeFreeDatabaseFromBioThread(job-arg2,job-arg3);}pthread_mutex_lock(bio_mutex[type]);listDelNode(bio_jobs[type],ln);bio_pending[type]--;pthread_mutex_unlock(bio_mutex[type]);}}典型的生产者-消费者模型。为什么需要这些后台线程BIO_CLOSE_FILEclose()系统调用在某些情况下会阻塞。比如关闭一个大文件或者 NFS 文件系统。主线程阻塞会导致所有客户端都卡住所以放到后台线程做。BIO_AOF_FSYNCAOF 持久化需要定期fsync。这是个磁盘 IO 操作可能很慢。appendfsync everysec配置就是每秒做一次 fsync交给后台线程处理。BIO_LAZY_FREEUNLINK、FLUSHDB ASYNC、FLUSHALL ASYNC这些命令用到的。删除大 key比如包含几百万元素的 hash会阻塞主线程所以放到后台线程慢慢删。这是 Redis 4.0 引入的特性。子进程持久化RDB 快照和 AOF 重写会fork()子进程// rdb.cif((childpidfork())0){/* Child process */closeListeningSockets(0);redisSetProcTitle(redis-rdb-bgsave);// 执行持久化...exitFromChild(0);}// aof.cif((childpidfork())0){/* Child process */closeListeningSockets(0);redisSetProcTitle(redis-aof-rewrite);// 执行 AOF 重写...exitFromChild(0);}为什么用fork()而不是线程因为 fork 出来的子进程有父进程内存的完整副本写时复制可以安全地遍历所有数据做持久化不用担心主线程同时修改。如果是多线程就要加各种锁复杂度飙升。但 fork 有代价父进程内存越大fork 越慢。所以 Redis 官方建议单实例内存不要太大。主线程为什么是单线程的回到核心问题处理命令的主逻辑为什么用单线程几个原因1. 没锁的代价多线程意味着共享数据要加锁。Redis 数据结构复杂加锁会带来锁竞争开销死锁风险代码复杂度上升单线程完全避免这些问题。2. 瓶颈不在 CPURedis 大部分操作是内存操作速度极快。瓶颈通常在网络带宽客户端连接数大 key 操作多线程不一定能提升性能反而增加复杂度。3. 事件循环模型Redis 用 epoll/kqueue 做多路复用一个线程就能处理成千上万的并发连接。这种 IO 模型本身就是单线程友好的Nginx 也是类似设计。那些慢操作怎么办单线程最大的问题是一个操作慢了后面所有请求都得等。Redis 的应对策略1. 把操作拆细比如KEYS *会遍历所有 key很慢。Redis 后来加了SCAN每次只遍历一小部分用游标续传。2. 扔给后台线程惰性删除lazy free就是这个思路。UNLINK命令异步删除大 keyvoidunlinkCommand(client*c){if(server.lazyfree_lazy_server_del){// 异步删除bioCreateBackgroundJob(BIO_LAZY_FREE,NULL,NULL,key);}else{// 同步删除旧版本行为dbDelete(c-db,key);}}3. 用子进程持久化交给 fork 出来的子进程。4. 直接禁止KEYS命令在生产环境不建议用DEBUG SLEEP也是调试用的。那 Redis 6.0 的多线程 IO 是什么Redis 6.0 引入了多线程来处理网络 IO读写 socket但命令执行还是单线程。这个特性的代码在networking.c里主要解决的是网络带宽瓶颈问题。当客户端数据量很大时读写 socket 成了瓶颈可以用多个线程并行处理。但核心的数据结构操作、命令执行依然是单线程。总结线程/进程职责主线程事件循环、命令执行bio 线程 1异步关闭文件bio 线程 2异步 AOF fsyncbio 线程 3异步惰性释放子进程RDB 持久化、AOF 重写Redis 的单线程是指命令处理的主流程。但像文件关闭、fsync、大 key 删除这些可能阻塞的操作都用后台线程或子进程处理了。这是一个务实的设计选择。单线程简单、无锁、容易维护配合异步 IO 和后台任务足以应付绝大多数场景。如果真的需要更高性能正确的做法不是改 Redis 代码而是部署多个实例用集群分担压力。毕竟 Redis 本身就支持集群模式。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2472703.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!