文档章节

Redis中使用Lua脚本的开发思路

secondriver
 secondriver
发布于 2015/09/17 09:26
字数 1645
阅读 78
收藏 1

   Redis提供了通过eval命令来执行Lua脚本。下面通过几个小例子来讲述如何在Redis服务端执行Lua脚本。


  1. 执行Lua脚本的几个命令如下:

   

命令格式 说明 对应Jedis客户端Jedis对象的方法之一(有更多重载方法)
EVAL script numkeys key [key ...] arg [arg ...] 执行Lua脚本


public Object eval(String script, int keyCount, String... params)

EVALSHA sha1 numkeys key [key ...] arg [arg ...] 根据给定的 sha1 校验码,对缓存在服务器中的脚本进行求值 public Object evalsha(String sha1, int keyCount, String... params)
SCRIPT LOAD script 将给定的脚本缓存,不执行,并返回sha1校验值 public String scriptLoad(String script)
SCRIPT EXISTS sha1 [sha1 ...] 给定一个或多个脚本的 SHA1 校验和,返回一个包含 0 和 1 的列表,表示校验和所指定的脚本是否已经被保存在缓存当中 public List<Boolean> scriptExists(String... sha1) 
SCRIPT FLUSH 
清除所有 Lua 脚本缓存
SCRIPT KILL 杀死当前正在运行的 Lua 脚本,当且仅当这个脚本没有执行过任何写操作时,这个命令才生效(如果已经执行了写操作,则需要通过shutdown nosave命令来处理)


  2.通过redis-cli客户端执行Lua脚本

    

 redis-cli --eval myscript.lua key1 key2 , arg1 arg2 arg3

   

    需要注意的是用逗号来分割key和参数,这里与在交互式模式下执行evel命令有所不同。


  3.实际案例


   场景一:对一个特定请求1秒钟只允许访问10次,当符合请求访问条件时,返回True,否则返回False。

   Java客户端操作Redis服务,实现代码如下:

   

/**
	 * 访问控制
	 * 
	 * 1秒内最多可访问10次
	 * 
	 * @param key
	 * @return
	 */
	public boolean isAccess(String key) {
		String rkey = "acc:" + key;
		long value = jedis.incr(rkey);
		if (value == 1) {
			jedis.expire(rkey, 1);
			return true;
		} else {
			boolean rs = value <= 10;
			return rs;
		}
	}


  INCR命令作为计数器,如果rkey存在,则增加1返回最终值,否则初始化值为0,然后加1。如上程序,如果访问rkey不存在,则表示第一次请求,这时对其rkey设置过期时间为1秒,否则比较其值是否超过制定请求数的阀值10.

 

  用Lua脚本来完成这一操作:

  

--[[
Judge status 
KEYS[1]:key
ARGV[1]:request numbers
ARGV[2]:expires times seconds
--]]

local key, rqn, exp  = KEYS[1], ARGV[1], ARGV[2];
local value=redis.call("incr", key);
redis.log(redis.LOG_NOTICE, "incr "..key);
if(tonumber(value) == 1)then
   redis.call("expire", key,  exp);
   redis.log(redis.LOG_NOTICE, "expire "..key.." "..exp)
   return true;
else
   return tonumber(value) <= tonumber(rqn);
end


   通过Java客户端代码实现该功能存在一定缺陷,比如每1秒就需要操作1个incr和expire命令,并且该命令是由客户端通过网络发起的,而使用Lua脚本则既可以保证操作的原子性,又能使每次操作只需要一个key即可在服务器端完成相应的判断操作。可以通过SCRIPT LOAD的方式将脚本缓存到服务器,通过sha1校验值+参数(Key,ARG)来执行,减轻网络传输,也对该功能做到较好的封装。


 场景二:指定模式key批量删除

    redis目前提供的删除命令del仅支持删除指定数量的key,并不能通过指定模式key来进行删除,比如:del *user 删除以user结尾的key。

    

  在redis中提供了keys命令,该命令可以通过指定模式key来获取key列表,下面通过keys和del命令组合实现一个指定模式key批量删除的命令。

  

--[[
Pattern delete key
KEYS[1]:pattern
--]]

redis.log(redis.LOG_NOTICE, "call keys "..KEYS[1]);

local keys=redis.call("keys", KEYS[1]);
local count = 0;
if(keys and (table.maxn(keys) > 0)) then
    for index, key in ipairs(keys) do
        redis.log(redis.LOG_NOTICE, "del "..key);
        count = count +  redis.call("del", key);
    end
end
return count;

 

  需要注意的是场景二可以作为一种思路,通过Lua脚本组合redis内置命令来实现特定功能的命令。而这里的模式key批量删除并未一个好的命令,因为如果key的数量很大时,将会有比较严重的性能问题。redis默认限制Lua脚本执行时间最大为5秒,如果超过5秒将继续接受来自客户端的请求,并简单的返回BUSY结果。这时候则需要SCRIPT KILL或者SHUTDOWN NOSAVE命令做相应的处理。因此应该尽力保证脚本的执行速度极快。


场景三:生成随机数

  对于Redis而且,脚本执行在相同数据集,相同参数下执行写命令具有一致性的。其不依赖与隐式的数据集,脚本执行过程中不同执行时期的状态变化,也不依赖外部I/O设备的输入。


  要符合Redis服务执行的脚本条件,需要注意的地方比较多,可以参见:                         http://redis.io/commands/eval


  下面是实现随机数列表的Lua脚本:


