当前位置: 首页 > news >正文

【源码】Sharding-JDBC源码分析之SQL中读写分离动态策略、数据库发现规则及DatabaseDiscoverySQLRouter路由的原理

 Sharding-JDBC系列

1、Sharding-JDBC分库分表的基本使用

2、Sharding-JDBC分库分表之SpringBoot分片策略

3、Sharding-JDBC分库分表之SpringBoot主从配置

4、SpringBoot集成Sharding-JDBC-5.3.0分库分表

5、SpringBoot集成Sharding-JDBC-5.3.0实现按月动态建表分表

6、【源码】Sharding-JDBC源码分析之JDBC

7、【源码】Sharding-JDBC源码分析之SPI机制

8、【源码】Sharding-JDBC源码分析之Yaml分片配置文件解析原理

9、【源码】Sharding-JDBC源码分析之Yaml分片配置原理(一)

10、【源码】Sharding-JDBC源码分析之Yaml分片配置原理(二)

11、【源码】Sharding-JDBC源码分析之Yaml分片配置转换原理

12、【源码】Sharding-JDBC源码分析之ShardingSphereDataSource的创建原理

13、【源码】Sharding-JDBC源码分析之ContextManager创建中mode分片配置信息的持久化存储的原理

14、【源码】Sharding-JDBC源码分析之ContextManager创建中ShardingSphereDatabase的创建原理

15、【源码】Sharding-JDBC源码分析之分片规则生成器DatabaseRuleBuilder实现规则配置到规则对象的生成原理

16、【源码】Sharding-JDBC源码分析之配置数据库定义的表的元数据解析原理

17、【源码】Sharding-JDBC源码分析之ShardingSphereConnection的创建原理

18、【源码】Sharding-JDBC源码分析之ShardingSpherePreparedStatement的创建原理

19、【源码】Sharding-JDBC源码分析之Sql解析的原理

20、【源码】Sharding-JDBC源码分析之SQL路由及SingleSQLRouter单表路由

21、【源码】Sharding-JDBC源码分析之SQL中分片键路由ShardingSQLRouter的原理

22、【源码】Sharding-JDBC源码分析之SQL中读写分离路由ReadwriteSplittingSQLRouter的原理

23、【源码】Sharding-JDBC源码分析之SQL中读写分离动态策略、数据库发现规则及DatabaseDiscoverySQLRouter路由的原理

前言

在上一篇

【源码】Sharding-JDBC源码分析之SQL中读写分离路由ReadwriteSplittingSQLRouter的原理

介绍了读写分离策略时,策略分为静态策略和动态策略,并详细介绍了静态策略的实现。本篇从源码的角度,分析动态策略的实现。读写分离规则的动态策略是结合数据库发现规则来实现的,且数据库发现规则可单独作为路由器,对应的路由器为DatabaseDiscoverySQLRouter,对应的规则对象为DatabaseDiscoveryRule。

ReadwriteSplittingStrategyFactory

在ReadwriteSplittingDataSourceRule读写分离规则类的构造方法中,通过ReadwriteSplittingStrategyFactory的newInstance()方法,创建ReadwriteSplittingStrategy读写分离策略。

ReadwriteSplittingStrategyFactory的源码如下:

package org.apache.shardingsphere.readwritesplitting.strategy;/*** 读写分离策略工厂*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class ReadwriteSplittingStrategyFactory {/*** 实例化读写分离策略* @param readwriteSplittingConfig 读写分离配置* @param builtRules 配置的规则* @return*/public static ReadwriteSplittingStrategy newInstance(final ReadwriteSplittingDataSourceRuleConfiguration readwriteSplittingConfig, final Collection<ShardingSphereRule> builtRules) {// 如果没有指定静态策略return null == readwriteSplittingConfig.getStaticStrategy()// 默认创建动态策略? createDynamicReadwriteSplittingStrategy(readwriteSplittingConfig.getDynamicStrategy(), builtRules)// 创建静态策略: createStaticReadwriteSplittingStrategy(readwriteSplittingConfig.getStaticStrategy());}/*** 创建一个静态读写分离策略* @param staticConfig* @return*/private static StaticReadwriteSplittingStrategy createStaticReadwriteSplittingStrategy(final StaticReadwriteSplittingStrategyConfiguration staticConfig) {return new StaticReadwriteSplittingStrategy(staticConfig.getWriteDataSourceName(), staticConfig.getReadDataSourceNames());}/*** 创建动态读写分离策略* @param dynamicConfig 读写分离的动态策略配置* @param builtRules 配置的规则* @return*/private static DynamicReadwriteSplittingStrategy createDynamicReadwriteSplittingStrategy(final DynamicReadwriteSplittingStrategyConfiguration dynamicConfig,final Collection<ShardingSphereRule> builtRules) {// 从配置的规则中获取DynamicDataSourceContainedRule,默认只有DatabaseDiscoveryRule,即动态数据库发现规则Optional<ShardingSphereRule> dynamicDataSourceStrategy = builtRules.stream().filter(each -> each instanceof DynamicDataSourceContainedRule).findFirst();// 获取是否允许写库支持读,默认为支持boolean allowWriteDataSourceQuery = Strings.isNullOrEmpty(dynamicConfig.getWriteDataSourceQueryEnabled()) ? Boolean.TRUE : Boolean.parseBoolean(dynamicConfig.getWriteDataSourceQueryEnabled());// 创建动态策略return new DynamicReadwriteSplittingStrategy(dynamicConfig.getAutoAwareDataSourceName(), allowWriteDataSourceQuery, (DynamicDataSourceContainedRule) dynamicDataSourceStrategy.get());}
}

通过createDynamicReadwriteSplittingStrategy()方法,创建DynamicReadwriteSplittingStrategy动态读写分离策略。

DynamicReadwriteSplittingStrategy

DynamicReadwriteSplittingStrategy的源码如下:

package org.apache.shardingsphere.readwritesplitting.strategy.type;/*** 动态读写分离策略*/
@RequiredArgsConstructor
@Getter
public final class DynamicReadwriteSplittingStrategy implements ReadwriteSplittingStrategy {// 自动感知的数据源名称private final String autoAwareDataSourceName;// 是否允许写数据源进行读操作private final boolean allowWriteDataSourceQuery;// 动态数据源包含规则private final DynamicDataSourceContainedRule dynamicDataSource;/*** 获取写数据源名称* @return*/@Overridepublic String getWriteDataSource() {return dynamicDataSource.getPrimaryDataSourceName(autoAwareDataSourceName);}/*** 获取读数据源名称* @return*/@Overridepublic List<String> getReadDataSources() {return new ArrayList<>(dynamicDataSource.getReplicaDataSourceNames(autoAwareDataSourceName));}@Overridepublic Collection<String> getAllDataSources() {return Collections.singletonList(autoAwareDataSourceName);}
}

在DynamicReadwriteSplittingStrategy动态读写分离策略中,通过DynamicDataSourceContainedRule对象,获取读写分离中的读数据源和写数据源名称。其中DynamicDataSourceContainedRule对象通过构造方法传入,即从ReadwriteSplittingStrategyFactory的createDynamicReadwriteSplittingStrategy()方法中传入的,且传入的规则是从系统配置的规则中,查找实现DynamicDataSourceContainedRule接口的规则,在系统中为DatabaseDiscoveryRule对象,即数据库发现规则。

动态读写分离配置示例

rules:- !READWRITE_SPLITTINGdataSources:readwrite_ds:  rw_ds    #逻辑数据源dynamicStrategy: #动态策略autoAwareDataSourceName: awareDataSource #允许自动识别的数据源名writeDataSourceQueryEnabled:  true #允许写库进行读操作- !DB_DISCOVERYdataSources:awareDataSource:  # 组名,同dynamicStrategy的autoAwareDataSourceNamedataSourceNames: ds_$->{1..2}discoveryHeartbeatName: ds_heartbeat   #心跳检测discoveryTypeName: MySQLMGRdiscoveryHeartbeats:ds_heartbeat:props:keep-alive-cron: '0/5 * * * * ?'discoveryTypes:    # 发现类型MySQLMGR:type: MySQL.MGRprops:group-name: your_group_name

DatabaseDiscoveryRule

数据库发现规则配置最后会解析成DatabaseDiscoveryRule对象。DatabaseDiscoveryRule的源码如下:

package org.apache.shardingsphere.dbdiscovery.rule;/*** 数据库发现规则*/
public final class DatabaseDiscoveryRule implements DatabaseRule, DataSourceContainedRule, DynamicDataSourceContainedRule, ExportableRule {// DatabaseDiscoveryRuleConfiguration对象@Getterprivate final RuleConfiguration configuration;// 数据源名称,默认为logic_dbprivate final String databaseName;// 数据库发现配置的数据源及对应的数据源对象private final Map<String, DataSource> dataSourceMap;// 配置的数据库发现提供算法对象信息private final Map<String, DatabaseDiscoveryProviderAlgorithm> discoveryTypes;// 数据库规则对象。key:组名(组名需为逻辑数据源或真实数据源的名称),一组一个DatabaseDiscoveryDataSourceRule对象@Getterprivate final Map<String, DatabaseDiscoveryDataSourceRule> dataSourceRules;private final InstanceContext instanceContext;// 针对Zookeeper集群的mode的日程上下文对象(非Zookeeper为空方法实例)private final ScheduleContext scheduleContext;/*** @param databaseName 数据源名称,默认为logic_db* @param dataSourceMap 数据库发现配置的数据源及对应的数据源对象* @param ruleConfig 数据库发现规则配置对象* @param instanceContext 实例上下文*/public DatabaseDiscoveryRule(final String databaseName, final Map<String, DataSource> dataSourceMap, final DatabaseDiscoveryRuleConfiguration ruleConfig, final InstanceContext instanceContext) {configuration = ruleConfig;this.databaseName = databaseName;this.dataSourceMap = dataSourceMap;this.instanceContext = instanceContext;this.scheduleContext = ScheduleContextFactory.newInstance(instanceContext.getModeConfiguration());discoveryTypes = getDiscoveryProviderAlgorithms(ruleConfig.getDiscoveryTypes());dataSourceRules = getDataSourceRules(ruleConfig.getDataSources(), ruleConfig.getDiscoveryHeartbeats());findPrimaryReplicaRelationship(databaseName, dataSourceMap);initHeartBeatJobs();}/*** 解析配置的算法信息,根据类型创建对应的算法对象* @param discoveryTypesConfig* @return*/private static Map<String, DatabaseDiscoveryProviderAlgorithm> getDiscoveryProviderAlgorithms(final Map<String, AlgorithmConfiguration> discoveryTypesConfig) {Map<String, DatabaseDiscoveryProviderAlgorithm> result = new LinkedHashMap<>(discoveryTypesConfig.size(), 1);for (Entry<String, AlgorithmConfiguration> entry : discoveryTypesConfig.entrySet()) {result.put(entry.getKey(), ShardingSphereAlgorithmFactory.createAlgorithm(entry.getValue(), DatabaseDiscoveryProviderAlgorithm.class));}return result;}/*** 获取数据源对象,key:组名,一组一个DatabaseDiscoveryDataSourceRule对象* @param dataSources 配置的数据源集合* @param heartbeatConfig 心跳配置* @return*/private Map<String, DatabaseDiscoveryDataSourceRule> getDataSourceRules(final Collection<DatabaseDiscoveryDataSourceRuleConfiguration> dataSources,final Map<String, DatabaseDiscoveryHeartBeatConfiguration> heartbeatConfig) {Map<String, DatabaseDiscoveryDataSourceRule> result = new HashMap<>(dataSources.size(), 1);// 遍历 配置的数据源集合for (DatabaseDiscoveryDataSourceRuleConfiguration each : dataSources) {// key为组名result.put(each.getGroupName(), new DatabaseDiscoveryDataSourceRule(each, Strings.isNullOrEmpty(each.getDiscoveryHeartbeatName()) ? new Properties(): heartbeatConfig.get(each.getDiscoveryHeartbeatName()).getProps(), discoveryTypes.get(each.getDiscoveryTypeName())));}return result;}/*** 查找主副本关系* @param databaseName* @param dataSourceMap 数据源Map集合*/private void findPrimaryReplicaRelationship(final String databaseName, final Map<String, DataSource> dataSourceMap) {// 遍历配置的数据源规则for (Entry<String, DatabaseDiscoveryDataSourceRule> entry : dataSourceRules.entrySet()) {String groupName = entry.getKey();DatabaseDiscoveryDataSourceRule dataSourceRule = entry.getValue();// 从dataSourceMap中获取数据库发现配置的数据源对象Map<String, DataSource> originalDataSourceMap = dataSourceRule.getDataSourceGroup(dataSourceMap);// 创建数据库发现引擎DatabaseDiscoveryEngine engine = new DatabaseDiscoveryEngine(dataSourceRule.getDatabaseDiscoveryProviderAlgorithm(), instanceContext.getEventBusContext());engine.checkEnvironment(databaseName, originalDataSourceMap);// 通过引擎,修改主数据源名称dataSourceRule.changePrimaryDataSourceName(engine.changePrimaryDataSource(databaseName, groupName, entry.getValue().getPrimaryDataSourceName(), originalDataSourceMap, dataSourceRule.getDisabledDataSourceNames()));}}/*** 从Map数据源集合的value中获取第一个数据源规则对象* @return*/public DatabaseDiscoveryDataSourceRule getSingleDataSourceRule() {return dataSourceRules.values().iterator().next();}/*** 根据数据源名称,获取数据库发现数据源规则* @param dataSourceName* @return*/public Optional<DatabaseDiscoveryDataSourceRule> findDataSourceRule(final String dataSourceName) {return Optional.ofNullable(dataSourceRules.get(dataSourceName));}@Overridepublic Map<String, Collection<String>> getDataSourceMapper() {Map<String, Collection<String>> result = new HashMap<>();for (Entry<String, DatabaseDiscoveryDataSourceRule> entry : dataSourceRules.entrySet()) {result.putAll(entry.getValue().getDataSourceMapper());}return result;}@Overridepublic void restartHeartBeatJob(final DataSourceStatusChangedEvent event) {PrimaryDataSourceChangedEvent dataSourceEvent = (PrimaryDataSourceChangedEvent) event;QualifiedDatabase qualifiedDatabase = dataSourceEvent.getQualifiedDatabase();DatabaseDiscoveryDataSourceRule dataSourceRule = dataSourceRules.get(qualifiedDatabase.getGroupName());Preconditions.checkNotNull(dataSourceRule, "Can not find database discovery data source rule in database `%s`", databaseName);dataSourceRule.changePrimaryDataSourceName(qualifiedDatabase.getDataSourceName());initHeartBeatJobs();}@Overridepublic void closeSingleHeartBeatJob(final String groupName) {DatabaseDiscoveryDataSourceRule dataSourceRule = dataSourceRules.get(groupName);Preconditions.checkNotNull(dataSourceRule, "Can not find database discovery data source rule in database `%s`", databaseName);scheduleContext.closeSchedule(dataSourceRule.getDatabaseDiscoveryProviderAlgorithm().getType() + "-" + databaseName + "-" + dataSourceRule.getGroupName());}@Overridepublic void closeAllHeartBeatJob() {for (Entry<String, DatabaseDiscoveryDataSourceRule> entry : dataSourceRules.entrySet()) {DatabaseDiscoveryDataSourceRule rule = entry.getValue();scheduleContext.closeSchedule(rule.getDatabaseDiscoveryProviderAlgorithm().getType() + "-" + databaseName + "-" + rule.getGroupName());}}/*** 初始化心跳检测的任务*/private void initHeartBeatJobs() {for (Entry<String, DatabaseDiscoveryDataSourceRule> entry : dataSourceRules.entrySet()) {DatabaseDiscoveryDataSourceRule rule = entry.getValue();// 创建任务名称String jobName = rule.getDatabaseDiscoveryProviderAlgorithm().getType() + "-" + databaseName + "-" + rule.getGroupName();// 根据配置信息,创建cron任务CronJob job = new CronJob(jobName, each -> new HeartbeatJob(databaseName, rule.getGroupName(), rule.getPrimaryDataSourceName(), rule.getDataSourceGroup(dataSourceMap),rule.getDatabaseDiscoveryProviderAlgorithm(), rule.getDisabledDataSourceNames(), instanceContext.getEventBusContext()).execute(null),rule.getHeartbeatProps().getProperty("keep-alive-cron"));// 添加到日程上下文中,根据cron,定时检测心跳scheduleContext.startSchedule(job);}}@Overridepublic String getPrimaryDataSourceName(final String dataSourceName) {return dataSourceRules.get(dataSourceName).getPrimaryDataSourceName();}/*** 通过数据源规则,从对应的逻辑数据源(组名)中获取副本数据源名称* @param dataSourceName data source name* @return*/@Overridepublic Collection<String> getReplicaDataSourceNames(final String dataSourceName) {return dataSourceRules.get(dataSourceName).getReplicaDataSourceNames();}@Overridepublic void updateStatus(final DataSourceStatusChangedEvent event) {StorageNodeDataSourceChangedEvent dataSourceChangedEvent = (StorageNodeDataSourceChangedEvent) event;DatabaseDiscoveryDataSourceRule dataSourceRule = dataSourceRules.get(dataSourceChangedEvent.getQualifiedDatabase().getGroupName());Preconditions.checkNotNull(dataSourceRule, "Can not find database discovery data source rule in database `%s`", databaseName);if (StorageNodeStatus.isDisable(dataSourceChangedEvent.getDataSource().getStatus())) {dataSourceRule.disableDataSource(dataSourceChangedEvent.getQualifiedDatabase().getDataSourceName());} else {dataSourceRule.enableDataSource(dataSourceChangedEvent.getQualifiedDatabase().getDataSourceName());}}@Overridepublic Map<String, Object> getExportData() {return Collections.singletonMap(ExportableConstants.EXPORT_DB_DISCOVERY_PRIMARY_DATA_SOURCES, exportPrimaryDataSourceMap());}private Map<String, String> exportPrimaryDataSourceMap() {Map<String, String> result = new HashMap<>(dataSourceRules.size(), 1);dataSourceRules.forEach((name, dataSourceRule) -> result.put(dataSourceRule.getGroupName(), dataSourceRule.getPrimaryDataSourceName()));return result;}@Overridepublic String getType() {return DatabaseDiscoveryRule.class.getSimpleName();}
}

