Redis的分布式锁

用压测模拟并发 

使用简易工具Apache ab

ab的使用非常简单,只需要在命令行输入

ab -n 100 -c http://www.baidu.com/

-n 表示发出100个请求,-c模拟100个并发,相当于100个人同时方式,最后是测试的url

ab -t 60 -c 100 http://www.baidu.com

-t表示60秒,-c表示100个并发,表示60秒内会发100个请求

添加synchronized处理并发,但是速度会变慢

因为使用了synchronized,它就用一个锁把这个方法给锁住了,而每次访问这个方法的线程只会有一个线程,

这就是导致慢的原因。

总之,添加synchronized是一种解决放,但是无法做到细粒度控制。

假如我们有很多商品,每个商品id不一样,但是他们都会访问这个方法,假如秒杀A商品的人很多,描述B商品的人很少。你一旦进入这个方法的话,都会一样的慢。只适合单点的情况。

-》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》

这就引入我们基于Redis的分布式锁了。

http://redis.cn/commands/setnx.html

http://redis.cn/commands/getset.html

Redis实现分布式锁 很大的原因是因为Redis是单线程的

支持分布式

可以更细粒度的控制

多台机器上多个进程对一个数据进行操作的互斥

秒杀-controller

@RestController
@RequestMapping("/skill")
@Slf4j
public class SeckillController {
    @Autowired
    private SecKillService secKillService;

    /**
     * 查询秒杀的结果
     * @param productId
     * @return
     * @throws Exception
     */
    @GetMapping("/query/{productId}")
    public String query(@PathVariable String productId) throws Exception{
        return secKillService.querySecKillProductInfo(productId);
    }

    /**
     * 秒杀
     * @param productId
     * @return
     * @throws Exception
     */
    @GetMapping("/order/{productId}")
    public String skill(@PathVariable String productId) throws Exception{
        secKillService.orderProductMockDiffUser(productId);
        return secKillService.querySecKillProductInfo(productId);
    }
}

秒杀-service

@Service
@Slf4j
public class SecKillServiceImpl implements SecKillService{

    static Map<String,Integer> products;
    static Map<String,Integer> stock;
    static Map<String,String> orders;
    static {
        products = new HashMap<>();
        stock = new HashMap<>();
        orders = new HashMap<>();
        products.put("123456",10000);
        stock.put("123456",10000);
    }

    private String queryMap(String productId){
        return "国庆活动,皮蛋瘦肉粥特价,限量份"+products.get(productId)
                +",还剩:"+stock.get(productId)
                +";该商品成功下单的用户数量:"+orders.size()+"人";
    }

    @Override
    public String querySecKillProductInfo(String productId) {
        return this.queryMap(productId);
    }

    @Override
    public void orderProductMockDiffUser(String productId) {
        //1、查询库存,为0则活动结束
        int stockNum = stock.get(productId);
        if(stockNum == 0){
            throw  new SellException(100,"活动结束");
        }else {
            //2、下单
            orders.put(KeyUtil.genUniqueKey(),productId);
            //3、减库存
            stockNum = stockNum - 1;
            try {
                Thread.sleep(100);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            stock.put(productId,stockNum);
        }
    }
}

访问

秒杀: http://localhost:8080/sell/skill/order/123456

查询: http://localhost:8080/sell/skill/query/123456

刷新执行秒杀的页面,一切正常即可。也可以查询。

压测工具 apache ab

下载官网: http://httpd.apache.org/
下载步骤: http://blog.csdn.net/ahaaaaa/article/details/51514175

解压,进入bin目录,打开命令行窗口。执行:

ab -n 100 -c 10 http://localhost:8080/sell/skill/order/123456
  • 这里是说连续发送100个请求,10个进程同时执行,执行完毕,查看结果。发现好像没什么问题。

  • 加大压力:

  • ab -n 400 -c 100 http://localhost:8080/sell/skill/order/123456
  • 这个时候就会发现成功下单的人数和剩下的份数之和是不等于总数的(一般是是大于:超卖现象)。

原因是进程多了,请求数多了,这个程序已经打架了,多个进程竞争同一个资源,怎么会不乱呢?

大家会想到用 synchronized 关键字,对其上锁,但是问题是虽然可以保证同一时间只有一个线程在执行任务,但是明显发现速度好慢好慢,对于这种秒杀的场景显然是不适用的。

## redis分布式锁

首先是安装redis 高并发神器 非关系型数据库NoSql之Redis介绍以及Linux环境下的安装

然后启动redis服务端

程序引入依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置:application.yml 

  redis:
    host: 127.0.0.1
    port: 6379

锁:

@Component
@Slf4j
public class RedisLock {
    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 加锁
     * value:当前时间+超时时间
     */
    public Boolean lock(String key, String value){
        if(redisTemplate.opsForValue().setIfAbsent(key,value)){
            return true;
        }
        String currentValue = redisTemplate.opsForValue().get(key);
        /*如果锁过期*/
        if(!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()){
            //获取上一个锁的时间
            String oldValue = redisTemplate.opsForValue().getAndSet(key,value);
            if(!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)){
                return true;
            }
        }
        return false;
    }


    /**
     * 解锁
     */
    public void unlock(String key,String value){
        try {
            String currentValue = redisTemplate.opsForValue().get(key);
            if(!StringUtils.isEmpty(currentValue) && currentValue.equals(value)){
                redisTemplate.opsForValue().getOperations().delete(key);
            }
        } catch (Exception e) {
            log.error("【redis分布式锁解锁异常】:{}",e);
            e.printStackTrace();
        }
    }
}

首先是判断有没有已经存在的key-value,有的话,仍然被锁住。

如果程序中出现异常,无法正常执行解锁操作,会造成死锁的情况。

所以还需要判断超时时间,如果达到超时时间了,就会用 getAndSet 将这个进程放进去,就会被新的线程锁住。

秒杀的那一段程序:

@Override
public void orderProductMockDiffUser(String productId) {
    //加锁
    long time = System.currentTimeMillis() + TIMEOUT;
    if(!redisLock.lock(productId,String.valueOf(time))){
        throw new SellException(101,"哎哟喂,人太多,换个姿势再试试");
    }

    //1、查询库存,为0则活动结束
    int stockNum = stock.get(productId);
    if(stockNum == 0){
        throw new SellException(100,"活动结束");
    }else {
        //2、下单
        orders.put(KeyUtil.genUniqueKey(),productId);
        //3、减库存
        stockNum = stockNum - 1;
        try {
            Thread.sleep(100);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        stock.put(productId,stockNum);
    }

    //解锁
    redisLock.unlock(productId,String.valueOf(time));
}

跟上面一样进行压力测试,速度还是很快的,而且保证不会出现数字的混乱。达到了快速的锁的要求。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值