分布式锁(第一部分)
分布式锁学习图:
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,然后在执行方法中,手动创建事务,手动提交与手动撤销事务。