pthread线程传递数据回主线程_深入学习redis(后台线程)

免责声明: 本人水平有限,难免有疏漏的地方。如果读者遇到文章中需要改进或者看不懂,甚至是觉得错误的地方,可以给我留言。我想做一个比较全面由浅入深去讲解redis原理和进阶的系列文章,内容偏源码较硬核,但我会尽量使用流程图和画图去配合源码讲解,让文章显得更通俗点。

本实验平台主要是基于本人的MacbookPro

  • redis 6.0.4 目前以单机版为主(均以默认参数为例),不会涉及集群或哨兵
  • macOS Catalina 10.15 内存 8 GB 2133 MHz LPDDR3
  • 我工作中是个Javaer,所以有些概念我会以java中的概念来类比,可能和实际有偏差但是便于理解。

经过一番考虑,决定暂时先把数据结构的学习停一下,因为内容有点太干了,学习一个框架或者技术最好还是能从宏观上有所把握,再深入到细节会更容易融会贯通点,之前的3种数据结构都是非常重要的,暂时足够用了。那么本次就讨论下redis服务端启动后除了主线程(或者也叫IO线程,即负责接受客户端请求的线程)外的其他线程到底是在干嘛? 这次的选题也比较巧,因为我看到我的关注公众号中有人推送了一篇关于redis的面试题,其中有说一句,我给截图大家看看:1fb884c7ae4a2622957e85ae5df6c5e5.png那么redis到底是单线程还是多线程的呢? 我直接来说结论,从严格意义上来说,这道题的答案肯定是:多线程。 我每篇文章前都会提到我所用到的讲解redis的版本是6.0.4的。在6.0版本之前,redis的IO线程就是主线程,在6.0版本之后,虽然默认情况下仍然是以主线程作为IO线程的,但是可以在redis.conf配置中配置io-threads 4来开启多个IO线程(就是被称为多线程模式)的redis,还有io-threads-do-reads yes配置用来启用这几个线程的读(默认这几个多线程只能写)。 我们下面来做个实验:

  • 1.默认配置(主线程作为IO线程)
# 启动服务端
$ redis-server
# 再通过top命令查看
$ top -o threads -pid
PID COMMAND %CPU TIME #TH #WQ #POR MEM PURG CMPRS PGRP PPID STATE BOOSTS %CPU_ME %CPU_OTHRS UID FAUL
45745 redis-server 0.0 00:00.16 4 0 17 1276K 0B 1068K 45745 43351 sleeping *0[1] 0.00000 0.00000 502 775

看到#TH即线程数是4,所以这个redis服务端的进程是有4个线程(1个主线程+3个后台线程)在运行的。

  • 2.开启多线程模式(io-threads 4)
# 启动服务端(指定了含有io-threads 4配置的配置文件路径)
$ redis-server ./redis.conf
# 再通过top命令查看
$ top -o threads -pid
PID COMMAND %CPU TIME #TH #WQ #POR MEM PURG CMPR PGRP PPID STATE BOOSTS %CPU_ME %CPU_OTHRS UID FAUL
46021 redis-server 0.0 00:00.07 7 0 20 1520K 0B 0B 46021 43351 sleeping *0[1] 0.00000 0.00000 502 836

看到#TH即线程数是7,所以这个redis服务端的进程是有7个线程(1个主线程+3个IO线程+3个后台线程)在运行的。

小结:

  • 无论是4还是7,redis服务端进程都是以多线程的方式在运行。
  • 当面试官问你这个问题的时候,你应该先问他:“你问的是IO线程数还是redis进程的总线程数”
  1. 如果问的是总线程数,答案必须得是多线程,如果他说你说的不对,那我劝你换个公司,这人完全在胡说八道。你可以把我的公众号推荐给他,让他多学习学习。
  2. 如果问的是IO线程,就回答6.0以前是单线程,6.0以后默认还是单线程,但是可以通过io-threads配置成多线程。

后台线程

IO线程这一块,我认为是redis核心中的核心,它的IO模型也是它超高性能的关键,这里的坑我之后再填,说回今天我们的主题“后台线程”,前面的例子中已经看到了后台线程有3个,在我之前关于服务端和客户端简单交互的文章里也已经做过简单的介绍了,这次我们将深入下去,好好的聊下这3个后台线程。

