找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2282

积分

0

好友

330

主题
发表于 2025-12-31 06:52:00 | 查看: 24| 回复: 0

flea-cache使用之Redis哨兵模式接入源代码:
https://github.com/Huazie/flea-framework/tree/dev/flea-cache

Flea框架Redis缓存核心类图
图1:Flea框架中Redis缓存的核心类图

2. 依赖

jedis-3.0.1.jar
https://mvnrepository.com/artifact/redis.clients/jedis/3.0.1

<!-- Java redis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.0.1</version>
</dependency>

spring-context-4.3.18.RELEASE.jar
https://mvnrepository.com/artifact/org.springframework/spring-context/4.3.18.RELEASE

<!-- Spring相关 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.3.18.RELEASE</version>
</dependency>

spring-context-support-4.3.18.RELEASE.jar
https://mvnrepository.com/artifact/org.springframework/spring-context-support/4.3.18.RELEASE

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>4.3.18.RELEASE</version>
</dependency>

3. 基础接入

3.1 定义Flea缓存接口

IFleaCache 的实现可参考Memcached接入的相关文档。

3.2 定义抽象Flea缓存类

AbstractFleaCache 的实现可参考Memcached接入的相关文档。

3.3 定义Redis客户端接口类

RedisClient 的实现可参考Redis集群模式接入的相关文档。

3.4 定义Redis客户端命令行

RedisClientCommand 的实现可参考Redis分片模式接入的相关文档。

3.5 定义哨兵模式Redis客户端实现类

FleaRedisSentinelClient 是哨兵模式下的Redis客户端实现,它封装了操作Redis缓存的基本操作。其内部功能通过Jedis哨兵连接池获取的Jedis实例对象完成。

哨兵模式下,单个缓存接入场景的使用方式:

RedisClient redisClient = new FleaRedisSentinelClient.Builder().build();
// 执行读,写,删除等基本操作
redisClient.set("key", "value");

哨兵模式下,整合缓存接入场景的使用方式:

RedisClient redisClient = new FleaRedisSentinelClient.Builder(poolName).build();
// 执行读,写,删除等基本操作
redisClient.set("key", "value");

当然,每次都新建Redis客户端并不可取,我们可以通过Redis客户端工厂来获取。对于单个缓存接入场景:

RedisClient redisClient = RedisClientFactory.getInstance(CacheModeEnum.SENTINEL);

对于整合缓存接入场景:

RedisClient redisClient = RedisClientFactory.getInstance(poolName, CacheModeEnum.SENTINEL);

以下是 FleaRedisSentinelClient 的核心代码片段:

public class FleaRedisSentinelClient extends FleaRedisClient {

    private JedisSentinelPool jedisSentinelPool;

    private int maxAttempts;

    /**
     * Redis哨兵客户端构造方法 (默认)
     *
     * @since 2.0.0
     */
    private FleaRedisSentinelClient() {
        this(CommonConstants.FleaPoolConstants.DEFAULT_POOL_NAME);
    }

    /**
     * Redis哨兵客户端构造方法(指定连接池名)
     *
     * @param poolName 连接池名
     * @since 2.0.0
     */
    private FleaRedisSentinelClient(String poolName) {
        super(poolName);
        init();
    }

    /**
     * 初始化Jedis哨兵实例
     *
     * @since 2.0.0
     */
    private void init() {
        if (CommonConstants.FleaPoolConstants.DEFAULT_POOL_NAME.equals(getPoolName())) {
            jedisSentinelPool = RedisSentinelPool.getInstance().getJedisSentinelPool();
            maxAttempts = RedisSentinelConfig.getConfig().getMaxAttempts();
        } else {
            jedisSentinelPool = RedisSentinelPool.getInstance(getPoolName()).getJedisSentinelPool();
            maxAttempts = CacheConfigUtils.getMaxAttempts();
        }
    }

