异地应用双活MshaSDK使用手册

本文为您提供 MshaSDK 的集成与使用指南,涵盖 Maven 依赖配置、JVM 参数设置,以及 MySQL、PostgreSQL、Redis 和 MongoDB 的应用修改。帮助您了解多活架构的配置要求,实现高可用性和容灾功能,并注意配置过程中的事项,以确保系统稳定运行。

1. 基础MshaSDK依赖

1.1 依赖MshaSDK

<dependency>
    <groupId>com.aliyun.msha</groupId>
    <artifactId>msha-router-all</artifactId>
    <version>x.y.z</version>
</dependency>

1.2 JVM-D参数配置

应用需要新增的JVM参数:

-Dregion-id=${实际的地域ID}
-Dzone-id=${实际的可用区ID}
-Downer-account-id=${实际的阿里云主账号ID}
-Dmsha.app.name=${应用名称}
-Dmsha.namespaces=${多活命名空间ID}
-Dmsha.nacos.namespace=${NACOS 命名空间ID}
-Dmsha.nacos.server.addr=${NACOS 连接地址}
  • ${实际的地域ID},混合云场景(含自建IDC或非阿里云场景)需要填写,不填写的话 msha 根据 阿里云ECS元数据接口获取机器所属地域。

  • ${实际的可用区ID},混合云场景(含自建IDC或非阿里云场景)需要填写,不填写的话 msha 根据 阿里云ECS元数据接口获取机器所属可用区。

  • ${多活命名空间ID},可以进入 多活容灾管控平台MSHA页面,菜单栏选择 基础配置 > 命名空间页面,查看已经创建好的多活命名空间ID。

  • ${应用名称},为您实际的应用名,应用名称不支持中文。

  • msha.nacos.namespace、msha.nacos.server.addr 是本云Nacos配置中心的命名空间ID和链接地址。也可替换成msha.acm.namespace、msha.acm.endpoint是本云ACM配置中心的命名空间ID和链接地址。(ACM配置中心可在EDAS中申请)

1.3 Java9+ 的额外配置

Java9+ 需要额外增加以下配置,否则无法使用cglib动态代理。

--add-opens java.base/java.lang=ALL-UNNAMED
# 例如:java -Dmsha.namespaces=${多活命名空间ID} *** --add-opens java.base/java.lang=ALL-UNNAMED -jar your-application.jar

2. MySQL应用改造

2.1 更换JDBC-Driver

  • 对于Spring的使用方式:

应用在spring properties文件中配置JDBC Driver,则修改该文件进行替换,将原生的com.mysql.jdbc.Driver 替换为多活Driver:

// 将原生的driver替换为多活Driver
// spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.driver-class-name=com.ali.unit.router.driver.Driver
  • 对于JNDI的使用方式:

在数据源的定义文件上,将原生的 com.mysql.jdbc.Driver 替换为多活Driver:

<Resource 
  name="jdbc/mysql"
  auth="Container" 
  type="javax.sql.DataSource"
  maxActive="100" 
  maxIdle="30" 
  maxWait="10000"
  username="root" 
  password="root"
<!--   将原生的  com.mysql.jdbc.Driver 替换为多活Driver -->
<!--   driverClassName="com.mysql.jdbc.Driver" -->
  driverClassName="com.ali.unit.router.driver.Driver"
<!--   修改url,添加主备库信息 -->
  url="jdbc:mysql://${主云的数据库连接地址}:3306/xxxDbName?useUnicode=true&characterEncoding=UTF-8&mshaStandbyHost=${备云的数据库连接地址}&mshaStandbyPort=3306"/>

2.2 数据源配置修改

  • 对于Spring的使用方式:

应用在spring properties文件中配置数据库连接参数,则修改该文件进行修改。改为默认数据库连接地址是主云数据库地址,而在URL参数中增加另外一朵云(备云)的数据库连接地址(即备库):

// 格式
spring.datasource.url=jdbc:mysql://${主云的数据库连接地址}:3306/xxxDbName?useUnicode=true&characterEncoding=UTF-8&mshaStandbyHost=${备云的数据库连接地址}&mshaStandbyPort=3306
  • 对于JNDI的使用方式:

在数据源的定义文件上,改为默认数据库连接地址是主云数据库地址,而在URL参数中增加另外一朵云(备云)的数据库连接地址(即备库):

<Resource 
  name="jdbc/mysql"
  auth="Container" 
  type="javax.sql.DataSource"
  maxActive="100" 
  maxIdle="30" 
  maxWait="10000"
  username="root" 
  password="root"
<!--   将原生的  com.mysql.jdbc.Driver 替换为多活Driver -->
<!--   driverClassName="com.mysql.jdbc.Driver" -->
  driverClassName="com.ali.unit.router.driver.Driver"
<!--   修改url,添加主备库信息 -->
  url="jdbc:mysql://${主云的数据库连接地址}:3306/xxxDbName?useUnicode=true&characterEncoding=UTF-8&mshaStandbyHost=${备云的数据库连接地址}&mshaStandbyPort=3306"/>
说明

两朵云的应用,该连接地址配置需要保持一致,即默认数据库连接地址是主云数据库地址,而在URL参数中包含另外一朵云(备云)的数据库连接地址。

2.3 Druid 场景修改连接池配置

druid建议版本(不强制):1.2.8

在使用DruidDatasource时,以下参数是必需配置的。

参数

说明

validation-query: SELECT 1

测试连接使用的SQL,如果使用原生的jdbcdriver,会有默认的检测连接可用性方式,可以不配置;但是因为我们使用的是mshadriver,不会用默认的,而是使用用户配置的包含SQL进行检测,所以必须配置。

test-while-idle: true

空闲时进行连接有效性测试,这个参数默认是true,这边不允许修改为false,否则将不会进行连接可用性检测。

keep-alive: true

是否保活,需要开启,定时保活可以让我们的连接处于可用状态。

keep-alive-between-time-millis: 60000

空闲连接的保活时间周期。

time-between-eviction-runs-millis: 5000

作为DestroyTask执行的时间周期,可以在连接可用性判定时作为有效的使用时间周期。

min-evictable-idle-time-millis: 300000

最小空闲时间,如果连接池中非运行中的连接数大于minIdle,并且那部分连接的非运行时间大于minEvictableIdleTimeMillis,则连接池会将那部分连接设置成Idle状态并关闭。

下面再提一下使用druid配置的注意点:

Druid注册了一个time-between-eviction-runs-millis为周期的轮询任务,进行连接的回收和检测,当空闲时间 idleMillis >= minEvictableIdleTimeMillis时,会将checkcount的连接放入回收队列,如果idleMillis > maxEvictableIdleTimeMillis,则不论什么情况,均放入回收队列。

如果配置了保活且:keepAlive && idleMillis >= keepAliveBetweenTimeMillis则会放入保活队列。

再看上面的配置中配置了keepAlive但是没有配置keepAliveBetweenTimeMillis,不配置的话默认120S,但是minEvictableIdleTimeMillis配置了60秒,这说明,极有可能需要保护的连接直接被回收队列拦截而未被保护,导致连接失效。

下面是一个示例:

spring:
  # Datasource配置
  datasource:
    driverClassName: com.ali.unit.router.driver.Driver
    url: ${META_DB_URL:jdbc:mysql://127.0.0.1:3306/my_db?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2B8}
    username: ${META_DB_USER:*****}
    password: ${META_DB_PWD:*****}
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      initial-size: 5 # 连接池初始大小
      max-active: 20 # 最大连接数
      min-idle: 5 # 最小闲置连接数
      max-wait: 60000 # 最大等待时间
      validationQuery: SELECT 1 # 验证连接是否有效
      testWhileIdle: true # 是否开启空闲连接测试
      testOnBorrow: false # 是否开启连接被借出前的测试
      testOnReturn: false # 是否开启连接被归还前的测试
      timeBetweenEvictionRunsMillis: 5000 # testWhileIdle为true时,测试的时间间隔
      minEvictableIdleTimeMillis: 300000 # 连接在池中最小的生存时间
      keepAlive: true
      keep-alive-between-time-millis: 60000

3. PostgreSQL应用改造

3.1更换JDBC-Driver

例如应用在spring properties文件中配置JDBC Driver,则修改该文件进行替换,将原生的 com.mysql.jdbc.Driver 替换为多活Driver:

// 将原生的driver替换为多活Driver
// spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.driver-class-name=com.ali.unit.router.driver.Driver

3.2数据源配置修改

例如应用在spring properties文件中配置数据库连接参数,则修改该文件进行修改。改为默认数据库连接地址是主云数据库地址,而在URL参数中增加另外一朵云(备云)的数据库连接地址(即备库):

// 格式
spring.datasource.url=jdbc:postgresql://${主云的数据库连接地址}:5432/xxxDbName?useSSL=false&mshaStandbyHost=${备云的数据库连接地址}&mshaStandbyPort=5432
说明

两朵云的应用,该连接地址配置需要保持一致,即默认数据库连接地址是主云数据库地址,而在URL参数中包含另外一朵云(备云)的数据库连接地址。

4. Redis应用改造

4.1 Spring-data-redis(Jedis)

引入多活SDKJedis依赖。

<dependency>
  <groupId>com.aliyun.unit.router</groupId>
  <artifactId>msha-bridge-redis-jedis</artifactId>
  <version>x.y.z</version>
</dependency>

4.1.1 单机模式

1)应用中添加主备数据库配置
spring.redis.host.primary=${主云redis的连接地址}
spring.redis.host.standby=${备云redis的连接地址}
spring.redis.port=${redis端口}
spring.redis.password=${redis密码}
2)替换默认的spring工厂类

新建一个config类,用于替换spring的默认工厂类。

    @Configuration
    public class RedisConfig {

        @Value("${spring.redis.host.primary}")
        private String primaryHost;

        @Value("${spring.redis.host.standby}")
        private String standbyHost;

        @Value("${spring.redis.password}")
        private String password;

        @Value("${spring.redis.port}")
        private int port;

        @Bean
        JedisConnectionFactory jedisConnectionFactory() throws NoSuchFieldException, IllegalAccessException {

            Pool<Jedis> pool = new MshaJedisPool(primaryHost, port, password, standbyHost, port, password, 0);
            JedisConnectionFactory jedisConnectionFactory = new MshaJedisConnectionFactory(pool);
            Class<JedisConnectionFactory> clazz = JedisConnectionFactory.class;
            Field poolField = clazz.getDeclaredField("pool");
            poolField.setAccessible(true);
            poolField.set(jedisConnectionFactory, pool);
            return jedisConnectionFactory;
        }

        @Bean
        public RedisTemplate<String, Object> redisTemplate(JedisConnectionFactory jedisConnectionFactory) {
            RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
            template.setConnectionFactory(jedisConnectionFactory);
            template.setDefaultSerializer(new StringRedisSerializer());
            return template;
        }

    }
3)规范和限制

目前不支持用法:pub/sub 类命令、自建的哨兵模式服务端、基于spring-session-data-redissession。

4)强制指定中心

引入msha-bridge-springboot-aspect依赖。

<dependency>
  <groupId>com.aliyun.unit.router</groupId>
  <artifactId>msha-bridge-springboot-aspect</artifactId>
  <version>x.y.z</version>
</dependency>

代码中使用@WithRedisCenter(value = true)指定主数据源。

@WithRedisCenter(value = true)
public Map<String, Object> get(String key) {
    // 此处为客户代码
}

4.1.2 集群模式

1)应用中添加主备数据库配置
# 用于获取控制台规则,需与控制台配置的连接地址相同
spring.redis.host.primary=${控制台配置的主云redis的连接地址}
spring.redis.host.standby=${控制台配置的备云redis的连接地址}
spring.redis.port=${控制台配置的redis端口}
# 中心机房及备机房对应的redis节点
spring.redis.host.primary.nodes=${主云节点1,格式为host:port},${主云节点2,格式为host:port},...,${主云节点n,格式为host:port}
spring.redis.host.standby.nodes=${备云节点1,格式为host:port},${备云节点2,格式为host:port},...,${备云节点n,格式为host:port}
spring.redis.password=${redis密码}

参考下图,spring.redis.host.primary、spring.redis.host.standbyspring.redis.port需与控制中填写的推送标识保持一致(一般任选一节点填写,主备云redisport需要相同)。 image.png

2)替换默认的spring工厂类
@Configuration
public class RedisConfig {

    @Value("${spring.redis.host.primary}")
    private String primaryHost;

    @Value("${spring.redis.host.standby}")
    private String standbyHost;

    @Value("${spring.redis.host.primary.nodes}")
    private String primaryNodesStr;

    @Value("${spring.redis.host.standby.nodes}")
    private String standbyNodesStr;

    private Set<HostAndPort> primaryNodes;

    private Set<HostAndPort> standbyNodes;

    @Value("${spring.redis.password}")
    private String password;

    @Value("${spring.redis.port}")
    private int port;

    @PostConstruct
    public void init() {
        primaryNodes = ClusterNodesUtil.getHostAndPortNodes(primaryNodesStr);
        standbyNodes = ClusterNodesUtil.getHostAndPortNodes(standbyNodesStr);
    }

    @Bean
    JedisConnectionFactory jedisConnectionFactory() {

        JedisCluster jedisCluster = getJedisCluster();
        return new MshaJedisConnectionFactory(jedisCluster);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(JedisConnectionFactory jedisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(jedisConnectionFactory);
        // 参照原有业务代码的配置项进行配置
        template.setDefaultSerializer(new StringRedisSerializer());
        return template;
    }

    private JedisCluster getJedisCluster() {
        MetaData defaultMetaData = new RedisMetaData(null, password, primaryHost, port);
        MetaData standbyMetaData = new RedisMetaData(null, password, standbyHost, port);
        final int DEFAULT_MAX_REDIRECTIONS = 5;

        return MshaJedisFactory.getJedisCluster(primaryNodes, defaultMetaData, standbyNodes, standbyMetaData,
        Protocol.DEFAULT_TIMEOUT, Protocol.DEFAULT_TIMEOUT, DEFAULT_MAX_REDIRECTIONS, password, password,
        new GenericObjectPoolConfig());

    }
}
3)规范和限制

目前不支持用法:pub/sub 类命令、自建的哨兵模式服务端、基于spring-session-data-redissession。

4.2 Spring-data-redis(Lettuce)

引入多活SDKLettuce依赖。

<dependency>
  <groupId>com.aliyun.unit.router</groupId>
  <artifactId>msha-bridge-redis-lettuce</artifactId>
  <version>x.y.z</version>
</dependency>

4.2.1 单机模式

1)应用中添加主备数据库配置
spring.redis.host.primary=${主云redis的连接地址}
spring.redis.host.standby=${备云redis的连接地址}
spring.redis.port=${redis端口}
spring.redis.password=${redis密码}

2)替换默认的spring工厂类

新建一个config类,用于替换spring的默认工厂类。

	@Configuration
    public class RedisConfig {

        @Value("${spring.redis.host.primary}")
        private String primaryHost;

        @Value("${spring.redis.host.standby}")
        private String standbyHost;

        @Value("${spring.redis.password}")
        private String password;

        @Value("${spring.redis.port}")
        private int port;

        @Bean(name = "mshaRedisClient")
        public RedisClient mshaRedisClient() {
            MetaData defaultMetaData = new RedisMetaData(null, password, primaryHost, port);
            MetaData standbyMetaData = new RedisMetaData(null, password, standbyHost, port);

            return MshaLettuceClient.create(defaultMetaData, standbyMetaData);
        }

        @Bean
        public LettuceConnectionFactory lettuceConnectionFactory(RedisClient mshaRedisClient) {
            return new MshaLettuceConnectionFactory(mshaRedisClient);
        }

        @Bean
        public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
            RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
            template.setConnectionFactory(lettuceConnectionFactory);
            // 参照原有业务代码的配置项进行配置
            template.setDefaultSerializer(new StringRedisSerializer());
            return template;
        }
    }
// 使用默认的Db 0
MshaLettuceClient.create(defaultMetaData, standbyMetaData, ruleProcessService);
// 使用指定的Db 
MshaLettuceClient.create(defaultMetaData, standbyMetaData, ruleProcessService, dbindex);
3)规范和限制

目前不支持用法:pub/sub 类命令、自建的哨兵模式服务端、基于spring-session-data-redissession

spring-boot版本使用2.1.3.RELEASE及以上

4)强制指定中心

引入msha-bridge-springboot-aspect依赖。

