Redis での接続プール jedis と lettuce を統合する springboot の比較と実装
Redis 接続プールを使用する理由
Redis 接続プールは、Redis サーバーへの接続を管理および維持するために使用されるコンポーネントです。Redis と通信するアプリケーションで重要な役割を果たし、次の重要な機能があります。
- パフォーマンスの向上: Redis 接続プールは再利用可能な接続のセットを維持できるため、Redis サーバーとの通信が必要になるたびに接続を作成および破棄するオーバーヘッドが軽減されます。接続の作成と破棄は通常、比較的高価な操作であるため、これはアプリケーションのパフォーマンスの向上に役立ちます。
- リソース消費の削減:接続プールは接続数を制御して、過剰な接続が作成されないようにすることで、メモリと CPU リソースの消費を削減します。これは、Redis サーバーとアプリケーション サーバーを不要な負荷から保護するために重要です。
- 接続の再利用:接続プーリングでは、リクエストごとに新しい接続を作成する代わりに、既存の接続を再利用できます。これにより、接続の確立と切断の回数が減り、効率が向上します。
- 接続管理:接続プールは、接続の可用性の確認、接続の健全性ステータスの維持、失敗した接続の自動再接続など、接続のステータスを管理できます。これは、アプリケーションと Redis 間の安定した接続を確保するのに役立ちます。
- 接続タイムアウト制御:接続プールは接続のタイムアウト期間を構成して、一定期間内にアクティブな接続が閉じられないようにすることで、リソースを解放し、接続リークを防ぐことができます。
- 同時実行制御:接続プールは、同時に使用される接続の数を制限して、Redis サーバーに対する同時リクエストが多すぎる影響を回避できます。これは、同時負荷をスムーズに処理するのに役立ちます。
- パフォーマンスの最適化:接続プールは接続の再利用を最適化し、接続のアイドル時間を短縮して、接続が最適な状態を維持し、リクエストに迅速に応答できるようにします。
つまり、Redis 接続プールの主な機能は、パフォーマンスの向上、リソース消費の削減、接続ステータスの管理、接続の再利用により、Redis サーバーとの効率的かつ安定した通信を確保することです。同時実行性の高いアプリケーションでは、システムの安定性とパフォーマンスを維持するために、適切な接続プール構成が重要です。
ジェディスとレタスの違い
適切な Redis 接続プールの選択を検討する場合、パフォーマンス、構成、保守性、適用可能なシナリオなどのさまざまな側面をより詳細に比較することができます。ここでは、2 つの一般的な Java Redis クライアント接続プールである Jedis と Lettuce の詳細な比較を示します。
-
パフォーマンス:
-
ジェダイ:
- Jedis はブロッキング I/O を使用するため、同時実行性が低い状況でも良好にパフォーマンスを発揮します。
- 同時実行性が高いシナリオでは、各接続がブロックされ、IO 操作が完了するまで待機する必要があるため、Jedis のパフォーマンスが制限される可能性があります。
-
レタス:
- Lettuce はノンブロッキング I/O を使用するため、同時実行性の高い環境でパフォーマンスが向上し、システム リソースを最大限に活用できます。
- 非同期操作とリアクティブプログラミングをサポートしており、非同期プログラミングに優れています。
-
-
接続プールの構成:
-
ジェダイ:
- Jedis の接続プール構成は比較的単純で、最大接続数、アイドル接続の最大数、接続タイムアウトなどのパラメーターを手動で設定する必要があります。
- 接続プールの管理は手動で実装する必要があります。
-
レタス:
- Lettuce は、接続プールの動作、トポロジの更新などを含む、より豊富な接続プール構成オプションを提供します。
- 高パフォーマンスの接続プールが組み込まれており、接続プールを手動で管理する必要はありません。
-
-
保守性:
-
ジェダイ:
- Jedis は比較的シンプルで使いやすいですが、接続プールとエラー処理を手動で管理する必要があります。
- コミュニティのサポートが少なく、維持するのが比較的難しい場合があります。
-
レタス:
- Lettuce には、より多くの機能と保守性があり、より優れたドキュメントとコミュニティ サポートが備わっています。
- トポロジのリフレッシュやリアクティブ プログラミングなど、いくつかの高度な機能が組み込まれています。
-
-
該当するシーン:
-
ジェダイ:
- 単純なアプリケーションまたは同時実行性の低い環境に適しています。
- 従来の同期プログラミングのニーズには十分対応します。
-
レタス:
- 高同時実行性、高スループットのアプリケーションに適しています。
- 特に非同期およびリアクティブ プログラミングのニーズに役立ちます。
-
-
エコシステムの統合:
-
ジェダイ:
- Jedis は、一部の古い Java フレームワークにおけるサポートをより適切に統合しています。
- 古いバージョンのフレームワークを使用する必要がある一部のプロジェクトでは、この方法の方が適している場合があります。
-
レタス:
- Lettuce は、最新の Java フレームワーク、特に Spring フレームワークでの統合サポートが優れています。
- Spring Boot などの最新の Java テクノロジーを使用するプロジェクトでより一般的です。
-
要約すると、アプリケーションで高い同時実行性を処理する必要がある場合、または非同期プログラミングのサポートが必要な場合は、Lettuce の方が良い選択肢になる可能性があります。また、アプリケーションが比較的単純である場合、または従来の Java フレームワークで実行される場合は、Jedis も検討できます。どちらを選択する場合でも、最適なパフォーマンスと安定性を確保するには、特定のニーズに基づいて接続プールを構成およびテストする必要があります。
Springboot での両方の実装
レタス
Redis 接続プーリングを Spring Boot プロジェクトに統合するには、Spring Data Redis を使用して統合プロセスを簡素化できます。Redis 接続プールを統合するための一般的な手順は次のとおりです。
- 依存関係を追加する:まず、 Spring Boot プロジェクトの
pom.xml
ファイルに Spring Data Redis 依存関係を追加します。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- Redis 接続プロパティを構成する:
application.properties
またはでapplication.yml
Redis 接続プロパティを構成します。構成例を次に示します。
# redis配置,如果是单个节点就不需要看注释的
#spring.redis.cluster.nodes=localhost:6399,localhost:6396
spring.redis.host=localhost
spring.redis.port=6396
# 如果没有密码的话就可以不填
spring.redis.password=
#spring.redis.cluster.max-redirects=3
spring.redis.timeout=10000
# 连接到 Redis 哨兵
#spring.redis.sentinel.master=mymaster
#spring.redis.sentinel.nodes=192.168.1.75:26379,192.168.1.75:26378,192.168.1.75:26377
- Redis 構成クラスの作成: Java 構成クラスを作成して、Redis 接続プールと RedisTemplate を構成します。例えば:
package com.todoitbo.baseSpringbootDasmart.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @author xiaobo
*/
@Configuration
@Slf4j
public class RedisConfiguration {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
log.info("开始创建redis模板对象");
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
// 设置redis连接工厂对象
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 设置 redis key 的序列化器,可以解决乱码问题
redisTemplate.setKeySerializer(new StringRedisSerializer());
// 设置 redis 值的序列化器,可以解决乱码问题
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
return redisTemplate;
}
}
この構成クラスは、Redis 接続プールとして Lettuce を使用します。必要に応じて、接続プールのプロパティをカスタマイズできます。
- RedisTemplate を使用する: Service または Controller に挿入し
RedisTemplate
、それを使用して Redis 操作を実行します。例えば:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class MyService {
private final RedisTemplate<String, Object> redisTemplate;
@Autowired
public MyService(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void addToRedis(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}
public Object getFromRedis(String key) {
return redisTemplate.opsForValue().get(key);
}
}
このようにして、Redis 接続プールを Spring Boot プロジェクトに正常に統合し、それを使用してRedisTemplate
さまざまな Redis 操作を実行できます。実際のニーズに合わせて構成と操作を適切に調整してください。
ジェダイ
Jedis を使用する場合は、次のように設定できます。
- Jedis 依存関係を追加します。
pom.xml
Jedis の依存関係を以下に追加します。
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
- Jedis 接続プールを作成します。
package com.todoitbo.tallybookdasmart.config;
import cn.hutool.core.text.CharSequenceUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* @author xiaobo
* @date 2022/7/25
*/
@Slf4j
@Configuration
@EnableAutoConfiguration
public class JedisConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.jedis.pool.max-active}")
private int maxActive;
@Value("${spring.redis.jedis.pool.max-wait}")
private int maxWait;
@Value("${spring.redis.jedis.pool.max-idle}")
private int maxIdle;
@Value("${spring.redis.jedis.pool.min-idle}")
private int minIdle;
@Value("${spring.redis.database}")
private int db;
@Bean
public JedisPool redisPoolFactory() {
try {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(maxIdle);
jedisPoolConfig.setMaxWaitMillis(maxWait);
jedisPoolConfig.setMaxTotal(maxActive);
jedisPoolConfig.setMinIdle(minIdle);
String pwd = CharSequenceUtil.isBlank(password) ? null : password;
JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout, pwd, db);
log.info("初始化Redis连接池JedisPool成功!地址: " + host + ":" + port);
return jedisPool;
} catch (Exception e) {
log.error("初始化Redis连接池JedisPool异常:" + e.getMessage());
}
return null;
}
/**
* description: 创建jedisConnectionFactory工厂
*
* @author bo
* @date 2023/4/9 14:49
*/
@Bean
public JedisConnectionFactory jedisConnectionFactory() {
RedisStandaloneConfiguration redisStandaloneConfiguration =
new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setHostName(host);
redisStandaloneConfiguration.setDatabase(db);
redisStandaloneConfiguration.setPassword(RedisPassword.of(password));
redisStandaloneConfiguration.setPort(port);
return new JedisConnectionFactory(redisStandaloneConfiguration);
}
/**
* description: 创建RedisMessageListenerContainer
* * @author bo
* @version 1.0
* @date 2023/4/9 14:50
*/
@Bean
public RedisMessageListenerContainer container(JedisConnectionFactory jedisConnectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(jedisConnectionFactory);
return container;
}
}
この構成クラスは、Redis 接続プールとして Jedis を使用します。必要に応じて、接続プールのプロパティをカスタマイズできます。ではJedisConnectionFactory
、最大接続数やアイドル接続の最大数など、接続プールのパラメータを設定できます。
- ツールクラスの実装
package com.todoitbo.tallybookdasmart.utils;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.todoitbo.tallybookdasmart.exception.BusinessException;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPubSub;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author xiaobo
*/
@Slf4j
@Component
public class JedisUtil {
/**
* 静态注入JedisPool连接池
* JedisUtil直接调用静态方法即可
*/
private static JedisPool jedisPool;
private static final String RETURN_OK = "OK";
@Autowired
public void setJedisPool(JedisPool jedisPool) {
JedisUtil.jedisPool = jedisPool;
}
/**
* description: getJedis
* @return redis.clients.jedis.Jedis
* @author bo
* @date 2022/7/22 9:06 AM
*/
public static synchronized Jedis getJedis() {
try {
if (jedisPool != null) {
return jedisPool.getResource();
} else {
return null;
}
} catch (Exception e) {
throw new BusinessException("获取Jedis资源异常:" + e.getMessage());
}
}
/**
* description: 释放资源
*
* @author bo
* @date 2022/7/22 9:06 AM
*/
public static void closePool() {
try {
jedisPool.close();
} catch (Exception e) {
throw new BusinessException("释放Jedis资源异常:" + e.getMessage());
}
}
/**
* description: 获取对象
*
* @param key k
* @return java.lang.Object
* @author bo
* @date 2022/7/22 9:07 AM
*/
public static Object getObject(String key) {
try (Jedis jedis = getJedis()) {
assert jedis != null;
byte[] bytes = jedis.get(key.getBytes());
if (!Assert.isEmpty(bytes)) {
return JSON.parse(bytes);
}
} catch (Exception e) {
throw new BusinessException("获取Redis键值getObject方法异常:key=" + key + " cause=" + e.getMessage());
}
return null;
}
/**
* description: setObject
* @param key,Object k v
* @return java.lang.String
* @author bo
* @date 2022/7/22 9:09 AM
*/
public static String setObject(String key, Object value) {
try (Jedis jedis = getJedis()) {
assert jedis != null;
return jedis.set(key.getBytes(), JSONObject.toJSONString(value).getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
throw new BusinessException("设置Redis键值setObject方法异常:key=" + key + " value=" + value + " cause=" + e.getMessage());
}
}
/**
* description: 过期时间
*
* @param key,Object,long k,v,mm
* @return java.lang.String
* @author bo
* @date 2022/7/22 9:11 AM
*/
public static String setObject(String key, Object value, long expiretime) {
String result;
try (Jedis jedis = getJedis()) {
assert jedis != null;
result = jedis.set(key.getBytes(), JSON.toJSONString(value).getBytes(StandardCharsets.UTF_8));
if (RETURN_OK.equals(result)) {
jedis.pexpire(key.getBytes(), expiretime);
}
return result;
} catch (Exception e) {
throw new BusinessException("设置Redis键值setObject方法异常:key=" + key + " value=" + value + " cause=" + e.getMessage());
}
}
/**
* description: getJson
* @param key k
* @return java.lang.String
* @author bo
* @date 2022/7/22 9:12 AM
*/
public static String getJson(String key) {
try (Jedis jedis = getJedis()) {
assert jedis != null;
return jedis.get(key);
} catch (Exception e) {
throw new BusinessException("获取Redis键值getJson方法异常:key=" + key + " cause=" + e.getMessage());
}
}
/**
* description: setJson * * @param key,Object k,v
* @return java.lang.String
* @author bo
* @date 2022/7/22 9:12 AM
*/
public static String setJson(String key, String value) {
try (Jedis jedis = getJedis()) {
assert jedis != null;
return jedis.set(key, value);
} catch (Exception e) {
throw new BusinessException("设置Redis键值setJson方法异常:key=" + key + " value=" + value + " cause=" + e.getMessage());
}
}
/**
* description: 删除指定的key
* @param key k
* @throws Exception e
* @author bo
* @date 2022/7/22 1:32 PM
*/
public static void deleteKey(@NonNull String key) throws Exception {
try (Jedis jedis = getJedis()) {
assert jedis != null;
jedis.del(key);
} catch (Exception e) {
log.error("redis删除 【key={}】出现异常: {}", key, e.getMessage());
throw new BusinessException("redis删除" + key + "出现异常");
}
}
/**
* description: 批量删除指定的key
* @param key k
* @throws Exception e
* @author bo
* @date 2022/7/22 1:33 PM
*/
public static void deleteKeys(String... key) throws Exception {
try (Jedis jedis = getJedis()) {
assert jedis != null;
jedis.del(key);
} catch (Exception e) {
log.error("redis删除 【keys= [{}]】出现异常: {}", key, e.getMessage());
throw new BusinessException("redis删除" + Arrays.toString(key) + "出现异常:" + e.getMessage());
}
}
/**
* description: 推送消息
*
* @param channel,message 消息通道,消息体
* @author bo
* @date 2022/7/22 1:34 PM
*/ public static void publish(String channel, String message) {
try (Jedis jedis = new Jedis()) {
jedis.publish(channel, message);
} catch (Exception e) {
log.error("redis发布消息出现异常: {}", e.getMessage());
throw new BusinessException("redis发布消息出现异常:" + e.getMessage());
}
}
/**
* 监听消息通道
*
* @param jedisPubSub 对象
* @param channels 消息通道
*/
public static void subscribe(JedisPubSub jedisPubSub, String... channels) {
try (Jedis jedis = getJedis()) {
assert jedis != null;
jedis.subscribe(jedisPubSub, channels);
} catch (Exception e) {
log.error("redis监听消息出现异常: {}", e.getMessage());
throw new BusinessException("redis监听消息出现异常:" + e.getMessage());
}
}
}
ここが重要なポイントです
try-with-resources
try-with-resources
これは、Java 7 で導入された重要な言語機能です。これは、リソース (ファイル、ネットワーク接続、データベース接続など) を自動的に管理し、コード ブロックの実行後にリソースが正しく閉じられ、解放されるようにするために使用されます。この機能は、リソース リークのリスクを軽減し、コードの可読性と保守性を向上させるのに役立ちます。
try-with-resources
以下に関する重要なポイントは次のとおりです。
-
構文:
try-with-resources
ブロックを使用してtry
コードをラップします。リソースはtry
ブロックの先頭で初期化され、try
ブロックの最後で自動的に閉じられて解放されます。AutoCloseable
リソースの初期化はインターフェイスの実装クラスを通じて行われ、リソースが閉じられるときにメソッドを実行する必要がありますclose()
。InputStream
通常、これには、OutputStream
、Reader
、Writer
、Socket
、Jedis
などの Java 標準ライブラリのいくつかのクラスが含まれます。 -
リソースの自動終了:ブロックの最後で
try-with-resources
、リソースのメソッドを手動で呼び出す必要はなくclose()
、自動的に呼び出されます。これにより、コード ブロックが正常に実行されるか例外がスローされるかに関係なく、リソースが適切に閉じられることが保証されます。 -
複数のリソース管理:
try-with-resources
複数のリソースを同じブロックで管理できます。try
これらのリソースは、ブロック内の宣言とは逆の順序で初期化され、閉じられます。 -
例外処理:
try
ブロック内で例外がスローされた場合、try-with-resources
初期化されたすべてのリソースが最初に閉じられてから、例外がスローされます。これにより、リソースが適切に閉じられ、リソース リークが発生しないことが保証されます。 -
適用範囲:
try-with-resources
インターフェースを実装したクラスに適用AutoCloseable
。AutoCloseable
リソース クラスをカスタマイズする場合は、そのクラスにインターフェイスを実装させ、そこで使用するclose()
メソッドをオーバーライドすることができます。try-with-resources
try-with-resources
ファイルを使用してリソースを読み取る方法を示す簡単な例を次に示します。
try (FileReader fileReader = new FileReader("example.txt")) {
int data;
while ((data = fileReader.read()) != -1) {
// 处理文件内容
}
} catch (IOException e) {
// 处理异常
}
この例では、インターフェイスFileReader
を実装するリソース クラスです。ブロックが終了するAutoCloseable
と、メソッドを手動で呼び出す必要がなく、自動的に閉じられます。try-with-resources
fileReader
close()
つまり、try-with-resources
これは、リソース管理を簡素化し、リソース リークのリスクを軽減し、コードの可読性と保守性を向上させる重要な Java 言語機能です。これは、ファイル、データベース接続、ネットワーク接続、明示的に閉じる必要があるその他のリソースなど、手動で閉じる必要があるリソースを処理する場合に役立ちます。
提案
一言で言えば、レタスを使うのがおすすめです
Jedis を Redis クライアントとして使用すると、特定の状況で次のような問題が発生する可能性があります。
-
パフォーマンスの問題: Jedis は、同時実行性が高い状況では、Lettuce ほどパフォーマンスが低下する可能性があります。Jedis はブロッキング I/O を使用するため、各接続がブロッキングされ、特に多数の同時リクエストが同時に到着した場合にパフォーマンスのボトルネックが発生する可能性があります。
-
接続管理: Jedis では、接続プールと接続ステータスを手動で管理する必要があります。つまり、接続プールのパラメータを自分で設定し、接続の作成と破棄を処理し、接続の健全性を管理する必要があります。これにより、接続の漏れや接続が不適切に閉じられるなどの問題が発生する可能性があります。
-
スレッド セーフ: Jedis のインスタンスはスレッド セーフではありません。つまり、正しい同時アクセスを保証するには、マルチスレッド環境で追加の同期が必要です。これにより、開発の複雑さが増大します。
-
例外処理: Jedis での例外処理には、開発者による慎重な処理が必要です。たとえば、接続の有効期限が切れたり、ネットワークの問題が発生したりした場合、アプリケーションの安定性を確保するために例外を捕捉して処理する必要があります。
-
コミュニティ サポート:レタスと比較して、Jedis はコミュニティ サポートが比較的少ないです。これは、関連する問題の解決策を見つけたり、最新のメンテナンスやサポートを受けたりするのが簡単ではない可能性があることを意味します。
-
リアクティブ プログラミングには適さない:アプリケーションでリアクティブ プログラミング モデルを使用する必要がある場合、Jedis はリアクティブ操作をサポートしていないため、最適な選択ではない可能性があります。この点ではレタスの方が適しています。
-
バージョンの互換性: Jedis の一部の古いバージョンは、Redis サーバーの新しいバージョンと互換性がない場合があるため、Redis をアップグレードする場合は特に注意する必要があります。
Jedis は依然としてフル機能の Redis クライアントであり、場合によっては十分ですが、高い同時実行性、パフォーマンス、最新のアプリケーションのニーズに関してより多くの機能を提供し、パフォーマンスが向上し、最新のアプリケーションに適しているため、通常は Lettuce が推奨されます。 Java プログラミング モデル。したがって、Jedis を使用すると上記の問題が発生する可能性があるため、Jedis を選択するかどうかは慎重に検討する必要があります