    @Override
    public String set(final String key, final Object value) {
        return new RedisClientCommand<String, JedisSentinelPool, Jedis>(this.jedisSentinelPool, this.maxAttempts) {
            @Override
            public String execute(Jedis connection) {
                if (value instanceof String)
                    return connection.set(key, (String) value);
                else
                    return connection.set(SafeEncoder.encode(key), ObjectUtils.serialize(value));
            }
        }.run();
    }

    // 省略其他方法(get, delete等)。。。。。。

    /**
     * 内部建造者类
     */
    public static class Builder {

        private String poolName; // 连接池名

        /**
         * 默认构造器
         */
        public Builder() {
        }

        /**
         * 指定连接池的构造器
         *
         * @param poolName 连接池名
         */
        public Builder(String poolName) {
            this.poolName = poolName;
        }

        /**
         * 构建Redis哨兵客户端对象
         *
         * @return Redis哨兵客户端
         */
        public RedisClient build() {
            if (StringUtils.isBlank(poolName)) {
                return new FleaRedisSentinelClient();
            } else {
                return new FleaRedisSentinelClient(poolName);
            }
        }
    }
}

从该类的构造函数初始化逻辑可以看出,我们使用了 RedisSentinelPool,下面将进行介绍。

3.6 定义Redis哨兵连接池

我们使用 RedisSentinelPool 来初始化Jedis哨兵连接池实例,其重点是获取分布式Jedis连接池 ShardedJedisPool。该类的一个关键构造方法如下:

public JedisSentinelPool(String masterName, Set<String> sentinels, final GenericObjectPoolConfig poolConfig,
    final int connectionTimeout, final int soTimeout, final String password, final int database, final String clientName) {
}

Redis哨兵连接池用于初始化Jedis哨兵连接池实例。

针对单独缓存接入场景,采用默认连接池初始化的方式:

// 初始化默认连接池
RedisSentinelPool.getInstance().initialize();

针对整合缓存接入场景,采用指定连接池初始化的方式:

// 初始化指定连接池
RedisSentinelPool.getInstance(group).initialize(cacheServerList);

RedisSentinelPool 的核心结构如下:

public class RedisSentinelPool {

    private static final ConcurrentMap<String, RedisSentinelPool> redisPools = new ConcurrentHashMap<>();

    private static final Object redisSentinelPoolLock = new Object();

    private String poolName; // 连接池名

    private JedisSentinelPool jedisSentinelPool; // Jedis哨兵连接池

    private RedisSentinelPool(String poolName) {
        this.poolName = poolName;
    }

    /**
     * 获取Redis哨兵连接池实例 (默认连接池)
     *
     * @return Redis哨兵连接池实例对象
     * @since 2.0.0
     */
    public static RedisSentinelPool getInstance() {
        return getInstance(CommonConstants.FleaPoolConstants.DEFAULT_POOL_NAME);
    }

    /**
     * 获取Redis哨兵连接池实例 (指定连接池名)
     *
     * @param poolName 连接池名
     * @return Redis哨兵连接池实例对象
     * @since 2.0.0
     */
    public static RedisSentinelPool getInstance(String poolName) {
        if (!redisPools.containsKey(poolName)) {
            synchronized (redisSentinelPoolLock) {
                if (!redisPools.containsKey(poolName)) {
                    RedisSentinelPool redisSentinelPool = new RedisSentinelPool(poolName);
                    redisPools.put(poolName, redisSentinelPool);
                }
            }
        }
        return redisPools.get(poolName);
    }

    /**
     * 默认初始化
     *
     * @since 2.0.0
     */
    public void initialize(int database) {
        // 省略具体初始化逻辑。。。。。。
    }

    /**
     * 初始化 (非默认连接池)
     *
     * @param cacheServerList 缓存服务器集
     * @since 2.0.0
     */
    public void initialize(List<CacheServer> cacheServerList) {
        // 省略具体初始化逻辑。。。。。。
    }

