Redis | 第9章 Lua 脚本与排序《Redis设计与实现》
前言
参考资料:《Redis设计与实现 第二版》;
第三部分为独立功能的实现,主要由以下模块组成:发布订阅、事务、Lua 脚本、排序、二进制位数组、慢查询日志、监视器;
本篇将介绍 Redis 的Lua 脚本与排序。Redis 从 2.6 版本开始可以使用一些命令操作 Lua 脚本,引入相关支持;Redis 的 SORT 命令可以对列表键、集合键或者有序集合键的值进行排序,SORT 命令的一些可选性能让我们得到想要的排序;
与本章相关的 Redis 命令总结在下篇文章,欢迎点击收藏,本篇将不再重复:
《Redis常用命令及示例总结(API)》:https://www.cnblogs.com/dlhjw/p/15639773.html
1. Lua 脚本
- Lua 脚本的执行的原子性的;
1.1 Redis 创建并修改 Lua 环境的步骤
- 创建 Lua 环境:调用 Lua 的 C API 函数 lua_open;
- 载入函数库:包括基础库、表格库、字符串库、数学库、调试库、Lua CJSON 库、Struct 库、Lua cmsgpack 库等;
- 创建 redis 全局表格:表格包含对 Redis 进行操作的函数,如
redis.call和redis.pcall函数等; - 使用 Redis 自制的随机函数替换 Lua 原有的随机函数:
math.random和math.randomseed函数带有副作用,Redis 对其进行了替换,以避免在脚本中引入副作用; - 创建排序辅助函数:有些命令在相同数据集上会产生不同结果,为了消除这些命令带来的不确定性,创建了
redis__compare_helper排列辅助函数; - 创建 redis.pcall 函数的错误报告辅助函数:创建
__redis__err__handler错误处理函数,提供更详细的出错信息; - 保护 Lua 全局环境:防止用户在执行脚本时将额外的全局变量添加到 Lua 环境中,但可以修改已存在的全局变量;
- 将 Lua 环境保存到服务器状态的 lua 属性里:然后等待服务器传来 lua 脚本;

1.2 Lua 环境协作组件
Redis 服务器创建两个与 Lua 环境进行协作的组件;
伪客户端:

lua_scripts 字典:
struct redisServer{
//...
//字典,键为某个 Lua 脚本的 SHA1 校验和,值为 SHA1 校验和对应的 Lua 脚本
dict *lua_scripts;
}
- 所有被 EVAL 和 SCRIPT LOAD 命令执行过的脚本会被保存到
lua_scripts字典里;
- 所有被 EVAL 和 SCRIPT LOAD 命令执行过的脚本会被保存到
1.3 EVAL 命令的实现
- 执行命令:
EVAL "return 'hello world'" 0,实现步骤如下:
- 定义脚本函数:给函数命令为
f_SHA1的校验和,函数体为脚本本身return 'hello world'; - 将脚本保存到 lua_scripts 字典:

- 执行脚本函数:先进行设置钩子(hook)、传入参数等准备工作,再执行函数,将执行结果保存到客户端状态的输出缓冲区,执行垃圾回收等操作;
1.4 EVALSHA 命令的实现
- 每个被 EVAL 命令成功执行过的 Lua 脚本,在 Lua 环境中有一个与该脚本对应的 Lua 函数,函数名为
f_SHA1的校验和; - 因此即使不知道脚本内容,也可以通过 EVALSHA SHA1校验和 命令调用对应函数;
1.5 脚本管理命令的实现
- SCRIPT FLUSH:
- 用于清除服务器中所有和 Lua 脚本有关的信息;
- 会释放并重建
lua_scripts字典,关闭现有 Lua 环境并重建一个新的 Lua 环境;
- SCRIPT EXISTS:
- 根通过检查给定校验和 SHA1 是否在的于
lua_scripts字典的键,判断对应的脚本是否存在服务器中;
- 根通过检查给定校验和 SHA1 是否在的于
- SCRIPT LOAD:
- 所做工作与 EVAL 命令前两步一样:定义脚本函数,将脚本函数保存进
lua_scripts字典;
- 所做工作与 EVAL 命令前两步一样:定义脚本函数,将脚本函数保存进
- SCRIPT KILL:
- 用于处理钩子(hook)超时的情况;
- 如果脚本未执行写入操作,客户端可以使用该命令停止执行脚本;
- 如果脚本已经进行过写入操作,客户端只能使用 SHUTDOWN nosave 命令来停止服务器,防止不合法的数据被写入到数据库中;

1.6 脚本复制
- 当服务器运行在复制模式下时,具有写性质的脚本命令会被复制到从服务器里,如:EVAL、EVALSHA、SCRIPT FLUSH、SCRIPTLOAD;
1.6.1 EVAL、SCRIPT FLUSH、SCRIPTLOAD 命令的复制
- 当主服务器执行完 EVAL、SCRIPT FLUSH、SCRIPTLOAD 命令时,会直接将被执行的命令传播给所有从服务器;

