All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.shepherdviolet.glacimon.spring.helper.jetcache.lettuce.SpringLettuceClusterClientImpl Maven / Gradle / Ivy

There is a newer version: 2024.6.0
Show newest version
/*
 * Copyright (C) 2022-2022 S.Violet
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Project GitHub: https://github.com/shepherdviolet/glacimon
 * Email: [email protected]
 */

package com.github.shepherdviolet.glacimon.spring.helper.jetcache.lettuce;

import io.lettuce.core.*;
import io.lettuce.core.api.StatefulConnection;
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.cluster.api.StatefulRedisClusterConnection;
import io.lettuce.core.cluster.api.async.RedisClusterAsyncCommands;
import io.lettuce.core.cluster.api.sync.RedisClusterCommands;
import io.lettuce.core.codec.RedisCodec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import com.github.shepherdviolet.glacimon.java.misc.CloseableUtils;
import com.github.shepherdviolet.glacimon.java.misc.CheckUtils;

import java.net.URI;
import java.time.Duration;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 一个在Spring中使用的简单的Lettuce Redis客户端, 连接Redis Cluster集群(或单机).
 *
 * Lettuce用法请参考官方文档, Connection和Commands都是线程安全的, Connection无需手动关闭(保持连接).
 *
 * 
 *     @Bean
 *     public SpringLettuceClusterClient lettuceRedisClusterClient() {
 *         // "redis://192.168.1.1:6379"
 *         // "redis://[email protected]:6379"
 *         // "redis://192.168.1.1:6379,redis://192.168.1.2:6379,redis://192.168.1.3:6379"
 *         return new SpringLettuceClusterClientImpl<>("redis://127.0.0.1:6379", StringCodec.UTF8);
 *     }
 * 
