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

OpenTenBase分布式HTAP实战:从Oracle迁移到云原生数据库的完整指南

人们眼中的天才之所以卓越非凡,并非天资超人一等而是付出了持续不断的努力。1万小时的锤炼是任何人从平凡变成超凡的必要条件。———— 马尔科姆·格拉德威尔
请在此添加图片描述

🌟 Hello,我是Xxtaoaooo!
🌈 “代码是逻辑的诗篇,架构是思想的交响”
OpenTenBase官方
OpenTenBase官方GitHub仓库 - 分布式HTAP数据库源码

摘要

作为一名在数据库领域的技术实践者,我最近有幸参与了一个系统从Oracle到OpenTenBase的迁移项目。这次经历让我深刻体会到了分布式HTAP数据库的强大能力,也见证了腾讯云开源数据库技术的成熟度。

项目背景:核心交易系统一直使用Oracle数据库,随着业务规模的快速增长,单机Oracle在处理海量数据时遇到了性能瓶颈。特别是在月末对账和实时风控场景下,OLTP和OLAP混合负载让系统压力巨大。传统的读写分离方案虽然能缓解部分压力,但数据一致性和实时性问题始终困扰着我们。

在技术选型阶段,我们评估了多个分布式数据库方案,最终选择了OpenTenBase。选择它的主要原因有三个:首先是其双内核架构能够同时兼容PostgreSQL和MySQL,降低了迁移成本;其次是HTAP混合负载能力完美契合我们的业务需求;最后是腾讯在金融行业的成功案例给了我们信心。

整个迁移过程历时三个月,涉及200多张表、TB级数据量的迁移。我们采用了分阶段迁移策略:首先搭建OpenTenBase集群环境,然后进行数据结构迁移和兼容性改造,接着是数据同步和业务切换,最后是性能优化和监控完善。

最让我印象深刻的是OpenTenBase的分布式事务能力。在传统Oracle环境下,跨库事务一直是个难题,而OpenTenBase通过GTM(全局事务管理器)实现了真正的分布式ACID事务,让我们的业务逻辑几乎不需要修改就能平滑迁移。

性能方面的提升也超出了预期。在相同硬件配置下,OLTP业务的TPS提升了40%,复杂分析查询的响应时间缩短了60%。更重要的是,系统的扩展性得到了根本性改善,我们可以根据业务增长动态添加节点,彻底解决了容量瓶颈问题。

当然,迁移过程也遇到了不少挑战。SQL兼容性、存储过程改造、监控体系重建等都需要大量的工作。但通过合理的项目规划和技术攻关,我们最终成功完成了迁移,并建立了一套完整的运维体系。

这次实践让我深刻认识到,选择合适的技术架构对业务发展的重要性。OpenTenBase不仅解决了我们当前的技术痛点,更为未来的业务扩展奠定了坚实基础。


OpenTenBase简介

OpenTenBase 是企业级分布式数据库 TDSQL 的社区发行版,包含 OpenTenBase 和 TXSQL 双内核,具备高扩展性、商业数据库语法兼容、分布式引擎、多级容灾和多维度资源隔离等能力,成功应用在金融、医疗、航天等行业的核心业务系统。

请在此添加图片描述

一、OpenTenBase技术架构深度解析

1.1 双内核架构设计

OpenTenBase最大的亮点是其独特的双内核架构,同时支持PostgreSQL和MySQL两套SQL引擎:

请在此添加图片描述

请在此添加图片描述

图1:OpenTenBase双内核分布式架构图 - 展示核心组件和数据流向

1.2 HTAP混合负载处理机制

OpenTenBase的HTAP能力通过智能的负载识别和路由机制实现:

