高并发情况下使用Redis采用Lua脚本生成多个唯一的流水号

高并发情况下使用Redis采用Lua脚本生成多个唯一的流水号

业务场景:

业务需求需要在高并发的情况下生成唯一的流水号,并且返回相应并发数量的流水号

模块+年+月作为Key值,如果在redis中不存在该模块的key则从数据库查询最新的模块流水号并加1设置到redis,且返回

主要问题:

1、需要保证高并发情况下流水号的唯一性

2、需要保证多个并发请求都可以拿到唯一的流水号

方案一:采用synchronized锁的方式

*缺陷:无法解决多节点服务问题*

使用本地锁的方式锁住生成流水号的业务代码

方案二:watch+redis 事务


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hEb3ZIlS-1634627454140)(file:///C:\Users\zzf\AppData\Local\Temp\ksohtml\wps2739.tmp.jpg)]

*缺陷:只能确保在并发情况下流水号的唯一,而无法生成对应并发数量的流水号*

*原因:watch 监听key 的模式是采用CAS乐观锁的方式,所以监听不到其它修改了key 并且key值未发生改变的情况,所以监听不会触发,会导致事务多个都可以成功,无法采用事务失败重试生成流水号*

相关链接:

redis:解决分布式高并发修改同一个key问题

RedisTemplate 事务处理方法 watch multi exec 的使用

Redis watch命令——监控事务

springboot2.0版本之前使用redis事务关闭问题

redis事务

方案三:使用分布式锁

1、使用分布式锁 redis nx锁

2、使用redisson

方案四:使用消息队列

利用消息中间件进行处理,将并发改变成串行化生成

方案五:采用redis lua脚本

实现代码:

  // 生成流水号方法
  private  BigDecimal getSerialNumber(String yearMouth, String serialNumberPrefix, BalanceModule balanceModule) {
        String key = RedisConstant.MODULE_YEAR_MONTH_SERIAL_NUMBER + serialNumberPrefix + ":" + yearMouth;
		// 数据库查询模块流水号并且自动版本加1,最新可使用的流水号
        BigDecimal serialNumberStartOnBatch = getSerialNumberStartOnBatch(balanceModule, Integer.valueOf(yearMouth));
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setResultType(Long.class);
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis/redis_serial_number.lua")));
        Long execute = redisTemplate.execute(redisScript, Collections.singletonList(key), serialNumberStartOnBatch + "");
        return BigDecimal.valueOf(execute);
    }

// 数据库查询模块流水号
 private BigDecimal getSerialNumberStartOnBatch(BalanceModule module, Integer yearMonth) {
        String prefix = module.getSerialNumberPrefix() + "-" + yearMonth;
        // 获取主表中对应年月最新的流水号
        String latestSerialNumberInBalance = frsBalanceService.getLatestSerialNumberInBalance(module.name(), prefix);
        // 获取日志表中对应年月最新的流水号
        String latestSerialNumberInLog = frsBalanceService.getLatestSerialNumberInLogBy(module.getModuleName(),                            									BalanceOperateType.TEMPLATE.getType(), prefix);

        if (latestSerialNumberInBalance == null && latestSerialNumberInLog == null) {
            return BigDecimal.ONE;
        }
        BigDecimal balanceSerialNumber = SerialNumberUtils.getNumber(latestSerialNumberInBalance);
        BigDecimal logSerialNumber = SerialNumberUtils.getNumber(latestSerialNumberInLog);
        BigDecimal result = balanceSerialNumber.compareTo(logSerialNumber) >= 0 ? balanceSerialNumber : logSerialNumber;

        return result.add(BigDecimal.ONE);
    }

lua脚本(放在项目resource目录下,名为redis_serial_number.lua文件)

思路:判断当前key值在redis中是否存在,存在则流水号加1并且返回,不存在则设置当前key,值为数据库查询出来的最新模块流水号

if
redis.call('get',KEYS[1])
then
    return redis.call('incr',KEYS[1]);
else
    return redis.call('set',KEYS[1],ARGV[1]);
end
相关链接:

lua教程

SpringBoot通过RedisTemplate执行Lua脚本的方法步骤

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值