一. 場景
下單后庫存校驗(yàn)或者秒殺場景下侯谁,有很多利用“鎖”的方案來解決問題噩茄。但是加鎖其實(shí)是一件性價(jià)比很低的事棺亭,所以我們采用用redis+lua的方式來實(shí)現(xiàn)這個(gè)功能湾戳。
二. 思路
階段一:
在庫存加減邏輯中分為2個(gè)步驟:STEP1.讀取庫存徽龟,STEP2.讀取庫存叮姑。
利用其他方法例如"鎖"等,也就是想控制好STEP2一定要緊跟STEP1,本質(zhì)上就是確保獲取的庫存的最新的數(shù)據(jù)為最新据悔。
階段二:
在相對較高的并發(fā)場景下传透,redis被常用作庫存管理,我們需要通過最小成本的改動(dòng)來實(shí)現(xiàn)庫存的限制极颓。但是redis的讀取庫存+讀取庫存一般都是有上層應(yīng)用代碼控制朱盐,有沒有辦法在一個(gè)函數(shù)調(diào)用中能串行執(zhí)行這倆個(gè)步驟了?
階段三:
眾所周知菠隆,redis是單線程的兵琳,并且現(xiàn)在已經(jīng)支持lua腳本,那是不是可以利用該組合實(shí)現(xiàn)我們的場景了骇径?
三. 設(shè)計(jì)方案
這個(gè)方案就很簡單了躯肌,直接利用redistemplate執(zhí)行l(wèi)ua腳本
四. 代碼
4.1 lua腳本代碼樣例
private static final String GET_COUPON_CODE =
"local values = redis.call('hmget',KEYS[1],'recvCnt','couponCnt');\n" + //lua返回value的數(shù)組
"if tonumber(values[1]) < tonumber(values[2]) then \n" + //lua的數(shù)組索引從1開始 values[1] = recvCnt,values[2] = couponCnt
" redis.call('hincrby',KEYS[1],'recvCnt',1);\n" +
" return true;\n" +
"else\n " +
" return false;\n" +
"end\n";
4.2 redis調(diào)用lua腳本
//執(zhí)行調(diào)用
execute(GET_COUPON_CODE, keys);
//此處將數(shù)值類型轉(zhuǎn)化為Long
public Long execute(String redisScript,List<String> keys){
RedisScript<Long> REDIS_SCRIPT = new DefaultRedisScript<>(redisScript, Long.class);
return redisTemplate.execute(REDIS_SCRIPT,keys);
}
4.3 lua基本用法
redis.call()
redis.pcall()
call與pcall基本上一樣。腳本報(bào)錯(cuò)時(shí)破衔,call會(huì)直接報(bào)錯(cuò)清女,pcall不會(huì)報(bào)錯(cuò),會(huì)把錯(cuò)誤信息放到lua table 的err字段中晰筛。
五. 說明
1.記得利用“\n”分行嫡丙,也可以利用string的append拼接
2.values[n]對應(yīng)有序數(shù)組keys拴袭,需要控制好各數(shù)據(jù)順序
3.lua的數(shù)組索引從1開始
六. 總結(jié)
我們先分析場景,通過多種方案對比曙博,選用了redis+lua的組合來滿足我們的業(yè)務(wù)需要拥刻。利用redis單線程的特點(diǎn),以及redis2.6版本后開始對lua的支持父泳,我們采用redis執(zhí)行l(wèi)ua腳本來確保我們查詢+修改的串行執(zhí)行泰佳。后面我們展示了code的實(shí)現(xiàn)案例,以及介紹了lua腳本的一些注意事項(xiàng)尘吗,可以依葫蘆畫瓢形式自己實(shí)現(xiàn)自己的需求逝她。綜合而言,我們分析場景應(yīng)先分析其核心問題睬捶,然后利用一些更簡潔的方法或小技巧來落地黔宛。