    /**
     * Jedis哨兵连接池
     *
     * @return Jedis哨兵连接池
     * @since 2.0.0
     */
    public JedisSentinelPool getJedisSentinelPool() {
        if (ObjectUtils.isEmpty(jedisSentinelPool)) {
            ExceptionUtils.throwFleaException(FleaCacheConfigException.class, "获取Jedis哨兵连接池失败:请先调用initialize初始化");
        }
        return jedisSentinelPool;
    }
}

3.7 定义Redis哨兵配置文件

flea-cache 读取 redis.sentinel.properties(Redis哨兵配置文件),用于初始化 RedisSentinelPool

配置文件示例 redis.sentinel.properties

# Redis哨兵配置
redis.sentinel.switch=1

redis.systemName=FleaFrame

redis.sentinel.masterName=mymaster

redis.sentinel.server=127.0.0.1:36379,127.0.0.1:36380,127.0.0.1:36381

#redis.sentinel.password=huazie123

redis.sentinel.connectionTimeout=2000

redis.sentinel.soTimeout=2000

# Redis哨兵客户端连接池配置
redis.pool.maxTotal=100

redis.pool.maxIdle=10

redis.pool.minIdle=0

redis.pool.maxWaitMillis=2000

redis.maxAttempts=5

redis.nullCacheExpiry=10

配置项说明:

  • redis.sentinel.switch : Redis哨兵配置开关(1:开启 0:关闭),如果不配置也默认开启
  • redis.systemName : Redis缓存所属系统名
  • redis.sentinel.masterName : Redis主服务器节点名称
  • redis.sentinel.server : Redis哨兵节点的地址集合
  • redis.sentinel.password : Redis主从服务器节点登录密码(各节点配置同一个)
  • redis.sentinel.connectionTimeout : Redis哨兵客户端socket连接超时时间(单位:ms)
  • redis.sentinel.soTimeout : Redis哨兵客户端socket读写超时时间(单位:ms)
  • redis.pool.maxTotal : Jedis连接池最大连接数
  • redis.pool.maxIdle : Jedis连接池最大空闲连接数
  • redis.pool.minIdle : Jedis连接池最小空闲连接数
  • redis.pool.maxWaitMillis : Jedis连接池获取连接时的最大等待时间(单位:ms)
  • redis.maxAttempts : Redis客户端操作最大尝试次数【包含第一次操作】
  • redis.nullCacheExpiry : 空缓存数据有效期(单位:s)

3.8 定义Redis Flea缓存类

RedisFleaCache 的实现可参考Redis分片模式接入的相关文档。

3.9 定义抽象Flea缓存管理类

AbstractFleaCacheManager 的实现可参考Memcached接入的相关文档。

3.10 定义Redis哨兵模式Flea缓存管理类

RedisSentinelFleaCacheManager 继承抽象Flea缓存管理类 AbstractFleaCacheManager,用于接入Flea框架管理Redis缓存。

它的默认构造方法用于初始化哨兵模式下默认连接池的Redis客户端(默认Redis数据库索引为0)。这里需要先初始化Redis哨兵连接池(默认连接池名为default),然后通过 RedisClientFactory 获取哨兵模式下默认连接池的Redis客户端 RedisClient

它的带参构造方法用于初始化哨兵模式下默认连接池的Redis客户端(可指定Redis数据库索引)。

方法 newCache 用于创建一个 RedisFleaCache 的实例对象,它包含了读、写、删除和清空缓存的基本操作。每一类Redis缓存数据都对应了一个 RedisFleaCache 的实例对象。

public class RedisSentinelFleaCacheManager extends AbstractFleaCacheManager {

    private RedisClient redisClient; // Redis客户端

    /**
     * 默认构造方法,初始化哨兵模式下默认连接池的Redis客户端
     *
     * @since 2.0.0
     */
    public RedisSentinelFleaCacheManager() {
        this(0);
    }