简单回顾

main函数中无论是否哨兵模式都会执行一个函数InitServerLast,而它也会执行一个bioInit,就会初始化和启动这3个后台线程

// server.c
int main(int argc, char **argv) {
...
if (!server.sentinel_mode) {
...
InitServerLast();
...
} else {
InitServerLast();
...
}
...
}


void InitServerLast() {
bioInit();
...
}

在头文件中定义了一些常量:3种后台任务的枚举值

// bio.h
#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. */
#define BIO_NUM_OPS 3
// bio.c
void bioInit(void) {
pthread_attr_t attr;
pthread_t thread;
size_t stacksize;
int j;
// 初始化一些后台线程都要用的对象到固定到数组中去
// 之后每一个线程都可以从这些数组中通过索引(枚举值)去获取到
for (j = 0; j < BIO_NUM_OPS; j++) {
// 互斥锁(类比Java的ReentrantLock)
pthread_mutex_init(&bio_mutex[j],NULL);
// newjob condition (类比Java的Condition)
pthread_cond_init(&bio_newjob_cond[j],NULL);
// step condition
pthread_cond_init(&bio_step_cond[j],NULL);
// 每一个后台线程都会有一个链表来记录当前线程待执行的任务
bio_jobs[j] = listCreate();
bio_pending[j] = 0;
}

// 初始化属性和设置线程栈大小
pthread_attr_init(&attr);
pthread_attr_getstacksize(&attr,&stacksize);
if (!stacksize) stacksize = 1;
while (stacksize < REDIS_THREAD_STACK_SIZE) stacksize *= 2;
pthread_attr_setstacksize(&attr, stacksize);

// 启动3个后台线程
for (j = 0; j < BIO_NUM_OPS; j++) {
void *arg = (void*)(unsigned long) j;
// bioProcessBackgroundJobs非常重要是个函数指针,类比Java中的run方法,当线程启动后就会去执行该函数
// arg就是传递给该函数的参数,这里取的就是0,1,2,用来代表不同的后台线程的枚举值
if (pthread_create(&thread,&attr,bioProcessBackgroundJobs,arg) != 0) {
// 任意一个后台线程创建失败都会导致进程退出
exit(1);
}
bio_threads[j] = thread;
}
}

bioProcessBackgroundJobs

这个函数是如此的重要,所以必须单独讲一下,这个函数大体分为两部分:1.设置属性,2.后台线程的主循环,主循环中用不同的枚举值来区分不同后台线程的逻辑。 先看下简易流程图:78ad418e925aa24dfb2ba8e28b4d8086.png对应源码:

void *bioProcessBackgroundJobs(void *arg) {
struct bio_job *job;
unsigned long type = (unsigned long) arg;
sigset_t sigset;
...
// 设置线程标题
switch (type) {
case BIO_CLOSE_FILE:
redis_set_thread_title("bio_close_file");
break;
case BIO_AOF_FSYNC:
redis_set_thread_title("bio_aof_fsync");
break;
case BIO_LAZY_FREE:
redis_set_thread_title("bio_lazy_free");
break;
}

redisSetCpuAffinity(server.bio_cpulist);

// 设置线程 可以被kill
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
// 加锁,类比Java中的Lock的lock方法
pthread_mutex_lock(&bio_mutex[type]);
...
// 第二部分,主循环
while(1) {
listNode *ln;

// job的链表中没有节点的话就会进入wait状态,等待唤醒
if (listLength(bio_jobs[type]) == 0) {
pthread_cond_wait(&bio_newjob_cond[type],&bio_mutex[type]);
continue;
}
// 取出头节点
ln = listFirst(bio_jobs[type]);
// 具体的value
job = ln->value;
// 解锁,类比Java中的Lock的unlock
pthread_mutex_unlock(&bio_mutex[type]);

// 这里就进入不同后台线程的不同逻辑了
if (type == BIO_CLOSE_FILE) {
...
} else if (type == BIO_AOF_FSYNC) {
...
} else if (type == BIO_LAZY_FREE) {
...
} else {
// 异常退出
}
// 释放内存
zfree(job);

// 再次lock
pthread_mutex_lock(&bio_mutex[type]);
// 删除该节点
listDelNode(bio_jobs[type],ln);
// pending数量减1
bio_pending[type]--;

// 给 step Condition 通知,类比Condition的signal
pthread_cond_broadcast(&bio_step_cond[type]);
}
}

