目的:利用Spring-data-redis,创建lettuce原生的客户端,并兼容单节点哨兵以及集群三种模式
欢迎指正
package com.demo.core.config;
import com.alibaba.fastjson.JSONObject;
import io.lettuce.core.AbstractRedisClient;
import io.lettuce.core.ClientOptions;
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;
import io.lettuce.core.cluster.RedisClusterClient;
import io.lettuce.core.protocol.DecodeBufferPolicies;
import io.lettuce.core.resource.ClientResources;
import io.lettuce.core.support.ConnectionPoolSupport;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
/**
* @author: luochenzc
* @create: 2023/3/30
* @Description:
* @FileName: RedisConfig
*/
@Configuration
@Slf4j
public class RedisConfig {
@Resource
RedisProperties redisProperties;
@Bean
@ConfigurationProperties(prefix = "spring.redis.lettuce.pool")
public GenericObjectPoolConfig genericObjectPoolConfig() {
return new GenericObjectPoolConfig();
}
@Bean
ClusterTopologyRefreshOptions clusterTopologyRefreshOptions() {
ClusterTopologyRefreshOptions clusterTopologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
.adaptiveRefreshTriggersTimeout(Duration.ofSeconds(5L))//设置自适应拓扑刷新超时,每次超时刷新一次,默认30s;
.closeStaleConnections(false)//刷新拓扑时是否关闭失效连接,默认true,isPeriodicRefreshEnabled()为true时生效;
.dynamicRefreshSources(true)//从拓扑中发现新节点,并将新节点也作为拓扑的源节点,动态刷新可以发现全部节点并计算每个客户端的数量,设置false则只有初始节点为源和计算客户端数量;
.enableAllAdaptiveRefreshTriggers()//启用全部触发器自适应刷新拓扑,默认关闭;
.enablePeriodicRefresh(Duration.ofSeconds(5L))//开启定时拓扑刷新并设置周期;
.refreshTriggersReconnectAttempts(3)//长连接重新连接尝试n次才拓扑刷新
.build();
return clusterTopologyRefreshOptions;
}
//配置集群选项,自动重连,最多重定型1次
@Bean
ClientOptions clusterClientOptions(ClusterTopologyRefreshOptions clusterTopologyRefreshOptions) {
ClientOptions clientOptions;
if (redisProperties.getCluster() != null) {
clientOptions = ClusterClientOptions.builder()
.autoReconnect(true)//在连接丢失时开启或关闭自动重连,默认true;
.decodeBufferPolicy(DecodeBufferPolicies.always())//设置丢弃解码缓冲区的策略,以回收内存;always:解码后丢弃,最大内存效率;alwaysSome:解码后丢弃一部分;ratio(n)基于比率丢弃,n/(1+n),通常用1-10对应50%-90%;
.disconnectedBehavior(ClientOptions.DisconnectedBehavior.DEFAULT)//设置连接断开时命令的调用行为,默认启用重连;DEFAULT:启用时重连中接收命令,禁用时重连中拒绝命令;ACCEPT_COMMANDS:重连中接收命令;REJECT_COMMANDS:重连中拒绝命令;
.maxRedirects(redisProperties.getCluster().getMaxRedirects())//当键从一个节点迁移到另一个节点,集群重定向次数,默认5;
// .cancelCommandsOnReconnectFailure(true)//允许在重连失败取消排队命令,默认false;
// .nodeFilter(nodeFilter)//设置节点过滤器
// .pingBeforeActivateConnection(true)//激活连接前设置PING,默认true;
// .protocolVersion(ProtocolVersion.RESP3)//设置协议版本,默认RESP3;
// .publishOnScheduler(false)//使用专用的调度器发出响应信号,默认false,启用时数据信号将使用服务的多线程发出;
// .requestQueueSize(requestQueueSize)//设置每个连接请求队列大小;
// .scriptCharset(scriptCharset)//设置Lua脚本编码为byte[]的字符集,默认StandardCharsets.UTF_8;
// .socketOptions(SocketOptions.builder().connectTimeout(Duration.ofSeconds(10)).keepAlive(true).tcpNoDelay(true).build())//设置低级套接字的属性
// .sslOptions(SslOptions.builder().build())//设置ssl属性
// .suspendReconnectOnProtocolFailure(false)//当重新连接遇到协议失败时暂停重新连接(SSL验证,连接失败前PING),默认值为false;
// .timeoutOptions(TimeoutOptions.enabled(Duration.ofSeconds(10)))//设置超时来取消和终止命令;
// .validateClusterNodeMembership(true)//在允许连接到集群节点之前,验证集群节点成员关系,默认值为true;
.topologyRefreshOptions(clusterTopologyRefreshOptions)//设置拓扑更新设置
.build();
} else {
clientOptions = ClientOptions.builder().autoReconnect(true).build();
}
return clientOptions;
}
//创建集群客户端
@Bean
AbstractRedisClient abstractRedisClient(ClientResources clientResources, ClientOptions clientOptions) {
if (redisProperties.getCluster() != null) {
List<RedisURI> servers = new ArrayList<>();
List<String> nodes = redisProperties.getCluster().getNodes();
if (CollectionUtils.isEmpty(nodes)) {
log.info("redis config error! redisProperties:{}", JSONObject.toJSONString(redisProperties));
System.exit(1);
}
for (String node : nodes) {
String[] nodeAddress = node.split(":");
if (nodeAddress == null || nodeAddress.length != 2) {
log.info("redis config error! redisProperties:{}", JSONObject.toJSONString(redisProperties));
System.exit(1);
}
servers.add(RedisURI.builder().withHost(nodeAddress[0]).withPort(Integer.valueOf(nodeAddress[1]))
.withAuthentication(redisProperties.getUsername(), redisProperties.getPassword())
.withDatabase(redisProperties.getDatabase()).build());
}
RedisClusterClient redisClusterClient = RedisClusterClient.create(clientResources, servers);
ClusterClientOptions clusterClientOptions = (ClusterClientOptions) clientOptions;
redisClusterClient.setOptions(clusterClientOptions);
return redisClusterClient;
} else if (redisProperties.getSentinel() != null) {
List<String> nodes = redisProperties.getSentinel().getNodes();
if (CollectionUtils.isEmpty(nodes)) {
log.info("redis config error! redisProperties:{}", JSONObject.toJSONString(redisProperties));
System.exit(1);
}
RedisURI.Builder builder = RedisURI.builder();
for (String node : nodes) {
String[] nodeAddress = node.split(":");
if (nodeAddress == null || nodeAddress.length != 2) {
log.info("redis config error! redisProperties:{}", JSONObject.toJSONString(redisProperties));
System.exit(1);
}
builder.withSentinel(nodeAddress[0], Integer.valueOf(nodeAddress[1]), redisProperties.getPassword());
}
String username = redisProperties.getUsername();
if (StringUtils.isBlank(username)) {
username = "default";
}
RedisURI redisURI = builder.withAuthentication(username,
redisProperties.getPassword()).withDatabase(redisProperties.getDatabase())
.withSentinelMasterId(redisProperties.getSentinel().getMaster()).build();
RedisClient redisClient = RedisClient.create(redisURI);
redisClient.setOptions(clientOptions);
return redisClient;
} else {
String username = redisProperties.getUsername();
if (StringUtils.isBlank(username)) {
username = "default";
}
RedisURI.Builder builder = RedisURI.builder();
RedisURI redisURI = builder
.withHost(redisProperties.getHost())
.withPort(redisProperties.getPort())
.withAuthentication(username, redisProperties.getPassword())
.withDatabase(redisProperties.getDatabase()).build();
RedisClient redisClient = RedisClient.create(redisURI);
redisClient.setOptions(clientOptions);
return redisClient;
}
}
/**
* 集群连接
*/
@Bean(destroyMethod = "close")
StatefulRedisConnection<String, String> statefulRedisConnection(GenericObjectPoolConfig genericObjectPoolConfig,
AbstractRedisClient abstractRedisClient) throws Exception {
GenericObjectPool<StatefulRedisConnection<String, String>> pool;
if (redisProperties.getCluster() != null) {
RedisClusterClient redisClusterClient = (RedisClusterClient) abstractRedisClient;
pool = ConnectionPoolSupport.createGenericObjectPool(redisClusterClient::connectPubSub, genericObjectPoolConfig);
} else {
RedisClient redisClient = (RedisClient) abstractRedisClient;
pool = ConnectionPoolSupport.createGenericObjectPool(redisClient::connect, genericObjectPoolConfig);
}
return pool.borrowObject();
}
}