redis哨兵模式分布式锁实现与实践方式(redisson)
作者:喵喵@香菜
这篇文章主要介绍了redis哨兵模式分布式锁实现与实践方式(redisson),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
一、前言
在某个线程操作数据库中的某条数据时,我们需要确保当前时刻只有一个线程在操作这条记录,如果有两个线程竞争同一个数据,就需要在考虑先后执行顺序以后,那么怎样在一个线程拿到这条数据时,阻塞其他线程操作呢?
分布式锁就可以解决上述难题。
以下演示是利用分布式锁,确保同一时间只有一个线程在操作数据库,阻塞其他线程。
环境:
- redis(哨兵模式)
- spring boot
二、redis的配置(注意是哨兵模式)
1)依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.zlc</groupId> <artifactId>distributedlock-a</artifactId> <version>0.0.1-SNAPSHOT</version> <name>distributedlock-a</name> <description>分布式锁(哨兵模式)</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.3.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
2)redis配置
server: port: 8081 spring: redis: sentinel: master: testmaster nodes: 127.0.0.1:26379,127.0.0.1:36379,127.0.0.1:16379 timeout: 3000 # 超时时间(数据处理超时时间,不是连接超时时间) lettuce: pool: max-active: 200 #连接池最大连接数(使用负值表示没有限制) max-idle: 20 #连接池中的最大空闲连接 min-idle: 5 #连接池中的最小空闲连接 max-wait: -1 #连接池最大阻塞等待时间(使用负值表示没有限制) password: 123456 #redis 密码 database: 1 # 使用的是库1,如果不配置,则使用默认的0
三、代码实战
根据上面的配置文件,将redis的各个配置转换为实体对象
1)sentinel 节点
package com.zlc.distributedlocka.model.redis; import lombok.Data; import lombok.ToString; /** * @author : 追到乌云的尽头找太阳-(Jacob) * @date : 2020/1/20 11:13 **/ @Data @ToString public class RedisSentinelModel { private String master; private String nodes; }
2)pool节点
package com.zlc.distributedlocka.model.redis; import lombok.Data; import lombok.ToString; /** * @author : 追到乌云的尽头找太阳-(Jacob) * @date : 2020/1/20 11:16 **/ @Data @ToString public class RedisPoolModel { private int maxIdle; private int minIdle; private int maxActive; private int maxWait; }
3)Lettuce 节点
package com.zlc.distributedlocka.model.redis; import lombok.Data; /** * @author : 追到乌云的尽头找太阳-(Jacob) * @date : 2020/1/20 11:18 **/ @Data public class RedisLettuceConfig { private RedisPoolModel redisPoolModel; }
4)redis
package com.zlc.distributedlocka.model.redis; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; /** * @author : 追到乌云的尽头找太阳-(Jacob) * @date : 2020/1/20 11:21 **/ @Data @Configuration @ConfigurationProperties(prefix = "spring.redis") public class RedisModel { private int database; /** * 等待节点回复命令的时间。该时间从命令发送成功时开始计时 **/ private int timeout; private String password; /** * 池配置 */ private RedisLettuceModel lettuce; /** * 哨兵配置 */ private RedisSentinelModel sentinel; }
5)redisson
package com.zlc.distributedlocka.config; import com.zlc.distributedlocka.model.redis.RedisModel; import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.redisson.config.ReadMode; import org.redisson.config.SentinelServersConfig; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * @author : 追到乌云的尽头找太阳-(Jacob) * @date : 2020/1/20 11:27 **/ @Configuration @EnableConfigurationProperties(RedisModel.class) public class RedissonConfig { private final RedisModel redisModel; public RedissonConfig(RedisModel redisModel) { this.redisModel = redisModel; } @Bean public RedissonClient redissonClient(){ Config config = new Config(); String [] nodes = redisModel.getSentinel().getNodes().split(","); List<String> newNodes = new ArrayList<>(nodes.length); newNodes.addAll(Arrays.asList(nodes)); SentinelServersConfig serverConfig = config.useSentinelServers() .addSentinelAddress(newNodes.toArray(new String[0])) .setMasterName(redisModel.getSentinel().getMaster()) .setReadMode(ReadMode.SLAVE) .setTimeout(redisModel.getTimeout()); // 设置密码 if(StringUtils.isNotBlank(redisModel.getPassword())){ serverConfig.setPassword(redisModel.getPassword()); } // 设置database if (redisModel.getDatabase()!=0){ serverConfig.setDatabase(redisModel.getDatabase()); } return Redisson.create(config); } }
四、使用
一个简单的使用方法示例
package com.zlc.distributedlocka; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import java.util.concurrent.TimeUnit; @SpringBootApplication public class DistributedlockAApplication { @Autowired private RedissonClient redissonClient; public static void main(String[] args) { SpringApplication.run(DistributedlockAApplication.class, args); } // 这里的锁是对哨兵模式下的database生效的, // 需要分布式锁的两个系统一定要使用同一哨兵模式的database // 如果一个使用默认0,一个使用1或者其他,是锁不住的 private void TestLock(){ boolean lockFlag = false; RLock rLock = null; try { // 使用redis中的某个key值作为获取分布式锁 rLock = redissonClient.getLock("redisKey"); // 第一个参数为等待时间,第二个参数为占有时间(单位都为毫秒) // 等待时间为如果没有通过redisKey获取到锁,则等待1s,1s后还没获取到锁,则tryLock返回false,表明有人正在使用 // 如果直接获取到锁了,则表明没有人使用,设置了你占有他的时间为5s lockFlag = rLock.tryLock(1000, 5000, TimeUnit.MILLISECONDS); if (lockFlag){ // 获取到锁,进行数据处理或者其他操作 }else { // 没有获取到锁,进行一些操作 } }catch (Exception e){ e.printStackTrace(); }finally { // 如果锁没有释放,手动释放锁 // 注意是使用isHeldByCurrentThread if (lockFlag && rLock.isHeldByCurrentThread()){ rLock.unlock(); } } } }
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。