1.6.2 EVALSHA 命令的复制
- EVALSHA 命令接受一个校验和 SHA1 作为参数传入;
- 每个服服务器间的脚本校验和 SHA1 可能不同,导致在不同的服务器间执行 EVALSHA 命令情况可能也不同;
- Redis 复制 EVALSHA 命令的方法:
判断传播 EVALSHA 命令是否安全的方法:主服务器使用
repl_scriptcache_dict字典记录哪些脚本传播给了所有从服务器。跟lua_scripts字典对比即可得知哪些校验和还未传播;struct redisServer{
//...
//字典,键是校验和 SHA1,值是 NULL
dict *repl_scriptcache_dict;
};
清空 repl_scriptcache_dict 字典:每当主服务器添加一个新的从服务器时,主服务器会清空自己的
repl_scriptcache_dict字典;EVALSHA 命令转换成 EVAL 命令的方法:根据 sha1 校验和在
lua_scripts字典中查找对应 Lua脚本 script;然后改写命令,用 script 替换 sha1;其他参数不变;- EVAL命令:
EVAL script numkeys key [key ...] arg [arg ...] - EVALSHA命令:
EVALSHA sha1 numkeys key [key ...] arg [arg ...]
- EVAL命令:
传播 EVALSHA 命令的方法:当主服务器成功执行完一个 EVALSHA 命令后,会根据该命令指定的 SHA1 校验和是否存在于
repl_scriptcache_dict字典里来决定是否向从服务器传播 EVALSHA 命令还是 EVAL 命令;

2. 排序
2.1 SORT <key> 命令的实现
- 服务器执行 SORT numbers 命令的详细步骤:
- 1)创建一个和 numbers 列表长度相同的数组,数组的每个项都是
redis.h/redisSortObject结构; - 2)遍历数组,将各个数组项的 obj 指针指向 numbers 列表的各个项,构成 obj 指针和列表项的一对一关系;

- 3)遍历数组,将各个 obj 指针所指向的列表项转换成一个 double 类型的浮点数,并将浮点数保存到相应数组项
u.score属性里;

- 4)根据数组项
u.score属性的值,对数组进行数字值排序;

- 5)遍历数组,将各个数组项的 obj 指针所指向的列表项作为排序结果返回给客户端;
2.2 SORT 命令的可选项
- ALPHA 选项可以对包含字符串的键进行排列;
- ASC 选项执行升序排序;
- DESC 选项执行降序排序;
- 升序和降序都采用快速排序算法;
- BY 选项可以指定某些字符串键,或者某个哈希键说包含的某些域来作为权重,对一个键排序;
- LIMIT 选项可以让结果只返回其中部分已排序的元素;
- GET 选项可以在键被排序后,根据被排序的元素,以及 GET 选项指定的模式,查找并返回某些键的值;
- STORE选项可以将排序结果保存在指定的键里面;
2.3 多个选项的执行顺序
- 选项的执行顺序:
- 1)排序:会先使用 ALPHA、ASC、DESC、BY 这几个选项,对输入键进行排序,并得到一个排序结果集;
- 2)限制排序结果集的长度:使用 LIMIT 选项,对排序结果集的长度进行限制;
- 3)获取外部键:使用 GET 选项,根据结果集的元素,以及 GET 指定的模式,查找并获取指定键的值,并用这些值构成新的结果集;
- 4)保存排序结果集:使用 STORE 选项,将排序结果集保存到指定的键上;
- 5)向客户端返回排序结果集:遍历排序结果集,并依次向客户端返回排序结果集中的元素;
- 除了 GET 选项之外,改变选项的摆放顺序不会影响 SORT 命令执行这些选项的顺序;
最后
新人制作,如有错误,欢迎指出,感激不尽!
欢迎关注公众号,会分享一些更日常的东西!
如需转载,请标注出处!

