Redis使用进阶

Redis使用进阶(Java)

关键词

Jedis
Lettuce
spring-data-redis

前言

本文将针对使用Java集成Redis进行讲解, JedisLettuce 的使用仅作简单描述, springredis 集成及使用将作为主要讲解内容.

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-redisSpring 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, StringRedisSerializerGenericJackson2JsonRedisSerializer 都是其实现.

  • (二): 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操作的一个封装类. 同样地, 对 hashset 等也有其对应的封装 HashOperationsSetOperations 等).

[版权声明]

本文发布于朴瑞卿的博客, 允许非商业用途转载, 但转载必须保留原作者朴瑞卿 及链接: https://blog.piaoruiqing.com .

如有授权方面的协商或合作, 请联系邮箱: piaoruiqing@gmail.com .

进阶

划分应用缓存

不同应用的缓存可以简单地通过key的 前缀 来划分

让我们来思考这样一个问题, 如果我们想要对不同应用(服务)的缓存进行划分, 以便于管理和维护, 应该如何实现?

或许增加前缀是一个不错的想法, 但如果每次编码中都需要将前缀 prefix 拼接到key中, 一方面增加了工作量, 另一份面也增加了出错的风险, 如果忘记拼接了怎么办. 对, 也许你也想到了, 前文提到 RedisSerializerspring-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 有必然的联系, 那么可以通过实现该接口并指定 RedisTemplateKeySerializer 来实现增加前缀的功能. 如此一来, 增加前缀的操作就从业务中剥离出来, 对于调用方来说, 完全是透明的, 还算优雅, 具体实现如下:

/**
 * 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的其他使用技巧进行讲解, 敬请关注.

参考文献

© 2019,朴瑞卿. 版权所有.

0

我来评几句
登录后评论

已发表评论数()

相关站点

热门文章