美文网首页
《redis设计与实现》读后总结

《redis设计与实现》读后总结

作者: 不存在的里皮 | 来源:发表于2019-04-19 07:29 被阅读0次

    参考

    《redis设计与实现》

    1. redis 协议

    1.1 redis请求与回复协议格式

    参考 redis请求与回复协议格式
    请求同一为:

    *<number of arguments> CR LF
    $<number of bytes of argument 1> CR LF
    <argument data> CR LF
    ...
    $<number of bytes of argument N> CR LF
    <argument data> CR LF
    

    回复根据类型,有五种:

    • 用单行回复,回复的第一个字节将是“+”
    • 错误消息,回复的第一个字节将是“-”
    • 整型数字,回复的第一个字节将是“:”
    • 批量回复,回复的第一个字节将是“$”
    • 多个批量回复,回复的第一个字节将是“*”

    1.2 编写redis协议并执行

    剖析Redis协议
    按照redis协议格式编写.ptl文件 ,然后用管道命令交给Redis一并执行。

    关于redis管道
    参考 redis通过pipeline提升吞吐量
    就是说,以前是:每条命令进行一次请求和响应。
    现在是:将请求的多条命令合并成一条,一并发送给服务端执行,执行结果由客户端一次性返回。
    好处是大大减少了网络I/O,增加了效率和并发能力。

    2. redis载入与保存

    2.1 保存

    2.1.1 rdb模式

    • SAVE 阻塞服务器进程
    • BGSAVE fork子进程保存,完成后通知主进程。
      BGSAVE
      serverCron函数每100ms检查一次saveparams
    struct redisServer {
        // ...
        // 记录了保存条件的数组
        struct saveparam *saveparams;
        // ...
    };
    

    当任意一个条件满足时执行BGSAVE。

    2.1.2 aof模式

    • BGREWRITEAOF 重写时,会创建一个当前 AOF 文件的体积优化版本,fork子进程

    aof写入

    1. 所有写入都会保存记录在aof_buf缓冲区
    2. 每个事件循环结束前调用flushAppendOnlyFile 将aof_buf缓冲区写入aof文件。 是否同步,根据appendfsync
      1. 若为always,同步到文件
      2. 若为everysec,若距离上次同步超过1s,则同步到文件。
      3. 若为no,不同步到文件,由os决定是否同步。

    aof重写

    1. fork出子进程,根据写时复制,读取快照,写入aof重写文件
    2. 期间的新写入操作,都追加到aof重写缓冲区
    3. 子进程完成时,向父进程发送一个信号
    4. 父进程收到信号,阻塞完成信号处理函数:
      1. 将AOF重写缓冲区全部写入新AOF文件中,这时新AOF文件所保存的数据库状态将和服务器当前的数据库状态一致
      2. 对新的AOF文件进行改名,原子地(atomic)覆盖现有的AOF文件,完成新旧两个AOF文件的替换。

    2.2 载入

    aof开启时,优先采用aof。

    3. 事件

    文件事件
    单线程Reactor模型。处理AE_READABLEAE_WRITABLE事件。
    原理:

    1. 用I/O多路复用(evport、epoll等之一)监听各个socket,并维护了一个处理队列。触发事件的socket会进入这个队列。


    2. 文件事件分派器不断从队列取出socket,并处理事件。一个socket的触发事件都被处理完成后,才取出下一个处理。


    时间事件
    无序链表。但实际使用时,其上只有一两个事件。当前版本正常模式下,只有serverCron一个时间事件。
    serverCron会做各种常规清理、调整工作,具体的事情可参照原文P.243。
    服务器主函数

    def main():
    
        # 初始化服务器
        init_server()
    
        # 一直处理事件,直到服务器关闭为止
        while server_is_not_shutdown():
            aeProcessEvents()
    
        # 服务器关闭,执行清理操作
        clean_server()
    

    aeProcessEvents: 阻塞等待最近的时间事件到达。苏醒后,处理文件事件和时间事件
    事件处理


    文件、时间事件处理器都会适时主动让出执行权,减少阻塞。

    4. 客户端

    5. 服务端

    5.1 命令请求的步骤

    一个命令请求从发送到完成主要包括以下步骤:

    1. 客户端将命令请求发送给服务器;
    2. 服务器读取命令请求,并分析出命令参数;
    3. 命令执行器根据参数查找命令的实现函数,然后执行实现函数并得出命令回复;
    4. 服务器将命令回复返回给客户端。

    5.2 计算每秒执行次数

    1. serverCron函数中的trackOperationsPerSecond每100毫秒执行一次。
    2. trackOperationsPerSecond根据上次调用后的间隔时间和执行次数,估算1秒执行次数,放入环形数组redisServer.ops_sec_samples
    3. 客户端执行INFO命令时,getOperationsPerSecond根据环形数组平均值计算一秒内估算执行次数。

    5.3 启动流程

    服务器从启动到能够处理客户端的命令请求需要执行以下步骤:

    1. 初始化服务器状态;
    2. 载入服务器配置;
    3. 初始化服务器数据结构;
    4. 还原数据库状态;
    5. 执行事件循环。

    其它

    看原书单元末尾的总结

    6. 多机数据库的实现

    6.1 主从复制

    6.1.1 旧版主从复制

    做法是:

    1. 从服务器向主服务器发送sync
    2. 主服务器执行BGSAVE,保存rdb文件,发送给从服务器。
    3. 期间主服务器的额外写入都保存到缓冲区。
    4. 主服务器发送rdb后,将缓冲区也一并发送给从服务器。

    缺点:
    对于短线重连来说,sync效率太低。

    6.1.2 新版主从复制

    psync的部分重同步中,主向从发送+continue,并发送断线期间的数据,以完成同步。


    只要断线时的offset之后的内容都在复制积压缓冲区内,则可以部分重同步。
    复制积压缓冲区默认为1MB。其大小可根据second * write_size_per_second来估算。

    6.2

    思考

    1. 似乎HSETSET更适合对象的存储,因为可以对每个field进行操作(而不用 反序列化->修改对象->序列化)。那常规的序列化存储对象的方法(比如用json serializer)是否合适呢?会不会效率低了。
      • 考虑如何用hset实现对象存储

    相关文章

      网友评论

          本文标题:《redis设计与实现》读后总结

          本文链接:https://www.haomeiwen.com/subject/czsfgqtx.html