Redis 之使用教程(Java 版)
博客地址 https://blog.piaoruiqing.com/blog/2019/06/11/redis 使用进阶
关键词
-
Jedis
: redis java 客户端实现. -
Lettuce
: redis java 客户端实现, 基于 netty. -
spring-data-redis
: Spring 针对 redis 的封装, 配置简单, 提供了与 Redis 存储交互的抽象封装, 十分优雅, 也极具扩展性. 可集成 Jedis、Lettuce 等 redis 客户端. springboot2.0 后官方默认集成的客户端从 Jedis 改为 Lettuce.
前言
本文将针对使用 Java 集成 Redis 进行讲解, Jedis
及 Lettuce
的使用仅作简单描述, spring
的 redis
集成及使用将作为主要讲解内容.
Jedis
https://github.com/xetorthio/jedis
引入依赖:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.3</version>
</dependency>
Jedis
是对 redis 命令的封装, 使用上基本与 redis-cli
无异, 操作 string 的使用示例如下:
/** jedis pool */
private static final JedisPool POOL = new JedisPool(new JedisPoolConfig(), "test-redis-server", 6379);
// test Binary-safe strings with Jedis
try (Jedis jedis = POOL.getResource()){
{ // SET mykey myvalue
String result = jedis.set("mykey", "myvalue");
LOGGER.info("cmd: SET mykey myvalue, result: {}", result);
}
{ // GET mykey
String result = jedis.get("mykey");
LOGGER.info("cmd: GET mykey, result: {}", result);
}
{ // KEYS my*
Set<String> keys = jedis.keys("my*");
LOGGER.info("cmd: KEYS my*, result: {}", JsonUtils.writeValueAsString(keys, true));
}
{ // EXISTS mykey
Boolean result = jedis.exists("mykey");
LOGGER.info("cmd: EXISTS mykey, result: {}", result);
}
{ // DEL mykey
Long result = jedis.del("mykey");
LOGGER.info("cmd: DEL mykey, result: {}", result);
}
{ // GET mykey
String result = jedis.get("mykey");
LOGGER.info("cmd: GET mykey, result: {}", result);
}
}
-
JedisPool
: Jedis 并不是线程安全的, 所以多线程情况下不应共用Jedis
实例, 但创建大量的 Jedis 会造成不必要的开销甚至对性能产生较大影响, 故使用JedisPool
来避免这些问题, 它是一个线程安全的网络连接池. 可以使用它可靠地创建多个 Jedis 实例, 完成后将 Jedis 实例回收到连接池中. -
JedisPool.getResource
: 从连接池获取一个 Jedis 连接, 注意:Jedis
使用完毕后需要调用Jedis.close
方法释放资源.(Jedis
实现了AutoCloseable
, 推荐使用try-with-resource
的写法)
Lettuce
https://lettuce.io/
引入依赖:
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
Lettuce 是一个可扩展的 Redis 客户端,用于构建非阻塞的 Reactive 应用程序. 它基于 Netty 框架构建, 性能较高, 且支持很多 redis 的高级特性. 目前 springboot2.0 已将 Lettuce 作为默认 redis 客户端. 与上一小节对应, 操作 string 的使用示例如下:
/** redis client */
private static final RedisClient CLIENT = RedisClient.create("redis://@test-redis-server:6379/0");
// test Binary-safe strings with Lettuce
try (StatefulRedisConnection<String, String> connection = CLIENT.connect()) {RedisCommands<String, String> commands = connection.sync();
{ // SET mykey myvalue
String result = commands.set("mykey", "myvalue");
LOGGER.info("cmd: SET mykey myvalue, result: {}", result);
}
{ // GET mykey
String result = commands.get("mykey");
LOGGER.info("cmd: GET mykey, result: {}", result);
}
{ // KEYS my*
List<String> keys = commands.keys("my*");
LOGGER.info("cmd: KEYS my*, result: {}", JsonUtils.writeValueAsString(keys, true));
}
{ // EXISTS mykey
Long result = commands.exists("mykey");
LOGGER.info("cmd: EXISTS mykey, result: {}", result);
}
{ // DEL mykey
Long result = commands.del("mykey");
LOGGER.info("cmd: DEL mykey, result: {}", result);
}
{ // GET mykey
String result = commands.get("mykey");
LOGGER.info("cmd: GET mykey, result: {}", result);
}
}
Spring 集成
spring-data-redis
是Spring Data
家族的一部分, 提供了简单的配置以轻松访问 redis, 针对存储操作提供了低级别和高级别的抽象, 将开发人员从基础实现中解放出来.
引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.1.5.RELEASE</version>
</dependency>
使用 spring-data-redis
开发时, 可能最常使用的就是RedisTemplate
, 所以在开始前我们先了解下RedisTemplate
:
-
RedisTemplate
是一个简化了 Redis 访问的工具类. - 线程安全(thread-safe), 作为单例使用即可.
- 其实现围绕
execute
方法, 支持 callback, 它提供的RedisConnection
处理方式不需要关心连接的声明周期(简言之就是不用创建也不用关连接)
使用方法很简单, 首先在 Configuration
中定义 StringRedisTemplate
的 Bean:
/**
* StringRedisTemplate
* @param factory
* @return
*/
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {StringRedisTemplate template = new StringRedisTemplate(factory);
StringRedisSerializer serializer = new StringRedisSerializer(); // (一)
template.setKeySerializer(serializer); // (二)
template.setHashKeySerializer(serializer);
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);
return template;
}
- (一):
RedisSerializer
: 对象到二进制数组序列化和反序列化接口, 序列化和反序列化 key 和 value,StringRedisSerializer
、GenericJackson2JsonRedisSerializer
都是其实现. - (二):
KeySerializer
用来序列化 redis key,HashKeySerializer
用来序列化 redis hash 数据结构的 field. 请勿混淆.
当然, 不要忘记了 application.yml
中添加 redis 相关配置:
spring:
redis:
host: test-redis-server
port: 6379
准备工作完成了, 现在就来体验一下, 同样地与前文对应, 操作 string 的使用示例如下: :
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* test Binary-safe strings with RedisTemplate
*/
@Test
public void testStringRedisTemplateSimple() {
{ // SET mykey myvalue
stringRedisTemplate.opsForValue().set("mykey", "myvalue");
}
{ // GET mykey
String result = stringRedisTemplate.opsForValue().get("mykey");
LOGGER.info("cmd: GET mykey, result: {}", result);
}
{ // KEYS my*
Set<String> keys = stringRedisTemplate.keys("my*");
LOGGER.info("cmd: KEYS my*, result: {}", JsonUtils.writeValueAsString(keys, true));
}
{ // EXISTS mykey
Boolean result = stringRedisTemplate.hasKey("mykey");
LOGGER.info("cmd: EXISTS mykey, result: {}", result);
}
{ // DEL mykey
Boolean result = stringRedisTemplate.delete("mykey");
LOGGER.info("cmd: DEL mykey, result: {}", result);
}
{ // GET mykey
String result = stringRedisTemplate.opsForValue().get("mykey");
LOGGER.info("cmd: GET mykey, result: {}", result);
}
}
-
opsForValue
: 获取Binary-safe strings
的操作类ValueOperations
(即 spring 对 redis 操作的一个封装类. 同样地, 对hash
、set
等也有其对应的封装HashOperations
、SetOperations
等).
[版权声明]
本文发布于朴瑞卿的博客, 非商业用途允许转载, 但转载必须保留原作者朴瑞卿 及链接:blog.piaoruiqing.com. 如有授权方面的协商或合作, 请联系邮箱: piaoruiqing@gmail.com.
进阶
划分应用缓存
不同应用的缓存可以简单地通过 key 的 前缀 来划分
让我们来思考这样一个问题, 如果我们想要对不同应用 (服务) 的缓存进行划分, 以便于管理和维护, 应该如何实现?
或许增加前缀是一个不错的想法, 但如果每次编码中都需要将前缀 prefix
拼接到 key 中, 一方面增加了工作量, 另一份面也增加了出错的风险, 如果忘记拼接了怎么办. 对, 也许你也想到了, 前文提到 RedisSerializer
是spring-data-redis
对象到二进制数组序列化和反序列化接口, 用来序列化和反序列化 key 和 value, 我们可以从这里做文章:
public interface RedisSerializer<T> {
/**
* Serialize the given object to binary data.
*
* @param t object to serialize. Can be {@literal null}.
* @return the equivalent binary data. Can be {@literal null}.
*/
@Nullable
byte[] serialize(@Nullable T t) 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;
}
-
serialize
: 对象 -> byte 数组, 当调用RedisTemplate
“ 存 ” 相关的方法时, 会用到这个方法, 将存入的对象转化为字节数组, 然后存储到 redis. -
deserialize
: byte 数组 -> 对象, 当调用RedisTemplate
“ 取 ” 相关的方法时, 会用到这个方法, 将从 redis 取出的数据反序列化为对象.
既然存取都和 RedisSerializer
有必然的联系, 那么可以通过实现该接口并指定 RedisTemplate
的KeySerializer
来实现增加前缀的功能. 如此一来, 增加前缀的操作就从业务中剥离出来, 对于调用方来说, 完全是透明的, 还算优雅, 具体实现如下:
/**
* generic redis key serializer
* @author piaoruiqing
* @date: 2019-06-11 22:37
*/
public class GenericRedisKeySerializer implements RedisSerializer<Object> {
private final Charset charset;
private String prefix;
private int index;
public GenericRedisKeySerializer(String prefix) {this(prefix, StandardCharsets.UTF_8);
}
public GenericRedisKeySerializer(String prefix, Charset charset) {Assert.notNull(charset);
Assert.notNull(prefix);
this.charset = charset;
this.prefix = prefix + ":";
index = this.prefix.length();}
@Override
public String deserialize(byte[] bytes) {if (null == bytes) {return null;}
String key = new String(bytes, charset);
if (key.indexOf(prefix) == 0) {return key.substring(index, key.length());
}
return key;
}
@Override
public byte[] serialize(Object key) {if (null == key) {return null;}
String string = key.toString();
if (!string.startsWith(prefix)) {string = prefix + string;}
return string.getBytes(charset);
}
}
将前文的 StringRedisTemplate
稍作修改:
@Value("${spring.application.name:undefined}")
private String applicationName;
/**
* StringRedisTemplate
* @param factory
* @return
*/
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {StringRedisTemplate template = new StringRedisTemplate(factory);
// StringRedisSerializer serializer = new StringRedisSerializer();
GenericRedisKeySerializer serializer = new GenericRedisKeySerializer(applicationName);
template.setKeySerializer(serializer);
template.setHashKeySerializer(serializer);
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);
return template;
}
-
StringRedisSerializer
替换为自定义的GenericRedisKeySerializer
并指定前缀为应用名
体验一下:
stringRedisTemplate.opsForValue().set("mykey", "myvalue");
String result = stringRedisTemplate.opsForValue().get("mykey"); // "myvalue"
连接到 redis 查看 key, 已经带有前缀了
root@ubuntu:/home/ubuntu# docker exec -it redis redis-cli
127.0.0.1:6379> keys *
1) "redis-simple:mykey"
自定义序列化
RedisTemplate
默认使用 JDK 序列化JdkSerializationRedisSerializer
, 我们可以指定使用其他方式的序列化, 比如 JSON、protostuff
前文已经描述了如何自定义 key 的序列化方式, value 的序列化配置与其相同, 都是实现 RedisSerializer
并在创建 RedisTemplate
时指定, 就不重复贴代码了.
常用的序列化方式有几种:
-
JDK
: 默认, 比较方便, 可序列化所有的类, 但速度慢且占空间较大. -
JSON
: 性能好, 输出内容比较易读. -
Protostuff
: 性能很高, 速度快且占用空间较小.
结语
本文针对 redis 讲解了 redis java 客户端的使用、与 spring 集成以及进阶使用, 后续将针对 Redis 的其他使用技巧进行讲解, 敬请关注.
参考文献
- https://redis.io
- https://spring.io/projects/spring-data-redis
- https://github.com/xetorthio/jedis
- https://lettuce.io/
[版权声明]
本文发布于朴瑞卿的博客, 非商业用途允许转载, 但转载必须保留原作者朴瑞卿 及链接:blog.piaoruiqing.com. 如有授权方面的协商或合作, 请联系邮箱: piaoruiqing@gmail.com.