使用lua+redis解決發(fā)多張券的并發(fā)問題
前言
公司有一個(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) // ... // 查出券碼 List<CouponCode> 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 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(); //加載后重新執(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)文章希望大家以后多多支持腳本之家!
相關(guān)文章
高并發(fā)場景分析之redis+lua防重校驗(yàn)
這篇文章主要介紹了高并發(fā)場景分析之redis+lua防重校驗(yàn),本文通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07Java實(shí)現(xiàn)多級(jí)緩存的方法詳解
對(duì)于高并發(fā)系統(tǒng)來說,有三個(gè)重要的機(jī)制來保障其高效運(yùn)行,它們分別是:緩存、限流和熔斷,所以本文就來和大家探討一下多級(jí)緩存的實(shí)現(xiàn)方法,希望對(duì)大家有所幫助2024-02-02redis數(shù)據(jù)類型及應(yīng)用場景知識(shí)點(diǎn)總結(jié)
在本篇文章里小編給大家整理的是關(guān)于2020-02-02基于Redis延遲隊(duì)列的實(shí)現(xiàn)代碼
在生活中很多時(shí)候都會(huì)用到延遲隊(duì)列,本文基于Redis延遲隊(duì)列的實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-05-05Redis特殊數(shù)據(jù)類型HyperLogLog基數(shù)統(tǒng)計(jì)算法講解
這篇文章主要為大家介紹了Redis特殊數(shù)據(jù)類型HyperLogLog基數(shù)統(tǒng)計(jì)算法講解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05Redis中3種特殊的數(shù)據(jù)類型(BitMap、Geo和HyperLogLog)
這篇文章主要給大家介紹了關(guān)于Redis中3種特殊的數(shù)據(jù)類型(BitMap、GEOADD和GEODIST)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-03-03一文詳解如何使用Redis實(shí)現(xiàn)分布式鎖
這篇文章主要介紹了一文詳解如何使用Redis實(shí)現(xiàn)分布式鎖,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09