4.1 构造方法

在构造方法中,执行如下:

1)记录配置对象、数据库名、配置的数据源、示例上下文;

2)创建日程上下文,用于心跳检测;

3)通过配置的发现规则,创建对应的发现提供器算法DatabaseDiscoveryProviderAlgorithm对象,存放在Map中,key为配置的提供者名称;

3.1)通过配置的发现类型的 type 值,使用SPI,获取对应的 DatabaseDiscoveryProviderAlgorithm 算法对象;

3.2)可配置的type值包括:

3.2.1)openGauss.NORMAL_REPLICATION:openGauss数据库的普通复制(主从),对应的算法对象为:OpenGaussNormalReplicationDatabaseDiscoveryProviderAlgorithm;

3.2.2)MySQL.MGR:MySQL是MGR机制,对应的算法对象为:MGRMySQLDatabaseDiscoveryProviderAlgorithm;

3.2.3)MySQL.NORMAL_REPLICATION:MySQL的普通复制(主从),对应的算法对象为:MySQLNormalReplicationDatabaseDiscoveryProviderAlgorithm;

4)通过配置的发现数据源、心跳检测规则,创建DatabaseDiscoveryDataSourceRule对象,存放在Map中,key为组名;

5)查找 DatabaseDiscoveryDataSourceRule 数据源中的主副本关系;

5.1)遍历 4)中的 DatabaseDiscoveryDataSourceRule;

5.2)执行DatabaseDiscoveryDataSourceRule的getDataSourceGroup()方法,从当前系统配置的数据源中查找当前数据库发现规则中配置的数据源DataSource的Map对象;

5.3)创建DatabaseDiscoveryEngine引擎;

5.4)通过DatabaseDiscoveryEngine引擎,进行环境检测,即检测对应数据源的有效性。通过对应的发现提供器算法DatabaseDiscoveryProviderAlgorithm对象,连接数据源,进行检测;

5.5)自动检测数据库发现中配置的数据源中的主数据源,并记录。通过对应的发现提供器算法DatabaseDiscoveryProviderAlgorithm对象,连接数据源,获取当前对应组的主数据源;

4.2 获取主数据源

通过当前分片或单表转换后的数据源名,匹配对应数据库发现规则的组名,找到 DatabaseDiscoveryDataSourceRule,获取主数据源名称。

4.3 获取副本数据源

通过当前分片或单表转换后的数据源名,匹配对应数据库发现规则的组名,找到 DatabaseDiscoveryDataSourceRule,获取数据库发现规则中配置的数据源中,可用且不是主数据源的其他数据源。

MySQL的MGR

简介

MySQL MGR(MySQL Group Replication)是MySQL官方在MySQL 5.7.17版本中以插件形式推出的主从复制高可用技术,可以为MySQL集群系统提供数据冗余和故障转移能力。

MGR基于原生的主从复制,将各节点归入到一个组中,通过组内节点的通信协商(组通信协议基于Paxos算法),实现数据的强一致性、故障探测、冲突检测、节点加组、节点离组等功能。它使用Paxos分布式算法来提供节点间的分布式协调,要求组中大多数节点在线才能达到法定票数,从而对一个决策做出一致的决定。

工作模式

