redis lua腳本redis事務(wù)實(shí)現(xiàn) 商品秒殺活動案例

redis lua腳本redis事務(wù)實(shí)現(xiàn) 商品秒殺活動案例

1. 前言

redis 利用單線程 IO多路復(fù)用 實(shí)現(xiàn)了 單命令操作的原子性,但是多個命令的操作就不具備原子性。
不過可以利用redis 事務(wù) 或者 lua腳本 來實(shí)現(xiàn) 多命令操作的原子性。
本文試圖通過模擬商品秒殺活動,演示怎么實(shí)現(xiàn)redis多命令操作具有原子性。
用到的工具: spring boot ,redis template,lua腳本。

2 準(zhǔn)備工作

2.1 配置redis Template

參照 Spring Boot 整合 RedisCache,EhCache,GuavaCache實(shí)戰(zhàn)中reids配置方式。

2.2 定義接口

public interface GoodsService {
   /**
     * 通過lua腳本實(shí)現(xiàn)的秒殺
     * @param skuCode 商品編碼
     * @param buyNum 購買數(shù)量
     * @return 購買數(shù)量
     */
    Long flashSellByLuaScript(String skuCode,int buyNum);
    /**
     * 通過redis 事務(wù) 實(shí)現(xiàn)的秒殺
     * @param skuCode 商品編碼
     * @param buyNum 購買數(shù)量
     * @return 購買數(shù)量
     */
    Long flashSellByRedisWatch(String skuCode,int buyNum);
}

3. redis 事務(wù)方式

  1. redisTemplate.excute(SessionCallback sessionCallback) 是執(zhí)行事務(wù)的api
  2. 所以要實(shí)現(xiàn)SessionCallback 來實(shí)現(xiàn)redis 事務(wù)。
  3. 如果直接 通過redisTemplate 執(zhí)行事務(wù)命令 會報redis.clients.jedis.exceptions.JedisDataException: Cannot use Jedis when in Multi. Please use Transation or reset jedis state
    異常。
public class GoodsServiceImpl implements GoodsService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
 @Override
    public Long flashSellByRedisWatch(String skuCode,int num){
    
        SessionCallback<Long> sessionCallback = new SessionCallback<Long>() {
            @Override
            public Long execute(RedisOperations operations) throws DataAccessException {
                int result = num;
                //redis 樂觀鎖
                operations.watch(skuCode);
                ValueOperations<String, String> valueOperations = operations.opsForValue();
                String goodsNumStr = valueOperations.get(skuCode);
                Integer goodsNum = Integer.valueOf(goodsNumStr);
                //標(biāo)記一個事務(wù)塊的開始。
               //事務(wù)塊內(nèi)的多條命令會按照先后順序被放進(jìn)一個隊(duì)列當(dāng)中,
               //最后由 EXEC 命令原子性(atomic)地執(zhí)行。
                operations.multi();
                if (goodsNum >= num) {
                    valueOperations.increment(skuCode, 0 - num);
                } else {
                    result = 0;
                }
                //多條命令執(zhí)行的結(jié)果集合
                List exec = operations.exec();
                if(exec.size()>0){
                    System.out.println(exec);
                }
                return (long) result;
            }
        };
       return stringRedisTemplate.execute(sessionCallback);
    }
//省略 其他的方法
}

4. lua腳本方式

  1. 通過redis eval命令 執(zhí)行一個lua腳本
  2. 如果不明白 eval命令 閱讀 redis eval 命令詳解

4.1 編寫lua腳本

lua腳本是配置在 application.properties中的lua.flashSaleScript=

local buyNum = ARGV[1]
local goodsKey = KEYS[1]  
local goodsNum = redis.call('get',goodsKey) 
if goodsNum >= buyNum 
then redis.call('decrby',goodsKey,buyNum) 
return buyNum 
else 
return '0'
end

4.2 flashSellByLuaScript實(shí)現(xiàn)代碼

@Service
public class GoodsServiceImpl implements GoodsService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private LuaScript luaScript;
    @Override
    public Long flashSellByLuaScript(String skuCode,int num) {
        DefaultRedisScript<String> longDefaultRedisScript = new DefaultRedisScript<>(luaScript.flashSaleScript, String.class);
        String result = stringRedisTemplate.execute(longDefaultRedisScript, Collections.singletonList(skuCode),String.valueOf(num));
        return Long.valueOf(result);
    }
    //省略 其他的方法

5. linux ab 壓力測試

  1. 提前在redis 中 存儲 貨品編碼為 0001 的貨品 ,庫存數(shù)量為 500.
  2. 通過 linux ab 壓力測試 進(jìn)行測試 。
  3. 分別對兩種實(shí)現(xiàn)方式 進(jìn)行各1000次的訪問,并發(fā)數(shù)為 100。
  4. 每次請求只購買一個商品

5.1 ab命令

  • shell
ab -n1000 -c100 -p data.json -T application/json http://192.168.33.222:8881/spring-boot/testFlashSell
  • data.json
{
'skuCode':'0001',
'num':1
}

5.2 redis 事務(wù)方式執(zhí)行后

  • 執(zhí)行一次 測試后,商品還剩余 369個。
  • 執(zhí)行第二次后,商品還剩余 237個。
  • 最后共執(zhí)行四次,就是共4000次請求后 商品數(shù)量才為0。
  • 這個跟redis watch 的樂觀鎖有關(guān)。因?yàn)椴皇敲看握埱蠖寄艹晒Α?/li>
  • 這種方式有可能會使本來可以賣完的商品 賣不完,或者需要更多的時間 才能售賣完。
  • 好在 并沒有出現(xiàn)負(fù)數(shù)。
192.168.26.230:14>get 0001 
"0"

5.3 測試lua腳本方案

  • 執(zhí)行一次后,商品就已經(jīng)為0.同樣也沒有出現(xiàn)負(fù)數(shù)。

6.總結(jié)

我比較喜歡lua腳本的實(shí)現(xiàn)方式。對于程序開發(fā)者而言, 學(xué)習(xí)lua腳本很簡單,稍微看下就可以編寫用于redis 的lua腳本。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容