使用redis分佈式鎖高併發下QPS測試,單機一秒下1千個訂單

前面一篇講過併發下單時進行優化的一些策略,後來我寫了代碼進行了實測。

關於redisson做分佈式鎖的代碼在這篇文章。這裏我來測試一下分佈式鎖的性能。

簡單的controller

package com.tianyalei.redislock.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author wuweifeng wrote on 2019-10-15.
 */
@RequestMapping("/redisLock")
@RestController
public class IndexController {
    @Resource
    private IndexService indexService;

    private volatile AtomicInteger index = new AtomicInteger(Integer.MAX_VALUE);


    @RequestMapping("random")
    public String sellTwo(int count) {
        int i = index.getAndDecrement() % count;
        if (i == 0) {
            indexService.sell(0);
        } else if (i == 1) {
            indexService.sell(1);
        } else if (i == 2) {
            indexService.sell(2);
        } else if (i == 3) {
            indexService.sell(3);
        } else if (i == 4) {
            indexService.sell(4);
        }

        return "ok";
    }

    @RequestMapping("mm")
    public String sellmm(int count) {
        int i = index.getAndDecrement() % count;
        if (i == 0) {
            indexService.sellMemory(0);
        } else if (i == 1) {
            indexService.sellMemory(1);
        } else if (i == 2) {
            indexService.sellMemory(2);
        } else if (i == 3) {
            indexService.sellMemory(3);
        } else if (i == 4) {
            indexService.sellMemory(4);
        }

        return "ok";
    }
}

定義了2個接口,上面的是用redis分佈式鎖,下面的是走內存。

package com.tianyalei.redislock.controller;

import com.tianyalei.redislock.annotation.RedissonLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author wuweifeng wrote on 2019-10-15.
 */
@Service
public class IndexService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    private Logger logger = LoggerFactory.getLogger(getClass());

    private volatile AtomicInteger count1 = new AtomicInteger(500000);
    private volatile AtomicInteger count2 = new AtomicInteger(500000);
    private volatile AtomicInteger count3 = new AtomicInteger(500000);
    private volatile AtomicInteger count4 = new AtomicInteger(500000);
    private volatile AtomicInteger count5 = new AtomicInteger(500000);


    /**
     * 上分佈式鎖
     */
    @RedissonLock(lockIndex = 0)
    public void sell(Integer goodsId) {
        logger.info("goodId:" + goodsId);
        stringRedisTemplate.opsForValue().set(getRandomString(8), System.currentTimeMillis() + "abc");
        logger.info(System.currentTimeMillis() + "");
    }

    /**
     * 直接讀內存
     */
    public void sellMemory(Integer goodsId) {
        logger.info("goodId:" + goodsId);
        switch (goodsId) {
            case 0:
                logger.info("count1:" + count1.getAndDecrement());
                break;
            case 2:
                logger.info("count2:" + count2.getAndDecrement());
                break;
            case 3:
                logger.info("count3:" + count3.getAndDecrement());
                break;
            case 4:
                logger.info("count4:" + count4.getAndDecrement());
                break;
            default:
                logger.info("count5:" + count5.getAndDecrement());
                break;
        }

        stringRedisTemplate.opsForValue().set(getRandomString(8), System.currentTimeMillis() + "abc");
        logger.info(System.currentTimeMillis() + "");
    }

    public static String getRandomString(int length) {
        String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < length; i++) {
            int number = random.nextInt(62);
            sb.append(str.charAt(number));
        }
        return sb.toString();
    }
}

關於RedissonLock註解在前面的文章裏寫了代碼了。

sell方法就是針對goodsId進行加分佈式鎖,如果只有一個商品的話,就等於是完全在該商品的售賣上進行排隊,性能就全靠redis的加鎖解鎖和業務耗時了。業務方面我採用直接將訂單下到redis裏的方式,不走數據庫。正常情況下,業務耗時在10ms左右比較靠譜。

sellMemory方法就是完全在內存中進行商品數量的減少,擺脫掉redis的網絡通信。業務上也是將訂單下到redis中。

redis的一次加鎖、解鎖,我們可以計算它的耗時,加鎖是一次通信,加鎖失敗後排隊,然後等待redis的Channel監聽回調,回調這又是一次通信,回調後再次去試圖加鎖併成功,這又是一次通信,業務上在redis裏下一單,這又是一次通信。共計4次。

那麼在業務耗時相同的情況下,在內存中直接扣減庫存會比通過redis分佈式鎖,少4次網絡IO。

結果如下:

通過redis分佈式鎖下單

200個線程併發,循環1次,購買1個商品,鎖在同一個goodsId。共計600ms,200單下單完畢。

200個線程併發,循環2次,購買1個商品,鎖在同一個goodsId。共計1100ms,400單下單完畢。

200個線程併發,循環2次,購買5個商品,鎖在不同的goodsId,性能明顯有所提升。共計600ms,400單下單完畢。

400個線程併發,循環1次,購買5個商品,鎖在不同的goodsId。共計600ms,400單下單完畢。

400個線程併發,循環2次,購買5個商品,鎖在不同的goodsId。共計1000ms,800單下單完畢。

在內存裏下單

200個線程併發,循環1次,購買1個商品,鎖在同一個goodsId。共計300ms,200單下單完畢。

200個線程併發,循環2次,購買1個商品,鎖在同一個goodsId。共計400ms,400單下單完畢。

200個線程併發,循環1次,購買5個商品,鎖在不同的goodsId。共計300ms,200單下單完畢。

200個線程併發,循環2次,購買5個商品,鎖在不同的goodsId。共計400ms,400單下單完畢。

400個線程併發,循環1次,購買5個商品,鎖在不同的goodsId。共計500ms,400單下單完畢。

400個線程併發,循環2次,購買5個商品,鎖在不同的goodsId。共計700ms,800單下單完畢。

注意,這個測試是直接在HaProxy後面做的server,沒有通過zuul網關。可以看到,在內存中下單,速度約是redis上鎖的2倍。在併發高的情況下,差距會更大。

當業務耗時更大的情況下,redis分佈式鎖在同一個商品上加鎖,每秒大概能完成100單。而基於內存的話,單實例能下到1千單。

基於內存,那麼就需要通過分佈式配置中心,在項目啓動後,給商品數量count賦初值。譬如1萬個商品,10個實例,則每個分配1千。

實例之間彼此不會發生鎖的衝突,基於cas的減庫存,性能優異。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章