ODPS工作笔记——数据一致性比对

场景:链路改造任务初始化数据比对


1,比什么?

比较新链路两次初始化的差异数据,是否都能在增量数据里找着
2什么时候比?
第一次初始化之后,稳定运行一段时间,第二次初始化以后比
3和谁比?
(第一次初始化+好多天增量的merge)和第二次初始化的数据比对
(暮老师说:新旧链路数据现阶段没必要比对的原因再强调下,在新链路自己的数据没有比对完成前,跟旧链路对数意义不大,两边都不知道自己是不是正确的)
所以是初始化都是副本库来的数据
比出来的再去和跑出来的增量数据比
4要比出什么?
比较第二次初始化比第一次初始化+merge*n多的,在增量里为I的
比较第二次初始化比第一次初始化+merge*n少的,在增量里为D的
比较第二次初始化比第一次初始化+merge*n明细不一样,在增量里为U的
5比完说明什么?
都找着了就ok fine
找不到就是链路有问题,丢数了
6 该咋比?(重点)


比较第二次初始化Tp2比(第一次初始化Tp1+merge)多的,在增量里为I的

(select 二次初始化.columns from 二次初始化 
left outer join 加了增量的第一次初始化 
on 二次初始化.pk=加了增量的第一次初始化.pk
where 加了增量的第一次初始化.pk is null ) as 二次比一次加增量多的
inner join 增量
on A1.pk=增量.pk
where coalesce(二次比一次加增量多的.column,’’) ==coalesce(增量.column,’’);
查询结果应找到并且增量标识符为I


比较第二次初始化Tp2比(第一次初始化Tp1+merge)少的,在增量里为D的
(select 二次初始化.columns from 二次初始化 
Right outer join 加了增量的第一次初始化 
on 二次初始化.pk=加了增量的第一次初始化.pk
where 二次初始化.pk is null ) as 二次比一次加增量少的
inner join 增量
on 二次比一次加增量少的.pk=增量.pk
where coalesce(二次比一次加增量多的.column,’’) ==coalesce(增量.column,’’);
查询结果应找到并且增量标识符为D



比较第二次初始化Tp2比(第一次初始化Tp1+merge)明细不同的,在增量里为U的
Select * from (select 二次初始化.columns from 二次初始化 
inner join 加了增量的第一次初始化 
on 二次初始化.pk=加了增量的第一次初始化.pk
) as 俩的明细
inner join 增量
on A1.pk=增量.pk
where coalesce(二次比一次加增量多的.column,’’) ==coalesce(增量.column,’’);
查询结果应找到并且增量标识符为U

始化

源端:Oracle

同步工具:DataX

目标端:ODPS

数据

第一次全量初始化的分区A(1)

merge了n天增量的分区A(n)

第二次全量初始化的分区B(n)

分区A(1)到A(n)的全部增量:(INCREMENT)INC

INC_(2)、INC_(3)...INC_(n)

说明

在T(1)抽取数据,初次初始化,存入T(0)的分区

在T(n)抽取数据,二次初始化,存入T(n)的分区

初始化的数据是非静态的,即PARTITION(T-1) = DATA_(T-1)+DATA_(T)

需求:

#---比对TIME_T(1) 至 TIME_T(n)之间的数据是否与INC_(2)至INC_(n-1)的数据一致

比对A比B多的,在增量INC中为delete动作的数据

比对A比B少的,在增量INC中为insert动作的数据

比对A和B明细存在差异的,在增量INC中为update动作的数据

方案:

        1.比对A比B多的,在增量INC中为delete动作的数据

        2.比对A比B少的,在增量INC中为insert动作的数据

          3.比对A和B明细存在差异的,在增量INC中为update动作的数据          

            coalesce,是为了保证NULL时,sql不报错

            trim去空格,是为了使比较明细的时候,忽略空格,只比较字符数据的明细

            使用两次to_date函数,会在问题处说明

SELECT 
    a.pk, a.col2, a.col3
FROM
    project.TABLE_Tn
        INNER JOIN
    (SELECT 
        b.pk, b.col2, b.col3
    FROM
        project.TABLE_Tn) ON a.pk = b.pk
WHERE
    b.pk IS NOT NULL
        AND (
        (COALESCE(a.pk, '') <> COALESCE(b.pk, '')) OR 
        (trim(COALESCE(a.col2, '')) <> trim(COALESCE(b.col2, ''))) OR 
        (TO_DATE(COALESCE(a.col3,
                    TO_DATE('9999-12-31 00:00:00',
                    'yyyy-mm-dd hh:mi:ss')),
            'yyyy-mm-dd hh:mi:ss') <> TO_DATE(COALESCE(b.col3,
                    TO_DATE('9999-12-31 00:00:00',
                            'yyyy-mm-dd hh:mi:ss')),
            'yyyy-mm-dd hh:mi:ss')));

问题:


全字段关联比对数据时,oracle时间戳格式的数据抽到odps的datetime类型,无法匹配。

因为ODPS不是开源的,所以猜测
DataX工具使用JDBC,当数据类型为TIMESTAMP时使用java.sql.Timestamp 的getTime方法,转换时间为一个长整型数,存放为object类型的rawData里
抽取到ODPS的数据,是有毫秒精度的。比数的时候
ODPS显示数据精度到秒,而比对精度到毫秒。但理论上不应该出现这些问题,所以希望大家给出指导建议

DateColumn.java

此处返回的是一个long类型

public DateColumn(final java.sql.Timestamp ts) {
		this(ts == null ? null : ts.getTime());
		this.setSubType(DateType.DATETIME);
	}




TIMESTAMP.java 返回long类型

/**

     * Returns the number of milliseconds since January 1, 1970, 00:00:00 GMT
     * represented by this <code>Timestamp</code> object.
     *
     * @return  the number of milliseconds since January 1, 1970, 00:00:00 GMT
     *          represented by this date.
     * @see #setTime
     */
    public long getTime() {
        long time = super.getTime();
        return (time + (nanos / 1000000));
    }



回到DataColumn类,使用父类构造初始化

public DateColumn(final Long stamp) {
		super(stamp, Column.Type.DATE, (null == stamp ? 0 : 8));
	}

 
 

父类(Column.java)的构造方法

public Column(final Object object, final Type type, int byteSize) {
		this.rawData = object;
		this.type = type;
		this.byteSize = byteSize;
	}
------目前为止,数据存在rawDate的是long类型
format

    /**
     * Converts a <code>String</code> object in JDBC timestamp escape format to a
     * <code>Timestamp</code> value.
     *
     * @param s timestamp in format <code>yyyy-[m]m-[d]d hh:mm:ss[.f...]</code>.  The
     * fractional seconds may be omitted. The leading zero for <code>mm</code>
     * and <code>dd</code> may also be omitted.
     *
     * @return corresponding <code>Timestamp</code> value
     * @exception java.lang.IllegalArgumentException if the given argument
     * does not have the format <code>yyyy-[m]m-[d]d hh:mm:ss[.f...]</code>
     */
此处是读数据
ResultSetReadProxy.java

