SpringBoot基于redisson 实现分布式锁

分布式锁主流实现方案和选型

a:基于Redis的分布式锁。使用并发量很大、性能要求很高而可靠性问题可以通过其他方案弥补的场景
b:基于ZooKeeper的分布式锁。适用于高可靠(高可用),而并发量不是太高的场景

在实际生产中,尤其是分布式环境下,因为我们逻辑真正处理的业务数据是只有一份的,接口并发时势必会出现并发问题,使得业务数据不正确,这个时候就需要一种类似于锁的东西来保证数据的幂等性,比如秒杀业务。实现分布式锁的方式非常多,zookeeper、redis、数据库等均可,如果使用redis原生方式来实现的话还是比较复杂的,基于这种场景,我们利用redisson来实现分布式锁。

以redis为例,这里选用功能比较丰富强大的redis客户端redisson来完成,https://github.com/redisson/redisson

一.启动redis server

cd /Users/sunww/Documents/soft/Java/redis-2.8.17

redis-server

二.  spring Boot 集成redisson框架

此处spring Boot 版本为2.2.5

<dependency>
  <groupId>org.redisson</groupId>
  <artifactId>redisson</artifactId>
  <version>3.6.5</version>
</dependency>

在application.properties中添加

#Redis settings
redisson.address=redis://127.0.0.1:6379

三.下单主要逻辑&redisson锁实现源码

创建redisson的配置管理类等,如下:

package com.robinboot.service.redisson;

import org.springframework.beans.factory.annotation.Value;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * redisson配置
 */
@Configuration
public class RedissonConfig {

    @Value("${redisson.address}")
    private String addressUrl; // redisson.address=redis://127.0.0.1:6379

    @Bean
    public RedissonClient getRedisson() throws Exception{
        RedissonClient redisson = null;
        Config config = new Config();
        config.useSingleServer()
                .setAddress(addressUrl);
        redisson = Redisson.create(config);

        System.out.println(redisson.getConfig().toJSON().toString());
        return redisson;
    }
}
package com.robinboot.service.redisson;

import org.redisson.api.RLock;

import java.util.concurrent.TimeUnit;

/**
 * @Auther: TF12778
 * @Date: 2020/7/29 16:23
 * @Description:
 */
public interface RedissonLocker {

    RLock lock(String lockKey);

    RLock lock(String lockKey, long timeout);

    RLock lock(String lockKey, TimeUnit unit, long timeout);

    boolean tryLock(String lockKey, TimeUnit unit, long waitTime, long leaseTime);

    void unlock(String lockKey);

    void unlock(RLock lock);
}
package com.robinboot.service.redisson;

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * @auther: TF12778
 * @date: 2020/7/29 16:23
 * @description:
 */
@Component
public class RedissonLockerImpl implements RedissonLocker {

    @Autowired
    private RedissonClient redissonClient; // RedissonClient已经由配置类生成,这里自动装配即可

    // lock(), 拿不到lock就不罢休,不然线程就一直block
    @Override
    public RLock lock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock();
        return lock;
    }

    // leaseTime为加锁时间,单位为秒
    @Override
    public RLock lock(String lockKey, long leaseTime) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(leaseTime, TimeUnit.SECONDS);
        return null;
    }

    // timeout为加锁时间,时间单位由unit确定
    @Override
    public RLock lock(String lockKey, TimeUnit unit, long timeout) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(timeout, unit);
        return lock;
    }

    @Override
    public boolean tryLock(String lockKey, TimeUnit unit, long waitTime, long leaseTime) {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            return lock.tryLock(waitTime, leaseTime, unit);
        } catch (InterruptedException e) {
            return false;
        }
    }

    @Override
    public void unlock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.unlock();
    }

    @Override
    public void unlock(RLock lock) {
        lock.unlock();
    }
}

这里主要使用了redisson来创建了一个分布式锁来下单,如下:

package com.robinboot.service.facade.impl;

import com.robinboot.facade.RedissionFacadeService;
import com.robinboot.result.Result;
import com.robinboot.service.domain.Stock;
import com.robinboot.service.domain.StockOrder;
import com.robinboot.service.redisson.RedissonLocker;
import com.robinboot.service.service.StockOrderService;
import com.robinboot.service.service.StockService;
import com.robinboot.service.utils.CuratorFrameworkUtils;
import com.robinboot.utils.ServiceException;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.concurrent.TimeUnit;

/**
 * @auther: TF12778
 * @date: 2020/7/29 11:09
 * @description:
 */
@Service("redissionFacadeService")
public class RedissionFacadeServiceImpl implements RedissionFacadeService {

    private static final String LOCK_KEY = "lockswww";

    @Autowired
    StockService stockService;

    @Autowired
    StockOrderService stockOrderService;

    @Autowired
    private RedissonLocker redissonLocker;

    /**
     * 下单步骤:校验库存,扣库存,创建订单,支付
     *
     */
//    @Transactional  此处不需要加事物,否则订单数量超表
    @Override
    public Result<String> saveOrder(int sid) {
        try {
               redissonLocker.lock(LOCK_KEY);
                /**
                 * 1.查库存
                 */
                Stock stock = new Stock();
                stock.setId(sid);
                Stock stockResult = stockService.selectDetail(stock);
                if (stockResult == null || stockResult.getCount() <= 0 || stockResult.getSale() == stockResult.getCount()) {
                    throw new ServiceException("库存不足", "500");
                }

                /**
                 * 2.根据查询出来的库存,更新已卖库存数量
                 */
                int count = stockService.updateStock(stockResult);
                if (count == 0){
                    throw new ServiceException("库存为0", "500");
                }

                /**
                 * 3.创建订单
                 */
                StockOrder order = new StockOrder();
                order.setSid(stockResult.getId());
                order.setName(stockResult.getName());
                int id = stockOrderService.saveStockOrder(order);
                if (id > 0) {
                    return  new Result<String>("success", "下单成功", "0", null, "200" );
                }
                return  new Result<String>("error", "下单失败", "0", null, "500" );

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            redissonLocker.unlock(LOCK_KEY); // 释放锁
        }

        return  new Result<String>("error", "下单失败", "0", null, "500" );
    }
}
package com.robinbootweb.dmo.controller;

import com.robinboot.facade.CuratorFacadeService;
import com.robinboot.facade.RedissionFacadeService;
import com.robinboot.result.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

/**
 * @auther: TF12778
 * @date: 2020/7/29 11:07
 * @description:
 */
@RestController
@RequestMapping("/redission")
public class RedissionController {

    @Autowired
    RedissionFacadeService redissionFacadeService;

    /**
     * http://localhost:8090/robinBootApi/redission/saveOrder
     * @param
     * @return
     */
    @ResponseBody
    @RequestMapping(value = "/saveOrder", method = RequestMethod.GET)
    public Result<String> saveOrder() {

        Result<String> result =  redissionFacadeService.saveOrder(1);
        return result;
    }
}

四. 启动jmeter测试工具

通过命令jmeter启动成功

五. 初始化数据库信息

CREATE TABLE `stock` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL DEFAULT '' COMMENT '名称',
  `count` int(11) NOT NULL COMMENT '库存',
  `sale` int(11) NOT NULL COMMENT '已售',
  `version` int(11) NOT NULL COMMENT '乐观锁,版本号',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8
 
CREATE TABLE `stock_order` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `sid` int(11) NOT NULL COMMENT '库存ID',
  `name` varchar(30) NOT NULL DEFAULT '' COMMENT '商品名称',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4868 DEFAULT CHARSET=utf8

 六.高并发,发起测试

 这里我们模拟200个用户来抢iphone手机这个操作,如下:

下面是发起200个请求的返回结果

1. 当库存充足时,可以看到下单成功了,如下界面

 2. 当库存不足时,可以看到下单失败了,如下界面

 查看数据库信息,可以看到我们的100个iphone手机全部都卖完了(sale=100)

 查看订单表,可以看到下了100个iphone订单,没有出现超卖的情况,说明加锁是成功的。

猜你喜欢

转载自blog.csdn.net/robinson_911/article/details/107670447