Redis 基础教程

Redis 命令

Redis 高级教程

Redis 笔记

Java 中,几种 Redis Client 的使用


Redis 基于 Java 的客户端非常多,其中比较常用的有 Jedis、lettuce 及 Redisson,此外还有 aredis、JDBC-Redis、Jedipus、JRedis、redis-protocol、RedisClient、RJC、vertx-redis-client 等,除此之外,还有更高层次的抽象,如 spring-data-redis。

Jedis

市面上如果搜索 Redis 基于 Java 的客户端,应该最多的是 Jedis,这个在 GitHub 上非常的火。

Jedis 安装

Jedis 的安装前提是 Java 环境已经安装好,然后下载诸如 jedis-2.9.0.jar 即可。

如果项目基于 maven 构建,导入如下 maven 坐标即可:

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>

gradle 坐标如下:

compile group: 'redis.clients', name: 'jedis', version: '2.9.0'

使用 Jedis 操作 Redis

Jedis 连接 Redis 有很多形式,可以最原始的创建 Jedis 对象,也可以基于 Jedis 连接池(生产环境使用),同时支持基于客户端的分片结构的连接,还有在 Redis3.0 版本之后支持的 RedisCluster(Redis 集群)连接方式。

首先是创建 Jedis 对象的操作方式。

import redis.clients.jedis.Jedis;

public class JedisDemo {

    public static void main(String[] args) {

        Jedis jedis = new Jedis("127.0.0.1", 6379);
        System.out.println(jedis.ping());
        System.out.println(jedis.mset("heilongjiang", "haerbin", "jilin", "changchun"));
        System.out.println(jedis.mget("heilongjiang", "jilin"));

    }
}

输出结果如下:

PONG
OK
[haerbin, changchun]

其次是基于 Jedis 连接池的连接及相关操作。

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class JedisPoolDemo {

    public static void main(String[] args) {

        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(512);
        jedisPoolConfig.setMaxIdle(128);
        jedisPoolConfig.setMaxWaitMillis(1000);
        jedisPoolConfig.setTestOnBorrow(true);

        JedisPool jedisPool = new JedisPool(jedisPoolConfig, "127.0.0.1", 6379);
        Jedis jedis = jedisPool.getResource();
        System.out.println(jedis.echo("hello jedis pool"));
        System.out.println(jedis.set("JedisPool", "test"));
        System.out.println(jedis.get("JedisPool"));

    }
}

输出结果如下:

hello jedis pool
OK
test

再次是客户端分片架构的连接。

import redis.clients.jedis.JedisShardInfo;
import redis.clients.jedis.ShardedJedis;

import java.util.Arrays;

public class ShardedJedisDemo {

    public static void main(String[] args) {

        JedisShardInfo jedisShardInfo = new JedisShardInfo("127.0.0.1", 6379);
        JedisShardInfo jedisShardInfo1 = new JedisShardInfo("127.0.0.1", 6380);

        ShardedJedis shardedJedis = new ShardedJedis(Arrays.asList(jedisShardInfo, jedisShardInfo1));
        System.out.println(shardedJedis.hset("ShardedJedis", "field1", "test"));
        System.out.println(shardedJedis.hget("ShardedJedis", "field1"));

    }

}

输出结果如下:

1
test

此外,下面是客户端分片架构的基于连接池的连接。

import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisShardInfo;
import redis.clients.jedis.ShardedJedis;
import redis.clients.jedis.ShardedJedisPool;

import java.util.Arrays;

public class ShardedJedisPoolDemo {

    public static void main(String[] args) {

        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(512);
        jedisPoolConfig.setMaxIdle(128);
        jedisPoolConfig.setMaxWaitMillis(1000);
        jedisPoolConfig.setTestOnBorrow(true);

        JedisShardInfo jedisShardInfo = new JedisShardInfo("127.0.0.1", 6379);
        JedisShardInfo jedisShardInfo1 = new JedisShardInfo("127.0.0.1", 6380);

        ShardedJedisPool shardedJedisPool = new ShardedJedisPool(jedisPoolConfig, Arrays.asList(jedisShardInfo, jedisShardInfo1));
        ShardedJedis shardedJedis = shardedJedisPool.getResource();
        System.out.println(shardedJedis.set("ShardedJedisPool", "test"));
        System.out.println(shardedJedis.get("ShardedJedisPool"));
    }
}

输出结果如下:

OK
test

下面是基于 RedisCluster 的连接操作。

import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;

import java.util.HashSet;
import java.util.Set;

public class JedisClusterDemo {

    public static void main(String[] args) {

        Set<HostAndPort> hostAndPortSet = new HashSet<>();
        hostAndPortSet.add(new HostAndPort("127.0.0.1", 7000));
        hostAndPortSet.add(new HostAndPort("127.0.0.1", 7001));
        hostAndPortSet.add(new HostAndPort("127.0.0.1", 7002));
        JedisCluster jedisCluster = new JedisCluster(hostAndPortSet);
        System.out.println(jedisCluster.lpush("JedisCluster", "1"));
        System.out.println(jedisCluster.rpop("JedisCluster"));

    }
}

输出结果如下:

1
1

最后是基于 RedisClusterPool 的连接操作。

import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPoolConfig;

import java.util.HashSet;
import java.util.Set;

public class JedisClusterPoolDemo {

