vlambda博客
学习文章列表

如何在redis中华丽丽地使用lua脚本

一、引言


在工作中,使用到redis的情况下,不管是redis单机还是redis集群,存在高并发的时候,难免出现业务冲突。例如使用reids存放接口调用访问次数,来达到限流的情况下,或者使用redis存放产品订购的重要业务参数,这个时候保证原子性显得格外的重要。此时,可以在redis中使用lua脚本。

简单介绍一下:

  1. 减少网络开销。

    可以将多个请求通过脚本的形式一次发送,减少网络时延

  2. 原子操作。

    redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。

    因此在编写脚本的过程中无需担心会出现竞态条件,无需使用事务。

  3. 复用。

    客户端发送的脚步会永久存在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,返回12.key值超过设置阈值times(毫秒),返回-13.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下面四种方法:
  1. 数据库自增

  2. uuid()

  3. redis使用lua

  4. 雪花算法

反思:使用uuid方法生成的id不利于创建索引,一般使用的不多。
所以使用较多的一般是雪花算法和redis生成。自增、有序、适合分布式场景,生成时不依赖于数据库,完全在内存中生成,每秒能生成数百万的自增 ID,存入数据库中,索引效率高。

  1. 时间位:可以根据时间进行排序,有助于提高查询速度。

  2. 机器 ID 位:适用于分布式环境下对多节点的各个节点进行标识,可以具体根据节点数和部署情况设计划分机器位 10 位长度,如划分5位表示进程位等。

  3. 序列号位:是一系列的自增id,可以支持同一节点同一毫秒生成多个 ID 序号,12 位的计数序列号支持每个节点每毫秒产生 4096 个 ID 序号

但是作为最强的雪花算法也算是存在缺陷:

    雪花算法在单机系统上 ID 是递增的,但强依赖与系统时间的一致性,如果系统时间被回拨或者改变,可能会造成 ID 冲突或者重复。在分布式系统多节点的情况下,所有节点的时钟并不能保证不完全同步,所以有可能会出现不是全局递增的情况。