redis原子性读写操作之LUA脚本和watch机制

时间:2023-02-01 06:49:22

  最近在开发电商平台的子系统——储值卡系统,系统核心业务涉及到金额消费以及库存控制,因此为了解决建立在内存上高并发情况下的事务控制,使用了spring封装的RedisTemplate执行lua脚本进行原子性操作,确保金额消费,库存按顺序处理,解决资源争抢。

  

  1.使用lua脚本

  Redis 使用单个 Lua 解释器去运行所有脚本,并且, Redis 也保证脚本会以原子性(atomic)的方式执行:当某个脚本正在运行的时候,不会有其他脚本或 Redis 命令被执行。这和使用 MULTI / EXEC 包围的事务很类似。在其他别的客户端看来,脚本的效果(effect)要么是不可见的(not visible),要么就是已完成的(already completed)。

  另一方面,这也意味着,执行一个运行缓慢的脚本并不是一个好主意。写一个跑得很快很顺溜的脚本并不难,因为脚本的运行开销(overhead)非常少,但是当你不得不使用一些跑得比较慢的脚本时,请小心,因为当这些蜗牛脚本在慢吞吞地运行的时候,其他客户端会因为服务器正忙而无法执行命令。

  2.实现watch机制

  

  WATCH key [key ...]

  监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。

  

  MULTI

  标记一个事务块的开始。

  事务块内的多条命令会按照先后顺序被放进一个队列当中,最后由 EXEC 命令原子性(atomic)地执行。

  EXEC

  执行所有事务块内的命令。

  假如某个(或某些) key 正处于 WATCH 命令的监视之下,且事务块中有和这个(或这些) key 相关的命令,那么 EXEC 命令只在这个(或这些) key 没有被其 他命令所改动的情况下执行并生效,否则该事务被打断(abort)。

    例子:

  

  # 事务被成功执行

  redis> MULTI
  OK   redis> INCR user_id
  QUEUED   redis> INCR user_id
  QUEUED   redis> INCR user_id
  QUEUED   redis> PING
  QUEUED   redis> EXEC
  1) (integer) 1
  2) (integer) 2
  3) (integer) 3
  4) PONG   # 监视 key ,且事务成功执行   redis> WATCH lock lock_times
  OK
  redis> MULTI
  OK   redis> SET lock "huangz"
  QUEUED   redis> INCR lock_times
  QUEUED   redis> EXEC
  1) OK
  2) (integer) 1   # 监视 key ,且事务被打断   redis> WATCH lock lock_times
  OK   redis> MULTI
  OK   redis> SET lock "joe" # 就在这时,另一个客户端修改了 lock_times 的值
  QUEUED   redis> INCR lock_times
  QUEUED   redis> EXEC # 因为 lock_times 被修改, joe 的事务执行失败
  (nil)