a:基于Redis的分布式锁。使用并发量很大、性能要求很高而可靠性问题可以通过其他方案弥补的场景
b:基于ZooKeeper的分布式锁。适用于高可靠(高可用),而并发量不是太高的场景
在实际生产中,尤其是分布式环境下,因为我们逻辑真正处理的业务数据是只有一份的,接口并发时势必会出现并发问题,使得业务数据不正确,这个时候就需要一种类似于锁的东西来保证数据的幂等性,比如秒杀业务。实现分布式锁的方式非常多,zookeeper、redis、数据库等均可,如果使用redis原生方式来实现的话还是比较复杂的,基于这种场景,我们利用redisson来实现分布式锁。
以redis为例,这里选用功能比较丰富强大的redis客户端redisson来完成,https://github.com/redisson/redisson。
cd /Users/sunww/Documents/soft/Java/redis-2.8.17
redis-server
此处spring Boot 版本为2.2.5
org.redisson
redisson
3.6.5
在application.properties中添加
#Redis settings
redisson.address=redis://127.0.0.1:6379
创建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 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("success", "下单成功", "0", null, "200" );
}
return new Result("error", "下单失败", "0", null, "500" );
} catch (Exception e) {
e.printStackTrace();
} finally {
redissonLocker.unlock(LOCK_KEY); // 释放锁
}
return new Result("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 saveOrder() {
Result result = redissionFacadeService.saveOrder(1);
return result;
}
}
通过命令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订单,没有出现超卖的情况,说明加锁是成功的。