使用lua+redis解决发多张券的并发问题
前言
公司有一个发券的接口有并发安全问题,下面列出这个问题和解决这个问题的方式。
业务描述
这个接口的作用是给会员发多张券码。涉及到4张主体,分别是:用户,券,券码,用户领取记录。
下面是改造前的伪代码。
主要是因为查出券码那行存在并发安全问题,多个线程拿到同几个券码。以下都是基于如何让取券码变成原子的去展开。
public boolean sendCoupons(Long userId, Long couponId) {
// 一堆校验
// ...
// 查出券码
List<CouponCode> couponCodes = couponCodeService.findByCouponId(couponId, num);
// batchUpdateStatus是一个被@Transactional(propagation = Propagation.REQUIRES_NEW)修饰的方法
// 批量更新为已被领取状态
couponCodeService.batchUpdateStatus(couponCods);
// 发券
// 发权益
// 新增用户券码领取记录
}
改造过程
因为券码是多张,想用lua+redis的list结构去做弹出。为什么用这种方案是因为for update直接被否了。
这是写的lua脚本。。
local result = {}
for i=1,ARGV[1],1 do
result[i] = redis.call("lpop", KEYS[1])
end
return table.contact(result , "|")
这是写的执行lua脚本的client。。其实主要的解决方法就是在redis的list里rpush(存),lpop(取)取数据
@Slf4j
@Component
public class CouponCodeRedisQueueClient implements InitializingBean {
/**
* redis lua脚本文件路径
*/
public static final String POP_COUPON_CODE_LUA_PATH = "lua/pop-coupon-code.lua";
public static final String SEPARATOR = "|";
private static final String COUPON_CODE_KEY_PATTERN = "PROMOTION:COUPON_CODE_{0}";
private String LUA_COUPON_CODE_SCRIPT;
private String LUA_COUPON_CODE_SCRIPT_SHA;
@Autowired
private JedisTemplate jedisTemplate;
@Override
public void afterPropertiesSet() throws Exception {
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 -> {
return jedis.scriptLoad(LUA_COUPON_CODE_SCRIPT);
});
log.info("redis lock script sha:{}", LUA_COUPON_CODE_SCRIPT_SHA);
}
}
/**
* 获取Code
*
* @param activityId
* @param num
* @return
*/
public List<String> popCouponCode(Long activityId, String num , int retryNum) {
if(retryNum == 0){
log.error("reload lua script error , try limit times ,activityId:{}", activityId);
return Collections.emptyList();
}
List<String> keys = Lists.newArrayList();
String key = buildKey(String.valueOf(activityId));
keys.add(key);
List<String> args = Lists.newArrayList();
args.add(num);
try {
Object result = jedisTemplate.execute(jedis -> {
if (StringUtils.isNotBlank(LUA_COUPON_CODE_SCRIPT_SHA)) {
return jedis.evalsha(LUA_COUPON_CODE_SCRIPT_SHA, keys, args);
} else {
return jedis.eval(LUA_COUPON_CODE_SCRIPT, keys, args);
}
});
log.info("pop coupon code by lua script.result:{}", result);
if (Objects.isNull(result)) {
return Collections.emptyList();
}
return Splitter.on(SEPARATOR).splitToList(result.toString());
} catch (JedisNoScriptException jnse) {
log.error("no lua lock script found.try to reload it", jnse);
reloadLuaScript();
//加载后重新执行
popCouponCode(activityId, num, --retryNum);
} catch (Exception e) {
log.error("failed to get a redis lock.key:{}", key, e);
}
return Collections.emptyList();
}
/**
* 重新加载LUA脚本
*
* @throws Exception
*/
public void reloadLuaScript() {
synchronized (CouponCodeRedisQueueClient.class) {
try {
afterPropertiesSet();
} catch (Exception e) {
log.error("failed to reload redis lock lua script.retry load it.");
reloadLuaScript();
}
}
}
/**
* 构建Key
*
* @param activityId
* @return
*/
public String buildKey(String activityId) {
return MessageFormat.format(COUPON_CODE_KEY_PATTERN, activityId);
}
}
当然这种操作需要去提前把所有券的券码丢到redis里去,这里我们也碰到了一些问题(券码量比较大的情况下)。比如开始直接粗暴的用@PostConstruct去放入redis,导致项目启动需要很久很久。。这里就不展开了,说一下我们尝试的几种方法
- @PostConstruct注解
- CommandLineRunner接口
- redis的pipeline技术
- 先保证每个卡券有一定量的券码在redis,再用定时任务定时(根据业务量)去补
使用lua+redis解决发多张券的并发问题的更多相关文章
- Lua + Redis 解决高并发
一.业务背景 优惠券业务主要提供用户领券和消券的功能:领取优惠券的动作由用户直接发起,由于资源有限,我们必须对用户的领取动作进行一些常规约束. 约束1(优惠券维度): 券的最大数量 max: 约束2( ...
- 基于nginx+lua+redis高性能api应用实践
基于nginx+lua+redis高性能api应用实践 前言 比较传统的服务端程序(PHP.FAST CGI等),大多都是通过每产生一个请求,都会有一个进程与之相对应,请求处理完毕后相关进程自动释放. ...
- 基于Lua脚本解决实时数据处理流程中的关键问题
摘要 在处理实时数据的过程中需要缓存的参与,由于在更新实时数据时并发处理的特点,因此在更新实时数据时经常产生新老数据相互覆盖的情况,针对这个情况调查了Redis事务和Lua脚本后,发现Redis事务并 ...
- 豌豆夹Redis解决方式Codis源代码剖析:Proxy代理
豌豆夹Redis解决方式Codis源代码剖析:Proxy代理 1.预备知识 1.1 Codis Codis就不详细说了,摘抄一下GitHub上的一些项目描写叙述: Codis is a proxy b ...
- 【网页加速】lua redis的二次升级
之前发过openresty的相关文章,也是用于加速网页速度的,但是上次没有优化好代码,这次整理了下,优化了nginx的配置和lua的代码,感兴趣的话可以看看上篇的文章: https://www.cnb ...
- 【重要】Nginx模块Lua-Nginx-Module学习笔记(三)Nginx + Lua + Redis 已安装成功(非openresty 方式安装)
源码地址:https://github.com/Tinywan/Lua-Nginx-Redis 一. 目标 使用Redis做分布式缓存:使用lua API来访问redis缓存:使用nginx向客户端提 ...
- Gulp解决发布线上文件(CSS和JS)缓存问题
Gulp解决发布线上文件(CSS和JS)缓存问题 本文的缘由:目前经常线上发布文件后要不断的刷新页面及过很长时间,页面上的CSS和JS文件才能生效,特别对于目前做微信商城的时候,微信内置的浏览器缓存非 ...
- Nginx+Lua+Redis 对请求进行限制
Nginx+Lua+Redis 对请求进行限制 一.概述 需求:所有访问/myapi/**的请求必须是POST请求,而且根据请求参数过滤不符合规则的非法请求(黑名单), 这些请求一律不转发到后端服务器 ...
- Nginx与Redis解决高并发问题
原文链接:http://bbs.phpchina.com/forum.php?mod=viewthread&tid=229629 第一版产品采用的是Jquery,Nginx,PHP(CI框架) ...
随机推荐
- 自己整理了一个 Dapper的Helper助手类
链接字符串配置: <connectionStrings> <add name="db" connectionString="server=.;datab ...
- SQL数据库优化的六种方法
SQL命令因为语法简单.操作高效受到了很多用户的欢迎.但是,SQL命令的效率受到不同的数据库功能的限制,特别是在计算时间方面,再加上语言的高效率也不意味着优化会更容易,所以每个数据库都需要依据实际情况 ...
- CentOS配置Nginx官方的Yum源
由于yum源中没有我们想要的nginx,那么我们就需要创建一个"/etc/yum.repos.d/nginx.repo"的文件,其实就是新增一个yum源. [root@niaoyu ...
- webstorm2017.02版本如何使用material theme
本想废话一番,表达对material theme的喜欢.还是直接说方法吧~ file-settings-Plugins-Browse repositories-搜索 material theme -选 ...
- JavaSE21-网络编程
1.网络编程入门 1.1 网络编程概述 计算机网络 是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共 ...
- vue 重置data
Object.assign(this.$data, this.$options.data())
- 2020-2021-1 20209307 《Linux内核原理与分析》第十一周作业
这个作业属于哪个课程 <2020-2021-1Linux内核原理与分析)> 这个作业要求在哪里 <2020-2021-1Linux内核原理与分析第十一周作业> 这个作业的目标 ...
- web服务器专题:tomcat(二)模块组件与server.xml 配置文件
web服务器专题:tomcat(二)模块组件与server.xml 配置文件 回顾: Web服务器专题:tomcat(一) 基础模块 一个Server.xml的实例 <?xml version= ...
- css进阶 06-CSS开发积累
06-CSS开发积累 #让flex盒子中的子元素们,居中 flex布局常用的三行代码: display: flex; justify-content: center; // 子元素在横轴的对齐方式 ( ...
- vue第三单元(webpack的应用-能根据具体的需求构建对应的开发环境)
第三单元(webpack的应用-能根据具体的需求构建对应的开发环境) #课程目标 理解什么是单页面应用. 掌握单页面和多页面的差异. 了解单页面的实现原理. 掌握模块化的方式实现webpack配置,区 ...