redis代码结构之一mem,bio

 Redis代码结构 一mem,bio

1.       Redis代码结构

事件库

类型库 网络库 持久化 复制 订阅 事务 main client 其它
ae.c
ae_epoll.c
ae_kqueue.c
ae_select.c
syncio.c
adlist.c
intset.c
object.c
sds.c
t_hash.c
t_list.c
t_set.c
t_string.c
t_zset.c
ziplist.c
zipmap.c
anet.c
networking.c
aof.c
rdb.c
redis-check-aof.c
redis-check-dump.c

replication.c

pubsub.c

multi.c config.c
redis.c
dict.c
db.c
redis-cli.c debug.c
lzf_c.c
lzf_d.c
pqsort.c
redis-benchmark.c
release.c
sha1.c
slowlog.c
sort.c
util.c
vm.c
zmalloc.c
bio.c
endian.c

 2.       zmalloc.c

该文件用于封装内存分配器:redis提供的内存分配器有c库的malloc、jemalloc、acmalloc。这几个分配器的适用场景见()。提供该层封装的主要目的是统计内存的使用量,因为redis本身是为全memory设计的,所以要时刻观察内存的使用情况。
void *zmalloc(size_t size)用于分配内存,它首先判断使用的分配器是使用有头部信息,即把大小保存到内容的正上方。默认的三种分配器都是有头部信息的,所以PREFIX_SIZE=0,然后会进行大小的sizeof(long)字节对齐,(ms malloc分配出来的默认应该是大小对齐的,其它的两种方式不清楚),对于没有对齐的则扩展分配的大小,并且累加used_memory。
update_zmalloc_stat_alloc(__n,__size)
if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1));
used_memory += _n;
void *zcalloc(size_t size),其实跟zmalloc差不多,而不像c的calloc,因为它只有一个输入参数。
void *zrealloc(void *ptr, size_t size)与c的realloc一样,只是多了更新used_memory的大小。Zfree类似。
char *zstrdup(const char *s)与strdup类似,进行字符串拷贝,该函数分配内存,要求调用它的函数在结束时释放相应的内存,不然就会导致内存泄漏。
size_t zmalloc_used_memory(void)返回已使用的内存量。
void zmalloc_enable_thread_safeness(void)用于设计该分配器是否可支持多线程,redis的默认是不支持的。
size_t zmalloc_get_rss(void),该函数用于获得redis-server的RSS即当前驻留在物理内存的页数。该函数通过读取/proc/pid/stat来获取该信息。上面的used_memory指是当前使用的虚拟内存量与RSS不一样。 

3.       bio.c

该文件用于提供后台线程的相关函数。Redis从 2.#引进了两个后台线程用于主线程的aof文件的close以及fsync操作。因为这两个操作会导致主线程阻塞。详细的应用场景见前面写的“Redis Aof”的文章。这里我们直接介绍该文件里的函数。
该文件包含4个静态全局变量以及一个bio_job的结构。

static pthread_mutex_t bio_mutex[REDIS_BIO_NUM_OPS]; //为访问静态全局变量bio_jobs,bio_pending提供互斥保障。

static pthread_cond_t bio_condvar[REDIS_BIO_NUM_OPS];

static list *bio_jobs[REDIS_BIO_NUM_OPS];  //保存job的两个adlist

static unsigned long long bio_pending[REDIS_BIO_NUM_OPS]; //当前等待处理的job数目

struct bio_job { //job的结构体,现在用arg1来保存fd

    time_t time; /* Time at which the job was created. */

    void *arg1, *arg2, *arg3;

};

void bioInit(void),该函数被initServer调用,完成上面的全局变量的初始化,以及创建close job处理线程以及fsync处理线程(bioProcessBackgroundJobs)。
void bioCreateBackgroundJob(int type, void *arg1, void *arg2, void *arg3),该函数用于创建bio_job并将该job加入到相应的bio_jobs[type] list里。当前有三个地方会调用该函数:其一,创建REDIS_BIO_CLOSE_FILE job。当后台rewrite aof子进程结束时,主进程wait收到SIGCHLD信号时,调用backgroundRewriteDoneHandler来进行相应的信号处理:此时就需要close(oldaof),然后rename(tempfilename,oldaofname),如果直接按照这个过程操作则会导致阻塞,所以这边采用的方法是如果oldaof是已经被关闭的则再打开(非关闭则不需要),然后再rename此时oldaof因为有一个open的引用,所以不会unlink也就不会阻塞,最后再由刚才我们说的后台REDIS_BIO_CLOSE_FILE线程进行close,该线程会被阻塞,但这不会影响主线程;其二,创建REDIS_BIO_AOF_FSYNC job,同样是在backgroundRewriteDoneHandler里,当把tempfile的fd重置为server.appendfd的fd时,if (server.appendfsync == APPENDFSYNC_EVERYSEC)则aof_background_fsync(newfd);该函数就会创建一个REDIS_BIO_AOF_FSYNC job。其三,flushAppendOnlyFile函数,我们在redis aof里介绍过,在每次event loop之前的beforesleep函数里都会调用该函数,显然该函数是用于刷新aof文件(注:这里有个write操作会导致主线程阻塞?为什么不添加到file event?),此时如果已经有REDIS_BIO_AOF_FSYNC job等待处理bioPendingJobsOfType(REDIS_BIO_AOF_FSYNC) != 0,则不需要再aof_background_fsync增加fsync job(因为这里只有这么一个aof fd),否则如果是server.appendfsync == APPENDFSYNC_EVERYSEC则aof_background_fsync增加一个REDIS_BIO_AOF_FSYNC job由后台fsync线程来进行sync操作。
上面就是当前版本的使用到后台close及fsync线程的地方。
void *bioProcessBackgroundJobs(void *arg),该函数是两个线程的处理函数,它们通过参数arg来辨别为不同的线程。该函数就是通过类型取得相应的bio_jobs list的job,然后调用close(job->arg1)或者aof_fsync(job->arg1)来完成相应的操作。
unsigned long long bioPendingJobsOfType(int type),返回当前等待type线程处理的jobs数目。对于fsync jobs显然该pending数最多为1,上面我们已经讲到了,因为它只对当前的aof fd有效,而任务时刻该文件都是唯一的;对于close jobs的pending数可能会有多个,因为close 的是old aof file,显然这个文件每次都是不一样的。
下面我们通过一张图来描述一下当前的几个进程及线程的关系。

redis代码结构之一mem,bio_第1张图片

图1 redis中的进程与线程

注:关于bgsave,bgrewrite子进程的运行情况见前面文章的介绍。

你可能感兴趣的:(redis,多线程,list,rss,File,jobs)