<dependency>
  <groupId>com.aliyun.unit.router</groupId>
  <artifactId>msha-bridge-springboot-aspect</artifactId>
  <version>x.y.z</version>
</dependency>

代码中使用@WithRedisCenter(value = true)指定主数据源。

@WithRedisCenter(value = true)
public Map<String, Object> get(String key) {
    // 此处为客户代码
}
5)支持使用jetcache配置

使用jetcache,上面的spring工厂类仍然需要改造,jetcache会使用上面的config里初始化出来的redisClient,保证全局唯一性,并且jetcacheredisTemplate都可以正常使用。

然后修改jetcache配置,remote配置改为msha配置,这里不需要配置uri,配置了不会报错也不会生效,jetcache会直接使用已存在的redisClientbean,其他配置与原remote配置保持一致即可。

说明

remotemsha的配置不能共存。

jetcache:
  statIntervalMinutes: 15
  areaInCacheName: false
  msha:
    default:
      type: redis.lettuce
      keyConvertor: fastjson

4.2.2 集群模式

1)应用中添加主备数据库配置
# 用于获取控制台规则,需与控制台配置的连接地址相同
spring.redis.host.primary=${控制台配置的主云redis的连接地址}
spring.redis.host.standby=${控制台配置的备云redis的连接地址}
spring.redis.port=${控制台配置的redis端口}
# 中心机房及备机房对应的redis节点
spring.redis.host.primary.nodes=${主云节点1,格式为host:port},${主云节点2,格式为host:port},...,${主云节点n,格式为host:port}
spring.redis.host.standby.nodes=${备云节点1,格式为host:port},${备云节点2,格式为host:port},...,${备云节点n,格式为host:port}
spring.redis.password=${redis密码}

参考下图,spring.redis.host.primary、spring.redis.host.standbyspring.redis.port需与控制中填写的推送标识保持一致(一般任选一节点填写,主备云redisport需要相同)。 image

2)替换默认的spring工厂类
@Configuration
public class RedisConfig implements InitializingBean {

    @Value("${spring.redis.host.primary}")
    private String primaryHost;

    @Value("${spring.redis.host.standby}")
    private String standbyHost;

    @Value("${spring.redis.host.primary.nodes}")
    private String primaryNodesStr;

    @Value("${spring.redis.host.standby.nodes}")
    private String standbyNodesStr;

    private Set<RedisURI> primaryNodes;

    private Set<RedisURI> standbyNodes;

    @Value("${spring.redis.password}")
    private String password;

    @Value("${spring.redis.port}")
    private int port;

    @Override
    public void afterPropertiesSet() throws Exception {
        primaryNodes = ClusterNodesUtil.getNodes(primaryNodesStr);
        standbyNodes = ClusterNodesUtil.getNodes(standbyNodesStr);
    }
    @Bean
    public LettuceConnectionFactory lettuceConnectionFactory() {

        RedisClusterClient redisClient = getRedisClient();
        return new MshaLettuceConnectionFactory(redisClient, new RedisClusterConfiguration());
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(lettuceConnectionFactory);
        // 参照原有业务代码的配置项进行配置
        template.setDefaultSerializer(new StringRedisSerializer());
        return template;
    }

    private RedisClusterClient getRedisClient() {
        MetaData defaultMetaData = new RedisMetaData(null, password, primaryHost, port);
        MetaData standbyMetaData = new RedisMetaData(null, password, standbyHost, port);


        return MshaLettuceClusterClient.create(primaryNodes, password, defaultMetaData, standbyNodes, password, standbyMetaData);
    }

}
3)规范和限制

目前不支持用法:pub/sub 类命令、自建的哨兵模式服务端、基于spring-session-data-redissession。

4.3 Redisson

引入多活SDKRedisson依赖。

<dependency>
  <groupId>com.aliyun.unit.router</groupId>
  <artifactId>msha-bridge-redis-redisson</artifactId>
  <version>x.y.z</version>
</dependency>

4.3.1 单机模式

1)应用中添加主备数据库配置
spring.redis.host.primary=${主云redis的连接地址}
spring.redis.host.standby=${备云redis的连接地址}
spring.redis.port=${redis端口}
spring.redis.password=${redis密码}

2)替换原有的RedissonClient

