1: 增加redis配置初始化指定key前缀,解决多应用共用一个redis环境时key覆盖的问题。

2: 增加redis哨兵配置模式,优化自动配置代码。
This commit is contained in:
yongen 2022-03-22 16:33:52 +08:00
parent ca085b9aaa
commit 654180e677
5 changed files with 411 additions and 182 deletions

View File

@ -19,7 +19,7 @@ xxl:
# 执行器开关
enabled: true
# 调度中心地址:如调度中心集群部署存在多个地址则用逗号分隔。
admin-addresses: http://localhost:9100/xxl-job-admin
admin-addresses: http://127.0.0.1:9870/xxl-job-admin/
# 执行器通讯TOKEN非空时启用
access-token: xxl-job
# 执行器配置
@ -55,7 +55,7 @@ spring:
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
username: root
password: root
password: 12345678
# 从库数据源
slave:
lazy: true
@ -121,7 +121,7 @@ spring:
# 数据库索引
database: 0
# 密码
password:
password: 12345678
# 连接超时时间
timeout: 10s
# 是否开启ssl
@ -134,6 +134,10 @@ redisson:
nettyThreads: 8
# 传输模式
transportMode: "NIO"
# 缓存前缀
key-prefix: ${ruoyi.name}
# 配置方式
mode: "SINGLE"
# 单节点配置
singleServerConfig:
# 客户端名称
@ -152,3 +156,32 @@ redisson:
retryInterval: 1500
# 发布和订阅连接池大小
subscriptionConnectionPoolSize: 50
# 集群配置
# clusterServersConfig:
# # 客户端名称
# clientName: ${ruoyi.name}
# # master最小空闲连接数
# masterConnectionMinimumIdleSize: 32
# # master连接池大小
# masterConnectionPoolSize: 64
# # slave最小空闲连接数
# slaveConnectionMinimumIdleSize: 32
# # slave连接池大小
# slaveConnectionPoolSize: 64
# # 连接空闲超时,单位:毫秒
# idleConnectionTimeout: 10000
# # ping连接间隔
# pingConnectionInterval: 1000
# # 命令等待超时,单位:毫秒
# timeout: 3000
# # 如果尝试在此限制之内发送成功,则开始启用 timeout 计时。
# retryAttempts: 3
# # 命令重试发送时间间隔,单位:毫秒
# retryInterval: 1500
# # 发布和订阅连接池大小
# subscriptionConnectionPoolSize: 50
# # 读取模式
# readMode: "SLAVE"
# # 订阅模式
# subscriptionMode: "MASTER"

View File

@ -1,179 +0,0 @@
package com.ruoyi.framework.config;
import cn.hutool.core.util.ObjectUtil;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.config.properties.RedissonProperties;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.codec.JsonJacksonCodec;
import org.redisson.config.Config;
import org.redisson.spring.cache.CacheConfig;
import org.redisson.spring.cache.RedissonSpringCacheManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* redis配置
*
* @author Lion Li
*/
@Slf4j
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
private static final String REDIS_PROTOCOL_PREFIX = "redis://";
private static final String REDISS_PROTOCOL_PREFIX = "rediss://";
@Autowired
private RedisProperties redisProperties;
@Autowired
private RedissonProperties redissonProperties;
@Primary
@Bean(destroyMethod = "shutdown")
public RedissonClient redisson() {
String prefix = REDIS_PROTOCOL_PREFIX;
if (redisProperties.isSsl()) {
prefix = REDISS_PROTOCOL_PREFIX;
}
Config config = new Config();
config.setThreads(redissonProperties.getThreads())
.setNettyThreads(redissonProperties.getNettyThreads())
.setCodec(JsonJacksonCodec.INSTANCE)
.setTransportMode(redissonProperties.getTransportMode());
RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
if (ObjectUtil.isNotNull(singleServerConfig)) {
// 使用单机模式
config.useSingleServer()
.setAddress(prefix + redisProperties.getHost() + ":" + redisProperties.getPort())
.setConnectTimeout(((Long) redisProperties.getTimeout().toMillis()).intValue())
.setDatabase(redisProperties.getDatabase())
.setPassword(StringUtils.isNotBlank(redisProperties.getPassword()) ? redisProperties.getPassword() : null)
.setTimeout(singleServerConfig.getTimeout())
.setRetryAttempts(singleServerConfig.getRetryAttempts())
.setRetryInterval(singleServerConfig.getRetryInterval())
.setClientName(singleServerConfig.getClientName())
.setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
.setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize())
.setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize())
.setConnectionPoolSize(singleServerConfig.getConnectionPoolSize());
}
// 集群配置方式 参考下方注释
RedissonProperties.ClusterServersConfig clusterServersConfig = redissonProperties.getClusterServersConfig();
if (ObjectUtil.isNotNull(clusterServersConfig)) {
// 使用集群模式
String finalPrefix = prefix;
List<String> nodes = redisProperties.getCluster().getNodes()
.stream()
.map(node -> finalPrefix + node)
.collect(Collectors.toList());
config.useClusterServers()
.setConnectTimeout(((Long) redisProperties.getTimeout().toMillis()).intValue())
.setPassword(StringUtils.isNotBlank(redisProperties.getPassword()) ? redisProperties.getPassword() : null)
.setTimeout(clusterServersConfig.getTimeout())
.setRetryAttempts(clusterServersConfig.getRetryAttempts())
.setRetryInterval(clusterServersConfig.getRetryInterval())
.setClientName(clusterServersConfig.getClientName())
.setIdleConnectionTimeout(clusterServersConfig.getIdleConnectionTimeout())
.setPingConnectionInterval(clusterServersConfig.getPingConnectionInterval())
.setSubscriptionConnectionPoolSize(clusterServersConfig.getSubscriptionConnectionPoolSize())
.setMasterConnectionMinimumIdleSize(clusterServersConfig.getMasterConnectionMinimumIdleSize())
.setMasterConnectionPoolSize(clusterServersConfig.getMasterConnectionPoolSize())
.setSlaveConnectionMinimumIdleSize(clusterServersConfig.getSlaveConnectionMinimumIdleSize())
.setSlaveConnectionPoolSize(clusterServersConfig.getSlaveConnectionPoolSize())
.setReadMode(clusterServersConfig.getReadMode())
.setSubscriptionMode(clusterServersConfig.getSubscriptionMode())
.setNodeAddresses(nodes);
}
RedissonClient redissonClient = Redisson.create(config);
log.info("初始化 redis 配置");
return redissonClient;
}
/**
* 整合spring-cache
*/
@Bean
public CacheManager cacheManager(RedissonClient redissonClient) {
List<RedissonProperties.CacheGroup> cacheGroup = redissonProperties.getCacheGroup();
Map<String, CacheConfig> config = new HashMap<>();
for (RedissonProperties.CacheGroup group : cacheGroup) {
CacheConfig cacheConfig = new CacheConfig(group.getTtl(), group.getMaxIdleTime());
cacheConfig.setMaxSize(group.getMaxSize());
config.put(group.getGroupId(), cacheConfig);
}
return new RedissonSpringCacheManager(redissonClient, config, JsonJacksonCodec.INSTANCE);
}
/**
* redis集群配置 yml
*
* --- # redis 集群配置(单机与集群只能开启一个另一个需要注释掉)
* spring:
* redis:
* cluster:
* nodes:
* - 192.168.0.100:6379
* - 192.168.0.101:6379
* - 192.168.0.102:6379
* # 密码
* password:
* # 连接超时时间
* timeout: 10s
* # 是否开启ssl
* ssl: false
*
* redisson:
* # 线程池数量
* threads: 16
* # Netty线程池数量
* nettyThreads: 32
* # 传输模式
* transportMode: "NIO"
* # 集群配置
* clusterServersConfig:
* # 客户端名称
* clientName: ${ruoyi.name}
* # master最小空闲连接数
* masterConnectionMinimumIdleSize: 32
* # master连接池大小
* masterConnectionPoolSize: 64
* # slave最小空闲连接数
* slaveConnectionMinimumIdleSize: 32
* # slave连接池大小
* slaveConnectionPoolSize: 64
* # 连接空闲超时单位毫秒
* idleConnectionTimeout: 10000
* # ping连接间隔
* pingConnectionInterval: 1000
* # 命令等待超时单位毫秒
* timeout: 3000
* # 如果尝试在此限制之内发送成功则开始启用 timeout 计时
* retryAttempts: 3
* # 命令重试发送时间间隔单位毫秒
* retryInterval: 1500
* # 发布和订阅连接池大小
* subscriptionConnectionPoolSize: 50
* # 读取模式
* readMode: "SLAVE"
* # 订阅模式
* subscriptionMode: "MASTER"
*/
}

View File

@ -1,6 +1,10 @@
package com.ruoyi.framework.config.properties;
import com.ruoyi.common.core.domain.R;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.redisson.config.ReadMode;
import org.redisson.config.SubscriptionMode;
@ -20,6 +24,15 @@ import java.util.List;
@ConfigurationProperties(prefix = "redisson")
public class RedissonProperties {
/**
* 连接模式
*/
public static final String MODE_SINGLE = "SINGLE";
public static final String MODE_SENTINEL = "SENTINEL";
public static final String MODE_CLUSTER = "CLUSTER";
/**
* 线程池数量,默认值 = 当前处理核数量 * 2
*/
@ -50,6 +63,16 @@ public class RedissonProperties {
*/
private List<CacheGroup> cacheGroup;
/**
* 缓存key前缀
*/
private String keyPrefix;
/**
* 配置方式
*/
private RedisMode mode;
@Data
@NoArgsConstructor
public static class SingleServerConfig {
@ -193,4 +216,21 @@ public class RedissonProperties {
}
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum RedisMode {
/**
* 单机
*/
SINGLE,
/**
* 集群
*/
CLUSTER,
/**
* 哨兵
*/
SENTINEL;
}
}

View File

@ -0,0 +1,42 @@
package com.ruoyi.framework.config.redisson;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.NameMapper;
/**
* redisson key 自动加前缀 解决多应用共用一个redis环境时key覆盖的问题
*
* @author im_libin@yeah.net
* @date 2022/3/21 2:50 下午
*/
public class KeyPrefixMapper implements NameMapper {
private final String keyPrefix;
public KeyPrefixMapper(String keyPrefix) {
this.keyPrefix = keyPrefix + ":";
}
@Override
public String map(String name) {
if (StringUtils.isBlank(name)) {
return null;
}
if (!name.startsWith(keyPrefix)) {
return keyPrefix + name;
} else {
return name;
}
}
@Override
public String unmap(String name) {
if (StringUtils.isBlank(name)) {
return null;
}
if (name.startsWith(keyPrefix)) {
return name.substring(keyPrefix.length());
}
return name;
}
}

View File

@ -0,0 +1,293 @@
package com.ruoyi.framework.config.redisson;
import cn.hutool.core.util.ObjectUtil;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.config.properties.RedissonProperties;
import com.ruoyi.framework.config.properties.RedissonProperties.ClusterServersConfig;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.DefaultNameMapper;
import org.redisson.api.NameMapper;
import org.redisson.api.RedissonClient;
import org.redisson.codec.JsonJacksonCodec;
import org.redisson.config.Config;
import org.redisson.spring.cache.CacheConfig;
import org.redisson.spring.cache.RedissonSpringCacheManager;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
/**
* redis配置
*
* @author Lion Li
*/
@Slf4j
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
private static final String REDIS_PROTOCOL_PREFIX = "redis://";
private static final String REDISS_PROTOCOL_PREFIX = "rediss://";
@Resource
private RedisProperties redisProperties;
@Resource
private RedissonProperties redissonProperties;
/**
* 单节点模式
*/
@Primary
@Bean(name = "redissonClient", destroyMethod = "shutdown")
@ConditionalOnProperty(name = "redisson.mode", havingValue = RedissonProperties.MODE_SINGLE)
@ConditionalOnMissingBean(RedissonClient.class)
RedissonClient single() {
RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
if (ObjectUtil.isNull(singleServerConfig)) {
throw new IllegalArgumentException("Redis connection 'redisson.singleServerConfig' is empty");
}
Config config = getConfig();
// 单机模式
config.useSingleServer()
.setNameMapper(getNameMapper())
.setAddress(getRedisProtocolPrefix() + redisProperties.getHost() + ":" + redisProperties.getPort())
.setConnectTimeout(((Long) redisProperties.getTimeout().toMillis()).intValue())
.setDatabase(redisProperties.getDatabase())
.setPassword(StringUtils.isNotBlank(redisProperties.getPassword()) ? redisProperties.getPassword() : null)
.setTimeout(singleServerConfig.getTimeout())
.setRetryAttempts(singleServerConfig.getRetryAttempts())
.setRetryInterval(singleServerConfig.getRetryInterval())
.setClientName(singleServerConfig.getClientName())
.setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
.setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize())
.setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize())
.setConnectionPoolSize(singleServerConfig.getConnectionPoolSize());
log.info("初始化 redis 配置: [mode={}, address={}]", redissonProperties.getMode(), redisProperties.getHost() + ":" + redisProperties.getPort());
return Redisson.create(config);
}
/**
* 哨兵模式
*/
@Primary
@Bean(name = "redissonClient", destroyMethod = "shutdown")
@ConditionalOnProperty(name = "redisson.mode", havingValue = RedissonProperties.MODE_SENTINEL)
@ConditionalOnMissingBean(RedissonClient.class)
RedissonClient sentinel() {
RedissonProperties.ClusterServersConfig clusterServersConfig = getClusterServersConfig();
String finalPrefix = getRedisProtocolPrefix();
List<String> nodes = redisProperties.getSentinel().getNodes()
.stream()
.map(node -> finalPrefix + node)
.collect(Collectors.toList());
Config config = getConfig();
// 哨兵方式
config.useSentinelServers()
.setCheckSentinelsList(false)
.setNameMapper(getNameMapper())
.setConnectTimeout(((Long) redisProperties.getTimeout().toMillis()).intValue())
.setPassword(StringUtils.isNotBlank(redisProperties.getPassword()) ? redisProperties.getPassword() : null)
.setMasterName(redisProperties.getSentinel().getMaster())
.setTimeout(clusterServersConfig.getTimeout())
.setRetryAttempts(clusterServersConfig.getRetryAttempts())
.setRetryInterval(clusterServersConfig.getRetryInterval())
.setClientName(clusterServersConfig.getClientName())
.setIdleConnectionTimeout(clusterServersConfig.getIdleConnectionTimeout())
.setPingConnectionInterval(clusterServersConfig.getPingConnectionInterval())
.setSubscriptionConnectionPoolSize(clusterServersConfig.getSubscriptionConnectionPoolSize())
.setMasterConnectionMinimumIdleSize(clusterServersConfig.getMasterConnectionMinimumIdleSize())
.setMasterConnectionPoolSize(clusterServersConfig.getMasterConnectionPoolSize())
.setSlaveConnectionMinimumIdleSize(clusterServersConfig.getSlaveConnectionMinimumIdleSize())
.setSlaveConnectionPoolSize(clusterServersConfig.getSlaveConnectionPoolSize())
.setReadMode(clusterServersConfig.getReadMode())
.setSubscriptionMode(clusterServersConfig.getSubscriptionMode())
.setSentinelAddresses(nodes);
log.info("初始化 redis 配置: [mode={}, master={}, nodes={}]", redissonProperties.getMode(), redisProperties.getSentinel().getMaster(),
nodes);
return Redisson.create(config);
}
/**
* 集群模式
*/
@Primary
@Bean(name = "redissonClient", destroyMethod = "shutdown")
@ConditionalOnProperty(name = "redisson.mode", havingValue = RedissonProperties.MODE_CLUSTER)
@ConditionalOnMissingBean(RedissonClient.class)
RedissonClient cluster() {
RedissonProperties.ClusterServersConfig clusterServersConfig = getClusterServersConfig();
String finalPrefix = getRedisProtocolPrefix();
List<String> nodes = redisProperties.getCluster().getNodes()
.stream()
.map(node -> finalPrefix + node)
.collect(Collectors.toList());
Config config = getConfig();
// 集群方式
config.useClusterServers()
.setNameMapper(getNameMapper())
.setConnectTimeout(((Long) redisProperties.getTimeout().toMillis()).intValue())
.setPassword(StringUtils.isNotBlank(redisProperties.getPassword()) ? redisProperties.getPassword() : null)
.setTimeout(clusterServersConfig.getTimeout())
.setRetryAttempts(clusterServersConfig.getRetryAttempts())
.setRetryInterval(clusterServersConfig.getRetryInterval())
.setClientName(clusterServersConfig.getClientName())
.setIdleConnectionTimeout(clusterServersConfig.getIdleConnectionTimeout())
.setPingConnectionInterval(clusterServersConfig.getPingConnectionInterval())
.setSubscriptionConnectionPoolSize(clusterServersConfig.getSubscriptionConnectionPoolSize())
.setMasterConnectionMinimumIdleSize(clusterServersConfig.getMasterConnectionMinimumIdleSize())
.setMasterConnectionPoolSize(clusterServersConfig.getMasterConnectionPoolSize())
.setSlaveConnectionMinimumIdleSize(clusterServersConfig.getSlaveConnectionMinimumIdleSize())
.setSlaveConnectionPoolSize(clusterServersConfig.getSlaveConnectionPoolSize())
.setReadMode(clusterServersConfig.getReadMode())
.setSubscriptionMode(clusterServersConfig.getSubscriptionMode())
.setNodeAddresses(nodes);
log.info("初始化 redis 配置: [mode={}, nodes={}]", redissonProperties.getMode(), nodes);
return Redisson.create(config);
}
private String getRedisProtocolPrefix(){
return redisProperties.isSsl() ? REDISS_PROTOCOL_PREFIX : REDIS_PROTOCOL_PREFIX;
}
private Config getConfig(){
Config config = new Config();
config.setThreads(redissonProperties.getThreads())
.setNettyThreads(redissonProperties.getNettyThreads())
.setCodec(JsonJacksonCodec.INSTANCE)
.setTransportMode(redissonProperties.getTransportMode());
return config;
}
private ClusterServersConfig getClusterServersConfig() {
RedissonProperties.ClusterServersConfig clusterServersConfig = redissonProperties.getClusterServersConfig();
if (ObjectUtil.isNull(clusterServersConfig)){
throw new IllegalArgumentException("Redis connection 'redisson.clusterServersConfig' is empty");
}
return clusterServersConfig;
}
private NameMapper getNameMapper() {
if (StringUtils.isBlank(redissonProperties.getKeyPrefix())){
return new DefaultNameMapper();
}
return new KeyPrefixMapper(redissonProperties.getKeyPrefix());
}
/**
* 整合spring-cache
*/
@Bean
public CacheManager cacheManager(RedissonClient redissonClient) {
List<RedissonProperties.CacheGroup> cacheGroup = redissonProperties.getCacheGroup();
Map<String, CacheConfig> config = new HashMap<>(cacheGroup.size());
for (RedissonProperties.CacheGroup group : cacheGroup) {
CacheConfig cacheConfig = new CacheConfig(group.getTtl(), group.getMaxIdleTime());
cacheConfig.setMaxSize(group.getMaxSize());
config.put(group.getGroupId(), cacheConfig);
}
return new RedissonSpringCacheManager(redissonClient, config, JsonJacksonCodec.INSTANCE);
}
/**
* redis集群配置 yml
*
* --- # redis 集群配置(单机集群哨兵只能开启一个另两个需要注释掉)
* spring:
* redis:
* sentinel:
* master: redis_master
* nodes:
* - 192.168.0.100:26379
* - 192.168.0.101:26379
* - 192.168.0.102:26379
* cluster:
* nodes:
* - 192.168.0.100:6379
* - 192.168.0.101:6379
* - 192.168.0.102:6379
* # 地址
* host: localhost
* # 端口默认为6379
* port: 6379
* # 密码
* password: 12345678
* # 连接超时时间
* timeout: 10s
* # 是否开启ssl
* ssl: false
*
* redisson:
* # 线程池数量
* threads: 16
* # Netty线程池数量
* nettyThreads: 32
* # 传输模式
* transportMode: "NIO"
* # 缓存key前缀
* keyPrefix: ${ruoyi.name}
* # 配置方式
* mode: "SINGLE"
* --- # 当mode: "SINGLE" 配置 singleServerConfig节点当mode: "CLUSTER/SENTINEL" 配置clusterServersConfig节点
* # 单节点配置
* singleServerConfig:
* # 客户端名称
* clientName: ${ruoyi.name}
* # 最小空闲连接数
* connectionMinimumIdleSize: 8
* # 连接池大小
* connectionPoolSize: 32
* # 连接空闲超时单位毫秒
* idleConnectionTimeout: 10000
* # 命令等待超时单位毫秒
* timeout: 3000
* # 如果尝试在此限制之内发送成功则开始启用 timeout 计时
* retryAttempts: 3
* # 命令重试发送时间间隔单位毫秒
* retryInterval: 1500
* # 发布和订阅连接池大小
* subscriptionConnectionPoolSize: 50
* # 集群配置
* clusterServersConfig:
* # 客户端名称
* clientName: ${ruoyi.name}
* # master最小空闲连接数
* masterConnectionMinimumIdleSize: 32
* # master连接池大小
* masterConnectionPoolSize: 64
* # slave最小空闲连接数
* slaveConnectionMinimumIdleSize: 32
* # slave连接池大小
* slaveConnectionPoolSize: 64
* # 连接空闲超时单位毫秒
* idleConnectionTimeout: 10000
* # ping连接间隔
* pingConnectionInterval: 1000
* # 命令等待超时单位毫秒
* timeout: 3000
* # 如果尝试在此限制之内发送成功则开始启用 timeout 计时
* retryAttempts: 3
* # 命令重试发送时间间隔单位毫秒
* retryInterval: 1500
* # 发布和订阅连接池大小
* subscriptionConnectionPoolSize: 50
* # 读取模式
* readMode: "SLAVE"
* # 订阅模式
* subscriptionMode: "MASTER"
*/
}