MGR可以以单主模式或多主模式运行,通过group_replication_single_primary_mode=[ON|OFF]变量指定工作模式。组内所有成员都必须运行相同的工作模式。

  • 单主模式:从复制组中众多个MySQL节点中自动选举一个master节点,只有master节点可以写,其他节点自动设置为read only。当master节点故障时,会自动选举一个新的master节点,选举成功后,它将设置为可写,其他slave将指向这个新的master。

  • 多主模式:复制组中的任何一个节点都可以写,因此没有master和slave的概念。只要突然故障的节点数量不太多,这个多主模型就能继续可用。但需要注意的是,多主模式下可能存在数据冲突和一致性问题,因此在使用时需要谨慎。

与普通主从复制的区别

MySQL组复制分单主模式和多主模式。如果仅使用MySQL主从模式,MySQL主从模式的复制技术仅解决了数据同步的问题,如果 master 宕机,意味着数据库管理员需要介入,应用系统可能需要修改数据库连接地址或者重启才能实现。组复制在数据库层面上做到了,只要集群中大多数主机可用,则服务可用,例如有一个3台的服务器集群,则允许其中1台宕机。

常用SQL语句

1)查看集群成员及其状态

SELECT * FROM performance_schema.replication_group_members;

此语句用于显示集群中所有成员的信息,包括成员ID、主机名、端口号、状态、角色和版本等。

2)检查MySQL服务器上group_replication插件的状态

SELECT PLUGIN_STATUS FROM information_schema.PLUGINS WHERE PLUGIN_NAME='group_replication';

这个查询会返回group_replication插件的当前状态,状态值可能是以下几种之一:

ACTIVE:表示插件已启用并正在运行。
INACTIVE:表示插件已安装但未启用。
DISABLED:表示插件已被禁用(在某些MySQL版本中,这个状态可能不被明确使用,插件可能只显示为INACTIVE,但通过设置disabled_plugins系统变量来禁用)。

3)检查MySQL服务器上MGR是否以单主模式执行

SELECT VARIABLE_VALUE FROM performance_schema.global_variables WHERE VARIABLE_NAME='group_replication_single_primary_mode'

这个查询会返回一个结果集,其中包含一个列VARIABLE_VALUE,该列的值指示了group_replication_single_primary_mode的设置:

  • 如果值为ON,则表示MGR集群配置为单主模式。在这种模式下,集群会自动选举一个主节点来处理写操作,而其他节点则作为从节点,只能处理读操作(除非配置了读写分离策略)。

  • 如果值为OFF,则表示MGR集群配置为多主模式。在这种模式下,集群中的任何节点都可以处理写操作,但需要小心处理数据冲突和一致性问题。

MGRMySQLDatabaseDiscoveryProviderAlgorithm

MGRMySQLDatabaseDiscoveryProviderAlgorithm为MySQL的MGR对应的数据库发现提供者算法对象。

MGRMySQLDatabaseDiscoveryProviderAlgorithm的源码如下:

package org.apache.shardingsphere.dbdiscovery.mysql.type;/*** MySQL Group Replication 数据库发现提供算法*/
@Getter
public final class MGRMySQLDatabaseDiscoveryProviderAlgorithm implements DatabaseDiscoveryProviderAlgorithm {/*** 查询 MGR 状态*/private static final String QUERY_PLUGIN_STATUS = "SELECT PLUGIN_STATUS FROM information_schema.PLUGINS WHERE PLUGIN_NAME='group_replication'";/*** 查询单主节点模式的可用值*/private static final String QUERY_SINGLE_PRIMARY_MODE = "SELECT VARIABLE_VALUE FROM performance_schema.global_variables WHERE VARIABLE_NAME='group_replication_single_primary_mode'";/*** 查询组名*/private static final String QUERY_GROUP_NAME = "SELECT VARIABLE_VALUE FROM performance_schema.global_variables WHERE VARIABLE_NAME='group_replication_group_name'";/***  查询所有数据源节点信息*/private static final String QUERY_MEMBER_LIST = "SELECT MEMBER_HOST, MEMBER_PORT, MEMBER_STATE FROM performance_schema.replication_group_members";/***  查询主数据源节点*/private static final String QUERY_PRIMARY_DATA_SOURCE = "SELECT MEMBER_HOST, MEMBER_PORT FROM performance_schema.replication_group_members WHERE MEMBER_ID = "+ "(SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'group_replication_primary_member')";/*** 查询某个ip节点的数据源状态*/private static final String QUERY_CURRENT_MEMBER_STATE = "SELECT MEMBER_STATE FROM performance_schema.replication_group_members WHERE MEMBER_HOST=? AND MEMBER_PORT=?";private Properties props;@Overridepublic void init(final Properties props) {this.props = props;}/*** 环境检测* @param databaseName database name* @param dataSources data sources*/@Overridepublic void checkEnvironment(final String databaseName, final Collection<DataSource> dataSources) {// 创建线程池ExecutorService executorService = ExecutorEngine.createExecutorEngineWithCPUAndResources(dataSources.size()).getExecutorServiceManager().getExecutorService();Collection<CompletableFuture<Void>> completableFutures = new LinkedList<>();// 遍历检测数据源for (DataSource dataSource : dataSources) {completableFutures.add(runAsyncCheckEnvironment(databaseName, dataSource, executorService));}CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0]));Iterator<CompletableFuture<Void>> mgrInstancesFuture = completableFutures.stream().iterator();while (mgrInstancesFuture.hasNext()) {// 确保所有的数据源都是有效的mgrInstancesFuture.next().join();}}/*** 异步运行环境检测* @param databaseName* @param dataSource* @param executorService* @return*/private CompletableFuture<Void> runAsyncCheckEnvironment(final String databaseName, final DataSource dataSource, final ExecutorService executorService) {return CompletableFuture.runAsync(() -> {try {checkSingleDataSourceEnvironment(databaseName, dataSource);} catch (final SQLException ex) {throw new SQLWrapperException(ex);}}, executorService);}/*** 检测单数据源环境* @param databaseName* @param dataSource* @throws SQLException*/private void checkSingleDataSourceEnvironment(final String databaseName, final DataSource dataSource) throws SQLException {try (Connection connection = dataSource.getConnection();Statement statement = connection.createStatement()) {checkPluginActive(databaseName, statement);checkSinglePrimaryMode(databaseName, statement);checkGroupName(databaseName, statement);checkMemberInstanceURL(databaseName, connection.getMetaData().getURL(), statement);}}/*** 检测MGR插件状态,确认是否为ACTIVE* @param databaseName* @param statement* @throws SQLException*/private void checkPluginActive(final String databaseName, final Statement statement) throws SQLException {try (ResultSet resultSet = statement.executeQuery(QUERY_PLUGIN_STATUS)) {ShardingSpherePreconditions.checkState(resultSet.next() && "ACTIVE".equals(resultSet.getString("PLUGIN_STATUS")), () -> new InvalidMGRPluginException(databaseName));}}/*** 检测单主节点模式的可用值* @param databaseName* @param statement* @throws SQLException*/private void checkSinglePrimaryMode(final String databaseName, final Statement statement) throws SQLException {try (ResultSet resultSet = statement.executeQuery(QUERY_SINGLE_PRIMARY_MODE)) {ShardingSpherePreconditions.checkState(resultSet.next() && "ON".equals(resultSet.getString("VARIABLE_VALUE")), () -> new InvalidMGRModeException(databaseName));}}/*** 检测组名是否和配置的组名一致* @param databaseName* @param statement* @throws SQLException*/private void checkGroupName(final String databaseName, final Statement statement) throws SQLException {try (ResultSet resultSet = statement.executeQuery(QUERY_GROUP_NAME)) {ShardingSpherePreconditions.checkState(resultSet.next() && props.getProperty("group-name", "").equals(resultSet.getString("VARIABLE_VALUE")),() -> new InvalidMGRGroupNameConfigurationException(props.getProperty("group-name"), databaseName));}}/*** 检测url对应的数据源的地址是否为MGR集群中的节点* @param databaseName* @param url* @param statement* @throws SQLException*/private void checkMemberInstanceURL(final String databaseName, final String url, final Statement statement) throws SQLException {try (ResultSet resultSet = statement.executeQuery(QUERY_MEMBER_LIST)) {while (resultSet.next()) {if (url.contains(String.join(":", resultSet.getString("MEMBER_HOST"), resultSet.getString("MEMBER_PORT")))) {return;}}}throw new InvalidMGRReplicationGroupMemberException(url, databaseName);}/*** 判断dataSource是否为主节点* @param dataSource data source to be judged* @return* @throws SQLException*/@Overridepublic boolean isPrimaryInstance(final DataSource dataSource) throws SQLException {try (Connection connection = dataSource.getConnection();Statement statement = connection.createStatement();ResultSet resultSet = statement.executeQuery(QUERY_PRIMARY_DATA_SOURCE)) {if (resultSet.next()) {MySQLDataSourceMetaData metaData = new MySQLDataSourceMetaData(connection.getMetaData().getURL());return metaData.getHostname().equals(resultSet.getString("MEMBER_HOST")) && Integer.toString(metaData.getPort()).equals(resultSet.getString("MEMBER_PORT"));}}return false;}@Overridepublic ReplicaDataSourceStatus loadReplicaStatus(final DataSource replicaDataSource) throws SQLException {try (Connection connection = replicaDataSource.getConnection()) {return new ReplicaDataSourceStatus(isOnlineDataSource(connection, new MySQLDataSourceMetaData(connection.getMetaData().getURL())), 0L);}}/*** 判断当前的metaData是否为online的节点* @param connection* @param metaData* @return* @throws SQLException*/private boolean isOnlineDataSource(final Connection connection, final MySQLDataSourceMetaData metaData) throws SQLException {try (PreparedStatement preparedStatement = connection.prepareStatement(QUERY_CURRENT_MEMBER_STATE)) {preparedStatement.setString(1, metaData.getHostname());preparedStatement.setString(2, Integer.toString(metaData.getPort()));try (ResultSet resultSet = preparedStatement.executeQuery()) {return resultSet.next() && "ONLINE".equals(resultSet.getString("MEMBER_STATE"));}}}@Overridepublic String getType() {return "MySQL.MGR";}
}

在MGRMySQLDatabaseDiscoveryProviderAlgorithm类中,主要完成以下实现:

1)数据源有效性检测;

