什么是分布式锁?
分布式锁是分布式系统中使用的一种机制,用于跨多个节点或进程同步对共享资源或关键部分的访问。分布式锁有助于防止多个节点或进程并发访问或修改共享资源。
技术堆栈
- Spring Boot
- Redis
- Docker
SpringBoot应用程序
锁定操作请求
[GET] http://localhost:8080/perform/{lockKey}
创建 Docker Compose文件
version: '3.8' services: redis: container_name: redis image: redis command: redis-server --appendonly yes ports: - "6379:6379" redis-insight: image: redislabs/redisinsight container_name: redis-insight ports: - 8001:8001
运行docker-compose up -d命令。
依赖关系
Spring Boot 版本:3.1.0
Java 版本:17
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </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> </dependency> <!--Redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> </dependencies>
Spring Boot 启动类
@SpringBootApplication @EnableScheduling public class SpringRedisDistributedLockApplication { public static void main(String[] args) { SpringApplication.run(SpringRedisDistributedLockApplication.class, args); } }
@EnableScheduling:用于在 Spring 应用程序中启用任务调度。
Redis配置
@Configuration public class RedisConfig { @Bean public RedisConnectionFactory redisConnectionFactory() { return new LettuceConnectionFactory("localhost", 6379); } @Bean public RedisTemplate<String, Object> redisTemplate() { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory()); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class)); return template; } }
LockController
@RestController public class LockController { @Autowired private LockService lockService; @GetMapping("/perform/{lockKey}") public String performOperation(@PathVariable String lockKey) throws InterruptedException { lockService.performWithLock(lockKey); return "Operation completed"; } }
LockService
@Service @Slf4j public class LockService { private final RedisDistributedLock lock; @Autowired public LockService(RedisDistributedLock lock) { this.lock = lock; } public void performWithLock(String lockKey) throws InterruptedException { if (lock.acquireLock(lockKey, 15000, TimeUnit.MILLISECONDS)) { log.info("Lock acquired. Operation started."); Thread.sleep(200); log.info("Operation completed."); // if you want, you can release lock. // lock.releaseLock(lockKey); } else { log.error("Failed to acquire lock. Resource is busy."); } } }
Redis分布式锁
@Component public class RedisDistributedLock { private final RedisTemplate<String, Object> redisTemplate; @Autowired public RedisDistributedLock(RedisTemplate<String, Object> redisTemplate) { this.redisTemplate = redisTemplate; } public boolean acquireLock(String lockKey, long timeout, TimeUnit unit) { return redisTemplate.opsForValue().setIfAbsent(lockKey, "locked", timeout, unit); } public void releaseLock(String lockKey) { redisTemplate.delete(lockKey); } }
acquireLock() :该方法以超时和时间单位作为参数,确定锁定的时间长度。如果成功获取锁,该方法返回true。
releaseLock() :该方法负责释放分布式锁。它使用 的 delete 方法来删除指定的键。RedisTemplate
发送 HTTP 请求
ab -n 10 -c 5 “http://localhost:8080/perform/lock-key”
请求计数:10
并发请求计数:5
我们使用第一个 HTTP 请求对密钥执行锁定操作。
1x =>锁定获取
9x ==>锁获取失败
定时服务
@Component @Slf4j public class CronService { private final RedisDistributedLock lock; private final String LOCK_KEY = "lock-key"; @Autowired public CronService(RedisDistributedLock lock) { this.lock = lock; } @Scheduled(fixedDelay = 15000L) private void cronMethod() throws InterruptedException { log.info("Cron job running.."); if (lock.acquireLock(this.LOCK_KEY, 15000, TimeUnit.MILLISECONDS)) { log.info("Lock acquired. Operation started."); Thread.sleep(200); log.info("Operation completed."); } else { log.error("Failed to acquire lock. Resource is busy."); } } }
fixedDelay 属性指定上次执行结束与下一次执行开始之间的时间延迟。
更改服务器端口
server.port=0
我们将端口号设置为 0 以运行多个实例。
允许多个实例
Edit Configurations => Modify options => Allow multiple instances
运行 2 个 Spring Boot 实例
Job 方法触发了 2 个实例,但 if 块中的代码不会同时执行两次。由于 Redis 锁,一项操作始终处于阻塞状态。