使用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的减库存,性能优异。

 

 

你可能感兴趣的:(架构,redis)