通过数据源对象,执行对应MGR的SQL查询语句,检查MGR对应的状态;

2)主节点判断;

通过数据源对象,执行MGR查询主节点的SQL语句,检查对应数据源在MGR组中的状态是否为主节点;

3)副本节点状态检测;

通过数据源对象,执行MGR查询节点的SQL语句,检查对应数据源在MGR组中的状态是否为在线;

对于数据库发现规则为MySQL.MGR的读写分离动态策略中,对应的DynamicReadwriteSplittingStrategy对象中的DynamicDataSourceContainedRule对象为DatabaseDiscoveryRule对象,其对应的提供者算法为MGRMySQLDatabaseDiscoveryProviderAlgorithm对象。

在DatabaseDiscoveryRule对象中,通过MGRMySQLDatabaseDiscoveryProviderAlgorithm对象,获取组中的主数据源。从而保证了主库的可用性。

DatabaseDiscoverySQLRouter

数据库发现规则也可以单独作为路由器进行SQL语句的数据源路由,其执行在读写分离路由之后。

DatabaseDiscoverySQLRouter的源码如下:

package org.apache.shardingsphere.dbdiscovery.route;/*** 数据库发现SQL路由器*/
public final class DatabaseDiscoverySQLRouter implements SQLRouter<DatabaseDiscoveryRule> {@Overridepublic RouteContext createRouteContext(final QueryContext queryContext, final ShardingSphereDatabase database,final DatabaseDiscoveryRule rule, final ConfigurationProperties props, final ConnectionContext connectionContext) {RouteContext result = new RouteContext();// 获取第一个DatabaseDiscoveryDataSourceRuleDatabaseDiscoveryDataSourceRule singleDataSourceRule = rule.getSingleDataSourceRule();// 路由到主数据源String dataSourceName = new DatabaseDiscoveryDataSourceRouter(singleDataSourceRule).route();// 创建路由单元,添加到路由上下文中result.getRouteUnits().add(new RouteUnit(new RouteMapper(singleDataSourceRule.getGroupName(), dataSourceName), Collections.emptyList()));return result;}@Overridepublic void decorateRouteContext(final RouteContext routeContext,final QueryContext queryContext, final ShardingSphereDatabase database, final DatabaseDiscoveryRule rule,final ConfigurationProperties props, final ConnectionContext connectionContext) {Collection<RouteUnit> toBeRemoved = new LinkedList<>();Collection<RouteUnit> toBeAdded = new LinkedList<>();for (RouteUnit each : routeContext.getRouteUnits()) {// 获取当前路由上下文的数据源名称String dataSourceName = each.getDataSourceMapper().getLogicName();// 获取数据源对应的数据库发现规则Optional<DatabaseDiscoveryDataSourceRule> dataSourceRule = rule.findDataSourceRule(dataSourceName);// 存在对应的数据库发现规则if (dataSourceRule.isPresent() && dataSourceRule.get().getGroupName().equalsIgnoreCase(each.getDataSourceMapper().getActualName())) {toBeRemoved.add(each);// 获取规则中的主数据源String actualDataSourceName = new DatabaseDiscoveryDataSourceRouter(dataSourceRule.get()).route();// 创建路由单元,添加到路由上下文中toBeAdded.add(new RouteUnit(new RouteMapper(each.getDataSourceMapper().getLogicName(), actualDataSourceName), each.getTableMappers()));}}routeContext.getRouteUnits().removeAll(toBeRemoved);routeContext.getRouteUnits().addAll(toBeAdded);}@Overridepublic int getOrder() {return DatabaseDiscoveryOrder.ORDER;}@Overridepublic Class<DatabaseDiscoveryRule> getTypeClass() {return DatabaseDiscoveryRule.class;}
}

数据库发现路由器的路由规则比较简单,在RouteContext上下文装饰的方法中,遍历路由单元,获取当前路由数据源,匹配数据库发现中的组名,获取对应组中的主数据源。

注:在数据库发现规则中,所有的操作都是路由到主库。

小结

关于数据库发现规则先介绍到这里,以下做一个小结:

1)数据库发现规则可以和读写分离规则结合一起使用;

1.1)在读写分离规则中,配置读写分离策略为动态策略及“允许自动识别的数据源名称”;

1.2)配置数据库发现规则的数据源组名,为对应的“允许自动识别的数据源名称”;

1.3)配置数据库发现规则的提供者类型,不同的提供者类型有对应的提供者算法;

