18 | 生产环境多数据源的处理方法有哪些

工作中我们时常会遇到跨数据库操作的情况,这时候就需要配置多数据源,那么如何配置呢?常用的方式及其背后的原理支撑是什么呢?我们下面来了解一下。

首先看看两种常见的配置方式,分别为通过多个 @Configuration 文件、利用 AbstractRoutingDataSource 配置多数据源。

第一种方式:多个数据源的 @Configuration 的配置方法

这种方式的主要思路是,不同 Package 下面的实体和 Repository 采用不同的 Datasource。所以我们改造一下我们的 example 目录结构,来看看不同 Repositories 的数据源是怎么处理的。

第一步:规划 Entity 和 Repository 的目录结构,为了方便配置多数据源。

将 User 和 UserAddress、UserRepository 和 UserAddressRepository 移动到 db1 里面;将 UserInfo 和 UserInfoRepository 移动到 db2 里面。如下图所示:

Drawing 0.png

我们把实体和 Repository 分别放到了 db1 和 db2 两个目录里面,这时我们假设数据源 1 是 MySQL,User 表和 UserAddress 在数据源 1 里面,那么我们需要配置一个 DataSource1 的 Configuration 类,并且在里面配置 DataSource、TransactionManager 和 EntityManager。

第二步:配置 DataSource1Config 类。

目录结构调整完之后,接下来我们开始配置数据源,完整代码如下:

复制代码

@Configuration
@EnableTransactionManagement//开启事务
//利用EnableJpaRepositories配置哪些包下面的Repositories,采用哪个EntityManagerFactory和哪个trannsactionManager
@EnableJpaRepositories(basePackages = {"com.example.jpa.example1.db1"},//数据源1的repository的包路径entityManagerFactoryRef = "db1EntityManagerFactory",//改变数据源1的EntityManagerFactory的默认值,改为db1EntityManagerFactorytransactionManagerRef = "db1TransactionManager"//改变数据源1的transactionManager的默认值,改为db1TransactionManager)
public class DataSource1Config {/*** 指定数据源1的dataSource配置* @return*/@Primary@Bean(name = "db1DataSourceProperties")@ConfigurationProperties("spring.datasource1") //数据源1的db配置前缀采用spring.datasource1public DataSourceProperties dataSourceProperties() {return new DataSourceProperties();}/*** 可以选择不同的数据源,这里我用HikariDataSource举例,创建数据源1* @param db1DataSourceProperties* @return*/@Primary@Bean(name = "db1DataSource")@ConfigurationProperties(prefix = "spring.datasource.hikari.db1") //配置数据源1所用的hikari配置key的前缀public HikariDataSource dataSource(@Qualifier("db1DataSourceProperties") DataSourceProperties db1DataSourceProperties) {HikariDataSource dataSource = db1DataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();if (StringUtils.hasText(db1DataSourceProperties.getName())) {dataSource.setPoolName(db1DataSourceProperties.getName());}return dataSource;}/*** 配置数据源1的entityManagerFactory命名为db1EntityManagerFactory,用来对实体进行一些操作* @param builder* @param db1DataSource entityManager依赖db1DataSource* @return*/@Primary@Bean(name = "db1EntityManagerFactory")public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder, @Qualifier("db1DataSource") DataSource db1DataSource) {return builder.dataSource(db2DataSource)
.packages("com.example.jpa.example1.db1") //数据1的实体所在的路径
.persistenceUnit("db1")// persistenceUnit的名字采用db1
.build();}/*** 配置数据源1的事务管理者,命名为db1TransactionManager依赖db1EntityManagerFactory* @param db1EntityManagerFactory * @return*/@Primary@Bean(name = "db1TransactionManager")public PlatformTransactionManager transactionManager(@Qualifier("db1EntityManagerFactory") EntityManagerFactory db1EntityManagerFactory) {return new JpaTransactionManager(db1EntityManagerFactory);}
}

到这里,数据源 1 我们就配置完了,下面再配置数据源 2。

第三步:配置 DataSource2Config类,加载数据源 2。

复制代码

@Configuration
@EnableTransactionManagement//开启事务
//利用EnableJpaRepositories,配置哪些包下面的Repositories,采用哪个EntityManagerFactory和哪个trannsactionManager
@EnableJpaRepositories(basePackages = {"com.example.jpa.example1.db2"},//数据源2的repository的包路径entityManagerFactoryRef = "db2EntityManagerFactory",//改变数据源2的EntityManagerFactory的默认值,改为db2EntityManagerFactorytransactionManagerRef = "db2TransactionManager"//改变数据源2的transactionManager的默认值,改为db2TransactionManager
)
public class DataSource2Config {/*** 指定数据源2的dataSource配置** @return*/@Bean(name = "db2DataSourceProperties")@ConfigurationProperties("spring.datasource2") //数据源2的db配置前缀采用spring.datasource2public DataSourceProperties dataSourceProperties() {return new DataSourceProperties();}/*** 可以选择不同的数据源,这里我用HikariDataSource举例,创建数据源2** @param db2DataSourceProperties* @return*/@Bean(name = "db2DataSource")@ConfigurationProperties(prefix = "spring.datasource.hikari.db2") //配置数据源2的hikari配置key的前缀public HikariDataSource dataSource(@Qualifier("db2DataSourceProperties") DataSourceProperties db2DataSourceProperties) {HikariDataSource dataSource = db2DataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();if (StringUtils.hasText(db2DataSourceProperties.getName())) {dataSource.setPoolName(db2DataSourceProperties.getName());}return dataSource;}/*** 配置数据源2的entityManagerFactory命名为db2EntityManagerFactory,用来对实体进行一些操作** @param builder* @param db2DataSource entityManager依赖db2DataSource* @return*/@Bean(name = "db2EntityManagerFactory")public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder, @Qualifier("db2DataSource") DataSource db2DataSource) {return builder.dataSource(db2DataSource).packages("com.example.jpa.example1.db2") //数据2的实体所在的路径.persistenceUnit("db2")// persistenceUnit的名字采用db2.build();}/*** 配置数据源2的事务管理者,命名为db2TransactionManager依赖db2EntityManagerFactory** @param db2EntityManagerFactory* @return*/@Bean(name = "db2TransactionManager")public PlatformTransactionManager transactionManager(@Qualifier("db2EntityManagerFactory") EntityManagerFactory db2EntityManagerFactory) {return new JpaTransactionManager(db2EntityManagerFactory);}
}

这一步你需要注意,DataSource1Config 和 DataSource2Config 不同的是,1 里面每个 @Bean 都 @Primary,而 2 里面不是的。

第四步:通过 application.properties 配置两个数据源的值,代码如下:

复制代码

###########datasource1 采用Mysql数据库
spring.datasource1.url=jdbc:mysql://localhost:3306/test2?logger=Slf4JLogger&profileSQL=true
spring.datasource1.username=root
spring.datasource1.password=root
##数据源1的连接池的名字
spring.datasource.hikari.db1.pool-name=jpa-hikari-pool-db1
##最长生命周期15分钟够了
spring.datasource.hikari.db1.maxLifetime=900000
spring.datasource.hikari.db1.maximumPoolSize=8
###########datasource2 采用h2内存数据库
spring.datasource2.url=jdbc:h2:~/test
spring.datasource2.username=sa
spring.datasource2.password=sa
##数据源2的连接池的名字
spring.datasource.hikari.db2.pool-name=jpa-hikari-pool-db2
##最长生命周期15分钟够了
spring.datasource.hikari.db2.maxLifetime=500000
##最大连接池大小和数据源1区分开,我们配置成6个
spring.datasource.hikari.db2.maximumPoolSize=6

第五步:我们写个 Controller 测试一下。

复制代码

@RestController
public class UserController {@Autowiredprivate UserRepository userRepository;@Autowiredprivate UserInfoRepository userInfoRepository;//操作user的Repository@PostMapping("/user")public User saveUser(@RequestBody User user) {return userRepository.save(user);}//操作userInfo的Repository@PostMapping("/user/info")public UserInfo saveUserInfo(@RequestBody UserInfo userInfo) {return userInfoRepository.save(userInfo);}
}

第六步:直接启动我们的项目,测试一下。

请看这一步的启动日志:

Drawing 1.png
Drawing 2.png

可以看到启动的是两个数据源,其对应的连接池的监控也是不一样的:数据源 1 有 8 个,数据源 2 有 6 个。

Drawing 3.png

如果我们分别请求 Controller 写的两个方法的时候,也会分别插入到不同的数据源里面去。

通过上面的六个步骤你应该知道了如何配置多数据源,那么它的原理基础是什么呢?我们看一下

Datasource 与 TransactionManager、EntityManagerFactory 的关系和职责分别是怎么样的。

Datasource 与 TransactionManager、EntityManagerFactory 的关系分析

我们通过一个类的关系图来分析一下:

Drawing 4.png

其中,

  1. HikariDataSource 负责实现 DataSource,交给 EntityManager 和 TransactionManager 使用;
  2. EntityManager 是利用 Datasouce 来操作数据库,而其实现类是 SessionImpl;
  3. EntityManagerFactory 是用来管理和生成 EntityManager 的,而 EntityManagerFactory 的实现类是 LocalContainerEntityManagerFactoryBean,通过实现 FactoryBean 接口实现,利用了 FactoryBean 的 Spring 中的 bean 管理机制,所以需要我们在 Datasource1Config 里面配置 LocalContainerEntityManagerFactoryBean 的 bean 的注入方式;
  4. JpaTransactionManager 是用来管理事务的,实现了 TransactionManager 并且通过 EntityFactory 和 Datasource 进行 db 操作,所以我们要在 DataSourceConfig 里面告诉 JpaTransactionManager 用的 TransactionManager 是 db1EntityManagerFactory。

上一讲我们介绍了 Datasource 的默认加载和配置方式,那么默认情况下 Datasource 的 EntityManagerFactory 和 TransactionManager 是怎么加载和配置的呢?

默认的 JpaBaseConfiguration 的加载方式分析

上一讲我只简单说明了 DataSource 的配置,其实我们还可以通过 HibernateJpaConfiguration,找到父类 JpaBaseConfiguration 类,如图所示:

Drawing 5.png

接着打开 JpaBaseConfiguration 就可以看到多数据源的参考原型,如下图所示:

Drawing 6.png

通过上面的代码,可以看到在单个数据源情况下的 EntityManagerFactory 和 TransactionManager 的加载方法,并且我们在多数据源的配置里面还加载了一个类:EntityManagerFactoryBuilder entityManagerFactoryBuilder,也正是从上面的方法加载进去的,看第 120 行代码就知道了。

那么除了上述的配置多数据源的方式,还有没有其他方法了呢?我们接着看一下。

第二种方式:利用 AbstractRoutingDataSource 配置多数据源

我们都知道 DataSource 的本质是获得数据库连接,而 AbstractRoutingDataSource 帮我们实现了动态获得数据源的可能性。下面还是通过一个例子看一下它是怎么使用的。

第一步:定一个数据源的枚举类,用来标示数据源有哪些。

复制代码

/*** 定义一个数据源的枚举类*/
public enum RoutingDataSourceEnum {DB1, //实际工作中枚举的语义可以更加明确一点;DB2;public static RoutingDataSourceEnum findbyCode(String dbRouting) {for (RoutingDataSourceEnum e : values()) {if (e.name().equals(dbRouting)) {return e;}}return db1;//没找到的情况下,默认返回数据源1}
}

第二步:新增 DataSourceRoutingHolder,用来存储当前线程需要采用的数据源。

复制代码

/*** 利用ThreadLocal来存储,当前的线程使用的数据*/
public class DataSourceRoutingHolder {private static ThreadLocal<RoutingDataSourceEnum> threadLocal = new ThreadLocal<>();public static void setBranchContext(RoutingDataSourceEnum dataSourceEnum) {threadLocal.set(dataSourceEnum);}public static RoutingDataSourceEnum getBranchContext() {return threadLocal.get();}public static void clearBranchContext() {threadLocal.remove();}
}

第三步:配置 RoutingDataSourceConfig,用来指定哪些 Entity 和 Repository 采用动态数据源。

