Redis哨兵模式接入
- 1. 參考
- 2. 依賴
- 3. 基礎接入
- 3.1 定義Flea緩存接口
- 3.2 定義抽象Flea緩存類
- 3.3 定義Redis客戶端接口類
- 3.4 定義Redis客戶端命令行
- 3.5 定義哨兵模式Redis客戶端實現類
- 3.6 定義Redis哨兵連接池
- 3.7 定義Redis哨兵配置文件
- 3.8 定義Redis Flea緩存類
- 3.9 定義抽象Flea緩存管理類
- 3.10 定義Redis哨兵模式Flea緩存管理類
- 3.11 定義Redis客戶端工廠類
- 3.12 定義Redis客戶端策略上下文
- 3.13 定義哨兵模式Redis客戶端策略
- 3.14 Redis集群模式接入自測
- 4. 進階接入
- 4.1 定義抽象Spring緩存
- 4.2 定義Redis Spring緩存類
- 4.3 定義抽象Spring緩存管理類
- 4.4 定義Redis哨兵模式Spring緩存管理類
- 4.5 spring 配置
- 4.6 緩存自測
- 結語
1. 參考
flea-cache使用之Redis哨兵模式接入 源代碼
2. 依賴
jedis-3.0.1.jar
<!-- Java redis -->
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>3.0.1</version>
</dependency>
spring-context-4.3.18.RELEASE.jar
<!-- 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
<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 即Flea哨兵模式Redis客戶端實現,封裝了Flea框架操作Redis緩存的基本操作。它內部具體操作Redis緩存的功能,由Jedis哨兵連接池獲取Jedis實例對象完成,包含讀、寫、刪除Redis緩存的基本操作方法。
哨兵模式下,單個緩存接入場景,可通過如下方式使用:
RedisClient redisClient = new FleaRedisSentinelClient.Builder().build();// 執行讀,寫,刪除等基本操作redisClient.set("key", "value");
哨兵模式下,整合緩存接入場景,可通過如下方式使用:
RedisClient redisClient = new FleaRedisSentinelClient.Builder(poolName).build();// 執行讀,寫,刪除等基本操作redisClient.set("key", "value");
當然每次都新建Redis客戶端顯然不可取,我們可通過Redis客戶端工廠獲取Redis客戶端。
哨兵模式下,單個緩存接入場景,可通過如下方式使用:
RedisClient redisClient = RedisClientFactory.getInstance(CacheModeEnum.SENTINEL);
哨兵模式下,整合緩存接入場景,可通過如下方式使用:
RedisClient redisClient = RedisClientFactory.getInstance(poolName, CacheModeEnum.SENTINEL);
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();}}@Overridepublic String set(final String key, final Object value) {return new RedisClientCommand<String, JedisSentinelPool, Jedis>(this.jedisSentinelPool, this.maxAttempts) {@Overridepublic String execute(Jedis connection) {if (value instanceof String)return connection.set(key, (String) value);elsereturn connection.set(SafeEncoder.encode(key), ObjectUtils.serialize(value));}}.run();}// 省略。。。。。。/*** 內部建造者類** @author huazie* @version 2.0.0* @since 2.0.0*/public static class Builder {private String poolName; // 連接池名/*** 默認構造器** @since 2.0.0*/public Builder() {}/*** 指定連接池的構造器** @param poolName 連接池名* @since 2.0.0*/public Builder(String poolName) {this.poolName = poolName;}/*** 構建Redis哨兵客戶端對象** @return Redis哨兵客戶端* @since 2.0.0*/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);
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哨兵配置
redis.sentinel.switch=1redis.systemName=FleaFrameredis.sentinel.masterName=mymasterredis.sentinel.server=127.0.0.1:36379,127.0.0.1:36380,127.0.0.1:36381#redis.sentinel.password=huazie123redis.sentinel.connectionTimeout=2000redis.sentinel.soTimeout=2000# Redis哨兵客戶端連接池配置
redis.pool.maxTotal=100redis.pool.maxIdle=10redis.pool.minIdle=0redis.pool.maxWaitMillis=2000redis.maxAttempts=5redis.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
,可在 3.11 查看。
它的帶參數的構造方法,用于初始化哨兵模式下默認連接池的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);}@Overrideprotected AbstractFleaCache newCache(String name, int expiry) {int nullCacheExpiry = RedisSentinelConfig.getConfig().getNullCacheExpiry();if (RedisSentinelConfig.getConfig().isSwitchOpen())return new RedisFleaCache(name, expiry, nullCacheExpiry, CacheModeEnum.SENTINEL, redisClient);elsereturn new EmptyFleaCache(name, expiry, nullCacheExpiry);}
}
3.11 定義Redis客戶端工廠類
RedisClientFactory ,有四種方式獲取 Redis 客戶端:
- 一是獲取分片模式下默認連接池的 Redis 客戶端,應用在單個緩存接入場景;
- 二是獲取指定模式下默認連接池的 Redis 客戶端,應用在單個緩存接入場景【3.10 采用】;
- 三是獲取分片模式下指定連接池的 Redis 客戶端,應用在整合緩存接入場景;
- 四是獲取指定模式下指定連接池的 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> {@Overridepublic 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;}
}
好了,到這里我們可以來測試 Redis 哨兵模式。
3.14 Redis集群模式接入自測
單元測試類 FleaCacheTest
首先,這里需要按照 Redis哨兵配置文件 中的地址部署相應的 Redis哨兵服務 和 Redis主從服務,后續有機會我再出一篇簡單的Redis主從 + 哨兵的搭建博文。
@Testpublic 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. 進階接入
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客戶端 RedisClient
,可在 3.11 查看。
它的帶參數的構造方法,用于初始化哨兵模式下默認連接池的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);}@Overrideprotected AbstractSpringCache newCache(String name, int expiry) {int nullCacheExpiry = RedisSentinelConfig.getConfig().getNullCacheExpiry();if (RedisSentinelConfig.getConfig().isSwitchOpen())return new RedisSpringCache(name, expiry, nullCacheExpiry, CacheModeEnum.SENTINEL, redisClient);elsereturn 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;@Testpublic 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 哨兵模式單獨接入的內容終于搞定了,有關整合接入Redis哨兵模式的,請查看筆者的《整合Memcached和Redis接入》。