1.4)数据库发现规则对应的对象为DatabaseDiscoveryRule,该对象主要执行如下:

a)创建日程上下文,用于心跳检测,根据配置的cron,定时访问数据源,确认数据源的状态;

b)通过配置的发现规则,创建对应的发现提供器算法DatabaseDiscoveryProviderAlgorithm对象,存放在Map中,key为配置的提供者名称;

c)通过配置的发现数据源、心跳检测规则,创建DatabaseDiscoveryDataSourceRule对象,存放在Map中,key为组名;

d)查找 DatabaseDiscoveryDataSourceRule 数据源中的主副本关系,确认主数据源名称;

e)提供通过组名,获取主数据源的方法。从DatabaseDiscoveryDataSourceRule中获取;

f)提供通过组名,获取副本数据源的方法。从DatabaseDiscoveryDataSourceRule中获取;

g)通过主、副数据源,结合读写分离规则,实现了读写分离;

2)数据库发现规则也可以单独作为数据源路由器使用;

在数据库发现规则路由器中,通过相应的数据库提供者算法,获取主从数据库中的主库。将SQL路由到对应组的主数据源中执行;

3)系统默认执行的数据库发现类型包括openGauss.NORMAL_REPLICATION、MySQL.MGR:MySQL、MySQL.NORMAL_REPLICATION;

3.1)NORMAL_REPLICATION:为普通的主从复制,通过对应数据库的主从操作SQL语句确认数据源的信息(如 show slave status 等);

3.2)MySQL.MGR:MySQL Group Replication 是MySQL官方在MySQL 5.7.17版本中以插件形式推出的主从复制高可用技术,在主从复制的基础上,添加了主库选举等功能,确保主库的高可用等。在ShardingSphere中,通过MGR相关的操作SQL语句确认数据源的信息;

关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨下吧。

相关文章:

【源码】Sharding-JDBC源码分析之SQL中读写分离动态策略、数据库发现规则及DatabaseDiscoverySQLRouter路由的原理

Sharding-JDBC系列 1、Sharding-JDBC分库分表的基本使用 2、Sharding-JDBC分库分表之SpringBoot分片策略 3、Sharding-JDBC分库分表之SpringBoot主从配置 4、SpringBoot集成Sharding-JDBC-5.3.0分库分表 5、SpringBoot集成Sharding-JDBC-5.3.0实现按月动态建表分表 6、【…...

服务器卸载安装的 Node.js

卸载安装的 Node.js 版本&#xff0c;具体步骤取决于你是通过包管理器&#xff08;如 yum 或 dnf&#xff09;安装的&#xff0c;还是通过 nvm (Node Version Manager) 安装的。以下是针对这两种情况的指南。 通过包管理器卸载 Node.js 如果你是通过 yum 或 dnf 安装的 Node.…...

一键图片提取表格导出为Excel文档工具体验

在日常工作中&#xff0c;我们经常会遇到需要将图片中的表格数据转换为可编辑的Excel文件的情况。这不仅能够提高工作效率&#xff0c;还能减少手动输入数据的错误。本文将介绍一款实用的工具&#xff0c;它能够帮助我们快速实现图片到Excel的转换&#xff0c;同时保持操作的简…...

SpringBoot异常处理

SpringBoot异常处理 一、认识异常 异常分类&#xff1a; Error: 代表编译和系统错误&#xff0c;不允许捕获Exception: 标准Java库的方法所激发的异常&#xff0c;包含运行异常Runtime_Exception和非运行异常 Non_RuntimeException 的子类Runtime_Exception: 运行时异常。No…...

在 OAuth 2.0 中,refreshToken(刷新令牌)存在的意义

在 OAuth 2.0 中&#xff0c;refreshToken&#xff08;刷新令牌&#xff09; 的主要目的是为了提升用户体验和安全性&#xff0c;同时确保访问令牌的有效性。以下是需要使用 refreshToken 的原因&#xff1a; 1. 访问令牌的有限生命周期 访问令牌&#xff08;accessToken&…...

【Redis】壹 —— Redis 介绍

文章目录&#xff1a; 前言 一、认识Redis 1. Redis 用途 作为数据库 作为流引擎 二、服务端高并发分布式结构演变 1. 单机架构 2. 应用数据分离架构 3. 应用服务集群架构 4. 读写分离 / 主从分离架构 5. 冷热分离 —— 引入缓存 6. 分库分表 7. 微服务架构 8. …...

【html网页页面010】html+css制作茶品牌文创网页制作含视频元素(7页面附效果及源码)

茶主题品牌文创网页制作 &#x1f964;1、写在前面&#x1f367;2、涉及知识&#x1f333;3、网页效果完整效果(7页)&#xff1a;代码目录结构&#xff1a;page1、主页page2、精品包装page3、茶园一角page4、品牌地带page5、衍生品page6、联X我们page7、视频详情页 &#x1f30…...

【项目实战】基于python+爬虫的电影数据分析及可视化系统

注意&#xff1a;该项目只展示部分功能&#xff0c;如需了解&#xff0c;文末咨询即可。 本文目录 1.开发环境2 系统设计 2.1 设计背景2.2 设计内容 3 系统页面展示 3.1 用户页面3.2 后台页面3.3 功能展示视频 4 更多推荐5 部分功能代码 5.1 爬虫代码5.2 电影信息代码 1.开发环…...

K8S命令部署后端(流水线全自动化部署)

前言 本文为链接: 云效流水线k8s半自动部署java&#xff08;保姆级&#xff09;的补充,本文起初的目的是为了补充完善k8s流水线的全自动化部署,但是也适用于k8s的一键重启,因为使用k8s的web页面容易出现漏点的情况,因此也可以把代码保存为shell脚本,同样可以实现一键重启。关于…...

GPS北斗卫星授时服务器功能是什么?应用是什么?

GPS北斗卫星授时服务器功能是什么&#xff1f;应用是什么&#xff1f; GPS北斗卫星授时服务器功能是什么&#xff1f;应用是什么&#xff1f; 摘 要:首先对计算机网络时间同步相关技术进行了介绍,然后阐述了时间同步技术在现代计算机网络中的应用与发展,最后指出时间同步网络…...

学习笔记064——如何手动将jar包导入到maven本地库

文章目录 1、背景&#xff1a;2、方法 1、背景&#xff1a; 有时网络慢的情况&#xff0c; 本地maven库需要导入外部下载的jar包。 以便于在项目的pom文件中&#xff0c;直接写dependency写导入依赖。 2、方法 在Windows终端中&#xff0c;输入&#xff1a; mvn install:in…...

未来趋势系列 篇二:HBM题材解析和股票梳理

文章目录 系列文章HBM题材解析环氧塑封电镀液PSPI(光敏性聚酰亚胺)前驱体封装基板其他材料TSV技术封装测试股票梳理系列文章 未来趋势系列 篇一:AI题材解析和股票梳理 HBM HBM(High Bandwidth Memory,高带宽内存)是一种专为高效能运算设计的新兴高速内存接口技术。它通…...

网卡驱动测试

以下是网卡驱动不同测试类型的具体方法和命令&#xff1a; 1. 功能性测试 驱动加载/卸载测试&#xff1a; 方法&#xff1a;加载/卸载网卡驱动&#xff0c;观察日志是否报错。命令&#xff1a; modprobe <driver_name> # 加载驱动 rmmod <driver_name> # 卸载驱动…...

