• 企业400电话
  • 微网小程序
  • AI电话机器人
  • 电商代运营
  • 全 部 栏 目

    企业400电话 网络优化推广 AI电话机器人 呼叫中心 网站建设 商标✡知产 微网小程序 电商运营 彩铃•短信 增值拓展业务
    使用lua+redis解决发多张券的并发问题

    前言

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

    业务描述

    这个接口的作用是给会员发多张券码。涉及到4张主体,分别是:用户,券,券码,用户领取记录。
    下面是改造前的伪代码。
    主要是因为查出券码那行存在并发安全问题,多个线程拿到同几个券码。以下都是基于如何让取券码变成原子的去展开。

    public boolean sendCoupons(Long userId, Long couponId) {
     // 一堆校验
     // ...
     // 查出券码
     ListCouponCode> 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 ListString> popCouponCode(Long activityId, String num , int retryNum) {
      if(retryNum == 0){
       log.error("reload lua script error , try limit times ,activityId:{}", activityId);
       return Collections.emptyList();
      }
      ListString> keys = Lists.newArrayList();
      String key = buildKey(String.valueOf(activityId));
      keys.add(key);
      ListString> 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,导致项目启动需要很久很久。。这里就不展开了,说一下我们尝试的几种方法

    到此这篇关于使用lua+redis解决发多张券的并发问题的文章就介绍到这了,更多相关redis多张券的并发内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

    您可能感兴趣的文章:
    • Redis实现分布式Session管理的机制详解
    • kubernetes环境部署单节点redis数据库的方法
    • 银河麒麟V10sp1服务器系统安装redis不能使用的快速解决办法
    • 使用docker搭建redis主从的方法步骤
    • 基于redis setIfAbsent的使用说明
    上一篇:redis-benchmark并发压力测试的问题解析
    下一篇:银河麒麟V10sp1服务器系统安装redis不能使用的快速解决办法
  • 相关文章
  • 

    © 2016-2020 巨人网络通讯 版权所有

    《增值电信业务经营许可证》 苏ICP备15040257号-8

    使用lua+redis解决发多张券的并发问题 使用,lua+redis,解决,发,多张,