Spring Data Redis 详解

分布式缓存技术redis学习系列(五)——spring-data-redis与JedisPool的区别、使用ShardedJedisPool与spring集成的实现及一致性哈希分析 一、Redis与spring的整合一般分为spring-data-redis整合和JedisPool整合,先看看两者的区别1)、引用的依赖不同:spring-data-redis使用的依赖如下: <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data 阅读详情

为什么要用 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 的工具库了。

Spring Data Redis 实践 关于Spring Data Redis的简单应用以及1.7新特性Redis Repositories简介~ 阅读详情

相关推荐

Spring Data Redis详解(超详细版)

Spring Data Redis是一个功能强大且易于使用的Redis客户端库。它封装了Redis的底层操作,为开发者提供了更加简洁、易用的API,使得在Spring应用中集成和使用Redis变得更加方便。通过合理的配置和使用,开发者可以轻松地在Spring应用中实现高效的数据存储和访问。随着Redis的不断发展和完善,Spring Data Redis也将不断更新和升级,以支持更多的Redis特性和功能。

专业深耕,技术前沿 1102

通过Spring-Data-Redis操作Redis

本文主要介绍的通过Spring-Data-Redis操作Redis

gaoqiandr的博客 555

spring-data-redis-1.8.4.RELEASE

spring-data-redis-1.8.4.RELEASE.jar,spring-data-redis-1.8.4.RELEASE.jar

SpringDataRedis 使用

/List。

chenbaifan的博客 1279

AI报告撰写实战指南:从提示词工程到全流程优化的底层逻辑与实践突破

传统AI写作常被诟病为“信息拼贴”——模型虽能生成文本,却常出现逻辑断裂、数据矛盾、风格不符等问题。而“全流程优化”则是通过系统化设计,将报告生成拆解为需求解析→数据对齐→提示词设计→初稿生成→迭代优化→格式适配六大环节,每个环节均通过技术手段提升输出质量。提示词工程:本质是通过结构化指令(如角色设定、任务分解、格式约束、示例引导)精准控制模型输出方向,是连接用户需求与模型能力的“翻译器”。全流程优化。

2501_92808811的博客 884

五分钟搞懂游戏开发中的抗锯齿算法

常见抗锯齿算法总结锯齿由来抗锯齿算法SSAAMSAACSAA 锯齿由来 场景的定义在三维空间中是连续的,而最终显示的像素则是一个离散的二维数组,这是计算机屏幕产生锯齿的原因。在计算机处理图形的过程中(渲染管线了解一下),有一个非常重要的阶段,就是光栅化,光栅化主要的作用是将顶点数据的不连续性通过插值计算,将两个顶点之间不存在的点进行弥补,然后实现到屏幕像素点上的一一映射。如图,现在要在屏幕上绘制一...

绿洲守望者 1万+

使用Spring Data Redis操作Redis

使用Spring Data Redis操作Redis、RedisTemplate

weixin_61769871的博客 1472

JavaFx之集成FontAwesomeFX与JFoenix(十五)

本文详细介绍了如何在JavaFx项目中同时集成JFoenix和FontAwesomeFX,以创建现代化、美观的桌面应用界面。通过Maven依赖配置、动态代码创建和FXML静态声明两种方式,演示了如何将JFoenix的Material Design控件与FontAwesomeFX的丰富矢量图标完美结合,并提供了实战案例展示如何构建一个功能完善的现代化工具栏。

pepsi的博客 437

Spring Data Redis

/ --- 关键配置 --- StringRedisSerializer stringSerializer = new StringRedisSerializer();// 通常,Key 使用 String 序列化器,因为 Key 通常是字符串 template . setKeySerializer(stringSerializer);

2401_83724374的博客 916

团结引擎+OpenHarmony 记录 (持续更新中)

获取不到的原因是因为二者不在同一个线程中,所以数据没有同步。而 globalThis.context 数据进行了同步。(TuanjieMainWorkerHandler.ts中可以看到)在进行绘制 UI 的时候 ,如果组件有 Native API 就可能不会显示出来,例如 web 控件就不能展示出来,刚加上权限的时候 会报错 因为缺少 reason ,添加上就不会报错了。只有纯 UI 控件是可以展示出来的,注释掉 web控件后就可以。

m_cainiaokuaifei的博客 1490

别再搞混了!MATLAB通信仿真中EbN0转SNR的保姆级公式推导与避坑指南

本文深入解析MATLAB通信仿真中EbN0与SNR转换的核心公式,揭示数字仿真与理论系统的关键差异。通过真实案例展示错误参数设置导致的隐蔽问题,并提供完整的参数自查清单和正确实现代码,帮助工程师和学生避免常见错误,确保仿真结果准确可靠。

weixin_28746223的博客 348

Redis以及Spring Data Redis

springboot项目中资源缓存,以及定时清理无用资源

轻言细语莫泪痕 2009

2025.06.17【WGS】|多样品VCF文件中每个样品SNP数统计方法详解(含代码与注释)