--[[
Random lpush a list key-value
KEYS[1]:key name
ARGV[1]:ramdom seed value
ARGV[2]:add element count
--]]

math.randomseed(ARGV[1]);
for i=1, ARGV[2], 1 do
    redis.call("lpush", KEYS[1], math.random());
end
redis.log(redis.LOG_NOTICE, "lpush " .. KEYS[1]);
return true;


  上述脚本通过改变randomseed函数的参数来实现随机数,如果两次执行上述脚本,ARGV[1]参数值相同,则产生的随机数是相同的。


 通过执行上述脚本,记录每次生产的值,然后删除对应key,再次生成。

 

wKioL1UYz4WSgIhaAAOqB1w19Sc305.jpg

wKiom1UYzkqx3NzwAAFpvCYWbRo501.jpg


wKiom1UYz6DwtZZyAAJG2oORc7c681.jpg


  对比上述结果,在执行该脚本时,随机数的生成由seed参数(第一个参数)决定的。

  相同随机数种子下生成的随机数是相同的,如果再次执行脚本,指定生成的随机数个数n小于已经生成的随机数个数m,则取已经生成的前n个,如果指定生成的随机数个数n大于已经生成的随机数个数m,则次数再生成(n-m)个随机数,并固定下来。


 4.Redis中使用Lua脚本总结

    Redis内置了Lua解释器,这为操作Redis服务器和数据提供了巨大的灵活性。

    文中几个场景并不见得实际,有效,但并不能掩盖Lua与Redis结合将为Redis的使用提供了更大的想象和操作空间。

    我们可以通过Lua来实现更多特定功能的命令;用Lua来封装复杂了Redis操作的业务;计数,统计,分析,收集数据;实现业务操作事务控制等等。更多场景,还需在实际中不断摸索和尝试。

本文出自 “野马红尘” 博客,转载请与作者联系!

© 著作权归作者所有

secondriver
粉丝 10
博文 229
码字总数 233821
作品 0
广州
程序员
私信 提问
Redis原子性写入HASH结构数据并设置过期时间

Redis中提供了原子性命令SETEX或SET来写入STRING类型数据并设置Key的过期时间: 但对于HASH结构则没有这样的命令,只能先写入数据然后设置过期时间: 这样就带了一个问题:HSET命令执行成功而...

雪飞鸿
07/19
0
0
Lua + Redis 优惠券领券设计

一、业务背景   优惠券业务主要提供用户领券和消券的功能;领取优惠券的动作由用户直接发起,由于资源有限,我们必须对用户的领取动作进行一些常规约束。   约束1(优惠券维度): 券的最大...

xiaomin0322
06/11
89
0
Redis进阶实践之七Redis和Lua初步整合使用

Redis进阶实践之七Redis和Lua初步整合使用 一、引言 Redis学了一段时间了,基本的东西都没问题了。从今天开始讲写一些redis和lua脚本的相关的东西,lua这个脚本是一个好东西,可以运行在任何...

morpheusWB
2018/09/13
31
2
如何优雅地在Redis中使用Lua

作者:可均可可 原文:http://www.cnblogs.com/PatrickLiu/p/8391829.html 一、引言 今天讲一些redis和lua脚本的相关的东西,lua这个脚本是一个好东西,可以运行在任何平台上,也可以嵌入到大...

程序员之家_
02/08
0
0
初学乍练:redis事务与脚本

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wzy0623/article/details/82350861 目录 一、事务 1. 概述 2. 错误处理 3. watch命令 二、redis脚本 1. 脚本介...

wzy0623
2018/09/03
0
0

没有更多内容

加载失败,请刷新页面

加载更多

32位与64位Linux系统下各类型长度对比

64 位的优点:64 位的应用程序可以直接访问 4EB 的内存和文件大小最大达到4 EB(2 的 63 次幂);可以访问大型数据库。本文介绍的是64位下C语言开发程序注意事项。 1. 32 位和 64 位C数据类型...

mskk
16分钟前
4
0
Vue 实现点击空白处隐藏某节点(三种方式:指令、普通、遮罩)

在项目中往往会有这样的需求: 弹出框(或Popover)在 show 后,点击空白处可以将其 hide。 针对此需求,整理了三种实现方式,大家按实际情况选择。 当然,我们做项目肯定会用到 UI 框架,常...

张兴华ZHero
22分钟前
5
0
SpringBoot激活profiles你知道几种方式?

多环境是最常见的配置隔离方式之一,可以根据不同的运行环境提供不同的配置信息来应对不同的业务场景,在SpringBoot内支持了多种配置隔离的方式,可以激活单个或者多个配置文件。 激活Profi...

恒宇少年
24分钟前
6
0
PDF修改文字的方法有哪些?怎么修改PDF文件中的文字

PDF修改文字一直以来都是一个难以解决的问题,很多的办公族在办公的时候会有修改PDF文件中的文字的需要,可是PDF文件一般是不能进行编辑和修改的,难道就没有什么办法解决这个问题了嘛?不要...

趣味办公社
27分钟前
3
0
企业组织中采用服务网格的挑战

作者:Christian Posta 译者:罗广明 原文:https://blog.christianposta.com/challenges-of-adopting-service-mesh-in-enterprise-organizations/ 编者按 本文作者介绍了企业组织采用服务网...

jimmysong
36分钟前
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部