将原有的RedissonClient 的初始化,替换为以下afterPropertiesSet()中的初始化方式。

@Configuration
public class RedissonRepository implements InitializingBean {

    @Value("${spring.redis.host.primary}")
    private String primaryHost;

    @Value("${spring.redis.host.standby}")
    private String standbyHost;

    @Value("${spring.redis.password}")
    private String password;

    @Value("${spring.redis.port}")
    private int port;

    private static RedissonClient redissonClient;

    @Override
    public void afterPropertiesSet() {
        Config defaultConfig = new Config();
        // 参照原有业务代码的配置项进行配置
        defaultConfig.useSingleServer()
        .setAddress("redis://" + primaryHost + ":" + port)
        .setPassword(password)
        .setDatabase(0); // 这里setDatabase(0)仅为示例,根据实际需求来设置

        Config standbyConfig = new Config();
        // 参照原有业务代码的配置项进行配置
        standbyConfig.useSingleServer()
        .setAddress("redis://" + standbyHost + ":" port)
        .setPassword(password)
        .setDatabase(0)// 这里setDatabase(0)仅为示例,根据实际需求来设置
        .setTimeout(20000);// 这里setTimeout(20000)仅为示例,根据实际需求来设置

        MetaData defaultMetaData = new RedisMetaData(null, password, primaryHost, port);
        MetaData standbyMetaData = new RedisMetaData(null, password, standbyHost, port);
        
        redissonClient = MshaRedissonFactory.create(defaultConfig, defaultMetaData, standbyConfig, standbyMetaData);
    }

    public static RedissonClient getRedisson() {
        return redissonClient;
    }
}
3)规范和限制
  1. 目前不支持用法:pub/sub 类命令、自建的哨兵模式服务端、基于spring-session-data-redissession。

  2. 如果在相关业务逻辑执行时间不超过30s,不能使用不带持续时间的分布式锁时(示例代码参考如下),否则在切换时可能会出现锁丢失的情况,导致业务受损。

RLock lock = redissonClient.getLock(lockName);
// 不带持续时间的锁
lock.lock();
// 持续时间为60s的锁
// lock.lock(60L, TimeUnit.SECONDS);
try {
    // 处理业务逻辑
} catch (InterruptedException e) {
    e.printStackTrace();
} finally {
    lock.unlock();
}
4)强制指定中心

引入msha-bridge-springboot-aspect依赖。

<dependency>
  <groupId>com.aliyun.unit.router</groupId>
  <artifactId>msha-bridge-springboot-aspect</artifactId>
  <version>x.y.z</version>
</dependency>

代码中使用@WithRedisCenter(value = true)指定主数据源。

@WithRedisCenter(value = true)
public Map<String, Object> get(String key) {
    // 此处为客户代码
}

4.3.2 集群模式

1)应用中添加主备数据库配置
# 用于获取控制台规则,需与控制台配置的连接地址相同
spring.redis.host.primary=${控制台配置的主云redis的连接地址}
spring.redis.host.standby=${控制台配置的备云redis的连接地址}
spring.redis.port=${控制台配置的redis端口}
# 中心机房及备机房对应的redis节点
spring.redis.host.primary.nodes=${主云节点1,格式为host:port},${主云节点2,格式为host:port},...,${主云节点n,格式为host:port}
spring.redis.host.standby.nodes=${备云节点1,格式为host:port},${备云节点2,格式为host:port},...,${备云节点n,格式为host:port}
spring.redis.password=${redis密码}

参考下图,spring.redis.host.primary、spring.redis.host.standbyspring.redis.port需与控制中填写的推送标识保持一致(一般任选一节点填写,主备云redisport需要相同)。 image

2)替换原有的RedissonClient

将原有的RedissonClient 的初始化,替换为以下afterPropertiesSet()中的初始化方式。

@Configuration
public class RedissonRepository implements InitializingBean {

    @Value("${spring.redis.host.primary}")
    private String primaryHost;

    @Value("${spring.redis.host.standby}")
    private String standbyHost;

    @Value("${spring.redis.host.primary.nodes}")
    private String primaryNodesStr;

    @Value("${spring.redis.host.standby.nodes}")
    private String standbyNodesStr;

    @Value("${spring.redis.password}")
    private String password;

    @Value("${spring.redis.port}")
    private int port;

    private static RedissonClient redissonClient;

    @Override
    public void afterPropertiesSet() {
        Config defaultConfig = new Config();
        // 参照原有业务代码的配置项进行配置
        defaultConfig.useClusterServers().addNodeAddress(ClusterNodesUtil.getNodes(primaryNodesStr)).setPassword(password);

        Config standbyConfig = new Config();
        // 参照原有业务代码的配置项进行配置
        standbyConfig.useClusterServers().addNodeAddress(ClusterNodesUtil.getNodes(standbyNodesStr)).setPassword(password);

        MetaData defaultMetaData = new RedisMetaData(null, password, primaryHost, port);
        MetaData standbyMetaData = new RedisMetaData(null, password, standbyHost, port);

        redissonClient = MshaRedissonFactory.create(defaultConfig, defaultMetaData, standbyConfig, standbyMetaData);
    }

    public static RedissonClient getRedisson() {
        return redissonClient;
    }
}
3)规范和限制
  1. 目前不支持用法:pub/sub 类命令、自建的哨兵模式服务端、基于spring-session-data-redissession。

  2. 如果在相关业务逻辑执行时间不超过30s,不能使用不带持续时间的分布式锁时(示例代码参考如下),否则在切换时可能会出现锁丢失的情况,导致业务受损。

RLock lock = redissonClient.getLock(lockName);
// 不带持续时间的锁
lock.lock();
// 持续时间为60s的锁
//lock.lock(60L, TimeUnit.SECONDS);
try {
    // 处理业务逻辑
} catch (InterruptedException e) {
    e.printStackTrace();
} finally {
    lock.unlock();
}

5. MongoDB应用改造

5.1 引入多活依赖

<dependency>
  <groupId>com.aliyun.unit.router</groupId>
  <artifactId>msha-bridge-mongo-springboot-starter</artifactId>
  <version>x.y.z-SNAPSHOT</version>
</dependency>

5.2 配置mongodb

  1. Mongodata配置使用uri模式,如果有用户使用host,port这种模式,需要用户改为uri模式。

  2. 增加standByUri,格式与uri格式一致,格式可以参考下面的示例。

重要

推送标识指定的host需要放在uri的第一位,MSHA会自动解析作为唯一key。

spring:
  application:
    name: mongo-test
  data:
    mongodb:
      uri: mongodb://${HOST1}:${PORT1},${HOST2}:${PORT2}/${DB}?replicaSet=${rp}&authSource=${DBName}
      standByUri: mongodb://${HOST1}:${PORT1},${HOST2}:${PORT2}/${DB}?replicaSet=${rp}&authSource=${DBName}

正常情况,走msha自动解析即可,下面的可以不用关注。

如果特殊情况,用户需要指定推送标识,可以增加参数masterUniqueKey(主云mongo推送标识)、standbyUniqueKey(备云mongo推送标识):

spring:
  application:
    name: mongo-test
  data:
    mongodb:
      uri: mongodb://${HOST1}:${PORT1},${HOST2}:${PORT2}/${DB}?replicaSet=${rp}&authSource=${DBName}
      standByUri: mongodb://${HOST1}:${PORT1},${HOST2}:${PORT2}/${DB}?replicaSet=${rp}&authSource=${DBName}
      masterUniqueKey: *******
      standbyUniqueKey: *******

5.3 配置mongodb注意事项

  1. Mongodata配置使用uri模式时,uri中加入用户名和密码,密码格式需要注意以下事项。

    说明

    password中不能含有%,出现百分号时会被转义,请求mongodb实例会报:“Exception authenticating MongoCredential{mechanism=SCRAM-SHA-256, userName='root', source='admin', password=<hidden>, mechanismProperties=<hidden>}”。更多信息,请参见如何解决连接串中账号密码包含特殊字符导致连接失败的问题?

  2. MSHA录入MongoDB数据源注意事项。

    image

    说明

    如上图,当MongoDB密码中还有%时,由于代码使用“mongodb://root:password@your-host:port/admin?authSource=admin”方式链接,会被当成转义符号,导致密码改变,选择数据库时报:“登录失败,请检查用户名密码是否正确。”。