Spring Data Redis 详解
为什么要用 Spring Data Redis?
在 Spring 的生态中,使用Redis、Redisson、Lettuce 等优秀的 Java 工具库来对 Redis 进行数据访问。而 Spring Data Redis 与这些库的关系如下:
Spring Data Redis 提供了统一的操作模板(RedisTemplate 类),封装了 Jedis、Lettuce 的 API 操作 Redis 数据。所以,Spring Data Redis 内置真正访问的实际是 Jedis、Lettuce 等 API 操作。
所以,应该考虑如何使用 Spring Data Redis 才是关键,我们已经而无需关心 Jedis、Lettuce 等各种不同 API 操作。甚至以后我们想将 Redis 访问从 Jedis 迁移成 Lettuce 来,无需做任何的变动。所以不需要在一开始选择哪个 Java Redis 工具库烦恼
NOTE:不过 Spring Data Redis 暂时只支持 Jedis、Lettuce 的内部封装,而 Redisson 是由 redisson-spring-data
来提供。
快速入门
引入依赖
<dependencies>
<!-- 实现对 Spring Data Redis 的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<!-- 去掉对 Lettuce 的依赖,因为 Spring Boot 优先使用 Lettuce 作为 Redis 客户端 -->
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 引入 Jedis 的依赖,这样 Spring Boot 实现对 Jedis 的自动化配置 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 使用 fastjson 作为 JSON 序列化的工具 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.61</version>
</dependency>
<!-- Spring Data Redis 默认使用 Jackson 作为 JSON 序列化的工具 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
配置文件
在 application.yml
中添加配置
spring:
redis:
host: 127.0.0.1
port: 6379
password: # Redis 服务器密码,默认为空。生产中,一定要设置 Redis 密码!
database: 0 # Redis 数据库号,默认为 0 。
timeout: 0 # Redis 连接超时时间,单位:毫秒。
# 对应 RedisProperties.Jedis 内部类
jedis:
pool:
max-active: 8 # 连接池最大连接数,默认为 8 。使用负数表示没有限制。
max-idle: 8 # 默认连接数最小空闲的连接数,默认为 8 。使用负数表示没有限制。
min-idle: 0 # 默认连接池最小空闲的连接数,默认为 0 。允许设置 0 和 正数。
max-wait: -1 # 连接池最大阻塞等待时间,单位:毫秒。默认为 -1 ,表示不限制。
测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisTests {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
public void testStringSet() {
stringRedisTemplate.opsForValue().set("red", "roy");
}
}
通过 StringRedisTemplate 类(Redistemplate 类也可以),我们进行了一次 Redis SET 指令的执行。
通过工具,我们可以看到 red
的 key roy
已经被设置进来了。
RedisTemplate 与 StringRedisTemplate
通过 org.springframework.data.redis.core.RedisTemplate<K, V> 类源码就可以知道,提供 Redis 操作模板 API 。
// RedisTemplate.java
// 序列化相关属性,这里有四个序列化相关的属性,用于 KEY 和 VALUE 的序列化,在使用 POJO 存储到 Redis 中,一般情况下,会使用 JSON 方式序列化成字符串,存储到 Redis 中
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer keySerializer = null;
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer valueSerializer = null;
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer hashKeySerializer = null;
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer hashValueSerializer = null;
private RedisSerializer<String> stringSerializer = RedisSerializer.string();
// Lua 脚本执行器,提供 Redis scripting API 操作
private @Nullable ScriptExecutor<K> scriptExecutor;
// Redis 常见数据结构操作类
// ValueOperations 类,提供 Redis String API 操作;ListOperations 类,提供 Redis List API 操作 等...
// cache singleton objects (where possible)
private @Nullable ValueOperations<K, V> valueOps;
private @Nullable ListOperations<K, V> listOps;
private @Nullable SetOperations<K, V> setOps;
private @Nullable ZSetOperations<K, V> zSetOps;
private @Nullable GeoOperations<K, V> geoOps;
private @Nullable HyperLogLogOperations<K, V> hllOps;
org.springframework.data.redis.core.StringRedisTemplate
继承 RedisTemplate
,使用 org.springframework.data.redis.serializer.StringRedisSerializer
字符串序列化方式。StringRedisSerializer 实现了 RedisSerializer
接口, 就知道只是用 Jackson 进行序列化。
序列化
RedisSerializer
Redis 序列化接口,用于 Redis KEY 和 VALUE 的序列化。定义了对象 与二进制数组之间的转换。
Redis Client 传递给 Redis Server 是传递的 KEY 和 VALUE 都是二进制值数组,Jedis
sendCommand(final Command cmd, final byte[]... args)
方法传的参数就是二进制数组,cmd 命令也会被序列化成二进制数组。
/**
* Serialize the given object to binary data.
*
* @param value object to serialize. Can be {@literal null}.
* @return the equivalent binary data. Can be {@literal null}.
*/
@Nullable
byte[] serialize(@Nullable T value) throws SerializationException;
/**
* Deserialize an object from the given binary data.
*
* @param bytes object binary representation. Can be {@literal null}.
* @return the equivalent object instance. Can be {@literal null}.
*/
@Nullable
T deserialize(@Nullable byte[] bytes) throws SerializationException;
所有的实现类如下:
大致分成这几类:
- JDK 序列化方式:
JdkSerializationRedisSerializer
,默认 RedisTemplate 使用该数据列化方式。在 RedisTemplate 的afterPropertiesSet()
方法,RedisTemplate 未设置序列化的情况下,使用 JdkSerializationRedisSerializer 作为序列化实现。在 Spring Boot 自动化配置 RedisTemplate Bean 对象时,就未设置。但在绝大多数情况下,我们不会使用 JdkSerializationRedisSerializer 序列化。
下面代码使用的是 RedisTemplate 而不是 StringRedisTemplate。
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestRedisTemplate {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void testStringSet() {
redisTemplate.opsForValue().set("red", "roy");
}
}
执行完之后,我们会发现会出现前面带着奇怪的 16 进制字符。而使用这个奇怪的 KEY 去获取对应的 VALUE ,结果前面也是一串奇怪的 16 进制字符。ObjectOutputStream 的 writeString(String str, boolean unshared)
,其实就是标志位 + 字符串长度 + 字符串内容(经典的自定义 Socket 协议内容)。所以我们这样操作非常不方便。
- String 序列化方式:
StringRedisSerializer
,这个比较简单,直接字符串和二进制数组的直接转换,而且正常情况下都会使用这种序列化方案,GenericToStringSerializer<T>
使用 Spring ConversionService 实现 对象和 String 的转换,再使用 String 和二进制数组的转换
@Override
public byte[] serialize(@Nullable String value) {
return (value == null ? null : value.getBytes(charset));
}
@Override
public String deserialize(@Nullable byte[] bytes) {
return (bytes == null ? null : new String(bytes, charset));
}
- 序列化方式:
GenericJackson2JsonRedisSerializer
,通用 Jackson 实现 JSON 的序列化方式(支持所有类)
public GenericJackson2JsonRedisSerializer(@Nullable String classPropertyTypeName) {
this(new ObjectMapper());
mapper.registerModule(new SimpleModule().addSerializer(new NullValueSerializer(classPropertyTypeName)));
// 如果传入了 classPropertyTypeName 属性,就是使用使用传入对象的 classPropertyTypeName 属性对应的值,作为默认类型(Default Typing)
if (StringUtils.hasText(classPropertyTypeName)) {
mapper.enableDefaultTypingAsProperty(DefaultTyping.NON_FINAL, classPropertyTypeName);
// 如果未传入 classPropertyTypeName 属性,则使用传入对象的类全名,作为默认类型(Default Typing)
} else {
mapper.enableDefaultTyping(DefaultTyping.NON_FINAL, As.PROPERTY);
}
}
在将一个对象序列化成一个字符串,为保证字符串反序列化成对象的类型,Jackson 通过 Default Typing ,会在字符串多冗余一个类型,这样反序列化就知道具体的类型了(虽然解决了反序列化后的对象类型,但带来 JSON 字符串占用变大,实际项目中也不采用 Jackson2JsonRedisSerializer
)
public class Jackson2JsonRedisSerializer<T> implements RedisSerializer<T> {
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
/**
* 指定类型,和 <T> 要一致。
*/
private final JavaType javaType;
private ObjectMapper objectMapper = new ObjectMapper();
}
- XML 序列化方式:
OxmSerializer
,使用 Spring OXM 实现将对象和 String 的转换,从而 String 和二进制数组的转换。基本上不用了。
配置序列化方式
@Configuration
public class RedisConfiguration {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
// 创建 RedisTemplate 对象
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 设置 RedisConnection 工厂。它就是实现多种 Java Redis 客户端接入的工厂。
template.setConnectionFactory(factory);
// 使用 String 序列化方式,序列化 KEY 。
template.setKeySerializer(RedisSerializer.string());
// 使用 JSON 序列化方式(库是 Jackson),序列化 VALUE 。
template.setValueSerializer(RedisSerializer.json());
return template;
}
}
RedisSerializer 方法
// 使用 UTF-8 编码的 StringRedisSerializer 对象
static RedisSerializer<String> string() {
return StringRedisSerializer.UTF_8;
}
// GenericJackson2JsonRedisSerializer 对象
static RedisSerializer<Object> json() {
return new GenericJackson2JsonRedisSerializer();
}
RedisSerializer 实现类
参考 GenericFastJsonRedisSerializer,最主要是实现 serialize
与 deserialize
两个方法即可
public class GenericFastJsonRedisSerializer implements RedisSerializer<Object> {
private final static ParserConfig defaultRedisConfig = new ParserConfig();
static { defaultRedisConfig.setAutoTypeSupport(true);}
public byte[] serialize(Object object) throws SerializationException {
if (object == null) {
return new byte[0];
}
try {
// 使用 JSON 进行序列化成二进制数组,同时通过 SerializerFeature.WriteClassName 参数,声明写入类全名。
return JSON.toJSONBytes(object, SerializerFeature.WriteClassName);
} catch (Exception ex) {
throw new SerializationException("Could not serialize: " + ex.getMessage(), ex);
}
}
public Object deserialize(byte[] bytes) throws SerializationException {
if (bytes == null || bytes.length == 0) {
return null;
}
try {
// 使用 JSON 解析成对象。
return JSON.parseObject(new String(bytes, IOUtils.UTF8), Object.class, defaultRedisConfig);
} catch (Exception ex) {
throw new SerializationException("Could not deserialize: " + ex.getMessage(), ex);
}
}
}
演示
@Repository
public class UserCacheDao {
private static final String KEY_PATTERN = "user:%d"; // user:用户编号,通过静态变量,声明 KEY 的前缀,并且使用冒号作为间隔
@Resource(name = "redisTemplate")
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
private ValueOperations<String, String> operations; // 声明 KEY_PATTERN 对应的 KEY 拼接方法,避免散落在每个方法中。
private static String buildKey(Integer id) { // 通过 @Resource 注入指定名字的 RedisTemplate 对应的 Operations 对象,这样明确每个 KEY 的类型。
return String.format(KEY_PATTERN, id);
}
public UserCacheObject get(Integer id) {
String key = buildKey(id);
String value = operations.get(key);
return JSONUtil.parseObject(value, UserCacheObject.class); // 这里直接使用 JSON Util 转换字符串就好了
}
public void set(Integer id, UserCacheObject object) {
String key = buildKey(id);
String value = JSONUtil.toJSONString(object); // 这里直接使用 JSON Util 转换字符串就好了
operations.set(key, value);
}
}
为什么不支持将 RedisTemplate 直接 Service 业务层调用呢?或像大多数人一样,做一个 RedisUtil,其实是没问题的,只不过在这里做个演示而已,不过也建议做一个 Redis 的 DAO,不然容易混杂着很多 Redis 访问代码的细节,导致很脏乱。
Pipline
Redis Pipeline 是一种在客户端向服务端发送多个请求而不等待响应的技术。为了减少了网络通信的开销而设计的。它可以显著提高 Redis 应用程序的性能。 管道的主要思想是客户端向服务端发送多个请求,而不等待这些请求的响应。这避免了在每个请求之间等待往返延迟。 使用Redis管道主要有以下好处:
- 减少往返延迟 。不需在每个请求间等待,效率更高。
- 优化网络利用率。在管道中打包多个请求,网络传输更有效。
- 简化多次请求的程序逻辑。通过管道可以避免重复的连接、发送等代码。
Redis Client 执行一条命令分为以下四个步骤:
发送命令 -- 命令排队 -- 命令执行 -- 返回结果
Redis 虽然提供了批量操作命令(例如 mget
、mset
等),有效的节约 RTT(Round Trip Time - 往返时间)。但大部分命令是不支持批量操作的。例如要执行 n 次 hgetall
命令,并没有 mhgetall
存在,需要消耗 n 次 RTT。而 Pipeline 机制就是来改善这问题的,它将一组 Redis 命令进行组装,通过一次 RTT 传输给 Redis,再将这组命令按照顺序执行并装填结果返回给客户端。Pipline 在很多技术上都有用,因为是基于 TCP/IP 的,所以大部分的中间件都会有,比如 MQ 或者 Netty。
在 RedisTemplate 类中,提供了 2 组四个方法,用于执行 Redis Pipeline 操作,每组方法里,差别在于是否传入 RedisSerializer 参数。如果不传,则使用 RedisTemplate 自己的序列化相关的属性。
// 基于 Session 执行 Pipeline,这里的 Session 不是 Redis 的功能,而是 Spring Data Redis 封装的一个功能。一次 Session ,代表通过同一个 Redis Connection 执行一系列的 Redis 操作。
@Override
public List<Object> executePipelined(SessionCallback<?> session) {
return executePipelined(session, valueSerializer);
}
@Override
public List<Object> executePipelined(SessionCallback<?> session, @Nullable RedisSerializer<?> resultSerializer) {
//
}
// 直接执行 Pipeline
@Override
public List<Object> executePipelined(RedisCallback<?> action) {
return executePipelined(action, valueSerializer);
}
@Override
public List<Object> executePipelined(RedisCallback<?> action, @Nullable RedisSerializer<?> resultSerializer) {
// 执行 Redis 方法,此处传入的 action 参数,不是我们传入的 RedisCallback 参数。我们的会在该 action 中被执行
return execute((RedisCallback<List<Object>>) connection -> {
// 打开 pipeline
connection.openPipeline();
boolean pipelinedClosed = false; // 标记 pipeline 是否关闭,默认打开
try {
// 执行在 Pipeline 中,想要执行的 Redis 操作
Object result = action.doInRedis(connection);
// 不要返回结果。因为 RedisCallback 是统一定义的接口,所以可以返回一个结果。但是在 Pipeline 中,未提交执行时,显然是没有结果,返回也没有意思。
if (result != null) {
throw new InvalidDataAccessApiUsageException(
"Callback cannot return a non-null value as it gets overwritten by the pipeline");
}
// 提交 Pipeline 执行,并返回执行结果
List<Object> closePipeline = connection.closePipeline();
pipelinedClosed = true;
// 反序列化结果,并返回
return deserializeMixedResults(closePipeline, resultSerializer, hashKeySerializer, hashValueSerializer);
} finally {
if (!pipelinedClosed) {
connection.closePipeline();
}
}
});
}
Spring Data Redis 对 Pipeline 的封装,实际就是经典的 模板方法
设计模式化的应用。
// RedisCallback.java
public interface RedisCallback<T> {
/**
* 从方法名可以很容易知道,实际可以理解是 Redis Action ,想要执行的 Redis 操作。注意传入的 connection 参数是 RedisConnection 对象,它提供的 'low level' 更底层的 Redis API 操作
* Gets called by {@link RedisTemplate} with an active Redis connection. Does not need to care about activating or
* closing the connection or handling exceptions.
*
* @param connection active Redis connection
* @return a result object or {@code null} if none
* @throws DataAccessException
*/
@Nullable
T doInRedis(RedisConnection connection) throws DataAccessException;
}
Session
在一个 Redis Transaction 中的时候,所有 Redis 操作都使用通过同一个 Redis Connection,因为会将获得到的 Connection 绑定到当前线程中。但若不在一个 Redis Transaction 中的时候,每次使用 Redis Operations 执行 Redis 操作的时候,每一次都会获取一次 Redis Connection 的获取。实际项目中,必然会使用 Redis Connection 连接池,那么在获取的时候,会存在一定的竞争,会有资源上的消耗。而使用 Spring Data Redis 的 Session 可以让我们在执行一个系列的 Redis 操作的时候,避免重复使用同一个 Redis Connection。
要执行在同一个 Session 里的操作时,我们通过实现 SessionCallback<T>
接口来实现
public interface SessionCallback<T> {
// RedisTemplate 的各种操作,实际就是在 RedisOperations 接口中定义,由 RedisTemplate 来实现
// 实际上,我们在实现 RedisCallback 接口,也能实现在同一个 Connection 执行一系列的 Redis 操作,因为 RedisCallback 的入参本身就是一个 Redis Connection
@Nullable
<K, V> T execute(RedisOperations<K, V> operations) throws DataAccessException;
}
测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisPipelineTest {
// 使用 StringRedisTemplate 自己的序列化相关属性,Redis GET 命令返回的二进制会被反序列化成了字符串
@Resource
private StringRedisTemplate stringRedisTemplate;
@Test
public void testPipline() {
List<Object> results = stringRedisTemplate.executePipelined(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
for (int i = 0; i < 3; i++) { // set 写入
connection.set(String.format("red:%d", i).getBytes(), "ray".getBytes());
}
for (int i = 0; i < 3; i++) { // get
connection.get(String.format("red:%d", i).getBytes());
}
// 返回 null 即可
return null;
}
});
// 打印结果
System.out.println(results);
}
}
Script
Redis 提供 Lua 脚本,满足我们希望组合排列使用 Redis 的命令,保证串行执行的过程中,不存在并发的问题。同时,通过将多个命令组合在同一个 Lua 脚本中,一次请求,直接处理,也是一个提升性能的手段。
演示
创建 Lua 脚本,放在 resources
文件夹下面
-- xxx.lua
if redis.call('GET', KEYS[1]) ~= ARGV[1] then -- 判断 KEYS[1] 对应的 VALUE 是否为 ARGV[1] 值。如果不是(Lua 中不等于使用 ~=),则直接返回 0 表示失败。
return 0
end
redis.call('SET', KEYS[1], ARGV[2]) -- 设置 KEYS[1] 对应的 VALUE 为新值 ARGV[2] ,并返回 1 表示成功
return 1
调用 Lua 脚本
@RunWith(SpringRunner.class)
@SpringBootTest
public class ScriptTest {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
public void test01() throws IOException {
// 读取 /resources/xxx.lua 脚本 。注意,需要引入下 commons-io 依赖。
String scriptContents = IOUtils.toString(getClass().getResourceAsStream("/xxx.lua"), "UTF-8");
// 创建 RedisScript 对象
RedisScript<Long> script = new DefaultRedisScript<>(scriptContents, Long.class);
// 执行 LUA 脚本
Long result = stringRedisTemplate.execute(script, Collections.singletonList("red:1"), "ray1", "ray");
System.out.println(result);
}
}
Pub/Sub
就是简单的订阅功能,没啥好说的。了解了 MQ 就知道了
Redisson
Java 最强的 Redis 客户端。除了提供了 Redis 客户端的常见操作之外,还提供了 Redis 分布式锁、BloomFilter 布隆过滤器等强大的功能。
配置
application.yml
配置类:
spring:
# 对应 RedisProperties 类
redis:
host: 127.0.0.1
port: 6379
database: 0 # Redis 数据库号,默认为 0 。
timeout: 0 # Redis 连接超时时间,单位:毫秒。
# RedissonProperties 类
redisson:
config: classpath:redisson.yml # 具体的每个配置项,见 org.redisson.config.Config 类。
redisson 配置类:
clusterServersConfig:
# 连接空闲超时 如果当前连接池里的连接数量超过了最小空闲连接数,而同时有连接空闲时间超过了该数值,那么这些连接将会自动被关闭,并从连接池里去掉。时间单位是毫秒。
idleConnectionTimeout: 10000
pingTimeout: 1000
# 连接超时
connectTimeout: 10000
# 命令等待超时
timeout: 3000
# 命令失败重试次数
retryAttempts: 3
# 命令重试发送时间间隔
retryInterval: 1500
# 重新连接时间间隔
reconnectionTimeout: 3000
# failedAttempts
failedAttempts: 3
# 密码
password: null
# 单个连接最大订阅数量
subscriptionsPerConnection: 5
# 客户端名称
clientName: null
#负载均衡算法类的选择 默认轮询调度算法RoundRobinLoadBalancer
loadBalancer: !<org.redisson.connection.balancer.RoundRobinLoadBalancer> {}
slaveSubscriptionConnectionMinimumIdleSize: 1
slaveSubscriptionConnectionPoolSize: 50
# 从节点最小空闲连接数
slaveConnectionMinimumIdleSize: 32
# 从节点连接池大小
slaveConnectionPoolSize: 64
# 主节点最小空闲连接数
masterConnectionMinimumIdleSize: 32
# 主节点连接池大小
masterConnectionPoolSize: 64
# 只在从服务节点里读取
readMode: "SLAVE"
# 主节点信息
nodeAddresses:
- "redis://192.168.56.128:7000"
- "redis://192.168.56.128:7001"
- "redis://192.168.56.128:7002"
#集群扫描间隔时间 单位毫秒
scanInterval: 1000
threads: 0
nettyThreads: 0
codec: !<org.redisson.codec.JsonJacksonCodec> {}
简单测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedissonTests {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
public void testStringSet() {
stringRedisTemplate.opsForValue().set("red", "ray");
}
}
最后发现代码不用改 - - !多快活。
Redis 分布式锁
正常情况下, 基于 Zookeeper 或是 Redis 实现分布式锁都是最先想到的。不过在考虑性能为优先因素,不需要特别绝对可靠性的场景下,会优先选择 Redis 实现的分布式锁。Redisson 中,提供了 8 种分布式锁的实现。
简单测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class LockTest {
private static final String LOCK_KEY = "anylock";
@Resource
private RedissonClient redissonClient;
@Test
public void test() throws InterruptedException {
// 启动一个线程 A ,去占有锁
new Thread(new Runnable() {
@Override
public void run() {
// 加锁以后 10 秒钟自动解锁
// 无需调用 unlock 方法手动解锁
final RLock lock = redissonClient.getLock(LOCK_KEY);
lock.lock(10, TimeUnit.SECONDS);
}
}).start();
// sleep 1 秒,保证线程 A 成功持有锁
Thread.sleep(1000L);
// 尝试加锁,最多等待 100 秒,上锁以后 10 秒自动解锁
System.out.println(String.format("准备开始获得锁时间:%s", new SimpleDateFormat("yyyy-MM-DD HH:mm:ss").format(new Date())));
final RLock lock = redissonClient.getLock(LOCK_KEY);
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {
System.out.println(String.format("实际获得锁时间:%s", new SimpleDateFormat("yyyy-MM-DD HH:mm:ss").format(new Date())));
} else {
System.out.println("加锁失败");
}
}
}
总结
Redis 其实非常简单,但是使用了 Spring Data Redis,会发现更简单,其实相当于加了个中间层在里面,并不需要再去关注多种 Redis 的工具库了。