DDR的跨4K问题

参考视频&#xff1a;【深入理解FPGA底层逻辑】、4k边界和outsdanding_哔哩哔哩_bilibili 1、AXI4_FULL突发写一个字节是一个地址&#xff0c; 2、协议规定&#xff0c;把AXI4从机的地址区间从0进行到了4095....每4K进行一次分配 所以突发长度的计算如下&#xff1a; 另外AX…...

数据结构---栈(Stack)

1. 简介 栈&#xff08;Stack&#xff09;是计算机科学中的一种抽象数据类型&#xff0c;它遵循特定的操作顺序&#xff0c;即后进先出&#xff08;Last In First Out&#xff0c;LIFO&#xff09;。这意味着最后添加到栈中的元素将是第一个被移除的。栈的基本操作通常包括&am…...

【JavaWeb后端学习笔记】Java上传文件到阿里云对象存储服务

阿里云对象存储 1、创建阿里云对象存储节点2、上传文件2.1 修改项目配置文件2.2 定义一个Properties类获取配置信息2.3 准备一个alioss工具类2.4 创建注册类&#xff0c;将AliOssUtil 注册成Bean2.5 使用AliOssUtil 工具类上传文件2.6 注意事项 使用阿里云对象存储服务分为以下…...

Unity3D RPG战斗系统详解

前言 设计一个RPG&#xff08;角色扮演游戏&#xff09;的战斗系统是游戏开发中的关键环节&#xff0c;它决定了游戏的乐趣和挑战性。在Unity3D中&#xff0c;可以通过多种技术和工具来实现一个功能完善的战斗系统。以下是对RPG战斗系统的技术详解以及代码实现。 对惹&#x…...

Spark架构及运行流程

Spark架构图 Driver&#xff1a; 解析用户的应用程序代码&#xff0c;转化为作业(job)。创建SparkContext上下文对象&#xff0c;其负责与资源管理器(ClusterManager)通信&#xff0c;进行资源的申请、任务的分配和监控等。跟踪Executor的执行情况。可通过UI界面查询运行情况。…...

SpringBoot3整合MyBatis

一、MyBatis整合步骤: (1).导入依赖:在Spring Boot项目的构建文件(如pom.xml)中添加MyBatis和数据库驱动的相关依赖。例如&#xff0c;如果使用MySQL数据库&#xff0c;您需要添加MyBatis和MySQL驱动的依赖。 (2).配置数据源:在application.properties或application.yml中配置…...

【计网笔记】习题

物理层 不属于物理层接口规范定义范畴的是&#xff08;C&#xff09; A. 接口形状 B. 引脚功能 C. 物理地址 D. 信号电平 【2023-912】光网络只能通过导向型介质传播。&#xff08;&#xff09; 【2017-408】若信道在无噪声情况下的极限数据传输速率不小于信噪比为30dB条件下的…...

力扣56.合并区间

题目描述 题目链接56. 合并区间 以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间&#xff0c;并返回 一个不重叠的区间数组&#xff0c;该数组需恰好覆盖输入中的所有区间 。 示例 1&#xff1a; …...

【oracle】大数据删除插入

文章目录 引言本文目标 Oracle大数据插入操作插入操作的场景和需求使用并行查询进行数据插入示例代码&#xff1a;创建新表并插入数据解释代码中的关键点 性能优化建议 Oracle大数据删除操作删除操作的场景和需求使用游标和批量处理进行数据删除示例代码&#xff1a;批量删除数…...

mysql 双1设置

MySQL 的"双1"设置通常指的是两个配置参数&#xff1a;innodb_flush_log_at_trx_commit 和 sync_binlog。这两个参数都与 MySQL 的数据安全和性能有关。 innodb_flush_log_at_trx_commit&#xff1a;这个参数控制了 InnoDB 引擎中事务日志的刷新频率。它有三个可能的…...

《C++ 赋能 K-Means 聚类算法:开启智能数据分类之旅》

在当今数字化浪潮汹涌澎湃的时代&#xff0c;人工智能无疑是引领科技变革的核心驱动力之一。而在人工智能的广袤天地中&#xff0c;数据分类与聚类作为挖掘数据内在价值、揭示数据潜在规律的关键技术手段&#xff0c;正发挥着前所未有的重要作用。K-Means 聚类算法&#xff0c;…...

用Python开发一个经典贪吃蛇小游戏

Python 是开发小游戏的绝佳工具,借助第三方库,如 pygame,我们可以快速开发一个经典的贪吃蛇游戏。本篇将介绍如何用 Python 实现一个完整的贪吃蛇小游戏。 一、游戏设计 1.1 游戏规则 玩家通过方向键控制贪吃蛇移动。贪吃蛇吃到食物后会变长,同时得分增加。如果贪吃蛇撞到…...

《大宋豪侠传》客户端源码 + 服务端源码 + 工具源码 + 资源,大小16.3G

《大宋豪侠传》客户端源码 服务端源码 工具源码 资源&#xff0c;大小16.3G 下载地址&#xff1a; 通过网盘分享的文件&#xff1a;【源码】《大宋豪侠传》客户端源码 服务端源码 工具源码 资源&#xff0c;大小16.3G 链接: https://pan.baidu.com/s/1lUf84LzXKB3iM7L-1P…...

使用vue-seamless-scroll实现echarts图表大屏滚动,出现空白间隔的解决方案

一、背景介绍 最近的业务开发需求&#xff0c;想要实现echarts图表大屏滚动&#xff0c;小编首先采用vue-seamless-scroll进行实现&#xff0c;结果发现第二屏出现空白间隔&#xff0c;尝试了多种解决方案均不生效&#xff0c;最终选择换一个方案。 二、封装的ScrollList组件…...

zsh配置

zsh配置 https://zhuanlan.zhihu.com/p/58073103 $ cat .zshrc If you come from bash you might have to change your $PATH. export PATH H O M E / b i n : / u s r / l o c a l / b i n : HOME/bin:/usr/local/bin: HOME/bin:/usr/local/bin:PATH Path to your oh-my-zs…...

Brain.js(八):RNNTimeStep 实战教程 - 股票价格预测 - 实操需警慎

前置声明&#xff0c;个人浅度炒股&#xff0c;但计划将基金转入股市。然后 股市有风险&#xff0c;不是技术可以完全预测的&#xff0c;但是在无头绪的时候&#xff0c;用技术指标做个参考也不错。 本文涉及到的股票预测&#xff0c;只是代码简单示例&#xff0c;实操需警慎&a…...

Python+requests实现接口自动化测试

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 接口测试是测试系统组件间接口的一种测试。接口测试主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点。测试的重点是要检查数据的交换&#xff0c;传…...

java------------常用API preiod duration 计算时间差

1&#xff0c;preiod 如果末天数比初天数小&#xff0c;需要进一位 package API;import java.time.LocalDate; import java.time.Period;public class preiod {public static void main(String[] args) {// 计算时间差// LocalDate获取对象其中的一个方法LocalDate d1 LocalD…...

Android水波纹效果

Android水波纹效果 需要到水波纹效果的场景还蛮少的。万一刚好你需要呢 一、思路&#xff1a; 自定义组件SpreadView 二、效果图&#xff1a; 看视频更直观点&#xff1a; Android轮子分享-水波纹效果 三、关键代码&#xff1a; public class SpreadView extends View {pr…...

