31

Redis使用进阶

 4 years ago
source link: https://www.tuicool.com/articles/um2e2ya
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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 .

如有授权方面的协商或合作, 请联系邮箱: [email protected] .

进阶

划分应用缓存

不同应用的缓存可以简单地通过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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK