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
下一篇 Nginx
页面信息
location:
protocol
: host
: hostname
: origin
: pathname
: href
: document:
referrer
: navigator:
platform
: userAgent
: