翻译 自 http://www.baeldung.com/jedis-java-redis-client-library
Intro to Jedis – the Java Redis Client Library
介绍 关于jedis - java redis客户端库
1. Overview
This article is an introduction to Jedis, a client library in Java for Redis – the popular in-memory data structure
store that can persist on disk as well. It is driven by a keystore-based data structure to persist data and can be
used as a database, cache, message broker, etc.
First, we are going to explain in which kind of situations Jedis is useful and what it is about.
In the subsequent sections we are elaborating on the various data structures and explaining transactions, pipelining
and the publish/subscribe feature. We conclude with connection pooling and Redis Cluster.
1 概述
这是一遍介绍接jedis的文章,一个redis客户端库操作
redis,这是目前最受欢迎的内存数据存储结构同时也
可以存储在磁盘中。它的驱动是根据键值存储数据结构
而且在使用的时候就想数据库,缓存,信息代理等
首先我们将要描述多种关于jedis的使用情况
在后来的章节,我们尽力在多种数据结构上进行解释 传输,
管道,和发布订阅一些特色。我们包括连接池和redis 集群
2. Why Jedis?
Redis lists the most well-known client libraries on their official site. There are multiple alternatives to Jedis,
but only two more are currently worthy of their recommendation star, lettuce, and Redisson.
These two clients do have some unique features like thread safety, transparent reconnection handling and an asynchronous API,
all features of which Jedis lacks.
However, it is small and considerably faster than the other two. Besides, it is the client library of choice of the Spring
Framework developers and it has the biggest community of all three.
2 为什么使用jedis
jedis是目前最流行的redis客户端库在他们的官方主页上
这里有多种替代选择jedis,但是仅仅只有两个目前值得推荐使用
这两种分别是lettuce和Redissson
这两种客户端有独特之处例如线程安全,透明重连接处理和
一些异步的api,所有的这些都是jedis所缺少的
3. Maven Dependencies
Let’s start by declaring the only dependency we will need in the pom.xml:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.1</version>
</dependency>
If you’re looking for the latest version of the library, check out this page.
3 使用Maven Dependencies
让我们开始声明一个独一无二的的dependency ,我们需要在pom.xml中
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.1</version>
</dependency>
如果你想查找最先版的redis库,请点击这里
4. Redis Installation
You will need to install and fire up one of the latest versions of Redis. We are running the latest stable version at this
moment (3.2.1), but any post 3.x version should be okay.
Find here more information about Redis for Linux and Macintosh, they have very similar basic installation steps. Windows is
not officially supported but this port is well maintained.
Thereafter we can directly dive in and connect to it from our Java code:
Jedis jedis = new Jedis();
The default constructor will work just fine unless you have started the service on a non-default port or on a remote machine,
in which case you can configure it correctly by passing
the correct values as parameters into the constructor.
4 Redis 的安装
你应该需要安装和启动一个目前最新版的redis,
我们目前运行的是最先稳定版3.2.1在目前时间范围内。
但是任何发布的3.X版本都是可以的
点击这里获取更多关于redis Linux版和mac的信息。
他们都是一些非常相似基础的安装步骤。Windows不是官方
的支持,但是这个网站是可以的获取到安装windows版本的
在这之后我们直接使用客户端驱动直接连接通过我们的Java代码
Jedis jedis = new Jedis();
这是一个默认的构造方法将会正常启动,除非你将要开始一个服务
在一个非默认端口的在一个远程机器,在这种情况下,
您可以将正确的值作为参数传递给构造函数,
从而正确地配置它。
5. Redis Data Structures
Most of the native operation commands are supported and, conveniently enough, they normally share the same method name.
5 Redis 数据结构
绝大数本地操作命令是支持的,足够的遍历。通常情况先
它们都是共享相同的方法名
5.1. Strings
Strings are the most basic kind of Redis value, useful for when you need to persist simple key-value data types:
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, discussed later,
it can work as a lightning fast
and simple to use cache layer for HTTP requests received at your web application and other caching requirements.
5.1 Strings
stringshi最基本的类型在Redis的值中,常用于持久化单一的键值数据类型
jedis.set("events/city/rome", "32,15,223,828");
String cachedResponse = jedis.get("events/city/rome");
这个变量cachedResponse 将会获得这个值32,15,223,828
于此相关的还有过期支持机制我们稍后再谈论。它可以工作很轻量级很快很方便的
去使用缓存层为HTTP请求获取在你的网络应用层和其他的缓存需求
5.2. Lists
Redis Lists are simply lists of strings, sorted by insertion order and make it an ideal tool to implement, for instance,
message queues:
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 you can serialize any object and persist it as a string,
so messages in the queue can carry more complex data when required.
5.2Lists
redis Lists 是一个简单字符串链表,根据插入的顺序使他更好的运行在一些方面
例如消息队列
jedis.lpush("queue#tasks", "firstTask");
jedis.lpush("queue#tasks", "secondTask");
String task = jedis.rpop("queue#tasks");
这个变量测试将会获取的fistTask的值。记住你可以序列化任何对象
和持久化这个对象作为一个字符串。所以信息在一个队列中
可以携带更复杂的数据当获取时
5.3. Sets
Redis Sets are an unordered collection of Strings that come in handy when you want to exclude repeated members:
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, the second addition of nickname#1 was ignored. Also,
the exists variable will have a value of true, the method sismember
enables you to quickly check for the existence of a particular member.
5.3Sets
Redis Sets是一个无序的String类型集合,当你使用它你可以排除重复对象
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");
这个java Set nicknames 的长度将会是2 在第二次加入
nickname#1时被忽略掉了,所以这个exists变量的值将会是true
这个方法sismember使你能够快速的检查某个变量会员是否存在
5.4. Hashes
Redis Hashes are mapping between String fields and String values:
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 you can see, hashes are a very convenient data type when you want to access object’s properties individually
since you do not need to retrieve the whole object.
5.4 Hashes
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");
正如你所看到一样,hashes 是一个 数据类型当它想通过一个对象的单独属性
而不需要遍历整个对象
5.5. Sorted Sets
Sorted Sets are like a Set where each member has an associated ranking, that is 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.keySet().forEach(player -> {
jedis.zadd("ranking", scores.get(player), player);
});
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 are retrieving the top 1 player and he
is the one with the highest score. The rank variable will have a value of
1 because PlayerOne is the second in the ranking and the ranking is zero-based.
5.5 Sorted Set
Sorted Sets是像Set一样但是他的每个元素hi向关联有序的。
通常在有序的时候使用它们
Map<String, Double> scores = new HashMap<>();
scores.put("PlayerOne", 3000.0);
scores.put("PlayerTwo", 1500.0);
scores.put("PlayerThree", 8200.0);
scores.keySet().forEach(player -> {
jedis.zadd("ranking", scores.get(player), player);
});
String player = jedis.zrevrange("ranking", 0, 1).iterator().next();
long rank = jedis.zrevrank("ranking", "PlayerOne");
变量player 将持有值player3,因为我们检索的是前1名player ,
他是得分最高的player 。rank变量的值为1,
因为PlayerOne是排名的第二名,排名为零。
6. Transactions
Transactions guarantee atomicity and thread safety operations, which means that requests
from other clients will never be handled concurrently during Redis transactions:
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();
You can even make a transaction success dependent on a specific key by “watching” it right before you instantiate your Transaction:
jedis.watch("friends#deleted#" + userOneId);
If the value of that key changes before the transaction is executed, the transaction will not be completed successfully.
6 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();
你也可以使事务成功取决于一个特定的键
通过“watching” 它在实例化你的事务之前
jedis.watch("friends#deleted#" + userOneId);
如果这个键改变在事务执行之前,那么那么这个事务将完全的失败
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 is 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 do not get direct access to the command responses, instead we are given a Response
instance from which we can request the underlying response after the pipeline has been synced.
7 Pinelining(管道)
当我们需要去发送多个命令。我们可以将它们打包在一起通过一个request
节约了连接费用通过使用pipeline。这是对于网络优化是很有必要的
当这些操作相互独立,我们可以从中获取有利是,使用这个技术
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();
注意 我们无法直接访问命令响应 在管道被同步之后,
取而代之的是 我们得到了一个响应实例
我们可以请求底层响应。
8. Publish/Subscribe
We can use the Redis messaging broker functionality to send messages between the different
components of our system. Make sure the subscriber
and publisher threads do not share the same Jedis connection.
8. 发布/订阅
8.1. Subscriber
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, you will need to unsubscribe from the JedisPubSub explicitly. We have overridden the
onMessage method but there are many more useful methods available to override.
8.1 Subscriber
订阅者收听信息来自于一个管道
Jedis jSubscriber = new Jedis();
jSubscriber.subscribe(new JedisPubSub() {
@Override
public void onMessage(String channel, String message) {
// handle message
}
}, "channel");
订阅是一个阻塞方法,你要显示的取消订阅来自JedisPubSub的
我们已经重载了onMessage犯法,但是更多的方法需要去覆盖
8.2. Publisher
Then simply send messages to that same channel from the publisher’s thread:
Jedis jPublisher = new Jedis();
jPublisher.publish("channel", "test message");
8.2 Publisher
就是仅仅是发送一个信息在同一个管道中通过发布者线程
Jedis jPublisher = new Jedis();
jPublisher.publish("channel", "test message");
9. Connection Pooling
It is important to know that the way we have been dealing with our Jedis instance is naive.
In a real world scenario, you do not want to use a single instance in a multi-threaded environment
as a single instance is not thread safe.
Luckily enough we can easily create a pool of connections to Redis for us to reuse on demand,
a pool that is thread safe and reliable as long as you return the
resource to the pool when you are done with it.
Let’s create the 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, you can store it somewhere statically but you 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 you cannot use this statement you can also close the resource manually in the finally clause.
Make sure you use a pool like we have described in your application if you do not want to face nasty
multi-threading issues. You can obviously play with the pool configuration
parameters to adapt it to the best setup in your system.
9 Connnectiong Pooling 连接池
我们都知道使用刚才的方法实例jedishis幼稚的。
在一个真实的方案中,你不想使用一个单一的实例在一个多线程环境下面
因为单一实例在多线程是线程不安全的
幸运的是我们能足够的简单创造一个连接池能使我们重用redis
线程池是线程安全的和足够信赖,只要你返回资源到池中当你运行完它
让我们开始创建一个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;
}
当池实例化是线程安全的,你可以存储它在任何静态的地方。
但是你需要注意的是销毁一个连接池避免泄露,当一个程序关闭
现在我们可以确保我们的池,当应用程序在任何地方需要连接的时候
try (Jedis jedis = jedisPool.getResource()) {
// do operations with jedis resource
}
我们使用Java的try with resource 声明避免去手动的关闭jedis资源
但是当你不能使用这样声明的时候,你也可以关闭资源通过手动的方式在finally语句中
确保我们使用池像我们使用described 在我们的应用程序中当我们
不想去面对令人不愉快的多线程问题。你可以显示的配置
池配置文件,参数去适应你的最新的设置在你的系统中
10. Redis Cluster
This Redis implementation provides easy scalability and high availability, we encourage you to read
their official specification if you are not familiar with it. We will not cover Redis cluster setup since that is a bit out of the scope for
this article, but you should have no problems in doing so when you are done with its documentation.
Once we have that 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, it will auto-discover
the rest of the instances in the cluster.
This is certainly a very powerful feature but it is not a silver bullet. When using Redis Cluster you cannot
perform transactions nor use pipelines, two important features on which many applications rely for ensuring data integrity.
Transactions are disabled because, in a clustered environment, keys will be persisted across multiple instances.
Operation atomicity and thread safety cannot be guaranteed for operations that involve command execution in different instances.
Some advanced key creation strategies will ensure that data that is interesting for you to be persisted in the same
instance will get persisted that way. In theory, that should enable you to perform transactions successfully using
one of the underlying Jedis instances of the Redis Cluster.
Unfortunately, currently you cannot find out in which Redis instance a particular key is saved using Jedis
(which is actually supported natively by Redis), so you do not know which of the instances you must perform
the transaction operation. If you are interested about this, you can find more information here.
10 Redis Cluster
这是一个实现提供一个简单可伸缩和高可用性。如果你对它不熟悉,我们鼓励去
去阅读这篇官方文章。我们不会说关于Redis cluster的配置因为这仅仅是那篇文章的很小一部分。
当你阅读完那篇文章,你应该不会出现什么问题在做这些事情的时候
一旦我们将下面的准备好,我们将开始使用它在我们的应用程序中
try (JedisCluster jedisCluster = new JedisCluster(new HostAndPort("localhost", 6379))) {
// use the jedisCluster resource as if it was a normal Jedis resource
} catch (IOException e) {
}
我们需要提供一个ip和一个端口信息给我们一个主节点实例之一。
它会自己自动的发现剩余的实例在我们的集群中
这是一个很重要的功能,但是它不是一个银弹。当使用Redis Cluster
你不能使用事务,管道这两个在Redis中很重要的功能。这两个公共在其他许多的应用程序中
确保数据的一致性
事务是残废的,因为在集群中一个键值将会持久化在多个实例中
操作的原子性和线程的安全性不能保证操作在多个命令的执行在不同的实例机器上
在一些先进的key的创建策略将会确保数据是有效的
当你想持久化到同一个机器上或从同一个机器上获取持久化的内容。
理论上说能够使帮你用事务的成功通过使用底层jedis的实例关于Redis cluster的