前言

公司有一个发券的接口有并发安全问题,下面列出这个问题和解决这个问题的方式。

业务描述

这个接口的作用是给会员发多张券码。涉及到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解决发多张券的并发问题的更多相关文章

  1. Lua + Redis 解决高并发

    一.业务背景 优惠券业务主要提供用户领券和消券的功能:领取优惠券的动作由用户直接发起,由于资源有限,我们必须对用户的领取动作进行一些常规约束. 约束1(优惠券维度): 券的最大数量 max: 约束2( ...

  2. 基于nginx+lua+redis高性能api应用实践

    基于nginx+lua+redis高性能api应用实践 前言 比较传统的服务端程序(PHP.FAST CGI等),大多都是通过每产生一个请求,都会有一个进程与之相对应,请求处理完毕后相关进程自动释放. ...

  3. 基于Lua脚本解决实时数据处理流程中的关键问题

    摘要 在处理实时数据的过程中需要缓存的参与,由于在更新实时数据时并发处理的特点,因此在更新实时数据时经常产生新老数据相互覆盖的情况,针对这个情况调查了Redis事务和Lua脚本后,发现Redis事务并 ...

  4. 豌豆夹Redis解决方式Codis源代码剖析:Proxy代理

    豌豆夹Redis解决方式Codis源代码剖析:Proxy代理 1.预备知识 1.1 Codis Codis就不详细说了,摘抄一下GitHub上的一些项目描写叙述: Codis is a proxy b ...

  5. 【网页加速】lua redis的二次升级

    之前发过openresty的相关文章,也是用于加速网页速度的,但是上次没有优化好代码,这次整理了下,优化了nginx的配置和lua的代码,感兴趣的话可以看看上篇的文章: https://www.cnb ...

  6. 【重要】Nginx模块Lua-Nginx-Module学习笔记(三)Nginx + Lua + Redis 已安装成功(非openresty 方式安装)

    源码地址:https://github.com/Tinywan/Lua-Nginx-Redis 一. 目标 使用Redis做分布式缓存:使用lua API来访问redis缓存:使用nginx向客户端提 ...

  7. Gulp解决发布线上文件(CSS和JS)缓存问题

    Gulp解决发布线上文件(CSS和JS)缓存问题 本文的缘由:目前经常线上发布文件后要不断的刷新页面及过很长时间,页面上的CSS和JS文件才能生效,特别对于目前做微信商城的时候,微信内置的浏览器缓存非 ...

  8. Nginx+Lua+Redis 对请求进行限制

    Nginx+Lua+Redis 对请求进行限制 一.概述 需求:所有访问/myapi/**的请求必须是POST请求,而且根据请求参数过滤不符合规则的非法请求(黑名单), 这些请求一律不转发到后端服务器 ...

  9. Nginx与Redis解决高并发问题

    原文链接:http://bbs.phpchina.com/forum.php?mod=viewthread&tid=229629 第一版产品采用的是Jquery,Nginx,PHP(CI框架) ...

随机推荐

  1. [BJDCTF2020]ZJCTF,不过如此 php伪协议, preg_replace() 函数/e模式

    转自https://www.cnblogs.com/gaonuoqi/p/12499623.html 题目给了源码 <?php error_reporting(0); $text = $_GET ...

  2. 使用cmd制作图片木马

    我们可以使用windows下自带的cmd制作图片木马,配合文件包含漏洞可以达到getshell的目的 我们找到一张图片:kiss.jpg 如图: 写好一句话木马:chopper.php 将两者放在同一 ...

  3. Kubernetes的Local Persistent Volumes使用小记

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  4. 在.Net中所有可序列化的类都被标记为_

    [Serializable] 这个叫Attribute写在类.属性.字段的上面,比如 [Serializable] class A { ... }

  5. C# operator 关键字的用法

    operator 只要是运算符都能重载 operator 关键字的主要作用是用来重载运算符的,还可以用于类或结构中类型的自定义转换. 下面看个例子 class Feige { //定义两个全局变量 i ...

  6. 【题单】最近遇见的 SHIT DP题 三连

    Hint: 本题单适合用于自虐和消磨时间. CF-Gym101620E https://codeforces.com/gym/101620 ARC109F https://atcoder.jp/con ...

  7. Java安全之原生readObject方法解读

    Java安全之原生readObject方法解读 0x00 前言 在上篇文章分析shiro中,遇到了Shiro重写了ObjectInputStream的resolveClass导致的一些基于Invoke ...

  8. windows远程msra windows使用技巧

    qq远程太慢,mstsc?又不想把电脑密码告诉好友,怎么办呢?在这里推荐一个方法: 点击开始运行输入msra,确定,邀请信任的人帮助你您,将该邀请另存为文件,然后这个窗口不要关,把密码复制粘贴给好友, ...

  9. [OI笔记]三种逆元的求法

    其实这篇博客只是搬运一下我之前(大概是NOIP那会)写在word里的笔记- 下面直接复制原话,题目是洛谷上求逆元的模板题:https://www.luogu.org/problemnew/show/P ...

  10. Boost.JSON Boost的JSON解析库(1.75首发)

    目录 目录 Boost的1.75版本新库 JSON库简介 JSON的简单使用 编码 最通用的方法 使用std::initializer_list json对象的输出 两种对比 解码 简单的解码 增加错 ...