Jedis 指南

将 Java 应用程序连接到 Redis 数据库

Jedis 是 Redis 的同步 Java 客户端。如果需要异步 Java 客户端,请使用 Lettuce。以下部分说明了如何安装 Jedis 并将应用程序连接到 Redis 数据库。

Jedis 需要运行 Redis 或 Redis Stack 服务器。请参阅 入门 获取 Redis 安装说明。

安装

若要将 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/latest/operate/oss_and_stack/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();
    }
}

生产中使用

以下部分说明了如何处理可能在你的生产环境中发生的各种情况。

配置连接池

正如前一节所述,使用 JedisPoolJedisPooled 创建连接池。JedisPooled(在 Jedis 版本 4.0.0 中添加)提供的功能类似于 JedisPool,但其 API 更直接。连接池保留指定数量的连接,并在需要时创建更多连接,并在不再需要时终止这些连接。

以下是池中一个简化的连接生命周期

  1. 从池中请求连接。
  2. 连接获得满足
    • 在非活动连接可用时满足空闲连接,或
    • 当连接数少于 maxTotal 时创建新连接。
  3. 连接变得活跃。
  4. 连接释放回池中。
  5. 连接被标记为陈旧。
  6. 连接空闲 minEvictableIdleTime
  7. 如果连接数大于 minIdle,则该连接可被驱逐。
  8. 连接可以关闭。

正确配置连接池非常重要。使用 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 参数的 JedisPooledJedisPool 构造函数,或使用带有 socketTimeoutconnectionTimeout 参数的 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 - 此异常是针对任何其他意外错误抛出的通用异常。

JedisException 可以抛出的情况

  • 使用 PING 命令进行健康检查时返回不良
  • SHUTDOWN 期间失败
  • 发出命令(断开连接)时出现 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");

了解更多

RATE THIS PAGE
Back to top ↑