Java秒殺系統實戰系列~基於Redis的原子操做優化秒殺邏輯

摘要:

本篇博文是「Java秒殺系統實戰系列文章」的第十四篇,本文將藉助緩存中間件Redis的「單線程」特性及其原子操做一同優化「秒殺系統中秒殺的核心業務邏輯」,完全初步解決「庫存超賣」、「重複秒殺」等問題。git

內容:

對於緩存中間件Redis,相信各位小夥伴或多或少都有據說過,甚至實戰過,本文咱們將基於SpringBoot整合Redis中間件,並基於其優秀的「單線程」特性和原子操做實現一種「分佈式鎖」,進而控制「高併發狀況下多線程對於共享資源的訪問」,最終解決「併發安全」,即「庫存超賣」或者「重複秒殺」的問題!redis

(1)按照慣例,首先咱們須要加入Redis的第三方依賴,以下所示:spring

<!-- redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-redis</artifactId>
    <version>1.3.5.RELEASE</version>
</dependency>複製代碼

而後,須要在application.properties配置文件中加入Redis服務所在的Host、端口Post、連接密鑰Password等信息,以下所示:
數據庫

(2)緊接着,咱們還須要自定義注入跟Redis的操做組件相關的Bean配置,在這裏主要是自定義注入配置RedisTemplate跟StringRedisTemplate操做組件,並指定其對應的Key、Value的序列化策略:
緩存

// redis的通用化配置
@Configuration
public class RedisConfig {
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Bean
    public RedisTemplate<String,Object> redisTemplate(){
        RedisTemplate<String,Object> redisTemplate=new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //TODO:指定Key、Value的序列化策略
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());

        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }

    @Bean
    public StringRedisTemplate stringRedisTemplate(){
        StringRedisTemplate stringRedisTemplate=new StringRedisTemplate();
        stringRedisTemplate.setConnectionFactory(redisConnectionFactory);
        return stringRedisTemplate;
    }
}複製代碼

(3)至此,能夠說是作好了充足的準備,接下來咱們就能夠拿來用了!爲了區分以前的秒殺邏輯方法,咱們開了一個新的秒殺邏輯方法killItemV3,並採用Redis的原子操做SETNX和EXPIRE方法來實現一種「分佈式鎖」,進而控制高併發多線程對共享資源的訪問,其完整源代碼以下所示:
安全

//商品秒殺核心業務邏輯的處理-redis的分佈式鎖
@Override
public Boolean killItemV3(Integer killId, Integer userId) throws Exception {
    Boolean result=false;

    if (itemKillSuccessMapper.countByKillUserId(killId,userId) <= 0){

        //TODO:藉助Redis的原子操做實現分佈式鎖-對共享操做-資源進行控制
        ValueOperations valueOperations=stringRedisTemplate.opsForValue();
        final String key=new StringBuffer().append(killId).append(userId).append("-RedisLock").toString();
        final String value=RandomUtil.generateOrderCode();
        Boolean cacheRes=valueOperations.setIfAbsent(key,value); 
        if (cacheRes){
            stringRedisTemplate.expire(key,30, TimeUnit.SECONDS);

            try {
                ItemKill itemKill=itemKillMapper.selectByIdV2(killId);
                if (itemKill!=null && 1==itemKill.getCanKill() && itemKill.getTotal()>0){
                    int res=itemKillMapper.updateKillItemV2(killId);
                    if (res>0){
                        commonRecordKillSuccessInfo(itemKill,userId);

                        result=true;
                    }
                }
            }catch (Exception e){
                throw new Exception("還沒到搶購日期、已過了搶購時間或已被搶購完畢!");
            }finally {
                if (value.equals(valueOperations.get(key).toString())){
                    stringRedisTemplate.delete(key);
                }
            }
        }
    }else{
        throw new Exception("Redis-您已經搶購過該商品了!");
    }
    return result;
}複製代碼

  在上述代碼中,咱們主要是經過如下幾個操做綜合實現了「分佈式鎖」的功能,其中包括bash

(1)valueOperations.setIfAbsent(key,value);:表示當前的Key若是不存在於緩存中,那麼將設置值成功,反之,若是Key已經存在於緩存中了,那麼設置值將不成功!經過這一特性,咱們能夠將「KillId和UserId的一一對應關係~即一我的只能搶到一個商品」組合在一塊兒做爲Key!微信

(2)設置了Key,那麼就須要在某個時間點去釋放,即stringRedisTemplate.expire(key,30, TimeUnit.SECONDS);操做能夠輔助實現!多線程

(3)然鵝,真正「釋放鎖」的操做是以下這段代碼去實現的,即判斷一下當前要釋放的鎖是否就是以前一開始獲取到的鎖,若是是,就釋放!這一點能夠很好的避免誤刪鎖的問題!併發

if (value.equals(valueOperations.get(key).toString())){
    stringRedisTemplate.delete(key);
}複製代碼

至此,基於Redis的原子操做實現的分佈式鎖,進而控制高併發多線程對於共享資源的訪問,從而解決秒殺場景下「庫存超賣」、「重複秒殺」等問題,下面採用JMeter進行壓測,壓測的用戶列表跟商品的「可秒殺數量total」跟上一篇章是同樣的,即total=6本書,用戶總共是10個。

點擊JMeter的啓動按鈕,觀察控制檯的輸出信息以及數據庫表item_kill和item_kill_success表,能夠看到秒殺記錄的結果非常使人滿意:


即庫存爲6本的商品~書籍剛好被10個用戶中的6個秒殺獲得!這種結果其實對於咱們、對於用戶來說確定是皆大歡喜的結局!

雖然演員對於本身的結局很滿意,可是導演卻察覺到戲中仍然有一些瑕疵!即若是秒殺系統在執行Redis的原子操做SetNX後、執行Expire以前,Redis的節點宕機了,那麼此時將頗有可能永久進入「Key鎖死」的窘境,即重啓以後,因爲以前的Key沒有獲得釋放,故而這個Key將永遠存在於緩存中,即對應的用戶將不能秒殺該商品了!這一點確實是一個隱患!

既然存在隱患,那麼咱們就得想辦法解決了!莫急,下一篇章咱們繼續!

補充:

一、目前,這一秒殺系統的總體構建與代碼實戰已經所有完成了,完整的源代碼數據庫地址能夠來這裏下載:gitee.com/steadyjack/… 記得Fork跟Star啊!!

二、最後,不要忘記了關注一下Debug的技術微信公衆號:

相關文章
相關標籤/搜索