redis分散ロックを使用してQPSテストを発行し、単機で1秒に1千個の注文を下す.


前の記事では、同時注文時に最適化するためのいくつかの戦略について説明しましたが、コードを書いて実測しました.
redissonが分散ロックをするコードについてこの文章で説明します.ここでは、分散ロックのパフォーマンスをテストします.
シンプルコントロール
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に対して分散ロックを行い、1つの商品しかないと、その商品の販売に完全に並ぶことになり、性能はredisのロック解除と業務時間のかかるものになる.業務面では直接注文をredisに下す方式を採用しており、データベースを行かない.通常、業務にかかる時間は10 ms程度が頼りです.
sellMemoryメソッドは,メモリ内で商品数の減少を完全に行い,redisのネットワーク通信から抜け出すことである.ビジネスでもredisに注文をします.
redisの1回のロック、ロックを解除して、私达はその时间を计算することができて、ロックは1回の通信で、ロックに失败した后に并んで、それからredisのChannelがコールバックを傍受することを待って、コールバックはこれはまた1回の通信で、コールバックの后で再びロックをプラスして成功することを试みて、これはまた1回の通信で、业务の上でredisの中で次の単で、これはまた1回の通信です.計4回.
そうすると、業務時間が同じ場合、メモリ内で在庫を直接差し引くのはredis分散ロックを通過するよりも4回少ないネットワークIOになります.
結果は次のとおりです.
redis分散ロックで注文
200スレッド同時、1サイクル、1商品購入、同じgoodsIdにロック.合計600 ms、200個の注文が完了しました.
200スレッド同時、2サイクル、1商品購入、同じgoodsIdにロック.合計1100 ms、400個の注文が完了しました.
200スレッド同時、2サイクル、5つの商品を購入し、異なるgoodsIdにロックし、性能が明らかに向上した.合計600 ms、400個の注文が完了しました.
400スレッド同時、1サイクル、5商品購入、異なるgoodsIdにロック.合計600 ms、400個の注文が完了しました.
400スレッド同時、2サイクル、5商品購入、異なるgoodsIdにロック.合計1000 ms、800個の注文が完了しました.
メモリで注文する
200スレッド同時、1サイクル、1商品購入、同じgoodsIdにロック.合計300 ms、200個の注文が完了しました.
200スレッド同時、2サイクル、1商品購入、同じgoodsIdにロック.合計400 ms、400単注文完了.
200スレッド同時、1サイクル、5商品購入、異なるgoodsIdにロック.合計300 ms、200個の注文が完了しました.
200スレッド同時、2サイクル、5商品購入、異なるgoodsIdにロック.合計400 ms、400単注文完了.
400スレッド同時、1サイクル、5商品購入、異なるgoodsIdにロック.合計500 ms、400個の注文が完了しました.
400スレッド同時、2サイクル、5商品購入、異なるgoodsIdにロック.合計700 ms、800個の注文が完了しました.
注意、このテストはHaProxyの後ろで直接行うserverで、zulゲートウェイを通過していません.メモリで注文すると、redisロックの約2倍の速度が表示されます.同時高の場合、差はさらに大きくなります.
ビジネスの時間がかかる場合、redis分散ロックは同じ商品にロックされ、毎秒約100個を完了することができます.メモリベースでは、単一インスタンスは1千個まで下がることができます.
メモリベースでは、分散型構成センターを通じて、プロジェクトが開始された後、商品数countに初期値を付与する必要があります.例えば1万個の商品、10個の例では、1個につき1千個が割り当てられる.
インスタンス間でロックの競合は発生せず、casベースの在庫削減でパフォーマンスに優れています.