    /**
     * 初始化哨兵模式下默认连接池的Redis客户端,指定Redis数据库索引
     *
     * @since 2.0.0
     */
    public RedisSentinelFleaCacheManager(int database) {
        if (!RedisSentinelConfig.getConfig().isSwitchOpen()) return;
        // 初始化默认连接池
        RedisSentinelPool.getInstance().initialize(database);
        // 获取哨兵模式下默认连接池的Redis客户端
        redisClient = RedisClientFactory.getInstance(CacheModeEnum.SENTINEL);
    }

    @Override
    protected AbstractFleaCache newCache(String name, int expiry) {
        int nullCacheExpiry = RedisSentinelConfig.getConfig().getNullCacheExpiry();
        if (RedisSentinelConfig.getConfig().isSwitchOpen())
            return new RedisFleaCache(name, expiry, nullCacheExpiry, CacheModeEnum.SENTINEL, redisClient);
        else
            return new EmptyFleaCache(name, expiry, nullCacheExpiry);
    }
}

3.11 定义Redis客户端工厂类

RedisClientFactory 提供了四种获取Redis客户端的方式:

  1. 获取分片模式下默认连接池的Redis客户端(单个缓存接入场景)。
  2. 获取指定模式下默认连接池的Redis客户端(单个缓存接入场景,3.10节采用)。
  3. 获取分片模式下指定连接池的Redis客户端(整合缓存接入场景)。
  4. 获取指定模式下指定连接池的Redis客户端(整合缓存接入场景)。
/**
 * Redis客户端工厂,用于获取Redis客户端。
 *
 * @author huazie
 * @version 1.1.0
 * @since 1.0.0
 */
public class RedisClientFactory {

    private static final ConcurrentMap<String, RedisClient> redisClients = new ConcurrentHashMap<>();

    private static final Object redisClientLock = new Object();

    private RedisClientFactory() {
    }

    /**
     * 获取分片模式下默认连接池的Redis客户端
     *
     * @return 分片模式的Redis客户端
     * @since 1.0.0
     */
    public static RedisClient getInstance() {
        return getInstance(CommonConstants.FleaPoolConstants.DEFAULT_POOL_NAME);
    }

    /**
     * 获取指定模式下默认连接池的Redis客户端
     *
     * @param mode 缓存模式
     * @return 指定模式的Redis客户端
     * @since 1.1.0
     */
    public static RedisClient getInstance(CacheModeEnum mode) {
        return getInstance(CommonConstants.FleaPoolConstants.DEFAULT_POOL_NAME, mode);
    }

    /**
     * 获取分片模式下指定连接池的Redis客户端
     *
     * @param poolName 连接池名
     * @return 分片模式的Redis客户端
     * @since 1.0.0
     */
    public static RedisClient getInstance(String poolName) {
        return getInstance(poolName, CacheModeEnum.SHARDED);
    }

    /**
     * 获取指定模式下指定连接池的Redis客户端
     *
     * @param poolName 连接池名
     * @param mode     缓存模式
     * @return 指定模式的Redis客户端
     * @since 1.1.0
     */
    public static RedisClient getInstance(String poolName, CacheModeEnum mode) {
        String key = StringUtils.strCat(poolName, CommonConstants.SymbolConstants.UNDERLINE, StringUtils.valueOf(mode.getMode()));
        if (!redisClients.containsKey(key)) {
            synchronized (redisClientLock) {
                if (!redisClients.containsKey(key)) {
                    RedisClientStrategyContext context = new RedisClientStrategyContext(poolName);
                    redisClients.putIfAbsent(key, FleaStrategyFacade.invoke(mode.name(), context));
                }
            }
        }
        return redisClients.get(key);
    }
}

getInstance(String poolName, CacheModeEnum mode) 方法中,使用了 RedisClientStrategyContext(Redis客户端策略上下文)。根据不同的缓存模式,可以找到对应的Redis客户端策略。