接下来就会针对每一个后台线程单独讲解了

BIO_CLOSE_FILE

从枚举值名称上来判断,这个线程是用来关闭文件的。 从之前的主循环中可以看到现实判断队列中是否有节点,没有的话就会挂起,一开始启动服务端的时候,队列中肯定是没有节点的,所以后台线程就会挂起,为了逻辑上是正常的,我们还是先从什么情况下该队列中会被放入节点开始说起(下2个后台线程同)。 3种job创建任务都会执行这个函数bioCreateBackgroundJob

// bio.c
// 这个type就是0、1、2,代表不同的job,也正好能获取数组中的不同索引
void bioCreateBackgroundJob(int type, void *arg1, void *arg2, void *arg3) {
// 分配内存
struct bio_job *job = zmalloc(sizeof(*job));

job->time = time(NULL);
// 三个参数赋值,不同的job,三个参数的含义不同
job->arg1 = arg1;
job->arg2 = arg2;
job->arg3 = arg3;
// 加锁
pthread_mutex_lock(&bio_mutex[type]);
// 把当前的任务放到队列的末尾
listAddNodeTail(bio_jobs[type],job);
// pending数量加1
bio_pending[type]++;
// 通知挂起的后台线程
pthread_cond_signal(&bio_newjob_cond[type]);
// 释放锁
pthread_mutex_unlock(&bio_mutex[type]);
}

搜索函数调用结果c1d58cefb874abe54adcca3357c9f90c.png第一个触发点是基于时间事件的定时任务,(关于时间事件的详细介绍,还是再挖一个坑,这里就想像成redis内置的定时任务就行) 首先还是在初始化服务端的时候

// server.c
void initServer(void) {
...
// 这里会创建时间事件,serverCron就是具体的函数
if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
// 异常退出
exit(1);
}
...
}

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
...
// 这里的间隔是1000ms,也就是1秒执行一次,执行函数是replicationCron
run_with_period(1000) replicationCron();
...
}
// replication.c
void replicationCron(void) {
...
// 主从节点复制完数据快照rdb后,清理rdb文件
removeRDBUsedToSyncReplicas();
...
}

void removeRDBUsedToSyncReplicas(void) {
...
// bg_unlink这个函数不是必定执行的,这个方法之前还牵涉到一些判断,这里省略了,主要就是为了看看调用关系
bg_unlink(server.rdb_filename);
...
}

int bg_unlink(const char *filename) {
int fd = open(filename,O_RDONLY|O_NONBLOCK);
if (fd == -1) {
...
} else {
...
// 终于调用到这个方法了,也可以看到BIO_CLOSE_FILE,只会用到第一个参数,其他两个默认都是NULL
// 而传入的参数是 rdb文件对应的 文件描述符,就是一个数字
bioCreateBackgroundJob(BIO_CLOSE_FILE,(void*)(long)fd,NULL,NULL);
return 0;
}
}

让我们来看BIO_CLOSE_FILE具体是干嘛的吧?

// bio.c
void *bioProcessBackgroundJobs(void *arg) {
...
while(1) {
...
if (type == BIO_CLOSE_FILE) {
// 这个close函数就是去关闭这个文件了,但是依赖于不同的操作系统实现会略有不同
// 这里就理解成关闭文件就行了
close((long)job->arg1);
} else if (type == BIO_AOF_FSYNC) {
...
} else if (type == BIO_LAZY_FREE) {
...
}
...
}
}

还有别的触发点吗?

// replication.c
// 还是这个主从复制的定时任务
void replicationCron(void) {
...
// 如果当前节点不是主节点
if (server.repl_state == REPL_STATE_CONNECT) {
// 连接至主节点
if (connectWithMaster() == C_OK) {
// 连接成功
...
}
}
...
}

int connectWithMaster(void) {
...
if (connConnect(server.repl_transfer_s, server.masterhost, server.masterport,
// 这里的syncWithMaster函数重要,和主节点同步
NET_FIRST_BIND_ADDR, syncWithMaster) == C_ERR) {
...
}
...
}

