濮阳杆衣贸易有限公司

主頁 > 知識(shí)庫 > 使用lua+redis解決發(fā)多張券的并發(fā)問題

使用lua+redis解決發(fā)多張券的并發(fā)問題

熱門標(biāo)簽:b2b外呼系統(tǒng) 高碑店市地圖標(biāo)注app 南京手機(jī)外呼系統(tǒng)廠家 一個(gè)地圖標(biāo)注多少錢 400電話辦理的口碑 臺(tái)灣電銷 四川穩(wěn)定外呼系統(tǒng)軟件 廊坊外呼系統(tǒng)在哪買 地圖標(biāo)注工廠入駐

前言

公司有一個(gè)發(fā)券的接口有并發(fā)安全問題,下面列出這個(gè)問題和解決這個(gè)問題的方式。

業(yè)務(wù)描述

這個(gè)接口的作用是給會(huì)員發(fā)多張券碼。涉及到4張主體,分別是:用戶,券,券碼,用戶領(lǐng)取記錄。
下面是改造前的偽代碼。
主要是因?yàn)椴槌鋈a那行存在并發(fā)安全問題,多個(gè)線程拿到同幾個(gè)券碼。以下都是基于如何讓取券碼變成原子的去展開。

public boolean sendCoupons(Long userId, Long couponId) {
 // 一堆校驗(yàn)
 // ...
 // 查出券碼
 ListCouponCode> couponCodes = couponCodeService.findByCouponId(couponId, num);
 // batchUpdateStatus是一個(gè)被@Transactional(propagation = Propagation.REQUIRES_NEW)修飾的方法
 // 批量更新為已被領(lǐng)取狀態(tài)
 couponCodeService.batchUpdateStatus(couponCods);
 // 發(fā)券
 // 發(fā)權(quán)益
 // 新增用戶券碼領(lǐng)取記錄
}

改造過程

因?yàn)槿a是多張,想用lua+redis的list結(jié)構(gòu)去做彈出。為什么用這種方案是因?yàn)閒or update直接被否了。

這是寫的lua腳本。。

local result = {}
for i=1,ARGV[1],1 do
 result[i] = redis.call("lpop", KEYS[1])
end
return table.contact(result , "|")

這是寫的執(zhí)行l(wèi)ua腳本的client。。其實(shí)主要的解決方法就是在redis的list里rpush(存),lpop(?。┤?shù)據(jù)

@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();
   //加載后重新執(zhí)行
   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();
   }
  }
 }

 /**
  * 構(gòu)建Key
  *
  * @param activityId
  * @return
  */
 public String buildKey(String activityId) {
  return MessageFormat.format(COUPON_CODE_KEY_PATTERN, activityId);
 }

}

當(dāng)然這種操作需要去提前把所有券的券碼丟到redis里去,這里我們也碰到了一些問題(券碼量比較大的情況下)。比如開始直接粗暴的用@PostConstruct去放入redis,導(dǎo)致項(xiàng)目啟動(dòng)需要很久很久。。這里就不展開了,說一下我們嘗試的幾種方法

  • @PostConstruct注解
  • CommandLineRunner接口
  • redis的pipeline技術(shù)
  • 先保證每個(gè)卡券有一定量的券碼在redis,再用定時(shí)任務(wù)定時(shí)(根據(jù)業(yè)務(wù)量)去補(bǔ)

到此這篇關(guān)于使用lua+redis解決發(fā)多張券的并發(fā)問題的文章就介紹到這了,更多相關(guān)redis多張券的并發(fā)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

您可能感興趣的文章:
  • Redis實(shí)現(xiàn)分布式Session管理的機(jī)制詳解
  • kubernetes環(huán)境部署單節(jié)點(diǎn)redis數(shù)據(jù)庫的方法
  • 銀河麒麟V10sp1服務(wù)器系統(tǒng)安裝redis不能使用的快速解決辦法
  • 使用docker搭建redis主從的方法步驟
  • 基于redis setIfAbsent的使用說明

標(biāo)簽:南寧 定州 伊春 拉薩 甘南 泰州 畢節(jié) 河源

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《使用lua+redis解決發(fā)多張券的并發(fā)問題》,本文關(guān)鍵詞  使用,lua+redis,解決,發(fā),多張,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。
  • 相關(guān)文章
  • 下面列出與本文章《使用lua+redis解決發(fā)多張券的并發(fā)問題》相關(guān)的同類信息!
  • 本頁收集關(guān)于使用lua+redis解決發(fā)多張券的并發(fā)問題的相關(guān)信息資訊供網(wǎng)民參考!
  • 推薦文章
    新平| 南平市| 兖州市| 林周县| 西和县| 蒲城县| 泾源县| 喜德县| 长兴县| 长岭县| 眉山市| 射阳县| 思南县| 台州市| 罗山县| 定边县| 神池县| 岳阳市| 资中县| 阿克陶县| 闽清县| 开化县| 章丘市| 喀什市| 教育| 苏尼特右旗| 肥城市| 泰安市| 和林格尔县| 铁力市| 旌德县| 镇坪县| 滨州市| 北安市| 剑阁县| 淮南市| 新竹县| 栾城县| 平阳县| 中西区| 宜川县|