    public static void main(String[] args) {

        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(512);
        jedisPoolConfig.setMaxIdle(128);
        jedisPoolConfig.setMaxWaitMillis(1000);
        jedisPoolConfig.setTestOnBorrow(true);

        Set<HostAndPort> hostAndPortSet = new HashSet<>();
        hostAndPortSet.add(new HostAndPort("127.0.0.1", 7000));
        hostAndPortSet.add(new HostAndPort("127.0.0.1", 7001));
        hostAndPortSet.add(new HostAndPort("127.0.0.1", 7002));
        JedisCluster jedisCluster = new JedisCluster(hostAndPortSet, jedisPoolConfig);
        System.out.println(jedisCluster.set("JedisClusterPool", "test"));
        System.out.println(jedisCluster.get("JedisClusterPool"));

    }
}

输出结果如下:

OK
test

Lettuce

Spring Boot 2.0 中 Redis 客户端驱动现在由 Jedis 变为了 Lettuce,这是随意的根据喜好的决定,还是有技术上的原因呢?

有其主观的原因(Spring 和 Lettuce 都属于 Pivotal 公司),主要是 Lettuce 的确有很多优秀的特性:

  • 基于 netty,支持事件模型
  • 支持同步、异步、响应式的方式
  • 可以方便的连接 Redis Sentinel
  • 完全支持 Redis Cluster
  • SSL 连接
  • Streaming API
  • CDI 和 Spring 的集成
  • 兼容 Java 8 和 9

lettuce 安装

如果项目基于 maven 构建,导入如下 maven 坐标即可:

<dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
    <version>5.1.0.RELEASE</version>
</dependency>

gradle 坐标如下:

compile group: 'io.lettuce', name: 'lettuce-core', version: '5.1.0.RELEASE'

lettuce 多线程共享

Jedis 是直连模式,在多个线程间共享一个 Jedis 实例时是线程不安全的,如果想要在多线程环境下使用 Jedis,需要使用连接池,每个线程都去拿自己的 Jedis 实例,当连接数量增多时,物理连接成本就较高了。

Lettuce 是基于 netty 的,连接实例可以在多个线程间共享,所以,一个多线程的应用可以使用一个连接实例,而不用担心并发线程的数量。

lettuce 异步

异步的方式可以让我们更好的利用系统资源,而不用浪费线程等待网络或磁盘 I/O。

Lettuce 是基于 netty 的,netty 是一个多线程、事件驱动的 I/O 框架,所以 Lettuce 可以帮助我们充分利用异步的优势。

如下代码示例:

连接:

//  连接
RedisClient redisClient = RedisClient.create("redis://localhost");
RedisAsyncCommands<String, String> commands = redisClient.connect().async();

使用阻塞的方式读取:

//  使用阻塞的方式读取
RedisFuture<String> future = commands.get("key");
try {
    String value = future.get();
    System.out.println(value);
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (ExecutionException e) {
    e.printStackTrace();
}

设置阻塞读取时的超时时间:

//  设置阻塞读取时的超时时间
RedisFuture<String> future = commands.get("key");
try {
    String value = future.get(1, TimeUnit.MINUTES);
    System.out.println(value);
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (ExecutionException e) {
    e.printStackTrace();
} catch (TimeoutException e) {
    e.printStackTrace();
}

异步方式,当 RedisFuture<T> 是完成状态时自动触发后面的动作:

//  异步方式,当 RedisFuture<T>是完成状态时自动触发后面的动作
RedisFuture<String> future = commands.get("key");
future.thenAccept(new Consumer<String>() {
    @Override
    public void accept(String value) {
        System.out.println(value);
    }
});

lettuce 很好的支持 Redis Cluster

对 Redis Cluster 的支持包括:

  • 支持所有的 Cluster 的命令
  • 基于哈希槽的命令路由
  • 对 Cluster 命令的高层抽象
  • 在多节点上执行命令
  • 根据槽和地址端口直接连接 cluster 中的节点
  • SSL 和认证
  • Cluster 拓扑的更新
  • 发布/订阅

lettuce 支持 Streaming API

Redis 中可能会有海量的数据,当你获取一个大的数据集合时,有可能会被撑爆,Lettuce 可以让我们使用流的方式来处理。

示例 1:

commands.hgetall(new KeyValueStreamingChannel<String, String>() {
            @Override
            public void onKeyValue(String key, String value) {
                System.out.println(key + "," + value);
            }
        }, "hkey");

示例 2:

commands.lpush("key", "one");
commands.lpush("key", "two");
commands.lpush("key", "three");
commands.lrange(new ValueStreamingChannel<String>() {
    @Override
    public void onValue(String value) {
        System.out.println(value);
    }
}, "key", 0, -1);

使用 lettuce 时,常遇见的错误

使用 lettuce 时,主要相关的异常类都继承自 io.lettuce.core.RedisException 类,具体包括如下几类:

连接相关异常

lettuce 的连接相关异常主要抛 io.lettuce.core.RedisConnectionException,如下列情况:

  • redis client 和 redis 服务无法建立理解时,会报出该异常,类似“io.lettuce.core.RedisConnectionException: Unable to connect to xxxxxx”;

命令超时相关异常

lettuce 框架下命令如果超时(默认命令超时设置为 60 s),就会报出 io.lettuce.core.RedisCommandTimeoutException,如下列情况:

  • redis server 关闭或连接不上,执行的命令会报出该异常,类似“io.lettuce.core.RedisCommandTimeoutException: Command timed out after 1 minute(s)”;