本系列文章,笔者准备对互联网缓存利器Redis的使用,做一下简单的总结,内容大概如下:
博文内容 | 资源链接 |
---|---|
Linux环境下搭建Redis基础运行环境 | https://blog.csdn.net/smilehappiness/article/details/107298145 |
互联网缓存利器-Redis的使用详解(基础篇) | https://blog.csdn.net/smilehappiness/article/details/107592368 |
Redis基础命令使用Api详解 | https://blog.csdn.net/smilehappiness/article/details/107593218 |
Redis编程客户端Jedis、Lettuce和Redisson的基础使用 | https://blog.csdn.net/smilehappiness/article/details/107301988 |
互联网缓存利器-Redis的使用详解(进阶篇) | https://blog.csdn.net/smilehappiness/article/details/107592336 |
如何基于Redis实现分布式锁 |
https://blog.csdn.net/smilehappiness/article/details/107592896 |
基于Redis的主从复制、哨兵模式以及集群的使用,史上最详细的教程来啦~ | https://blog.csdn.net/smilehappiness/article/details/107433525 |
Redis相关的面试题总结 | https://blog.csdn.net/smilehappiness/article/details/107592686 |
使用分布式锁的目的,为了保证一个方法在高并发情况下,同一时间只能被同一个线程执行,即保证同一时间只有一个客户端可以对共享资源进行操作
。
在传统单体应用单机部署的情况下,可以使用Java并发处理相关的API(如ReentrantLcok或synchronized)进行互斥控制
。但是,随着业务发展的需要,原单体单机部署的系统被演化成分布式系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问
,这就是分布式锁要解决的问题。
使用Spring提供的redisTemplate,操作redis还是比较简单的,使用RedisTemplate如何实现分布式锁呢?
这里,建议使用2.1.0以上的版本,因为Redis在2.1.0以上版本,已经实现了原子性操作,在设置key、value时,直接可以设置超时时间
<dependency>
<groupId>org.springframework.datagroupId>
<artifactId>spring-data-redisartifactId>
<version>2.1.0.RELEASEversion>
dependency>
<dependency>
<groupId>io.lettucegroupId>
<artifactId>lettuce-coreartifactId>
<version>5.2.2.RELEASEversion>
dependency>
//使用RedisTemplate实现分布式锁
private final RedisLockHelper redisLockHelper;
public GoodsServiceImpl(RedisLockHelper redisLockHelper) {
this.redisLockHelper = redisLockHelper;
}
【redis配置】
#ip地址
redis.hostName=127.0.0.1
#端口号
redis.port=6379
#如果有密码
redis.password=123456
redis.database=0
【spring配置文件】
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="cn.smilehappiness.distributed"/>
<context:property-placeholder location="classpath:redis.properties" ignore-unresolvable="true"/>
<bean id="lettuceConnectionFactory"
class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory">
<constructor-arg index="0" ref="redisStandaloneConfiguration"/>
bean>
<bean id="redisStandaloneConfiguration"
class="org.springframework.data.redis.connection.RedisStandaloneConfiguration">
<property name="hostName" value="${redis.hostName}"/>
<property name="port" value="${redis.port}"/>
<property name="database" value="${redis.database}"/>
<property name="password" ref="redisPassword"/>
bean>
<bean id="redisPassword" class="org.springframework.data.redis.connection.RedisPassword">
<constructor-arg index="0" value="${redis.password}"/>
bean>
<bean id="keySerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
<bean id="valueSerializer" class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="lettuceConnectionFactory"/>
<property name="keySerializer" ref="keySerializer"/>
<property name="valueSerializer" ref="valueSerializer"/>
<property name="hashKeySerializer" ref="keySerializer"/>
<property name="hashValueSerializer" ref="valueSerializer"/>
bean>
beans>
如果你是spring boot项目,参考网上配置即可,网上资源有很多。
下面,重点来啦,如何实现分布式锁?
【分布式锁工具类】
package cn.smilehappiness.distributed.lock.redistemplate;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
*
* Redis中,基于redisTemplate的分布式锁
* 注解@Order或者接口Ordered的作用是定义Spring IOC容器中Bean的执行顺序的优先级(默认是最低优先级,值越小优先级越高),而不是定义Bean的加载顺序
*
*
* @author smilehappiness
* @Date 2020/8/2 13:53
*/
@Order(1)
@Component
public class RedisLockHelper {
private static Logger logger = LoggerFactory.getLogger(RedisLockHelper.class);
/**
* 设置分布式锁业务前缀
*/
private static final String REDIS_LOCK_PREFIX = "redis:lock:";
/**
* 设置分布式锁超时(过期)时间,单位是秒
*/
private static final Long LOCK_TIME_OUT = 60L;
/**
* 获取分布式锁超时时间,单位是秒,如果指定时间内还未获取到锁,则不能进行业务处理
*/
private static final Long ACQUIRE_LOCK_TIME_OUT = 15L;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
*
* 创建RedisLockHelper对象的时候,监听redis节点key是否需要续期(不推荐使用)
* 这里进行redis分布式锁的续期,针对业务执行时,如果超过了锁超时时间的一半还没有释放锁,说明该业务方法比较耗时,进行自动续期。
* 防止业务未执行完,锁过期了导致自动释放锁,造成业务数据问题
*
* 注意:通常情况下不需要考虑续期问题,如果业务方法确实执行的比较耗时,才考虑此种问题
*
*
* @param
* @return
* @Date 2020/8/2 17:18
*/
public RedisLockHelper() {
//分布式锁的自动续期,因为锁超时时间内,可能还没有执行完业务处理
//启动一个后台任务,定时去检查redis的分布式锁是否需要续期(过期时间往后延一下)
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
scheduledExecutorService.scheduleAtFixedRate(this::checkRedisExpire, 5, 10, TimeUnit.SECONDS);
}
private void checkRedisExpire() {
Set<String> keys = redisTemplate.keys(REDIS_LOCK_PREFIX + "*");
keys.forEach(key -> {
//目前redis中锁剩下的过期时间
//从redis中获取key对应的过期时间:如果该值有过期时间,就返回相应的过期时间,如果该值没有设置过期时间,就返回-1,如果没有该值,就返回-2
Long expireTime = redisTemplate.opsForValue().getOperations().getExpire(key);
//如果小于一半过期时间,因为还没有执行完,延长过期时间
if (expireTime >= -1 && expireTime <= LOCK_TIME_OUT / 2) {
redisTemplate.expire(key, LOCK_TIME_OUT, TimeUnit.SECONDS);
System.out.println("执行redisTemplate分布式锁【" + key + "】续期......");
}
});
}
/**
*
* Redis老版本实现方案(2.1以下)
*
*
* @param lockName
* @return java.lang.String
* @Date 2020/8/2 15:40
*/
public String getLockOld(String lockName) {
String redisLockKey = REDIS_LOCK_PREFIX + lockName;
String uniqueValue = UUID.randomUUID().toString();
//往后延acquireLockTimeOut秒(比如30秒)
Long endTime = System.currentTimeMillis() + ACQUIRE_LOCK_TIME_OUT * 1000;
//如果不超过指定的锁获取时间,有资格重复获取锁
while (System.currentTimeMillis() < endTime) {
// 注意:这里不是原子操作(不在一个步骤中,是分了两步),有可能会有小问题(如果设置了锁还没来得及设置过期时间,redis服务挂了,可能导致死锁问题)
//解决方案:下边判断过期时间,如果没有设置超时时间,来避免死锁问题
if (redisTemplate != null && !redisTemplate.hasKey(redisLockKey) && redisTemplate.opsForValue().setIfAbsent(redisLockKey, uniqueValue)) {
redisTemplate.expire(redisLockKey, LOCK_TIME_OUT, TimeUnit.SECONDS);
return uniqueValue;
}
/**
* 从redis中获取key对应的过期时间:
* 如果该值有过期时间,就返回相应的过期时间
* 如果该值没有设置过期时间,就返回-1
* 如果没有该值,就返回-2
*/
Long expireTime = redisTemplate.opsForValue().getOperations().getExpire(redisLockKey);
if (expireTime != null && expireTime == -1) {
//设置过期时间
redisTemplate.expire(redisLockKey, LOCK_TIME_OUT, TimeUnit.SECONDS);
}
try {
//立刻马上去循环再获取锁,其实不是很好也没有什么意义,最好是稍等片刻再去重试获取锁(这里可以根据实际场景去设计,是否需要重试获取锁,如果不需要,就不用设置while循环)
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
/**
*
* Redis老版本实现方案(2.1以下),老版本未提供原子操作,自己实现原子操作
* 注:在2.1以上版本,已经实现了原子性操作,无需自己实现
*
*
* @param lockName
* @return java.lang.String
* @Date 2020/8/2 16:30
*/
public String getLockOldTwo(String lockName) {
try {
String redisLockKey = REDIS_LOCK_PREFIX + lockName;
String uniqueValue = UUID.randomUUID().toString();
//往后延acquireLockTimeOut秒(比如30秒)
Long endTime = System.currentTimeMillis() + ACQUIRE_LOCK_TIME_OUT * 1000;
//如果不超过指定的锁获取时间,有资格获取锁
while (System.currentTimeMillis() < endTime) {
// 上面方案中,虽然可以解决可能的死锁问题,但是,既然本质上是因为不是原子操作导致的问题,那么,能不能改成原子操作呢?
if (redisTemplate != null && !redisTemplate.hasKey(redisLockKey) && this.setIfAbsent(redisLockKey, uniqueValue, LOCK_TIME_OUT, TimeUnit.SECONDS)) {
return uniqueValue;
}
/**
* 从redis中获取key对应的过期时间:
* 如果该值有过期时间,就返回相应的过期时间,如果该值没有设置过期时间,就返回-1
* 如果没有该值,就返回-2
* 这里由于上面可以保证原子性操作了,所以这里可以不用再判断是否有设置过过期时间
*/
Long expireTime = redisTemplate.opsForValue().getOperations().getExpire(redisLockKey);
if (expireTime != null && expireTime == -1) {
//设置过期时间
redisTemplate.expire(redisLockKey, LOCK_TIME_OUT, TimeUnit.SECONDS);
}
try {
//立刻马上去循环再获取锁,其实不是很好也没有什么意义,最好是稍等片刻再去重试获取锁(这里可以根据实际场景去设计,是否需要重试获取锁,如果不需要,就不用设置while循环)
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
*
* Set {@code key} to hold the string {@code value} and expiration {@code timeout} if {@code key} is absent
*
*
* @param key
* @param value
* @param timeout
* @param unit
* @return java.lang.Boolean
* @Date 2020/3/13 11:03
*/
private Boolean setIfAbsent(Object key, Object value, long timeout, TimeUnit unit) {
byte[] rawKey = rawKey(key);
byte[] rawValue = rawValue(value);
Expiration expiration = Expiration.from(timeout, unit);
return redisTemplate.execute(connection -> connection.set(rawKey, rawValue, expiration, RedisStringCommands.SetOption.ifAbsent()), true);
}
byte[] rawKey(Object key) {
Assert.notNull(key, "non null key required");
return this.keySerializer() == null && key instanceof byte[] ? (byte[]) ((byte[]) key) : this.keySerializer().serialize(key);
}
byte[] rawValue(Object value) {
return this.valueSerializer() == null && value instanceof byte[] ? (byte[]) ((byte[]) value) : this.valueSerializer().serialize(value);
}
RedisSerializer keySerializer() {
return this.redisTemplate.getKeySerializer();
}
RedisSerializer valueSerializer() {
return this.redisTemplate.getValueSerializer();
}
/**
*
* Redis在2.1.0以上版本,已经实现了原子性操作,无需自己实现
*
*
* @param lockName
* @return java.lang.String
* @Date 2020/8/2 17:30
*/
public String getLock(String lockName) {
try {
String redisLockKey = REDIS_LOCK_PREFIX + lockName;
String uniqueValue = UUID.randomUUID().toString();
//往后延acquireLockTimeOut秒(比如30秒)
Long endTime = System.currentTimeMillis() + ACQUIRE_LOCK_TIME_OUT * 1000;
//如果不超过指定的锁获取时间,有资格获取锁
while (System.currentTimeMillis() < endTime) {
// 上面方案中,虽然可以解决可能的死锁问题,但是,既然本质上是因为不是原子操作导致的问题,那么,能不能改成原子操作呢?
if (redisTemplate != null && !redisTemplate.hasKey(redisLockKey) && redisTemplate.opsForValue().setIfAbsent(redisLockKey, uniqueValue, LOCK_TIME_OUT, TimeUnit.SECONDS)) {
return uniqueValue;
}
/**
* 从redis中获取key对应的过期时间:
* 如果该值有过期时间,就返回相应的过期时间,如果该值没有设置过期时间,就返回-1
* 如果没有该值,就返回-2
* 这里由于上面可以保证原子性操作了,所以这里可以不用再判断是否有设置过过期时间
*/
Long expireTime = redisTemplate.opsForValue().getOperations().getExpire(redisLockKey);
if (expireTime != null && expireTime == -1) {
//设置过期时间
redisTemplate.expire(redisLockKey, LOCK_TIME_OUT, TimeUnit.SECONDS);
}
try {
//立刻马上去循环再获取锁,其实不是很好也没有什么意义,最好是稍等片刻再去重试获取锁(这里可以根据实际场景去设计,是否需要重试获取锁,如果不需要,就不用设置while循环)
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
*
* 释放分布式锁,保证只能释放自己的锁(自己的锁自己解,不要把别人的锁给解了)
*
*
* @param lockName
* @param value
* @return java.lang.Boolean
* @Date 2020/8/2 16:57
*/
public Boolean releaseLock(String lockName, String value) {
//redis锁的key
String redisLockKey = REDIS_LOCK_PREFIX + lockName;
//保证只能释放自己的锁(自己的锁自己解,不要把别人的锁给解了)
Object object = redisTemplate.opsForValue().get(redisLockKey);
if (object != null && StringUtils.equals(value, String.valueOf(object))) {
return redisTemplate.delete(redisLockKey);
}
return false;
}
}
/**
*
* 分布式锁测试
*
*
* @param
* @return void
* @Date 2020/8/2 20:56
*/
private void testDistributedLock() {
String lockName = "lockName";
String lockUniqueValue = null;
try {
//获取分布式锁,然后下面的业务代码就会按顺序排队执行
lockUniqueValue = redisLockHelper.getLock(lockName);
//拿到redis分布式锁
if (lockUniqueValue != null) {
//TODO 执行业务代码
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
//释放分布式锁
redisLockHelper.releaseLock(lockName, lockUniqueValue);
}
}
以上,就基于RedisTemplate模板,实现了分布式锁。有需要的童鞋们,可以实际操作一下,该方案适用于多线程且高并发的场景
。
<dependency>
<groupId>org.springframework.datagroupId>
<artifactId>spring-data-redisartifactId>
<version>2.1.10.RELEASEversion>
dependency>
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>3.3.0version>
dependency>
package cn.smilehappiness.distributed.lock.jedis.util;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
*
* 获取Jedis实例对象
*
*
* @author smilehappiness
* @Date 2020/8/2 11:58
*/
public class JedisPoolInstance {
/**
* redis服务器的ip地址
*/
private static final String HOST = "localhost";
/**
* redis服务器的端口
*/
private static final int PORT = 6379;
/**
* 连接redis服务器的密码
*/
private static final String PASSWORD = "123456";
private static final int TIMEOUT = 10000;
/**
* redis连接池对象,单例的连接池对象
*/
private static JedisPool jedisPool = null;
//私有构造方法
private JedisPoolInstance() {
}
/**
* 获取线程池实例对象
*
* @return
*/
public static JedisPool getJedisPoolInstance() {
//双重检测锁
if (null == jedisPool) {
synchronized (JedisPoolInstance.class) {
if (null == jedisPool) {
//对连接池的参数进行配置,根据项目的实际情况配置这些参数
JedisPoolConfig poolConfig = new JedisPoolConfig();
//最大连接数
poolConfig.setMaxTotal(1000);
//最大空闲连接数
poolConfig.setMaxIdle(32);
//获取连接时的最大等待毫秒数
poolConfig.setMaxWaitMillis(90 * 1000);
//在获取连接的时候检查连接有效性
poolConfig.setTestOnBorrow(true);
jedisPool = new JedisPool(poolConfig, HOST, PORT, TIMEOUT, PASSWORD);
}
}
}
return jedisPool;
}
}
【redis配置】
#ip地址
redis.hostName=127.0.0.1
#端口号
redis.port=6379
#如果有密码
redis.password=123456
redis.database=0
【Spring配置】
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:redis.properties" ignore-unresolvable="true"/>
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<constructor-arg index="0" ref="redisStandaloneConfiguration"/>
bean>
<bean id="redisStandaloneConfiguration"
class="org.springframework.data.redis.connection.RedisStandaloneConfiguration">
<property name="hostName" value="${redis.hostName}"/>
<property name="port" value="${redis.port}"/>
<property name="database" value="${redis.database}"/>
<property name="password" ref="redisPassword"/>
bean>
<bean id="redisPassword" class="org.springframework.data.redis.connection.RedisPassword">
<constructor-arg index="0" value="${redis.password}"/>
bean>
<bean id="keySerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
<bean id="valueSerializer" class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory"/>
<property name="keySerializer" ref="keySerializer" />
<property name="valueSerializer" ref="valueSerializer" />
<property name="hashKeySerializer" ref="keySerializer" />
<property name="hashValueSerializer" ref="valueSerializer" />
bean>
beans>
package cn.smilehappiness.distributed.lock.jedis;
import cn.smilehappiness.distributed.lock.jedis.util.JedisPoolInstance;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
*
* 基于Jedis客户端,实现分布式锁
*
*
* @author smilehappiness
* @Date 2020/8/2 12:05
*/
public class JedisDistributeLock {
private static final String redisLockPrefix = "redis:lock:";
/**
* 设置锁超时时间,单位是毫秒
*/
private static final Long LockTimeOut = 30000L;
/**
* 静态代码块在类加载的时候执行,只会一次
*/
static {
//分布式锁的自动续期,因为锁超时时间内,可能还没有执行完业务处理
//启动一个后台任务,定时去检查redis的分布式锁是否需要续期(过期时间往后延一下)
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
scheduledExecutorService.scheduleAtFixedRate(() -> {
System.out.println("执行Jedis分布式锁续期..........");
Jedis jedis = JedisPoolInstance.getJedisPoolInstance().getResource();
try {
Set<String> setKeys = jedis.keys(redisLockPrefix + "*");
setKeys.forEach((String key) -> {
//目前redis中锁剩下的过期时间
//key不存在的时候返回-2
Long leftExpire = jedis.ttl(key);
if (leftExpire >= -1 && leftExpire <= (LockTimeOut - 10000) / 1000) {
jedis.pexpire(key, LockTimeOut);
}
});
} finally {
if (jedis != null) {
jedis.close();
}
}
},
5,
3,
TimeUnit.SECONDS);
}
/**
*
* 获取分布式锁
*
*
* @param lockName
* @param acquireTimeOut 单位是毫秒
* @param lockTimeOut 单位是毫秒
* @return java.lang.String
* @Date 2020/8/2 12:13
*/
public String getRedisLock(String lockName, Long acquireTimeOut, Long lockTimeOut) {
String redisLockKey = redisLockPrefix + lockName;
String uniqueValue = UUID.randomUUID().toString();
JedisPool jedisPool = JedisPoolInstance.getJedisPoolInstance();
Jedis jedis = jedisPool.getResource();
System.out.println("获取jedis连接池:" + Thread.currentThread().getName() + ":" + jedisPool + ", " + jedisPool.getNumActive() + ", " + jedisPool.getNumIdle());
try {
//往后延acquireTimeOut秒(比如3秒)
Long endTime = System.currentTimeMillis() + acquireTimeOut;
//如果不超过指定的锁获取时间,有资格获取锁
while (System.currentTimeMillis() < endTime) {
// 设置key和设置key的过期时间
// 注意:这里不是原子操作(不在一个步骤中,是分了两步),有可能会有小问题(如果设置了锁还没来得及设置过期时间,redis服务挂了,可能导致死锁问题,下边双重判断过期时间,如果没有设置超时时间,来避免死锁问题)
if (jedis.setnx(redisLockKey, uniqueValue) == 1) {
//设置key成功,表示拿到锁
jedis.pexpire(redisLockKey, lockTimeOut);
return uniqueValue;
}
//这里如果不做处理,可能产生死锁,因为上边不是原子操作
if (jedis.ttl(redisLockKey) == -1) {
//设置过期时间
jedis.pexpire(redisLockKey, lockTimeOut);
}
try {
//立刻马上去循环再获取锁,其实不是很好也没有什么意义,最好是稍等片刻再去重试获取锁
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
if (jedis != null) {
jedis.close();
System.out.println("关闭jedis连接池:" + Thread.currentThread().getName() + ":" + jedisPool + ", " + jedisPool.getNumActive() + ", " + jedisPool.getNumIdle());
}
}
return null;
}
/**
*
* 释放redis锁
*
*
* @param lockName
* @param uniqueValue
* @return void
* @Date 2020/8/2 12:12
*/
public void releaseRedisLock(String lockName, String uniqueValue) {
//redis锁的key
String redisLockKey = redisLockPrefix + lockName;
Jedis jedis = JedisPoolInstance.getJedisPoolInstance().getResource();
try {
//保证只能释放自己的锁(自己的锁自己解,不要把别人的锁给解了)
if (jedis.get(redisLockKey).equals(uniqueValue)) {
jedis.del(redisLockKey);
}
} finally {
if (jedis != null) {
jedis.close();
}
}
}
}
/**
*
* 分布式锁测试
*
*
* @param
* @return void
* @Date 2020/8/2 20:56
*/
private void testDistributedLock() {
//使用jedis客户端实现的redis锁
JedisDistributeLock redisDistributeLock = new JedisDistributeLock();
String lockName = "lockName";
String lockUniqueValue = null;
try {
//获取分布式锁,然后下面的业务代码就会按顺序排队执行
lockUniqueValue = redisDistributeLock.getRedisLock(lockName, 3000L, 30000L);
//拿到redis分布式锁
if (lockUniqueValue != null) {
//TODO 执行业务代码
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
//释放分布式锁
redisDistributeLock.releaseRedisLock(lockName, lockUniqueValue);
}
}
<dependency>
<groupId>org.springframework.datagroupId>
<artifactId>spring-data-redisartifactId>
<version>2.1.10.RELEASEversion>
dependency>
<dependency>
<groupId>io.lettucegroupId>
<artifactId>lettuce-coreartifactId>
<version>5.2.2.RELEASEversion>
dependency>
package cn.smilehappiness.distributed.lock.lettuce.util;
import io.lettuce.core.RedisClient;
import io.lettuce.core.api.StatefulRedisConnection;
public class LettuceConnection {
private static final String REDIS_ADDRESS = "redis://123456@localhost:6379/0";
private static StatefulRedisConnection<String, String> statefulRedisConnection = null;
private LettuceConnection() {
}
public static StatefulRedisConnection<String, String> getStatefulRedisConnection() {
//双重检测锁
if (null == statefulRedisConnection) {
synchronized (LettuceConnection.class) {
if (null == statefulRedisConnection) {
RedisClient redisClient = RedisClient.create(REDIS_ADDRESS);
statefulRedisConnection = redisClient.connect();
}
}
}
return statefulRedisConnection;
}
}
【redis配置】
#ip地址
redis.hostName=127.0.0.1
#端口号
redis.port=6379
#如果有密码
redis.password=123456
redis.database=0
【Spring配置】
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="cn.smilehappiness.distributed"/>
<context:property-placeholder location="classpath:redis.properties" ignore-unresolvable="true"/>
<bean id="lettuceConnectionFactory"
class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory">
<constructor-arg index="0" ref="redisStandaloneConfiguration"/>
bean>
<bean id="redisStandaloneConfiguration"
class="org.springframework.data.redis.connection.RedisStandaloneConfiguration">
<property name="hostName" value="${redis.hostName}"/>
<property name="port" value="${redis.port}"/>
<property name="database" value="${redis.database}"/>
<property name="password" ref="redisPassword"/>
bean>
<bean id="redisPassword" class="org.springframework.data.redis.connection.RedisPassword">
<constructor-arg index="0" value="${redis.password}"/>
bean>
<bean id="keySerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
<bean id="valueSerializer" class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="lettuceConnectionFactory"/>
<property name="keySerializer" ref="keySerializer"/>
<property name="valueSerializer" ref="valueSerializer"/>
<property name="hashKeySerializer" ref="keySerializer"/>
<property name="hashValueSerializer" ref="valueSerializer"/>
bean>
beans>
package cn.smilehappiness.distributed.lock.lettuce;
import cn.smilehappiness.distributed.lock.lettuce.util.LettuceConnection;
import io.lettuce.core.api.sync.RedisCommands;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
*
* 基于Lettuce客户端,实现分布式锁
*
*
* @author smilehappiness
* @Date 2020/8/2 12:05
*/
public class LettuceDistributeLock {
private static final String redisLockPrefix = "redis:lock:";
/**
* 设置锁超时时间,单位是毫秒
*/
private static final Long LockTimeOut = 30000L;
/**
* 静态代码块在类加载的时候执行,只会一次
*/
static {
//分布式锁的自动续期,因为锁超时时间内,可能还没有执行完业务处理
//启动一个后台任务,定时去检查redis的分布式锁是否需要续期(过期时间往后延一下)
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
scheduledExecutorService.scheduleAtFixedRate(() -> {
System.out.println("执行Lettuce分布式锁续期..........");
RedisCommands<String, String> syncCommands = LettuceConnection.getStatefulRedisConnection().sync();
List<String> setKeys = syncCommands.keys(redisLockPrefix + "*");
setKeys.forEach((String key) -> {
//目前redis中锁剩下的过期时间
//key不存在的时候返回-2
Long leftExpire = syncCommands.ttl(key);
if (leftExpire >= -1 && leftExpire <= (LockTimeOut - 10000) / 1000) {
syncCommands.pexpire(key, LockTimeOut);
}
});
},
5,
3,
TimeUnit.SECONDS);
}
/**
*
* 获取分布式锁
*
*
* @param lockName
* @param acquireTimeOut 单位是毫秒
* @param lockTimeOut 单位是毫秒
* @return java.lang.String
* @Date 2020/8/2 12:13
*/
public String getRedisLock(String lockName, Long acquireTimeOut, Long lockTimeOut) {
String redisLockKey = redisLockPrefix + lockName;
String uniqueValue = UUID.randomUUID().toString();
RedisCommands<String, String> syncCommands = LettuceConnection.getStatefulRedisConnection().sync();
//往后延acquireTimeOut秒(比如3秒)
Long endTime = System.currentTimeMillis() + acquireTimeOut;
//如果不超过指定的锁获取时间,有资格获取锁
while (System.currentTimeMillis() < endTime) {
// 设置key和设置key的过期时间
// 注意:这里不是原子操作(不在一个步骤中,是分了两步),有可能会有小问题(如果设置了锁还没来得及设置过期时间,redis服务挂了,可能导致死锁问题,下边双重判断过期时间,如果没有设置超时时间,来避免死锁问题)
if (syncCommands.setnx(redisLockKey, uniqueValue)) {
//设置key成功,表示拿到锁
syncCommands.pexpire(redisLockKey, lockTimeOut);
return uniqueValue;
}
if (syncCommands.ttl(redisLockKey) == -1) {
//设置过期时间
syncCommands.pexpire(redisLockKey, lockTimeOut);
}
try {
//立刻马上去循环再获取锁,其实不是很好也没有什么意义,最好是稍等片刻再去重试获取锁
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
/**
*
* 释放redis锁
*
*
* @param lockName
* @param uniqueValue
* @return void
* @Date 2020/8/2 12:12
*/
public void releaseRedisLock(String lockName, String uniqueValue) {
//redis锁的key
String redisLockKey = redisLockPrefix + lockName;
RedisCommands<String, String> syncCommands = LettuceConnection.getStatefulRedisConnection().sync();
//保证只能释放自己的锁(自己的锁自己解,不要把别人的锁给解了)
if (syncCommands.get(redisLockKey).equals(uniqueValue)) {
syncCommands.del(redisLockKey);
}
}
}
/**
*
* 分布式锁测试
*
*
* @param
* @return void
* @Date 2020/8/2 20:56
*/
private void testDistributedLock() {
//使用Lettuce客户端实现的分布式锁
LettuceDistributeLock redisDistributeLock = new LettuceDistributeLock();
String lockName = "lockName";
String lockUniqueValue = null;
try {
//获取分布式锁,然后下面的业务代码就会按顺序排队执行
lockUniqueValue = redisDistributeLock.getRedisLock(lockName, 3000L, 30000L);
//拿到redis分布式锁
if (lockUniqueValue != null) {
//TODO 执行业务代码
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
//释放分布式锁
redisDistributeLock.releaseRedisLock(lockName, lockUniqueValue);
}
}
<dependency>
<groupId>org.springframework.datagroupId>
<artifactId>spring-data-redisartifactId>
<version>2.1.10.RELEASEversion>
dependency>
<dependency>
<groupId>org.redissongroupId>
<artifactId>redissonartifactId>
<version>3.12.5version>
dependency>
【redis配置】
#ip地址
redis.hostName=127.0.0.1
#端口号
redis.port=6379
#如果有密码
redis.password=123456
redis.database=0
【Spring配置】
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:redisson="http://redisson.org/schema/redisson"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://redisson.org/schema/redisson
http://redisson.org/schema/redisson/redisson.xsd">
<redisson:client>
<redisson:single-server address="redis://localhost:6379" password="123456"/>
redisson:client>
beans>
Redisson客户端还是非常强大的,客户端本身提供的有分布式锁,而且支持自动续期等,推荐使用。
/**
*
* 分布式锁测试
*
*
* @param
* @return void
* @Date 2020/8/2 20:56
*/
private void testDistributedLock() {
String lockName = "lockName";
RLock rLock = redissonClient.getLock(lockName);
try {
//获取分布式锁,然后下面的业务代码就会按顺序排队执行
rLock.lock();
//TODO 拿到redis分布式锁,执行业务代码
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
//释放分布式锁
if (rLock.isHeldByCurrentThread() && rLock.isLocked()) {
rLock.unlock();
}
}
}
好啦,本界内容就介绍到这里了,如果对你有帮助,老铁们给个赞支持下呗!
有问题的伙伴们,欢迎评论讨论哈。鉴于笔者理解的深度,可能理解的有不到位的地方,欢迎各路大神指点!
写博客是为了记住自己容易忘记的东西,另外也是对自己工作的总结,希望尽自己的努力,做到更好,大家一起努力进步!
如果有什么问题,欢迎大家评论,一起探讨,代码如有问题,欢迎各位大神指正!
给自己的梦想添加一双翅膀,让它可以在天空中自由自在的飞翔!