<commit_before>
    kiterunner_t
    TO THE HAPPY FEW


## 环境准备
从2.6.4版本为基础了解redis的设计与实现，首先搭建一个原始模型，以便根据这个模型分析其代码的设计与实现（当然，随着进一步对redis细节的了解，肯定会对该模型进行调整，以便更适合分析其设计与实现细节）。在对该版本有较深的了解后，跟随github代码库，追踪新功能添加、bug/issue等过程，更进一步的了解redis的发展，直至最新版本，以求更全面的掌握这个分布式的k-v存储数据库。

用于分析的redis网络结构下图所示。由于主要目的在于分析，故所有节点都放在单机环境，通过不同进程来模拟（真实环境绝对不会这样做）。当前主要有一主两从节点，一个监视节点（sentinel也提供了一些集群的功能，诸如failover）。每个节点的配置当前采用redis2.6.4的默认配置（修改一下代码目录下的配置文件中端口和pid文件即可，这里就不列出来了）。

![redis-topology][1]


整个系统启动过程如下：

    redis-server ./redis.6379.conf >log/6379.log 2>&1 &
    redis-server ./redis.6380.conf >log/6380.log 2>&1 &
    redis-server ./redis.6381.conf >log/6381.log 2>&1 &
    
    redis-server ./sentinel.conf >log/sentinel.log 2>&1 &
    
    redis-cli -h 192.168.47.120 -p 6380 slaveof 192.168.47.120 6379
    redis-cli -h 192.168.47.120 -p 6381 slaveof 192.168.47.120 6379


后续分析以该模型为基础，不断修改，争取构建一个适于分析redis的模型。


[1]: images/redis-topology.png "redis-topology"<commit_msg>redis分析增加ae和hiredis<commit_after>
    kiterunner_t
    TO THE HAPPY FEW


## 1 环境准备
从2.6.4版本为基础了解redis的设计与实现，首先搭建一个原始模型，以便根据这个模型分析其代码的设计与实现（当然，随着进一步对redis细节的了解，肯定会对该模型进行调整，以便更适合分析其设计与实现细节）。在对该版本有较深的了解后，跟随github代码库，追踪新功能添加、bug/issue等过程，更进一步的了解redis的发展，直至最新版本，以求更全面的掌握这个分布式的k-v存储数据库。

用于分析的redis网络结构下图所示。由于主要目的在于分析，故所有节点都放在单机环境，通过不同进程来模拟（真实环境绝对不会这样做）。当前主要有一主两从节点，一个监视节点（sentinel也提供了一些集群的功能，诸如failover）。每个节点的配置当前采用redis2.6.4的默认配置（修改一下代码目录下的配置文件中端口和pid文件即可，这里就不列出来了）。

![redis-topology][1]


整个系统启动过程如下：

    redis-server ./redis.6379.conf >log/6379.log 2>&1 &
    redis-server ./redis.6380.conf >log/6380.log 2>&1 &
    redis-server ./redis.6381.conf >log/6381.log 2>&1 &
    
    redis-server ./sentinel.conf --sentinel >log/sentinel.log 2>&1 &
    
    redis-cli -h 192.168.47.120 -p 6380 slaveof 192.168.47.120 6379
    redis-cli -h 192.168.47.120 -p 6381 slaveof 192.168.47.120 6379


后续分析以该模型为基础，不断修改，争取构建一个适于分析redis的模型。

## 2 服务器执行流程

信号是单独的，并没有放在redisServer结构中

忽略SIGHUP、SIGPIPE
SIGTERM sigtermHandler，设置server.shutdown_asap，在事件循环中返回，从而退出服务器。
SIGSEGV、SIGBUS、SIGFPE、SIGILL，发生了极端严重的编程错误，debug模块

数据库服务器初始化流程

* 初始化服务器参数，如设置OOM处理器、字典随机数种子，初始化服务器默认配置initServerConfig。
* 命令行选项解析、服务器配置加载。命令行选项解析（非-vh、--test-memory之类的控制服务器的选项）后，读取配置文件，并将命令行选项直接附加到配置文件的sds串中，调用loadServerConfigFromString，将各选项配置到表示服务器的结构体server中。
* 若设置了daemonize选项，则服务器daemonize化。
* 初始化服务器各子模块，如下：

    * 初始化信号处理器，忽略SIGHUP、SIGPIPE，设置SIGTERM触发时置位关闭服务器标志，设置SIGSEGV、SIGBUS、SIGFPE、SIGILL为debug模块处理函数。
    * 初始化日志模块，主要是syslog，普通日志已在前面配置加载部分初始化了。
    * 初始化客户端模块。
    * 初始化slaves模块。
    * 创建服务器共享对象、调整服务器软件限制。
    * 事件循环模块初始化，打开ipfd、sofd套接字。
    * 初始化数据库模块。
    * 初始化发布订阅模块。
    * 初始化持久化rdb/aof模块。
    * 初始化脚本模块。
    * 初始化慢速日志模块。
    * 初始化bio模块。