Redis | 第9章 Lua 脚本与排序《Redis设计与实现》的更多相关文章
- Redis分布式锁—SETNX+Lua脚本实现篇
前言 平时的工作中,由于生产环境中的项目是需要部署在多台服务器中的,所以经常会面临解决分布式场景下数据一致性的问题,那么就需要引入分布式锁来解决这一问题. 针对分布式锁的实现,目前比较常用的就如下几种 ...
- Redis进阶之使用Lua脚本开发
1.在Redis中使用Lua 在Redis中执行Lua脚本有两种方法:eval和evalsha. (1)eval eval 脚本内容 key个数 key列表 参数列表 下面例子使用了key列表和参数列 ...
- redis集群搭建+lua脚本的使用
详细参考这篇文章(windows) https://blog.csdn.net/qiuyufeng/article/details/70474001 一.使用JAVA代码操作redis集群 publi ...
- redis集群+JedisCluster+lua脚本实现分布式锁(转)
https://blog.csdn.net/qq_20597727/article/details/85235602 在这片文章中,使用Jedis clien进行lua脚本的相关操作,同时也使用一部分 ...
- Redis进阶之使用Lua脚本自定义Redis命令
[本文版权归微信公众号"代码艺术"(ID:onblog)所有,若是转载请务必保留本段原创声明,违者必究.若是文章有不足之处,欢迎关注微信公众号私信与我进行交流!] 1.在Redis ...
- Nginx 内嵌lua脚本,结合Redis使用
0x00 Nginx 内嵌Lua脚本有下面特点: 20k个并发连接 Lua脚本能够在Nignx 11个层次的不同层次发挥作用,扩展Ngnix功能 Lua速度极快(寄存器指令) 0x01 应用场景 在w ...
- Redis(六)Lua脚本的支持
Redis为什么需要Lua脚本的支持 当应用需要Redis完成一些Redis命令不支持的特性时,要么扩展Redis client或者更甚至编写c扩展Redis server.这都大大造成了应用的实现的 ...
- Redis | 第11章 服务器的复制《Redis设计与实现》
目录 前言 1. 旧版复制功能的实现 1.1 同步与命令传播 1.2 旧版复制功能的缺陷 2. 新版复制功能的实现 2.1 部分重同步的实现原理 3. PSYNC 命令的实现 4. 复制的详细步骤 4 ...
- 用Jedis调用Lua脚本来完成redis的数据操作
1.先完成一个简单的set/get操作 package com.example.HnadleTaskQueue; import redis.clients.jedis.Jedis; import ja ...
随机推荐
- 2021.9.28考试总结[NOIP模拟64]
T1 三元组 发现确定\(b,c\)的情况下,\(a\)的值域是连续的.确定\(b\)后\(a+b\)的取值是\([1+b,b+b]\).树状数组维护对每个\(b\)可行的\(c\). 注意取模后取值 ...
- MIPI归纳---为什么阻抗为100欧姆
根据LVDS(Low Voltage Differential Signaling)电平定义的. LVDS差分信号PN两线最大幅度是350mV,内部一个恒流源电流是3.5mA.于是终端匹配电阻是100 ...
- 链地址法查找成功与不成功的平均查找长度ASL
晚上,好像是深夜了,突然写到这类题时遇到的疑惑,恰恰这个真题只让计算成功的ASL,但我想学一下不成功的计算,只能自己来解决了,翻了李春葆和严蔚敏的教材没有找到相关链地址法的计算,于是大致翻到两篇不错的 ...
- 【行人惯性导航】关于行人导航中IMU位姿推导的知识点及相关代码
IMU姿态惯性推导 最近从事行人惯性导航的研究,本人也是一个小白,其中看了很多文献,有很多个人思考很费时间的地方,撰写此随笔的目的不仅是给自己做一个笔记,也是给各位有需要的仁兄一点个人理解. 本文只关 ...
- Envoy实现.NET架构的网关(三)代理GRPC
什么是GRPC gRPC是一种与语言无关的高性能远程过程调用 (RPC) 框架.gRPC 的主要好处是: 现代.高性能.轻量级的 RPC 框架. 契约优先的 API 开发,默认使用协议缓冲区,与语言无 ...
- Linux 显示ip、dns、网关等命令
在新版的ubuntu 终端里输入命令nm-tool, 想查看网络参数设置, 没想到却返回如下内容: 未找到 'nm-tool' 命令,您要输入的是否是: 命令 'dm-tool' 来自于包 'l ...
- hdu 3199 Hamming Problem(构造?枚举?)
题意: For each three prime numbers p1, p2 and p3, let's define Hamming sequence Hi(p1, p2, p3), i=1, . ...
- linux 内核源代码情景分析——几个重要的数据结构和函数
页面目录PGD.中间目录PMD和页面表PT分别是由表项pgd_t.pmd_t和pte_t构成的数组,而这些表项都是数据结构 1 /* 2 * These are used to make use of ...
- 如何系统学习C 语言(中)之 指针篇
谈到指针,我们可能会想到钟表上的指针,但这里的指针不是现实生活中看得见摸得着的钟表上的指针,c 语言中的指针只存在于逻辑思维中,物理上并不存在. 同时,指针也是C 语言中最精华的部分,通过灵活地运用指 ...
- 【Python+postman接口自动化测试】(3)什么是接口测试?
什么是接口测试? 接口测试是测试系统组件间接口的一种测试.接口测试主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点.测试的重点是要检查数据的交换.传递和控制管理过程,以及系统间的相互逻辑依 ...