如何在redis中华丽丽地使用lua脚本
一、引言
在工作中,使用到redis的情况下,不管是redis单机还是redis集群,存在高并发的时候,难免出现业务冲突。例如使用reids存放接口调用访问次数,来达到限流的情况下,或者使用redis存放产品订购的重要业务参数,这个时候保证原子性显得格外的重要。此时,可以在redis中使用lua脚本。
简单介绍一下:
减少网络开销。
可以将多个请求通过脚本的形式一次发送,减少网络时延
原子操作。
redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。
因此在编写脚本的过程中无需担心会出现竞态条件,无需使用事务。
复用。
客户端发送的脚步会永久存在redis中,这样,其他客户端可以复用这一脚本而不需要使用代码完成相同的逻辑。
二、基本语法
EVAL script numkeys key [key ...] arg [arg ...]
script : 要执行的脚本
numkeys :指定后面跟着的key个数,后面跟着就是自定义的参数
example:
->eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 someone age is 20
"someone"
"age"
"is"
"20"
三、实际案例
1、生成固定位数的业务索引,超过位数变为从头开始
local num =
redis.call('get', KEYS[1])
if(not num) then return
redis.call('incr', KEYS[1])
else if(tonumber(num) < tonumber(ARGV[1])) then
return redis.call('incr', KEYS[1])
else redis.call('set', KEYS[1], 1)
return 1
end
end
2、通过redis实现流量控制
1.key不存在,设置key值为1,设置失效时间expireTime,返回1
2.key值超过设置阈值times(毫秒),返回-1
3.key存在并不超过阈值times,返回该key当前value值
if not redis.call('get', KEYS[1])
then
redis.call('incr', KEYS[1])
redis.call('pexpire', KEYS[1], ARGV[1])
return 1
else if tonumber(redis.call('get', KEYS[1])) > tonumber(ARGV[2]) - 1
then
return -1
else
return redis.call('incr', KEYS[1]) end
end
四、业务索引的思考
一般在工作中生产不重复的唯一id下面四种方法:
数据库自增
uuid()
redis使用lua
雪花算法
反思:使用uuid方法生成的id不利于创建索引,一般使用的不多。
所以使用较多的一般是雪花算法和redis生成。自增、有序、适合分布式场景,生成时不依赖于数据库,完全在内存中生成,每秒能生成数百万的自增 ID,存入数据库中,索引效率高。
时间位:可以根据时间进行排序,有助于提高查询速度。
机器 ID 位:适用于分布式环境下对多节点的各个节点进行标识,可以具体根据节点数和部署情况设计划分机器位 10 位长度,如划分5位表示进程位等。
序列号位:是一系列的自增id,可以支持同一节点同一毫秒生成多个 ID 序号,12 位的计数序列号支持每个节点每毫秒产生 4096 个 ID 序号
但是作为最强的雪花算法也算是存在缺陷:
雪花算法在单机系统上 ID 是递增的,但强依赖与系统时间的一致性,如果系统时间被回拨或者改变,可能会造成 ID 冲突或者重复。在分布式系统多节点的情况下,所有节点的时钟并不能保证不完全同步,所以有可能会出现不是全局递增的情况。