* 从磁盘加载数据库持久化文件。
* 事件循环，服务器初始化完成，各子模块开始干活。

sentinel服务器初始化流程，与数据库服务器相比，多数流程一样，少了部分与数据库相关的初始化工作，同时也做了一些数据有关的无用初始化。

* 初始化服务器参数，如设置OOM处理器、字典随机数种子，初始化服务器默认配置initServerConfig。
* 命令行选项解析、服务器配置加载。命令行选项解析（非-vh、--test-memory之类的控制服务器的选项）后，读取配置文件，并将命令行选项直接附加到配置文件的sds串中，调用loadServerConfigFromString，将各选项配置到表示服务器的结构体server中。清空数据库服务器的命令，仅仅sentinel的命令可用。
* 若设置了daemonize选项，则服务器daemonize化。
* 初始化服务器各子模块，如下：

    * 初始化信号处理器，忽略SIGHUP、SIGPIPE，设置SIGTERM触发时置位关闭服务器标志，设置SIGSEGV、SIGBUS、SIGFPE、SIGILL为debug模块处理函数。
    * 初始化日志模块，主要是syslog，普通日志已在前面配置加载部分初始化了。
    * 初始化客户端模块。
    * 初始化slaves模块。
    * 创建服务器共享对象、调整服务器软件限制。
    * 事件循环模块初始化，打开ipfd、sofd套接字。
    * （不需要）初始化数据库模块。
    * 初始化发布订阅模块。
    * （不需要）初始化持久化rdb/aof模块。
    * 初始化脚本模块。
    * （不需要）初始化慢速日志模块。
    * （不需要）初始化bio模块。

* 事件循环，服务器初始化完成，各子模块开始干活。

业务执行流程（接收请求、请求管理、请求返回）
在服务器初始化以及后续来自客户端连接过程中，添加了以下事件（beforeSleep虽然不是事件，但每次进入事件等待前会调用该函数，因此也在这里分析）。

![redis-event-table][2]


每次事件循环进入等待之前，会调用beforeSleep函数处理unblocked_clients以及将命令添加到aof中去。

serverCron
每1ms定时执行一次，定义了一个宏run_with_period表示多少ms执行一次。该函数完成了以下工作：

* 设置watchdog，产生SIGALRM信号；更新服务器时间，更新lru时间，更新内存峰值，每100ms更新一次操作统计信息。
* 查看shutdow_asap是否置位，若是准备关闭服务器。
* 每5s进行一次数据库统计信息输出。
* 无后台进程时，根据需要重设hash表大小，并在激活了rehash动作时进行1ms的rehash。
* 非sentinel模式时，每5s输出一次客户端统计信息，包括客户端数目、slaves数目以及内存使用率
* 执行异步客户端操作 clientCron。
* 没有后台进程，且开启了aof_rewrite_scheduled时，起一个后台进程进行异步aof操作；若有后台，检查是否有后台rdb或aof进程结束，结束时根据rdb或aof操作分别进行善后操作：rdb操作时，准备向slave客户端发送数据；aof时，将server.aof_filename名字修改为临时文件名字。
* 若没有后台进程，检查是否需要rdb或aof操作，若需要则进行后续相应操作。
* 将aof_buf中内容刷新到文件中。
* 激活过期数据，每个周期要么按时间、要么按照最小数目的过期键进行。
* 关闭clients_to_close链表中客户端（为了达到异步关闭的效果）。
* 每秒进行一个复制cron，判断连接、传输、客户端超时；判断是否处于REDIS_REPL_CONNECT状态，进行连接；每10s ping一下slave节点。
* 每100ms进行sentinelTimer操作，tilt模式开启关闭，failover，运行脚本。
* cronloops加1，每个cronloops值代表大约1ms。

## 3 服务器各子模块
### 3.1 事件循环
这里以epoll演示事件循环的机制，不同事件底层机制不同点在于aeApiState。

