作者:钟华锦
本文可作为深入阅读Redis源码的大纲
前言
本文以Redis 6.0为例,通过不修改Redis源码、在运行时(linux /一主一从环境)动态加入日志的方式捕捉了正常运行情况下Redis master空转、建立连接、断开连接、进行增删改查操作、事务操作等场景下的核心代码,希望能提供成体系的、函数而非行级别的源码介绍。
阅读建议:在介绍的函数中设置断点观察上下文对理解有帮助。
一、场景1:空转状态下会触发的代码
Calling afterSleep at server.c
Calling moduleCount at module.c
Calling processTimeEvents at ae.c
Calling aeGetTime at ae.c
Calling serverCron at server.c
Calling updateCachedTime at server.c
Calling ustime at server.c
Calling trackInstantaneousMetric at server.c
Calling mstime at server.c
Calling getLRUClock at evict.c
Calling zmalloc_used_memory at zmalloc.c
Calling zmalloc_get_rss at zmalloc.c
Calling zmalloc_get_allocator_info at zmalloc.c
Calling clientsCron at server.c
Calling listRotateTailToHead at adlist.c
Calling clientsCronHandleTimeout at timeout.c
Calling clientsCronResizeQueryBuffer at server.c
Calling sdsAllocSize at sds.c
Calling sdsHdrSize at sds.c
Calling clientsCronTrackExpansiveClients at server.c
Calling sdsZmallocSize at networking.c
Calling sdsAllocPtr at sds.c
Calling getClientOutputBufferMemoryUsage at networking.c
Calling clientsCronTrackClientsMemUsage at server.c
Calling getClientType at networking.c
Calling databasesCron at server.c
Calling iAmMaster at server.c
Calling activeExpireCycle at expire.c
Calling clientsArePaused at networking.c
Calling activeDefragCycle at defrag.c
Calling hasActiveChildProcess at server.c
Calling tryResizeHashTables at server.c
Calling htNeedsResize at server.c
Calling incrementallyRehash at server.c
Calling ldbPendingChildren at scripting.c
Calling stopThreadedIOIfNeeded at networking.c
Calling moduleFireServerEvent at module.c
Calling aeAddMillisecondsToNow at ae.c
Calling aeProcessEvents at ae.c
Calling aeSearchNearestTimer at ae.c
Calling beforeSleep at server.c
Calling handleBlockedClientsTimeout at timeout.c
Calling raxSize at rax.c
Calling handleClientsWithPendingReadsUsingThreads at networking.c
Calling tlsProcessPendingData at tls.c
Calling listRewind at adlist.c
Calling listNext at adlist.c
Calling tlsHasPendingData at tls.c
Calling aeSetDontWait at ae.c
Calling trackingBroadcastInvalidationMessages at tracking.c
Calling flushAppendOnlyFile at aof.c
Calling handleClientsWithPendingWritesUsingThreads at networking.c
Calling freeClientsInAsyncFreeQueue at networking.c
Calling handleClientsBlockedOnKeys at blocked.c
Calling aeApiPoll at ae_select.c
Calling connSocketEventHandler at connection.c
Calling readQueryFromClient at networking.c
Calling connGetPrivateData at connection.c
Calling postponeClientRead at networking.c
Calling sdsMakeRoomFor at sds.c
Calling sdsReqType at sds.c
Calling zmalloc at zmalloc.c
Calling zfree at zmalloc.c
Calling connSocketRead at connection.c
Calling sdsIncrLen at sds.c
Calling processInputBuffer at networking.c
Calling processMultibulkBuffer at networking.c
Calling string2ll at util.c
Calling createStringObject at object.c
Calling createEmbeddedStringObject at object.c
Calling LRU_CLOCK at evict.c
Calling processCommandAndResetClient at networking.c
Calling processCommand at server.c
Calling moduleCallCommandFilters at module.c
Calling lookupCommand at server.c
Calling dictFetchValue at dict.c
Calling dictFind at dict.c
Calling dictSdsCaseHash at server.c
Calling dictGenCaseHashFunction at dict.c
Calling siphash_nocase at siphash.c
Calling siptlw at siphash.c
Calling dictSdsKeyCaseCompare at server.c
Calling ACLCheckCommandPerm at acl.c
Calling writeCommandsDeniedByDiskError at server.c
Calling call at server.c
Calling redisOpArrayInit at server.c
Calling replconfCommand at replication.c
Calling getLongLongFromObject at object.c
Calling slowlogPushEntryIfNeeded at slowlog.c
Calling commandProcessed at networking.c
Calling resetClient at redis-benchmark.c
Calling freeClientArgv at networking.c
Calling decrRefCount at object.c
Calling freeStringObject at object.c
Calling sdsrange at sds.c
Calling sdsRemoveFreeSpace at sds.c
Calling replicationCron at replication.c
Calling removeRDBUsedToSyncReplicas at replication.c
Calling refreshGoodSlavesCount at replication.c
Calling migrateCloseTimedoutSockets at cluster.c
Calling dictGetSafeIterator at dict.c
Calling dictGetIterator at dict.c
Calling dictNext at dict.c
Calling dictReleaseIterator at dict.c
Calling serverLog at server.c
Calling serverLogRaw at server.c
Calling nolocks_localtime at server.c
Calling is_leap_year at localtime.c
Calling replicationFeedSlaves at replication.c
Calling ll2string at util.c
Calling digits10 at util.c
Calling feedReplicationBacklog at replication.c
Calling stringObjectLen at object.c
Calling feedReplicationBacklogWithObject at replication.c
Calling addReplyArrayLen at networking.c
Calling addReplyAggregateLen at networking.c
Calling addReply at networking.c
Calling prepareClientToWrite at networking.c
Calling clientHasPendingReplies at networking.c
Calling clientInstallWriteHandler at networking.c
Calling listAddNodeHead at adlist.c
Calling _addReplyToBuffer at networking.c
Calling addReplyBulk at networking.c
Calling addReplyBulkLen at networking.c
Calling handleClientsWithPendingWrites at networking.c
Calling listDelNode at adlist.c
Calling writeToClient at networking.c
Calling connSocketWrite at connection.c
可见server.c,module.c,ae.c,evict.c,zmalloc.c,adlist.c,timeout.c,sds.c,networking.c,expire.c,defrag.c,scripting.c,rax.c,tls.c,tracking.c,aof.c,blocked.c,ae_select.c,connection.c,util.c,object.c,dict.c,siphash.c,acl.c,replication.c,slowlog.c,redis-benchmark.c,cluster.c,localtime.c都会在系统空转的过程中触发。(module.c、scripting.c都是类似插件的独立机制,这里不做介绍)
一)server.c
redis是一个基于事件驱动的程序,server.c最核心的功能就是事件的监听和响应,此外server.c还负责一些定期任务的处理。
- afterSleep。如上所说redis是一个事件驱动程序,监听事件的过程可能是一个sleep的过程,当sleep timeout或者有事件发生时就会从监听事件的函数中返回,返回后会立刻调用这个函数。
- serverCron。这个函数处理一些定期任务,比如过期key的搜集、更新内存占用等统计信息、slave重连等。
- updateCachedTime。根据代码注释这个函数,每次对象访问时都会将当前时间存储在对象中,但是每次都使用time(NULL)获取时间是低效的,而且其实这个时间戳不需要非常精准,所以使用updateCachedTime中更新的全局变量server中对应的字段就可以提高效率并且满足要求,达到优化的效果。
- ustime。获取unix时间,单位是微秒。这个是updateCachedTime中用于计算时间戳的函数。
- trackInstantaneousMetric。定期计算单位时间出口流量,用于统计。
- mstime。获取unix时间,单位是毫秒。
- clientsCron。定期处理客户端连接,比如断开超时的连接。
- clientsCronResizeQueryBuffer。回收客户端查询缓冲区的空间。
- clientsCronTrackExpansiveClients。此功能用于跟踪最近几秒内使用最大内存量的客户端。
- clientsCronTrackClientsMemUsage。计算客户端占用的内存大小。这是个优化函数,INFO命令获取的信息不是瞬时的值,而是定期调用这个函数得到的值,因为瞬时值的获取是耗时的。
- databasesCron。定期的数据库后台操作,比如调整哈希表大小。
- iAmMaster。判断自己是不是master。
- hasActiveChildProcess。判断有没有活动的子进程执行 RDB 保存、AOF 重写或加载模块产生的某些副进程。
- tryResizeHashTables。如果哈希表中已用槽的百分比达到 HASHTABLE_MIN_FILL 则调整哈希表的大小以节省内存。
- htNeedsResize。判断哈希表是否需要调整大小。
- incrementallyRehash。当哈希表保存的键值对数量太多或者太少时, redis对哈希表的大小进行相应的扩展或者收缩,Redis会定期检查并执行哈希表的扩展或者收缩。
- beforeSleep。和上面的afterSleep对应,在监听事件之前调用,在sleep前做些事情,比如释放客户端连接,把aof缓存写到文件中等等。
- processCommand。命令如果已经从网络中读取完,这个函数就会处理命令。
- lookupCommand。获取key的值。
- dictSdsCaseHash。对key进行哈希运算。
- dictSdsKeyCaseCompare。封装strcasecmp。
- call。执行指令。
- redisoparrayinit。初始化永远也命令传播的数组。
- serverLog和serverLogRaw。都是写日志。
二)ae.c
ae.c处理redis的两种事件:文件事件(命令处理相关)和时间事件(定期任务相关)。
- aeProcessEvents。在一个eventLoop中被循环调用,文件事件和时间事件都在这里面处理。
- processTimeEvents。处理时间事件,上面提到过的server.c/serverCron实际上就是一种时间时间。
- aeGetTime。gettimeofday的封装。
- aeAddMillisecondsToNow。aeGetTime获得的时间加上一个时间间隔,用于决定下一次时间事件的执行判断。
- aeSearchNearestTimer。获取最早要触发的时间事件。
- aeSetDontWait。上面提到的aeProcessEvents函数的入参有一个位标志,其中有一个AE_DONT_WAIT位,当这个位是1时,aeProcessEvents在调用多路复用函数处理文件事件的时候不会阻塞;此外,在调用多路复用函数之前执行的server.c/beforeSleep函数中会判断网络中是否有未读数据,如果还有未读数据则AE_DONT_WAIT会被设置成1避免多路复用的睡眠阻塞导致数据不能及时读取。
三)evict.c
- getLRUClock。根据LRU精度(精确到秒/分秒/毫秒等)计算时间戳。这个函数涉及Redis的LRU和Object机制。
- LRU_CLOCK。封装getLRUClock。Redis所有的基础数据结构(value的类型)都是以Object的方式存在的,每个Object被访问的时间都会被记录在Object中。如果LRU的精度低于定期任务中刷新全局的lruclock的频率,则会使用全局的lruclock而不是调用getLRUClock计算。
四)zmalloc.c
系统空转时zmalloc.c主要用于统计,获取内存占用的信息。
- zmalloc_used_memory。获取当前已经占用的内存空间大小,这个值保存在全局变量zmalloc.c/used_memory中。
- zmalloc_get_rss。获取进程实际占用的内存空间。
- zmalloc_get_allocator_info。Redis编译时默认使用jemalloc作为内存管理器降低内存碎片减少常驻内存占用,zmalloc_get_allocator_info获取jemalloc用于管理内存的各种信息。
- zmalloc。封装malloc。
- zfree。封装free。
五)expire.c
- activeExpireCycle。清除失效key。
六)networking.c
- sdsZmallocSize。用于计算客户端缓冲区内存占用。上面提到的clientsCronTrackExpansiveClients中使用这个函数计算输入缓冲区的内存占用
- getClientOutputBufferMemoryUsage。用于输出缓冲区内存占用,即客户端仍未读取的回复的字节数。
- getClientType。获取客户端类别。
- clientsArePaused。Redis提供了CLIENT PAUSE命令阻塞客户端命令一段时间,如果pause时间到了则清除暂停状态。
- stopThreadedIOIfNeeded。Redis 6 事件驱动模型是多reactor模型,多reactor模型包含了负责建立连接的基础线程,负责读、写、解码的IO线程,以及处理逻辑计算的工作线程,这样设计可以处理更多的并发。但是如果客户端连接并没有那么多的时候,其实必要使用那么多的IO线程,基础线程就够用了,所以Redis提供了stopThreadedIOIfNeeded停止IO线程。
- handleClientsWithPendingReadsUsingThreads。把没有处理完的读事件分发给IO线程。
- handleClientsWithPendingWritesUsingThreads。把没有处理完的写事件分发给IO线程。
- freeClientsInAsyncFreeQueue。关闭客户端。
- readQueryFromClient。从网络中读取指令。
- postponeClientRead。多线程状态下要根据这个函数的返回结果决定是不是在基础线程中执行的的readQueryFromClient中读数据,如果这个函数返回1,表示读数据的任务后续再分配给其它IO线程处理。
- processInputBuffer。获取Gopher、Multi-bulk协议或者管道方式完整的指令,如果指令已经读取完整则执行指令。
- processMultibulkBuffer。读取Multibulk协议的指令。
- processCommandAndResetClient。执行指令并重置客户端,为客户端的下一条指令做准备。
- commandProcessed。执行一条指令执行完成之后需要做一些额外的工作。比如上面提到的 重置客户端;此外主节点在处理完写入命令后,会把命令的字节长度做累加,更新用于主从复制的replication offset,然后把这条指令传播到从机。
- freeClientArgv。释放指令参数占用的空间。
- addReplyArrayLen。把响应的数组个数转换成字符串放到写出缓冲区中。
- addReplyAggregateLen。上面提到的addReplyArrayLen就是封装这个函数。
- addReply。把指令的响应写入缓冲区。
- prepareClientToWrite。把客户端放到等待写队列。后续会分配IO线程(如果有)执行写操作。
- clientHasPendingReplies。判断client的缓冲区中是否还有数据未处理。
- clientInstallWriteHandler。上述的prepareClientToWrite主要就是封装这个函数。
- _addReplyToBuffer。上述的addReply的核心功能有这个函数完成。
- addReplyBulk。添加一个对象作为bulk回复。
- addReplyBulkLen。添加bulk协议开头的数字。bulk回复的开头是一个指示前端进行数据读出的数字。
- handleClientsWithPendingWrites。上面stopThreadedIOIfNeeded中提到过有客户端连接少的时候可能只有一个IO线程工作,这种情况下直接在主线程中用这个函数把回复缓冲区中的数据写入socket中。
- writeToClient。上述的handleClientsWithPendingWrites主要就是封装了这个函数。
七)defrag.c
- activeDefragCycle。内存碎片整理。
八)timeout.c
- clientsCronHandleTimeout。server.c/clientCron中用这个函数处理空闲超时和阻塞超时的客户端。
- handleBlockedClientsTimeout。客户端阻塞超时后解除客户端阻塞状态。Redis有些命令会阻塞客户端,在server.c/beforeSleep中调用这个函数进行解除阻塞的操作,这样在进入多路复用监听新的事件的时候这些客户端又可以发送新的指令了。
九)rax.c
rax.c是radix压缩树的实现,上面提到过handleBlockedClientsTimeout所处理的阻塞超时的客户端记录在一颗radix树中,此外Redis 6 中还有很多信息都用radix树记录,由于rax.c是相对独立的基础数据结构的实现,这里不做过多讨论。
十)tls.c
Redis基于OpenSSL实现的传输安全连接协议,编译的时候打开该功能,是一个比较独立的模块,这里不做讨论。
十一)tracking.c
tracking.c用于方便客户端的分布式环境下实现本地缓存。
- trackingBroadcastInvalidationMessages。由于key的值发生变化,客户端本地缓存的key就失效了,这个函数就是通过广播的方式通知客户端。
十二)adlist.c
adlist.c是Redis的基础数据结构——双向链表的实现,这里对于Redis的基础数据结构不做讨论。
十三)aof.c
- flushAppendOnlyFile。把AOF 缓存写入到文件中。
十四)blocked.c
- handleClientsBlockedOnKeys。server.c/beforeSleep中会调用这个函数,根据是否有就绪的key解除客户端的阻塞状态。Redis针对列表对象类型以及有序集合对象类型提供了一组阻塞命令,执行这些命令可以从指定的列表数据对象之中弹出数据,如果指令的列表对象为空,那么命令的调用者将会进入阻塞状态,直到这个指定的列表对象中有数据为止。
十五)ae_select.c
Redis编译时可以选择多路复用的实现方式,默认采用select方式。
- aeApiPoll。封装select,监听网络事件。
十六)connection.c
- connSocketEventHandler。Redis6采用的是reactor事件驱动网络模型,当有事件发生时,这个函数就是处理事件的总入口,根据不同的事件调用不同的事件处理函数。
- connGetPrivateData。获取连接对应的client数据结构。管理每个socket连接的数据结构最核心的成员除了文件标识符之外,还对应着一个client数据结构,读写缓冲区都在这个结构中管理。
- connSocketRead。封装read,从socket中读数据。
- connSocketWrite。封装write,写数据到socket中。
十七)util.c
工具文件,不讨论。
十八)object.c
Redis的Object系统是对最基础的底层数据结构简单动态字符串、双向链表、压缩列表、哈希表、跳表、整数数组的封装。object.c抽象出若干种对象:字符串、链表、哈希表、有序集合、集合,其中每种对象都可以基于若干种底层结构实现,可以根据不同的需要选择不同的实现,比如哈希对象可以用底层的压缩列表或者底层的哈希表实现,诸如此类。下面不在讨论。
十九)replication.c
- replconfCommand。处理REPLCONF命令。该命令目前唯一的用途是向master传达Slave redis实例的监听端口是什么,以便master在INFO输出中准确列出slave及其监听端口。
- replicationCron。这个函数和主从复制相关,函数内处理检查主从服务连接的健康以及处理不健康的连接;启动Bgsave用于在后台异步保存当前数据库的数据到磁盘,在主从全盘复制中使用。
- removeRDBUsedToSyncReplicas。删除用于同步的RDB文件。
- refreshGoodSlavesCount。刷新低延迟 slave 数量。如果打开了min-slaves-max-lag配置,当低延迟的slave数量过低时服务器拒绝写操作。
- replicationFeedSlaves。增量同步。同步新增的写指令到从服务器。
- feedReplicationBacklog。复制数据到replication backlog缓冲区。用于暂存了最近写入的命令。
- feedReplicationBacklogWithObject。把replication backlog封装成Redis的字符串对象。
二十)slowlog.c
- slowlogPushEntryIfNeeded。检测当前命令执行时间是否过长,过长的话就会在慢日志记录中加一条记录。Redis虽说以高性能著称,但是依然存在一些耗时比较高的命令,耗时的命令都会通过日志记录下来。
二十一)redis-benchmark.c
- resetClient。一条指令执行结束后重置客户端,为下一条指令做准备。上面提到的processCommandAndResetClient就是用这个函数完成客户端的重置。
二十二)cluster.c
本文不分析集群相关代码。
二十三)dict.c
dict.c是Redis的基础数据结构——哈希表的实现。Redis是一个key-value内存数据库,其中key-value对就是用dict数据结构保存的。这里对于Redis的基础数据结构不做讨论。
二十四)siphash.c
哈希算法的实现,不做讨论。
二十五)acl.c
ACLCheckCommandPerm。在写操作之前鉴权。在 Redis 6.0 中引入了 ACL(Access Control List) 的支持,在此前的版本中 Redis 中是没有用户的概念的,其实没有办法很好的控制权限,redis 6.0 开始支持用户,可以给每个用户分配不同的权限来控制权限。
二十六)localtime.c
- nolocks_localtime。对应标准库的 localtime()的功能。 标准库的 localtime()在多线程下可能出现的死锁,所以redis实现了一个不会死锁的版本。上面提到的serverLog写日志的时候使用这个函数获取日志的时间戳。
- is_leap_year。nolocks_localtime中会使用这个函数判断是否是闰年。
二十七)sds.c
sds.c是Redis的基础数据结构——简单动态字符串的实现。动态字符串才可以支持字符串类型的值的修改操作,所以Redis实现了动态字符串。这里对于Redis的基础数据结构不做讨论。
二、场景2:客户端建立连接
Calling acceptTcpHandler at networking.c
Calling anetTcpAccept at anet.c
Calling anetGenericAccept at anet.c
Calling connCreateAcceptedSocket at connection.c
Calling connCreateSocket at connection.c
Calling zcalloc at zmalloc.c
Calling acceptCommonHandler at networking.c
Calling connGetState at connection.c
Calling getClusterConnectionsCount at cluster.c
Calling createClient at redis-benchmark.c
Calling connNonBlock at connection.c
Calling anetNonBlock at anet.c
Calling anetSetBlock at anet.c
Calling connEnableTcpNoDelay at connection.c
Calling anetEnableTcpNoDelay at anet.c
Calling anetSetTcpNoDelay at anet.c
Calling connKeepAlive at connection.c
Calling anetKeepAlive at anet.c
Calling connSocketSetReadHandler at connection.c
Calling aeCreateFileEvent at ae.c
Calling aeApiAddEvent at ae_select.c
Calling connSetPrivateData at connection.c
Calling selectDb at db.c
Calling sdsempty at sds.c
Calling sdsnewlen at sds.c
Calling listCreate at adlist.c
Calling dictCreate at dict.c
Calling _dictInit at dict.c
Calling _dictReset at dict.c
Calling linkClient at networking.c
Calling listAddNodeTail at adlist.c
Calling intrev64 at endianconv.c
Calling memrev64 at endianconv.c
Calling raxInsert at rax.c
Calling raxGenericInsert at rax.c
Calling raxLowWalk at rax.c
Calling raxCompressNode at rax.c
Calling raxNewNode at rax.c
Calling zrealloc at zmalloc.c
Calling raxReallocForData at rax.c
Calling raxSetData at rax.c
Calling initClientMultiState at multi.c
Calling connSocketAccept at connection.c
Calling clientAcceptHandler at networking.c
Calling anetSetError at anet.c
可见networking.c,anet.c,connection.c,zmalloc.c,cluster.c,redis-benchmark.c,ae.c,ae_select.c,db.c,sds.c,adlist.c,dict.c,endianconv.c,rax.c,multi.c都会在建立连接的时候触发。
一)networking.c
- acceptTcpHandler。设置客户端发起建立连接请求的事件处理器。
- acceptCommonHandler。创建和连接对应的的客户端数据结构。
- linkClient。把新创建的客户端放到全局的客户端链表中。
- clientAcceptHandler。拒绝保护模式下没有使用密码的连接。
二)anet.c
这个文件主要用来封装底层的socket接口。
- anetTcpAccept。封装 accept 函数。
- anetGenericAccept。上述的anetTcpAccept主调用这个函数实现封装。
- anetNonBlock。将套接字设置为非阻塞模式。
- anetSetBlock。上述的anetNonBlock调用这个函数实现套接字的设置。
- anetEnableTcpNoDelay。封装setsockopt,启用套接字的TCP_NODELAY选项。
- anetSetTcpNoDelay。上述的anetEnableTcpNoDelay调用这个函数实现功能。
- anetKeepAlive。封装setsockopt,启用SO_KEEPALIVE选项。
- anetSetError。打印错误信息。
三)connection.c
- connCreateAcceptedSocket。创建管理连接的数据结构。
- connCreateSocket。connCreateAcceptedSocket主要通过这个函数实现功能。
- connGetState。获取连接的状态。
- connNonBlock。封装anet.c/anetNonBlock。
- connEnableTcpNoDelay。封装anet.c/anetEnableTcpNoDelay。
- connKeepAlive。封装anet.c/anetKeepAlive。
- connSocketSetReadHandler。为连接注册一个读处理器。
- connSetPrivateData。设置连接对应的客户端数据结构。
- connSocketAccept。触发事件处理器。在当前场景下触发的上面提到过的clientAcceptHandle。
四)zmalloc.c
zmalloc.c负责的是底层的内存管理,从这里开始也不再详细讨论了。
五)redis-benchmark.c
- createClient。创建和连接对应的客户端。一个连接对应一个客户端数据结构。
六)ae.c
- aeCreateFileEvent。让连接的事件被监听。
七)ae_select.c
- aeApiAddEvent。上述的aeCreateFileEvent主要就是封装这个函数。
八)db.c
- selectDb。设置客户端当前使用的数据库。
九)endianconv.c
Redis使用小端存储,endianconv.c负责大小端转换,这里不详细讨论。
十)multi.c
- initClientMultiState。初始化事务相关的状态,在上面提到的createClient中调用。
三、场景3:客户端断开连接
Calling freeClientAsync at networking.c
Calling listAddNodeTail at adlist.c
Calling freeClient at redis-benchmark.c
Calling moduleNotifyUserChanged at module.c
Calling sdsfree at sds.c
Calling sdsfree at sds.c
Calling dictRelease at dict.c
Calling _dictClear at dict.c
Calling _dictReset at dict.c
Calling _dictClear at dict.c
Calling _dictReset at dict.c
Calling unwatchAllKeys at multi.c
Calling listRelease at adlist.c
Calling listEmpty at adlist.c
Calling pubsubUnsubscribeAllChannels at pubsub.c
Calling pubsubUnsubscribeAllPatterns at pubsub.c
Calling dictRelease at dict.c
Calling _dictClear at dict.c
Calling _dictReset at dict.c
Calling _dictClear at dict.c
Calling _dictReset at dict.c
Calling listRelease at adlist.c
Calling listEmpty at adlist.c
Calling listRelease at adlist.c
Calling listEmpty at adlist.c
Calling unlinkClient at networking.c
Calling intrev64 at endianconv.c
Calling memrev64 at endianconv.c
Calling raxRemove at rax.c
Calling raxStackInit at rax.c
Calling raxLowWalk at rax.c
Calling raxStackPush at rax.c
Calling raxStackPop at rax.c
Calling raxRemoveChild at rax.c
Calling raxStackFree at rax.c
Calling connSocketClose at connection.c
Calling aeDeleteFileEvent at ae.c
Calling aeApiDelEvent at ae_select.c
Calling aeDeleteFileEvent at ae.c
Calling freeClientMultiState at multi.c
Calling sdsfree at sds.c
可见networking.c,adlist.c,redis-benchmark.c,module.c,sds.c,dict.c,multi.c,pubsub.c,endianconv.c,rax.c,connection.c,ae.c,ae_select.c都会在断开连接的时候触发。
一)networking.c
- freeClientAsync。将客户端添加到需要释放的队列中,上述的server.c/serverCron定期任务中会释放掉这个队列中的客户端。
- unlinkClient。上述networking.c/linkClient函数的反操作。
- freeClient。关闭连接,释放客户端占用资源。
二)multi.c
- unwatchAllKeys。客户端所有曾经watch的key都不再watch。Redis的 watch 命令可以决定事务是执行还是回滚:在 multi 命令之前使用 watch 命令监控某些键值对,然后使用 multi 命令开启事务,当使用 exec命令执行事务时,Redis先会去比对被 watch 命令所监控的键值对,如果没有发生变化才会真正执行事务,否则会回滚。
- freeClientMultiState。释放客户端事务相关的资源。
三)pubsub.c
- pubsubUnsubscribeAllChannels。取消订阅客户端订阅的所有频道。Redis支持消息的发布/订阅,在某些业务场景中可以作为MQ使用,断开连接前要取消所有频道的订阅。
- pubsubUnsubscribeAllPatterns。取消客户端按模式订阅的频道。订阅频道的时候可以用通配符通配频道,这个函数用于取消用这种方式订阅的频道。
四)connection.c
- connSocketClose。封装close函数。
五)ae.c
- aeDeleteFileEvent。上面提到过的aeCreateFileEvent的反操作。
六)ae_select.c
- aeApiDelEvent。上面提到过的aeApiAddEvent的反操作。
四、场景4:增加一条记录会触发的代码
Calling setCommand at t_string.c
Calling tryObjectEncoding at object.c
Calling string2l at util.c
Calling setGenericCommand at t_string.c
Calling genericSetKey at db.c
Calling lookupKeyWrite at db.c
Calling lookupKeyWriteWithFlags at db.c
Calling expireIfNeeded at db.c
Calling keyIsExpired at db.c
Calling getExpire at db.c
Calling lookupKey at db.c
Calling dbAdd at db.c
Calling sdsdup at sds.c
Calling sdsnewlen at sds.c
Calling dictAdd at dict.c
Calling dictAddRaw at dict.c
Calling dictSdsHash at t_zset.c
Calling dictGenHashFunction at dict.c
Calling siphash at siphash.c
Calling _dictKeyIndex at dict.c
Calling _dictExpandIfNeeded at dict.c
Calling incrRefCount at object.c
Calling removeExpire at db.c
Calling dictSdsKeyCompare at t_zset.c
Calling dictDelete at dict.c
Calling dictGenericDelete at dict.c
Calling signalModifiedKey at db.c
Calling touchWatchedKey at multi.c
Calling trackingInvalidateKey at tracking.c
Calling trackingInvalidateKeyRaw at tracking.c
Calling notifyKeyspaceEvent at notify.c
Calling moduleNotifyKeyspaceEvent at module.c
Calling propagate at server.c
Calling feedAppendOnlyFile at aof.c
Calling sdsempty at sds.c
Calling sdscatprintf at sds.c
Calling sdscatvprintf at sds.c
Calling sdscat at sds.c
Calling sdscatlen at sds.c
Calling zrealloc at zmalloc.c
Calling catAppendOnlyGenericCommand at aof.c
Calling getDecodedObject at object.c
Calling sdsfree at sds.c
Calling aofWrite at aof.c
Calling sdsclear at sds.c
可见t_string.c,object.c,util.c,db.c,sds.c,dict.c,t_zset.c,siphash.c,multi.c,tracking.c,notify.c,module.c,server.c,aof.c,zmalloc.c都会在增加一条记录的时候触发。
一)t_string.c
- setCommand。执行set命令。
- setGenericCommand。上述的setCommand主要就是封装这个函数实现功能。
二)db.c
- genericSetKey。数据入库。
- lookupKeyWrite。见下面的lookupKeyWriteWithFlags。
- lookupKeyWriteWithFlags。上面的lookupKeyWrite就是封装这个函数。数据入库分为添加和覆盖两种,这个函数查找key-value对,用于判断执行覆盖还是添加。
- expireIfNeeded。检查key是否过期,如果过期则将它从数据库中删除。上面的lookupKeyWriteWithFlags中调用这个函数,如果key过期就顺便处理一下。
- keyIsExpired。检查key是否过期。
- getExpire。获取key的过期时间。
- lookupKey。查找key-value对。上面的lookupKeyWriteWithFlags最核心的就是调用这个函数。
- dbAdd。添加新的key-value对到数据库中。
- removeExpire。删除键的过期时间。
- signalModifiedKey。这个函数封装multi.c/touchWatchedKey,下面介绍本场景触发的multi.c的函数时再讨论。
三)multi.c
- touchWatchedKey。打开watch这个key的客户端的REDIS_DIRTY_CAS 选项,当执行客户端发来的EXEC命令时服务器会放弃执行打开了REDIS_DIRTY_CAS 选项的客户端,表示事务执行失败,直接向客户端返回空回复。
四)tracking.c
- trackingInvalidateKey。封装trackingInvalidateKeyRaw,见下面的介绍。
- trackingInvalidateKeyRaw。把key的变化通知到所有启用了tracking机制的客户端。
五)notify.c
- notifyKeyspaceEvent。发布一条消息。用于发布/订阅机制。
六)server.c
- propagate。把命令放到aof缓存以及传播到从节点。
七)aof.c
- feedAppendOnlyFile。将命令追加到aof缓存。
- catAppendOnlyGenericCommand。把命令转成RESP协议文本。aof缓存中保持的并不是简单的的命令,而是这个函数转换的协议文本。
- aofWrite。把aof缓存写到文件中。
五、场景5:删除一条记录会触发的代码
Calling delCommand at db.c
Calling delGenericCommand at db.c
Calling expireIfNeeded at db.c
Calling keyIsExpired at db.c
Calling getExpire at db.c
Calling dbSyncDelete at db.c
Calling dictDelete at dict.c
Calling dictGenericDelete at dict.c
Calling dictSdsHash at t_zset.c
Calling dictGenHashFunction at dict.c
Calling siphash at siphash.c
Calling dictSdsKeyCompare at t_zset.c
Calling dictSdsDestructor at server.c
Calling sdsfree at sds.c
Calling dictObjectDestructor at server.c
Calling signalModifiedKey at db.c
Calling touchWatchedKey at multi.c
Calling trackingInvalidateKey at tracking.c
Calling trackingInvalidateKeyRaw at tracking.c
Calling notifyKeyspaceEvent at notify.c
Calling moduleNotifyKeyspaceEvent at module.c
Calling propagate at server.c
Calling feedAppendOnlyFile at aof.c
Calling sdsempty at sds.c
Calling sdsnewlen at sds.c
Calling sdscatprintf at sds.c
Calling sdscatvprintf at sds.c
Calling sdscat at sds.c
Calling sdscatlen at sds.c
Calling zrealloc at zmalloc.c
Calling catAppendOnlyGenericCommand at aof.c
Calling getDecodedObject at object.c
Calling incrRefCount at object.c
Calling aofWrite at aof.c
Calling sdsclear at sds.c
Calling dictResize at dict.c
Calling dictExpand at dict.c
Calling _dictNextPower at dict.c
Calling zcalloc at zmalloc.c
Calling dictRehashMilliseconds at dict.c
Calling timeInMilliseconds at dict.c
Calling dictRehash at dict.c
Calling _dictReset at dict.c
可见db.c,dict.c,t_zset.c,siphash.c,server.c,sds.c,multi.c,tracking.c,notify.c,module.c,aof.c,zmalloc.c,object.c都会在删除一条记录的时候触发。
一)db.c
- delCommand。执行del指令。
- delGenericCommand。上面的delCommand就是封装这个函数实现功能。
- expireIfNeeded。在增加一条记录的场景中已经介绍过。
- keyIsExpired。同上。
- getExpire。同上。
- dbSyncDelete。同步删除key。Redis支持同步删除和异步删除key,del是同步删除,所以执行这个函数,这个函数还有一个对应的异步删除版本。
- signalModifiedKey。增加一条记录的场景中已经介绍过。
二)server.c
- dictSdsDestructor。释放key占用的空间。
- dictObjectDestructor。释放value占用的空间。
- propagate。在增加一条记录的场景中已经介绍过。
三)multi.c
- touchWatchedKey。在增加一条记录的场景中已经介绍过。
四)tracking.c
- trackingInvalidateKey。在增加一条记录的场景中已经介绍过。
- trackingInvalidateKeyRaw。同上。
五)notify.c
- notifyKeyspaceEvent。在增加一条记录的场景中已经介绍过。
六)aof.c
- feedAppendOnlyFile。在增加一条记录的场景中已经介绍过。
- catAppendOnlyGenericCommand。同上。
- aofWrite。同上。
六、场景6:修改一条记录会触发的代码
这个场景下会触发的代码和上面已经介绍过的增加一条记录触发的函数几乎是一样的,唯一的区别是数据真正入库的函数由dbAdd(见上)变成了dbOverwrite。
七、场景七:获取一条记录会触发的代码
Calling getCommand at t_string.c
Calling getGenericCommand at t_string.c
Calling lookupKeyReadOrReply at db.c
Calling lookupKeyRead at db.c
Calling lookupKeyReadWithFlags at db.c
Calling expireIfNeeded at db.c
Calling keyIsExpired at db.c
Calling getExpire at db.c
Calling lookupKey at db.c
Calling dictSdsHash at t_zset.c
Calling dictGenHashFunction at dict.c
Calling siphash at siphash.c
Calling dictSdsKeyCompare at t_zset.c
可见t_string.c,db.c,t_zset.c,dict.c,siphash.c都会在查一条记录的时候触发。
一)t_string.c
- getCommand。执行get命令。
- getGenericCommand。上面的getCommand就是封装这个函数实现功能。
二)db.c
- lookupKeyReadOrReply。封装lookupKeyRead(下面介绍),如果查到数据就返回。
- lookupKeyRead。封装lookupKeyReadWithFlags(下面介绍)。
- lookupKeyReadWithFlags。封装lookupKey(下面介绍),顺便处理过期的key。
- expireIfNeeded、keyIsExpired、getExpire在上面的场景都已经介绍过。
- lookupKey。在底层的dict数据结构中找key。
八、场景8:执行事务会触发的代码(把set命令放到事务中执行)
Calling multiCommand at multi.c
Calling queueMultiCommand at multi.c
Calling zrealloc at zmalloc.c
Calling incrRefCount at object.c
Calling execCommand at multi.c
Calling unwatchAllKeys at multi.c
Calling execCommandPropagateMulti at multi.c
Calling propagate at server.c
Calling feedAppendOnlyFile at aof.c
Calling sdsempty at sds.c
Calling sdsnewlen at sds.c
Calling catAppendOnlyGenericCommand at aof.c
Calling sdscatlen at sds.c
Calling getDecodedObject at object.c
Calling sdsfree at sds.c
Calling setCommand at t_string.c
Calling tryObjectEncoding at object.c
Calling string2l at util.c
Calling setGenericCommand at t_string.c
Calling genericSetKey at db.c
Calling lookupKeyWrite at db.c
Calling lookupKeyWriteWithFlags at db.c
Calling expireIfNeeded at db.c
Calling keyIsExpired at db.c
Calling getExpire at db.c
Calling lookupKey at db.c
Calling dbAdd at db.c
Calling sdsdup at sds.c
Calling dictAdd at dict.c
Calling dictAddRaw at dict.c
Calling dictSdsHash at t_zset.c
Calling dictGenHashFunction at dict.c
Calling siphash at siphash.c
Calling _dictKeyIndex at dict.c
Calling _dictExpandIfNeeded at dict.c
Calling removeExpire at db.c
Calling dictSdsKeyCompare at t_zset.c
Calling dictDelete at dict.c
Calling dictGenericDelete at dict.c
Calling signalModifiedKey at db.c
Calling touchWatchedKey at multi.c
Calling trackingInvalidateKey at tracking.c
Calling trackingInvalidateKeyRaw at tracking.c
Calling notifyKeyspaceEvent at notify.c
Calling moduleNotifyKeyspaceEvent at module.c
Calling discardTransaction at multi.c
Calling freeClientMultiState at multi.c
Calling initClientMultiState at multi.c
Calling aofWrite at aof.c
Calling sdsclear at sds.c
可见multi.c,zmalloc.c,object.c,server.c,aof.c,sds.c,t_string.c,util.c,db.c,dict.c,t_zset.c,siphash.c,tracking.c,notify.c,module.c都会在执行事务的场景中触发。
一)multi.c
- multiCommand。执行multi命令。实际上只是给客户端打上一个标志。
- queueMultiCommand。把命令放到事务要执行的队列中。
- execCommand。执行exec命令。
- unwatchAllKeys。在上面客户端断开连接的场景中介绍过。
- execCommandPropagateMulti。把事务同步到AOF和从节点。
- touchWatchedKey。由事务中的set命令触发,已经在增加一条记录的场景中介绍过。
- discardTransaction。封装freeClientMultiState(见下面)。
- freeClientMultiState。事务执行完毕后清空事务模式相关的状态和数据。
- initClientMultiState。已经在客户端建立连接的场景中介绍过。
二)server.c
- propagate。在增加一条记录的场景中介绍过。
三)aof.c
feedAppendOnlyFile、catAppendOnlyGenericCommand、aofWrite都已经在上面的场景中介绍过。
四)t_string.c
setCommand、setGenericCommand已经增加一条记录的场景中介绍过。
五)db.c、tracking.c、notify.c
和增加一条记录的场景中由set命令触发的函数一致。
PS:
- 本文并没有覆盖Redis运行时的所有场景。