Jedis 指南
将 Java 应用程序连接到 Redis 数据库
安装 Redis 和 Redis 客户端,然后将 Java 应用程序连接到 Redis 数据库。
Jedis
Jedis 是一款为 Redis 设计的 Java 客户端,以性能和易用性为目标。
安装
要将 Jedis
作为依赖项包含在应用程序中,请按如下方式编辑依赖项文件。
-
如果您使用 Maven
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>5.1.2</version> </dependency>
-
如果您使用 Gradle
repositories { mavenCentral() } //... dependencies { implementation 'redis.clients:jedis:5.1.2' //... }
-
如果您使用 JAR 文件,请从 Maven Central 或任何其他 Maven 存储库下载最新的 Jedis 和 Apache Commons Pool2 JAR 文件。
-
从 源代码 构建
连接
对于许多应用程序,最好使用连接池。您可以像这样实例化和使用 Jedis
连接池
package org.example;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class Main {
public static void main(String[] args) {
JedisPool pool = new JedisPool("localhost", 6379);
try (Jedis jedis = pool.getResource()) {
// Store & Retrieve a simple string
jedis.set("foo", "bar");
System.out.println(jedis.get("foo")); // prints bar
// Store & Retrieve a HashMap
Map<String, String> hash = new HashMap<>();;
hash.put("name", "John");
hash.put("surname", "Smith");
hash.put("company", "Redis");
hash.put("age", "29");
jedis.hset("user-session:123", hash);
System.out.println(jedis.hgetAll("user-session:123"));
// Prints: {name=John, surname=Smith, company=Redis, age=29}
}
}
}
由于为每个命令添加 try-with-resources
块可能会很麻烦,因此可以考虑使用 JedisPooled
作为一种更简单的连接池方式。
import redis.clients.jedis.JedisPooled;
//...
JedisPooled jedis = new JedisPooled("localhost", 6379);
jedis.set("foo", "bar");
System.out.println(jedis.get("foo")); // prints "bar"
连接到 Redis 集群
要连接到 Redis 集群,请使用 JedisCluster
。
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.HostAndPort;
//...
Set<HostAndPort> jedisClusterNodes = new HashSet<HostAndPort>();
jedisClusterNodes.add(new HostAndPort("127.0.0.1", 7379));
jedisClusterNodes.add(new HostAndPort("127.0.0.1", 7380));
JedisCluster jedis = new JedisCluster(jedisClusterNodes);
使用 TLS 连接到生产 Redis
部署应用程序时,请使用 TLS 并遵循 Redis 安全 指南。
在将应用程序连接到启用了 TLS 的 Redis 服务器之前,请确保证书和私钥采用正确的格式。
要将用户证书和私钥从 PEM 格式转换为 pkcs12
,请使用此命令
openssl pkcs12 -export -in ./redis_user.crt -inkey ./redis_user_private.key -out redis-user-keystore.p12 -name "redis"
输入密码以保护您的 pkcs12
文件。
使用 JDK 附带的 keytool 将服务器(CA)证书转换为 JKS 格式。
keytool -importcert -keystore truststore.jks \
-storepass REPLACE_WITH_YOUR_PASSWORD \
-file redis_ca.pem
使用此代码段与 Redis 数据库建立安全连接。
package org.example;
import redis.clients.jedis.*;
import javax.net.ssl.*;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
public class Main {
public static void main(String[] args) throws GeneralSecurityException, IOException {
HostAndPort address = new HostAndPort("my-redis-instance.cloud.redislabs.com", 6379);
SSLSocketFactory sslFactory = createSslSocketFactory(
"./truststore.jks",
"secret!", // use the password you specified for keytool command
"./redis-user-keystore.p12",
"secret!" // use the password you specified for openssl command
);
JedisClientConfig config = DefaultJedisClientConfig.builder()
.ssl(true).sslSocketFactory(sslFactory)
.user("default") // use your Redis user. More info https://redis.ac.cn/docs/management/security/acl/
.password("secret!") // use your Redis password
.build();
JedisPooled jedis = new JedisPooled(address, config);
jedis.set("foo", "bar");
System.out.println(jedis.get("foo")); // prints bar
}
private static SSLSocketFactory createSslSocketFactory(
String caCertPath, String caCertPassword, String userCertPath, String userCertPassword)
throws IOException, GeneralSecurityException {
KeyStore keyStore = KeyStore.getInstance("pkcs12");
keyStore.load(new FileInputStream(userCertPath), userCertPassword.toCharArray());
KeyStore trustStore = KeyStore.getInstance("jks");
trustStore.load(new FileInputStream(caCertPath), caCertPassword.toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509");
trustManagerFactory.init(trustStore);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("PKIX");
keyManagerFactory.init(keyStore, userCertPassword.toCharArray());
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
return sslContext.getSocketFactory();
}
}
生产使用
配置连接池
如前一节所述,使用 JedisPool
或 JedisPooled
创建连接池。在 Jedis 4.0.0 版本中添加的 JedisPooled
提供了与 JedisPool
类似的功能,但 API 更加直接。连接池保存指定数量的连接,在需要时创建更多连接,并在不再需要时终止它们。
以下是池中简化的连接生命周期
- 从池中请求连接。
- 提供连接
- 当有非活动连接可用时,提供空闲连接,或
- 当连接数低于
maxTotal
时,创建新连接。
- 连接变为活动状态。
- 将连接释放回池中。
- 连接标记为陈旧。
- 连接保持空闲
minEvictableIdleTime
。 - 如果连接数大于
minIdle
,连接变为可驱逐。 - 连接准备关闭。
正确配置连接池非常重要。使用 Apache Commons Pool2 中的 GenericObjectPoolConfig
。
ConnectionPoolConfig poolConfig = new ConnectionPoolConfig();
// maximum active connections in the pool,
// tune this according to your needs and application type
// default is 8
poolConfig.setMaxTotal(8);
// maximum idle connections in the pool, default is 8
poolConfig.setMaxIdle(8);
// minimum idle connections in the pool, default 0
poolConfig.setMinIdle(0);
// Enables waiting for a connection to become available.
poolConfig.setBlockWhenExhausted(true);
// The maximum number of seconds to wait for a connection to become available
poolConfig.setMaxWait(Duration.ofSeconds(1));
// Enables sending a PING command periodically while the connection is idle.
poolConfig.setTestWhileIdle(true);
// controls the period between checks for idle connections in the pool
poolConfig.setTimeBetweenEvictionRuns(Duration.ofSeconds(1));
// JedisPooled does all hard work on fetching and releasing connection to the pool
// to prevent connection starvation
JedisPooled jedis = new JedisPooled(poolConfig, "localhost", 6379);
超时
要为连接设置超时,请使用带 timeout
参数的 JedisPooled
或 JedisPool
构造函数,或使用带 socketTimeout
和 connectionTimeout
参数的 JedisClientConfig
HostAndPort hostAndPort = new HostAndPort("localhost", 6379);
JedisPooled jedisWithTimeout = new JedisPooled(hostAndPort,
DefaultJedisClientConfig.builder()
.socketTimeoutMillis(5000) // set timeout to 5 seconds
.connectionTimeoutMillis(5000) // set connection timeout to 5 seconds
.build(),
poolConfig
);
异常处理
Jedis 异常层次结构根植于 JedisException
,后者实现了 RuntimeException
,因此都是未检查异常。
JedisException
├── JedisDataException
│ ├── JedisRedirectionException
│ │ ├── JedisMovedDataException
│ │ └── JedisAskDataException
│ ├── AbortedTransactionException
│ ├── JedisAccessControlException
│ └── JedisNoScriptException
├── JedisClusterException
│ ├── JedisClusterOperationException
│ ├── JedisConnectionException
│ └── JedisValidationException
└── InvalidURIException
一般异常
通常,Jedis 在执行命令时可能会抛出以下异常
JedisConnectionException
- 当与 Redis 的连接意外丢失或关闭时。使用 Resilience4J 和内置的 Jedis 故障转移机制配置故障转移以自动处理此异常。JedisAccessControlException
- 当用户没有执行命令的权限或用户 ID 和/或密码不正确时。JedisDataException
- 当发送到 Redis 服务器或从 Redis 服务器接收的数据有问题时。通常,错误消息将包含有关失败命令的更多信息。JedisException
- 此异常是一个 catch-all 异常,可以针对任何其他意外错误抛出。
可以抛出 JedisException
的条件
- 使用
PING
命令进行健康检查时返回不良 - 关闭期间发生故障
- 发出命令(断开连接)时 Pub/Sub 发生故障
- 任何未知的服务器消息
- Sentinel:可以连接到 Sentinel,但未监控主服务器或所有 Sentinel 都已关闭。
- MULTI 或 DISCARD 命令失败
- 分片命令键哈希检查失败或没有可访问的分片
- 重试截止时间已过/尝试次数(重试命令执行器)
- POOL - 池已耗尽,添加空闲对象时出错,将损坏的资源返回池
所有 Jedis 异常都是运行时异常,并且在大多数情况下无法恢复,因此通常会冒泡到捕获错误消息的 API。
DNS 缓存和 Redis
当您连接到具有多个端点的 Redis 时,例如 Redis Enterprise Active-Active,建议禁用 JVM 的 DNS 缓存以在多个端点之间进行负载均衡请求。
您可以在应用程序的代码中使用以下代码段执行此操作
java.security.Security.setProperty("networkaddress.cache.ttl","0");
java.security.Security.setProperty("networkaddress.cache.negative.ttl", "0");