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)
”;