void syncWithMaster(connection *conn) {
...
// 设置同步的函数
if (connSetReadHandler(conn, readSyncBulkPayload)
== C_ERR)
{
// 异常
...
}
...
}

void readSyncBulkPayload(connection *conn) {
...
if (use_diskless_load) {
...
} else {
...
// 另一个触发点
if (old_rdb_fd != -1) bioCreateBackgroundJob(BIO_CLOSE_FILE,(void*)(long)old_rdb_fd,NULL,NULL);

if (rdbLoad(server.rdb_filename,&rsi,RDBFLAGS_REPLICATION) != C_OK) {
...
bg_unlink(server.rdb_filename);
...
}

/* Cleanup. */
if (server.rdb_del_sync_files && allPersistenceDisabled()) {
...
bg_unlink(server.rdb_filename);
}
}
...
}

之前的文件关闭都是rdb的文件,这个BIO_CLOSE_FILE还会处理aof的文件,调用链如下:

// server.c
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
...
if (hasActiveChildProcess() || ldbPendingChildren())
{
checkChildrenDone();
}
...
}

void checkChildrenDone(void) {
...
} else if (pid == server.aof_child_pid) {
backgroundRewriteDoneHandler(exitcode,bysignal);
if (!bysignal && exitcode == 0) receiveChildInfo();
} else if (pid == server.module_child_pid) {
...
}
// aof.c
void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
...
if (oldfd != -1) bioCreateBackgroundJob(BIO_CLOSE_FILE,(void*)(long)oldfd,NULL,NULL);
...
}

前面源码中我展示了触发点,你们不用深究上下文的逻辑,因为牵涉到主从复制等复杂的机制,而且我一阶段也不打算深入这块,先把单机部分的内容做做好。

小结:BIO_CLOSE_FILE就是用来异步处理rdb和aof文件关闭的后台线程

BIO_AOF_FSYNC

从枚举值名称上来判断,这个线程是用来进行AOF同步的 同样,我们来看看有几个触发的地方

// aof.c
void flushAppendOnlyFile(int force) {
...
try_fsync:
...
if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
...
} else if ((server.aof_fsync == AOF_FSYNC_EVERYSEC &&
server.unixtime > server.aof_last_fsync)) {
if (!sync_in_progress) {
// 如果当前没有正在同步
// 后台同步aof
aof_background_fsync(server.aof_fd);
...
}
...
}
}

void aof_background_fsync(int fd) {
bioCreateBackgroundJob(BIO_AOF_FSYNC,(void*)(long)fd,NULL,NULL);
}
// bio.c
void *bioProcessBackgroundJobs(void *arg) {
...
while(1) {
...
if (type == BIO_CLOSE_FILE) {
...
} else if (type == BIO_AOF_FSYNC) {
// BIO_AOF_FSYNC也只需要用到第一个参数,底层调用到是fsync函数
// 把内存中到数据刷新到磁盘上
redis_fsync((long)job->arg1);
} else if (type == BIO_LAZY_FREE) {
...
}
...
}
}
// config.h
#define redis_fsync fsync

还有别的触发点

// aof.c
void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
...
if (server.aof_fd == -1) {
...
} else {
...
else if (server.aof_fsync == AOF_FSYNC_EVERYSEC)
aof_background_fsync(newfd);
...
}
...
}

小结:BIO_AOF_FSYNC就是异步将aof文件内容刷新到磁盘上的后台线程

BIO_LAZY_FREE

从枚举值名称上来判断,这个线程是用来释放内存的 这个job不太一样,因为释放内存的对象有三种,传参的方式也有三种

  • 1.只用到了arg1  ->  释放redisObject对象
  • 2.同时用到了arg2,arg3  ->  释放Database对象
  • 3.只用到了arg3  ->  释放rax对象 下面是源码
// bio.c
void *bioProcessBackgroundJobs(void *arg) {
...
while(1) {
...
if (type == BIO_CLOSE_FILE) {
...
} else if (type == BIO_AOF_FSYNC) {
...
} else if (type == BIO_LAZY_FREE) {
if (job->arg1)
lazyfreeFreeObjectFromBioThread(job->arg1);
else if (job->arg2 && job->arg3)
lazyfreeFreeDatabaseFromBioThread(job->arg2,job->arg3);
else if (job->arg3)
lazyfreeFreeSlotsMapFromBioThread(job->arg3);
}
...
}
}

