使用lua+redis解决发多张券的并发问题
前言
公司有一个发券的接口有并发安全问题,下面列出这个问题和解决这个问题的方式。
业务描述
这个接口的作用是给会员发多张券码。涉及到4张主体,分别是:用户,券,券码,用户领取记录。
下面是改造前的伪代码。
主要是因为查出券码那行存在并发安全问题,多个线程拿到同几个券码。以下都是基于如何让取券码变成原子的去展开。
publicbooleansendCoupons(LonguserId,LongcouponId){
//一堆校验
//...
//查出券码
ListcouponCodes=couponCodeService.findByCouponId(couponId,num);
//batchUpdateStatus是一个被@Transactional(propagation=Propagation.REQUIRES_NEW)修饰的方法
//批量更新为已被领取状态
couponCodeService.batchUpdateStatus(couponCods);
//发券
//发权益
//新增用户券码领取记录
}
改造过程
因为券码是多张,想用lua+redis的list结构去做弹出。为什么用这种方案是因为forupdate直接被否了。
这是写的lua脚本。。
localresult={}
fori=1,ARGV[1],1do
result[i]=redis.call("lpop",KEYS[1])
end
returntable.contact(result,"|")
这是写的执行lua脚本的client。。其实主要的解决方法就是在redis的list里rpush(存),lpop(取)取数据
@Slf4j
@Component
publicclassCouponCodeRedisQueueClientimplementsInitializingBean{
/**
*redislua脚本文件路径
*/
publicstaticfinalStringPOP_COUPON_CODE_LUA_PATH="lua/pop-coupon-code.lua";
publicstaticfinalStringSEPARATOR="|";
privatestaticfinalStringCOUPON_CODE_KEY_PATTERN="PROMOTION:COUPON_CODE_{0}";
privateStringLUA_COUPON_CODE_SCRIPT;
privateStringLUA_COUPON_CODE_SCRIPT_SHA;
@Autowired
privateJedisTemplatejedisTemplate;
@Override
publicvoidafterPropertiesSet()throwsException{
LUA_COUPON_CODE_SCRIPT=Resources.toString(Resources.getResource(POP_COUPON_CODE_LUA_PATH),Charsets.UTF_8);
if(StringUtils.isNotBlank(LUA_COUPON_CODE_SCRIPT)){
LUA_COUPON_CODE_SCRIPT_SHA=jedisTemplate.execute(jedis->{
returnjedis.scriptLoad(LUA_COUPON_CODE_SCRIPT);
});
log.info("redislockscriptsha:{}",LUA_COUPON_CODE_SCRIPT_SHA);
}
}
/**
*获取Code
*
*@paramactivityId
*@paramnum
*@return
*/
publicListpopCouponCode(LongactivityId,Stringnum,intretryNum){
if(retryNum==0){
log.error("reloadluascripterror,trylimittimes,activityId:{}",activityId);
returnCollections.emptyList();
}
Listkeys=Lists.newArrayList();
Stringkey=buildKey(String.valueOf(activityId));
keys.add(key);
Listargs=Lists.newArrayList();
args.add(num);
try{
Objectresult=jedisTemplate.execute(jedis->{
if(StringUtils.isNotBlank(LUA_COUPON_CODE_SCRIPT_SHA)){
returnjedis.evalsha(LUA_COUPON_CODE_SCRIPT_SHA,keys,args);
}else{
returnjedis.eval(LUA_COUPON_CODE_SCRIPT,keys,args);
}
});
log.info("popcouponcodebyluascript.result:{}",result);
if(Objects.isNull(result)){
returnCollections.emptyList();
}
returnSplitter.on(SEPARATOR).splitToList(result.toString());
}catch(JedisNoScriptExceptionjnse){
log.error("nolualockscriptfound.trytoreloadit",jnse);
reloadLuaScript();
//加载后重新执行
popCouponCode(activityId,num,--retryNum);
}catch(Exceptione){
log.error("failedtogetaredislock.key:{}",key,e);
}
returnCollections.emptyList();
}
/**
*重新加载LUA脚本
*
*@throwsException
*/
publicvoidreloadLuaScript(){
synchronized(CouponCodeRedisQueueClient.class){
try{
afterPropertiesSet();
}catch(Exceptione){
log.error("failedtoreloadredislockluascript.retryloadit.");
reloadLuaScript();
}
}
}
/**
*构建Key
*
*@paramactivityId
*@return
*/
publicStringbuildKey(StringactivityId){
returnMessageFormat.format(COUPON_CODE_KEY_PATTERN,activityId);
}
}
当然这种操作需要去提前把所有券的券码丢到redis里去,这里我们也碰到了一些问题(券码量比较大的情况下)。比如开始直接粗暴的用@PostConstruct去放入redis,导致项目启动需要很久很久。。这里就不展开了,说一下我们尝试的几种方法
- @PostConstruct注解
- CommandLineRunner接口
- redis的pipeline技术
- 先保证每个卡券有一定量的券码在redis,再用定时任务定时(根据业务量)去补
到此这篇关于使用lua+redis解决发多张券的并发问题的文章就介绍到这了,更多相关redis多张券的并发内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。