当前位置 : 首页 » 文章分类 :  开发  »  Spring-Data-Redis

Spring-Data-Redis

Spring Data Redis 使用笔记


默认连接本地redis

引入spring redis后,假如没有host等配置,默认会链接无密码的 localhost:6379 redis

#spring.redis.host=10.153.109.13
#spring.redis.port=8379
#spring.redis.password=123
#spring.redis.database=1
spring.redis.lettuce.shutdown-timeout=3000
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=1

SpringBoot2.2 连接 Redis 集群

spring.redis.cluster.nodes 逗号分割的集群节点
spring.redis.cluster.max-redirects 最大重定向数
spring.redis.timeout 连接超时时长(毫秒)
spring.redis.pool.max-active 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-wait 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-idle 连接池中的最大空闲连接
spring.redis.pool.min-idle 连接池中的最小空闲连接

application.properties 配置文件

spring.redis.cluster.nodes=10.153.177.20:8479,10.153.177.20:8579,10.153.177.20:8679
spring.redis.cluster.max-redirects=3
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=-1
spring.redis.pool.max-idle=8
spring.redis.pool.min-idle=1

使用这种默认配置可以触发 SpringBoot 的自动配置,然后直接注入 redisTemplate 就可以使用。

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Test
public  void test1(){
    redisTemplate.opsForValue().set("k","vvv");
    System.out.println(redisTemplate.opsForValue().get("k"));
}

ConnectionWatchdog Reconnecting, last destination was

正常的

11:11:07.101 [lettuce-eventExecutorLoop-1-23] INFO  i.l.core.protocol.ConnectionWatchdog - Reconnecting, last destination was /10.153.177.20:8479
11:11:07.104 [lettuce-nioEventLoop-4-11] INFO  i.l.c.protocol.ReconnectionHandler - Reconnected to 10.153.177.20:8479

spring.redis.cluster.max-redirects

只留一个这个参数,也会去尝试连接集群。
所以要删集群配置就要删干净。


无密码redis配置密码导致连不上

假如 redis 无密码,但是在 spring 配置文件中配置了密码
spring.redis.password=123 会导致 lettuce 客户端连不上 redis,报如下错误:

org.springframework.data.redis.connection.PoolException: Could not get a resource from the pool; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to localhost:6379
...
Caused by: io.lettuce.core.RedisConnectionException: Unable to connect to localhost:6379
...
Caused by: io.lettuce.core.RedisCommandExecutionException: ERR AUTH <password> called without any password configured for the default user. Are you sure your configuration is correct?
    at io.lettuce.core.ExceptionFactory.createExecutionException(ExceptionFactory.java:135)

解决方法,去掉密码配置,如果 jar 包中已经配置了密码,可以在命令行配置空密码覆盖
比如命令行参数 --spring.redis.password=""
或 jvm 参数中配置 -Dspring.redis.password=""


SpringBoot2.x中jedis改为Lettuce

随着 SpringBoot2.x 的到来,支持的组件越来越丰富,也越来越成熟,其中对 Redis 的支持不仅仅是丰富了它的 API,更是替换掉底层 Jedis 的依赖,取而代之换成了 Lettuce(生菜)

Lettuce
Lettuce 和 Jedis 的都是连接Redis Server的客户端程序。Jedis在实现上是直连redis server,多线程环境下非线程安全,除非使用连接池,为每个Jedis实例增加物理连接。Lettuce基于Netty的连接实例(StatefulRedisConnection),可以在多个线程间并发访问,且线程安全,满足多线程环境下的并发访问,同时它是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。

一起来学SpringBoot | 第九篇:整合Lettuce Redis
https://blog.battcn.com/2018/05/11/springboot/v2-nosql-redis/

Lettuce - Advanced Java Redis client
lettuce-io / lettuce-core
https://github.com/lettuce-io/lettuce-core


RedisTemplate中的executePipelined

// 创建结果集
Map<Long, Long> result = Maps.newLinkedHashMap();

// 加入从redis中没取到
// values为空时从数据库查
userIds.forEach(u -> result.put(u, getAccountIdByUserId(u)));