FreeObject

这次我们先看job本身的逻辑,再看触发的地方在哪里

// lazyfree.c
void lazyfreeFreeObjectFromBioThread(robj *o) {
decrRefCount(o);
// lazyfree_objects是一个int值,这里也通过原子的方式去减1
atomicDecr(lazyfree_objects,1);
}

这里牵涉到redisObject这个对象了,这里暂时也不展开,现在就提一下,redis中所有存储的键值对中的value其实都是redisObject这个对象,它有一个int字段type,用来表示这个对象具体是什么类型,还有一个refcount字段用来记录这个对象上的引用次数

// object.c
void decrRefCount(robj *o) {
if (o->refcount == 1) {
// 当前引用数量等于1,这次就是等于要释放该对象的内存了
switch(o->type) {
// 根据不同的对象类型,调用不同的函数
case OBJ_STRING: freeStringObject(o); break;
case OBJ_LIST: freeListObject(o); break;
case OBJ_SET: freeSetObject(o); break;
case OBJ_ZSET: freeZsetObject(o); break;
case OBJ_HASH: freeHashObject(o); break;
case OBJ_MODULE: freeModuleObject(o); break;
case OBJ_STREAM: freeStreamObject(o); break;
default: serverPanic("Unknown object type"); break;
}
// 释放对象的内存
zfree(o);
} else {
if (o->refcount <= 0) serverPanic("decrRefCount against refcount <= 0");
// 不等于1的话,把引用数量减1
if (o->refcount != OBJ_SHARED_REFCOUNT) o->refcount--;
}
}

再来看看触发点

// db.c
void delGenericCommand(client *c, int lazy) {
...
for (j = 1; j < c->argc; j++) {
...
int deleted = lazy ? dbAsyncDelete(c->db,c->argv[j]) :
dbSyncDelete(c->db,c->argv[j]);
...
}
addReplyLongLong(c,numdel);
}
// del 命令会执行这个函数
void delCommand(client *c) {
// del是否异步取决于lazyfree-lazy-server-del这个参数的设置,默认是0,即同步
// 配置成1的话 del和unlink就没区别了
delGenericCommand(c,server.lazyfree_lazy_user_del);
}

// unlink 命令会执行这个函数(异步删除)
void unlinkCommand(client *c) {
delGenericCommand(c,1);
}
// lazyfree.c
int dbAsyncDelete(redisDb *db, robj *key) {
...
// 从db中取出entry
dictEntry *de = dictUnlink(db->dict,key->ptr);
if (de) {
// entry存在就取出value值
robj *val = dictGetVal(de);
bioCreateBackgroundJob(BIO_LAZY_FREE,val,NULL,NULL);
...
}

小结:FreeObject是用在删除key的时候可以异步释放内存。

FreeDatabase

// lazyfree.c
// 看参数知道是删除两个字典
// ht1是当前db的数据,ht2是过期的key
void lazyfreeFreeDatabaseFromBioThread(dict *ht1, dict *ht2) {
// 统计所有的元素数量
size_t numkeys = dictSize(ht1);
// 释放内存
dictRelease(ht1);
dictRelease(ht2);
// 减去对应删除的元素数
atomicDecr(lazyfree_objects,numkeys);
}

也来看看什么时候触发的吧,flushall是删除所有数据库的方法,4.0之后的redis增加了async参数表示异步删除

// server.c
...
{"flushall",flushallCommand,-1,
"write @keyspace @dangerous",
0,NULL,0,0,0,0,0,0},
...
// db.c
void flushallCommand(client *c) {
int flags;
// 这个flags会被根据是否传入async,决定取值, 1为异步,0为同步
if (getFlushCommandFlags(c,&flags) == C_ERR) return;
flushAllDataAndResetRDB(flags);
addReply(c,shared.ok);
}

void flushAllDataAndResetRDB(int flags) {
// -1代表全部删除
server.dirty += emptyDb(-1,flags,NULL);
...
}

long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
return emptyDbGeneric(server.db, dbnum, flags, callback);
}