yolov8 转华为昇腾om脚本

目录 yolov8 转华为昇腾 om脚本 测试ok 推理demo: yolov8 转华为昇腾 om脚本 测试ok import sys import osos.chdir(os.path.dirname(os.path.abspath(__file__)))import torchcurrent_dir = os.path.dirname(os.path.abspath(__file__))paths = [os.path.abspath(__file__)…...

【人工智能】从基础到实践:用Python和PyTorch实现深度学习图像分割模型

《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 图像分割是计算机视觉中的核心任务之一,旨在将图像划分为具有语义意义的区域,在自动驾驶、医疗影像分析等领域有广泛应用。本篇文章将从图像分割的基础知识出发,详细讲解分割任务的目标、评价指标以及常…...

AI绘画设计实战-Day2

Stable Diffusion 提示词前缀 FF,(masterpiece:1.2),best quality,highres,extremely detailed CG,perfect lighting,8k wallpaper,anime,comic,game CG, FF&#xff0c;&#xff08;杰作&#xff1a;1.2&#xff09;&#xff0c;最高质量&#xff0c;高分辨率&#xff0c;极其…...

详解LeetCode地下城游戏(动态规划)——区分两种状态表示形式

地下城游戏 题目链接&#xff1a;174. 地下城游戏 状态表示&#xff1a; 按照以往题的表示&#xff0c;dp[i][j]表示&#xff1a;从起点&#xff08;0&#xff0c;0&#xff09;位置到达&#xff08;i&#xff0c;j&#xff09;位置时&#xff0c;所需的最小初始健康值。但是…...

CV(3)--噪声滤波和特征

前言 仅记录学习过程&#xff0c;有问题欢迎讨论 图像噪声&#xff08;需要主动干扰的场景&#xff09;&#xff1a; 添加高斯噪声&#xff1a;概率密度函数服从高斯分布的一类噪声 通过设置sigma和mean生成符合高斯分布的随机数&#xff0c;然后计算输出像素&#xff0c;放缩…...

[C++]常对象、常对象成员、指向对象的常指针、指向常对象的指针变量以及对象的常引用

一、 常对象 1.定义&#xff1a; 一个常对象就是声明为常量的对象。我们不能改变这个对象的任何成员数据。具体来说&#xff0c;它是通过const关键字来声明的。 2.语法格式&#xff1a; const 类名 对象名;3.代码示例&#xff1a; class MyClass { public:int x;void setX…...

Spring Boot微服务应用实战:构建高效、可扩展的服务架构

在当今的软件开发领域&#xff0c;微服务架构凭借其高度的灵活性、可扩展性和可靠性&#xff0c;已成为众多企业的首选。而Spring Boot&#xff0c;作为Spring框架的一个子项目&#xff0c;以其简洁的API、快速的应用启动以及内嵌的Servlet容器等特点&#xff0c;成为了构建微服…...

如何通过 Windows 自带的启动管理功能优化电脑启动程序

在日常使用电脑的过程中&#xff0c;您可能注意到开机后某些程序会自动运行。这些程序被称为“自启动”或“启动项”&#xff0c;它们可以在系统启动时自动加载并开始运行&#xff0c;有时甚至在后台默默工作。虽然一些启动项可能是必要的&#xff08;如杀毒软件&#xff09;&a…...

力扣每日一题 - 1812. 判断国际象棋棋盘中一个格子的颜色

题目 还需要你前往力扣官网查看详细的题目要求 地址 1.给你一个坐标 coordinates &#xff0c;它是一个字符串&#xff0c;表示国际象棋棋盘中一个格子的坐标。下图是国际象棋棋盘示意图。2.如果所给格子的颜色是白色&#xff0c;请你返回 true&#xff0c;如果是黑色&#xff…...

Python subprocess.run 使用注意事项,避免出现list index out of range

在执行iOS UI 自动化专项测试的时候&#xff0c;在运行第一遍的时候遇到了这样的错误&#xff1a; 2024-12-04 20:22:27 ERROR conftest pytest_runtest_makereport 106 Test test_open_stream.py::TestOpenStream::test_xxx_open_stream[iPhoneX-xxx-1-250] failed with err…...

UI自动化测试框架:PO模式+数据驱动

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 1. PO 设计模式简介 什么是 PO 模式&#xff1f; PO&#xff08;PageObject&#xff09;设计模式将某个页面的所有元素对象定位和对元素对象的操作封装成一个 Pa…...

第四十六篇 Vision Transformer论文翻译

论文连接:https://arxiv.org/abs/2010.11929 GitHub:https://github.com/google-research/vision_transformer 摘要 虽然Transformer架构已成为自然语言处理任务的实际标准,但其在计算机视觉中的应用仍然有限。在计算机视觉中,注意力机制要么与卷积网络结合使用,要么在保…...

如何在Ubuntu中利用repo和git地址下载获取imx6ull的BSP

01-设置git的用户名和邮箱 git config --global user.name "suwenhao" git config --global user.email "2487872782qq.com"这里不设置的话后面在第5步的repo配置中还是会要求输入&#xff0c;而且以后进行相关操作都要输入&#xff0c;不妨现在就进行配置…...

redis数据结构和内部编码及单线程架构

博主主页: 码农派大星. 数据结构专栏:Java数据结构 数据库专栏:数据库 JavaEE专栏:JavaEE 软件测试专栏:软件测试 关注博主带你了解更多知识 1. 数据结构和内部编码 Redis会在合适的场景选择合适的内部编码 我们可以通过objectencoding命令查询内部编码 : 2. 单线程架构 …...

2412d,d的6月会议

信息:gtkD的文档位置 原文 总结 DMDARM后端 Razvan问Walter他对DMD的ARM后端的分发,并想知道他是否考虑过其他选择,如整合DMD前端与LDC后端. Walter说,人们写信告诉他,他们喜欢使用DMD,因为它体积小,速度快.多年来,就要求他实现ARM后端. 有的人想写一个,但后来因为太难或太耗…...

提升网站流量的关键:AI在SEO关键词优化中的应用

内容概要 在当今数字时代&#xff0c;提升网站流量已成为每个网站管理员的首要任务。而人工智能的技术进步&#xff0c;为搜索引擎优化&#xff08;SEO&#xff09;提供了强有力的支持&#xff0c;尤其是在关键词优化方面。关键词是连接用户需求与网站内容的桥梁&#xff0c;其…...

【模型对比】ChatGPT vs Kimi vs 文心一言那个更好用?数据详细解析,找出最适合你的AI辅助工具!

在这个人工智能迅猛发展的时代&#xff0c;AI聊天助手已经深入我们的工作与生活。你是否曾在选择使用ChatGPT、Kimi或是百度的文心一言时感到一头雾水&#xff1f;每款AI都有其独特的魅力与优势&#xff0c;那么&#xff0c;究竟哪一款AI聊天助手最适合你呢&#xff1f;本文将带…...

利润表在Zebra BI 中的应用(一)

效果如图。本案例采用极简式对比 需要注意的是&#xff1a;如原始数据是一维的&#xff0c;则需要确保比较的各年份所含项目一致&#xff0c;缺失的也要占位&#xff0c;否则会出错&#xff01; 2022% of Revenue&#xff08;占收入%&#xff09; DIVIDE( [值_2022], CALCULA…...