文章导航

Redis源码系列的初衷,是帮助我们更好地理解Redis,更懂Redis,而怎么才能懂,光看是不够的,建议跟着下面的这一篇,把环境搭建起来,后续可以自己阅读源码,或者跟着我这边一起阅读。由于我用c也是好几年以前了,些许错误在所难免,希望读者能不吝指出。

曹工说Redis源码(1)-- redis debug环境搭建,使用clion,达到和调试java一样的效果

曹工说Redis源码(2)-- redis server 启动过程解析及简单c语言基础知识补充

曹工说Redis源码(3)-- redis server 启动过程完整解析(中)

曹工说Redis源码(4)-- 通过redis server源码来理解 listen 函数中的 backlog 参数

曹工说Redis源码(5)-- redis server 启动过程解析,以及EventLoop每次处理事件前的前置工作解析(下)

曹工说Redis源码(6)-- redis server 主循环大体流程解析

本讲主题

本讲,聚焦于redis的周期执行任务。redis启动起来后,基本就剩下两件事,上一讲的主流程分析中,已经讲到了。1个是处理客户端请求,2就是指向周期任务。处理客户端请求,大概会细分为:处理客户端连接事件(客户端连接到redis)、客户端读写事件(客户端发送请求,redis返回响应);

周期任务呢,就是本讲主题,let's go。

周期任务的大体流程

周期任务,上一讲已经提到,其就是一个函数指针,具体实现,就是redis.c中的 serverCron 函数。