![redis-eventloop][3]

提供了以下基本接口

* 事件循环的创建、删除
* 事件循环的处理函数aeMain、aeProcessEvents、aeStop
* 定时器事件的添加和删除
* 文件事件的添加和删除
* aeWait

el->stop用于事件循环处理器检测事件是否需要停止。每次事件循环进入epoll等待事件发生之前，若设置了beforeSleep函数，则会调用该函数。

#### 3.1.1 定时器事件
el->lastTime和el->timeEventNextId用于定时器事件。timeEventNextId用于标识新建定时器，内部自增。lastTime用于检测系统时间被调整到将来，然后又调整回去的情况，每次执行定时器事件时，检测当前时间是否小于该时间，若小于则说明发生了，将所有定时器事件置0，强制都被处理。

如图所示，定时器事件使用链表进行管理，每次新增时将定时器插入到链表头。

#### 3.1.2 文件描述符事件
el->setsize限制了处理的最大文件描述符，在初始化过程中，就为events、fired、aeApiState下的events分配setsize大小的数组。添加事件时，以文件描述符fd为下标，直接添加到对应的events中，发生事件的fd以及其事件则放在fired下。

### 3.2 hiredis
hiredis是一个很小巧的用于Redis数据库的客户端库代码，提供了对printf-alike的Redis协议支持，可以对Redis数据库进行同步或异步的访问。

#### 3.2.1 同步
6种reply对象：字符串（STATUS和ERROR也是字符串类型）、整数、NIL、ARRAY。

    *2 \r\n
    $5 \r\n hello \r\n
    *3 \r\n
    :42 \r\n
    +status \r\n
    -error \r\n

上面是一串响应，则结构如下图所示（忽略空格，\r\n为ascii的可读形式）。结构如下图所示

![hiredis-sync][4]


整个流程可以简单用文字概括如下：

* 客户端连接服务器后，调用redisCommand之类的命令函数格式化命令，将其转换成Redis协议，存储在redisContext->obuf中。
* 将Redis协议发送给服务器。
* 同步等待来自服务端的响应，此时将响应复制到redisReader->buf中。
* 利用redisReader->rstack堆栈解析响应，将命令的响应以redisReply的形式返回给客户端。hiredis一次尽可能多的读取来自服务器端的响应，但一次却只返回一个响应。客户端可以使用类似管道的机制，将多个命令一次发给服务器，然后可以按照添加命令的顺序获取对应的响应。
* 客户端使用完响应后，释放redisReply资源。
* 
在解析该串响应的时候，使用栈递归解析。上图是解析代码中的服务器端响应的数据结构图。

API如下：

    redisContext *redisConnect(const char *ip, int port);
    void redisFree(redisContext *c);
    void *redisCommand(redisContext *c, const char *fmt, ...);
    void redisAppendCommand(redisContext *c, const char *fmt, ...);
    int redisGetReply(redisContext *ctx, redisReply **reply);
    void freeReplyObject(void *reply);


#### 3.2.2 异步
hiredis也提供了异步的方式进行客户服务端的沟通。如下图所示，异步方式需要与事件循环机制结合，图中所示为ae的数据结构（绿色部分，其他事件循环机制如libev、libevent有所不同）。

![hiredis-async][5]


异步解析的流程为：

* 客户端连接服务端后，将命令格式化成Redis协议形式保存在redisContext->obuf中，同时将fd可写事件注册。
* 当接收到可写事件时，将命令发送给服务端，然后立即注册可读事件，让事件循环去等待客户端的响应，而不是如同步等待一样阻塞客户端。
* 接收到来自服务端的响应，读响应数据，将这原始数据复制到redisContext->buf中，然后调用同步流解析的方式解析响应。

在异步方式下，注册了大量的回调函数，用于事件发生时进行回调。

[1]: images/redis-topology.png "redis-topology"
[2]: images/redis-event-table.png "redis-event-table"
[3]: images/redis-eventloop.png "redis-eventloop"
[4]: images/hiredis-sync.png "hiredis-sync"
[5]: images/hiredis-async.png "hiredis-async"
{'fraction_non_alphanumeric': 0.1153214978367895, 'fraction_numerical': 0.036998358943756526, 'mean_word_length': 7.096618357487923, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 28, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 8, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5911852', 'n_tokens_mistral': 4877, 'n_tokens_neox': 5447, 'n_words': 261}