1、现实问题
1.redis 采用单线程架构,可以保证单个命令的原子性,但是无法保证一组命令在高并发场景下的原子性。例如: 在串行场景下:A和B的值肯定都是3 在并发场景下:A和B的值可能在0-6之间。
2.极限情况下1:则A的结果是0,B的结果是3 3.极限情况下2:A和B的结果都是6 如果redis 客户端通过lua 脚本把3个命令一次性发送给redis 服务器,那么这三个指令就不会被其他客户端指令打断。 Redis 也保证脚本会以原子性(atomic)的方式执行: 当某个脚本正在运行的时候,不会有其他脚本或 Redis 命令被执行。 这和使用 MULTI/ EXEC 包围的事务很类似。 但是MULTI/ EXEC方法来使用事务功能,将一组命令打包执行,无法进行业务逻辑的操作。这期间有某一条命令执行报错(例如给字符串自增),其他的命令还是会执行,并不会回滚。
1.Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。 2.设计目的:其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。 3.Lua 特性: 轻量级 :它用标准C语言编写并以源代码形式开放,编译后仅仅一百余K,可以很方便的嵌入别的程序里。可扩展 :Lua提供了非常易于使用的扩展接口和机制:由宿主语言(通常是C或C++)提供这些功能,Lua可以使用它们,就像是本来就内置的功能一样。其它特性: 支持面向过程(procedure-oriented)编程和函数式编程(functional programming); 自动内存管理;只提供了一种通用类型的表(table),用它可以实现数组,哈希表,集合,对象; 语言内置模式匹配;闭包(closure);函数也可以看做一个值;提供多线程(协同进程,并非操作系统所支持的线程)支持; 通过闭包和table可以很方便地支持面向对象编程所需要的一些关键机制,比如数据抽象,虚函数,继承和重载等。
1.对lua 脚本感兴趣的同学,请移步到官方教程或者《菜鸟教程》。这里仅以redis 中可能会用到的部分语法作介绍。
lua">a = 5
local b = 5
a, b = 10 , 2 * x
lua">if ( 布尔表达式 1 )
then
elseif ( 布尔表达式 2 )
then
else
end
EVAL script numkeys key [ key .. .] arg [ arg .. .]
script:lua 脚本字符串,这段Lua脚本不需要(也不应该)定义函数。
numkeys:lua 脚本中KEYS数组的大小
key [ key .. .] :KEYS数组中的元素
arg [ arg .. .] :ARGV数组中的元素
b3-1.案例1:基本案例:
EVAL "return 10" 0
b3-2.案例2:动态传参
EVAL "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 5 10 20 30 40 50 60 70 80 90
EVAL "if KEYS[1] > ARGV[1] then return 1 else return 0 end" 1 10 20
EVAL "if KEYS[1] > ARGV[1] then return 1 else return 0 end" 1 20 10
2.传入了两个参数10和20,KEYS的长度是1,所以KEYS中有一个元素10,剩余的一个20就是ARGV数组的元素。 3.redis .call()
中的redis 是redis 中提供的lua 脚本类库,仅在redis 环境中可以使用该类库。
set aaa 10 -- 设置一个aaa值为10
EVAL "return redis .call('get', 'aaa')" 0
2.注意: **脚本里使用的所有键都应该由 KEYS 数组来传递。**但并不是强制性的,代价是这样写出的脚本不能被 Redis 集群所兼容。
redis _81">b3-4.案例4:给redis 类库方法动态传参
EVAL "return redis .call('set', KEYS[1], ARGV[1])" 1 bbb 20
2.学到这里基本可以应付redis 分布式锁所需要的脚本知识了。
b3-5.案例5:pcall函数的使用(了解)
-- 当call( ) 在执行命令的过程中发生错误时,脚本会停止执行,并返回一个脚本错误,输出错误信息
EVAL "return redis .call('sets', KEYS[1], ARGV[1]), redis .call('set', KEYS[2], ARGV[2])" 2 bbb ccc 20 30
-- pcall函数不影响后续指令的执行
EVAL "return redis .pcall('sets', KEYS[1], ARGV[1]), redis .pcall('set', KEYS[2], ARGV[2])" 2 bbb ccc 20 30
2.注意: set方法写成了sets**,肯定会报错。
lua _99">3、使用lua 保证删除原子性
lua">if redis . call ( 'get' , KEYS[ 1 ] ) == ARGV[ 1 ] then return redis . call ( 'del' , KEYS[ 1 ] ) else return 0 end
public void deduct ( ) { String uuid = UUID . randomUUID ( ) . toString ( ) ; while ( ! this . redis Template. opsForValue ( ) . setIfAbsent ( "lock" , uuid, 3 , TimeUnit . SECONDS ) ) { try { Thread . sleep ( 50 ) ; } catch ( InterruptedException e) { e. printStackTrace ( ) ; } } try { String stock = redis Template. opsForValue ( ) . get ( "stock" ) . toString ( ) ; if ( stock != null && stock. length ( ) != 0 ) { Integer st = Integer . valueOf ( stock) ; if ( st > 0 ) { redis Template. opsForValue ( ) . set ( "stock" , String . valueOf ( -- st) ) ; } } } finally { String script = "if redis .call('get', KEYS[1]) == ARGV[1] " + "then " + " return redis .call('del', KEYS[1]) " + "else " + " return 0 " + "end" ; this . redis Template. execute ( new DefaultRedisScript < > ( script, Boolean . class ) , Arrays . asList ( "lock" ) , uuid) ; }
}