SpringCloud在Redis中执行Lua脚本,避免并发所致id重复问题

问题背景

应公司产品要求,为了方便观察,编号需要由统一前缀+日期+连续增长序列构成。

方案

采用redis作为中间件从而产生唯一增长序列后缀。

伪代码大概如下:(线程不安全)

if(exists(key)) {incr(key);
} else {
	// 设置初始值为1000,并设置过期时间为凌晨0点加随机整数
	// 增加随机数是为了避免大量key同时过期,导致redis短暂不可用问题
③	SETEX key 过期时间 1000
}

发现问题

  1. 项目为内部系统,平时使用并不集中,因此一直没有出现过问题
  2. 由于每天凌晨需要定时获取昨日的司机运费数据,统一汇总并存储,项目在测试环境观察数据时发现产生了多个一模一样的1000这个id号

分析问题

  1. 线程到达①处代码时,向redis询问有无key,得到的结果为false,同时将此结果存储在本地内存
  2. 当执行③处代码时,线程被切换了,此线程休眠
  3. 第二个线程执行了③处代码,向redis设置了初始值,并获取到了编号1000
  4. 第一个线程被调度执行的时候又重复执行了③处代码,再次向redis设置了初始值,并获取到了编号1000,这时就出现了id重复问题了

解决方案

  1. 锁—简单粗暴,由于是SpringCloud项目,所以只能使用分布式锁。然而代价就是原本此处只有在每天初次使用时才需要加锁,其它时间使用redis的incr命令已经可以保证原子性了,如果采用分布式锁的方案就会做n-1次无用功,代价太大了
  2. 使用Lua脚本,在redis中执行具有天然的原子性特性,推荐

在SpringBoot中实现

  1. Lua脚本
 if redis.call('EXISTS', KEYS[1]) == 1   
 then   
     return redis.call('INCR', KEYS[1])   
 else   
     redis.call('SETEX', KEYS[1], ARGV[1], ARGV[2])   
     return tonumber(ARGV[2])   
 end 
  1. 代码调用
stringRedisTemplate.execute(new DefaultRedisScript<Long>(SCRIPT, Long.class), keys, args);

注意事项

returnType 参数不能使用Integer.class ,需要使用Long.class。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值