摘要:本文介绍了一种从合并后的VCF文件中统计每个样品SNP数目的方法。通过解析VCF文件中的GT字段,排除纯合参考(0/0)和缺失(./.)的基因型,准确计算各样本的SNP位点数。文中提供了完整的Python脚本,利用bcftools工具高效处理大规模VCF文件,并支持输出SNP统计结果。该方法适用于群体遗传学和RAD-seq等高通量测序数据分析的质控环节,还可扩展为统计杂合/纯合SNP等进阶需求。脚本使用简单,只需输入VCF文件即可生成样本-SNP数目的统计报表。

1696

Redis && Spring Data Redis && Spring Cache

使用方式通常为Jedis或者RedisTemplate。

qq_42765398的博客 945

AUTOSAR进阶图解==>AUTOSAR_SWS_CANStateManager

AUTOSAR CAN状态管理器(CanSM)摘要 CanSM是AUTOSAR通信栈中的关键模块,负责管理CAN网络状态和模式转换。主要功能包括: 控制CAN控制器/收发器工作模式(STARTED/STOPPED,NORMAL/STANDBY) 处理三种网络状态(全通信、静默通信、无通信)之间的转换 监控和处理总线关闭(Bus-Off)事件 与ComM、CanIf、BswM等模块协同工作 CanSM通过状态机管理网络状态转换,支持可配置的总线关闭恢复策略。它为每个CAN网络维护独立的状态机,实现网络状态的精

一名留德华的,康德的信徒的,专注于汽车电子领域的博客 5578

C++和OpenGL实现3D游戏编程【连载6】——不规则图形的纹理贴图(附源码)

上一节我们讨论了纹理贴图的相关基础操作,但上一节的纹理贴图操作基本上都是规则图形,包括圆形和球形虽然复杂一点,但是它也是规则的。这一节课我们要讨论一下,怎么在不规则图形上纹理贴图,就比如文章下图的心形纹理贴图,以及纹理的平移、旋转、放大缩小功能。

zhooyu的博客 1307

使用 Spring Data Redis 实现 Redis 数据存储详解

Spring Data Redis 是 Spring 生态中操作 Redis 的核心模块,它封装了 Redis 客户端的底层细节(如 Jedis 或 Lettuce),提供了统一的 API 来操作 Redis 的数据结构。确保所有操作的 Key/Value 序列化方式一致,避免出现乱码或类型错误。或手动关闭连接,避免未释放的连接耗尽资源。在 Redis 配置中设置。

FAQEW的博客 2861

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

spring.redis 和 spring.data.redis的区别

Bean总结:负责 Redis 的基础连接配置(必填)。:控制 Spring Data Redis 的高级行为(可选)。两者是互补关系,共同完成对 Redis 的完整集成。

qq_37533306的博客 1719

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

spring-data-redis

Spring Data Redis (SDR) 是 Spring 大家族的一部分,旨在简化 Spring 应用中 Redis 数据存储的访问和操作。它在 Redis 客户端库(如 Jedis 和 Lettuce)之上提供了一个高级抽象层,让我们无需关注底层客户端的具体实现细节是spring-data-redis的核心类,他负责这么几件事序列化与反序列化:在给定对象与 Redis 存储中的底层二进制数据之间执行自动序列化/反序列化Redis连接管理:通过execute()

qq_36678573的博客 918

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

Spring Data Redis本质

今天花半小时补一篇技术文章纯纯是因为早上发的那篇感悟随记很多人可能会认为不是技术文章。而一般这种文章的阅读量会远高于技术文章。为了不破坏公众号的基调,今天写篇技术文章明天一早发出去。大家上班路上看到的是:这还是一个技术公众号。但是我想说的是上篇文章我是在讲技术,如果对技术的理解过于狭隘,那在工作中会很容易进入瓶颈期。咱们宏观到微观来审视一下:业务的服务在技术上需要解决4个问题:分布式、通信、存储和服务治理。在存储方面,spring家族在使用时做了封装就是spring data。

m0_46995061的博客 858

spring data redis和spring cache

springboot对缓存做的统一的规范,,就类似spring data jdbc 类似,,对数据库层做统一规范,,底层可以切换任意的数据库。默认是jdk序列化方式,,也就是不管是key,还是value,,都会通过jdk序列化存入redis,,,这种一般不用,,可读性太差,,,,这个是全局配置,,这个方法已经被弃用,,因为不安全,,他会根据@class转换成任意类型的Java对象,,如果这个java对象是。标明自己是什么类型,,,在反序列化的时候才能强转为这个类型,,否则就是。等这些类,,就会有风险。

qq_36022463的博客 1196

Spring-data-redis

说明: 在 SpringBoot2.x 之后,原来使用的jedis 被替换为了 lettucejedis : 采用的直连,多个线程操作的话,是不安全的,如果想要避免不安全的,使用 jedis pool 连接池lettuce : 采用netty,实例可以再多个线程中进行共享,不存在线程不安全的情况!可以减少线程数据了@Bean//1.创建自定义模板类//配置json类型的序列化工具//这样key会用字符串方式保存/*** 测试自定义。

m0_68041576的博客 1991
上一篇: Netty 基础
下一篇: Nacos 注册中心与配置中心
RedRay0
博客等级 码龄2年 120粉丝 11原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值