3.12 定义Redis客户端策略上下文

RedisClientStrategyContext 的实现可参考Redis分片模式接入的相关文档。

3.13 定义哨兵模式Redis客户端策略

RedisSentinelClientStrategy 用于新建一个Flea Redis哨兵客户端。

/**
 * 哨兵模式Redis客户端 策略
 *
 * @author huazie
 * @version 2.0.0
 * @since 2.0.0
 */
public class RedisSentinelClientStrategy implements IFleaStrategy<RedisClient, String> {

    @Override
    public RedisClient execute(String poolName) throws FleaStrategyException {
        RedisClient originRedisClient;
        // 新建一个Flea Redis哨兵客户端类实例
        if (CommonConstants.FleaPoolConstants.DEFAULT_POOL_NAME.equals(poolName)) {
            originRedisClient = new FleaRedisSentinelClient.Builder().build();
        } else {
            originRedisClient = new FleaRedisSentinelClient.Builder(poolName).build();
        }
        return originRedisClient;
    }
}

3.14 Redis哨兵模式接入自测

单元测试类 FleaCacheTest

首先,需要按照 redis.sentinel.properties 配置文件中的地址部署相应的Redis哨兵服务和Redis主从服务。

    @Test
    public void testRedisSentinelFleaCache() {
        try {
            // 哨兵模式下Flea缓存管理类,复用原有获取方式
//            AbstractFleaCacheManager manager = FleaCacheManagerFactory.getFleaCacheManager(CacheEnum.RedisSentinel.getName());
            // 哨兵模式下Flea缓存管理类,指定数据库索引
            AbstractFleaCacheManager manager = FleaCacheManagerFactory.getFleaCacheManager(0);
            AbstractFleaCache cache = manager.getCache("fleajerseyresource");
            LOGGER.debug("Cache={}", cache);
            //#### 1.  简单字符串
            cache.put("author", "huazie");
            cache.put("other", null);
//            cache.get("author");
//            cache.get("other");
//            cache.delete("author");
//            cache.delete("other");
//            cache.clear();
            cache.getCacheKey();
            LOGGER.debug(cache.getCacheName() + ">>>" + cache.getCacheDesc());
        } catch (Exception e) {
            LOGGER.error("Exception:", e);
        }
    }

4. 进阶接入(Spring集成)

4.1 定义抽象Spring缓存

AbstractSpringCache 的实现可参考Memcached接入的相关文档。

4.2 定义Redis Spring缓存类

RedisSpringCache 的实现可参考Redis分片模式接入的相关文档。

4.3 定义抽象Spring缓存管理类

AbstractSpringCacheManager 的实现可参考Memcached接入的相关文档。

4.4 定义Redis哨兵模式Spring缓存管理类

RedisSentinelSpringCacheManager 继承抽象Spring缓存管理类 AbstractSpringCacheManager,用于接入Spring框架管理Redis缓存。其基本实现同 RedisSentinelFleaCacheManager,唯一不同在于 newCache 方法的实现。

它的默认构造方法用于初始化哨兵模式下默认连接池的Redis客户端(默认Redis数据库索引为0)。这里需要先初始化Redis哨兵连接池(默认连接池名为default),然后通过 RedisClientFactory 获取哨兵模式下默认连接池的Redis客户端。

它的带参构造方法用于初始化哨兵模式下默认连接池的Redis客户端(可指定Redis数据库索引)。

方法 newCache 用于创建一个Redis Spring缓存,而其内部是由Redis Flea缓存实现具体的读、写、删除和清空缓存的基本操作。

public class RedisSentinelSpringCacheManager extends AbstractSpringCacheManager {

    private RedisClient redisClient; // Redis客户端

    /**
     * 默认构造方法,初始化哨兵模式下默认连接池的Redis客户端
     *
     * @since 2.0.0
     */
    public RedisSentinelSpringCacheManager() {
        this(0);
    }

    /**
     * 初始化哨兵模式下默认连接池的Redis客户端,指定Redis数据库索引
     *
     * @since 2.0.0
     */
    public RedisSentinelSpringCacheManager(int database) {
        if (!RedisSentinelConfig.getConfig().isSwitchOpen()) return;
        // 初始化默认连接池
        RedisSentinelPool.getInstance().initialize(database);
        // 获取哨兵模式下默认连接池的Redis客户端
        redisClient = RedisClientFactory.getInstance(CacheModeEnum.SENTINEL);
    }

    @Override
    protected AbstractSpringCache newCache(String name, int expiry) {
        int nullCacheExpiry = RedisSentinelConfig.getConfig().getNullCacheExpiry();
        if (RedisSentinelConfig.getConfig().isSwitchOpen())
            return new RedisSpringCache(name, expiry, nullCacheExpiry, CacheModeEnum.SENTINEL, redisClient);
        else
            return new RedisSpringCache(name, new EmptyFleaCache(name, expiry, nullCacheExpiry));
    }

}

4.5 Spring配置

    <!--
        配置缓存管理 redisSentinelSpringCacheManager
        配置缓存时间 configMap (key缓存对象名称 value缓存过期时间)
    -->
    <bean id="redisSentinelSpringCacheManager" class="com.huazie.fleaframework.cache.redis.manager.RedisSentinelSpringCacheManager">
        <!-- 使用带参数的构造函数实例化,指定Redis数据库索引 -->
        <!--<constructor-arg index="0" value="0"/>-->
        <property name="configMap">
            <map>
                <entry key="fleajerseyi18nerrormapping" value="86400"/>
                <entry key="fleajerseyresservice" value="86400"/>
                <entry key="fleajerseyresclient" value="86400"/>
                <entry key="fleajerseyresource" value="86400"/>
            </map>
        </property>
    </bean>

    <!-- 开启缓存 -->
    <cache:annotation-driven cache-manager="redisSentinelSpringCacheManager" proxy-target-class="true"/>

4.6 缓存自测

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class SpringCacheTest {

    private static final FleaLogger LOGGER = FleaLoggerProxy.getProxyInstance(SpringCacheTest.class);

    @Autowired
    @Qualifier("redisSentinelSpringCacheManager")
    private AbstractSpringCacheManager redisSentinelSpringCacheManager;

    @Test
    public void testRedisSentinelSpringCache() {
        try {
            // 哨兵模式下Spring缓存管理类
            AbstractSpringCache cache = redisSentinelSpringCacheManager.getCache("fleajerseyresource");
            LOGGER.debug("Cache = {}", cache);

            //#### 1.  简单字符串
            cache.put("menu1", "huazie");
            cache.put("menu2", null);
//            cache.get("menu1");
//            cache.get("menu2");
//            cache.getCacheKey();
//            cache.delete("menu1");
//            cache.delete("menu2");
//            cache.clear();
            cache.getCacheKey();
            AbstractFleaCache fleaCache = (AbstractFleaCache) cache.getNativeCache();
            LOGGER.debug(fleaCache.getCacheName() + ">>>" + fleaCache.getCacheDesc());
        } catch (Exception e) {
            LOGGER.error("Exception:", e);
        }
    }
}

结语

至此,Redis哨兵模式单独接入Flea框架的内容已介绍完毕。有关Memcached和Redis哨兵模式的整合接入,可参考《整合Memcached和Redis接入》一文。希望这篇详细的接入指南能帮助你构建高可用的Java缓存系统,更多关于系统架构的讨论欢迎在云栈社区交流。




上一篇:云服务器带宽计费详解:按流量与固定带宽如何选择及省钱避坑指南
下一篇:多模态AI耳机为何需要摄像头?探讨视觉感知对AI硬件设计的影响
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-10 09:07 , Processed in 0.341555 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表