SpringBoot整合Redis实现分布式锁
分布式系统为什么要使用分布式锁?
首先,分布式系统是由多个独立节点组成的,这些节点可能运行在不同的物理或虚拟机器上,它们通过网络进行通信和协作。在这样的环境中,多个节点可能同时尝试访问和修改共享资源,这就可能导致数据不一致和并发冲突的问题。
为了解决这个问题,分布式锁被引入作为一种同步机制。分布式锁能够在分布式系统中提供全局唯一的锁,确保在任意时刻只有一个节点能够访问和修改共享资源。当节点需要访问共享资源时,它必须先获取分布式锁,如果锁已经被其他节点持有,则当前节点需要等待或执行其他操作。只有当节点成功获取到锁后,才能对共享资源进行访问和修改。
此外,分布式锁还可以帮助实现一些复杂的分布式系统需求,如分布式事务、分布式缓存一致性等。通过使用分布式锁,我们可以更好地控制并发访问,保证数据的一致性和完整性,提高系统的可靠性和稳定性。
实现分布式锁需要考虑到多种因素,如锁的粒度、锁的公平性、锁的释放机制等。同时,由于分布式系统的复杂性和不确定性,分布式锁的实现也可能存在一些挑战和限制,如网络延迟、节点故障等问题都可能导致锁的行为变得复杂和难以预测。因此,在使用分布式锁时,我们需要根据实际情况进行权衡和选择,确保能够满足系统的需求并避免潜在的问题。
分布式系统使用分布式锁是为了实现资源的同步访问和并发控制,保证数据的一致性和完整性,提高系统的可靠性和稳定性。
redis配置类
package com.test.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @author TANGSHUAI
* @version 1.0
* @date 2023-06-09 10:58
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
//设置序列化Key的实例化对象
redisTemplate.setKeySerializer(new StringRedisSerializer());
//设置序列化Value的实例化对象
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
}
分布式锁工具类
package com.test.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
/**
* @author TANGSHUAI
* @version 1.0
* @date 2024-03-08 14:33
*/
@Component
public class RedisDistributedLock {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 尝试获取分布式锁
*
* @param lockKey 锁
* @param requestId 请求标识
* @param expireTime 超期时间
* @return 是否获取成功
*/
public boolean tryGetDistributedLock(String lockKey, String requestId, int expireTime) {
return stringRedisTemplate.opsForValue().setIfAbsent(lockKey, requestId, expireTime, TimeUnit.SECONDS);
}
/**
* 释放分布式锁
*
* @param lockKey 锁
* @param requestId 请求标识
* @return 是否释放成功
*/
public boolean releaseDistributedLock(String lockKey, String requestId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) " +
"else " +
"return 0 " +
"end";
Long result = stringRedisTemplate.execute(
new DefaultRedisScript<Long>(script, Long.class),
Collections.singletonList(lockKey),
requestId);
System.out.println(result);
return result.equals(1L);
}
}
定时任务测试分布式锁
package com.test.service;
import com.test.config.RedisDistributedLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @author TANGSHUAI
* @version 1.0
* @date 2024-03-08 14:44
*/
@Service
public class TestService {
@Autowired
private RedisDistributedLock redisDistributedLock;
@Scheduled(cron = "0/10 * * * * ?")
//@Scheduled(cron = "0 10 16 * * ?")
@Transactional(rollbackFor = Exception.class)
public void testLock(){
System.out.println("进入分布式锁方法");
// 尝试获取分布式锁
if (redisDistributedLock.tryGetDistributedLock("tangshuai", "123", 30)) {
System.out.println("释放分布式锁");
redisDistributedLock.releaseDistributedLock("tangshuai", "123");
} else {
// 未能获取到锁,可以选择重试或返回失败
throw new RuntimeException("未能获取到锁,请重试");
}
}
}
分别启动两个相同的服务
package com.test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* @author TANGSHUAI
* @version 1.0
* @date 2023-06-08 17:04
*/
@SpringBootApplication
@EnableScheduling
public class SaTokenDemoApp {
public static void main(String[] args) {
SpringApplication.run(SaTokenDemoApp.class, args);
}
}
查看两个控制台打印情况,两个服务谁谁拿到锁,谁开始执行业务代码,没拿到锁的服务抛出异常