一、Lua脚本:Eval、Evalsha、ScriptiLoad等命令的实现原理
1.1 创建并修改Lua环境
- 创建Lua环境
- 载入函数库
- 创建redis全局表格
- 使用Rdis自制的随机函数来替换Lua原有的随机函数
- 创建排序辅助函数
- 创建redis.pcall函数的错误报告辅助函数
- 保护Lua的全局环境
- 将Lua环境保存到服务器状态的lua属性里面
1.2 Lua环境协作组件
- 伪客户端:用于执行Lua脚本
- lua_scripts字典:用于保存Lua脚本。Redis服务器会将所有被EVAL命令执行过的Lua脚本,以及所有被SCRIPT LOAD命令载入过的Lua脚本都保存到lua_scripts字典里面。用于实现SCRIPT EXISTS命令以及脚本复制功能
1.3 EVAL命令的实现
- 定义脚本函数
当客户端向服务器发送 EVAL命令,要求海之星Lua脚本时,会首先要在Lua环境中,为传入的脚本定义一个与这个脚本相对应的Lua函数,函数名字由f_前缀加上脚本的SHAI校验和组成,而函数体则是脚本本身
- 将脚本保存到lua_script字典
- 执行脚本函数
1.4 EVALSHA命令的实现
每个被 EVAL命令成功执行过的Lua脚本,在Lua环境里面都有一个与这个脚本相对应的函数,函数名由f_前缀加校验和组成。客户端可以根据这个校验和来调用脚本对应的函数,从而到达执行脚本目的。
1.5 脚本管理命令的实现
- SCRIPT FLUSH
清除服务器中所有和Lua脚本有关的信息,并释放重建 lua_scripts字典,关闭现有的Lua环境并重新创建一个新的Lua环境
- SCRIPT EXISTS
根据输入的SHA1校验和,检查校验和对应的脚本是否存在于服务器中,不存在返回0,存在返回1
- SCRIPT LOAD
所做的事情和EVAL命令执行脚本时所做的前两步一样,最终会生成校验和
- SCRIPT KILL
![2a778874c153322a416eaf12a21eed49.png](https://i-blog.csdnimg.cn/blog_migrate/994f7c12a18b2affd15a217fdc8df6da.jpeg)
1.6 脚本复制
当服务器运行在复制模式下时,具有写性质的脚本命令会被复制到从服务器
- 复制EVAL、SCRIPT FLUSH和SCRIPT LOAD命令
- 复制EVALSHA命令
![3b49566071aa200627c9cf526427756b.png](https://i-blog.csdnimg.cn/blog_migrate/b998d13293d9fc85f8bd5e40be7bd02d.jpeg)
重点回顾:
(1)Rdis服务器在启动时会对内嵌的lua环境执行一系列修改操作,从而确保内嵌环境可以满足Redis在功能性、安全性等方面需求
(2)Redis服务器专门使用一个伪客户端来执行lua脚本中包含的Redis命令
(3)Redis使用脚本字典来保存所有被EVAL命令执行过或者被SCRIPT LOAD命令载入过的lua脚本,这些脚本可以用于实现SCRIPT EXISTS命令以及实现脚本复制功能
(4)EVAL命令为客户端输入的脚本在Lua环境中定义一个函数,并通过调用这个函数来执行脚本
(5)EVALSHA命令通过直接调用Lua环境中已经定义的函数来执行脚本
(6)SCRIPT FLUSH命令会清空服务器lua_scripts字典中保存的脚本,并重置Lua环境(7)SCRIPT EXISTS命令接受一个或多个SHA1校验和为参数,并通过检查lua_scripts字典来确认校验和对应的脚本是否存在
(8)SCRIPT LOAD命令接受一个lua脚本参数,为该脚本在lua环境中创建函数,并将脚本保存到lua_scripts字典中
(9)服务器在执行脚本之前,会为Lua环境设置一个超时处理钩子,当脚本出现超时运行情况时,客户端可以通过向服务器发送SCRIPT KILL命令来让钩子停止正在执行的脚本,或者发送SHUTDOWN nosave命令让钩子关闭整个服务器
(10)主服务器复制EVAL、SCRIPT FLUSH、SCRIPT LOAD三个命令的方法和复制普通Redis命令一样,只要将相同的命令传播给从服务器就可以了
(11)主服务器在复制EVALSHA命令时,必须确保所有从服务器都已经载入了EVALSH命令指定的SHA1校验和所对应的lua脚本,如果不能确保这一点的话,主服务器会将EVALSHA命令转换成等效的EVAL命令,并通过传播EVAL命令来获取相同的脚本执行效果。
二、排序:Sort命令的实现原理 (快排)
2.1 SORT <key>命令的实现
对一个包含数字值的键key进行排序。 SORT命令为每个被排序的键都创建一个与键长度相同的数组,数组的每个项都是一个 redisSortObject结构。
2.2 ALPHA选项的实现
通过使用 ALPHA选项 SORT命令可以包含字符串值的键进行排序
2.3 ASC选项和DESC选项的实现
默认是升序
2.4 BY选项的实现
2.5 带有ALPHA选项的BY选项的实现
BY选项默认假设权重键保存的值为数字值,如果权重键保存的是字符串值的话,就需要在使用BY选项的同事,配合使用ALPHA选项
2.6 LIMIT选项的实现
limit用于只返回部分已排序的元素
2.7 GET选项的实现
让SORT命令在对键进行排序之后,根据被排序的元素以及GET选项所指定的模式,查找并返回某些键的值
2.8 STORE选项的实现结果
默认情况下SORT命令只向客户端返回排序结果,而不保存排序
2.9 执行顺序
先执行排序操作(可用的选项ALPHA、ASC、DESC、BY),然后执行LIMIT选项,之后执行GET选项,再之后执行STORE选项,最后才将排序结果集返回给客户端。并且调整选项的摆放位置不会影响STORE命令的排序结果。
三、二进制位数组:GetBit、SetBit、BitCount、BitOp等命令的实现原理
Redis提供了
(1) SETBIT:为位数组指定偏移量上的二进制位设置值
(2) GETBIT:获取维数组指定偏移量上的二进制位的值
(3) BITCOUNT:统计位数组里面值为1的二进制的数量
(4) BITOP:多个位数组进行按位与、按位或、按位异或、取反等操作;四个命令处理二进制位数组
3.1 位数组的表示
Redis使用字符串对象来表示位数组,并且使用逆序来保存数组。
![5cf3fb1bded88e1b615136228e2183dd.png](https://i-blog.csdnimg.cn/blog_migrate/8dfd055846997138dacec973bc58c480.jpeg)
3.2 GETBIT命令的实现
getbit <bitarray> <offset>
- 计算byte=|offset/8|,byte记录了offset偏移量指定的二进制位保存在位数组的哪个字节
- 计算bit=(offset%8) + 1,bit记录了offset偏移量指定的二进制位是byte字节的第几个二进制位
- 根据byte值和bit值在位数组bitarray中定位offset偏移量指定的二进制位,并返回这个位的值
3.3 SETBIT命令的实现
setbit <bitarray> <offset> <value>
- 计算len=|offset/8|+1,len值记录了保存offset偏移量指定的二进制位至少需要多少字节
- 检查bitarray键保存的位数组的长度是否小于len,是的话将SDS长度扩展为len字节,并将所有新扩展空间的二进制位的值设置为0
- 计算byte=|offset/8|,byte记录了offset偏移量指定的二进制位保存在位数组的哪个字节
- 计算bit=(offset mod 8) + 1,bit值记录了offset偏移量指定的二进制位是byte字节的第几个二进制位
- 根据byte值和bit值,在bitarray键保存的位数组中定位offset偏移量指定的二进制位,首先将指定二进制位现在值保存在oldvalue变量,然后将新值value设置为这个二进制位的值
- 向客户端返回oldvaluse变量的值
3.4 BITCOUNT命令的实现(用到了查表和variable-precisionSWAR两种算法,算法复杂度为O(n),n为输入二进制位的数量)
3.5 BITOP命令的实现
直接使用c语言内置的位操作&、|、^、~ 等操作
重点:
(1)使用SDS保存位数组
(2)SDS使用逆序来保存位数组,这种保存顺序简化了SETBIT命令的实现,使得SETBIT命令可以在不移动现有二进制位的情况下,对位数组进行空间扩展
(3)BITCOUNT命令使用了查表算法和variable-precision SWAR算法来优化命令的执行效率
(4)BITOP命令的所有操作都使用C语言内置的位操作来实现
四、慢查询日志:Slow Log get、Slow Log len、Slow Log Reset等命令的实现原理
Redis的慢查询日志功能用于记录执行时间超过给定时长的命令请求,用户可以通过这个个功能产生的日志来监视和优化查询速度 slowlog-log-slower-than:指定执行时间超过多少微秒的命令请求会被记录到日志上 slowlog-max-len:指定服务器最多保存多少条慢查询
- 使用SLOWLOG GET命令可以查看服务器所保存的慢查询日志
- Redis服务将所有慢查询日志保存在服务器状态的slowlog链表中,每个链表节点都包含一个slowlogEntry结构,每个slowlogEntry结构代表一条慢查询日志
- 打印和删除慢查询日志可以通过遍历slowlog链表来完成
- slowlog链表长度就是服务器所保存慢查询日志的数量
- 新的慢查询日志会被添加到slowlog链表的表头,如果日志的数量超过slowlog-max-len选项的值,那么多出来的日志会被删除
五、监视器:Monitor
通过执行 MONITOR命令,客户端可以将自己变成一个监视器,实时地接收并打印出服务器当前处理的命令请求的相关信息
![885cb8c6d199444ddb4d278594bd9bd9.png](https://i-blog.csdnimg.cn/blog_migrate/a1d033ad7a1291cfab2a2f31d4f2b81e.png)
- 当一个客户端从普通客户端变为监视器时,该客户端的REDIS_MONITOR标识会被打开
- 服务器将所有监视器都记录在MONITORS链表中
- 每次处理请求命令时,服务器都会遍历monitor链表,将相关信息发送给监视器