8、redis分布式锁

来源:互联网 发布:java购物系统 编辑:程序博客网 时间:2024/06/06 21:14

做一个秒杀的案例

1、秒杀的程序-controller

@RestController@RequestMapping("/skill")@Slf4jpublic 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);    }}

2、秒杀的程序-service

@Service@Slf4jpublic 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);        }    }}

3、访问

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

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

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

4、压测工具

  • 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 关键字,对其上锁,但是问题是虽然可以保证同一时间只有一个线程在执行任务,但是明显发现速度好慢好慢,对于这种秒杀的场景显然是不适用的。

5、redis分布式锁

首先是安装redis(略)

然后启动redis服务端

程序引入依赖:

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

配置:

  redis:    host: 127.0.0.1    port: 6379

锁:

@Component@Slf4jpublic 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 将这个进程放进去,就会被新的线程锁住。

秒杀的那一段程序:

@Overridepublic 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));}

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

原创粉丝点击