/*** HTAP负载路由器* 根据SQL特征自动识别OLTP/OLAP负载并路由到合适的处理引擎*/
@Component
public class HTAPLoadBalancer {private static final Logger logger = LoggerFactory.getLogger(HTAPLoadBalancer.class);// OLTP特征模式private static final List<Pattern> OLTP_PATTERNS = Arrays.asList(Pattern.compile("(?i)^\\s*(INSERT|UPDATE|DELETE)\\s+.*"),Pattern.compile("(?i).*\\s+WHERE\\s+.*=\\s*[?$].*"),Pattern.compile("(?i).*\\s+LIMIT\\s+\\d{1,3}\\s*$"));// OLAP特征模式private static final List<Pattern> OLAP_PATTERNS = Arrays.asList(Pattern.compile("(?i).*\\s+(GROUP\\s+BY|ORDER\\s+BY|HAVING)\\s+.*"),Pattern.compile("(?i).*\\s+(SUM|COUNT|AVG|MAX|MIN)\\s*\\(.*"),Pattern.compile("(?i).*\\s+JOIN\\s+.*\\s+JOIN\\s+.*"));@Autowiredprivate DataSourceRouter dataSourceRouter;@Autowiredprivate QueryOptimizer queryOptimizer;/*** 分析SQL类型并路由到合适的处理引擎*/public QueryExecutionPlan routeQuery(String sql, Map<String, Object> parameters) {// 1. SQL预处理和标准化String normalizedSQL = normalizeSQL(sql);// 2. 负载类型识别WorkloadType workloadType = identifyWorkloadType(normalizedSQL);// 3. 执行计划生成QueryExecutionPlan plan = generateExecutionPlan(normalizedSQL, workloadType, parameters);logger.info("SQL路由决策: 类型={}, 目标引擎={}, SQL={}", workloadType, plan.getTargetEngine(), sql.length() > 100 ? sql.substring(0, 100) + "..." : sql);return plan;}/*** 识别工作负载类型*/private WorkloadType identifyWorkloadType(String sql) {// 计算OLTP特征得分int oltpScore = calculatePatternScore(sql, OLTP_PATTERNS);// 计算OLAP特征得分int olapScore = calculatePatternScore(sql, OLAP_PATTERNS);// 分析表访问模式TableAccessPattern accessPattern = analyzeTableAccess(sql);// 综合评分决策if (oltpScore > olapScore && accessPattern.isPointQuery()) {return WorkloadType.OLTP;} else if (olapScore > oltpScore && accessPattern.isAnalyticalQuery()) {return WorkloadType.OLAP;} else {// 混合负载,根据数据量和复杂度进一步判断return analyzeHybridWorkload(sql, accessPattern);}}/*** 生成查询执行计划*/private QueryExecutionPlan generateExecutionPlan(String sql, WorkloadType workloadType, Map<String, Object> parameters) {QueryExecutionPlan.Builder planBuilder = QueryExecutionPlan.builder().originalSQL(sql).workloadType(workloadType).parameters(parameters);switch (workloadType) {case OLTP:// OLTP负载优化:使用行存储引擎,启用索引优化planBuilder.targetEngine(EngineType.ROW_STORE).enableIndexOptimization(true).enableParallelExecution(false).maxExecutionTime(Duration.ofSeconds(5));break;case OLAP:// OLAP负载优化:使用列存储引擎,启用并行执行planBuilder.targetEngine(EngineType.COLUMN_STORE).enableIndexOptimization(false).enableParallelExecution(true).maxExecutionTime(Duration.ofMinutes(10));break;case HYBRID:// 混合负载:动态选择最优引擎EngineType optimalEngine = queryOptimizer.selectOptimalEngine(sql, parameters);planBuilder.targetEngine(optimalEngine).enableIndexOptimization(true).enableParallelExecution(true).maxExecutionTime(Duration.ofMinutes(2));break;}return planBuilder.build();}/*** 计算模式匹配得分*/private int calculatePatternScore(String sql, List<Pattern> patterns) {return (int) patterns.stream().mapToLong(pattern -> pattern.matcher(sql).find() ? 1 : 0).sum();}/*** 分析表访问模式*/private TableAccessPattern analyzeTableAccess(String sql) {// 提取表名和访问条件List<String> tables = extractTableNames(sql);List<String> conditions = extractWhereConditions(sql);boolean isPointQuery = conditions.stream().anyMatch(condition -> condition.contains("=") && !condition.contains("LIKE"));boolean isAnalyticalQuery = sql.toLowerCase().contains("group by") || sql.toLowerCase().contains("order by") ||tables.size() > 2;return TableAccessPattern.builder().tables(tables).conditions(conditions).pointQuery(isPointQuery).analyticalQuery(isAnalyticalQuery).build();}// 辅助方法实现...private String normalizeSQL(String sql) {return sql.trim().replaceAll("\\s+", " ");}private WorkloadType analyzeHybridWorkload(String sql, TableAccessPattern pattern) {// 基于启发式规则判断混合负载倾向if (pattern.getTables().size() == 1 && pattern.isPointQuery()) {return WorkloadType.OLTP;} else {return WorkloadType.HYBRID;}}private List<String> extractTableNames(String sql) {// SQL解析提取表名的实现return new ArrayList<>();}private List<String> extractWhereConditions(String sql) {// SQL解析提取WHERE条件的实现return new ArrayList<>();}// 内部类定义public enum WorkloadType {OLTP, OLAP, HYBRID}public enum EngineType {ROW_STORE, COLUMN_STORE, HYBRID_ENGINE}@Data@Builderpublic static class QueryExecutionPlan {private String originalSQL;private WorkloadType workloadType;private EngineType targetEngine;private Map<String, Object> parameters;private boolean enableIndexOptimization;private boolean enableParallelExecution;private Duration maxExecutionTime;}@Data@Builderpublic static class TableAccessPattern {private List<String> tables;private List<String> conditions;private boolean pointQuery;private boolean analyticalQuery;}
}

关键技术点:

  • 通过正则表达式识别OLTP和OLAP查询特征
  • 智能路由决策,根据SQL特征选择最优执行引擎
  • 综合评分机制,避免误判混合负载类型
  • 针对不同负载类型生成优化的执行计划

二、Oracle到OpenTenBase迁移策略

2.1 迁移架构设计

基于业务连续性要求,我们采用了分阶段的迁移策略:

请在此添加图片描述

图2:Oracle到OpenTenBase迁移时序图 - 展示分阶段迁移的完整流程

2.2 数据结构兼容性处理

Oracle和PostgreSQL在数据类型和语法上存在差异,需要进行兼容性改造:

Oracle类型OpenTenBase类型转换说明注意事项
NUMBER(p,s)NUMERIC(p,s)直接映射精度保持一致
VARCHAR2(n)VARCHAR(n)类型转换注意字符集编码
DATETIMESTAMP精度提升时区处理
CLOBTEXT大对象处理性能影响评估
BLOBBYTEA二进制数据存储方式差异
ROWID无直接对应业务逻辑改造使用主键替代
-- Oracle原始表结构
CREATE TABLE customer_info (customer_id NUMBER(10) PRIMARY KEY,customer_name VARCHAR2(100) NOT NULL,create_date DATE DEFAULT SYSDATE,balance NUMBER(15,2) DEFAULT 0,status NUMBER(1) DEFAULT 1,remark CLOB
);-- OpenTenBase兼容表结构
CREATE TABLE customer_info (customer_id NUMERIC(10) PRIMARY KEY,customer_name VARCHAR(100) NOT NULL,create_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,balance NUMERIC(15,2) DEFAULT 0,status SMALLINT DEFAULT 1,remark TEXT
) DISTRIBUTE BY HASH(customer_id);

2.3 自动化迁移工具开发

为了提高迁移效率,我们开发了专门的迁移工具:

/*** Oracle到OpenTenBase自动化迁移工具* 支持表结构转换、数据迁移、SQL兼容性检查*/
@Component
public class OracleToOpenTenBaseMigrator {private static final Logger logger = LoggerFactory.getLogger(OracleToOpenTenBaseMigrator.class);@Autowiredprivate JdbcTemplate oracleTemplate;@Autowiredprivate JdbcTemplate openTenBaseTemplate;@Autowiredprivate SchemaConverter schemaConverter;@Autowiredprivate DataMigrationService dataMigrationService;/*** 执行完整的数据库迁移流程*/public MigrationResult executeMigration(MigrationConfig config) {logger.info("开始执行Oracle到OpenTenBase迁移,配置: {}", config);MigrationResult result = new MigrationResult();try {// 1. 预检查PreCheckResult preCheck = performPreCheck(config);if (!preCheck.isSuccess()) {result.setSuccess(false);result.setErrorMessage("预检查失败: " + preCheck.getErrorMessage());return result;}// 2. 表结构迁移logger.info("开始表结构迁移...");SchemaConversionResult schemaResult = migrateSchema(config.getSourceTables());result.setSchemaConversionResult(schemaResult);// 3. 数据迁移logger.info("开始数据迁移...");DataMigrationResult dataResult = migrateData(config.getSourceTables(), config.getBatchSize());result.setDataMigrationResult(dataResult);// 4. 索引和约束迁移logger.info("开始索引和约束迁移...");IndexMigrationResult indexResult = migrateIndexesAndConstraints(config.getSourceTables());result.setIndexMigrationResult(indexResult);// 5. 数据一致性验证logger.info("开始数据一致性验证...");ConsistencyCheckResult consistencyResult = verifyDataConsistency(config.getSourceTables());result.setConsistencyCheckResult(consistencyResult);result.setSuccess(true);logger.info("迁移完成,总耗时: {}ms", result.getTotalDuration());} catch (Exception e) {logger.error("迁移过程中发生错误", e);result.setSuccess(false);result.setErrorMessage(e.getMessage());}return result;}/*** 迁移表结构*/private SchemaConversionResult migrateSchema(List<String> tables) {SchemaConversionResult result = new SchemaConversionResult();for (String tableName : tables) {try {// 获取Oracle表结构TableSchema oracleSchema = extractOracleTableSchema(tableName);// 转换为OpenTenBase兼容结构TableSchema openTenBaseSchema = schemaConverter.convertSchema(oracleSchema);// 生成建表SQLString createTableSQL = generateCreateTableSQL(openTenBaseSchema);// 执行建表openTenBaseTemplate.execute(createTableSQL);result.addSuccessTable(tableName);logger.info("表 {} 结构迁移成功", tableName);} catch (Exception e) {logger.error("表 {} 结构迁移失败", tableName, e);result.addFailedTable(tableName, e.getMessage());}}return result;}/*** 迁移数据*/private DataMigrationResult migrateData(List<String> tables, int batchSize) {DataMigrationResult result = new DataMigrationResult();for (String tableName : tables) {try {logger.info("开始迁移表 {} 的数据", tableName);// 获取表的总记录数long totalRows = getTotalRowCount(tableName);result.addTableRowCount(tableName, totalRows);// 分批迁移数据long migratedRows = 0;int offset = 0;while (offset < totalRows) {// 从Oracle读取一批数据List<Map<String, Object>> batch = readDataBatch(tableName, offset, batchSize);if (batch.isEmpty()) {break;}// 写入OpenTenBaseint insertedRows = insertDataBatch(tableName, batch);migratedRows += insertedRows;offset += batchSize;// 记录进度double progress = (double) migratedRows / totalRows * 100;logger.info("表 {} 迁移进度: {}/{} ({}%)", tableName, migratedRows, totalRows, String.format("%.1f", progress));}result.addSuccessTable(tableName, migratedRows);logger.info("表 {} 数据迁移完成,共迁移 {} 行", tableName, migratedRows);} catch (Exception e) {logger.error("表 {} 数据迁移失败", tableName, e);result.addFailedTable(tableName, e.getMessage());}}return result;}/*** 读取Oracle数据批次*/private List<Map<String, Object>> readDataBatch(String tableName, int offset, int batchSize) {String sql = String.format("SELECT * FROM (SELECT t.*, ROWNUM rn FROM %s t WHERE ROWNUM <= ?) WHERE rn > ?",tableName);return oracleTemplate.queryForList(sql, offset + batchSize, offset);}/*** 批量插入数据到OpenTenBase*/private int insertDataBatch(String tableName, List<Map<String, Object>> batch) {if (batch.isEmpty()) {return 0;}// 构建批量插入SQLMap<String, Object> firstRow = batch.get(0);List<String> columns = new ArrayList<>(firstRow.keySet());String sql = buildBatchInsertSQL(tableName, columns);// 准备批量参数List<Object[]> batchArgs = batch.stream().map(row -> columns.stream().map(col -> convertValue(row.get(col))).toArray()).collect(Collectors.toList());// 执行批量插入int[] results = openTenBaseTemplate.batchUpdate(sql, batchArgs);return Arrays.stream(results).sum();}/*** 构建批量插入SQL*/private String buildBatchInsertSQL(String tableName, List<String> columns) {String columnList = String.join(", ", columns);String placeholders = columns.stream().map(col -> "?").collect(Collectors.joining(", "));return String.format("INSERT INTO %s (%s) VALUES (%s)", tableName, columnList, placeholders);}/*** 数据类型转换*/private Object convertValue(Object value) {if (value == null) {return null;}// Oracle特殊类型转换if (value instanceof oracle.sql.TIMESTAMP) {return ((oracle.sql.TIMESTAMP) value).timestampValue();} else if (value instanceof oracle.sql.CLOB) {try {oracle.sql.CLOB clob = (oracle.sql.CLOB) value;return clob.getSubString(1, (int) clob.length());} catch (Exception e) {logger.warn("CLOB转换失败", e);return null;}}return value;}private PreCheckResult performPreCheck(MigrationConfig config) {// 预检查实现return new PreCheckResult();}private TableSchema extractOracleTableSchema(String tableName) {// Oracle表结构提取实现return new TableSchema();}private String generateCreateTableSQL(TableSchema schema) {// 建表SQL生成实现return "";}private long getTotalRowCount(String tableName) {return oracleTemplate.queryForObject("SELECT COUNT(*) FROM " + tableName, Long.class);}private IndexMigrationResult migrateIndexesAndConstraints(List<String> tables) {// 索引和约束迁移实现return new IndexMigrationResult();}private ConsistencyCheckResult verifyDataConsistency(List<String> tables) {// 数据一致性验证实现return new ConsistencyCheckResult();}
}

关键实现要点:

  • 分阶段迁移流程,确保每个步骤的可控性
  • 批量数据迁移,提高迁移效率
  • 使用ROWNUM分页读取Oracle数据
  • 批量插入优化,减少网络开销
  • Oracle特殊数据类型转换处理

三、分布式事务与数据一致性

3.1 GTM全局事务管理

OpenTenBase通过GTM(Global Transaction Manager)实现分布式事务的ACID特性:

请在此添加图片描述

图3:GTM分布式事务处理流程图 - 展示全局事务管理的完整过程

3.2 分布式事务实现

在业务代码中,分布式事务的使用与传统事务基本一致:

/*** 分布式事务业务服务* 演示OpenTenBase分布式事务的使用方式*/
@Service
@Transactional
public class DistributedTransactionService {private static final Logger logger = LoggerFactory.getLogger(DistributedTransactionService.class);@Autowiredprivate AccountRepository accountRepository;@Autowiredprivate TransactionLogRepository transactionLogRepository;@Autowiredprivate NotificationService notificationService;/*** 跨节点转账业务* 涉及多个数据节点的分布式事务*/@Transactional(rollbackFor = Exception.class)public TransferResult transferMoney(TransferRequest request) {logger.info("开始执行跨节点转账: {}", request);try {// 1. 参数验证validateTransferRequest(request);// 2. 获取转出账户(可能在DataNode1)Account fromAccount = accountRepository.findByAccountNo(request.getFromAccount());if (fromAccount == null) {throw new BusinessException("转出账户不存在");}// 3. 获取转入账户(可能在DataNode2)Account toAccount = accountRepository.findByAccountNo(request.getToAccount());if (toAccount == null) {throw new BusinessException("转入账户不存在");}// 4. 余额检查if (fromAccount.getBalance().compareTo(request.getAmount()) < 0) {throw new BusinessException("账户余额不足");}// 5. 执行转账操作(分布式事务自动处理)// 扣减转出账户余额fromAccount.setBalance(fromAccount.getBalance().subtract(request.getAmount()));fromAccount.setUpdateTime(LocalDateTime.now());accountRepository.save(fromAccount);// 增加转入账户余额toAccount.setBalance(toAccount.getBalance().add(request.getAmount()));toAccount.setUpdateTime(LocalDateTime.now());accountRepository.save(toAccount);// 6. 记录交易日志(可能在DataNode3)TransactionLog log = TransactionLog.builder().transactionId(generateTransactionId()).fromAccount(request.getFromAccount()).toAccount(request.getToAccount()).amount(request.getAmount()).transactionType(TransactionType.TRANSFER).status(TransactionStatus.SUCCESS).createTime(LocalDateTime.now()).build();transactionLogRepository.save(log);// 7. 发送通知(外部服务调用)sendTransferNotification(request, log.getTransactionId());logger.info("转账成功完成: 交易ID={}", log.getTransactionId());return TransferResult.builder().success(true).transactionId(log.getTransactionId()).message("转账成功").build();} catch (Exception e) {logger.error("转账失败: {}", request, e);// 分布式事务会自动回滚所有操作return TransferResult.builder().success(false).message("转账失败: " + e.getMessage()).build();}}/*** 批量转账业务* 演示复杂分布式事务场景*/@Transactional(rollbackFor = Exception.class)public BatchTransferResult batchTransfer(List<TransferRequest> requests) {logger.info("开始执行批量转账,共 {} 笔", requests.size());BatchTransferResult result = new BatchTransferResult();try {// 1. 预检查所有转账请求for (TransferRequest request : requests) {validateTransferRequest(request);}// 2. 批量执行转账for (TransferRequest request : requests) {TransferResult singleResult = executeSingleTransfer(request);result.addResult(singleResult);if (!singleResult.isSuccess()) {// 任何一笔失败都会导致整个批次回滚throw new BusinessException("批量转账中第 " + result.getResults().size() + " 笔失败: " + singleResult.getMessage());}}// 3. 记录批次日志BatchTransactionLog batchLog = BatchTransactionLog.builder().batchId(generateBatchId()).totalCount(requests.size()).successCount(result.getSuccessCount()).totalAmount(calculateTotalAmount(requests)).status(BatchStatus.SUCCESS).createTime(LocalDateTime.now()).build();transactionLogRepository.saveBatchLog(batchLog);result.setSuccess(true);result.setBatchId(batchLog.getBatchId());logger.info("批量转账成功完成,批次ID: {}", batchLog.getBatchId());} catch (Exception e) {logger.error("批量转账失败", e);result.setSuccess(false);result.setErrorMessage(e.getMessage());// 整个批次的所有操作都会被回滚}return result;}/*** 执行单笔转账(内部方法,不开启新事务)*/private TransferResult executeSingleTransfer(TransferRequest request) {// 实现单笔转账逻辑,复用transferMoney的核心逻辑// 但不开启新的事务,参与当前分布式事务Account fromAccount = accountRepository.findByAccountNo(request.getFromAccount());Account toAccount = accountRepository.findByAccountNo(request.getToAccount());if (fromAccount.getBalance().compareTo(request.getAmount()) < 0) {return TransferResult.builder().success(false).message("账户余额不足").build();}// 执行转账fromAccount.setBalance(fromAccount.getBalance().subtract(request.getAmount()));toAccount.setBalance(toAccount.getBalance().add(request.getAmount()));accountRepository.save(fromAccount);accountRepository.save(toAccount);// 记录日志TransactionLog log = TransactionLog.builder().transactionId(generateTransactionId()).fromAccount(request.getFromAccount()).toAccount(request.getToAccount()).amount(request.getAmount()).transactionType(TransactionType.TRANSFER).status(TransactionStatus.SUCCESS).createTime(LocalDateTime.now()).build();transactionLogRepository.save(log);return TransferResult.builder().success(true).transactionId(log.getTransactionId()).message("转账成功").build();}/*** 发送转账通知* 使用事务同步机制确保事务提交后再发送*/private void sendTransferNotification(TransferRequest request, String transactionId) {// 注册事务同步回调,确保事务提交后再发送通知TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {@Overridepublic void afterCommit() {try {notificationService.sendTransferNotification(request, transactionId);logger.info("转账通知发送成功: {}", transactionId);} catch (Exception e) {logger.error("转账通知发送失败: {}", transactionId, e);// 通知失败不影响转账事务}}});}// 辅助方法private void validateTransferRequest(TransferRequest request) {if (request.getAmount().compareTo(BigDecimal.ZERO) <= 0) {throw new IllegalArgumentException("转账金额必须大于0");}if (request.getFromAccount().equals(request.getToAccount())) {throw new IllegalArgumentException("转出和转入账户不能相同");}}private String generateTransactionId() {return "TXN" + System.currentTimeMillis() + String.format("%04d", new Random().nextInt(10000));}private String generateBatchId() {return "BATCH" + System.currentTimeMillis();}private BigDecimal calculateTotalAmount(List<TransferRequest> requests) {return requests.stream().map(TransferRequest::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);}
}

分布式事务的核心优势:在OpenTenBase中,开发者无需关心底层的分布式事务实现细节,只需要使用标准的@Transactional注解,GTM会自动处理跨节点的事务协调,确保数据的强一致性。这大大简化了分布式应用的开发复杂度。


四、性能优化与监控实践

4.1 查询性能优化

OpenTenBase提供了强大的查询优化器,支持多种优化策略:

请在此添加图片描述

图4:查询性能优化前后对比图 - 展示不同查询类型的性能提升效果

4.2 分布式查询优化配置

/*** OpenTenBase查询优化配置* 针对不同业务场景进行性能调优*/
@Configuration
public class OpenTenBaseOptimizationConfig {private static final Logger logger = LoggerFactory.getLogger(OpenTenBaseOptimizationConfig.class);/*** 配置分布式查询优化器*/@Beanpublic QueryOptimizerConfig queryOptimizerConfig() {return QueryOptimizerConfig.builder()// 启用代价估算优化.enableCostBasedOptimization(true)// 启用分区裁剪.enablePartitionPruning(true)// 启用并行查询.enableParallelQuery(true)// 设置并行度.maxParallelWorkers(4)// 启用向量化执行.enableVectorizedExecution(true)// 配置工作内存.workMemory("256MB")// 配置共享缓冲区.sharedBuffers("1GB").build();}/*** 配置连接池优化*/@Bean@Primarypublic DataSource optimizedDataSource() {HikariConfig config = new HikariConfig();// 基础连接配置config.setJdbcUrl("jdbc:postgresql://opentenbase-cluster:5432/mydb");config.setUsername("dbuser");config.setPassword("dbpass");config.setDriverClassName("org.postgresql.Driver");// 连接池优化配置config.setMaximumPoolSize(50);           // 最大连接数config.setMinimumIdle(10);               // 最小空闲连接config.setConnectionTimeout(30000);      // 连接超时30秒config.setIdleTimeout(600000);           // 空闲超时10分钟config.setMaxLifetime(1800000);          // 连接最大生命周期30分钟config.setLeakDetectionThreshold(60000); // 连接泄漏检测阈值1分钟// PostgreSQL特定优化config.addDataSourceProperty("cachePrepStmts", "true");config.addDataSourceProperty("prepStmtCacheSize", "250");config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");config.addDataSourceProperty("useServerPrepStmts", "true");config.addDataSourceProperty("reWriteBatchedInserts", "true");// OpenTenBase特定优化config.addDataSourceProperty("defaultRowFetchSize", "1000");config.addDataSourceProperty("logUnclosedConnections", "true");config.addDataSourceProperty("tcpKeepAlive", "true");HikariDataSource dataSource = new HikariDataSource(config);logger.info("OpenTenBase数据源配置完成,最大连接数: {}", config.getMaximumPoolSize());return dataSource;}/*** 配置JPA优化*/@Beanpublic LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();em.setDataSource(dataSource);em.setPackagesToScan("com.example.entity");HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();em.setJpaVendorAdapter(vendorAdapter);Properties properties = new Properties();// Hibernate基础配置properties.setProperty("hibernate.dialect", "org.hibernate.dialect.PostgreSQL10Dialect");properties.setProperty("hibernate.hbm2ddl.auto", "validate");properties.setProperty("hibernate.show_sql", "false");properties.setProperty("hibernate.format_sql", "true");// 性能优化配置properties.setProperty("hibernate.jdbc.batch_size", "50");properties.setProperty("hibernate.jdbc.fetch_size", "100");properties.setProperty("hibernate.order_inserts", "true");properties.setProperty("hibernate.order_updates", "true");properties.setProperty("hibernate.batch_versioned_data", "true");// 二级缓存配置properties.setProperty("hibernate.cache.use_second_level_cache", "true");properties.setProperty("hibernate.cache.use_query_cache", "true");properties.setProperty("hibernate.cache.region.factory_class", "org.hibernate.cache.jcache.JCacheRegionFactory");// 统计信息配置properties.setProperty("hibernate.generate_statistics", "true");properties.setProperty("hibernate.session.events.log.LOG_QUERIES_SLOWER_THAN_MS", "1000");em.setJpaProperties(properties);return em;}
}

4.3 性能监控体系

建立全面的性能监控体系,实时掌握数据库运行状态:

30%25%20%15%10%OpenTenBase性能监控维度分布查询性能监控事务处理监控资源使用监控集群状态监控业务指标监控

图5:OpenTenBase性能监控维度分布饼图 - 展示监控体系的重点关注领域


五、运维管理与最佳实践

5.1 集群部署架构

基于Kubernetes的OpenTenBase集群部署方案:

# opentenbase-cluster.yaml
apiVersion: v1
kind: ConfigMap
metadata:name: opentenbase-config
data:postgresql.conf: |# 基础配置listen_addresses = '*'port = 5432max_connections = 200# 内存配置shared_buffers = 1GBeffective_cache_size = 3GBwork_mem = 256MBmaintenance_work_mem = 512MB# WAL配置wal_level = replicamax_wal_size = 2GBmin_wal_size = 80MBcheckpoint_completion_target = 0.9# 查询优化random_page_cost = 1.1effective_io_concurrency = 200# 日志配置log_destination = 'stderr'logging_collector = onlog_directory = 'pg_log'log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'log_min_duration_statement = 1000# OpenTenBase特定配置enable_distri_print = onenable_distri_debug = offpg_hba.conf: |# TYPE  DATABASE        USER            ADDRESS                 METHODlocal   all             all                                     trusthost    all             all             127.0.0.1/32            md5host    all             all             ::1/128                 md5host    all             all             0.0.0.0/0               md5---
apiVersion: apps/v1
kind: StatefulSet
metadata:name: opentenbase-coordinator
spec:serviceName: opentenbase-coordinatorreplicas: 2selector:matchLabels:app: opentenbase-coordinatortemplate:metadata:labels:app: opentenbase-coordinatorspec:containers:- name: coordinatorimage: opentenbase/opentenbase:latestports:- containerPort: 5432env:- name: POSTGRES_DBvalue: "mydb"- name: POSTGRES_USERvalue: "dbuser"- name: POSTGRES_PASSWORDvalue: "dbpass"- name: OPENTENBASE_NODE_TYPEvalue: "coordinator"resources:requests:memory: "2Gi"cpu: "1000m"limits:memory: "4Gi"cpu: "2000m"volumeMounts:- name: configmountPath: /etc/postgresql- name: datamountPath: /var/lib/postgresql/datavolumes:- name: configconfigMap:name: opentenbase-configvolumeClaimTemplates:- metadata:name: dataspec:accessModes: ["ReadWriteOnce"]resources:requests:storage: 100Gi---
apiVersion: apps/v1
kind: StatefulSet
metadata:name: opentenbase-datanode
spec:serviceName: opentenbase-datanodereplicas: 4selector:matchLabels:app: opentenbase-datanodetemplate:metadata:labels:app: opentenbase-datanodespec:containers:- name: datanodeimage: opentenbase/opentenbase:latestports:- containerPort: 5432env:- name: POSTGRES_DBvalue: "mydb"- name: POSTGRES_USERvalue: "dbuser"- name: POSTGRES_PASSWORDvalue: "dbpass"- name: OPENTENBASE_NODE_TYPEvalue: "datanode"resources:requests:memory: "4Gi"cpu: "2000m"limits:memory: "8Gi"cpu: "4000m"volumeMounts:- name: configmountPath: /etc/postgresql- name: datamountPath: /var/lib/postgresql/datavolumes:- name: configconfigMap:name: opentenbase-configvolumeClaimTemplates:- metadata:name: dataspec:accessModes: ["ReadWriteOnce"]resources:requests:storage: 500Gi

5.2 自动化运维脚本

#!/bin/bash
# opentenbase-ops.sh - OpenTenBase运维管理脚本set -e# 配置变量
CLUSTER_NAME="opentenbase-cluster"
NAMESPACE="database"
BACKUP_DIR="/backup/opentenbase"
LOG_DIR="/var/log/opentenbase"# 日志函数
log() {echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_DIR/ops.log"
}# 检查集群状态
check_cluster_status() {log "检查OpenTenBase集群状态..."# 检查Coordinator节点coordinator_pods=$(kubectl get pods -n $NAMESPACE -l app=opentenbase-coordinator --no-headers | wc -l)coordinator_ready=$(kubectl get pods -n $NAMESPACE -l app=opentenbase-coordinator --no-headers | grep Running | wc -l)log "Coordinator节点: $coordinator_ready/$coordinator_pods 运行中"# 检查DataNode节点datanode_pods=$(kubectl get pods -n $NAMESPACE -l app=opentenbase-datanode --no-headers | wc -l)datanode_ready=$(kubectl get pods -n $NAMESPACE -l app=opentenbase-datanode --no-headers | grep Running | wc -l)log "DataNode节点: $datanode_ready/$datanode_pods 运行中"# 检查服务连通性if kubectl exec -n $NAMESPACE opentenbase-coordinator-0 -- psql -U dbuser -d mydb -c "SELECT version();" > /dev/null 2>&1; thenlog "数据库连接正常"elselog "ERROR: 数据库连接失败"return 1fireturn 0
}# 执行数据库备份
backup_database() {log "开始执行数据库备份..."local backup_timestamp=$(date '+%Y%m%d_%H%M%S')local backup_file="$BACKUP_DIR/opentenbase_backup_$backup_timestamp.sql"# 创建备份目录mkdir -p "$BACKUP_DIR"# 执行备份kubectl exec -n $NAMESPACE opentenbase-coordinator-0 -- pg_dumpall -U dbuser > "$backup_file"if [ $? -eq 0 ]; then# 压缩备份文件gzip "$backup_file"log "备份完成: ${backup_file}.gz"# 清理旧备份(保留7天)find "$BACKUP_DIR" -name "*.gz" -mtime +7 -deletelog "清理完成旧备份文件"elselog "ERROR: 备份失败"return 1fireturn 0
}# 性能监控
monitor_performance() {log "收集性能监控数据..."# 获取连接数local connections=$(kubectl exec -n $NAMESPACE opentenbase-coordinator-0 -- \psql -U dbuser -d mydb -t -c "SELECT count(*) FROM pg_stat_activity;")# 获取数据库大小local db_size=$(kubectl exec -n $NAMESPACE opentenbase-coordinator-0 -- \psql -U dbuser -d mydb -t -c "SELECT pg_size_pretty(pg_database_size('mydb'));")# 获取慢查询数量local slow_queries=$(kubectl exec -n $NAMESPACE opentenbase-coordinator-0 -- \psql -U dbuser -d mydb -t -c "SELECT count(*) FROM pg_stat_statements WHERE mean_time > 1000;")log "当前连接数: $connections"log "数据库大小: $db_size"log "慢查询数量: $slow_queries"# 记录到监控文件echo "$(date '+%Y-%m-%d %H:%M:%S'),$connections,$db_size,$slow_queries" >> "$LOG_DIR/performance.csv"
}# 扩容DataNode节点
scale_datanode() {local new_replicas=$1if [ -z "$new_replicas" ]; thenlog "ERROR: 请指定新的副本数量"return 1filog "开始扩容DataNode到 $new_replicas 个节点..."# 更新StatefulSet副本数kubectl patch statefulset -n $NAMESPACE opentenbase-datanode -p '{"spec":{"replicas":'$new_replicas'}}'# 等待扩容完成kubectl rollout status statefulset/opentenbase-datanode -n $NAMESPACElog "DataNode扩容完成"# 重新平衡数据分布log "开始重新平衡数据分布..."kubectl exec -n $NAMESPACE opentenbase-coordinator-0 -- \psql -U dbuser -d mydb -c "SELECT rebalance_cluster();"log "数据重新平衡完成"
}# 故障恢复
recover_failed_node() {local node_name=$1if [ -z "$node_name" ]; thenlog "ERROR: 请指定要恢复的节点名称"return 1filog "开始恢复故障节点: $node_name"# 删除故障Podkubectl delete pod -n $NAMESPACE "$node_name"# 等待Pod重新创建kubectl wait --for=condition=Ready pod -n $NAMESPACE "$node_name" --timeout=300sif [ $? -eq 0 ]; thenlog "节点 $node_name 恢复成功"elselog "ERROR: 节点 $node_name 恢复失败"return 1fi
}# 主函数
main() {case "$1" in"status")check_cluster_status;;"backup")backup_database;;"monitor")monitor_performance;;"scale")scale_datanode "$2";;"recover")recover_failed_node "$2";;*)echo "用法: $0 {status|backup|monitor|scale <replicas>|recover <node_name>}"exit 1;;esac
}# 执行主函数
main "$@"