该函数的大的流程,按照代码中的执行顺序,我们先了解下:

  1. 注册一个watchdog,注册方式是通过一个timer,注册了该timer之后,会定期给当前进程,触发一个SIGALRM信号,触发了这个信号后,会干嘛呢,会回调位于 debug.c 文件中的 watchdogSignalHandler方法,这个方法,主要是在redis执行一些命令时,超过指定时长后,打印一些debug日志。

    可以参考:

    Redis 2.6 的新特性:Watchdog(看门狗)

    Redis software watchdog

  2. 更新server时间,redis server在很多时候,都需要获取当前时间,就像我们写业务代码差不多,但是,redis比较扣,扣什么?扣性能。在不需要获取当前时间的时候,redis觉得,获取一个不那么准确的时间就行了。所以,就缓存了一个全局时间,这个全局时间,什么时候刷新呢,就在这个周期任务中。

    大家仔细看注释吧:

    /* We take a cached value of the unix time in the global state because with
    * virtual memory and aging there is to store the current time in objects at
    * every object access, and accuracy is not needed. To access a global var is
    * a lot faster than calling time(NULL) */
    void updateCachedTime(void) {
    server.unixtime = time(NULL);
    server.mstime = mstime();
    }

    简单翻译下,就是说,每个对象,每次被访问的时候,有个access-time,这个时间,不需要那么精确,没必要每次去new date(),使用缓存的时间就行了,这样能比较快。全局时间,缓存在server.unixtime 和 server.mstime中。

  3. 计算redis的ops,类似于tps;这个操作,不是每次该周期任务时,都要执行,而是自定义执行的周期,总体来说,没有本周期任务那么频繁。

    redis中,定义了一个宏来实现这个功能,比如:

        // 记录服务器执行命令的次数
    run_with_period(100) trackOperationsPerSecond();

    这个就是,每100ms执行一次上面的这个操作。

    这个怎么去计算ops(operation per second)呢?看下面的代码即懂:

    void trackOperationsPerSecond(void) {
    
        // 计算两次抽样之间的时间长度,毫秒格式
    long long t = mstime() - server.ops_sec_last_sample_time; // 计算两次抽样之间,执行了多少个命令
    long long ops = server.stat_numcommands - server.ops_sec_last_sample_ops; long long ops_sec; //1 计算距离上一次抽样之后,每秒执行命令的数量
    ops_sec = t > 0 ? (ops * 1000 / t) : 0;
    ...
    }

    1处,分子分母,大家一看,应该就懂了。ops = 一段时间内的操作数量/ 时间长度。

  4. 刷新服务器的 LRU 时间,目前,我觉得可以简单理解为:redis的空间大小是有限的,假设机器内存10g,那么不可能把数据库的几个t的数据都放redis,所以基本是放热数据,那不热的数据怎么办?被清除。清除的算法,就是lru。每个key,不管设没设过期时间,都会维护一个lruClock,即最近一次被访问的时间。

    计算一个对象的空闲时长,就是用服务器的LRU时间 减去 key的LRU时间。

    // 使用近似 LRU 算法,计算出给定对象的闲置时长
    unsigned long long estimateObjectIdleTime(robj *o) {
    unsigned long long lruclock = LRU_CLOCK();
    if (lruclock >= o->lru) {
    return (lruclock - o->lru) * REDIS_LRU_CLOCK_RESOLUTION;
    } else {
    return (lruclock + (REDIS_LRU_CLOCK_MAX - o->lru)) *
    REDIS_LRU_CLOCK_RESOLUTION;
    }
    }

    网上的一篇文章写得不错,可以参考:

    redis的LRU策略理解

  5. 记录服务器的内存峰值

        /* Record the max memory used since the server was started. */
    // 记录服务器的内存峰值
    if (zmalloc_used_memory() > server.stat_peak_memory)
    server.stat_peak_memory = zmalloc_used_memory();

    什么时候用呢?好像只在info命令里看到使用了。

  6. 判断服务器的关闭标识是否打开,如打开,则关闭

    // 服务器进程收到 SIGTERM 信号,关闭服务器
    if (server.shutdown_asap) {
    // 尝试关闭服务器
    if (prepareForShutdown(0) == REDIS_OK) exit(0);
    }
  7. 打印数据库的键值对信息、客户端信息

    单纯的log操作,唯一注意的是,要把日志级别调到REDIS_VERBOSE才看得到

  8. 检查客户端空闲时长,关闭空闲超时的客户端

    int clientsCronHandleTimeout(redisClient *c) {
    
        // 获取当前时间
    time_t now = server.unixtime; // 服务器设置了 maxidletime 时间
    if (server.maxidletime &&
    ...
    // 客户端最后一次与服务器通讯的时间已经超过了 maxidletime 时间
    (now - c->lastinteraction > server.maxidletime)) {
    redisLog(REDIS_VERBOSE, "Closing idle client");
    // 关闭超时客户端
    freeClient(c);
    return 1;
    }
    ...
    }
  9. 对数据库执行各种操作

    /* This function handles 'background' operations we are required to do
    * incrementally in Redis databases, such as active key expiring, resizing,
    * rehashing. */
    // 对数据库执行删除过期键,调整大小,以及主动和渐进式 rehash
    void databasesCron(void)

    看注释可知,大概有如下工作:删除过期key,hash表的rehash,hash的size调整(如果字典的使用率低,会缩小其占用的内存大小)

    后续会详解这部分。

  10. 如果当前没有aof或者rdb后台任务正在执行,且server之前被schedule了一个aof rewrite后台任务,则执行

    aof 重写。(aof记录了每一条命令,时间长了,会重复,比如先把key a设为1,再设为2,再设为3,这样,aof中有3条记录,实际上,只需要一条即可,所以会重写)

    aof 重写在一个子进程中进行,子进程完成后,会给当前进程发送信号,所以,当前进程会一直等待信号,等待子进程完成后,自己再做些处理。

    比如,主进程要做什么处理呢?在 aof 重写期间,主进程可能还是要不断地处理命令(这里不会无限期等待,这次等不到就到下一次周期任务时再等),这期间,处理的命令,不能记录到aof文件中,免得影响正在进行aof 重写的子进程,所以,主进程会把这期间的命令,记录到一个小本本上。

    等到子进程写完了,主进程再把小本本上的aof命令,写到aof日志文件里。

  11. 如果当前没有aof或者rdb后台任务在执行,也没有被schedule 一个aof rewrite任务,那么,上面这步中的全部操作,都不会发生。

    此时,会去检查,当前是否满足aof 重写、rdb 保存的条件。

    比如,rdb不是一般需要配置如下参数吗:

    save 900 1
    save 300 10
    save 60 10000

    此时,就会去检查,这些参数,是否满足,如果满足,就要开始进行rdb后台保存。

    或者,当以下的aof参数满足时,也会触发aof重写:

    auto-aof-rewrite-percentage 100
    auto-aof-rewrite-min-size 64mb
  12. 根据配置的aof fsync策略,决定是否要刷新到文件中

    前面我们说的aof写日志文件,不一定真的就写入了文件,可能还在OS cache中,需要调用 fsync 才能写入到文件中。

    这里即对应配置文件中的:

    # appendfsync always
    appendfsync everysec
    # appendfsync no

    默认每秒执行一次fsync,性能和数据安全性的折衷。

  13. 涉及slave、cluster、sentinel的部分操作

    如果运行在以上几种模式下,会涉及到对应的一些周期操作,后续再涉及这块。

总结

本讲的主题大概是这些,其中,细节部分,比如数据库的周期任务等,留待下讲继续。