public static void transportOneRecord(RecordSender recordSender, ResultSet rs, 
			ResultSetMetaData metaData, int columnNumber, String mandatoryEncoding, 
			TaskPluginCollector taskPluginCollector) {
		Record record = recordSender.createRecord();

		try {
			for (int i = 1; i <= columnNumber; i++) {
				switch (metaData.getColumnType(i)) {

				case Types.CHAR:
				case Types.NCHAR:
				case Types.VARCHAR:
				case Types.LONGVARCHAR:
				case Types.NVARCHAR:
				case Types.LONGNVARCHAR:
					String rawData;
					if(StringUtils.isBlank(mandatoryEncoding)){
						rawData = rs.getString(i);
					}else{
						rawData = new String((rs.getBytes(i) == null ? EMPTY_CHAR_ARRAY : 
							rs.getBytes(i)), mandatoryEncoding);
					}
					record.addColumn(new StringColumn(rawData));
					break;

				case Types.CLOB:
				case Types.NCLOB:
					record.addColumn(new StringColumn(rs.getString(i)));
					break;

				case Types.SMALLINT:
				case Types.TINYINT:
				case Types.INTEGER:
				case Types.BIGINT:
					record.addColumn(new LongColumn(rs.getString(i)));
					break;

				case Types.NUMERIC:
				case Types.DECIMAL:
					record.addColumn(new DoubleColumn(rs.getString(i)));
					break;

				case Types.FLOAT:
				case Types.REAL:
				case Types.DOUBLE:
					record.addColumn(new DoubleColumn(rs.getString(i)));
					break;

				case Types.TIME:
					record.addColumn(new DateColumn(rs.getTime(i)));
					break;

				// for mysql bug, see http://bugs.mysql.com/bug.php?id=35115
				case Types.DATE:
					if (metaData.getColumnTypeName(i).equalsIgnoreCase("year")) {
						record.addColumn(new LongColumn(rs.getInt(i)));
					} else {
						record.addColumn(new DateColumn(rs.getDate(i)));
					}
					break;

				case Types.TIMESTAMP:
					record.addColumn(new DateColumn(rs.getTimestamp(i)));
					break;

				case Types.BINARY:
				case Types.VARBINARY:
				case Types.BLOB:
				case Types.LONGVARBINARY:
					record.addColumn(new BytesColumn(rs.getBytes(i)));
					break;

				// warn: bit(1) -> Types.BIT 可使用BoolColumn
				// warn: bit(>1) -> Types.VARBINARY 可使用BytesColumn
				case Types.BOOLEAN:
				case Types.BIT:
					record.addColumn(new BoolColumn(rs.getBoolean(i)));
					break;

				case Types.NULL:
					String stringData = null;
					if(rs.getObject(i) != null) {
						stringData = rs.getObject(i).toString();
					}
					record.addColumn(new StringColumn(stringData));
					break;

				// TODO 添加BASIC_MESSAGE
				default:
					throw DataXException
							.asDataXException(
									DBUtilErrorCode.UNSUPPORTED_TYPE,
									String.format(
											"您的配置文件中的列配置信息有误. 因为DataX 不支持数据库读取这种字段类型. 字段名:[%s], 字段名称:[%s], 字段Java类型:[%s]. 请尝试使用数据库函数将其转换datax支持的类型 或者不同步该字段 .",
											metaData.getColumnName(i),
											metaData.getColumnType(i),
											metaData.getColumnClassName(i)));
				}
			}
		} catch (Exception e) {
			if (IS_DEBUG) {
				LOG.debug("read data " + record.toString()
						+ " occur exception:", e);
			}

			//TODO 这里识别为脏数据靠谱吗?
			taskPluginCollector.collectDirtyRecord(record, e);
			if (e instanceof DataXException) {
				throw (DataXException) e;
			}
		}

		recordSender.sendToWriter(record);
	}


ReaderProxy.java

 dataXRecord.addColumn(new DateColumn(odpsRecord
                        .getDatetime(columnNameValue)));

这些都不管

到odpswriter时odpswriterproxey.java

 switch (type) {
                    case STRING:
                        odpsRecord.setString(currentIndex, columnValue.asString());
                        break;
                    case BIGINT:
                        odpsRecord.setBigint(currentIndex, columnValue.asLong());
                        break;
                    case BOOLEAN:
                        odpsRecord.setBoolean(currentIndex, columnValue.asBoolean());
                        break;
                    case DATETIME:
                        odpsRecord.setDatetime(currentIndex, columnValue.asDate());
                        break;

此处调用了asDate方法

回到DataColumn.java

public Date asDate() {
		if (null == this.getRawData()) {
			return null;
		}

		return new Date((Long)this.getRawData());
	}

通过长整型转换Date类型。

放入

odpsRecord

所以到底是人性的扭曲,还是道德的沦丧。过几日,测试一下这个思路对不对。

-----欢迎大神批评指导,刚上班8个月,还不是很懂,请发邮件[email protected]联系我,或者留言谢谢。

猜你喜欢

转载自blog.csdn.net/weixin_40245633/article/details/79903624