// 查完批量放入redis(其实直接用mset一条命令即可,不用管道)
executorService.execute(() -> {
    try {
        // 批量放入redis
        redisClient.executePipelined(redisConnection -> {
            RedisSerializer<String> serializer = new StringRedisSerializer();
            result.forEach((userId, accountId) -> {
                if (accountId != null) {
                    String key = Joiner.on("-").join(USER_ID_PRE, userId);
                    redisConnection.set(serializer.serialize(key), serializer.serialize(String.valueOf(accountId)));
                }
            });
            return null;
        });
    } catch (RedisConnectionFailureException e) {
        logger.error("[Redis] Exception catched when set mapping of user id and account id", e);
    }
});

redis pipeline简介
https://www.jianshu.com/p/a8e33e058518


RedisTemplate

package org.springframework.data.redis.core;
public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperations<K, V>, BeanClassLoaderAware {
}

类型参数:
K 模板中的 Redis key 的类型(通常为 String)如:RedisTemplate<String, Object>
V 模板中的 Redis value 的类型

redisTemplate 种经常用的有两类方法:

  • 一类是 opsForXxx,Xxx 是 value 的类型,返回一个 Opercation 但是没有指定操作的key,可以在一个连接(事务)内操作多个 key 以及对应的 value。
  • 一类是 boundXxxOps,Xxx 是 value 的类型,返回一个绑定了 key 的 operation,在一个连接内只操作这个 key 对应的 value。比如 boundValueOps 操作字符串,boundHashOps 操作哈希。

常用的 5 种 opsForXxx 操作:

ValueOperations<K, V> opsForValue();             // 操作简单 k-v,比如字符串
<HK, HV> HashOperations<K, HK, HV> opsForHash(); // 操作 hash
ListOperations<K, V> opsForList();               // 操作 list
SetOperations<K, V> opsForSet();                 // 操作 set
ZSetOperations<K, V> opsForZSet();               // 操作有序集合 zset

常用的 5 种 boundXxxOps 操作:

BoundValueOperations<K, V> boundValueOps(K key);               // 指定 key 操作简单 k-v,比如字符串
<HK, HV> BoundHashOperations<K, HK, HV> boundHashOps(K key);   // 指定 key 操作 hash
BoundListOperations<K, V> boundListOps(K key);                 // 指定 key 操作 list
BoundSetOperations<K, V> boundSetOps(K key);                   // 指定 key 操作 set
BoundZSetOperations<K, V> boundZSetOps(K key);                 // 指定 key 操作 zset

opsForValue() 操作字符串

opsForValue() 返回一个 ValueOperations<K, V> 类型,可用于读写 字符串 类型。

opsForZSet() 操作有序集合zset

boundValueOps() 指定key操作字符串

public BoundValueOperations<K, V> boundValueOps(K key)

返回一个 BoundValueOperations,即一个绑定了 key 的操作对象,之后可以对这个 key 进行各种操作。


ValueOperations

get()

V get(Object key);
String get(K key, long start, long end);

redisTemplate.opsForValue().get(key) 如果 key 在 redis 中不存在,返回为 null

set()

void set(K key, V value);
void set(K key, V value, long timeout, TimeUnit unit);

setIfAbsent()

Boolean setIfAbsent(K key, V value, long timeout, TimeUnit unit)
源码

@Override
public Boolean setIfAbsent(K key, V value, long timeout, TimeUnit unit) {

    byte[] rawKey = rawKey(key);
    byte[] rawValue = rawValue(value);

    Expiration expiration = Expiration.from(timeout, unit);
    return execute(connection -> connection.set(rawKey, rawValue, expiration, SetOption.ifAbsent()), true);
}

其中的 SetOption 如下

enum SetOption {
    // 默认新增或更新
    UPSERT,

    // NX
    SET_IF_ABSENT,

    // XX
    SET_IF_PRESENT;

    public static SetOption upsert() {
        return UPSERT;
    }

