数据源接入策略:结构化、半结构化与非结构化

聊到数据源接入,我脑子里第一个蹦出来的词就是「五花八门」。做了这么多年ETL,接过的数据源少说也有上百种。MySQL、Oracle、JSON、XML、CSV、日志文件、图片……每种都有它的脾气。

今天我就把这几类数据源的接入策略掰开揉碎了讲。你想想看,如果连数据都接不进来,后面的清洗、建模全是空谈。

结构化数据接入:MySQL/Oracle

结构化数据,说白了就是那种规规矩矩待在关系型数据库里的数据。有表、有字段、有主键、有外键。这类数据接入相对简单,但坑也不少。

全量接入 vs 增量接入

我个人习惯,第一次接入时用全量。把整张表拉过来,做个快照。但后续怎么办?

我建议用增量接入。为什么?因为全量太慢了。我在项目中遇到过一张订单表,每天新增200万条记录,全量拉一次要40分钟。后来改成增量,5分钟搞定。

增量接入的常见策略:

  • 时间戳增量:利用表中的update_time或create_time字段,只拉取最近变更的数据
  • 自增ID增量:记录上次拉取的最大ID,下次只拉取大于该ID的数据
  • 日志解析增量:解析MySQL的binlog或Oracle的redo log,实时捕获变更

核心原则:能增量就别全量,能实时就别离线。但增量接入必须保证数据一致性,否则会出现数据丢失或重复。

连接池与并发控制

嗯,这里要注意。直接连数据库拉数据,很容易把源库拖垮。我曾经见过一个新手,开了50个线程同时拉Oracle,结果源库CPU直接飙到100%,业务系统挂了半小时。

正确的做法:

  • 使用连接池,控制最大连接数(一般建议5-10个)
  • 分页拉取,每页1000-5000条,避免一次拉取过多数据
  • 设置超时时间,防止连接长时间占用
-- 分页拉取示例(MySQL)
SELECT * FROM orders 
WHERE id > :last_id 
ORDER BY id 
LIMIT 1000;

数据类型映射

MySQL的datetime和Oracle的TIMESTAMP,到了大数据平台怎么映射?我踩过这个坑。

源数据类型 Hive/Spark类型 注意事项
MySQL DATETIME STRING / TIMESTAMP 注意时区转换,建议统一转为UTC
Oracle NUMBER(18,2) DECIMAL(18,2) 精度不能丢,否则金额对不上
MySQL VARCHAR(255) STRING 长度限制可以放宽,但注意特殊字符
Oracle CLOB STRING 大字段要单独处理,避免影响性能

避坑指南:我曾经因为没处理好Oracle的NUMBER类型,导致金额字段精度丢失,对账差了3万多块钱。从那以后,所有金额字段我都强制用DECIMAL(18,4)来存。

半结构化数据接入:JSON/XML/CSV

半结构化数据,说白了就是「有结构但不那么死板」。JSON可以嵌套,XML有属性,CSV字段顺序可能变。这类数据接入,核心在于「解析」和「扁平化」。

JSON数据接入

JSON现在太常见了。API接口返回的基本都是JSON。我建议的做法:

  • 先做Schema推断,看看JSON里到底有哪些字段
  • 嵌套结构要扁平化,比如把address.city变成address_city
  • 数组类型要炸开(explode),一行变多行
-- Spark SQL解析JSON示例
SELECT 
  id,
  name,
  get_json_object(detail, '$.address.city') as city,
  get_json_object(detail, '$.address.street') as street
FROM raw_json_table;

我在项目中遇到过一个问题:JSON里某个字段有时是字符串,有时是数组。比如tags字段,有时是"tag1,tag2",有时是["tag1","tag2"]。这种不一致最头疼。我的处理方式是:统一用数组解析,如果是字符串就先用split转成数组。

XML数据接入

XML现在用得少了,但金融、保险行业还在大量使用。XML的难点在于:

  • 属性(attribute)和元素(element)混在一起
  • 命名空间(namespace)处理麻烦
  • 层级深,解析性能差

我建议用XPath来提取数据,比逐层解析快得多。

-- Hive解析XML示例
SELECT 
  xpath_string(xml_data, '//order/@id') as order_id,
  xpath_string(xml_data, '//order/items/item/name') as item_name
FROM raw_xml_table;

CSV数据接入

CSV看着简单,其实坑最多。你想想看,字段里如果包含逗号怎么办?字段值里有换行符怎么办?

我总结了几条铁律:

  • 指定分隔符,不要依赖默认(常见的有逗号、制表符、竖线)
  • 处理引号转义,字段值用双引号包裹时,内部的双引号要转义
  • 处理换行符,字段值内部可能包含换行,不能简单按行切分
  • 处理BOM头,Windows生成的CSV文件开头可能有个不可见字符

小技巧:我一般用Spark的DataFrameReader来读CSV,设置option("escapeQuotes", "true")和option("multiLine", "true"),能解决90%的问题。

非结构化数据接入:日志/图片

非结构化数据,说白了就是「没有固定格式」的数据。日志文件一行一个格式,图片更是连文本都不是。这类数据接入,核心在于「提取有价值的信息」。

日志文件接入

日志文件是最常见的非结构化数据。Nginx日志、应用日志、系统日志……每种的格式都不一样。

我的接入策略:

  • 先确定日志格式(Apache格式、JSON格式、自定义格式)
  • 用正则表达式提取关键字段(时间、级别、来源、消息)
  • 处理异常行(格式不对的行不能直接丢弃,要单独存储)
-- 解析Nginx日志示例(正则表达式)
^(\S+) (\S+) (\S+) \[([^\]]+)\] "([^"]*)" (\d{3}) (\d+) "([^"]*)" "([^"]*)"$

-- 提取字段:
-- $1: IP地址
-- $4: 时间戳
-- $5: 请求方法+路径
-- $6: 状态码
-- $7: 响应大小

嗯,这里要注意。日志文件接入最大的问题是「乱序」。比如凌晨2点的日志,可能因为网络延迟,凌晨3点才到达。我建议用事件时间(event_time)而不是处理时间(process_time)来做分区。

图片文件接入

图片接入,说白了就是「存起来,等后面用」。图片本身没有结构化信息,但它的元数据(文件名、大小、创建时间)是有价值的。

我建议的做法:

  • 图片文件存到对象存储(HDFS、S3)
  • 元数据存到数据库(文件名、路径、大小、MD5、上传时间)
  • 后续通过OCR、图像识别提取内容

核心思路:非结构化数据接入,不是要把数据本身结构化,而是要把「描述数据的数据」(元数据)结构化。图片的内容,留给AI去处理。

接入策略总结

说了这么多,我最后总结一下:

数据源类型 接入方式 核心关注点 常见工具
MySQL/Oracle JDBC直连、CDC 增量策略、连接控制、类型映射 Sqoop、DataX、Canal
JSON/XML/CSV 文件解析、API调用 Schema推断、扁平化、异常处理 Spark、Flume、Logstash
日志/图片 文件采集、流式接入 格式解析、元数据提取、乱序处理 Flume、Filebeat、Kafka

数据源接入,说白了就是「把数据从A搬到B,同时保证数据不丢、不重、不乱」。听起来简单,做起来全是细节。我做了这么多年,每次接新数据源还是会遇到新问题。但只要你掌握了这些核心策略,大部分场景都能应对。

下一章,我会讲数据清洗的实战技巧。到时候再聊。