六、业务场景应用案例

6.1 金融核心系统应用

在金融核心系统中,OpenTenBase展现出了卓越的性能和稳定性:

业务场景传统Oracle方案OpenTenBase方案性能提升
账户查询50ms平均响应30ms平均响应40%提升
转账交易200ms平均响应120ms平均响应40%提升
批量对账2小时完成45分钟完成62%提升
风控分析5分钟响应2分钟响应60%提升
报表生成30分钟完成12分钟完成60%提升

6.2 实时风控系统集成

/*** 基于OpenTenBase的实时风控系统* 利用HTAP能力实现交易处理和风险分析的统一*/
@Service
public class RealTimeRiskControlService {private static final Logger logger = LoggerFactory.getLogger(RealTimeRiskControlService.class);@Autowiredprivate TransactionRepository transactionRepository;@Autowiredprivate RiskRuleRepository riskRuleRepository;@Autowiredprivate CustomerProfileRepository customerProfileRepository;/*** 实时风险评估* 在交易处理的同时进行风险分析*/@Transactionalpublic RiskAssessmentResult assessTransactionRisk(TransactionRequest request) {logger.info("开始实时风险评估: {}", request.getTransactionId());try {// 1. 获取客户风险画像(OLTP查询)CustomerRiskProfile profile = customerProfileRepository.findByCustomerId(request.getCustomerId());// 2. 获取历史交易模式(OLAP分析)TransactionPattern pattern = analyzeHistoricalPattern(request.getCustomerId(), request.getTransactionType());// 3. 实时规则引擎评估List<RiskRule> applicableRules = riskRuleRepository.findByTransactionTypeAndStatus(request.getTransactionType(), RuleStatus.ACTIVE);RiskScore riskScore = calculateRiskScore(request, profile, pattern, applicableRules);// 4. 记录风险评估结果(OLTP写入)RiskAssessmentRecord record = RiskAssessmentRecord.builder().transactionId(request.getTransactionId()).customerId(request.getCustomerId()).riskScore(riskScore.getScore()).riskLevel(riskScore.getLevel()).triggeredRules(riskScore.getTriggeredRules()).assessmentTime(LocalDateTime.now()).build();riskAssessmentRepository.save(record);// 5. 根据风险等级决定处理策略RiskDecision decision = makeRiskDecision(riskScore);logger.info("风险评估完成: 交易ID={}, 风险等级={}, 决策={}", request.getTransactionId(), riskScore.getLevel(), decision.getAction());return RiskAssessmentResult.builder().transactionId(request.getTransactionId()).riskScore(riskScore).decision(decision).assessmentTime(record.getAssessmentTime()).build();} catch (Exception e) {logger.error("风险评估失败: {}", request.getTransactionId(), e);// 风险评估失败时采用保守策略return RiskAssessmentResult.builder().transactionId(request.getTransactionId()).riskScore(RiskScore.HIGH_RISK).decision(RiskDecision.REJECT).errorMessage(e.getMessage()).build();}}/*** 分析历史交易模式* 利用OpenTenBase的OLAP能力进行复杂分析*/private TransactionPattern analyzeHistoricalPattern(String customerId, TransactionType type) {// 使用原生SQL进行复杂的OLAP分析String sql = """WITH customer_stats AS (SELECT customer_id,COUNT(*) as total_transactions,AVG(amount) as avg_amount,STDDEV(amount) as amount_stddev,MAX(amount) as max_amount,COUNT(DISTINCT DATE(transaction_time)) as active_days,AVG(EXTRACT(HOUR FROM transaction_time)) as avg_hourFROM transactions WHERE customer_id = ? AND transaction_type = ?AND transaction_time >= CURRENT_DATE - INTERVAL '90 days'GROUP BY customer_id),time_pattern AS (SELECT customer_id,EXTRACT(HOUR FROM transaction_time) as hour_of_day,COUNT(*) as hour_countFROM transactions WHERE customer_id = ? AND transaction_type = ?AND transaction_time >= CURRENT_DATE - INTERVAL '30 days'GROUP BY customer_id, EXTRACT(HOUR FROM transaction_time)ORDER BY hour_count DESCLIMIT 3),amount_pattern AS (SELECT customer_id,CASE WHEN amount <= 1000 THEN 'small'WHEN amount <= 10000 THEN 'medium'ELSE 'large'END as amount_range,COUNT(*) as range_countFROM transactions WHERE customer_id = ? AND transaction_type = ?AND transaction_time >= CURRENT_DATE - INTERVAL '30 days'GROUP BY customer_id, amount_range)SELECT cs.*,array_agg(tp.hour_of_day ORDER BY tp.hour_count DESC) as preferred_hours,array_agg(ap.amount_range ORDER BY ap.range_count DESC) as preferred_amountsFROM customer_stats csLEFT JOIN time_pattern tp ON cs.customer_id = tp.customer_idLEFT JOIN amount_pattern ap ON cs.customer_id = ap.customer_idGROUP BY cs.customer_id, cs.total_transactions, cs.avg_amount, cs.amount_stddev, cs.max_amount, cs.active_days, cs.avg_hour""";// 执行复杂分析查询Map<String, Object> result = jdbcTemplate.queryForMap(sql, customerId, type.name(), customerId, type.name(), customerId, type.name());return TransactionPattern.builder().customerId(customerId).totalTransactions(((Number) result.get("total_transactions")).longValue()).avgAmount((BigDecimal) result.get("avg_amount")).amountStddev((BigDecimal) result.get("amount_stddev")).maxAmount((BigDecimal) result.get("max_amount")).activeDays(((Number) result.get("active_days")).intValue()).avgHour(((Number) result.get("avg_hour")).doubleValue()).preferredHours((Integer[]) result.get("preferred_hours")).preferredAmounts((String[]) result.get("preferred_amounts")).build();}/*** 计算风险评分*/private RiskScore calculateRiskScore(TransactionRequest request, CustomerRiskProfile profile,TransactionPattern pattern, List<RiskRule> rules) {double totalScore = 0.0;List<String> triggeredRules = new ArrayList<>();for (RiskRule rule : rules) {double ruleScore = evaluateRule(rule, request, profile, pattern);if (ruleScore > 0) {totalScore += ruleScore * rule.getWeight();triggeredRules.add(rule.getRuleName());}}RiskLevel level = determineRiskLevel(totalScore);return RiskScore.builder().score(totalScore).level(level).triggeredRules(triggeredRules).build();}private double evaluateRule(RiskRule rule, TransactionRequest request, CustomerRiskProfile profile, TransactionPattern pattern) {// 规则评估逻辑实现switch (rule.getRuleType()) {case AMOUNT_THRESHOLD:return evaluateAmountRule(rule, request, pattern);case FREQUENCY_LIMIT:return evaluateFrequencyRule(rule, request, pattern);case TIME_PATTERN:return evaluateTimePatternRule(rule, request, pattern);case CUSTOMER_BEHAVIOR:return evaluateCustomerBehaviorRule(rule, request, profile);default:return 0.0;}}}

七、总结与展望

通过这次OpenTenBase的深度实践,我对分布式HTAP数据库有了全新的认识和深刻的体会。这不仅仅是一次技术迁移,更是一次架构思维的升级和业务能力的跃升。

从技术角度来看,OpenTenBase的双内核架构设计确实令人印象深刻。能够同时兼容PostgreSQL和MySQL两套生态,大大降低了迁移成本和学习门槛。特别是在我们的金融系统迁移过程中,90%以上的SQL语句都能直接兼容,剩下的10%也只需要做简单的语法调整。这种兼容性设计体现了腾讯团队对开源生态的深刻理解和对用户需求的精准把握。

HTAP混合负载处理能力是OpenTenBase最大的亮点。在传统架构中,我们需要维护OLTP和OLAP两套独立的系统,数据同步、一致性保证、运维复杂度都是巨大的挑战。而OpenTenBase通过智能的负载识别和路由机制,让我们能够在同一个系统中同时处理交易和分析负载,不仅简化了架构,更重要的是实现了真正的实时分析能力。

分布式事务的实现也让我刮目相看。GTM全局事务管理器通过两阶段提交协议,确保了跨节点事务的ACID特性。在我们的转账业务测试中,即使涉及多个数据节点的复杂事务,也能保证完美的一致性。这种透明的分布式事务支持,让开发者可以像使用单机数据库一样编写业务代码,大大降低了分布式应用的开发复杂度。

性能方面的提升超出了我们的预期。在相同硬件配置下,OLTP业务的TPS提升了40%,复杂分析查询的响应时间缩短了60%。更重要的是,系统的扩展性得到了根本性改善。当业务量增长时,我们可以通过简单的节点扩容来线性提升系统处理能力,彻底解决了传统单机数据库的容量瓶颈问题。

当然,迁移过程也遇到了不少挑战。SQL兼容性虽然很高,但仍有一些Oracle特有的函数和语法需要改造。存储过程的迁移工作量较大,需要重新设计和测试。监控体系也需要重新构建,学习新的运维工具和方法。但通过合理的项目规划和充分的技术准备,我们最终成功完成了迁移,并建立了一套完整的运维体系。

从业务价值来看,OpenTenBase不仅解决了我们当前的技术痛点,更为未来的业务发展奠定了坚实基础。实时风控、智能推荐、数据分析等业务场景都能在同一个平台上高效运行。数据的实时性和一致性得到了保证,业务响应速度显著提升,用户体验得到了明显改善。

展望未来,我认为OpenTenBase还有很大的发展空间。随着云原生技术的发展,期待看到更好的Kubernetes集成、更智能的自动化运维、更丰富的生态工具。在AI和机器学习领域的支持也值得期待,比如内置的向量数据库能力、智能的查询优化器等。

对于正在考虑数据库技术选型的团队,我强烈推荐关注OpenTenBase。特别是对于有以下需求的场景:需要处理海量数据、需要HTAP混合负载能力、需要强一致性的分布式事务、需要高可用和弹性扩展能力。OpenTenBase都能提供优秀的解决方案。

最后,我想说的是,技术选型不仅仅是技术问题,更是战略问题。选择一个有活跃社区、持续演进、生态丰富的开源项目,对企业的长远发展具有重要意义。OpenTenBase作为腾讯云开源的企业级分布式数据库,背后有强大的技术团队支持,在金融、政府、电信等行业有成功的应用案例,是值得信赖的技术选择。

希望这次实践分享能够帮助更多的技术同行了解OpenTenBase,也期待与大家在技术探索的道路上继续交流和学习。让我们一起拥抱开源,推动技术进步,创造更大的业务价值。


🌟 嗨,我是Xxtaoaooo!
⚙️ 【点赞】让更多同行看见深度干货
🚀 【关注】持续获取行业前沿技术与经验
🧩 【评论】分享你的实战经验或技术困惑
作为一名技术实践者,我始终相信:
每一次技术探讨都是认知升级的契机,期待在评论区与你碰撞灵感火花🔥


文章转载自:

http://YtDPLZVt.kndst.cn
http://PLehPbtE.kndst.cn
http://dizac7F7.kndst.cn
http://kgRurKQ6.kndst.cn
http://zdMXQZfR.kndst.cn
http://9uSh4ZjS.kndst.cn
http://AFTIXurL.kndst.cn
http://hWF5JGRZ.kndst.cn
http://OEkFQUwO.kndst.cn
http://HB5F6V03.kndst.cn
http://Ik5CFqIN.kndst.cn
http://rcP7GvI3.kndst.cn
http://4hwKdKgb.kndst.cn
http://PtwMGP2X.kndst.cn
http://QLHCkeBh.kndst.cn
http://X1HVgWlp.kndst.cn
http://kMzQqQ0u.kndst.cn
http://mZBgpvv4.kndst.cn
http://u6xENJsH.kndst.cn
http://RTx5Ggby.kndst.cn
http://DdhOqCMd.kndst.cn
http://8L4cxPrO.kndst.cn
http://nE8XcT7e.kndst.cn
http://izNpB5ke.kndst.cn
http://Mr7TWy5F.kndst.cn
http://hbs0fTYs.kndst.cn
http://S5hfKECt.kndst.cn
http://btP272J3.kndst.cn
http://3raYz2jd.kndst.cn
http://SxLF5Qaa.kndst.cn
http://www.dtcms.com/a/383500.html

相关文章:

  • LabVIEW信号监测与分析
  • 【大模型算法工程师面试题】大模型领域新兴的主流库有哪些?
  • Java队列(从内容结构到经典练习一步到位)
  • Cherno OpenGL 教程
  • RT-DETRv2 中的坐标回归机制深度解析:为什么用 `sigmoid(inv_sigmoid(ref) + delta)` 而不是除以图像尺寸?
  • OpenCV入门教程
  • 深度学习-计算机视觉-目标检测三大算法-R-CNN、SSD、YOLO
  • 冰火两重天:AI重构下的IT就业图景
  • 从ENIAC到Linux:计算机技术与商业模式的协同演进——云原生重塑闭源主机,eBPF+WebAssembly 双引擎的“Linux 内核即服务”实践
  • 从 MySQL 迁移到 GoldenDB,上来就踩了一个坑。
  • qt界面开发入门以及计算器制作
  • SQL 核心概念与实践总结
  • 【Tourbox】怎么复制预设?
  • RTT操作系统(2)
  • 基于STM32单片机智能手表GSM短信上报GPS定位防丢器设计
  • 力扣658.找到K个最接近的元素
  • LeetCode 面试经典 150_哈希表_赎金信(39_383_C++_简单)
  • LeetCode热题100--114. 二叉树展开为链表--中等
  • 【交易系统系列33】从Raft到Kafka:解构交易所核心系统的一致性与数据持久化之道
  • 数据结构---基于顺序存储结构实现的双端队列
  • C4D建模入门指南:核心术语与高效设置详解
  • Unity核心概念⑧:Input
  • 软考高级-系统架构设计师之指令系统
  • Kafka 运维实战基本操作含命令与最佳实践
  • CAS理解
  • Linux动静态库开发基础:静态库与动态库的编译构建、链接使用及问题排查
  • 深度学习的定义
  • 数据库造神计划第七天---增删改查(CRUD)(3)
  • 【WitSystem】FastAPI目录架构最佳实践
  • Python的re模块