曹工说Redis源码(7)-- redis server 的周期执行任务,到底要做些啥的更多相关文章

  1. 曹工说JDK源码(4)--抄了一小段ConcurrentHashMap的代码,我解决了部分场景下的Redis缓存雪崩问题

    曹工说JDK源码(1)--ConcurrentHashMap,扩容前大家同在一个哈希桶,为啥扩容后,你去新数组的高位,我只能去低位? 曹工说JDK源码(2)--ConcurrentHashMap的多线 ...

  2. 曹工说JDK源码(2)--ConcurrentHashMap的多线程扩容,说白了,就是分段取任务

    前言 先预先说明,我这边jdk的代码版本为1.8.0_11,同时,因为我直接在本地jdk源码上进行了部分修改.调试,所以,导致大家看到的我这边贴的代码,和大家的不太一样. 不过,我对源码进行修改.重构 ...

  3. Redis源码研究--redis.h

    ------------7月3日------------ /* The redisOp structure defines a Redis Operation, that is an instance ...

  4. 曹工说JDK源码(1)--ConcurrentHashMap,扩容前大家同在一个哈希桶,为啥扩容后,你去新数组的高位,我只能去低位?

    如何计算,一对key/value应该放在哪个哈希桶 大家都知道,hashmap底层是数组+链表(不讨论红黑树的情况),其中,这个数组,我们一般叫做哈希桶,大家如果去看jdk的源码,会发现里面有一些变量 ...

  5. 曹工说JDK源码(3)--ConcurrentHashMap,Hash算法优化、位运算揭秘

    hashcode,有点讲究 什么是好的hashcode,一般来说,一个hashcode,一般用int来表示,32位. 下面两个hashcode,大家觉得怎么样? 0111 1111 1111 1111 ...

  6. Redis源码剖析--源码结构解析

    请持续关注我的个人博客:https://zcheng.ren 找工作那会儿,看了黄建宏老师的<Redis设计与实现>,对redis的部分实现有了一个简明的认识.在面试过程中,redis确实 ...

  7. 玩一把redis源码(一):为redis添加自己的列表类型

    2019年第一篇文档,为2019年做个良好的开端,本文档通过step by step的方式向读者展示如何为redis添加一个数据类型,阅读本文档后读者对redis源码的执行逻辑会有比较清晰的认识,并且 ...

  8. redis源码(一):为redis添加自己的列表类型

    本文档分为三大部分: 环境介绍与效果演示 redis接收命令到返回数据的执行逻辑 代码实现 文档的重点和难点在第三部分,完全阅读本文档需要读者具备基本的c语言和数据结构知识. 环境介绍和效果演示环境介 ...

  9. Redis源码漂流记(二)-搭建Redis调试环境

    Redis源码漂流记(二)-搭建Redis调试环境 一.目标 搭建Redis调试环境 简要理解Redis命令运转流程 二.前提 1.有一些c知识简单基础(变量命名.常用数据类型.指针等) 可以参考这篇 ...

  10. 曹工说Redis源码(2)-- redis server 启动过程解析及简单c语言基础知识补充

    文章导航 Redis源码系列的初衷,是帮助我们更好地理解Redis,更懂Redis,而怎么才能懂,光看是不够的,建议跟着下面的这一篇,把环境搭建起来,后续可以自己阅读源码,或者跟着我这边一起阅读.由于 ...

随机推荐

  1. OpenCV-Python | 图像的基本操作 十

    目标 学会: 访问像素值并修改它们 访问图像属性 设置感兴趣区域(ROI) 分割和合并图像 本节中的几乎所有操作都主要与Numpy相关,而不是与OpenCV相关.要使用OpenCV编写更好的优化代码, ...

  2. CSS 常用属性之 阴影

    text-shadow 是一个给文字添加阴影的属性 text-shadow: X偏移量,Y偏移量,模糊值,颜色  可以同时设置多个文本阴影,需要用逗号隔开 如果是需要兼容低版本的浏览器 -webkit ...

  3. 倒计时器CountDownLatch

    1.背景: countDownLatch是在java1.5被引入,跟它一起被引入的工具类还有CyclicBarrier.Semaphore.concurrentHashMap和BlockingQueu ...

  4. coding++:Linux - Shell - 常用命令

    1.在多个文件中 查找内容 find . -type f -name "*.html" | xargs grep "1"

  5. 高性能RabbitMQ

    1,什么是RabbitMq RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件).RabbitMQ服务器是用Erlang语言编写的,而集群和故障转移是构建在开 ...

  6. 《java编程思想》 初始化与清理

    1.初始化与清理的重要性: 1.许多C程序的错误都源于程序员忘记初始化变量,特别是使用程序库时,如果不知道如何初始化库的构件更容易出错 2.当使用完一个元素时,这个元素就不会有什么影响了,所以很容易就 ...

  7. Android 数据库框架 DBFlow 的使用

    原文首发于微信公众号:jzman-blog,欢迎关注交流! DBFlow 是一个基于注解处理器开发的使用方便的 ORM Android 数据库,该库简化了很多多余的代码,并且提供了好用的 API 来处 ...

  8. 1012 The Best Rank (25 分)

    To evaluate the performance of our first year CS majored students, we consider their grades of three ...

  9. ln 软连接与硬连接

                                                                                                        ...

  10. Linux如何配制Tcl编程环境

    首先,打开终端. 接着在终端输入以下命令: sudo apt-get install tcl