曹工说Redis源码(7)-- redis server 的周期执行任务,到底要做些啥
文章导航
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 函数。
该函数的大的流程,按照代码中的执行顺序,我们先了解下:
注册一个watchdog,注册方式是通过一个timer,注册了该timer之后,会定期给当前进程,触发一个
SIGALRM
信号,触发了这个信号后,会干嘛呢,会回调位于 debug.c 文件中的watchdogSignalHandler
方法,这个方法,主要是在redis执行一些命令时,超过指定时长后,打印一些debug日志。可以参考:
更新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中。
计算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 = 一段时间内的操作数量/ 时间长度。
刷新服务器的 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;
}
}
网上的一篇文章写得不错,可以参考:
记录服务器的内存峰值
/* 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命令里看到使用了。
判断服务器的关闭标识是否打开,如打开,则关闭
// 服务器进程收到 SIGTERM 信号,关闭服务器
if (server.shutdown_asap) {
// 尝试关闭服务器
if (prepareForShutdown(0) == REDIS_OK) exit(0);
}
打印数据库的键值对信息、客户端信息
单纯的log操作,唯一注意的是,要把日志级别调到
REDIS_VERBOSE
才看得到检查客户端空闲时长,关闭空闲超时的客户端
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;
}
...
}
对数据库执行各种操作
/* 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调整(如果字典的使用率低,会缩小其占用的内存大小)
后续会详解这部分。
如果当前没有aof或者rdb后台任务正在执行,且server之前被schedule了一个aof rewrite后台任务,则执行
aof 重写。(aof记录了每一条命令,时间长了,会重复,比如先把key a设为1,再设为2,再设为3,这样,aof中有3条记录,实际上,只需要一条即可,所以会重写)
aof 重写在一个子进程中进行,子进程完成后,会给当前进程发送信号,所以,当前进程会一直等待信号,等待子进程完成后,自己再做些处理。
比如,主进程要做什么处理呢?在 aof 重写期间,主进程可能还是要不断地处理命令(这里不会无限期等待,这次等不到就到下一次周期任务时再等),这期间,处理的命令,不能记录到aof文件中,免得影响正在进行aof 重写的子进程,所以,主进程会把这期间的命令,记录到一个小本本上。
等到子进程写完了,主进程再把小本本上的aof命令,写到aof日志文件里。
如果当前没有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
根据配置的aof fsync策略,决定是否要刷新到文件中
前面我们说的aof写日志文件,不一定真的就写入了文件,可能还在OS cache中,需要调用 fsync 才能写入到文件中。
这里即对应配置文件中的:
# appendfsync always
appendfsync everysec
# appendfsync no
默认每秒执行一次fsync,性能和数据安全性的折衷。
涉及slave、cluster、sentinel的部分操作
如果运行在以上几种模式下,会涉及到对应的一些周期操作,后续再涉及这块。
总结
本讲的主题大概是这些,其中,细节部分,比如数据库的周期任务等,留待下讲继续。
曹工说Redis源码(7)-- redis server 的周期执行任务,到底要做些啥的更多相关文章
- 曹工说JDK源码(4)--抄了一小段ConcurrentHashMap的代码,我解决了部分场景下的Redis缓存雪崩问题
曹工说JDK源码(1)--ConcurrentHashMap,扩容前大家同在一个哈希桶,为啥扩容后,你去新数组的高位,我只能去低位? 曹工说JDK源码(2)--ConcurrentHashMap的多线 ...
- 曹工说JDK源码(2)--ConcurrentHashMap的多线程扩容,说白了,就是分段取任务
前言 先预先说明,我这边jdk的代码版本为1.8.0_11,同时,因为我直接在本地jdk源码上进行了部分修改.调试,所以,导致大家看到的我这边贴的代码,和大家的不太一样. 不过,我对源码进行修改.重构 ...
- Redis源码研究--redis.h
------------7月3日------------ /* The redisOp structure defines a Redis Operation, that is an instance ...
- 曹工说JDK源码(1)--ConcurrentHashMap,扩容前大家同在一个哈希桶,为啥扩容后,你去新数组的高位,我只能去低位?
如何计算,一对key/value应该放在哪个哈希桶 大家都知道,hashmap底层是数组+链表(不讨论红黑树的情况),其中,这个数组,我们一般叫做哈希桶,大家如果去看jdk的源码,会发现里面有一些变量 ...
- 曹工说JDK源码(3)--ConcurrentHashMap,Hash算法优化、位运算揭秘
hashcode,有点讲究 什么是好的hashcode,一般来说,一个hashcode,一般用int来表示,32位. 下面两个hashcode,大家觉得怎么样? 0111 1111 1111 1111 ...
- Redis源码剖析--源码结构解析
请持续关注我的个人博客:https://zcheng.ren 找工作那会儿,看了黄建宏老师的<Redis设计与实现>,对redis的部分实现有了一个简明的认识.在面试过程中,redis确实 ...
- 玩一把redis源码(一):为redis添加自己的列表类型
2019年第一篇文档,为2019年做个良好的开端,本文档通过step by step的方式向读者展示如何为redis添加一个数据类型,阅读本文档后读者对redis源码的执行逻辑会有比较清晰的认识,并且 ...
- redis源码(一):为redis添加自己的列表类型
本文档分为三大部分: 环境介绍与效果演示 redis接收命令到返回数据的执行逻辑 代码实现 文档的重点和难点在第三部分,完全阅读本文档需要读者具备基本的c语言和数据结构知识. 环境介绍和效果演示环境介 ...
- Redis源码漂流记(二)-搭建Redis调试环境
Redis源码漂流记(二)-搭建Redis调试环境 一.目标 搭建Redis调试环境 简要理解Redis命令运转流程 二.前提 1.有一些c知识简单基础(变量命名.常用数据类型.指针等) 可以参考这篇 ...
- 曹工说Redis源码(2)-- redis server 启动过程解析及简单c语言基础知识补充
文章导航 Redis源码系列的初衷,是帮助我们更好地理解Redis,更懂Redis,而怎么才能懂,光看是不够的,建议跟着下面的这一篇,把环境搭建起来,后续可以自己阅读源码,或者跟着我这边一起阅读.由于 ...
随机推荐
- OpenCV-Python | 图像的基本操作 十
目标 学会: 访问像素值并修改它们 访问图像属性 设置感兴趣区域(ROI) 分割和合并图像 本节中的几乎所有操作都主要与Numpy相关,而不是与OpenCV相关.要使用OpenCV编写更好的优化代码, ...
- CSS 常用属性之 阴影
text-shadow 是一个给文字添加阴影的属性 text-shadow: X偏移量,Y偏移量,模糊值,颜色 可以同时设置多个文本阴影,需要用逗号隔开 如果是需要兼容低版本的浏览器 -webkit ...
- 倒计时器CountDownLatch
1.背景: countDownLatch是在java1.5被引入,跟它一起被引入的工具类还有CyclicBarrier.Semaphore.concurrentHashMap和BlockingQueu ...
- coding++:Linux - Shell - 常用命令
1.在多个文件中 查找内容 find . -type f -name "*.html" | xargs grep "1"
- 高性能RabbitMQ
1,什么是RabbitMq RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件).RabbitMQ服务器是用Erlang语言编写的,而集群和故障转移是构建在开 ...
- 《java编程思想》 初始化与清理
1.初始化与清理的重要性: 1.许多C程序的错误都源于程序员忘记初始化变量,特别是使用程序库时,如果不知道如何初始化库的构件更容易出错 2.当使用完一个元素时,这个元素就不会有什么影响了,所以很容易就 ...
- Android 数据库框架 DBFlow 的使用
原文首发于微信公众号:jzman-blog,欢迎关注交流! DBFlow 是一个基于注解处理器开发的使用方便的 ORM Android 数据库,该库简化了很多多余的代码,并且提供了好用的 API 来处 ...
- 1012 The Best Rank (25 分)
To evaluate the performance of our first year CS majored students, we consider their grades of three ...
- ln 软连接与硬连接
...
- Linux如何配制Tcl编程环境
首先,打开终端. 接着在终端输入以下命令: sudo apt-get install tcl