FlinkX增量读取

FlinkX增量读取

增量同步指每次记录最大值,下次从最大值的位置来同步。

累加器是具有添加操作和最终累积结果的简单构造,可在作业结束后使用。

从Flink的实现上面讲,可以使用Flink的累加器记录作业的最大值,同步任务的每次运行使用上一个任务实例作为起始位置同步。

配置

  • increColumn

    • 描述:当需要增量同步或间隔轮询时指定此参数,任务运行过程中会把此字段的值存储到flink的Accumulator里,如果配置了指标,名称为:endLocation,类型为string,日期类型会转为时间戳,精度最多到纳秒,数值类型的为字段的值,程序结束时由外部应用获取。

    • 注意:

      • 指定的字段必须在column列表里存在,否则任务会失败;

      • 增量字段支持数值类型和日期类型,并且是升序的,推荐使用表主键;

    • 必选:否

    • 默认值:无

  • startLocation

    • 描述:此配置参数和increColumn参数配合使用,表示本次任务获取数据的开始位置。

    • 注意:

      • 此参数为空时进行全量同步
    • 必选:否

    • 默认值:无

  • useMaxFunc

    • 描述:进行增量同步任务时,如果指定的字段值存在重复值,比如字段类型为时间,精度到秒,就可能出现重复的时间,需要指定此字段为true,读取数据前会获取增量字段的最大值作为此次任务的结束位置,防止数据丢失。

    • 注意:

      • 此参数设为true时,会执行select max(increCol) from tb语句,会影响数据库负载,配置时需要考虑数据库的使用情况;

      • 此参数设置为true时,本次任务不会读取 increCol = max(increCol)的记录,会在任务下次运行时读取;

    • 必选:否

    • 默认:false

  • polling

    • 描述:是否开启间隔轮询,开启后会根据pollingInterval轮询间隔时间周期性的从数据库拉取数据。开启间隔轮询还需配置参数pollingIntervalincreColumn,可以选择配置参数startLocation。若不配置参数startLocation,任务启动时将会从数据库中查询增量字段最大值作为轮询的开始位置。

    • 必选:否

    • 默认值:false

  • pollingInterval

    • 描述:轮询间隔时间,从数据库中拉取数据的间隔时间,默认为5000毫秒。

    • 必选:否

    • 默认值:5000

实体类

public class IncrementConfig implements Serializable {

    /**
     * 是否为增量任务
     */
    private boolean increment;

    /**
     * 是否为增量轮询
     */
    private boolean polling;

    /**
     * 用于标记是否保存endLocation位置的一条或多条数据
     *  true:不保存
     *  false(默认):保存
     *  某些情况下可能出现最后几条数据被重复记录的情况,可以将此参数配置为true
     */
    private boolean useMaxFunc;

    private int columnIndex;

    private String columnName;

    private String columnType;

    private String startLocation;

    /**
     * 轮询时间间隔
     */
    private long pollingInterval;

    /**
     * 发送查询累加器请求的间隔时间
     */
    private int requestAccumulatorInterval;
}

初始化


protected void initMetric(InputSplit split) {
        if (!incrementConfig.isIncrement()) {
            return;
        }

        startLocationAccumulator = new LongMaximum();
        if (StringUtils.isNotEmpty(incrementConfig.getStartLocation())) {
            startLocationAccumulator.add(Long.parseLong(incrementConfig.getStartLocation()));
        }
        customPrometheusReporter.registerMetric(startLocationAccumulator, Metrics.START_LOCATION);
        getRuntimeContext().addAccumulator(Metrics.START_LOCATION, startLocationAccumulator);
        endLocationAccumulator = new LongMaximum();
        String endLocation = ((JdbcInputSplit) split).getEndLocation();
        if (endLocation != null && incrementConfig.isUseMaxFunc()) {
            endLocationAccumulator.add(Long.parseLong(endLocation));
        } else if (StringUtils.isNotEmpty(incrementConfig.getStartLocation())) {
            endLocationAccumulator.add(Long.parseLong(incrementConfig.getStartLocation()));
        }
        customPrometheusReporter.registerMetric(endLocationAccumulator, Metrics.END_LOCATION);
        getRuntimeContext().addAccumulator(Metrics.END_LOCATION, endLocationAccumulator);
    }
  • 初始化 startLocation累加器
  • 初始化endLocation累加器

查询上次同步的位置

private void queryStartLocation() throws SQLException{
        ps = dbConn.prepareStatement(querySql,ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY);
        ps.setFetchSize(fetchSize);
        ps.setFetchDirection(ResultSet.FETCH_REVERSE);
        ps.setQueryTimeout(queryTimeOut);
        resultSet = ps.executeQuery();
        hasNext = resultSet.next();
        if(hasNext){
            querySql = querySql + "and " + databaseInterface.quoteColumn(incrementConfig.getColumnName()) + " > ?";
            ps = dbConn.prepareStatement(querySql);
            ps.setFetchSize(fetchSize);
            ps.setQueryTimeout(queryTimeOut);
            LOG.info("update querySql, sql = {}", querySql);
        }
    }

设置最大值

public void openInternal(InputSplit inputSplit) throws IOException {
        try {
            // ----------------------------------------------

            String startLocation = incrementConfig.getStartLocation();
            // 如果是增量轮询
            if (incrementConfig.isPolling()) {
                endLocationAccumulator.add(Long.parseLong(startLocation));
                isTimestamp = "timestamp".equalsIgnoreCase(incrementConfig.getColumnType());
            // 如果是增量查询,并且使用最大值查询
            } else if ((incrementConfig.isIncrement() && incrementConfig.isUseMaxFunc())) {
                getMaxValue(inputSplit);
            }

            // ----------------------------------------------

        } catch (SQLException se) {
            throw new IllegalArgumentException("open() failed." + se.getMessage(), se);
        }

        LOG.info("JdbcInputFormat[{}]open: end", jobName);
    }



    /**
     * 将增量任务的数据最大值设置到累加器中
     *
     * @param inputSplit 数据分片
     */
    protected void getMaxValue(InputSplit inputSplit) {
        String maxValue = null;
        // 只有第0个任务才有更新增量值的权利,其它的都需要去查询api。
        if (inputSplit.getSplitNumber() == 0) {
            maxValue = getMaxValueFromDb();
            maxValueAccumulator = new StringAccumulator();
            maxValueAccumulator.add(maxValue);
            getRuntimeContext().addAccumulator(Metrics.MAX_VALUE, maxValueAccumulator);
        } else {
            maxValue = getMaxValueFromApi();
        }

        if (StringUtils.isEmpty(maxValue)) {
            throw new RuntimeException("Can't get the max value from accumulator");
        }

        ((JdbcInputSplit) inputSplit).setEndLocation(maxValue);
    }
  1. 如果是增量轮询,将当前开始的值,设置到``endLocationAccumulator里面。queryForPolling(endLocationAccumulator.getLocalValue().toString())`;
  2. 如果是增量查询,将增量任务的数据最大值设置到累加器中,只有第0个任务才有更新增量值的权利,其它的都需要去查询api。

你可能感兴趣的:(FlinkX,原理剖析,Flink,FlinkX)