分布式锁(第一部分)
分布式锁学习图:
1、什么是分布式锁,为什么需要它?
所谓的锁,在操作系统中了解过,就是为了避免对临界资源的访问而出现资源不一致性的问题,必须要是哦也能够互斥机制来保证资源的一致性。
在单体项目中,一般是使用JVM层面的锁实现资源的互斥,那就是Synchronized和ReentrantLock同步锁实现
但是如果在分布式的微服务里面,有多个计算机,各个计算机之间是跨JVM的,那么这些同步锁就不能实现,以至于失效,所以需要一个全局的锁,这个全局的锁,他不会特意的在哪一个计算机或者服务器里面提供服务,而是抽取出来,作为一个全局资源的把控,比如,如图:
该图就是一个分布式的系统,现在有两个服务器处理下订单的业务,如果不加分布式锁的话,结果就是:数据库的商品库存总共就1000件,但是在同一时刻有两个请求进入到了服务器1和服务器2,都想要下订单1000件,结果就是卖出去了2000件,但是数据库就只有1000件,所以出现问题了,就算你添加了Synchronized和ReentrantLock锁也是无济于事。
而分布式锁他应该是这样的,如图:
会发现,在服务器之间是有一个分布式锁的存在,在真正处理订单业务的时候,会进行获取锁,如果可以获得锁的话,就继续执行创建订单,反之,则需要等待。
分布式锁,在任意一个时刻,都是只有一个线程使用锁,其余的线程全部等待。
2、在单体项目下抢购订单(不加锁)
第一、创建数据库// 订单表 CREATE TABLE `t_order` ( `id` int(11) NOT NULL AUTO_INCREMENT, `productid` int(11) DEFAULT NULL, `count` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=41 DEFAULT CHARSET=utf8; // 商品表 CREATE TABLE `t_product` ( `id` int(11) NOT NULL AUTO_INCREMENT, `count` int(11) DEFAULT NULL, `price` int(10) DEFAULT NULL, `name` varchar(22) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=10002 DEFAULT CHARSET=utf8;第二、创建pojo实体对象
// 创建商品实体类
/**
* @Description: 商品实体类
* @Author: huidou 惠豆
* @CreateTime: 2022/6/12 10:36
*/
@Data
public class Product {
private Integer id;
private Integer count;
private Integer price;
private String name;
}
// 创建订单实体类
/**
* @Description: 订单实体类
* @Author: huidou 惠豆
* @CreateTime: 2022/6/12 10:35
*/
@Data
public class Order {
private Integer id;
private Integer productid;
private Integer count;
}
第三、项目依赖
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.3</version> </parent> <dependencies> <!-- mysql driver --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- mybatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.1</version> </dependency> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.22</version> </dependency> <!-- web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- thymeleaf --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> </dependencies>第四、创建MVC三层结构
// controller层
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private ProductService productService;
@Autowired
private OrderService orderService;
/**
* 创建一个订单
* @param id
* @param count
* @return
*/
@Transactional(rollbackFor = Exception.class)
@GetMapping("/createOrder")
public String createOrder(Integer id,Integer count) {
// 1、根据商品ID查询出商品信息
Product product = productService.selectProduct(id);
if (product == null) {
return "该商品不存在";
}
// 2、根据购买的数量进行判断是否合理
if (product.getCount() < count) {
return "商品的库存不足,无法购买";
}
// 3、创建订单,并且将其信息插入数据库中
Order insertOrder = new Order();
insertOrder.setId(12);
insertOrder.setProductid(id);
insertOrder.setCount(count);
Integer insertResult = orderService.addOrder(insertOrder);
// 4、修改商品表的库存数量信息
Product updateProduct = new Product();
updateProduct.setCount(product.getCount() - count);
Integer updateResult = productService.consumerProduct(product.getId(),count);
// 5、判断操作
if (insertResult >= 1 && updateResult >= 1) {
return "创建订单信息成功";
} else {
return "创建订单信息失败";
}
}
}
// service层
/**
* @Description: 商品服务接口
* @Author: huidou 惠豆
* @CreateTime: 2022/6/12 10:41
*/
public interface ProductService {
// 1、消费商品
Integer consumerProduct(Integer id,Integer count);
// 2、根据商品ID,查询商品信息
Product selectProduct(Integer id);
}
/**
* @Description: 订单服务接口
* @Author: huidou 惠豆
* @CreateTime: 2022/6/12 10:40
*/
public interface OrderService {
// 1、创建订单
Integer addOrder(Order order);
}
-----------------------------------------------
/**
* @Description: 订单服务接口实现类
* @Author: huidou 惠豆
* @CreateTime: 2022/6/12 10:43
*/
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
/**
* 添加一个订单
* @param order
* @return
*/
@Override
public Integer addOrder(Order order) {
return orderMapper.insert(order);
}
}
/**
* @Description: 商品服务接口实现类
* @Author: huidou 惠豆
* @CreateTime: 2022/6/12 10:44
*/
@Service
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductMapper productMapper;
/**
* 消费商品
* @param id
* @return
*/
@Override
public Integer consumerProduct(Integer id,Integer count) {
return productMapper.updateById(id,count);
}
/**
* 根据商品ID,查询商品信息
* @param id
* @return
*/
@Override
public Product selectProduct(Integer id) {
return productMapper.selectById(id);
}
}
// mapper层
/**
* @Description: 订单mapper
* @Author: huidou 惠豆
* @CreateTime: 2022/6/12 10:38
*/
@Repository
public interface OrderMapper {
// 创建一个订单
Integer insert(Order order);
}
/**
* @Description: 商品实体类
* @Author: huidou 惠豆
* @CreateTime: 2022/6/12 10:39
*/
@Repository
public interface ProductMapper{
/**
* 根据商品的id修改商品信息
* @param id
* @return
*/
Integer updateById(Integer id,Integer count);
/**
* 根据商品id查询商品信息
* @param id
* @return
*/
Product selectById(Integer id);
}
第五、配置文件application.yml
spring: application: name: DistributedLock datasource: url: jdbc:mysql://localhost:3306/school username: root password: root mybatis: mapper-locations: classpath:/mapper/*.xml第六、Mapper映射配置文件xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hui.mapper.OrderMapper">
<!-- 1、 创建一个订单 -->
<insert id="insert" parameterType="com.hui.pojo.Order">
insert into t_order values (default ,#{productid},#{count})
</insert>
</mapper>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hui.mapper.ProductMapper">
<!-- 1、selectById 据商品id查询商品信息 -->
<select id="selectById" resultType="com.hui.pojo.Product">
select * from t_product where id = #{id}
</select>
<!-- 2、根据商品的id修改商品信息 -->
<update id="updateById" parameterType="int">
update t_product set count = count - #{count} where id = #{id}
</update>
</mapper>
3、在单体项目下抢购订单(加锁)
单体项目加锁可以使用Synchronized或者ReentrantLock锁实现数据的互斥访问。/**
* 创建一个订单
* @param id
* @param count
* @return
*/
@Transactional(rollbackFor = Exception.class)
@GetMapping("/createOrder")
public synchronized String createOrder(Integer id,Integer count) {
// 1、根据商品ID查询出商品信息
Product product = productService.selectProduct(id);
if (product == null) {
return "该商品不存在";
}
// 2、根据购买的数量进行判断是否合理
if (product.getCount() < count) {
return "商品的库存不足,无法购买";
}
// 3、创建订单,并且将其信息插入数据库中
Order insertOrder = new Order();
insertOrder.setId(12);
insertOrder.setProductid(id);
insertOrder.setCount(count);
Integer insertResult = orderService.addOrder(insertOrder);
// 4、修改商品表的库存数量信息
Product updateProduct = new Product();
updateProduct.setCount(product.getCount() - count);
Integer updateResult = productService.consumerProduct(product.getId(),count);
// 5、判断操作
if (insertResult >= 1 && updateResult >= 1) {
return "创建订单信息成功";
} else {
return "创建订单信息失败";
}
}
4、在分布式项目下抢购订单(在加锁的前提下,暴露分布式锁的必要性)
第一、看代码/**
* 创建一个订单
* @param id
* @param count
* @return
*/
@Transactional(rollbackFor = Exception.class)
@GetMapping("/createOrder")
public synchronized String createOrder(Integer id,Integer count) {
// 1、根据商品ID查询出商品信息
Product product = productService.selectProduct(id);
if (product == null) {
return "该商品不存在";
}
// 2、根据购买的数量进行判断是否合理
if (product.getCount() < count) {
return "商品的库存不足,无法购买";
}
// 3、创建订单,并且将其信息插入数据库中
Order insertOrder = new Order();
insertOrder.setId(12);
insertOrder.setProductid(id);
insertOrder.setCount(count);
Integer insertResult = orderService.addOrder(insertOrder);
// 4、修改商品表的库存数量信息
Product updateProduct = new Product();
updateProduct.setCount(product.getCount() - count);
Integer updateResult = productService.consumerProduct(product.getId(),count);
// 5、判断操作
if (insertResult >= 1 && updateResult >= 1) {
return "创建订单信息成功";
} else {
return "创建订单信息失败";
}
}
第二、设计nginx网关
upstream test{
server localhost:8080 ;
server localhost:8081 ;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://test;
}
location = /50x.html {
root html;
}
}
看到这里大家会发现,我的代码方法是添加了同步锁Synchronized,那么看看Jmeter测试之后的结果吧!
设置数据库库存为2,请求由1000个,查看最后的执行结果:
会发现居然超买了,这是为啥?分为两个原因:
-
第一,就是我之前所说的JVM层面的锁只适合在单体的项目中,对于分布式的请求是不适合的
-
第二,就是Synchronized与注解@Transactional配合有问题,这是针对于在同一个机器中,当前一个前一个请求占了所之后,会进行处理,处理完成之后,退出方法,这是@Transactional注解才会去提交事务,但是这个时候,第二个请求已经进来了,所以就会导致数据的脏读
这个问题如何解决呢?
-
对于分布式请求们就需要分布式解决方案处理,具体看后面描述。
-
对于单体的问题。首先,添加锁是一定的,因为有可能同一个机器同时接收了很多的请求,这时就需要同一台JVM对请求进行控制,而对于Synchronized与注解@Transactional配合的问题,则需要使用事务管理器来处理,实现手动的提交事务,即可解决。
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private ProductService productService;
@Autowired
private OrderService orderService;
// 声明事务管理器
@Autowired
private PlatformTransactionManager platformTransactionManager;
// 声明事务定义
@Autowired
private TransactionDefinition transactionDefinition;
/**
* 创建一个订单
* @param id
* @param count
* @return
*/
// @Transactional(rollbackFor = Exception.class)
@GetMapping("/createOrder")
public synchronized String createOrder(Integer id,Integer count) {
// 手动创建事务
TransactionStatus transaction = platformTransactionManager.getTransaction(transactionDefinition);
// 1、根据商品ID查询出商品信息
Product product = productService.selectProduct(id);
if (product == null) {
platformTransactionManager.rollback(transaction);
return "该商品不存在";
}
// 2、根据购买的数量进行判断是否合理
if (product.getCount() < count) {
platformTransactionManager.rollback(transaction);
return "商品的库存不足,无法购买";
}
// 3、创建订单,并且将其信息插入数据库中
Order insertOrder = new Order();
insertOrder.setId(12);
insertOrder.setProductid(id);
insertOrder.setCount(count);
Integer insertResult = orderService.addOrder(insertOrder);
// 4、修改商品表的库存数量信息
Product updateProduct = new Product();
updateProduct.setCount(product.getCount() - count);
Integer updateResult = productService.consumerProduct(product.getId(),count);
// 5、提交事务
platformTransactionManager.commit(transaction);
return "事务提交成功---订单创建成功";
}
}
会发现,需要注入一个事务管理器PlatformTransactionManager和事务定义TransactionDefinition,然后在执行方法中,手动创建事务,手动提交与手动撤销事务。
传音控股公司福利 327人发布
