看完该文预计用时:15分钟
看之前应具体的技术栈:springboot mysql nginx(了解即可)
0.写在前面
在多线程高并发场景下,为了保证资源的线程安全问题,jdk为我们提供了synchronized关键字和
ReentrantLock可重入锁,但是它们只能保证一个jvm内的线程安全。在分布式集群、微服务、云原生横行的当下,如何保证不同进程、不同服务、不同机器的线程安全问题,jdk并没有给我们提供既有的解决方案。此时,我们就必须借助于相关技术手动实现了。目前主流的实现有三种方式:
1. 基于mysql关系型实现
2. 基于redis非关系型数据实现
3. 基于zookeeper实现
这篇文章主要讲解的是基于基于mysql关系型实现分布式锁
1. 从减库存聊起
库存在并发量较大情况下很容易发生超卖现象,一旦发生超卖现象,就会出现多成交了订单而发不了货的情况。
场景:
商品S库存余量为5时,用户A和B同时来购买一个商品S,此时查询库存数都为5,库存充足则开始减库存:
用户A:update db_stock set stock = stock - 1 where id = 1
用户B:update db_stock set stock = stock - 1 where id = 1
并发情况下,更新后的结果可能是4,而实际的最终库存量应该是3才对
1.1. 环境准备
为了模拟具体场景我们需要准备开发环境
首先需要在mysql数据库中准备一张表:
1. CREATE TABLE `db_stock` ( 2. `id` bigint(20) NOT NULL AUTO_INCREMENT, 3. `product_code` varchar(255) DEFAULT NULL COMMENT '商品编号', 4. `stock_code` varchar(255) DEFAULT NULL COMMENT '仓库编号', 5. `count` int(11) DEFAULT NULL COMMENT '库存量', 6. PRIMARY KEY (`id`) 7. ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
表中数据如下:
创建分布式锁demo工程:
建立以下工具目录结构:
pom依赖文件:
1. 2. <dependencies> 3. <dependency> 4. <groupId>org.springframework.boot</groupId> 5. <artifactId>spring-boot-starter-web</artifactId> 6. </dependency> 7. <dependency> 8. <groupId>mysql</groupId> 9. <artifactId>mysql-connector-java</artifactId> 10. <version>5.1.46</version> 11. </dependency> 12. <dependency> 13. <groupId>com.baomidou</groupId> 14. <artifactId>mybatis-plus-boot-starter</artifactId> 15. <version>3.4.0</version> 16. </dependency> 17. <dependency> 18. <groupId>org.projectlombok</groupId> 19. <artifactId>lombok</artifactId> 20. <version>1.18.16</version> 21. </dependency> 22. <dependency> 23. <groupId>org.springframework.boot</groupId> 24. <artifactId>spring-boot-starter-data-redis</artifactId> 25. </dependency> 26. <dependency> 27. <groupId>org.springframework.boot</groupId> 28. <artifactId>spring-boot-devtools</artifactId> 29. </dependency> 30. <dependency> 31. <groupId>org.springframework.boot</groupId> 32. <artifactId>spring-boot-starter-test</artifactId> 33. <scope>test</scope> 34. <exclusions> 35. <exclusion> 36. <groupId>org.junit.vintage</groupId> 37. <artifactId>junit-vintage-engine</artifactId> 38. </exclusion> 39. </exclusions> 40. </dependency> 41. </dependencies> 42. <build> 43. <plugins> 44. <plugin> 45. <groupId>org.springframework.boot</groupId> 46. <artifactId>spring-boot-maven-plugin</artifactId> 47. </plugin> 48. </plugins> 49. </build> 50. </project>
application.yml配置文件:
1. server: 2. port: 6000 3. spring: 4. datasource: 5. driver-class-name: com.mysql.jdbc.Driver 6. url: jdbc:mysql://172.16.116.100:3306/test 7. username: root 8. password: root
DistributedLockApplication启动类:
1. @SpringBootApplication 2. @MapperScan("com.atguigu.distributedlock.mapper") 3. 4. public class DistributedLockApplication { 5. public static void main(String[] args) { 6. SpringApplication.run(DistributedLockApplication.class, args); 7. } 8. }
Stock实体类:
1. @Data 2. @TableName("db_stock") 3. public class Stock { 4. @TableId 5. private Long id; 6. private String productCode; 7. private String stockCode; 8. private Integer count; 9. }
StockMapper接口:
1. public interface StockMapper extends BaseMapper<Stock> { 2. }
1.2. 简单实现减库存
接下来咱们代码实操一下
StockController:
1. @RestController 2. public class StockController { 3. @Autowired 4. private StockService stockService; 5. @GetMapping("check/lock") 6. public String checkAndLock(){ 7. this.stockService.checkAndLock(); 8. return "验库存并锁库存成功!"; 9. } 10. }
StockService:
1. @Service 2. public class StockService { 3. @Autowired 4. private StockMapper stockMapper; 5. 6. public void checkAndLock() { 7. // 先查询库存是否充足 8. Stock stock = this.stockMapper.selectById(1L); 9. // 再减库存 10. if (stock != null && stock.getCount() > 0) { 11. stock.setCount(stock.getCount() - 1); 12. this.stockMapper.updateById(stock); 13. } 14. } 15. }
测试:
查看数据库:
在浏览器中一个一个访问时,每访问一次,库存量减1,没有任何问题。