分布式锁概述
为什么需要分布式锁
在单机部署的系统中,使用线程锁来解决高并发的问题,多线程访问共享变量的问题达到数据一致性,如使用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.tong.lock.mapper") @SpringBootApplication public class LockdemoApplication { public static void main(String[] args) { SpringApplication.run(LockdemoApplication.class, args); log.info("************** 分布式锁 **************"); } }
代码生成
使用Mybaits Plus生成订单表、商品表、订单商品关联表的相关代码。
package com.tong.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.tong.lock.service.impl; import com.tong.lock.entity.OrderItem; import com.tong.lock.entity.Product; import com.tong.lock.entity.TOrder; import com.tong.lock.mapper.OrderItemMapper; import com.tong.lock.mapper.ProductMapper; import com.tong.lock.mapper.TOrderMapper; import com.tong.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 tong * @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); } }
测试订单