全链路压测时动态路由数据源MySQL、MongoDB、Redis

这篇具有很好参考价值的文章主要介绍了全链路压测时动态路由数据源MySQL、MongoDB、Redis。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

一、全链路压测

二、动态路由Mysql

1. 参数配置application-localDynamic.yml

2. 加载配置参数DynamicDataSourceProperties.java

3. 动态数据源DynamicDataSource.java

4. 动态数据源供应DynamicDataSourceProvider.java

5. 动态数据源bean

6. 动态数据源上下文DynamicDataSourceContextHolder.java

7. 动态数据源过滤器DynamicDataSourceFilter.java

8. 测试动态路由

三、动态路由MongoDB

1. 参数配置application-localDynamicMongo.yml

2. 加载配置参数DynamicMongoSourceProperties.java

3. 动态数据源DynamicMongoSource.java

4. 动态数据源供应DynamicMongoSourceProvider.java

5. 动态数据源bean

6. 动态数据源上下文DynamicMongoSourceContextHolder.java

7. 动态数据源过滤器DynamicMongoSourceFilter.java

8. 测试动态路由

四、动态路由Redis

1. 参数配置application-localDynamicRedis.yml

2. 加载配置参数DynamicRedisSourceProperties.java

3. 动态数据源DynamicRedisSource.java

4. 动态数据源供应DynamicRedisSourceProvider.java

5. 动态数据源bean

6. 动态数据源上下文DynamicRedisSourceContextHolder.java

7. 动态数据源过滤器DynamicRedisSourceFilter.java

8. 测试动态路由

五、参考资料


一、全链路压测

        验证系统所能够承受的最大负载是否接近于预期,是否经得住大流量的冲击,绝非是一件易事。有过分布式系统开发经验的同学都应该非常清楚,简单对某个接口、子系统进行压测,并不能够准确探测出系统整体的流量水平,并且对环境有着极为严苛的要求。全链路压测就是确保大促来临时核心链路的整体稳定。

        如何在大促前夕对线上环境实施全链路压测,做到有指导的进行容量规划和性能优化。大促前最基本也是最棘手的两项关键任务:

  • 评估机器扩容数量;
  • 验证系统整体容量是否能够有效支撑所预估的流量峰值。

        首先梳理系统中的核心链路。其次算出单机最大流量。然后根据GMV(Gross Merchandise Volume _ 商品交易总额)或根据历史估计现有最大流量算出扩容的机器数量。最后压测整体系统才能暴露问题,如:慢SQL、连接资源耗尽(DB连接池连接)、加锁导致大量线程等待(排他锁、分布式锁、DB的行锁)等问题。

        线上压测具有较大的风险,绝不能出现一丝失误。虽然困难重重,但是也要测试系统的真实流量水位。首先高峰期绝对是不能压测的,安全做法低峰期时,Nginx层控制用户流量方向。以下是线上全链路压测的关键环节:

  • 如何标记流量是用户流量还是压测流量:如请求头、URL请求参数等来区分;
  • 如何将压测数据引流到隔离环境中:如同表的某字段区分、动态路由到隔离环境(本章介绍);
  • 如何构造压测数据:如自动生成数据、线上数据敏感过滤后的数据等;
  • 如何升级和改造业务系统和中间件:如定时任务、消息中间件、外部接口等;
  • 如何发起超大规模的流量:如集群Jmeter、分布式压测nGrinder等。

        压测最大的难点是压测数据如何构造和隔离。本章节主要实现动态路由MySQL、MongoDB、Redis,用户流量和压测流量同时访问一台服务器后,动态路由实现用户数据与压测数据的完全隔离,用户数据避免被污染,避免导致出现数据安全事故。

二、动态路由Mysql

        实现Mysql的动态路由,首先了解org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource类。该类是在Spring2.0.1中引入的(不是Spring Boot2.0.1), 充当了DataSource的路由中介,它能够在运行时, 根据某种key值来动态切换到真正的DataSource上

       AbstractRoutingDataSource大致逻辑是:提前准备好各种数据源,存入到一个Map中,Map的key是数据源的名字,Map的value就是具体的数据源,然后再把这个Map配置到AbstractRoutingDataSource中,最后每次执行数据库查询的时候,拿一个key出来,该类会找到具体的数据源去执行这次的数据库操作。

        MongoDB、Redis没有该类,则本人根据各自的工厂实现了动态路由类,来完成各自的切换,见下小节

1. 参数配置application-localDynamic.yml

spring:
  dynamic-data-source:
    druid:
#      dsKey: test
      ds:
        # 主库数据源,默认master不能变
        master:
          url: jdbc:mysql://${remote.ip}:3307/prod?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8
          username: root
          password: 123456
        # 压测库数据源
        test:
          url: jdbc:mysql://${remote.ip}:3307/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8
          username: root
          password: 123456
      driverClassName: com.mysql.cj.jdbc.Driver
      type: mysql
      initialSize: 10
      keepAlive: true
      minIdle: 10
      maxActive: 50
      maxWait: 60000
      timeBetweenEvictionRunsMillis: 60000
      minEvictableIdleTimeMillis: 300000
      maxEvictableIdleTimeMillis: 306000
      validationQuery: SELECT 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      filters: stat,wall,log4j
      poolPreparedStatements: false
      maxPoolPreparedStatementPerConnectionSize: -1
      useGlobalDataSourceStat: true
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=1000

2. 加载配置参数DynamicDataSourceProperties.java

package com.common.instance.demo.config.dynamicDataSource;

import com.alibaba.druid.pool.DruidDataSource;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * @author tcm
 * @version 1.0.0
 * @description 动态加载数据源配置
 * @date 2023/3/14 15:13
 **/
@Data
@Component
@ConfigurationProperties(prefix = "spring.dynamic-data-source.druid")
public class DynamicDataSourceProperties {

    private int initialSize;

    private int minIdle;

    private int maxActive;

    private int maxWait;

    private int timeBetweenEvictionRunsMillis;

    private int minEvictableIdleTimeMillis;

    private int maxEvictableIdleTimeMillis;

    private String validationQuery;

    private boolean testWhileIdle;

    private boolean testOnBorrow;

    private boolean testOnReturn;

    private Map<String, Map<String, String>> ds;

    private String dsKey;

    public DruidDataSource dataSource(DruidDataSource datasource) {
        /** 配置初始化大小、最小、最大 */
        datasource.setInitialSize(initialSize);
        datasource.setMaxActive(maxActive);
        datasource.setMinIdle(minIdle);

        /** 配置获取连接等待超时的时间 */
        datasource.setMaxWait(maxWait);

        /** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */
        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);

        /** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */
        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);

        /**
         * 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
         */
        datasource.setValidationQuery(validationQuery);
        /** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */
        datasource.setTestWhileIdle(testWhileIdle);
        /** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
        datasource.setTestOnBorrow(testOnBorrow);
        /** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
        datasource.setTestOnReturn(testOnReturn);
        return datasource;
    }

}

3. 动态数据源DynamicDataSource.java

        继承org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource类,是实现动态路由切换的核心逻辑类

package com.common.instance.demo.config.dynamicDataSource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import java.util.HashMap;
import java.util.Map;

/**
 * @author tcm
 * @version 1.0.0
 * @description 动态数据源
 * @date 2023/3/14 15:36
 **/
public class DynamicDataSource extends AbstractRoutingDataSource {

    private DynamicDataSourceProvider dynamicDataSourceProvider;

    private DynamicDataSourceProperties dynamicDataSourceProperties;

    public DynamicDataSource(DynamicDataSourceProvider dynamicDataSourceProvider, DynamicDataSourceProperties dynamicDataSourceProperties) {
        this.dynamicDataSourceProvider = dynamicDataSourceProvider;
        this.dynamicDataSourceProperties = dynamicDataSourceProperties;

        // 获取所有目标数据源
        Map<Object, Object> targetDataSources = new HashMap<>(dynamicDataSourceProvider.loadDataSources());
        super.setTargetDataSources(targetDataSources);

        // 设置默认数据源
        super.setDefaultTargetDataSource(dynamicDataSourceProvider.loadDataSources().get(DynamicDataSourceProvider.DEFAULT_DATASOURCE));
        super.afterPropertiesSet();
    }

    @Override
    protected Object determineCurrentLookupKey() {
//        return dynamicDataSourceProperties.getDsKey() == null ? DynamicDataSourceProvider.DEFAULT_DATASOURCE:dynamicDataSourceProperties.getDsKey();
        return DynamicDataSourceContextHolder.getDataSourceType();
    }

}

4. 动态数据源供应DynamicDataSourceProvider.java

package com.common.instance.demo.config.dynamicDataSource;

import javax.sql.DataSource;
import java.util.Map;

/**
 * @author tcm
 * @version 1.0.0
 * @description 动态数据源提供者接口
 * @date 2023/3/14 15:21
 **/
public interface DynamicDataSourceProvider {

    // 默认数据源
    String DEFAULT_DATASOURCE = "master";
    /**
     * 加载所有的数据源
     * @return
     */
    Map<String, DataSource> loadDataSources();

}
package com.common.instance.demo.config.dynamicDataSource;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import lombok.Data;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author tcm
 * @version 1.0.0
 * @description 动态数据源配置
 * @date 2023/3/17 9:15
 **/
@Data
@Configuration
@EnableConfigurationProperties(DynamicDataSourceProperties.class)
public class YamlDynamicDataSourceProvider implements DynamicDataSourceProvider {

    @Resource
    private DynamicDataSourceProperties dynamicDataSourceProperties;

    @Override
    public Map<String, DataSource> loadDataSources() {
        Map<String, DataSource> ds = new HashMap<>(dynamicDataSourceProperties.getDs().size());
        try {
            Map<String, Map<String, String>> map = dynamicDataSourceProperties.getDs();
            Set<String> keySet = map.keySet();
            for (String s : keySet) {
                DruidDataSource dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(map.get(s));
                ds.put(s, dynamicDataSourceProperties.dataSource(dataSource));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ds;
    }

}

5. 动态数据源bean

package com.common.instance.demo.config.dynamicDataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;
import javax.sql.DataSource;

/**
 * @author tcm
 * @version 1.0.0
 * @description 数据源配置
 * @date 2023/3/15 17:18
 **/
@Configuration
public class DataSourceBean {

    @Resource
    private DynamicDataSourceProperties dynamicDataSourceProperties;
    @Resource
    private DynamicDataSourceProvider dynamicDataSourceProvider;

    @Bean
    public DataSource getDataSource() {
        return new DynamicDataSource(dynamicDataSourceProvider, dynamicDataSourceProperties);
    }

}

6. 动态数据源上下文DynamicDataSourceContextHolder.java

        作用是:InheritableThreadLocal继承类来缓存路由类型(压测或用户流量),异步线程可以继承该缓存。

package com.common.instance.demo.config.dynamicDataSource;


import com.log.util.LogUtil;

/**
 * @author tcm
 * @version 1.0.0
 * @description 动态数据源上下文
 * @date 2023/3/15 11:16
 **/
public class DynamicDataSourceContextHolder {

    /**
     * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
     * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
     */
    private static final ThreadLocal<String> CONTEXT_HOLDER = new InheritableThreadLocal<>();

    /**
     * 设置数据源的变量
     */
    public static void setDataSourceType(String dsType) {
        LogUtil.info(String.format("切换到%s数据源", dsType));
        CONTEXT_HOLDER.set(dsType);
    }

    /**
     * 获得数据源的变量
     */
    public static String getDataSourceType() {
        return CONTEXT_HOLDER.get();
    }

    /**
     * 清空数据源变量
     */
    public static void clearDataSourceType() {
        CONTEXT_HOLDER.remove();
    }

}

7. 动态数据源过滤器DynamicDataSourceFilter.java

        该类的作用:使用请求头来标记是用户还是压测,若是压测则切换到压测环境Mysql库。

package com.common.instance.demo.config.dynamicDataSource;

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author tcm
 * @version 1.0.0
 * @description 动态数据源过滤器
 * @date 2023/3/15 15:07
 **/
@Component
@WebFilter(filterName = "dynamicDataSourceFilter", urlPatterns = "/*")
@Order(-10)
public class DynamicDataSourceFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 获取测试标记
        String testFlag = request.getHeader("Test-Flag");

        // 如有测试标记,则设置测试数据源
        if (testFlag != null ) {
            DynamicDataSourceContextHolder.setDataSourceType("test");
        }

        // 添加到过滤链中
        filterChain.doFilter(request, response);
    }

}

8. 测试动态路由

// 路由生产mysql
curl --request POST \
  --url http://127.0.0.1:9013/instance-demo/dynamicDataSource/insertData \
  --header 'content-type: application/json' \
  --data '{
    "tabId":"fde7c1d4cad049c89612afb6c2c2979",
    "transactionId":"7F0000013B2818B4AAC22CE1BDA20004"
}'


// 路由测试mysql
curl --request POST \
  --url http://127.0.0.1:9013/instance-demo/dynamicDataSource/insertData \
  --header 'Test-Flag: true' \
  --header 'content-type: application/json' \
  --data '{
    "tabId":"fde7c1d4cad049c89612afb6c2c2979",
    "transactionId":"7F0000013B2818B4AAC22CE1BDA20004-t"
}'

全链路压测时动态路由数据源MySQL、MongoDB、Redis

三、动态路由MongoDB

        动态路由原理参考Mysql动态路由。自定义抽象路由工厂AbstractRoutingMongoSource,根据key路由到不同的MongoDB数据源。把路由工厂实现类DynamicMongoSource注入到MongoTemplate中,实现路由切换。

1. 参数配置application-localDynamicMongo.yml

spring:
  dynamic-mongo-source:
#    dsKey: test
    ds:
      # 主库数据源,默认master不能变
      master:
        hosts: ${remote.ip}
        ports: 27018
        database: demo
        username: admin
        password: 123456
        authentication-database: admin
        connections-per-host: 100
        min-connections-per-host: 1
        maxConnectionIdleTime: 150000
        maxConnectionLifeTime: 150000
        connectTimeout: 6000
        socketTimeout: 10000
      # 压测库数据源
      test:
        hosts: ${remote.ip}
        ports: 27018
        database: test
        username: admin
        password: 123456
        authentication-database: admin
        connections-per-host: 100
        min-connections-per-host: 1
        maxConnectionIdleTime: 150000
        maxConnectionLifeTime: 150000
        connectTimeout: 6000
        socketTimeout: 10000

2. 加载配置参数DynamicMongoSourceProperties.java

package com.common.instance.demo.config.dynamicMongoSource;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * @author tcm
 * @version 1.0.0
 * @description Mongo动态加载数据源配置
 * @date 2023/3/17 9:21
 **/
@Data
@Component
@ConfigurationProperties(prefix = "spring.dynamic-mongo-source")
public class DynamicMongoSourceProperties {

    private Map<String, MongoSettingsProperties> ds;
    private String dsKey;

}
package com.common.instance.demo.config.dynamicMongoSource;

import lombok.Data;
import org.hibernate.validator.constraints.NotBlank;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.validation.annotation.Validated;

import java.util.List;

/**
 * @author tcm
 * @version 1.0.0
 * @description Mongo配置
 * @date 2023/3/17 9:14
 **/
@Validated
@Data
public class MongoSettingsProperties {

    @NotBlank
    private String database;

    @NotEmpty
    private List<String> hosts;

    @NotEmpty
    private List<Integer> ports;

    private String replicaSet;
    private String username;
    private String password;
    private String authenticationDatabase;
    private Integer minConnectionsPerHost = 10;
    private Integer connectionsPerHost = 20;
    private Integer maxConnectionIdleTime;
    private Integer maxConnectionLifeTime;
    private Integer connectTimeout;
    private Integer socketTimeout;

}

3. 动态数据源DynamicMongoSource.java

        自定义抽象路由工厂AbstractRoutingMongoSource,根据key路由到不同的MongoDB数据源。把路由工厂实现类DynamicMongoSource注入到MongoTemplate中,实现路由切换。

package com.common.instance.demo.config.dynamicMongoSource;

import com.common.instance.demo.config.dynamicDataSource.DynamicDataSourceProvider;
import org.springframework.data.mongodb.MongoDatabaseFactory;

import java.util.HashMap;
import java.util.Map;

/**
 * @author tcm
 * @version 1.0.0
 * @description Mongo动态数据源
 * @date 2023/3/17 11:22
 **/
public class DynamicMongoSource extends AbstractRoutingMongoSource {

    private DynamicMongoSourceProvider dynamicMongoSourceProvider;

    private DynamicMongoSourceProperties dynamicMongoSourceProperties;

    public DynamicMongoSource(DynamicMongoSourceProvider dynamicMongoSourceProvider, DynamicMongoSourceProperties dynamicMongoSourceProperties) {
        this.dynamicMongoSourceProvider = dynamicMongoSourceProvider;
        this.dynamicMongoSourceProperties = dynamicMongoSourceProperties;

        // 获取所有目标数据源
        Map<String, MongoDatabaseFactory> targetMongoSources = new HashMap<>(dynamicMongoSourceProvider.loadMongoSources());
        super.setTargetMongoSources(targetMongoSources);

        // 设置默认数据源
        super.setDefaultTargetMongoSource(dynamicMongoSourceProvider.loadMongoSources().get(DynamicDataSourceProvider.DEFAULT_DATASOURCE));
    }

    @Override
    protected Object determineCurrentLookupKey() {
//        return dynamicMongoSourceProperties.getDsKey() == null ? DynamicMongoSourceProvider.DEFAULT_DATASOURCE:dynamicMongoSourceProperties.getDsKey();
        return DynamicMongoSourceContextHolder.getMongoSourceType();
    }


}
package com.common.instance.demo.config.dynamicMongoSource;

import com.mongodb.ClientSessionOptions;
import com.mongodb.client.ClientSession;
import com.mongodb.client.MongoDatabase;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.mongodb.MongoDatabaseFactory;

import java.util.Map;

/**
 * @author tcm
 * @version 1.0.0
 * @description MongoDB路由抽象类
 * @date 2023/3/16 17:39
 **/
public abstract class AbstractRoutingMongoSource implements MongoDatabaseFactory {

    private Map<String, MongoDatabaseFactory> targetMongoSources;

    private MongoDatabaseFactory defaultTargetMongoSource;

    public void setTargetMongoSources(Map<String, MongoDatabaseFactory> targetMongoSources) {
        this.targetMongoSources = targetMongoSources;
    }

    public void setDefaultTargetMongoSource(MongoDatabaseFactory defaultTargetMongoSource) {
        this.defaultTargetMongoSource = defaultTargetMongoSource;
    }

    protected MongoDatabaseFactory determineTargetMongoSource() {
        if (this.targetMongoSources == null) {
            throw new IllegalArgumentException("Property 'targetMongoSources' is required");
        }
        Object lookupKey = determineCurrentLookupKey();
        MongoDatabaseFactory mongoSource = this.targetMongoSources.get(lookupKey);
        if (mongoSource == null && lookupKey == null) {
            mongoSource = this.defaultTargetMongoSource;
        }
        if (mongoSource == null) {
            throw new IllegalStateException("Cannot determine target MongoTemplate for lookup key [" + lookupKey + "]");
        }
        return mongoSource;
    }

    protected abstract Object determineCurrentLookupKey();

    @Override
    public MongoDatabase getMongoDatabase() throws DataAccessException {
        return determineTargetMongoSource().getMongoDatabase();
    }

    @Override
    public MongoDatabase getMongoDatabase(String s) throws DataAccessException {
        return determineTargetMongoSource().getMongoDatabase(s);
    }

    @Override
    public PersistenceExceptionTranslator getExceptionTranslator() {
        return null;
    }

    @Override
    public ClientSession getSession(ClientSessionOptions clientSessionOptions) {
        return determineTargetMongoSource().getSession(clientSessionOptions);
    }

    @Override
    public MongoDatabaseFactory withSession(ClientSession clientSession) {
        return determineTargetMongoSource().withSession(clientSession);
    }
}

4. 动态数据源供应DynamicMongoSourceProvider.java

package com.common.instance.demo.config.dynamicMongoSource;

import org.springframework.data.mongodb.MongoDatabaseFactory;

import java.util.Map;

/**
 * @author tcm
 * @version 1.0.0
 * @description Mongo动态数据源提供者接口
 * @date 2023/3/17 9:14
 **/
public interface DynamicMongoSourceProvider {

    // 默认数据源
    String DEFAULT_DATASOURCE = "master";

    /**
     * 加载所有的数据源
     * @return
     */
    Map<String, MongoDatabaseFactory> loadMongoSources();

}
package com.common.instance.demo.config.dynamicMongoSource;

import com.mongodb.*;
import com.mongodb.client.MongoClient;
import com.mongodb.client.internal.MongoClientImpl;
import lombok.Data;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author tcm
 * @version 1.0.0
 * @description Mongo动态数据源配置
 * @date 2023/3/17 9:15
 **/
@Data
@Configuration
@EnableConfigurationProperties(DynamicMongoSourceProperties.class)
public class YamlDynamicMongoSourceProvider implements DynamicMongoSourceProvider {

    @Resource
    private DynamicMongoSourceProperties dynamicMongoSourceProperties;

    @Override
    public Map<String, MongoDatabaseFactory> loadMongoSources() {
        Map<String, MongoDatabaseFactory> ds = new HashMap<>(dynamicMongoSourceProperties.getDs().size());
        try {
            Map<String, MongoSettingsProperties> map = dynamicMongoSourceProperties.getDs();
            Set<String> keySet = map.keySet();
            for (String s : keySet) {
                MongoSettingsProperties mongoSettingsProperties = map.get(s);

                ds.put(s, getMongoFactory(mongoSettingsProperties));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ds;
    }

    private MongoDatabaseFactory getMongoFactory(MongoSettingsProperties mongoSettingsProperties) {
        // MongoDB地址列表
        List<ServerAddress> serverAddresses = new ArrayList<>();
        for (String host : mongoSettingsProperties.getHosts()) {
            int index = mongoSettingsProperties.getHosts().indexOf(host);
            Integer port = mongoSettingsProperties.getPorts().get(index);
            ServerAddress serverAddress = new ServerAddress(host, port);
            serverAddresses.add(serverAddress);
        }

        // 连接认证
        MongoCredential credential = MongoCredential.createCredential(mongoSettingsProperties.getUsername(),
                mongoSettingsProperties.getAuthenticationDatabase(),
                mongoSettingsProperties.getPassword().toCharArray());

        MongoDriverInformation info = MongoDriverInformation.builder().build();

        MongoClientSettings build = MongoClientSettings.builder()
                .applyToClusterSettings(builder -> builder.hosts(serverAddresses))
                .applyToConnectionPoolSettings(builder -> builder
                        .maxConnectionIdleTime(mongoSettingsProperties.getMaxConnectionIdleTime(), TimeUnit.MILLISECONDS)
                        .maxConnectionLifeTime(mongoSettingsProperties.getMaxConnectionLifeTime(), TimeUnit.MILLISECONDS))
                .credential(credential).build();

        // 创建客户端和Factory
        MongoClient mongoClient = new MongoClientImpl(build, info);

        return new SimpleMongoClientDatabaseFactory(mongoClient, mongoSettingsProperties.getDatabase());
    }

}

5. 动态数据源bean

package com.common.instance.demo.config.dynamicMongoSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;

import javax.annotation.Resource;

/**
 * @author tcm
 * @version 1.0.0
 * @description Mongo数据源配置
 * @date 2023/3/21 10:08
 **/
@Configuration
public class MongoSourceBean {

    @Resource
    private DynamicMongoSourceProperties dynamicMongoSourceProperties;
    @Resource
    private DynamicMongoSourceProvider dynamicMongoSourceProvider;

    @Bean
    public MongoTemplate getMongoSource() {
        // 动态数据源配置
        DynamicMongoSource dynamicMongoSource = new DynamicMongoSource(dynamicMongoSourceProvider, dynamicMongoSourceProperties);

        // 插入数据时,去除class字段
        DefaultDbRefResolver dbRefResolver = new DefaultDbRefResolver(dynamicMongoSource);
        MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, new MongoMappingContext());
        converter.setTypeMapper(new DefaultMongoTypeMapper(null));
        converter.setMapKeyDotReplacement("。");

        return new MongoTemplate(dynamicMongoSource, converter);
    }

}

6. 动态数据源上下文DynamicMongoSourceContextHolder.java

package com.common.instance.demo.config.dynamicMongoSource;

import com.log.util.LogUtil;

/**
 * @author tcm
 * @version 1.0.0
 * @description Mongo动态数据源上下文
 * @date 2023/3/21 14:16
 **/
public class DynamicMongoSourceContextHolder {

    /**
     * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
     * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
     */
    private static final ThreadLocal<String> CONTEXT_MONGO_HOLDER = new InheritableThreadLocal<>();

    /**
     * 设置数据源的变量
     */
    public static void setMongoSourceType(String dsType) {
        LogUtil.info(String.format("mongo切换到%s数据源", dsType));
        CONTEXT_MONGO_HOLDER.set(dsType);
    }

    /**
     * 获得数据源的变量
     */
    public static String getMongoSourceType() {
        return CONTEXT_MONGO_HOLDER.get();
    }

    /**
     * 清空数据源变量
     */
    public static void clearMongoSourceType() {
        CONTEXT_MONGO_HOLDER.remove();
    }

}

7. 动态数据源过滤器DynamicMongoSourceFilter.java

package com.common.instance.demo.config.dynamicMongoSource;

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author tcm
 * @version 1.0.0
 * @description Mongo动态数据源过滤器
 * @date 2023/3/21 14:17
 **/
@Component
@WebFilter(filterName = "dynamicMongoSourceFilter", urlPatterns = "/*")
@Order(-10)
public class DynamicMongoSourceFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 获取测试标记
        String testFlag = request.getHeader("Test-Mongo-Flag");

        // 如有测试标记,则设置测试数据源
        if (testFlag != null ) {
            DynamicMongoSourceContextHolder.setMongoSourceType("test");
        }

        // 添加到过滤链中
        filterChain.doFilter(request, response);
    }

}

8. 测试动态路由

// 路由生产mongo
curl --request POST \
  --url http://127.0.0.1:9013/instance-demo/test/mongodb/listAll

// 路由测试mongo
curl --request POST \
  --url http://127.0.0.1:9013/instance-demo/test/mongodb/listAll \
  --header 'Test-Mongo-Flag: true'

四、动态路由Redis

        动态路由原理参考Mysql动态路由。自定义抽象路由工厂AbstractRoutingRedisSource,根据key路由到不同的redis数据源。把路由工厂实现类DynamicRedisSource注入到RedisTemplate中,实现路由切换。

1. 参数配置application-localDynamicRedis.yml

spring:
  dynamic-redis-source:
#    dsKey: test
    ds:
      # 主库数据源,默认master不能变
      master:
        address: redis://127.0.0.1:6379
        database: 1
        password: abcdef
      # 压测库数据源
      test:
        address: redis://127.0.0.1:6379
        database: 0
        password: abcdef
    config:
      singleServerConfig:
        # 如果当前连接池里的连接数量超过了最小空闲连接数,而同时有连接空闲时间超过了该数值,那么这些连接将会自动被关闭,并从连接池里去掉。时间单位是毫秒。
        idleConnectionTimeout: 10000
        pingTimeout: 1000
        # 同任何节点建立连接时的等待超时。时间单位是毫秒。
        connectTimeout: 10000
        # 等待节点回复命令的时间。该时间从命令发送成功时开始计时。
        timeout: 3000
        # 如果尝试达到 retryAttempts(命令失败重试次数) 仍然不能将命令发送至某个指定的节点时,将抛出错误。如果尝试在此限制之内发送成功,则开始启用 timeout(命令等待超时) 计时。
        retryAttempts: 3
        # 在一条命令发送失败以后,等待重试发送的时间间隔。时间单位是毫秒。
        retryInterval: 1500
        # 当与某个节点的连接断开时,等待与其重新建立连接的时间间隔。时间单位是毫秒。
        reconnectionTimeout: 3000
        # 在某个节点执行相同或不同命令时,连续 失败 failedAttempts(执行失败最大次数) 时,该节点将被从可用节点列表里清除,直到 reconnectionTimeout(重新连接时间间隔) 超时以后再次尝试。
        failedAttempts: 3
        # 在Redis节点里显示的客户端名称
        clientName: null
        # 发布和订阅连接的最小空闲连接数 默认1
        subscriptionConnectionMinimumIdleSize: 1
        # 发布和订阅连接池大小 默认50
        subscriptionConnectionPoolSize: 100
        # 单个连接最大订阅数量 默认5
        subscriptionsPerConnection: 5
        # 最小空闲连接数,默认值:10,最小保持连接数(长连接)
        connectionMinimumIdleSize: 12
        # 连接池最大容量。默认值:64;连接池的连接数量自动弹性伸缩
        connectionPoolSize: 64
      # 这个线程池数量被所有RTopic对象监听器,RRemoteService调用者和RExecutorService任务共同共享。
      # 默认值: 当前处理核数量 * 2
      # threads: 4
      ## 这个线程池数量是在一个Redisson实例内,被其创建的所有分布式数据类型和服务,以及底层客户端所一同共享的线程池里保存的线程数量。
      ## 默认值: 当前处理核数量 * 2
      # nettyThreads: 0
      # 编码 默认值: org.redisson.codec.JsonJacksonCodec  Redisson的对象编码类是用于将对象进行序列化和反序列化,以实现对该对象在Redis里的读取和存储。
#      codec: !<org.redisson.codec.JsonJacksonCodec> { }
      # 传输模式 默认值:TransportMode.NIO
      transportMode: NIO


2. 加载配置参数DynamicRedisSourceProperties.java

package com.common.instance.demo.config.dynamicRedisSource;

import lombok.Data;
import org.redisson.config.Config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * @author TCM
 * @version 1.0
 * @description 动态加载数据源配置
 * @date 2023/3/25 12:40
 **/
@Data
@Component
@ConfigurationProperties(prefix = "spring.dynamic-redis-source")
public class DynamicRedisSourceProperties {

    private Config config;

    private Map<String, Map<String, String>> ds;

    private String dsKey;

}

3. 动态数据源DynamicRedisSource.java

        自定义抽象路由工厂AbstractRoutingRedisSource,根据key路由到不同的redis数据源。把路由工厂实现类DynamicRedisSource注入到RedisTemplate中,实现路由切换。

package com.common.instance.demo.config.dynamicRedisSource;

import org.redisson.spring.data.connection.RedissonConnectionFactory;

import java.util.HashMap;
import java.util.Map;

/**
 * @author TCM
 * @version 1.0
 * @description Redis动态数据源
 * @date 2023/3/25 13:32
 **/
public class DynamicRedisSource extends AbstractRoutingRedisSource {
    private DynamicRedisSourceProvider dynamicMongoSourceProvider;

    private DynamicRedisSourceProperties dynamicRedisSourceProperties;

    public DynamicRedisSource(DynamicRedisSourceProvider dynamicMongoSourceProvider, DynamicRedisSourceProperties dynamicRedisSourceProperties) {
        try {
            this.dynamicMongoSourceProvider = dynamicMongoSourceProvider;
            this.dynamicRedisSourceProperties = dynamicRedisSourceProperties;

            // 获取所有目标数据源
            Map<String, RedissonConnectionFactory> targetRedisSources = new HashMap<>(dynamicMongoSourceProvider.loadRedisSources());
            super.setTargetRedisSources(targetRedisSources);

            // 设置默认数据源
            super.setDefaultTargetRedisSource(dynamicMongoSourceProvider.loadRedisSources().get(DynamicRedisSourceProvider.DEFAULT_DATASOURCE));
            super.afterPropertiesSet();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected Object determineCurrentLookupKey() {
//        return dynamicRedisSourceProperties.getDsKey() == null ? DynamicRedisSourceProvider.DEFAULT_DATASOURCE:dynamicRedisSourceProperties.getDsKey();
        return DynamicRedisSourceContextHolder.getRedisSourceType();
    }

}
package com.common.instance.demo.config.dynamicRedisSource;

import org.redisson.spring.data.connection.RedissonConnectionFactory;
import org.springframework.data.redis.connection.RedisClusterConnection;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisSentinelConnection;

import java.util.Map;

/**
 * @author TCM
 * @version 1.0
 * @description Redis路由抽象类
 * @date 2023/3/25 13:18
 **/
public abstract class AbstractRoutingRedisSource extends RedissonConnectionFactory {

    private Map<String, RedissonConnectionFactory> targetRedisSources;

    private RedissonConnectionFactory defaultTargetRedisSource;

    public void setTargetRedisSources(Map<String, RedissonConnectionFactory> targetRedisSources) {
        this.targetRedisSources = targetRedisSources;
    }

    public void setDefaultTargetRedisSource(RedissonConnectionFactory defaultTargetRedisSource) {
        this.defaultTargetRedisSource = defaultTargetRedisSource;
    }

    protected RedisConnectionFactory determineTargetRedisSource() {
        if (this.defaultTargetRedisSource == null) {
            throw new IllegalArgumentException("Property 'defaultTargetRedisSource' is required");
        }
        Object lookupKey = determineCurrentLookupKey();
        RedissonConnectionFactory redisSource = this.targetRedisSources.get(lookupKey);
        if (redisSource == null && lookupKey == null) {
            redisSource = this.defaultTargetRedisSource;
        }
        if (redisSource == null) {
            throw new IllegalStateException("Cannot determine target RedissonTemplate for lookup key [" + lookupKey + "]");
        }
        return redisSource;
    }

    protected abstract Object determineCurrentLookupKey();

    @Override
    public RedisConnection getConnection() {
        return determineTargetRedisSource().getConnection();
    }

    @Override
    public RedisClusterConnection getClusterConnection() {
        return determineTargetRedisSource().getClusterConnection();
    }

    @Override
    public RedisSentinelConnection getSentinelConnection() {
        return determineTargetRedisSource().getSentinelConnection();
    }

}

4. 动态数据源供应DynamicRedisSourceProvider.java

package com.common.instance.demo.config.dynamicRedisSource;

import org.redisson.spring.data.connection.RedissonConnectionFactory;

import java.util.Map;

/**
 * @author tcm
 * @version 1.0.0
 * @description Redis动态数据源提供者接口
 * @date 2023/3/25 12:51
 **/
public interface DynamicRedisSourceProvider {

    // 默认数据源
    String DEFAULT_DATASOURCE = "master";

    /**
     * 加载所有的数据源
     * @return
     */
    Map<String, RedissonConnectionFactory> loadRedisSources();

}
package com.common.instance.demo.config.dynamicRedisSource;

import lombok.Data;
import org.apache.logging.log4j.util.Strings;
import org.redisson.Redisson;
import org.redisson.config.Config;
import org.redisson.spring.data.connection.RedissonConnectionFactory;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author TCM
 * @version 1.0
 * @description Redis动态数据源配置
 * @date 2023/3/25 12:54
 **/
@Data
@Configuration
@EnableConfigurationProperties(DynamicRedisSourceProperties.class)
public class YamlDynamicRedisSourceProvider implements DynamicRedisSourceProvider {

    @Resource
    private DynamicRedisSourceProperties dynamicRedisSourceProperties;

    @Override
    public Map<String, RedissonConnectionFactory> loadRedisSources() {
        Map<String, RedissonConnectionFactory> ds = new HashMap<>(dynamicRedisSourceProperties.getDs().size());
        try {
            Map<String, Map<String, String>> map = dynamicRedisSourceProperties.getDs();
            Set<String> keySet = map.keySet();
            for (String s : keySet) {
                ds.put(s, getRedisFactory(s));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ds;
    }

    private RedissonConnectionFactory getRedisFactory(String key) {
        Config config = dynamicRedisSourceProperties.getConfig();

        config.useSingleServer().setAddress(dynamicRedisSourceProperties.getDs().get(key).get("address"));
        config.useSingleServer().setPassword(Strings.EMPTY.equals(dynamicRedisSourceProperties.getDs().get(key).get("password")) ? null:dynamicRedisSourceProperties.getDs().get(key).get("password"));
        config.useSingleServer().setDatabase(Integer.parseInt(dynamicRedisSourceProperties.getDs().get(key).get("database")));

        return new RedissonConnectionFactory(Redisson.create(config));
    }

}

5. 动态数据源bean

package com.common.instance.demo.config.dynamicRedisSource;

import com.aliyun.openservices.shade.com.alibaba.fastjson.parser.ParserConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import javax.annotation.Resource;

/**
 * @author TCM
 * @version 1.0
 * @description Redis数据源配置
 * @date 2023/3/25 13:40
 **/
@Configuration
public class RedisSourceBean {

    @Resource
    private DynamicRedisSourceProvider dynamicRedisSourceProvider;
    @Resource
    private DynamicRedisSourceProperties dynamicRedisSourceProperties;

    @Bean("dynamicRedisTemplate")
    public RedisTemplate<String, Object> redisTemplate() {
        DynamicRedisSource dynamicRedisSource = new DynamicRedisSource(dynamicRedisSourceProvider, dynamicRedisSourceProperties);

        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        // 全局开启AutoType,不建议使用
        ParserConfig.getGlobalInstance().setAutoTypeSupport(false);
        // 建议使用这种方式,小范围指定白名单
        ParserConfig.getGlobalInstance().addAccept("com.common");
        // value 值的序列化采用 fastJsonRedisSerializer
        FastJson2JsonRedisSerializer fastJsonRedisSerializer = new FastJson2JsonRedisSerializer(Object.class);
        // key 的序列化采用 StringRedisSerializer
        RedisSerializer stringSerializer = new StringRedisSerializer();
        template.setKeySerializer(stringSerializer);
        template.setHashKeySerializer(stringSerializer);
        template.setValueSerializer(fastJsonRedisSerializer);
        template.setHashValueSerializer(fastJsonRedisSerializer);
        template.setConnectionFactory(dynamicRedisSource);
        template.afterPropertiesSet();
        return template;
    }

}
package com.common.instance.demo.config.dynamicRedisSource;

import com.alibaba.fastjson.JSON;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;

import java.nio.charset.Charset;

/**
 * @description 自定义redis对象序列化
 * @author tcm
 * @version 1.0.0
 * @date 2021/5/26 17:59
 **/
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {

    private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private Class<T> clazz;

    public FastJson2JsonRedisSerializer(Class<T> clazz) {
        super();
        this.clazz = clazz;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException {
        if (t == null) {
            return new byte[0];
        }
        return JSON.toJSONString(t).getBytes(DEFAULT_CHARSET);
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length <= 0) {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);
        return (T) JSON.parseObject(str, clazz);
    }

}

6. 动态数据源上下文DynamicRedisSourceContextHolder.java

package com.common.instance.demo.config.dynamicRedisSource;

import com.log.util.LogUtil;

/**
 * @author TCM
 * @version 1.0
 * @description Redis动态数据源上下文
 * @date 2023/3/25 13:37
 **/
public class DynamicRedisSourceContextHolder {

    /**
     * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
     * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
     */
    private static final ThreadLocal<String> CONTEXT_REDIS_HOLDER = new InheritableThreadLocal<>();

    /**
     * 设置数据源的变量
     */
    public static void setRedisSourceType(String dsType) {
        LogUtil.info(String.format("redis切换到%s数据源", dsType));
        CONTEXT_REDIS_HOLDER.set(dsType);
    }

    /**
     * 获得数据源的变量
     */
    public static String getRedisSourceType() {
        return CONTEXT_REDIS_HOLDER.get();
    }

    /**
     * 清空数据源变量
     */
    public static void clearRedisSourceType() {
        CONTEXT_REDIS_HOLDER.remove();
    }

}

7. 动态数据源过滤器DynamicRedisSourceFilter.java

package com.common.instance.demo.config.dynamicRedisSource;

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author TCM
 * @version 1.0
 * @description Redis动态数据源过滤器
 * @date 2023/3/26 18:00
 **/
@Component
@WebFilter(filterName = "dynamicRedisSourceFilter", urlPatterns = "/*")
@Order(-10)
public class DynamicRedisSourceFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 获取测试标记
        String testFlag = request.getHeader("Test-Redis-Flag");

        // 如有测试标记,则设置测试数据源
        if (testFlag != null ) {
            DynamicRedisSourceContextHolder.setRedisSourceType("test");
        }

        // 添加到过滤链中
        filterChain.doFilter(request, response);
    }

}

8. 测试动态路由

// 路由生产redis
curl --request GET \
  --url 'http://localhost:9013/instance-demo/lua/luaScript?keys=sku10&num=78'


// 路由测试redis
curl --request GET \
  --url 'http://localhost:9013/instance-demo/lua/luaScript?keys=sku10&num=50' \
  --header 'Test-Redis-Flag: true'

全链路压测时动态路由数据源MySQL、MongoDB、Redis

五、参考资料

https://www.cnblogs.com/shih945/p/16650481.html

https://www.cnblogs.com/heyouxin/p/15119341.html文章来源地址https://www.toymoban.com/news/detail-412968.html

到了这里,关于全链路压测时动态路由数据源MySQL、MongoDB、Redis的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 利用 Mybatis-Plus 的动态数据源实现多数据源配置

    目录 一、导入依赖 二、Application.yaml配置文件 三、切换数据源 四、其他方法 4.1 配置多个数据源 4.2 定义Datasource和EntityManager 4.3 在需要使用数据源的地方注入不同的EntityManager 官网:https://baomidou.com/pages/a61e1b/#dynamic-datasource 默认是使用配置文件中master参数设置的数据库。

    2024年02月13日
    浏览(43)
  • SpringBoot动态切换数据源

      Spring提供一个DataSource实现类用于动态切换数据源—— AbstractRoutingDataSource pom.xml 大概的项目结构 注意:这两个事务管理器,并不能处理分布式事务 链接:https://pan.baidu.com/s/1ymxeKYkI-cx7b5nTQX0KWQ  提取码:6bii  --来自百度网盘超级会员V4的分享                

    2024年02月06日
    浏览(51)
  • Springboot 多数据源 dynamic-datasource动态添加移除数据源

    上一篇文章我们讲了如何通过多数据源组件,在Spring boot Druid 连接池项目中配置多数据源,并且通过@DS注解的方式切换数据源,《Spring Boot 配置多数据源【最简单的方式】》。但是在多租户的业务场景中,我们通常需要手动的切换数据源,那么本文将解答你的额疑惑。 dynam

    2024年02月13日
    浏览(56)
  • springMvc项目如何配置动态数据源

    具体文件引入和结构体参考另一章连接

    2024年01月21日
    浏览(33)
  • mybatisplus快速实现动态数据源切换

    1.背景   通常一个系统只需要连接一个数据库就可以了。但是在企业应用的开发中往往会和其他子系统交互,特别是对于一些数据实时性要求比较高的数据,我们就需要做实时连接查询,而不是做同步。这个时候就需要用到多数据源。   举个简单的例子某企业要做订单网上订

    2024年02月06日
    浏览(47)
  • Spring动态添加数据源(全自动)

    一、使用场景 1、现在网上所有写的动态数据源,都是假动态数据,都是预先知道要连接几个数据库,但这不满足具体真正需要动态添加连接数据库的需求; 2、在很多业务场景下,如项目默认连一个主数据库,服务启动后,因业务不停的变更,需要动态连接其它数据库处理数

    2024年02月01日
    浏览(53)
  • Mybatis-plus动态数据源

    由于服务没有做微服务部署,需要在后台管理系统访问其他服务的库,所以需要用到动态数据源切换 引入依赖 mybatis-plus动态数据源依赖 更改配置 配置类 添加注解 @DS注解我一般放在dao层,因为觉得这样更合理 启动测试 问题: 动态数据源切换时效 当服务层接口添加事务注解

    2024年04月12日
    浏览(52)
  • springboot使用DynamicDataSource来动态切换数据源

    DynamicDataSource是一个数据源路由器,可以根据上下文动态选择数据源。可以在每个请求或线程中将数据源设置为当前需要使用的数据. 创建一个 DynamicDataSource 类,它继承自 AbstractRoutingDataSource 。在该类中重写**determineCurrentLookupKey()**方法,该方法返回一个字符串,用于指示当前

    2024年02月05日
    浏览(45)
  • 【Java】Spring Boot配置动态数据源

    1.1 创建动态数据源 通过实现Spring提供的AbstractRoutingDataSource类,可以实现自己的数据源选择逻辑,从而可以实现数据源的动态切换。 1.2 创建动态数据源配置类 跟配置静态多数据源一样,需要手动配置下面的三个 Bean,只不过DynamicDataSource类的targetDataSources是空的。 1.3 创建动

    2024年02月09日
    浏览(52)
  • 实例讲解Spring boot动态切换数据源

    摘要: 本文模拟一下在主库查询订单信息查询不到的时候,切换数据源去历史库里面查询。 本文分享自华为云社区《springboot动态切换数据源》,作者:小陈没烦恼 。 在公司的系统里,由于数据量较大,所以配置了多个数据源,它会根据用户所在的地区去查询那一个数据库

    2024年02月06日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包