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 {@Beanpublic 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 {@Autowiredprivate 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 {@Autowiredprivate 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);}
}
查看两个控制台打印情况,两个服务谁谁拿到锁,谁开始执行业务代码,没拿到锁的服务抛出异常