    public static SetOption ifPresent() {
        return SET_IF_PRESENT;
    }

    public static SetOption ifAbsent() {
        return SET_IF_ABSENT;
    }
}

能否用setIfAbsent()实现分布式锁

redis 从 2.6.12 开始给 set 命令增加了 EX, PX, NX, XX 选项,可以通过 set key value px milliseconds nx 实现分布式锁。

spring-data-redis 在 2.1 版本中做了适配,增加了 Boolean setIfAbsent(K key, V value, long timeout, TimeUnit unit) 方法, 可以通过此方法来实现分布式锁。

spring-data-redis 2.1 之前的版本中,setIfAbsent() 无法同时指定过期时间, 若先使用 setIfAbsent() 再设置key的过期时间, 两步操作不是原子的。
解决方法有:
1、若连接的 redis 版本大于等于 2.6.12,通过 redisTemplate.execute() 执行原始 set key value px milliseconds nx 命令即可。
1、若连接的 redis 版本小于 2.6.12,执行 lua 脚本,将 setnx 和 expire 放入一个原子操作中。


StringRedisTemplate

package org.springframework.data.redis.core;
public class StringRedisTemplate extends RedisTemplate<String, String> {
}

StringRedisTemplate 与 RedisTemplate 区别

StringRedisTemplate 继承 RedisTemplate

两者的数据是不共通的;也就是说 StringRedisTemplate 只能管理 StringRedisTemplate 里面的数据,RedisTemplate 只能管理 RedisTemplate 中的数据。

StringRedisTemplate 默认采用的是 String 的序列化策略,保存的 key 和 value 都是采用此策略序列化保存的。

RedisTemplate 默认采用的是 JDK 的序列化策略,保存的 key 和 value 都是采用此策略序列化保存的。

如何使用RedisTemplate访问Redis数据结构
https://www.jianshu.com/p/7bf5dc61ca06


序列化

Jackson2JsonRedisSerializer

GenericJackson2JsonRedisSerializer

Jackson2JsonRedisSerializer 和 GenericJackson2JsonRedisSerializer 都是序列化为 json格式。

不同:
如果存储的类型为List等带有泛型的对象,反序列化的时候 Jackson2JsonRedisSerializer 序列化方式会报错,而 GenericJackson2JsonRedisSerializer 序列化方式是成功的,


embedded-redis 单测

kstyrc / embedded-redis
https://github.com/kstyrc/embedded-redis

用于 java 单元测试的嵌入式 redis

<dependency>
    <groupId>it.ozimov</groupId>
    <artifactId>embedded-redis</artifactId>
    <version>0.7.2</version>
</dependency>
private static final int EMBEDDED_REDIS_PORT = 6379;

private RedisServer redisServer;

@BeforeEach
public void before() {
    this.redisServer = new RedisServer(EMBEDDED_REDIS_PORT);
    redisServer.start();
    log.info("---- start embedded redis server on {} ----", EMBEDDED_REDIS_PORT);
}

Can’t start redis server. Check logs for details

报错:

java.lang.RuntimeException: Can't start redis server. Check logs for details.
    at redis.embedded.AbstractRedisInstance.awaitRedisServerReady(AbstractRedisInstance.java:62)
    at redis.embedded.AbstractRedisInstance.start(AbstractRedisInstance.java:39)
    at redis.embedded.RedisServer.start(RedisServer.java:9)

原因:
本机上已经启动了一个 redis, 端口也是 6379, 端口冲突了。

解决:
嵌入式 redis 换个端口启动即可。

java.lang.RuntimeException: Can’t start redis server. Check logs for details
https://stackoverflow.com/questions/53287218/java-lang-runtimeexception-cant-start-redis-server-check-logs-for-details


上一篇 MySQL-InnoDB 存储引擎

下一篇 Nginx

阅读
评论
2.1k
阅读预计10分钟
创建日期 2018-10-20
修改日期 2022-11-16
类别

页面信息

location:
protocol:
host:
hostname:
origin:
pathname:
href:
document:
referrer:
navigator:
platform:
userAgent:

评论