long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)(void*)) {
// EMPTYDB_ASYNC=1,flags如果也等于1的话,async就是1,下面就会执行异步删除
int async = (flags & EMPTYDB_ASYNC);
...
for (int j = startdb; j <= enddb; j++) {
removed += dictSize(dbarray[j].dict);
if (async) {
emptyDbAsync(&dbarray[j]);
} else {
// 同步删除
...
}
}
...
}

void emptyDbAsync(redisDb *db) {
// 先取出两个旧的字典
dict *oldht1 = db->dict, *oldht2 = db->expires;
// 对db上的引用初始化成新的字典
db->dict = dictCreate(&dbDictType,NULL);
db->expires = dictCreate(&keyptrDictType,NULL);
atomicIncr(lazyfree_objects,dictSize(oldht1));
bioCreateBackgroundJob(BIO_LAZY_FREE,NULL,oldht1,oldht2);
}

小结:FreeDatabase是用在flushallflushdb异步删除。

FreeSlots

rax是基数树,一种树形结构,用在集群模式下存储slot对应的的所有key信息,非常复杂,这里没法展开(其实是我压根还没看懂),大概长这样(图片来源于网上)d5483068c8492a49c3279ab2f12ec153.png

void lazyfreeFreeSlotsMapFromBioThread(rax *rt) {
size_t len = rt->numele;
raxFree(rt);
atomicDecr(lazyfree_objects,len);
}

那么同样触发点在哪儿呢?

// replication.c
void readSyncBulkPayload(connection *conn) {
...
// 上面省略了非常多
if (server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB) {
disklessLoadRestoreBackups(diskless_load_backup,1,
empty_db_flags);
}
...
}

void disklessLoadRestoreBackups(redisDb *backup, int restore, int empty_db_flags){
// restore为1
if (restore) {
// 这个和下面的else的区别就在于flags
emptyDbGeneric(server.db,-1,empty_db_flags,replicationEmptyDbCallback);
...
} else {
// 记住这个 ‘| EMPTYDB_BACKUP’ EMPTYDB_BACKUP为4
emptyDbGeneric(backup,-1,empty_db_flags|EMPTYDB_BACKUP,replicationEmptyDbCallback);
...
}
...
}
// db.c
long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)(void*)) {
int async = (flags & EMPTYDB_ASYNC);
// 如果之前有 或EMPTYDB_BACKUP 的操作就会导致这里的backup变量不为0,表示需要 backup
int backup = (flags & EMPTYDB_BACKUP);
if (!backup) {
// flush的前置操作
...
}
// flush的操作
...
if (!backup) {
// flush的后置操作,即不需要backup的时候,不但要把内存中的db给释放掉,在集群中还需要把对应的slot数据也给删掉
if (server.cluster_enabled) {
if (async) {
slotToKeyFlushAsync();
} else {
...
}
}
...
}
}
// lazyfree.c
void slotToKeyFlushAsync(void) {
rax *old = server.cluster->slots_to_keys;

server.cluster->slots_to_keys = raxNew();
memset(server.cluster->slots_keys_count,0,
sizeof(server.cluster->slots_keys_count));
atomicIncr(lazyfree_objects,old->numele);
// 加入了后台线程的队列中
bioCreateBackgroundJob(BIO_LAZY_FREE,NULL,NULL,old);
}

小结:FreeSlots是用在集群状态下flushallflushdb时,当不需要留下备份时,异步删除rax。


上面源码片段不少,但是核心思想是能搞懂redis后台线程到底在干嘛,不需要太纠结源码的含义,大致知道调用链就行了。下面总结下:

总结:

  • redis服务端启动的最后,会创建3个后台线程,分别如下:
    • 1.普通的redisObject对象,区分不同的数据结构,unlink命令
    • 2.db对象,flushdb asyncflushall async命令
    • 3.rax基数树,同样也是flushdb asyncflushall async命令,但是需要在集群环境下。
    • 1.关闭rdbaof文件。
    • 2.aof刷磁盘。
    • 3.释放内存,又分为3种:

本文开始,我就不发朋友圈打扰大家了,大家看到了公众号的文章后,点个赞点个在看就行了,谢谢大家。下一期我准备讲下redisObject对象,尽情期待。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值