redis请求处理流程

1, 编译:redis源码是基于makefile构建的,在ide里调试很麻烦,不能符号跳转,所以就根据makefile里描述的编译过程,用cmake重新写一遍,导入到clion里调试分析。
编译c / c++,不管怎么构建,不管用什么ide (eclipse cdt,visual studio,xcode,clion),都要设置几个关键的东西:头文件路径,依赖的lib路径,lib名,编译器选项,源码中编译相关的宏。
编译redis server的CMakeLists.txt

编译redis-server

aux_source_directory(. DIR_REDIS_SERVER_SRCS)
add_executable(redis-server ${DIR_REDIS_SERVER_SRCS})
target_include_directories(redis-server PRIVATE
./include
../lua
../hiredis
../jemalloc)
target_link_libraries(redis-server
m dl hiredis lua jemalloc)

编译redis-cli

set(REDIS_CLI_SRCS anet.c adlist.c redis-cli/redis-cli.c zmalloc.c release.c anet.c ae.c crc64.c)
add_executable(redis-cli ${REDIS_CLI_SRCS})
target_include_directories(redis-cli PRIVATE
.
../hiredis
../linenoise)
target_link_libraries(redis-cli
m dl hiredis linenoise jemalloc)

//target_compile_options() 添加编译选项 -Wall之类的
//target_compile_definitions() 添加预定义宏
//PRIVATE依赖:头文件不依赖,源文件依赖
//INTERFACE:头文件依赖 源文件不依赖
//PUBLIC 头文件和源文件都依赖

2,c/c++指令的项目,直接搜索main方法。
redis-server的main方法在server.c文件里。下面开始描述server启动的整个过程。
整个server的主要状态都保存在redisServer server这个结构体里,先看一眼:


redisServer.png

首先初始化server,c语言中变量不初始化,值是不确定的。


initServerConfig.png

值得注意的是默认的持久化策略。

然后是命令行参数解析:


命令行参数解析.png
参数解析.png

redis-server的第一个参数如果不是以一个或两个减号开头就表示配置文件路径,
后续的可选参数会跟读到内存中的配置文件内容合到一起解析。然后就是一行一行的处理配置文件了。


加载配置文件.png

server状态加载完毕,可以初始化socket准备接收client连接了,毫无疑问使用IO多播,但是跟netty又不太一样,netty是使用jdk里的nio,jdk为了可移植,使用了所有系统都支持且行为一致的select/poll。但是select/poll由于每次都要把监控的fd和关联event传入内核,然后内核再把IO可用的fd和event返回给调用方,性能较差,select比poll性能更差,select只能指定最大fd,poll可以精确指定fd。所以像redis和nginx这种支持超高并发的系统都会使用各个系统特定的性能更高的api。linux上使用epoll,osx上使用kqueue,unix上使用/dev/poll。
先解释一下fd这个东西:file descriptor,文件描述符,linux/unix系统中号称一切都是文件,每个文件都有一个fd,类似windows上的Handle,打开文件时获取fd,操作文件时指定fd。linux系统上有一个配置,指定了fd_set的大小。fd_set表示一个进程打开的所有fd,顺序增加,0,1,2分别表示stdin/stdout/stderr。每次打开文件,从0开始查找fd_set中第一个未被占用的fd作为新打开文件的fd。fd_set默认大小1024。所以第一步是根据设置的client连接数最大值调整fd_set。


修改fd_set.png

之后是调用kqueue系统api初始化aeEventLoop结构体,绑定到指定端口。aeEventLoop中主要有两类字段:timeEventNextId,lastTime,timeEventHead跟定时任务有关,其他的跟IO多播有关。


EventLoop.png

之后是初始化redis的16个db。dict是hash table结构,之后统一描述各种数据结构。


16db.png

之后会把一个非常重要的定时任务添加到EventLoop上,然后把server socket的fd注册到kqueue的监控列表里。


添加定时任务.png

之后是加载动态模块和从aof/rdb里恢复状态。redis有一套扩展用的api,server启动时可以加载实现里这些api的动态库。有一个很明显很有用的扩展点:通过实现api往EventLoop里添加定时任务。


