什么是分布式锁
在单机环境中,一般在多并发多线程场景下,出现多个线程去抢占一个资源,这个时候会出现线程同步问题,造成执行的结果没有达到预期。我们会用线程间加锁的方式,比如synchronized,lock,volatile,以及JVM并发包中提供的其他工具类去处理此问题。
但是随着技术的发展,分布式系统的出现,各个应用服务都部署在不同节点,由各自的JVM去操控,资源已经不是在 线程 之间的共享,而是变成了 进程 之间的共享,以上解决线程同步问题的办法已经无法满足。
因此,引入了分布式锁的概念。
分布式锁,既在分布式部署的环境下,通过在外部设置锁,让客户端之间互斥,当多应用发生对共享资源的抢占时,该资源同一时刻只能一个应用访问,从而保证数据一致性
分布式锁满足条件
分布式锁的重要性不言而喻,原因不在赘述,每一位菜鸟都有理由掌握它。提到分布式锁,解决方案更是乌泱乌泱的,如:
本文暂时先介绍一种,基于Redission实现的方式
有一个简单的SpringBoot环境即可,便于测试:
依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.6.5</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
配置
server: port: 7077 spring: redis: host: 192.144.228.170 database: 0
启动及配置类
package com.ideax.distributed; import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; @SpringBootApplication public class DistributedApplication { public static void main(String[] args) { SpringApplication.run(DistributedApplication.class,args); } /** * 配置redisson客户端 * @return org.redisson.Redisson * @author zhangxs * @date 2022-01-06 10:01 */ @Bean public Redisson redisson(){ // 单机模式 Config config = new Config(); config.useSingleServer().setAddress("redis://192.144.228.170:6379").setDatabase(0); return (Redisson) Redisson.create(config); } }
package com.ideax.distributed.controller; import org.redisson.Redisson; import org.redisson.api.RLock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Objects; import java.util.UUID; import java.util.concurrent.TimeUnit; /** * 库存 前端控制器 * @author zhangxs * @date 2022-01-06 09:46 */ @RequestMapping("/inventory") @RestController public class InventoryController { @Autowired private StringRedisTemplate redisTemplate; @Autowired private Redisson redisson; @GetMapping("/minus") public ResponseEntity<String> minusInventory(){ // 分布式高并发场景下,这样肯定不行 synchronized (this) { int stock = Integer.parseInt(Objects.requireNonNull(redisTemplate.opsForValue().get("stock"))); if (stock > 0) { int currentStock = stock - 1; redisTemplate.opsForValue().set("stock", currentStock + ""); System.out.println("扣减成功,当前库存为" + currentStock); } else { System.out.println("库存不足,扣减失败!"); } } return ResponseEntity.ok("success"); } @GetMapping("/minus1") public ResponseEntity<String> minusInventory1(){ // 相当于setnx命令 String lockKey = "lockKey"; // 务必加try-finally,因为如果服务挂了,锁还得释放 String clientId = UUID.randomUUID().toString(); try { // 相当于加锁 // Boolean absent = redisTemplate.opsForValue().setIfAbsent(lockKey, "zxs"); // 上下两行不能分开写,如果这中间报异常了,依然出现死锁 // redisTemplate.expire(lockKey,10, TimeUnit.SECONDS); Boolean absent = redisTemplate.opsForValue().setIfAbsent(lockKey, clientId,30,TimeUnit.SECONDS); if (!absent) { return ResponseEntity.notFound().build(); } int stock = Integer.parseInt(Objects.requireNonNull(redisTemplate.opsForValue().get("stock"))); if (stock > 0) { int currentStock = stock - 1; redisTemplate.opsForValue().set("stock", currentStock + ""); System.out.println("扣减成功,当前库存为" + currentStock); } else { System.out.println("库存不足,扣减失败!"); } } finally { // 如果系统挂了呢,finally也不起作用了,因此还需要设置超时时间 // 释放锁之前,判断一下,务必释放的锁是自己持有的锁 if (clientId.equals(redisTemplate.opsForValue().get(lockKey))) { redisTemplate.delete(lockKey); } } return ResponseEntity.ok("success"); } /** * 终极方案 */ @GetMapping("/minus2") public ResponseEntity<String> minusInventory2(){ // redisson解决方案 String lockKey = "lockKey"; RLock lock = redisson.getLock(lockKey); try { // 加锁 lock.lock(); int stock = Integer.parseInt(Objects.requireNonNull(redisTemplate.opsForValue().get("stock"))); if (stock > 0) { int currentStock = stock - 1; redisTemplate.opsForValue().set("stock", currentStock + ""); System.out.println("扣减成功,当前库存为" + currentStock); } else { System.out.println("库存不足,扣减失败!"); } } finally { // 释放锁 lock.unlock(); } return ResponseEntity.ok("success"); } }