1. Overview
1.概述
In this tutorial, we’ll introduce Jedis, a client library in Java for Redis. This popular in-memory data structure store can persist on a disk as well. It’s driven by a keystore-based data structure to persist data, and can be used as a database, cache, message broker, etc.
在本教程中,我们将介绍Jedis,这是一个用于Redis的Java客户端库。这个流行的内存数据结构存储也可以在磁盘上持久化。它由一个基于keystore的数据结构来驱动数据的持久化,并可作为数据库、缓存、消息代理等使用。
We’ll begin by discussing what Jedis is all about, and what kind of situations it’s useful in. Then we’ll elaborate on the various data structures, and explain transactions, pipelining, and the publish/subscribe feature. Finally, we’ll learn about connection pooling and the Redis Cluster.
我们将首先讨论Jedis是什么,以及它在什么样的情况下是有用的。然后,我们将详细介绍各种数据结构,并解释事务、管道和发布/订阅功能。最后,我们将学习连接池和Redis集群。
2. Why Jedis?
2.为什么是杰迪人?
Redis lists the most well-known client libraries on their official site. There are multiple alternatives to Jedis, but only two are currently worthy of their recommendation star, lettuce, and Redisson.
Redis在其官方网站上列出了最知名的客户端库。Jedis有多个替代品,但目前只有两个值得他们推荐的明星,即lettuce,和Redisson。
These two clients do have some unique features, like thread safety, transparent reconnection handling, and an asynchronous API, all features that Jedis lacks.
这两个客户端确实有一些独特的功能,比如线程安全、透明的重连处理和异步API,这些都是Jedis所缺乏的功能。
However, Jedis is small, and considerably faster than the other two. Moreover, it’s the Spring Framework developers’ client library of choice, and it has the biggest community of all three.
然而,Jedis很小,而且比其他两个快得多。此外,它是Spring框架开发者的首选客户端库,而且它拥有所有三个库中最大的社区。
3. Maven Dependencies
3.Maven的依赖性
We’ll start by declaring the necessary dependency in the pom.xml:
我们将首先在pom.xml中声明必要的依赖关系。
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.1</version>
</dependency>
The latest version of the library is available on this page.
该库的最新版本可在本页面上找到。
4. Redis Installation
4.Redis的安装
Then we’ll install and fire up one of the latest versions of Redis. For this tutorial, we’re running the latest stable version (3.2.1), but any post 3.x version should be okay.
然后,我们将安装并启动一个最新版本的Redis。在本教程中,我们运行的是最新的稳定版本(3.2.1),但任何3.x之后的版本都应该是可以的。
For more information about Redis for Linux and Macintosh, check out this link; they have very similar basic installation steps. Windows isn’t officially supported, but this port is well maintained.
有关 Linux 和 Macintosh 的 Redis 的更多信息,请查看这个链接;它们的基本安装步骤非常相似。Windows不受官方支持,但这个端口维护得很好。
Now we can dive in directly, and connect to it from our Java code:
现在我们可以直接深入进去,从我们的Java代码中连接到它。
Jedis jedis = new Jedis();
The default constructor will work just fine unless we started the service on a non-default port or a remote machine, in which case, we can configure it correctly by passing the correct values as parameters into the constructor.
除非我们在一个非默认的端口或远程机器上启动服务,否则默认的构造函数可以正常工作,在这种情况下,我们可以通过将正确的值作为参数传入构造函数来正确配置它。
5. Redis Data Structures
5.Redis数据结构
Most of the native operation commands are supported, and conveniently enough, they normally share the same method name.
大多数本地操作命令都被支持,而且很方便,它们通常共享同一个方法名称。
5.1. Strings
5.1.字符串
Strings are the most basic kind of Redis value, useful for when we need to persist simple key-value data types:
字符串是最基本的一种Redis值,在我们需要持久化简单的键值数据类型时非常有用。
jedis.set("events/city/rome", "32,15,223,828");
String cachedResponse = jedis.get("events/city/rome");
The variable cachedResponse will hold the value 32,15,223,828. Coupled with expiration support, which we’ll discuss later, it can work as a lightning fast and simple to use cache layer for HTTP requests received at our web application, along with other caching requirements.
变量cachedResponse将保持32,15,223,828的值。再加上过期支持(我们将在后面讨论),它可以作为一个闪电般的快速和简单的缓存层,用于我们的Web应用程序收到的HTTP请求,以及其他的缓存要求。
5.2. Lists
5.2.列表
Redis Lists are simply lists of strings sorted by insertion order. This makes them an ideal tool to implement message queues, for instance:
Redis 列表只是按插入顺序排序的字符串列表。这使得它们成为实现消息队列的理想工具,例如。
jedis.lpush("queue#tasks", "firstTask");
jedis.lpush("queue#tasks", "secondTask");
String task = jedis.rpop("queue#tasks");
The variable task will hold the value firstTask. Remember that we can serialize any object and persist it as a string, so messages in the queue can carry more complex data when required.
变量task将持有firstTask的值。请记住,我们可以序列化任何对象,并将其持久化为一个字符串,所以队列中的消息在需要时可以携带更复杂的数据。
5.3. Sets
5.3.集合
Redis Sets are an unordered collection of Strings that come in handy when we want to exclude repeated members:
Redis Sets是一个无序的字符串集合,当我们想排除重复的成员时,它就会派上用场。
jedis.sadd("nicknames", "nickname#1");
jedis.sadd("nicknames", "nickname#2");
jedis.sadd("nicknames", "nickname#1");
Set<String> nicknames = jedis.smembers("nicknames");
boolean exists = jedis.sismember("nicknames", "nickname#1");
The Java Set nicknames will have a size of 2, as the second addition of nickname#1 was ignored. Also, the exists variable will have a value of true. The method sismember enables us to check for the existence of a particular member quickly.
Java集合nicknames的大小将为2,因为nickname#1的第二次添加被忽略了。另外,exists变量的值为true。方法sismember使我们能够快速检查某个特定成员是否存在。
5.4. Hashes
5.4.哈希值
Redis Hashes are mapping between String fields and String values:
Redis Hashes是String字段和String值之间的映射。
jedis.hset("user#1", "name", "Peter");
jedis.hset("user#1", "job", "politician");
String name = jedis.hget("user#1", "name");
Map<String, String> fields = jedis.hgetAll("user#1");
String job = fields.get("job");
As we can see, hashes are a very convenient data type when we want to access an object’s properties individually, since we don’t need to retrieve the whole object.
我们可以看到,当我们想单独访问一个对象的属性时,哈希是一个非常方便的数据类型,因为我们不需要检索整个对象。
5.5. Sorted Sets
5.5.分类的套装
Sorted Sets are like a Set, where each member has an associated ranking that’s used for sorting them:
排序集就像一个集合,其中每个成员都有一个相关的排名,用于对它们进行排序。
Map<String, Double> scores = new HashMap<>();
scores.put("PlayerOne", 3000.0);
scores.put("PlayerTwo", 1500.0);
scores.put("PlayerThree", 8200.0);
scores.entrySet().forEach(playerScore -> {
jedis.zadd(key, playerScore.getValue(), playerScore.getKey());
});
String player = jedis.zrevrange("ranking", 0, 1).iterator().next();
long rank = jedis.zrevrank("ranking", "PlayerOne");
The variable player will hold the value PlayerThree because we’re retrieving the top 1 player and he’s the one with the highest score. The rank variable will have a value of 1 because PlayerOne is second in the ranking and the ranking is zero-based.
变量player将保持PlayerThree的值,因为我们正在检索前1名的球员,他是分数最高的那个。变量rank的值为1,因为PlayerOne是排名第二的,而且排名是以0为基础的。
6. Transactions
6.事务
Transactions guarantee atomicity and thread safety operations, which means that requests from other clients will never be handled concurrently during Redis transactions:
事务保证了原子性和线程安全操作,这意味着在Redis事务期间,其他客户端的请求永远不会被同时处理。
String friendsPrefix = "friends#";
String userOneId = "4352523";
String userTwoId = "5552321";
Transaction t = jedis.multi();
t.sadd(friendsPrefix + userOneId, userTwoId);
t.sadd(friendsPrefix + userTwoId, userOneId);
t.exec();
We can even make a transaction’s success dependent on a specific key by “watching” it right before we instantiate our Transaction:
我们甚至可以通过在实例化我们的交易之前 “观察 “它,使交易的成功依赖于一个特定的键。
jedis.watch("friends#deleted#" + userOneId);
If the value of that key changes before the transaction is executed, the transaction won’t be completed successfully.
如果该键的值在事务执行前发生变化,事务将不会成功完成。
7. Pipelining
7.Pipelining
When we have to send multiple commands, we can pack them together in one request and save connection overhead by using pipelines. It’s essentially a network optimization. As long as the operations are mutually independent, we can take advantage of this technique:
当我们必须发送多个命令时,我们可以将它们打包在一个请求中,并通过使用管道节省连接开销。这本质上是一种网络优化。只要这些操作是相互独立的,我们就可以利用这种技术。
String userOneId = "4352523";
String userTwoId = "4849888";
Pipeline p = jedis.pipelined();
p.sadd("searched#" + userOneId, "paris");
p.zadd("ranking", 126, userOneId);
p.zadd("ranking", 325, userTwoId);
Response<Boolean> pipeExists = p.sismember("searched#" + userOneId, "paris");
Response<Set<String>> pipeRanking = p.zrange("ranking", 0, -1);
p.sync();
String exists = pipeExists.get();
Set<String> ranking = pipeRanking.get();
Notice we don’t get direct access to the command responses. Instead, we’re given a Response instance from which we can request the underlying response after the pipeline has been synced.
注意,我们不能直接访问命令的响应。相反,我们得到了一个Response实例,在管道同步后,我们可以从中请求基础响应。
8. Publish/Subscribe
8.发布/订阅
We can use the Redis messaging broker functionality to send messages between the different components of our system. We just need to make sure the subscriber and publisher threads don’t share the same Jedis connection.
我们可以使用Redis的消息代理功能,在我们系统的不同组件之间发送消息。我们只需要确保订阅者和发布者的线程不共享同一个Jedis连接。
8.1. Subscriber
8.1.签约人
We can subscribe and listen to messages sent to a channel:
我们可以订阅和收听发送到一个频道的消息。
Jedis jSubscriber = new Jedis();
jSubscriber.subscribe(new JedisPubSub() {
@Override
public void onMessage(String channel, String message) {
// handle message
}
}, "channel");
Subscribe is a blocking method; we’ll need to unsubscribe from the JedisPubSub explicitly. Here we’ve overridden the onMessage method, but there are many more useful methods available to override.
订阅是一个阻塞的方法;我们需要明确地从JedisPubSub中取消订阅。这里我们重写了onMessage方法,但是还有很多有用的方法可以重写。
8.2. Publisher
8.2.出版商
Then we can simply send messages to that same channel from the publisher’s thread:
然后我们可以简单地从发布者的线程中向同一通道发送消息。
Jedis jPublisher = new Jedis();
jPublisher.publish("channel", "test message");
9. Connection Pooling
9.连接池
It’s important to know that the way we’ve been dealing with our Jedis instance is naive. In a real-world scenario, we don’t want to use a single instance in a multi-threaded environment, as a single instance isn’ thread-safe.
重要的是要知道,我们处理Jedis实例的方式是天真的。在现实世界中,我们不想在多线程环境中使用单个实例,因为单个实例是线程安全的。
Luckily enough, we can easily create a pool of connections to Redis for us to reuse on demand. This pool is thread safe and reliable, as long as we return the resource to the pool when we’re done with it.
幸运的是,我们可以很容易地创建一个连接Redis的池子,供我们按需重用。这个池子是线程安全和可靠的,只要我们在使用完资源后将其返回到池子里。
Let’s create the JedisPool:
让我们创建JedisPool。
final JedisPoolConfig poolConfig = buildPoolConfig();
JedisPool jedisPool = new JedisPool(poolConfig, "localhost");
private JedisPoolConfig buildPoolConfig() {
final JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(128);
poolConfig.setMaxIdle(128);
poolConfig.setMinIdle(16);
poolConfig.setTestOnBorrow(true);
poolConfig.setTestOnReturn(true);
poolConfig.setTestWhileIdle(true);
poolConfig.setMinEvictableIdleTimeMillis(Duration.ofSeconds(60).toMillis());
poolConfig.setTimeBetweenEvictionRunsMillis(Duration.ofSeconds(30).toMillis());
poolConfig.setNumTestsPerEvictionRun(3);
poolConfig.setBlockWhenExhausted(true);
return poolConfig;
}
Since the pool instance is thread safe, we can store it somewhere statically, but we should take care of destroying the pool to avoid leaks when the application is being shutdown.
由于池子的实例是线程安全的,我们可以把它静态地存储在某个地方,但我们应该注意销毁池子,以避免在应用程序关闭时发生泄漏。
Now we can make use of our pool from anywhere in the application when needed:
现在我们可以在需要时从应用程序的任何地方利用我们的池子。
try (Jedis jedis = jedisPool.getResource()) {
// do operations with jedis resource
}
We used the Java try-with-resources statement to avoid having to manually close the Jedis resource, but if we can’t use this statement, we can also close the resource manually in the finally clause.
我们使用Java try-with-resources语句来避免手动关闭Jedis资源,但如果我们不能使用这个语句,我们也可以在finally子句中手动关闭资源。
It’s important to use a pool like we’ve described in our application if we don’t want to face nasty multi-threading issues. We can also play with the pool configuration parameters to adapt it to the best setup for our system.
如果我们不想面对讨厌的多线程问题,在我们的应用程序中使用一个像我们描述的池是很重要的。我们还可以玩玩池的配置参数,使其适应我们系统的最佳设置。
10. Redis Cluster
10.Redis群集
This Redis implementation provides easy scalability and high availability. To gain more familiarity with it, we can check out their official specification. We won’t cover the Redis cluster setup, since that’s a bit out of the scope for this article, but we shouldn’t have any problems with it when we’re done with the documentation.
这种Redis实现提供了轻松的可扩展性和高可用性。为了更熟悉它,我们可以查看其官方规范。我们将不涉及Redis集群的设置,因为这有点超出了本文的范围,但当我们完成文档后,我们应该不会有任何问题。
Once we have it ready, we can start using it from our application:
一旦我们准备好了,我们就可以从我们的应用程序中开始使用它。
try (JedisCluster jedisCluster = new JedisCluster(new HostAndPort("localhost", 6379))) {
// use the jedisCluster resource as if it was a normal Jedis resource
} catch (IOException e) {}
We only need to provide the host and port details from one of our master instances, and it will auto-discover the rest of the instances in the cluster.
我们只需要提供其中一个主实例的主机和端口信息,它就会自动发现集群中的其他实例。
This is certainly a very powerful feature, but it’s not a silver bullet. When using Redis Cluster, we can’t perform transactions or use pipelines, two important features on which many applications rely for ensuring data integrity.
这当然是一个非常强大的功能,但它不是一个银弹。当使用Redis Cluster时,我们不能执行事务或使用管道,这是许多应用程序为确保数据完整性而依赖的两个重要功能。
Transactions are disabled because in a clustered environment, keys will be persisted across multiple instances. Operation atomicity and thread safety can’t be guaranteed for operations that involve command execution in different instances.
交易被禁用是因为在集群环境中,键值将在多个实例中被持久化。对于涉及在不同实例中执行命令的操作,不能保证操作的原子性和线程安全。
Some advanced key creation strategies will ensure that the data we want persisted in the same instance will get persisted that way. In theory, that should enable us to perform transactions successfully using one of the underlying Jedis instances of the Redis Cluster.
一些高级的密钥创建策略将确保我们想要在同一实例中持久化的数据将得到这样的持久化。理论上,这应该能让我们使用Redis集群的一个底层Jedis实例成功执行事务。
Unfortunately, we can’t currently find out which Redis instance a particular key is saved in using Jedis (which is actually supported natively by Redis), so we don’t know which of the instances we must perform the transaction operation on. If we want to learn more, more information is available here.
不幸的是,我们目前无法使用Jedis(实际上Redis原生支持Jedis)找出某个特定密钥保存在哪个Redis实例中,因此我们不知道我们必须在哪个实例上执行事务操作。如果我们想了解更多,可以在这里获得更多信息。
11. Conclusion
11.结论
The vast majority of the features from Redis are already available in Jedis, and its development moves forward at a good pace.
Redis的绝大部分功能在Jedis中已经可以使用,而且它的发展速度也很好。
It gives us the ability to integrate a powerful in-memory storage engine in our application with very little hassle. We just can’t forget to set up connection pooling to avoid thread safety issues.
它使我们有能力在我们的应用程序中集成一个强大的内存存储引擎,而不需要太多的麻烦。我们只是不能忘记设置连接池以避免线程安全问题。
As always, the source code for this article can be found in the GitHub project.
一如既往,本文的源代码可以在GitHub项目中找到。