恢复状态.png

最后进入主循环:


主循环.png

主循环的核心流程如下:
先计算多少毫秒之后要触发最早的定时任务,利用定时任务触发的间隙检查是否用io可用的fd,定时任务都在EventLoop的单链表里。如果没有定时任务要执行,直接阻塞直到有fd可用。


查找最早的定时任务.png

使用kqueue在指定时间段内检查可用的fd,调用关联的函数


处理可用的fd.png

一共有三种情况:accept client连接;client请求可读,client可以接收响应。

accept client是之前绑定到指定端口的server socket注册的,触发时会把收到的client连接注册到kqueue里。client结构体保存每个client相关的状态,所有的client都保存在server.clients的双链表里。


accept.png
createClient.png

保护模式下,只有跟server ip相同的client才能连接。


保护模式.png

client发送请求fd触发时:
先把请求缓存在client的querybuf里。


缓存请求到buffer.png

redis client协议:先按换行符拆分,每行是一个指令,每个指令再按空格拆分,保存到client里


拆分指令.png

然后就是根据指令列表里每个指令关联的函数,执行指令。


查找指令.png
各种检查.png
执行指令.png

以简单的get key为例分析响应过程:


从db里查找key.png

响应信息放入client的buf,放不下里放入replay链表.


响应信息放入client的buf和replay链表.png

有响应信息要返回的client,加入server.clients_pending_write链表
加入clients_pending_write链表.png

每次调用kqueue等待IO之前都会先调用beforeSleep,beforeSleep会检查上个循环有响应信息待处理的client,注册write事件。


注册write事件.png

当本次事件循环,发现write可用时,


write.png

完整的指令处理过程结束了,可以发现目前只有一个线程,一个线程干了很多活,避免了多线程中线程切换和锁相关的开销,这也是另一个redis高性能的原因。

redis除了响应client请求外,还有很多其他的要做:最明显的就是缓存持久化,过期key清除等。这些任务都集中在server初始化时注册到EventLoop上的定时任务里。之前说过请求处理任务是在定时任务触发的间隙中处理的。

现在分析一下定时任务都干了啥:很简单,循环EventLoop上定时任务单链表,触发时间过期的任务,调用对应的定时任务函数,如果timeProc不返回-1,表示定时任务还需要触发,计算好下次触发时间,否则id设置为-1,等待下次从定时任务链表里删除。


定时任务.png

目前redis只有一个定时任务:serverCron,根据配置文件里server.hz的值,决定serverCron每秒执行 1000 / server.hz 个周期
serverCron的子任务如下:

首先是100个周期执行一次的统计任务:


统计任务.png

然后是记录内存占用的峰值,如果收到了shutdown指令,关闭子进程


shutdown.png

每5000个周期打印一次状态log


状态log.png

检查很久不发指令的client,关闭连接,检查阻塞在b开头的指令的client,阻塞指定时间后返回timeout错误信息。


关闭超时的client.png

如果client太多,一次全部检查一遍很阻塞很长时间,所以分散压力,每次只执行一部分。一秒之内全部检查一遍,其实这个时间是非常不精确又无法保证的。


分散压力.png

检查过期key,如果key很多,一个一个检查肯定会阻塞很久,所以每次都花定量的时间随机删除过期的key,如果多次随机都没有遇到过期key,密度很小,停止任务。


检查过期key.png

删除过期key竟然还有异步删除,原来是漏掉了一个地方:redis除了主线程,还有三个后台任务线程,用于处理各种异步操作。


异步删除key.png

异步任务只有三种:异步关闭连接,异步同步aof,异步删除。


异步任务类型.png

使用pthread创建线程,等待条件变量通知。


异步任务线程.png

然后会fork两个子进程,分别用于处理aof和rdb持久化。

aof持久化.png
rdb持久化.png

然后是一些跟集群和主从相关的任务,等待下回分解。


集群和主从.png

本节完!!!。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,884评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,212评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,351评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,412评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,438评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,127评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,714评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,636评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,173评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,264评论 3 339
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,402评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,073评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,763评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,253评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,382评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,749评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,403评论 2 358

推荐阅读更多精彩内容