复制代码

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(//数据源的repository的包路径,这里我们覆盖db1和db2的包路径basePackages = {"com.example.jpa.example1"},entityManagerFactoryRef = "routingEntityManagerFactory",transactionManagerRef = "routingTransactionManager"
)
public class RoutingDataSourceConfig {@Autowired@Qualifier("db1DataSource")private DataSource db1DataSource;@Autowired@Qualifier("db2DataSource")private DataSource db2DataSource;/*** 创建RoutingDataSource,引用我们之前配置的db1DataSource和db2DataSource** @return*/@Bean(name = "routingDataSource")public DataSource dataSource() {Map<Object, Object> dataSourceMap = Maps.newHashMap();dataSourceMap.put(RoutingDataSourceEnum.DB1, db1DataSource);dataSourceMap.put(RoutingDataSourceEnum.DB2, db2DataSource);RoutingDataSource routingDataSource = new RoutingDataSource();//设置RoutingDataSource的默认数据源routingDataSource.setDefaultTargetDataSource(db1DataSource);//设置RoutingDataSource的数据源列表routingDataSource.setTargetDataSources(dataSourceMap);return routingDataSource;}/*** 类似db1和db2的配置,唯一不同的是,这里采用routingDataSource* @param builder* @param routingDataSource entityManager依赖routingDataSource* @return*/@Bean(name = "routingEntityManagerFactory")public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder, @Qualifier("routingDataSource") DataSource routingDataSource) {return builder.dataSource(routingDataSource).packages("com.example.jpa.example1") //数据routing的实体所在的路径,这里我们覆盖db1和db2的路径.persistenceUnit("db-routing")// persistenceUnit的名字采用db-routing.build();}/*** 配置数据的事务管理者,命名为routingTransactionManager依赖routtingEntityManagerFactory** @param routingEntityManagerFactory* @return*/@Bean(name = "routingTransactionManager")public PlatformTransactionManager transactionManager(@Qualifier("routingEntityManagerFactory") EntityManagerFactory routingEntityManagerFactory) {return new JpaTransactionManager(routingEntityManagerFactory);}
}

路由数据源配置与 DataSource1Config 和 DataSource2Config 有相互覆盖关系,这里我们直接覆盖 db1 和 db2 的包路径,以便于我们的动态数据源生效。

第四步:写一个 MVC 拦截器,用来指定请求分别采用什么数据源。

新建一个类 DataSourceInterceptor,用来在请求前后指定数据源,请看代码:

复制代码

/*** 动态路由的实现逻辑,我们通过请求里面的db-routing,来指定此请求采用什么数据源*/
@Component
public class DataSourceInterceptor extends HandlerInterceptorAdapter {/*** 请求处理之前更改线程里面的数据源*/@Overridepublic boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler) throws Exception {String dbRouting = request.getHeader("db-routing");DataSourceRoutingHolder.setBranchContext(RoutingDataSourceEnum.findByCode(dbRouting));return super.preHandle(request, response, handler);}/*** 请求结束之后清理线程里面的数据源*/@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {super.afterCompletion(request, response, handler, ex);DataSourceRoutingHolder.clearBranchContext();}
}

同时我们需要在实现 WebMvcConfigurer 的配置里面,把我们自定义拦截器 dataSourceInterceptor 加载进去,代码如下:

复制代码

