前言
分布式锁一般有三种实现方式:
数据库乐观锁; 2. 基于 Redis 的分布式锁; 3. 基于 ZooKeeper 的分布式锁.
本篇博客将介绍第二种方式, 基于 Redis 实现分布式锁.
虽然网上已经有各种介绍 Redis 分布式锁实现的博客, 然而他们的实现却有着各种各样的问题, 为了避免误人子弟, 本篇博客将详细介绍如何正确地实现 Redis 分布式锁.
可靠性
首先, 为了确保分布式锁可用, 我们至少要确保锁的实现同时满足以下四个条件:
互斥性: 在任意时刻, 只有一个客户端能持有锁.
不会发生死锁: 即使有一个客户端在持有锁的期间崩溃而没有主动解锁, 也能保证后续其他客户端能加锁.
具有容错性: 只要大部分的 Redis 节点正常运行, 客户端就可以加锁和解锁.
解铃还须系铃人: 加锁和解锁必须是同一个客户端, 客户端自己不能把别人加的锁给解了.
代码实现
一, 引入 Redis 依赖
- <dependency>
- <groupId>Redis.clients</groupId>
- <artifactId>jedis</artifactId>
- <version>2.9.0</version>
- </dependency>
备注: 根据版本不同, jedis 的 set 方法也有所不同
二, 配置文件增加 Redis 配置
- # Redis.properties 配置文件:
- # region Redis jedis
- # Redis 配置开始
- # Redis 数据库索引 (默认为 0)
- spring.Redis.database=0
- # Redis 服务器地址
- spring.Redis.host=localhost
- # Redis 服务器连接端口
- spring.Redis.port=6379
- # Redis 服务器连接密码 (默认为空)
- spring.Redis.password=redis123456
- # 连接池最大连接数 (使用负值表示没有限制)
- spring.Redis.jedis.pool.max-active=1024
- # 连接池最大阻塞等待时间 (使用负值表示没有限制)
- spring.Redis.jedis.pool.max-wait=10000
- # 连接池中的最大空闲连接
- spring.Redis.jedis.pool.max-idle=100
- # 连接池中的最小空闲连接
- spring.Redis.jedis.pool.min-idle=5
- # 连接超时时间 (毫秒)
- spring.Redis.timeout=10000
- # 连接耗尽时是否阻塞, false 报异常, ture 阻塞直到超时
- spring.Redis.block-when-exhausted=true
- # endregion
三, 利用 Spring 把 JedisPool 加入 Bean 工厂
- @Configuration
- @PropertySource("classpath:application.properties")
- @Slf4j
- public class RedisConfig {
- @Value("${spring.redis.host}")
- private String host;
- @Value("${spring.redis.port}")
- private int port;
- @Value("${spring.redis.timeout}")
- private int timeout;
- @Value("${spring.redis.jedis.pool.max-idle}")
- private int maxIdle;
- @Value("${spring.redis.jedis.pool.max-wait}")
- private long maxWaitMillis;
- @Value("${spring.redis.password}")
- private String password;
- @Value("${spring.redis.block-when-exhausted}")
- private boolean blockWhenExhausted;
- @Bean
- public JedisPool redisPoolFactory() throws Exception {
- log.info("JedisPool 注入开始!!");
- log.info("redis 地址:" + host + ":" + port);
- JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
- jedisPoolConfig.setMaxIdle(maxIdle);
- jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
- // 连接耗尽时是否阻塞, false 报异常, ture 阻塞直到超时, 默认 true
- jedisPoolConfig.setBlockWhenExhausted(blockWhenExhausted);
- // 是否启用 pool 的 jmx 管理功能, 默认 true
- jedisPoolConfig.setJmxEnabled(true);
- JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout, password);
- log.info("JedisPool 注入成功!!");
- return jedisPool;
- }
- }
四, 封装 RedisService 对外提供服务
- @Service
- @Slf4j
- public class RedisService {
- //Redis 成功返回结果标识
- private static final String LOCK_SUCCESS = "OK";
- //Reis 操作返回成功
- private static final Long RELEASE_SUCCESS = 1L;
- //Redis 锁不存在时才设置成功
- private static final String SET_IF_NOT_EXIST = "NX";
- //Redis 锁超时时间单位
- private static final String SET_WITH_EXPIRE_TIME = "PX";
- @Autowired
- private JedisPool jedisPool;
- /**
- * 尝试获取分布式锁
- *
- * @param lockKey 锁
- * @param requestId 请求唯一标识 (可以通过 uuid 等方式获取唯一 ID)
- * @param expireTime 超期时间 (毫秒)
- * @return 是否获取成功
- */
- public boolean tryGetDistributedLock(String lockKey, String requestId, int expireTime) {
- Jedis jedis = jedisPool.getResource();
- //Jedis 3.0.0 以前的版本
- //String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
- //Jedis 3.0.0 及以后的版本
- SetParams setParams = SetParams.setParams().nx().px(expireTime);
- String result = jedis.set(lockKey, requestId, setParams);
- return LOCK_SUCCESS.equals(result);
- }
- /**
- * 释放分布式锁
- *
- * @param lockKey 锁
- * @param requestId 请求唯一标识 (加锁时用的唯一标识)
- * @return 是否释放成功
- */
- public boolean releaseDistributedLock(String lockKey, String requestId) {
- Jedis jedis = jedisPool.getResource();
- String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
- Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
- return RELEASE_SUCCESS.equals(result);
- }
- }
五, 简单解释
利用 Redis 的 set 命令的 5 个参数保证操作的原子性
利用 Lua 脚本保证在释放锁时的原子性
利用 requestId 唯一标识保证不会释放别人的锁
来源: https://www.cnblogs.com/liqingjiang/p/12059340.html