* * @author shepherdviolet */ public class SpringLettuceClusterClientImpl implements SpringLettuceClusterClient, InitializingBean, DisposableBean, BeanNameAware { private final Logger logger = LoggerFactory.getLogger(getClass()); private String beanName = "unknown"; private AbstractRedisClient redisClient; private StatefulConnection statefulConnection; private RedisClusterCommands syncCommand; private RedisClusterAsyncCommands asyncCommand; private String uris; private long defaultTimeoutMillis = -1L; private RedisCodec codec; private RedisConnectionStateListener redisConnectionStateListener; /** * uris and codec is required ! */ public SpringLettuceClusterClientImpl() { } /** * @param uris "redis://192.168.1.1:6379" * "redis://[email protected]:6379" * "redis://192.168.1.1:6379,redis://192.168.1.2:6379,redis://192.168.1.3:6379" * @param codec StringCodec.UTF8 / ByteArrayCodec.INSTANCE */ public SpringLettuceClusterClientImpl(String uris, RedisCodec codec) { setUris(uris); setCodec(codec); } /** * 获取单例的Redis连接 (线程安全), 请勿手动关闭连接 */ public StatefulConnection getConnection(){ return statefulConnection; } /** * 获取单例的Redis同步Command (线程安全) */ public RedisClusterCommands syncCommands() { return syncCommand; } /** * 获取单例的Redis异步Command (线程安全) */ public RedisClusterAsyncCommands asyncCommands() { return asyncCommand; } @Override public void afterPropertiesSet() throws Exception { if (codec == null) { throw new IllegalArgumentException("SpringLettuceRedisClient " + beanName + " | Property 'codec' is required, but it's null"); } redisClient = createClient(parseUri(uris)); if (redisClient == null) { throw new IllegalStateException("SpringLettuceRedisClient " + beanName + " | Create redis client failed, return null"); } if (defaultTimeoutMillis > 0) { redisClient.setDefaultTimeout(Duration.ofMillis(defaultTimeoutMillis)); } if (redisConnectionStateListener != null) { redisClient.addListener(redisConnectionStateListener); } /* * 为了统一 单机Redis 和 Cluster Redis * Command统一使用Cluster系列的 (Cluster系列的API比单机的少) */ if (redisClient instanceof RedisClient) { statefulConnection = ((RedisClient) redisClient).connect(codec); syncCommand = ((StatefulRedisConnection) statefulConnection).sync(); asyncCommand = ((StatefulRedisConnection) statefulConnection).async(); } else if (redisClient instanceof RedisClusterClient) { statefulConnection = ((RedisClusterClient) redisClient).connect(codec); syncCommand = ((StatefulRedisClusterConnection) statefulConnection).sync(); asyncCommand = ((StatefulRedisClusterConnection) statefulConnection).async(); } else { throw new IllegalStateException("SpringLettuceRedisClient " + beanName + " | Illegal redisClient type " + redisClient.getClass().getName()); } } protected AbstractRedisClient createClient(List uriList) { if (uriList.size() == 1) { RedisClient client = RedisClient.create(uriList.get(0)); client.setOptions(ClientOptions.builder() .disconnectedBehavior(ClientOptions.DisconnectedBehavior.REJECT_COMMANDS) .build()); return client; } else { /* * 避免Redis集群拓扑变化时报出错误: Connection to ?:? not allowed. This connection point is not known in the cluster view * 开启集群拓扑刷新(topologyRefreshOptions): 当服务端拓扑发生变化时, 短时间内还会出现连接错误, 刷新后才恢复 * 关闭集群节点验证(validateClusterNodeMembership): 当服务端拓扑发生变化时, 由于不验证, 能够更快恢复(但是, 官方默认开启验证, 应该是有某种原因的, 所以这里默认不用这个方案) */ RedisClusterClient client = RedisClusterClient.create(uriList); client.setOptions(ClusterClientOptions.builder() .disconnectedBehavior(ClientOptions.DisconnectedBehavior.REJECT_COMMANDS) .topologyRefreshOptions(ClusterTopologyRefreshOptions.builder() .enablePeriodicRefresh()//开启定期刷新, 默认关闭 .refreshPeriod(Duration.ofMinutes(1))//默认1分钟 .enableAdaptiveRefreshTrigger()//开启拓扑刷新, 默认关闭 .enableAllAdaptiveRefreshTriggers()//启用全部拓扑刷新定时器 .build()) // .validateClusterNodeMembership(false)//直接关闭集群节点检查, 默认true(避免Redis集群拓扑变化时报出错误: Connection to ?:? not allowed. This connection point is not known in the cluster view) .build()); return client; } } private List parseUri(String uris){ logger.info("SpringLettuceRedisClient " + beanName + " | Connection URIs: " + uris); if (CheckUtils.isEmpty(uris)) { throw new IllegalArgumentException("SpringLettuceRedisClient " + beanName + " | Connection URIs is null or empty"); } List uriList = Stream.of(uris.split(",")) .map(String::trim) .filter(CheckUtils::notEmpty) .collect(Collectors.toList()); logger.info("SpringLettuceRedisClient " + beanName + " | Connection URI List: " + uriList); return uriList.stream() .map(u -> RedisURI.create(URI.create(u))) .collect(Collectors.toList()); } @Override public void destroy() throws Exception { CloseableUtils.closeQuiet(statefulConnection); if (redisClient != null) { try { redisClient.shutdown(); } catch (Exception ignore) { } } } @Override public void setBeanName(String beanName) { this.beanName = beanName; } /** * @param uris "redis://192.168.1.1:6379" * "redis://[email protected]:6379" * "redis://192.168.1.1:6379,redis://192.168.1.2:6379,redis://192.168.1.3:6379" */ public void setUris(String uris) { this.uris = uris; } /** * @param codec StringCodec.UTF8 / ByteArrayCodec.INSTANCE */ public void setCodec(RedisCodec codec) { this.codec = codec; } public void setDefaultTimeoutMillis(long defaultTimeoutMillis) { this.defaultTimeoutMillis = defaultTimeoutMillis; } public void setRedisConnectionStateListener(RedisConnectionStateListener redisConnectionStateListener) { this.redisConnectionStateListener = redisConnectionStateListener; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy