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

news2024/11/28 4:52:20

目录

一、全链路压测

二、动态路由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"
}'

三、动态路由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'

五、参考资料

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

https://www.cnblogs.com/heyouxin/p/15119341.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/423382.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PWN-ret2shellcode原理

我们之前做过很简单的pwn题目 buuctf-rip这种 是在程序中存在shellcode 直接返回地址改为这个shellcode的地址即可 但是如果程序里面没有呢 这种类型就是ret2shellcode 常见的shellcode shellcode "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5…

一起读源码 —— Fastjson 的核心方法及其实现原理

源码介绍 Fastjson 是阿里巴巴开源的一个 Java 工具库&#xff0c;它常常被用来完成 Java 的对象与 JSON 格式的字符串的相互转化。 此文读的源码是撰写此文时 Fastjson 的最新的发布版本&#xff0c;即 1.2.83 下载源码 请前去 github 找到 release 最新版下载后解压&…

智慧水务之排水系统物联网监测

1.1排水系统 1.1.1监测范围选择依据 &#xff08;1&#xff09;管网老化、设计标准低、合流制管网区域 管网建设年代久远&#xff0c;通常管网发生破损问题较大&#xff1b;管网设计标准较低&#xff0c;易引发淤堵或溢流&#xff1b;合流制管网受天气影响大&#xff0c;会对…

FFMPEG: [ API ] >打开/关闭一个输入文件

它们是成对出现的. ffmpeg 把输入文件--转换成--->AVFormatContext实例 ◆ avformat_open_input() int avformat_open_input(AVFormatContext ** ps,const char * url,ff_const59 AVInputFormat * fmt,AVDictionary ** options )Open an input stream and read the header.…

跨越语言的艺术:Weblogic序列化漏洞与IIOP协议

0x01 概述 Weblogic 的序列化漏洞主要依赖于 T3 和 IIOP 协议&#xff0c;这两种协议在通信交互的过程中存在如跨语言、网络传输等方面的诸多问题&#xff0c;会给漏洞的检测和利用带来许多不便。在白帽汇安全研究院的理念中&#xff0c;漏洞检测和利用是一项需要创造性的工作…

速锐得新能源电动汽车整车能耗热管理CAN总线模块开发方案

一、新能源时代背景 新能源汽车浪潮席卷而来&#xff0c;随着汽车向电动化和智能化方向发展&#xff0c;对汽车能量管理的要求也越来越高。而直冷直热热泵空调热管理系统是新能源汽车领域的新蓝海&#xff0c;随着热管理系统的崛起&#xff0c;在整车能耗热管理采集模块开发方…

计算机组成原理——第四章指令系统(下)

本是青灯不归客&#xff0c;却因浊酒恋红尘 文章目录前言4.3.1 高级语言与机器级代码之间的对应4.3.2 常用的X86汇编指令4.3.3 ATu0026T格式和Intel格式4.3.4 选择语句的机器级表示4.3.5 循环语句的机器级表示4.4 CiSC和RiSC前言 接下来这部分主要讲的就是高级语言与汇编语言的…

Thymeleaf select回显并选中多个

语法&#xff1a; selected"selected" 或 selectedtrue ${#strings.indexOf(name,frag)} 或者 ${#lists.contains(list, element)} 或者 ${#strings.contains(name,ez)} 或者 ${#strings.containsIgnoreCase(name,ez)} 都可以实现。 多选示例 &#xff1a; &…

linux 集群时间同步

前言 由于搭建hadoop集群需要进行集群时间同步&#xff0c;记录下具体操作过程。 这里我的集群环境为192.168.184.129&#xff08;主&#xff09;、192.168.184.130&#xff08;从&#xff09;、192.168.184.131&#xff08;从&#xff09;&#xff0c;设置从机器从主机器同步…

Windows XP设置Outlook电子邮箱

一、问题描述 在Windows XP操作系统中进行Outlook电子邮箱的设置。 二、具体步骤 1、点击“开始”&#xff0c;找到“电子邮件&#xff08;Outlook Express&#xff09;并点击&#xff1a; 2、点击“设置邮件账户”。 3、输入自己的姓名&#xff0c;点击“下一步”。 4、…

Hive UDTF、窗口函数、自定义函数

目录 1 UDTF 1.1 概述 1.2 explode 1.3 posexplode 1.4 inline 1.5 Lateral View 2 窗口函数&#xff08;开窗函数&#xff09; 2.1 定义 2.2 语法 2.2.1 语法--函数 2.2.2 语法--窗口 2.2.3 常用窗口函数 3 自定义函数 3.1 基本知识 3.2 实现自定义函数 3.2.1 …

RestClient操作文档

RestClient操作文档5.RestClient操作文档5.1.新增文档5.1.1.索引库实体类5.1.2.语法说明5.1.3.完整代码5.2.查询文档5.2.1.语法说明5.2.2.完整代码5.3.删除文档5.4.修改文档5.4.1.语法说明5.4.2.完整代码5.5.批量导入文档5.5.1.语法说明5.5.2.完整代码5.6.小结5.RestClient操作…

JavaSE学习进阶day04_02 Calendar类

第三章 Calendar类 3.1 概述 java.util.Calendar类表示一个“日历类”&#xff0c;可以进行日期运算。它是一个抽象类&#xff0c;不能创建对象&#xff0c;我们可以使用它的子类&#xff1a;java.util.GregorianCalendar类。 有两种方式可以获取GregorianCalendar对象&#…

《Advanced R》学习笔记 | Chapter3 Vectors

专注系列化、高质量的R语言教程推文索引 | 联系小编 | 付费合集本篇推文是学堂君学习第3章“Vectors”的笔记&#xff0c;原文链接是https://adv-r.hadley.nz/vectors-chap.html&#xff0c;可在文末“阅读原文”处直达。通过本章的学习&#xff0c;我们可以更清晰地理解R语言中…

IDPChat:探索基于LLaMA和Stable Diffusion的「开源」中文多模态AI大模型

中文多模态模型 IDPChat 和大家见面了。 随着GPT4、文心一言等的发布&#xff0c;预训练大模型正式开启由单模态向多模态模型演进。多模态的特性为语言模型带来更加丰富的应用场景。 我们认为&#xff0c;未来的AI应用将主要以大模型为核心基石。 而在大模型的领域&#xff0c;…

PFTL101A 2.0KN 3BSE004172R1控制卷取物体时保持物体相互拉长或者绷紧的力

PFTL101A 2.0KN 3BSE004172R1控制卷取物体时保持物体相互拉长或者绷紧的力 ​ 基于单片机的放卷机张力控制系统设计 张力控制&#xff0c;通俗地讲&#xff0c;就是要控制卷取物体时保持物体相互拉长或者绷紧的力。张力应用于最广泛的造纸、纤维、塑料薄膜、电线、印刷品、磁带…

基于SpringBoot的健身房管理系统

有需要请私信或看评论链接哦 可远程调试 基于SpringBoot健身房管理系统一 介绍 此健身房管理系统基于SpringBoot开发&#xff0c;数据库mysql&#xff0c;前端startbootstrap。系统角色分为用户和管理员。用户登录后可查看个人信息&#xff0c;课程报名和课程查询&#xff0c;…

react-5 高阶组件 (HOC)(防抖节流) --- 高阶函数(HOF)(拖拽)

高阶函数&#xff1a;指一类函数,防抖&#xff0c;节流 防抖&#xff1a; 短时间内频繁触发同一事件时&#xff0c;只有最后一次生效. 例如电梯关门的效果 节流&#xff1a; 短时间内频繁触发同一个事件时&#xff0c;在单位时间内&#xff0c;只生效一次。例如lol英雄的大招…

【SpringBoot】面试组合技-天羽屠龙舞,SpringBootApplication注解的作用是什么?SpringBoot怎么实现自动装配的?

SpringBoot源码下载地址&#xff1a;https://github.com/spring-projects/spring-boot/tags 文章目录&#x1f35f;下载源码&#x1f357;环境准备&#x1f356;注解解析&#x1f35d;SpringBootConfiguration注解&#x1f35b;EnableAutoConfiguration注解&#x1f364;AutoC…

Kettle7.0同步数据(简单操作步骤hive-hive)

一、Kettle说明介绍和原理说明 Kettle是一款免费的ETL工具。 ETL分别是“Extract”、“ Transform” 、“Load”三个单词的首字母缩写&#xff0c;也就是代表ETL过程的三个最主要步骤&#xff1a;“抽取”、“转换”、“装载”&#xff0c;但我们平时往往简称其为数据抽取。 ET…