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,最主要是实现 serializedeserialize 两个方法即可


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 虽然提供了批量操作命令(例如 mgetmset 等),有效的节约 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 的工具库了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值