代码是这样处理的:package com.bms.sync.data.service;
import com.bms.sync.data.util.TimeUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import javax.sql.DataSource;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.Arrays;
@Service
public class DataMigrationService {
private final JdbcTemplate mysqlJdbcTemplate;
private final JdbcTemplate tdengineJdbcTemplate;
@Value("${table.tag.columns}")
private String tagColumnsConfig;
@Value("${table.timestamp.field}")
private String timestampField;
// TDengine时间戳字段名
@Value("${table.tdengine.timestamp.field}")
private String tdengineTimestampField;
@Value("${performance.batch-size.historical}")
private int historicalBatchSize;
@Value("${performance.batch-size.incremental}")
private int incrementalBatchSize;
// 重试配置
@Value("${retry.max-attempts}")
private int maxRetries;
@Value("${retry.interval}")
private long retryInterval;
@Autowired
private PerformanceMonitorService performanceMonitorService;
@Autowired
private TableAutoCreateService tableAutoCreateService;
@Autowired
private RedisQueueService redisQueueService;
@Autowired
public DataMigrationService(JdbcTemplate mysqlJdbcTemplate,
JdbcTemplate tdengineJdbcTemplate,
PerformanceMonitorService performanceMonitorService,
TableAutoCreateService tableAutoCreateService,
RedisQueueService redisQueueService) {
this.mysqlJdbcTemplate = mysqlJdbcTemplate;
this.tdengineJdbcTemplate = tdengineJdbcTemplate;
this.performanceMonitorService = performanceMonitorService;
this.tableAutoCreateService = tableAutoCreateService;
this.redisQueueService = redisQueueService;
}
/**
* 迁移数据(支持批量处理)
* @param mysqlTableName MySQL表名
* @param superTableName TDengine超级表名
*/
public void migrateData(String mysqlTableName, String superTableName) {
long startTime = System.currentTimeMillis();
try {
System.out.println("开始数据迁移: " + mysqlTableName + " -> " + superTableName);
// 确保表结构已创建
System.out.println("步骤1: 确保表结构已创建");
ensureTableStructureExists(mysqlTableName, superTableName);
// 1. 获取总记录数
System.out.println("步骤2: 获取总记录数");
int totalRecords = getTotalRecordCount(mysqlTableName);
if (totalRecords == 0) {
System.out.println("没有数据需要迁移");
return;
}
System.out.println("总记录数: " + totalRecords);
// 2. 分批迁移数据(增加批处理大小以提高效率)
int batchSize = historicalBatchSize; // 每批处理5000条记录
int totalBatches = (int) Math.ceil((double) totalRecords / batchSize);
System.out.println("总批次数: " + totalBatches + ",批处理大小: " + batchSize);
// 使用Redis队列处理历史数据
System.out.println("步骤3: 使用Redis队列处理历史数据");
processHistoricalDataWithQueue(mysqlTableName, superTableName, totalRecords, batchSize);
System.out.println("数据迁移完成");
} catch (Exception e) {
System.err.println("数据迁移失败: " + e.getMessage());
e.printStackTrace();
} finally {
long endTime = System.currentTimeMillis();
performanceMonitorService.recordTime("data.migration", endTime - startTime);
performanceMonitorService.incrementCounter("data.migration.totalRecords", getTotalRecordCount(mysqlTableName));
System.out.println("数据迁移总耗时: " + (endTime - startTime) + "ms");
}
}
/**
* 确保表结构已创建
* @param mysqlTableName MySQL表名
* @param superTableName TDengine超级表名
*/
public void ensureTableStructureExists(String mysqlTableName, String superTableName) {
try {
System.out.println("确保表结构存在: " + mysqlTableName + " -> " + superTableName);
// 从配置中获取标签列
String[] tagColumnsArray = tagColumnsConfig.split(",");
List<String> tagColumns = Arrays.asList(tagColumnsArray);
// 自动创建表结构
tableAutoCreateService.autoCreateTableStructure(mysqlTableName, superTableName, tagColumns);
System.out.println("表结构创建完成");
} catch (Exception e) {
System.err.println("确保表结构存在时失败: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 使用Redis队列处理历史数据
* @param mysqlTableName MySQL表名
* @param superTableName TDengine超级表名
* @param totalRecords 总记录数
* @param batchSize 批处理大小
*/
private void processHistoricalDataWithQueue(String mysqlTableName, String superTableName, int totalRecords, int batchSize) {
try {
String queueName = mysqlTableName + "_to_" + superTableName;
// 清空之前的队列数据
redisQueueService.clearQueue(queueName);
// 使用基于时间戳的分页方式替代LIMIT/OFFSET,避免数据遗漏
String lastProcessedTime = null;
String currentTime = java.time.LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
long startTime = System.currentTimeMillis();
int totalProcessed = 0;
boolean hasMoreData = true;
while (hasMoreData) {
// 构建查询SQL,使用时间戳分页
StringBuilder querySql = new StringBuilder();
querySql.append("SELECT * FROM ").append(mysqlTableName);
// 添加时间范围条件,确保时间字段不为空
querySql.append(" WHERE ").append(timestampField).append(" IS NOT NULL"); // 确保时间字段不为空
if (lastProcessedTime != null && !lastProcessedTime.isEmpty()) {
querySql.append(" AND ").append(timestampField).append(" > '").append(lastProcessedTime).append("'");
}
querySql.append(" ORDER BY ").append(timestampField).append(" ASC");
querySql.append(" LIMIT ").append(batchSize);
List<Map<String, Object>> records = mysqlJdbcTemplate.queryForList(querySql.toString());
if (!records.isEmpty()) {
// 将数据放入Redis队列
redisQueueService.pushHistoricalDataToQueue(queueName, records);
// 更新最大时间戳
Object lastTimestamp = records.get(records.size() - 1).get(timestampField);
if (lastTimestamp != null) {
lastProcessedTime = lastTimestamp.toString();
}
totalProcessed += records.size();
System.out.println("已将 " + records.size() + " 条记录放入Redis队列,总计已处理: " + totalProcessed);
} else {
hasMoreData = false;
}
}
long endTime = System.currentTimeMillis();
System.out.println("历史数据已全部放入Redis队列,队列大小: " + redisQueueService.getQueueSize(queueName) + ",耗时: " + (endTime - startTime) + "ms");
// 从Redis队列中取出数据并同步到TDengine
int successCount = syncDataFromQueue(queueName, superTableName);
System.out.println("历史数据同步完成,总计成功同步记录数: " + successCount);
} catch (Exception e) {
System.err.println("使用Redis队列处理历史数据失败: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 从Redis队列中取出数据并同步到TDengine
* @param queueName 队列名称
* @param superTableName TDengine超级表名
* @return 成功同步的记录数
*/
private int syncDataFromQueue(String queueName, String superTableName) {
int totalSynced = 0;
int batchSize = incrementalBatchSize; // 从队列中每次取出100条记录进行同步
long startTime = System.currentTimeMillis();
System.out.println("开始从Redis队列同步数据到TDengine,批处理大小: " + batchSize);
int batchCount = 0;
while (redisQueueService.getQueueSize(queueName) > 0) {
// 先查看但不移除队列中的数据
List<Object> records = redisQueueService.peekBatchFromQueue(queueName, batchSize);
if (records != null && !records.isEmpty()) {
// 转换为正确的类型
List<Map<String, Object>> convertedRecords = (List<Map<String, Object>>) (List<?>) records;
// 批量插入到TDengine并统计成功插入的记录数
int successCount = batchInsertIntoTDengine(superTableName, convertedRecords);
totalSynced += successCount;
batchCount++;
// 只有在数据成功同步后才从队列中移除
if (successCount > 0) {
// 从队列中移除已成功处理的数据
redisQueueService.removeBatchFromQueue(queueName, successCount);
}
// 每处理10批打印一次进度信息
if (batchCount % 10 == 0) {
System.out.println("已同步 " + batchCount + " 批次数据,当前批次成功记录数: " + successCount + ",总计已同步: " + totalSynced);
}
}
}
long endTime = System.currentTimeMillis();
// 只有在有数据成功同步时才打印完成信息
if (totalSynced > 0) {
System.out.println("从Redis队列同步数据完成,总计同步: " + totalSynced + " 条记录,总批次数: " + batchCount + ",耗时: " + (endTime - startTime) + "ms");
} else {
System.out.println("Redis队列中没有数据需要同步");
}
return totalSynced;
}
/**
* 获取记录数
* @param mysqlTableName MySQL表名
* @return 记录数
*/
public int getRecordCount(String mysqlTableName) {
// 直接从数据库获取,不使用缓存
String countSql = "SELECT COUNT(*) FROM " + mysqlTableName;
Integer count = mysqlJdbcTemplate.queryForObject(countSql, Integer.class);
return count != null ? count : 0;
}
/**
* 获取总记录数
* @param tableName 表名
* @return 总记录数
*/
public int getTotalRecordCount(String tableName) {
// 直接从数据库获取,不使用缓存
String countSql = "SELECT COUNT(*) FROM " + tableName;
Integer count = mysqlJdbcTemplate.queryForObject(countSql, Integer.class);
return count != null ? count : 0;
}
/**
* 批量插入数据到TDengine
* @param superTableName TDengine超级表名
* @param records 记录列表
* @return 成功插入的记录数
*/
private int batchInsertIntoTDengine(String superTableName, List<Map<String, Object>> records) {
if (records.isEmpty()) {
return 0;
}
int successCount = 0;
try {
// 一次性检查并创建所有需要的子表
ensureAllChildTablesExist(superTableName, records);
// 然后批量插入数据
for (Map<String, Object> record : records) {
try {
insertIntoTDengine(superTableName, record);
successCount++;
} catch (Exception e) {
System.err.println("单条记录插入失败: " + e.getMessage());
}
}
} catch (Exception e) {
System.err.println("批量数据插入失败: " + e.getMessage());
// 不打印完整堆栈,减少日志输出
}
return successCount;
}
/**
* 一次性检查并创建所有需要的子表
* @param superTableName 超级表名
* @param records 记录列表
*/
private void ensureAllChildTablesExist(String superTableName, List<Map<String, Object>> records) {
try {
// 首先确保超级表存在
// 不使用缓存检查超级表是否存在
// 直接确保超级表存在
ensureSuperTableExists(superTableName);
// 不使用缓存存储超级表存在信息
// 收集所有需要的子表名
java.util.Set<String> childTableNames = new java.util.HashSet<>();
java.util.Map<String, String> childTableToTagMap = new java.util.HashMap<>();
String tagColumn = tagColumnsConfig;
for (Map<String, Object> record : records) {
Object tagValueObj = record.get(tagColumn);
String tagValue = (tagValueObj != null) ? tagValueObj.toString() : "null";
String childTableName = superTableName + "_" + tagValue;
// 仅处理缓存中不存在的表
String cacheKey = "tdengine:table:" + childTableName;
// 不使用缓存,直接检查表是否存在
if (!childTableNames.contains(childTableName)) {
childTableNames.add(childTableName);
childTableToTagMap.put(childTableName, tagValue);
}
}
// 一次性查询所有子表是否存在
if (!childTableNames.isEmpty()) {
// 使用更高效的方式检查表是否存在
for (String childTableName : childTableNames) {
if (!isTableExists(childTableName)) {
String tagValue = childTableToTagMap.get(childTableName);
createChildTable(superTableName, childTableName, tagValue);
} else {
// 缓存已存在的表信息
// 不使用缓存存储表存在信息
}
}
}
} catch (Exception e) {
System.err.println("批量检查子表存在时失败: " + e.getMessage());
// 不打印完整堆栈,减少日志输出
}
}
/**
* 检查TDengine表是否存在
* @param tableName 表名
* @return 是否存在
*/
private boolean isTableExists(String tableName) {
try {
String checkSql = "SELECT table_name FROM information_schema.ins_tables WHERE table_name = ?";
List<Map<String, Object>> result = tdengineJdbcTemplate.queryForList(checkSql, tableName);
return !result.isEmpty();
} catch (Exception e) {
System.err.println("检查表是否存在时出错: " + e.getMessage());
return false;
}
}
/**
* 创建子表
* @param superTableName 超级表名
* @param childTableName 子表名
* @param tagValue 标签值
*/
private void createChildTable(String superTableName, String childTableName, String tagValue) {
try {
String createChildTableSql = "CREATE TABLE IF NOT EXISTS " + childTableName +
" USING " + superTableName + " TAGS ('" + tagValue + "')";
tdengineJdbcTemplate.execute(createChildTableSql);
// 缓存表存在信息
// 不使用缓存存储表存在信息
System.out.println("创建子表: " + childTableName);
} catch (Exception e) {
System.err.println("创建子表失败: " + e.getMessage());
}
}
/**
* 插入数据到TDengine
* @param superTableName TDengine超级表名
* @param record 记录
*/
public void insertIntoTDengine(String superTableName, Map<String, Object> record) {
long startTime = System.currentTimeMillis();
int retryCount = 0;
while (retryCount < maxRetries) {
try {
// 获取标签列名(从配置中获取)
String tagColumn = tagColumnsConfig;
// 获取标签值
Object tagValueObj = record.get(tagColumn);
String tagValue = (tagValueObj != null) ? tagValueObj.toString() : "null";
// 生成子表名(使用标签值)
String childTableName = superTableName + "_" + tagValue;
// 优化:仅在缓存中不存在时检查子表
String cacheKey = "tdengine:table:" + childTableName;
// 不使用缓存检查表是否存在
// 直接检查表是否存在
if (!isTableExists(childTableName)) {
// 确保子表存在
ensureChildTableExists(superTableName, childTableName, tagValue);
// 缓存表存在信息,避免频繁检查
// 不使用缓存存储表存在信息
}
// 构建SQL
StringBuilder sql = new StringBuilder();
sql.append("INSERT INTO ").append(childTableName).append(" USING ").append(superTableName)
.append(" TAGS ('").append(tagValue).append("') (").append(tdengineTimestampField).append(", ");
// 添加列名
boolean first = true;
for (String columnName : record.keySet()) {
// 跳过标签列和时间戳字段
if (tagColumn.equals(columnName) || tdengineTimestampField.equals(columnName)) {
continue;
}
if (!first) {
sql.append(", ");
}
// 处理关键字冲突
sql.append(escapeKeyword(columnName));
first = false;
}
// 获取时间戳字段的值
Object timestampValue = record.get(timestampField);
if (timestampValue == null) {
// 如果时间戳为空,跳过这条记录
System.err.println("警告: 记录的时间戳字段为空,跳过该记录");
return;
}
String formattedTimestamp;
if (timestampValue instanceof Timestamp) {
// 格式化时间戳
Timestamp ts = (Timestamp) timestampValue;
formattedTimestamp = "'" + TimeUtils.formatWithMillis(TimeUtils.from(ts)) + "'";
} else if (timestampValue instanceof LocalDateTime) {
// 格式化LocalDateTime
LocalDateTime ldt = (LocalDateTime) timestampValue;
formattedTimestamp = "'" + TimeUtils.formatWithMillis(ldt) + "'";
} else {
// 其他情况使用字符串值
formattedTimestamp = "'" + timestampValue.toString() + "'";
}
sql.append(") VALUES (").append(formattedTimestamp).append(", ");
// 添加值
first = true;
for (Map.Entry<String, Object> entry : record.entrySet()) {
String columnName = entry.getKey();
Object value = entry.getValue();
// 跳过标签列和时间戳字段
if (tagColumn.equals(columnName) || tdengineTimestampField.equals(columnName)) {
continue;
}
if (!first) {
sql.append(", ");
}
// 处理NULL值
if (value == null) {
sql.append("NULL");
} else if (value instanceof String || value instanceof Timestamp || value instanceof LocalDateTime) {
// 对字符串和时间戳类型进行特殊处理
if (value instanceof Timestamp) {
// 格式化时间戳
Timestamp ts = (Timestamp) value;
sql.append("'").append(TimeUtils.formatWithMillis(TimeUtils.from(ts))).append("'");
} else if (value instanceof LocalDateTime) {
// 格式化LocalDateTime
LocalDateTime ldt = (LocalDateTime) value;
sql.append("'").append(TimeUtils.formatWithMillis(ldt)).append("'");
} else {
// 字符串值需要添加引号,并处理NULL字符串
String strValue = value.toString();
if ("NULL".equalsIgnoreCase(strValue)) {
sql.append("NULL");
} else {
sql.append("'").append(strValue.replace("'", "''")).append("'");
}
}
} else {
// 其他类型直接添加
sql.append(value.toString());
}
first = false;
}
sql.append(")");
// System.out.println("SQL语句: " + sql.toString());
tdengineJdbcTemplate.execute(sql.toString());
// 更新性能监控
performanceMonitorService.incrementCounter("data.migration.insert.success", 1);
break; // 成功执行,跳出重试循环
} catch (Exception e) {
retryCount++;
System.err.println("数据插入失败,正在进行第" + retryCount + "次重试: " + e.getMessage());
if (retryCount >= maxRetries) {
System.err.println("数据插入最终失败: " + e.getMessage());
performanceMonitorService.incrementCounter("data.migration.insert.failure", 1);
throw new RuntimeException("数据插入失败", e);
} else {
try {
Thread.sleep(retryInterval * retryCount); // 使用配置的重试间隔和指数退避
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("重试被中断", ie);
}
}
} finally {
long endTime = System.currentTimeMillis();
performanceMonitorService.recordTime("data.migration.insert", endTime - startTime);
}
}
}
/**
* 确保子表存在
* @param superTableName 超级表名
* @param childTableName 子表名
* @param tagValue 标签值
*/
private void ensureChildTableExists(String superTableName, String childTableName, String tagValue) {
// 使用类级锁来确保在多线程环境下不会重复创建表
synchronized (DataMigrationService.class) {
try {
// 首先确保超级表存在(使用缓存优化)
String superTableCacheKey = "tdengine:stable:" + superTableName;
// 不使用缓存检查超级表是否存在
// 直接确保超级表存在
ensureSuperTableExists(superTableName);
// 不使用缓存存储超级表存在信息
// 检查子表是否存在
if (!isTableExists(childTableName)) {
createChildTable(superTableName, childTableName, tagValue);
}
} catch (Exception e) {
System.err.println("确保子表存在时失败: " + e.getMessage());
// 不打印完整堆栈,减少日志输出
}
}
}
/**
* 确保超级表存在
* @param superTableName 超级表名
*/
private void ensureSuperTableExists(String superTableName) {
try {
// 检查超级表是否存在
if (!isTableExists(superTableName)) {
// 超级表应该已经由TableAutoCreateService创建,这里只是确保缓存存在
// 不再创建默认的超级表结构
System.out.println("警告: 超级表 " + superTableName + " 不存在,需要先分析MySQL表结构并创建");
}
} catch (Exception e) {
System.err.println("确保超级表存在时失败: " + e.getMessage());
// 不打印完整堆栈,减少日志输出
}
}
/**
* 处理SQL关键字冲突,为字段名添加反引号
* @param columnName 字段名
* @return 处理后的字段名
*/
private String escapeKeyword(String columnName) {
// TDengine中的关键字列表(部分)
String[] keywords = {"query", "order", "group", "limit", "offset", "select", "from", "where", "insert", "update", "delete"};
for (String keyword : keywords) {
if (keyword.equalsIgnoreCase(columnName)) {
return "`" + columnName + "`";
}
}
return columnName;
}
}