/*** 实现WebMvcConfigurer*/
@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {@Autowiredprivate DataSourceInterceptor dataSourceInterceptor;//添加自定义拦截器@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(dataSourceInterceptor).addPathPatterns("/**");WebMvcConfigurer.super.addInterceptors(registry);}
...//其他不变的代码省略}

此处我们采用的是 MVC 的拦截器机制动态改变的数据配置,你也可以使用自己的 AOP 任意的拦截器,如事务拦截器、Service 的拦截器等,都可以实现。需要注意的是,要在开启事务之前配置完毕。

第五步:启动测试。

我们在 Http 请求头里面加上 db-routing:DB2,那么本次请求就会采用数据源 2 进行处理,请求代码如下:

复制代码

POST /user/info HTTP/1.1
Host: 127.0.0.1:8089
Content-Type: application/json
db-routing: DB2
Cache-Control: no-cache
Postman-Token: 56d8dc02-7f3e-7b95-7ff1-572a4bb7d102
{"ages":10}

通过上面五个步骤,我们可以利用 AbstractRoutingDataSource 实现动态数据源,实际工作中可能会比我讲述的要复杂,有的需要考虑多线程、线程安全等问题,你要多加注意。
在实际应用场景中,对于多数据源的问题,我还有一些思考,下面分享给你。

微服务下多数据源的思考:还需要这样用吗?

通过上面的两种方式,我们分别可以实现同一个 application 应用的多数据源配置,那么有什么注意事项呢?我简单总结如下几点建议。

多数据源实战注意事项
  1. 此种方式利用了当前线程事务不变的原理,所以要注意异步线程的处理方式;
  2. 此种方式利用了 DataSource 的原理,动态地返回不同的 db 连接,一般需要在开启事务之前使用,需要注意事务的生命周期;
  3. 比较适合读写操作分开的业务场景;
  4. 多数据的情况下,避免一个事务里面采用不同的数据源,这样会有意想不到的情况发生,比如死锁现象;
  5. 学会通过日志检查我们开启请求的方法和开启的数据源是否正确,可以通过 Debug 断点来观察数据源是否选择的正确,如下图所示:

Drawing 7.png

微服务下的实战建议

在实际工作中,为了便捷省事,更多开发者喜欢配置多个数据源,但是我强烈建议不要在对用户直接提供的 API 服务上面配置多数据源,否则将出现令人措手不及的 Bug。

如果你是做后台管理界面,供公司内部员工使用的,那么这种 API 可以为了方便而使用多数据源。

微服务的大环境下,服务越小,内聚越高,低耦合服务越健壮,所以一般跨库之间一定是是通过 REST 的 API 协议,进行内部服务之间的调用,这是最稳妥的方式,原因有如下几点:

  1. REST 的 API 协议更容易监控,更容易实现事务的原子性;
  2. db 之间解耦,使业务领域代码职责更清晰,更容易各自处理各种问题;
  3. 只读和读写的 API 更容易分离和管理。

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

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

相关文章

【完美世界】云曦篇开播时间定档,推迟两周,石昊新形态帅翻,怒斩战王

Hello,小伙伴们&#xff0c;我是小郑继续为大家深度解析国漫资讯。 完美世界动画更新最新资讯&#xff0c;石昊在血色平原与云曦重逢并英雄救美。 官方公布了云曦特别篇的先导预告&#xff0c;播出时间推迟了两周。 石昊在特别篇中出现了新形态&#xff0c;以雷帝甲胄为主&…

Leetcode—88.合并两个有序数组【简单】

2023每日刷题&#xff08;一&#xff09; Leetcode—88.合并两个有序数组 题解 因为这两个数组已经排好序&#xff0c;我们可以把两个指针分别放在两个数组的末尾&#xff0c;即 nums1 的m − 1 位和 nums2 的 n − 1 位。每次将较大的那个数字复制到 nums1 的后边&#xff0…

Kafka SASL认证授权(六)全方位性能测试

Kafka SASL认证授权(六)全方位性能测试。 官网地址:https://kafka.apache.org/ 一、场景 线上已经有kafka集群,服务运行稳定。但是因为产品升级,需要对kakfa做安全测试,也就是权限验证。 但是增加权限验证,会不会对性能有影响呢?影响大吗?不知道呀! 因此,本文就此…

【Hyper-V】Windows的Hyper-V管理器创建的虚拟机上怎么复制粘贴文件

在hyper-V 管理器中创建的虚拟机里面&#xff0c;发现它是不能直接复制粘贴文件的&#xff0c;那只能粘贴纯文本内容&#xff0c;也就是模拟输入内容&#xff0c;这是虚拟系统是被隔离的&#xff0c;也带来不便之处&#xff0c;要怎么解决这个限制呢。 例如&#xff0c;打开一…

基于静电放电优化的BP神经网络(分类应用) - 附代码

基于静电放电优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于静电放电优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.静电放电优化BP神经网络3.1 BP神经网络参数设置3.2 静电放电算法应用 4.测试结果…

电动力学专题研讨:运动电荷之间的相互作用是否满足牛顿第三定律?

电动力学专题研讨&#xff1a;运动电荷之间的相互作用是否满足牛顿第三定律&#xff1f;​​​​​​​ 两个稳恒电流元之间的相互作用不一定服从牛顿第三定律常见的解释是&#xff1a;稳恒电流元是不能孤立存在的&#xff0c;因此不能得出结论 符号约定 两个运动点电荷之间的力…

文心一言 VS 讯飞星火 VS chatgpt (113)-- 算法导论10.2 5题

五、用go语言&#xff0c;使用单向循环链表实现字典操作 INSERT、DELETE 和 SEARCH&#xff0c;并给出所写过程的运行时间。 文心一言&#xff1a; 在Go语言中&#xff0c;我们首先需要定义一个Node结构体来表示单向循环链表的节点&#xff0c;然后再定义一个LinkedList结构体…

Redis 分布式锁 @Klock 注解详解及使用教程

文章目录 一、作用二、参数三、锁的流程四、SpringBoot 集成1. pom 依赖2. yaml 配置3. 使用方式 五、变量级锁和方法级锁六、常见应用场景1. 页面重复提交2. 定时任务3. 核心业务 七、锁的粒度与锁的时间 一、作用 注解 klock 是基于 Redis 的分布式锁&#xff0c;作用在分布…

CCF CSP认证 历年题目自练Day29

题目一 试题编号&#xff1a; 202112-1 试题名称&#xff1a; 序列查询 时间限制&#xff1a; 300ms 内存限制&#xff1a; 512.0MB 样例1输入 3 10 2 5 8 样例1输出 15 样例2输入 9 10 1 2 3 4 5 6 7 8 9 样例2输出 45 题目分析&#xff08;个人理解&#xff09; 还是…

Chrome Extensions v3 迁移清单

一、前置问题 1.1为什么需要迁移 v3&#xff1f; Chrome 计划完全停止 v2 版本维护&#xff0c;后续 v2 版本将无法上架谷歌插件商店&#xff0c;除此之外&#xff0c;未来新版本 Chrome 对于 v2 版本插件的限制会越来越大&#xff0c;比如安全性限制 iframe 嵌套只能通过沙盒…

云原生Kubernetes:Rancher管理k8s集群

目录 一、理论 1.Rancher 2.Rancher 安装及配置 二、实验 1.Rancher 安装及配置 三、问题 1. Rancher 部署监控系统报错 四、总结 一、理论 1.Rancher (1) 概念 Rancher 简介 Rancher 是一个开源的企业级多集群 Kubernetes 管理平台&#xff0c;实现了 Kubernetes …

利用MobaXterm连接服务器的全程配置

一、服务器上的操作 1.1 保证openssh的安装 openssh安装命令如下 apt-get update apt install openssh-server1.2 保证SSH服务没有在相应端口上侦听连接 1确保本地 SSH 服务正在运行 可以尝试使用以下命令检查 SSH 服务的状态&#xff08;在大多数 Linux 系统上&#xff0…

分布式存储系统Ceph应用详解

Ceph的应用 一、Ceph 存储池(Pool)1.1 Ceph存储池的基本概念1.2 原理1.3 一个Pool资源池应该包含多少PG数&#xff1f;1.4 Ceph 存储池相关管理命令1.4.1 创建1.4.2 查看1.4.3 修改1.4.4 删除 二、 CephFS文件系统MDS接口三、创建CephFS文件系统MDS接口3.1 服务端操作Step1 在管…

【frp实现内网穿透踩坑到成功篇】

【frp实现内网穿透踩坑到成功篇】 背景&需求配置服务器端配置客户端总结 背景&需求 白嫖了一个tencent入门级服务器 ∗ 1 *1 ∗1&#xff0c;学校实验室内网服务器 ∗ 1 *1 ∗1&#xff0c;需要访问内网的服务器。一顿搜寻资料后确定大致的路子&#xff1a; 第一步是…

虹科分享 | 独特的FRER机制:TSN如何确保网络的可靠性?

1.IEEE802.1 CB协议 Frame Replication and Elimination for Reliability(FRER)是IEEE 802.1CB协议的一个重要特性&#xff0c;旨在增强以太网网络的可靠性。FRER利用帧复制和消除技术提供冗余保护和从连接故障中快速恢复。 FRER-IEEE 802.1CB协议的应用场景&#xff1a; 高…

【HttpRunner】接口自动化测试框架

简介 2018年python开发者大会上&#xff0c;了解到HttpRuuner开源自动化测试框架&#xff0c;采用YAML/JSON格式管理用例&#xff0c;能录制和转换生成用例功能&#xff0c;充分做到用例与测试代码分离&#xff0c;相比excel维护测试场景数据更加简洁。在此&#xff0c;利用业…

阿里云韩国服务器测试IP地址及公网带宽收费价格表

阿里云服务器韩国&#xff08;首尔&#xff09;地域公网带宽价格表&#xff0c;1M带宽价格是23.0元/月&#xff0c;按使用流量1GB价格是0.8元&#xff0c;阿里云韩国服务器测试IP地址&#xff1a;149.129.12.20&#xff0c;阿里云百科aliyunbaike.com来详细说下阿里云韩国服务器…

火伞云Web应用防火墙的特点与优势

在前文中&#xff0c;我们已经介绍了Web应用防火墙&#xff08;WAF&#xff09;的基本原理和重要性。接下来&#xff0c;我们将深入探讨火伞云Web应用防火墙的特点与优势&#xff0c;了解它如何为企业提供更为完善和专业的网络安全保障。 一、强大的防御能力 火伞云Web应用防火…

SVN报错fail to run the WC Db work queue associated with,清理失败,乱码的解决方式

替换掉 wc.db 文件即可 SVN报错fail to run the WC Db work queue associated with&#xff0c;清理失败&#xff0c;乱码的解决方式_svn failed to run the wc db-CSDN博客