目前的同步方法有以下三种
-
查询hive库,发送kafka消息,应用消费kafka,写入mysql库
-
查询hive库,spark dataset save jdbc方式写入
-
如果是tidb库,官方支持tisprk方式写入
各自优缺点
-
把hive数据查出来,在发送kafka增加了复杂性,引入了kafka,出现问题多了一个排查范围,但是这种方式能够利用kafka削峰,不至于将tidb打垮,缺点:消息是无序的,所以最后一条的消费情况无法确认,需要借助redis来记录数据消费情况以及总数,因为kafka速度较快只能借助redis辅助记录。
-
通过spark dataset jdbc方式需要控制partition分区数,防止把db打垮,使用dataset.repartition(n)
-
tispark方式是通过tidb官方的spark插件,支持直连pd server 直接将数据写入tidb region ,速度很快,但是官方不再维护了这个插件,tispark也支持直接通过spark sql查询tidb与hive表进行join,但是太慢了
dataset查询tidb收集,计算慢问题
tispark查询大数据量的tidb会异常的慢,因为spark节点的通信将非常耗时,因为tidb不再spark集群内,所以网络将成为瓶颈,故不建议使用tispark查询大批量的tidb数据
sparkSession.sql(" select ifnull(max(convert_id),'') as convert_id from tidb_catalog.%s.%s where convert_id in ('%s')")
当执行tidb查询的时候,触发计算的操作,如count,isempty,filter过滤等都异常的慢,所以要避免使用tispark进行查询tidb操作,spark job的excutor 进行计算的时候会和tidb进行网络交互,会比和hive交互慢很多,使用tispark查询tidb只能查询小数据量数据
这里介绍下tispark方式同步hive数据到tidb
通过阅读官方文档增加以下配置
https://docs-archive.pingcap.com/zh/tidb/v3.0/tispark-overview
https://docs.pingcap.com/zh/tidb/stable/tispark-overview
查看tidb版本
select tidb_version();
查看spark版本

代码实现
/**
获取tispark方式的配置,将传给spark
*/
protected Map<String, String> getTiOptionMap() {
Properties props = getProperties();
String tidbHost = props.getProperty("tidbHost");
String tidbPort = props.getProperty("tidbPort");
String tidbDbName = props.getProperty("tidbDbName");
String tidbUser = props.getProperty("tidbUser");
String tidbPwd = props.getProperty("tidbPwd");
String pdServerPorts = props.getProperty("pdServerPorts");
//通过 TiSpark 将 DataFrame 批量写入 TiDB
Map<String, String> tiOptionMap = new HashMap<>();
tiOptionMap.put("tidb.addr", tidbHost);
tiOptionMap.put("tidb.port", tidbPort);
tiOptionMap.put("tidb.user", tidbUser);
tiOptionMap.put("tidb.password", tidbPwd);
tiOptionMap.put("pd.addresses ", pdServerPorts);
tiOptionMap.put("database", tidbDbName);
//需要制定替换,否则重复会报 java.lang.RuntimeException: com.pingcap.tikv.exception.TiBatchWriteException: data to be inserted has conflicts with TiKV data
tiOptionMap.put("replace", "true");
//写入时是否锁定表
tiOptionMap.put("useTableLock", "false");
return tiOptionMap;
}
protected void syncByTiSpark(List<String> tableNameList) {
Map<String, String> tiOptionMap = getTiOptionMap();
Map<String, Long> tableCountMap = new HashMap<>();
for (String table : tableNameList) {
logger.info("开始同步table:{}数据", table);
String columnJoinString = String.join(",", columnInfoJsonJSONObject.keySet());
tiOptionMap.put("table", table);
//开始查询大数据,并给所有行打上标记=行号,
String queryHiveSql = String.format(" select row_number() OVER (PARTITION BY batch_no ORDER BY convert_id asc) rn,%s from %s.%s " +
//这里我们排下序保证数据是顺序写入的
" where %s = %s and batch_no = '%s' order by convert_id ",
columnJoinString,
convertDwd,
table,
Constant.PARTITION_NUMBER, partitionNumber,
batchNo);
logger.info("spark_hive_sql开始执行toTable:{},querySql:{}", table, queryHiveSql);
Dataset<Row> dataset = getSparkSession().sql(queryHiveSql);
long count = dataset.count();
tableCountMap.put(table, count);
if (count <= 0) {
logger.info("all无数据本次不写入tidb");
continue;
}
//分页查询,这里我们计算出每页的起始偏移量
// count = 551 queryHiveMaxSize= 50
//[0,50,100,150,200,250,300,350,400,450,500,550,600]
List<Long> pageList = getPageList(count, queryHiveMaxSize);
//过滤出来每行的主键值
String filterSql = String.format(" rn in (%s)", pageList.stream().map(String::valueOf).collect(Collectors.joining(",")));
Dataset<Row> indexPositionDataset = dataset.filter(filterSql).select("rn", Constant.CONVERT_ID);
List<JSONObject> indexPositionJsonList = indexPositionDataset.collectAsList().stream().map(RowUtil::toJSONObjectAllowNull).collect(Collectors.toList());
//将刚才的每页第一个偏移量的主键映射为一个map
Map<String, Long> convertIdRowNumberMap = indexPositionJsonList.stream().collect(Collectors.toMap(key -> key.getString(Constant.CONVERT_ID), val -> val.getLong("rn")));
if (convertIdRowNumberMap.isEmpty()) {
logger.info("convertIdRowNumberMap无数据本次不写入tidb");
continue;
}
//为了防止中断写入,我们看下mysql库中根据主键写到第几个主键了
Collection<String> convertIdList = convertIdRowNumberMap.keySet();
String queryTidbSql = String.format(" select ifnull(max(convert_id),'') as convert_id from tidb_catalog.%s.%s where convert_id in ('%s')",
tiOptionMap.get("database"), tiOptionMap.get("table"),
String.join("','", convertIdList));
// 注意这里用的是tidb_catalog tispark的catalog,这里将会查询tidb,我把查询tidb放在这里后查是因为spark查询tidb太慢了,我这里只制定了少量的唯一主键,进行查询
Dataset<Row> tidbMaxConvertIdDataSet = getSparkSession().sql(queryTidbSql);
Row firstRow = tidbMaxConvertIdDataSet.first();
String tidbMaxConvertId = firstRow.getString(firstRow.fieldIndex(Constant.CONVERT_ID));
String convertId = StrUtil.isBlank(tidbMaxConvertId) ? "" : tidbMaxConvertId;
//根据mysql写入的最大值,来获取我们本次要从第几页开始写,toPageList最终可能只写入偏移量为100之后的数据
List<Long> toPageList = DestSaveTidbMain.subExistRowNumberPageList(pageList, convertIdRowNumberMap, convertId);
//获取hive库中的字段类型
StructType schema = dataset.schema();
//转为map
Map<String, ColumnInfoVO> columnInfoVOMap = Arrays.stream(schema.fields()).map(itemField -> new ColumnInfoVO().setType(itemField.dataType()).setName(itemField.name())).collect(Collectors.toMap(ColumnInfoVO::getName, Function.identity(), (v1, v2) -> {
logger.info("字段重名了,请排查,v1:{},v2:{}", v1, v2);
return v1;
}));
//这里根据tidb数据库的类型和hive库类型转一下,有些类型不支持自动转,比如hive[date]不能转tidb[timestamp]
List<Column> selectColumnList = transferDataTypeByTidb(columnInfoVOMap, columnInfoJsonJSONObject);
logger.info("tidb的convertId:{},queryHiveMaxSize:{},toPageList:{},convertIdRowNumberMap:{}", convertId, queryHiveMaxSize, toPageList, convertIdRowNumberMap);
//这里控制分页写入,控制事务大小,防止数据量太大,导致tidb压垮
for (int page = 1; page < toPageList.size(); page++) {
long start = toPageList.get(page - 1);
long end = toPageList.get(page);
Dataset<Row> itemDataset = dataset.filter(String.format(" rn > %d ", start)).filter(String.format(" rn <= %d ", end));
logger.info("{}<rn<={}_本次数据开始写入allSize:{}", start, end, count);
long startMills = System.currentTimeMillis();
//将数据写入TiDB
Dataset<Row> saveDataset = itemDataset.select(selectColumnList.toArray(new Column[0]));
try {
saveDataset.repartition(partitionMaxSize).write()
.format(Constant.DATA_SOURCE_TYPE_TIDB)
.options(tiOptionMap)
// tispark 只有这一种模式
// tidb有 写入时会只写没有的
.mode(SaveMode.Append)
.save();
} catch (Throwable e) {
itemDataset.show(1000);
saveDataset.show(1000);
logger.info("写入tidb报错", e);
throw e;
}
}
}
//记录下每个表写入总数
saveCountToBizDestSyncTable(batchNo, tiOptionMap, tableCountMap);
}
总结
tispark方式直连tidb 的pdserver,速度还是很快的
1934

被折叠的 条评论
为什么被折叠?



