大数据中数据同步从hive中同步到mysql,tidb中的方法

目前的同步方法有以下三种

  1. 查询hive库,发送kafka消息,应用消费kafka,写入mysql库

  2. 查询hive库,spark dataset save jdbc方式写入

  3. 如果是tidb库,官方支持tisprk方式写入

各自优缺点

  1. 把hive数据查出来,在发送kafka增加了复杂性,引入了kafka,出现问题多了一个排查范围,但是这种方式能够利用kafka削峰,不至于将tidb打垮,缺点:消息是无序的,所以最后一条的消费情况无法确认,需要借助redis来记录数据消费情况以及总数,因为kafka速度较快只能借助redis辅助记录。

  2. 通过spark dataset jdbc方式需要控制partition分区数,防止把db打垮,使用dataset.repartition(n)

  3. 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,速度还是很快的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值