目录
分布式锁概述
为什么需要分布式锁
什么是分布式锁
分布式锁的特点
分布式锁问题_业务介绍
案列介绍
技术选型
创建表
创建订单表
创建商品表
创建订单商品关联表
分布式锁问题_创建SpringBoot项目
引入依赖
修改配置文件
编写主启动类
代码生成
编写创建订单接口
实现创建订单接口
编写创建订单api接口
测试订单
分布式锁问题_演示问题
启动订单服务9090
启动订单服务9091
创建两个SpringBoot服务
启动Nginx服务
配置负载均衡
启动Nginx服务
启动JMeter压测工具
设置Http请求参数
查看库存数据
查看订单数据
基于synchronized锁解决超卖问题
事务功能的总体接口设计
方法加锁
声明事务管理器
手动创建事务
手动提交事务
分布式锁解决方案
分布式锁实现方案
基于数据库实现的分布式锁
基于 Redis 实现的分布式锁
基于 Zookeeper 实现的分布式锁
分布式锁解决方案_数据库悲观锁实现的分布式锁
什么是悲观锁
演示for update
测试for update
项目中使用for update
配置文件添加配置
mapper添加方法
mapper编写语句
修改订单接口实现类
压测吞吐量
分布式锁解决方案_数据库乐观锁实现的分布式锁
什么是乐观锁
编写乐观锁更新语句
编写创建订单业务层
分布式锁解决方案_Redis实现的分布式锁原理
获取锁
释放锁
分布式锁解决方案_Redis实现的分布式锁
引入依赖
添加Redis配置
编写创建订单实现类
分布式锁解决方案_Redis分布式锁误删除问题
配置锁标识
获取锁
释放锁
分布式锁解决方案_Redis分布式锁不可重入问题
不可重入问题
如何解决
分布式锁解决方案_基于Redisson实现的分布式锁实现
Redisson介绍
引入Redisson依赖
添加Reids的配置
编写Redis分布式锁工具类
编写创建订单接口实现
分布式锁解决方案_Zookeeper分布式锁原理
公平锁和可重入锁的原理
什么是可重入锁呢?
创建临时顺序节点
ZK分布式锁的实现原理
释放锁
分布式锁解决方案_基于Zookeeper实现分布式锁
简介
引入Curator依赖
编写Zookeeper配置
编写创建订单接口实现
三种分布式锁对比
数据库分布式锁实现
Redis 分布式锁实现
Zookeeper 分布式锁实现
汇总对比
分布式锁概述
为什么需要分布式锁
在单机部署的系统中,使用线程锁来解决高并发的问题,多线程访问共享变量的问题达到数据一致性,如使用synchornized、 ReentrantLock等。
但是在后端集群部署的系统中,程序在不同的JVM虚拟机中运行, 且因为synchronized或ReentrantLock都只能保证同一个JVM进程 中保证有效,所以这时就需要使用分布式锁了。
什么是分布式锁
分布式锁其实就是,控制分布式系统不同进程共同访问共享资源的 一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享 了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性。
分布式锁的特点
分布式锁问题_业务介绍
案列介绍
技术选型
创建表
创建订单表
CREATE TABLE `t_order` (
`id` varchar(255) CHARACTER SET utf8 COLLATE
utf8_general_ci NOT NULL,
`order_status` int(1) NULL DEFAULT NULL
COMMENT '订单状态 1 待支付 2已支付',
`receiver_name` varchar(255) CHARACTER SET
utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
COMMENT '收货人名字',
`receiver_mobile` varchar(255) CHARACTER SET
utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
COMMENT '收货人手机',
`order_amount` decimal(10, 2) NULL DEFAULT
NULL COMMENT '订单价格',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE
= utf8_general_ci ROW_FORMAT = Dynamic;
创建商品表
CREATE TABLE `product` (
`id` int(11) NOT NULL,
`product_name` varchar(255) CHARACTER SET
utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
COMMENT '商品名字',
`price` decimal(10, 2) NULL DEFAULT NULL
COMMENT '商品价格',
`count` bigint(50) UNSIGNED NULL DEFAULT NULL
COMMENT '库存',
`product_desc` varchar(255) CHARACTER SET
utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
COMMENT '商品描述',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE
= utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of product
-- ----------------------------
INSERT INTO `product` VALUES (1001,'拯救者',100.00, 5,'好用实惠', 1);
创建订单商品关联表
CREATE TABLE `order_item` (
`id` varchar(255) CHARACTER SET utf8 COLLATE
utf8_general_ci NOT NULL,
`order_id` varchar(36) CHARACTER SET utf8
COLLATE utf8_general_ci NULL DEFAULT NULL
COMMENT '订单ID',
`produce_id` int(11) NULL DEFAULT NULL
COMMENT '商品ID',
`purchase_price` decimal(10, 2) NULL DEFAULT
NULL COMMENT '购买价格',
`purchase_num` int(11) NULL DEFAULT NULL
COMMENT '购买数量',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE
= utf8_general_ci ROW_FORMAT = Dynamic;
分布式锁问题_创建SpringBoot项目
引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.2</version>
</dependency>
<!-- 模板引擎 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
</dependencies>
修改配置文件
spring:
application:
name: lock
datasource:
url:
jdbc:mysql://192.168.66.100:3306/distribute?serverTimezone=UTC
username: root
password01: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
server:
port: 9091
编写主启动类
@Slf4j
@MapperScan("com.itbaizhan.lock.mapper")
@SpringBootApplication
public class LockdemoApplication {
public static void main(String[] args) {
SpringApplication.run(LockdemoApplication.class, args);
log.info("************** 分布式锁 **************");
}
}
代码生成
使用Mybaits Plus生成订单表、商品表、订单商品关联表的相关代码。
package com.itbaizhan.lock.utils;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import java.util.Arrays;
import java.util.List;
public class CodeGenerator {
public static void main(String[] args) {
FastAutoGenerator.create("jdbc:mysql://192.168.66.100:3306/distribute", "root", "123456")
.globalConfig(builder -> {
builder.author("itbaizhan")// 设置作者
.commentDate("MMdd") // 注释日期格式
.outputDir(System.getProperty("user.dir")+ "/src/main/java/") // 指定输出目录
.fileOverride(); //覆盖文件
})
// 包配置
.packageConfig(builder -> {
builder.parent("com.itbaizhan.lock") // 包名前缀
.entity("entity")//实体类包名
.mapper("mapper")//mapper接口包名
.service("service"); //service包名
})
.strategyConfig(builder -> {
List<String> strings = Arrays.asList("t_order");
// 设置需要生成的表名
builder.addInclude(strings)
// 开始实体类配置
.entityBuilder()
// 开启lombok模型
.enableLombok()
//表名下划线转驼峰
.naming(NamingStrategy.underline_to_camel)
//列名下划线转驼峰
.columnNaming(NamingStrategy.underline_to_camel);
})
.execute();
}
}
编写创建订单接口
public interface ITOrderService extends IService<TOrder> {
/**
* 创建订单
* @return
*/
String createOrder(Integer productId,Integer count);
}
实现创建订单接口
package com.itbaizhan.lock.service.impl;
import com.itbaizhan.lock.entity.OrderItem;
import com.itbaizhan.lock.entity.Product;
import com.itbaizhan.lock.entity.TOrder;
import com.itbaizhan.lock.mapper.OrderItemMapper;
import com.itbaizhan.lock.mapper.ProductMapper;
import com.itbaizhan.lock.mapper.TOrderMapper;
import com.itbaizhan.lock.service.ITOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.concurrent.locks.ReentrantLock;
/**
* <p>
* 服务实现类
* </p>
*
* @author itbaizhan
* @since 05-25
*/
@Service
public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> implements ITOrderService {
@Resource
OrderItemMapper orderItemMapper;
@Resource
ProductMapper productMapper;
/**
* 创建订单
* @return
*/
@Transactional(rollbackFor = Exception.class)
@Override
public String createOrder(Integer productId,Integer count) {
// 1、根据商品id查询商品信息
Product product = productMapper.selectById(productId);
// 2、判断商品是否存在
if (product == null){
throw new RuntimeException("购买商品不存在:" + productId + "不存在");
}
// 3、校验库存
if( count > product.getCount() ){
throw new RuntimeException("商品" + productId + "仅剩" + product.getCount() + "件,无法购买");
}
// 4、计算库存
Integer leftCount = product.getCount() - count;
// 5、更新库存
product.setCount(leftCount);
productMapper.updateById(product);
// 6、 创建订单
TOrder order = new TOrder();
order.setOrderStatus(1);//待处理
order.setReceiverName("张三");
order.setReceiverMobile("18587781068");
order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
baseMapper.insert(order);
// 7、 创建订单和商品关系数据
OrderItem orderItem = new OrderItem();
orderItem.setOrderId(order.getId());
orderItem.setProduceId(product.getId());
orderItem.setPurchasePrice(product.getPrice());
orderItem.setPurchaseNum(count);
orderItemMapper.insert(orderItem);
return order.getId();
}
}
编写创建订单api接口
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private ITOrderService iOrderService;
/**
* 创建订单
* @param productId 商品id
* @param count 商品数量
* @return
*/
@PostMapping("/create")
public String createOrder(Integer productId,Integer count){
return iOrderService.createOrder(productId,count);
}
}
测试订单
分布式锁问题_演示问题
启动订单服务9090
启动订单服务9091
创建两个SpringBoot服务
启动Nginx服务
下载Nginx windows服务,官网http://nginx.org/en/download.html
配置负载均衡
编辑nginx.conf文件添加负载均衡的配置。
upstream test{
server localhost:9090 ;
server localhost:9091 ;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://test;
}
}
启动Nginx服务
在nginx目录下面
启动JMeter压测工具
添加线程组 -> HTTP请求 -> 察看结果树 -> 聚合报告
设置Http请求参数
查看库存数据
查看订单数据
基于synchronized锁解决超卖问题
PlatformTransactionManager事务管理器接口 , 事务的 提交、回滚等操作
事务功能的总体接口设计
三个接口功能一句话总的来说事务管理器基于事务基础信息在操作 事务时候对事务状态进行更新。
方法加锁
public synchronized String createOrder(Integer produceId, Integer purchaseNum) { }
声明事务管理器
@Autowired
private PlatformTransactionManager platformTransactionManager;
@Autowired
private TransactionDefinition transactionDefinition;
手动创建事务
TransactionStatus transaction = platformTransactionManager.getTransaction(transactionDefinition);
手动提交事务
/**
* 创建订单
*
* @param produceId 商品id
* @param purchaseNum 购买数量
* @return
*/
@Override
public synchronized String createOrder(Integer produceId, Integer purchaseNum) {
TransactionStatus transaction = platformTransactionManager.getTransaction(trans
actionDefinition);
// 1、根据商品id获取商品信息
Product product = productMapper.selectById(produceId);
// 2、判断商品是否存在
if (product == null) {
platformTransactionManager.rollback(transaction);
throw new RuntimeException("购买商品不存在");
}
log.info(Thread.currentThread().getName() + "库存数量" + product.getCount());
// 3、校验库存
if (purchaseNum > product.getCount()) {
platformTransactionManager.rollback(transaction);
throw new RuntimeException("库存不足");
}
// 4、更新库存操作
int count = product.getCount() - purchaseNum;
product.setCount(count);
productMapper.updateById(product);
// 5、创建订单
TOrder order = new TOrder();
order.setOrderStatus(1);//订单状态
order.setReceiverName("张三");
order.setReceiverMobile("18587781058");
order.setOrderAmount(product.getPrice().multiply(new BigDecimal(purchaseNum)));//订单价格
baseMapper.insert(order);
// 6、创建订单和商品关联
OrderItem orderItem = new OrderItem();
orderItem.setOrderId(order.getId());//订单id
orderItem.setProduceId(product.getId());// 商品id
orderItem.setPurchasePrice(product.getPrice());// 购买价格
orderItem.setPurchaseNum(purchaseNum);// 购买数量
orderItemMapper.insert(orderItem);
//提交事务
platformTransactionManager.commit(transaction);
return order.getId();
}
分布式锁解决方案
分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一 致性(Consistency)、可用性(Availability)和分区容错性 (Partition tolerance),最多只能同时满足两项。”所以,很多系 统在设计之初就要对这三者做出取舍。在互联网领域的绝大多数的 场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只 需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。
分布式锁实现方案
基于数据库实现的分布式锁
基于数据库实现分布式锁主要是利用数据库的唯一索引来实现,唯 一索引天然具有排他性,这刚好符合我们对锁的要求:同一时刻只能允许一个竞争者获取锁。
基于 Redis 实现的分布式锁
SETNX key value
基于 Zookeeper 实现的分布式锁
Zookeeper一般用作配置中心,其实现分布式锁的原理和Redis类 似,我们在Zookeeper中创建临时顺序节点,利用节点不能重复创建的特性来保证排他性。
分布式锁解决方案_数据库悲观锁实现的分布式锁
什么是悲观锁
顾名思义,就是比较悲观的锁,总是假设最坏的情况,每次去拿数 据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁, 这样别人想拿这个数据就会阻塞直到它拿到锁。
演示for update
开启2个命令行界面
测试for update
项目中使用for update
配置文件添加配置
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
mapper添加方法
public interface ProductMapper extends BaseMapper<Product> {
Product findById(@Param("id")Integer id);
}
mapper编写语句
<select id="findById" parameterType="int" resultType="com.itbaizhan.lock.entity.Product">
select * from product where id = #{id} for update
</select>
修改订单接口实现类
@Transactional(rollbackFor = Exception.class)
@Override
public String createOrder(Integer productId, Integer count) {
// 1、根据商品id查询商品信息
Product product = productMapper.findById(productId);
// 2、判断商品是否存在
if (product == null) {
throw new RuntimeException("购买商品不存在:" + productId + "不存在");
}
// 3、校验库存
if (count > product.getCount()) {
throw new RuntimeException("商品" +productId + "仅剩" + product.getCount() + "件,无法购买");
}
// 4、计算库存
Integer leftCount = product.getCount() - count;
// 5、更新库存
product.setCount(leftCount);
productMapper.updateById(product);
// 6、 创建订单
TOrder order = new TOrder();
order.setOrderStatus(1);//待处理
order.setReceiverName("张三");
order.setReceiverMobile("18587781068");
order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
baseMapper.insert(order);
// 7、 创建订单和商品关系数据
OrderItem orderItem = new OrderItem();
orderItem.setOrderId(order.getId());
orderItem.setProduceId(product.getId());
orderItem.setPurchasePrice(product.getPrice());
orderItem.setPurchaseNum(count);
orderItemMapper.insert(orderItem);
return order.getId();
}
压测吞吐量
分布式锁解决方案_数据库乐观锁实现的分布式锁
什么是乐观锁
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改, 所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有 去更新这个数据,可以使用版本号机制和CAS算法实现。
编写乐观锁更新语句
<update id="decreaseStockForVersion" parameterType="int" >
UPDATE product SET count = count - # {count}, version = version + 1 WHERE id = #{id} AND count > 0 AND version = #{version}
</update>
编写创建订单业务层
/**
* 创建订单 乐观锁
*
* @return
*/
@Transactional(rollbackFor = Exception.class)
@Override
public String createOrder(Integer productId, Integer count) throws Exception {
int retryCount = 0;
int update = 0;
// 1、根据商品id查询商品信息
Product product = productMapper.selectById(productId);
// 2、判断商品是否存在
if (product == null) {
throw new RuntimeException("购买商品不存在:" + productId + "不存在");
}
// 3、校验库存
if (count > product.getCount()) {
throw new Exception("库存不够");
}
// 乐观锁更新库存
// 更新失败,说明其他线程已经修改过数据,本次扣减库存失败,可以重试一定次数或者返回
// 最多重试3次
while(retryCount < 3 && update == 0){
update = this.reduceStock(product.getId(),count);
retryCount++;
}
if (update == 0){
throw new Exception("库存不够");
}
// 6、 创建订单
TOrder order = new TOrder();
order.setOrderStatus(1);//待处理
order.setReceiverName("张三");
order.setReceiverMobile("18587781068");
order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
baseMapper.insert(order);
// 7、 创建订单和商品关系数据
OrderItem orderItem = new OrderItem();
orderItem.setOrderId(order.getId());
orderItem.setProduceId(product.getId());
orderItem.setPurchasePrice(product.getPrice());
orderItem.setPurchaseNum(count);
orderItemMapper.insert(orderItem);
return order.getId();
}
/**
* 减库存
* <p>
* 由于默认的事务隔离级别是可重复读,produce.findById()
* 得到的数据始终是相同的,所以需要提取 reduceStock方法。每次循环都启动新的事务尝试扣减库存操作。
*/
@Transactional(rollbackFor = Exception.class)
public int reduceStock(int gid,int count) {
int result = 0;
//1、查询商品库存
Product product = productMapper.selectById(gid);
//2、判断库存是否充足
if (product.getCount() >= count) {
//3、减库存
// 乐观锁更新库存
result = productMapper.decreaseStockForVersion(gid,count, product.getVersion());
}
return result;
}
分布式锁解决方案_Redis实现的分布式锁原理
获取锁
互斥:确保只有一个线程获得锁
# 添加锁 利用setnx的互斥性
127.0.0.1:6379> setnx lock thread1
释放锁
1、手动释放锁
2、超时释放:获取锁时设置一个超时时间
#释放锁 删除即可
127.0.0.1:6379> del lock
超时释放
127.0.0.1:6379> setnx lock tread1
127.0.0.1:6379> expire lock 5
127.0.0.1:6379> ttl lock
两步合成一步
help set
SET key value [EX seconds] [PX milliseconds] [NX|XX]
summary: Set the string value of a key
since: 1.0.0
group: string
127.0.0.1:6379> get k1
(nil)
127.0.0.1:6379> set lock k1 ex 5 nx
OK
127.0.0.1:6379> set lock k1 ex 5 nx
nil
分布式锁解决方案_Redis实现的分布式锁
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
添加Redis配置
spring:
redis:
host: localhost
port: 6379
编写创建订单实现类
@Override
public String createOrderRedis(Integer productId, Integer count) throws Exception {
log.info("*************** 进入方法 **********");
String key = "lock:";
String value = UUID.randomUUID().toString();
// 获取分布式锁
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(key+productId,String.valueOf(Thread.currentThread().getId()),30,TimeUnit.SECONDS);
// 判断是否获取锁成功
if (!result){
log.info("我进入了锁");
return "不允许重复下单";
}
try {
// 1、根据商品id查询商品信息
Product product = productMapper.selectById(productId);
// 2、判断商品是否存在 if (product == null) {
throw new RuntimeException("购买商品不存在:" + productId + "不存在");
}
// 3、校验库存
if (count > product.getCount()) {
throw new RuntimeException("商品" + productId + "仅剩" + product.getCount() + "件,无法购买");
}
// 4、计算库存
Integer leftCount = product.getCount() - count;
// 5、更新库存
product.setCount(leftCount);
productMapper.updateById(product);
// 6、 创建订单
TOrder order = new TOrder();
order.setOrderStatus(1);//待处理
order.setReceiverName("张三");
order.setReceiverMobile("18587781068");
order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
baseMapper.insert(order);
// 7、 创建订单和商品关系数据
OrderItem orderItem = new OrderItem();
orderItem.setOrderId(order.getId());
orderItem.setProduceId(product.getId());
orderItem.setPurchasePrice(product.getPrice());
orderItem.setPurchaseNum(count);
orderItemMapper.insert(orderItem);
return order.getId();
}catch (Exception e){
e.printStackTrace();
}finally {
// 释放锁
stringRedisTemplate.delete(key+productId);
}
return "创建失败";
}
分布式锁解决方案_Redis分布式锁误删除问题
配置锁标识
private static final String KEY_PREFIX = "lock:";
private static final String ID_PREFIX = UUID.randomUUID().toString().replace("-" ,"");
获取锁
//1、获取线程标识
String threadId = ID_PREFIX + Thread.currentThread().getId();
// 2、获得锁 setnx key value time type
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX+produceId, threadId, 30,TimeUnit.SECONDS);
释放锁
// 获取锁标识
String s = stringRedisTemplate.opsForValue().get(KEY_PREFIX + produceId);
// 判断标识是否一致
if (s.equals(threadId)){
// 释放锁
stringRedisTemplate.delete(KEY_PREFIX + produceId);
}
分布式锁解决方案_Redis分布式锁不可重入问题
不可重入问题
如何解决
分布式锁解决方案_基于Redisson实现的分布式锁实现
Redisson介绍
Redisson - 是一个高级的分布式协调Redis客服端,能帮助用户在分 布式环境中轻松实现一些Java的对象,Redisson、Jedis、Lettuce 是三个不同的操作 Redis 的客户端,Jedis、Lettuce 的 API 更侧重 对 Reids 数据库的 CRUD(增删改查),而 Redisson API 侧重于分布式开发。
引入Redisson依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.17.2</version>
</dependency>
添加Reids的配置
spring:
redis:
host: localhost
port: 6379
编写Redis分布式锁工具类
package com.itbaizhan.lock.utils;
import lombok.extern.slf4j.Slf4j;
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;
@Component
@Slf4j
public class DistributedRedisLock {
@Autowired
private RedissonClient redissonClient;
// 加锁
public Boolean lock(String lockName) {
if (redissonClient == null) {
log.info("DistributedRedisLock redissonClient is null");
return false;
}
try {
RLock lock = redissonClient.getLock(lockName);
// 锁15秒后自动释放,防止死锁
lock.lock(15, TimeUnit.SECONDS);
log.info("Thread [{}] DistributedRedisLock lock [{}] success",Thread.currentThread().getName(), lockName);
// 加锁成功
return true;
} catch (Exception e) {
log.error("DistributedRedisLocklock [{}] Exception:", lockName, e);
return false;
}
}
// 释放锁
public Boolean unlock(String lockName) {
if (redissonClient == null) {
log.info("DistributedRedisLock redissonClient is null");
return false;
}
try {
RLock lock = redissonClient.getLock(lockName);
lock.unlock();
log.info("Thread [{}] DistributedRedisLock unlock [{}] success",Thread.currentThread().getName(), lockName);
// 释放锁成功
return true;
} catch (Exception e) {
log.error("DistributedRedisLock unlock [{}] Exception:", lockName, e);
return false;
}
}
}
编写创建订单接口实现
/**
* Redis锁实现
*
* @param productId
* @param count
* @return
* @throws Exception
*/
@Override
public String createOrderRedis(Integer productId, Integer count) throws Exception {
//获取锁对象
if (distributedRedisLock.lock(String.valueOf(productId))) {
try {
// 1、根据商品id查询商品信息
Product product = productMapper.selectById(productId);
// 2、判断商品是否存在
if (product == null) {
throw new RuntimeException("购买商品不存在:" + productId + "不存在");
}
// 3、校验库存
if (count > product.getCount())
{
throw new RuntimeException("商品" + productId + "仅剩" + product.getCount() + "件,无法购买");
}
// 4、计算库存
Integer leftCount = product.getCount() - count;
// 5、更新库存
product.setCount(leftCount);
productMapper.updateById(product);
// 6、 创建订单
TOrder order = new TOrder();
order.setOrderStatus(1);//待处理
order.setReceiverName("张三");
order.setReceiverMobile("18587781068");
order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
baseMapper.insert(order);
// 7、 创建订单和商品关系数据
OrderItem orderItem = new OrderItem();
orderItem.setOrderId(order.getId());
orderItem.setProduceId(product.getId());
orderItem.setPurchasePrice(product.getPrice());
orderItem.setPurchaseNum(count);
orderItemMapper.insert(orderItem);
return order.getId();
} catch (Exception e) {
e.printStackTrace();
} finally {
distributedRedisLock.unlock(String.valueOf(productId));
}
}
return "创建失败";
}
分布式锁解决方案_Zookeeper分布式锁原理
公平锁和可重入锁的原理
这种排队取水模型,就是一种锁的模型。
什么是可重入锁呢?
创建临时顺序节点
[zk: localhost:2181(CONNECTED) 1] create -e -s
/test 123
ZK分布式锁的实现原理
当第一个客户端请求过来时,Zookeeper 客户端会创建一个持久节 点 locks。如果它(Client1)想获得锁,需要在 locks 节点下创建 一个顺序节点 lock1。
接着,客户端 Client1 会查找 locks 下面的所有临时顺序子节点,判 断自己的节点 lock1 是不是排序最小的那一个,如果是,则成功获得锁。
这时候如果又来一个客户端 client2 前来尝试获得锁,它会在 locks 下再创建一个临时节点 lock2。
客户端 client2 一样也会查找 locks 下面的所有临时顺序子节点,判 断自己的节点 lock2 是不是最小的,此时,发现 lock1 才是最小 的,于是获取锁失败。获取锁失败,它是不会甘心的,client2 向它 排序靠前的节点 lock1 注册 Watcher 事件,用来监听 lock1 是否存 在,也就是说 client2 抢锁失败进入等待状态。
此时,如果再来一个客户端Client3来尝试获取锁,它会在 locks 下 再创建一个临时节点 lock3。
同样的,client3 一样也会查找 locks 下面的所有临时顺序子节点, 判断自己的节点 lock3 是不是最小的,发现自己不是最小的,就获 取锁失败。它也是不会甘心的,它会向在它前面的节点 lock2 注册 Watcher 事件,以监听 lock2 节点是否存在。
释放锁
如果是任务完成,Client1 会显式调用删除 lock1 的指令。
如果是客户端故障了,根据临时节点得特性,lock1 是会自动删除的。
lock1 节点被删除后,Client2 可开心了,因为它一直监听着 lock1。lock1 节点删除,Client2 立刻收到通知,也会查找 locks 下面的所有临时顺序子节点,发下 lock2 是最小,就获得锁。
同理,Client2 获得锁之后,Client3 也对它虎视眈眈:
分布式锁解决方案_基于Zookeeper实现分布式锁
简介
Apache Curator是一个比较完善的ZooKeeper客户端框架,通过封 装的一套高级API 简化了ZooKeeper的操作。
引入Curator依赖
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-client</artifactId>
<version>5.2.0</version>
</dependency>
编写Zookeeper配置
@Configuration
public class ZookeeperConfig {
@Bean
public CuratorFramework zookeeperClient() {
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("127.0.0.1:2181")
.sessionTimeoutMs(5000)
.connectionTimeoutMs(5000)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
//.namespace("test")
.build();
client.start();
return client;
}
}
编写创建订单接口实现
使用InterProcessMutex的acquire和release方法,来获取和释放锁。
@Autowired
CuratorFramework client;
@Override
public String createOrderZookeeper(Integer productId, Integer count) throws Exception
{
// client cruator中zk客户端对象 path 抢锁路径同一个锁path需要一致
InterProcessMutex lock = new InterProcessMutex(client, "/lockPath");
//第一个属性:定时的时间数字
//第二个属性:定义时间的单位
if (lock.acquire(3, TimeUnit.SECONDS))
{
try {
// 1、根据商品id查询商品信息
Product product = productMapper.selectById(productId);
// 2、判断商品是否存在
if (product == null) {
throw new RuntimeException("购买商品不存在:" + productId + "不存在");
}
// 3、校验库存
if (count > product.getCount())
{
throw new RuntimeException("商品" + productId + "仅剩" +
product.getCount() + "件,无法购买");
}
// 4、计算库存
Integer leftCount = product.getCount() - count;
// 5、更新库存
product.setCount(leftCount);
productMapper.updateById(product);
// 6、 创建订单
TOrder order = new TOrder();
order.setOrderStatus(1);//待处理
order.setReceiverName("张三");
order.setReceiverMobile("18587781068");
order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
baseMapper.insert(order);
// 7、 创建订单和商品关系数据
OrderItem orderItem = new OrderItem();
orderItem.setOrderId(order.getId());
orderItem.setProduceId(product.getId());
orderItem.setPurchasePrice(product.getPrice());
orderItem.setPurchaseNum(count);
orderItemMapper.insert(orderItem);
return order.getId();
} finally {
lock.release();
}
}
return "创建失败";
}
三种分布式锁对比
数据库分布式锁实现
优点:简单,使用方便,不需要引入 Redis、Zookeeper 等中间件。
缺点:1、不适合高并发的场景 2、db 操作性能较差
Redis 分布式锁实现
优点:1、性能好,适合高并发场景 2、较轻量级 3、有较好的框架支持,如 Redisson
缺点:1、过期时间不好控制 2、需要考虑锁被别的线程误删场景
Zookeeper 分布式锁实现
优点:1、有较好的性能和可靠性 2、有封装较好的框架,如 Curator
缺点:1、性能不如 Redis 实现的分布式锁 2、比较重的分布式锁。
汇总对比
1、从性能角度:Redis > Zookeeper >= 数据库
2、从实现的复杂性角度:Zookeeper > Redis > 数据库
3、从可靠性角度:Zookeeper > Redis > 数据库