简介:这份《10数据处理和存储系统建设方案》面向智能化管控平台的项目规划人员、系统集成工程师及信息化方案撰写者,围绕用户规模测算、数据计算、存储容量与传输带宽四大维度,给出可落地的建设思路。方案以300个用户(化工企业、政府园区职能部门、互联网公众三类)为基准,采用TPC-C基准推算数据库服务器峰值处理能力,并据此估算CPU核数与服务器数量;存储部分按系统数据年增500M、业务数据年约2.7TB测算,规划三年8.1TB容量;传输部分覆盖用户访问、物联网感知与视频监控带宽需求,并给出软硬件选型与冗余配置建议。资源包为1个doc文档,约183KB,内容以目录分节形式组织,涵盖信息量分析与预测、系统结构、数据计算、数据存储、数据传输及设备选型等模块,结构完整、测算过程清晰。目前已有223人学习,适合需要撰写同类方案或进行容量规划的技术人员参考借鉴。
1. 数据处理和存储系统建设方案:从一堆散落日志到能查、能算、能扩容的底座
很多团队第一次认真对待“数据处理和存储系统建设方案”,不是因为规划做得好,而是因为一次线上排查翻车了。日志散在几台机器上,业务库慢查询把磁盘打满,运营要一份近 30 天的留存报表,数据组翻了三个脚本才拼出来,还不敢保证口径一致。这时候大家才意识到,缺的不是某一款数据库,而是一套从采集、落盘、加工到查询的完整链路。
这个方案要解决的核心问题很朴素:数据进来不丢、存下来不贵、查起来不慢、涨上来能扩。它适合正在从单机脚本往平台化过渡的中小团队,也适合已有数仓但采集和存储层各自为政、维护成本高的场景。下面按“先定分层和选型,再动手搭最小链路,最后处理扩容和排错”的顺序讲,每一步都给出可复现的命令和参数。
2. 先定分层与选型:别让存储引擎替你做架构决策
2.1 数据分层怎么切:ODS、DWD、DWS 的边界
数据处理和存储系统建设方案里最容易含糊过去的就是分层。常见做法是切成三层:ODS 贴源存放原始数据,不做清洗,只做格式统一和分区;DWD 做清洗、去重、字段标准化;DWS 按主题做轻度聚合,供报表和接口查询。边界不清的后果是,所有人都在 ODS 上写查询,原始表被反复全表扫描,存储成本和处理成本一起涨。
判断分层是否合理,看两个信号:一是 ODS 层是否只允许追加写入,不允许更新和删除;二是 DWS 层是否能独立回答 80% 的常规查询。如果做不到,说明聚合粒度选错了,要么太细导致没省下计算,要么太粗导致口径对不上。
分区策略上,时间字段是默认选择,但要注意分区粒度和数据量的匹配。日增百万行以内按天分区足够;日增千万行以上,按小时分区能显著减少扫描量,但小文件问题会变严重,需要配合合并任务。
2.2 存储选型对照:关系库、列式库、对象存储各管什么
选型不是选“最好的”,是选“各管一段”的。下面这张对照表是我在多个模拟项目里反复用到的判断依据:
| 存储类型 | 适合的数据形态 | 写入特征 | 查询特征 | 典型用途 |
|---|---|---|---|---|
| 关系型数据库 | 结构化、强事务 | 高频小批量 | 点查、关联 | 元数据、配置、订单 |
| 列式数据库 | 宽表、分析型 | 批量导入 | 聚合、扫描 | DWD/DWS 明细与汇总 |
| 对象存储 | 原始文件、归档 | 一次写入多次读取 | 按前缀列举 | ODS 原始落盘、备份 |
| 搜索引擎 | 半结构化、文本 | 近实时写入 | 全文检索、过滤 | 日志检索、标签查询 |
一个常见误区是把列式库当关系库用,频繁做单行更新,结果写入放大严重、合并压力大。列式库的正确用法是批量写入、按分区覆盖,更新交给上层调度去重写分区。
2.3 采集与写入链路的最小闭环
采集层要解决三件事:来源统一、格式统一、失败可重试。最小闭环可以这样搭:业务侧通过日志文件或消息队列产出数据,采集组件按固定间隔拉取或订阅,写入 ODS 层,同时记录采集位点和批次状态。
写入时有两个参数必须显式设置:批次大小和重试次数。批次太小,写入频繁、小文件多;批次太大,单次失败重传成本高。我一般把批次控制在 5 万到 20 万行之间,重试 3 次并带退避。位点要持久化,否则重启后会重复消费或丢数据。
注意:采集位点不要只存在内存里,进程重启就丢。落盘或写入外部存储,才能保证至少一次投递。
3. 动手搭最小链路:从建表到跑通第一条查询
3.1 建 ODS 表:分区字段和文件格式怎么定
先建一张贴源表,字段与来源保持一致,只增加分区字段和采集时间。以列式库为例:
-- ODS 层:贴源存放,只追加,不做业务清洗 CREATE TABLE ods_event_log ( event_id STRING, -- 事件唯一标识,用于后续去重 user_id STRING, -- 用户标识 event_type STRING, -- 事件类型 event_time TIMESTAMP, -- 事件发生时间 payload STRING, -- 原始内容,保持原样 collect_time TIMESTAMP -- 采集落库时间 ) PARTITIONED BY (dt STRING) -- 按天分区,dt 格式 yyyy-MM-dd STORED AS PARQUET; -- 列式存储,压缩比和扫描效率兼顾逻辑说明:event_id保留原始值,去重放到 DWD 层做,ODS 不承担清洗职责。payload用字符串存原始内容,避免解析失败导致整条数据丢失。分区字段dt单独抽出,写入时由采集程序按事件时间填充。
参数说明:文件格式选列式,是因为分析查询只读部分列时能跳过无关数据。如果来源是嵌套结构,可以先存字符串,在 DWD 层用解析函数展开,这样 ODS 层不会因为结构变化而频繁改表。
3.2 写采集脚本:批次、重试、位点三个参数
下面是一个简化的采集写入脚本,演示批次控制和位点记录:
import time import json BATCH_SIZE = 100000 # 单批行数,5万到20万之间按内存调整 MAX_RETRY = 3 # 失败重试次数 RETRY_BACKOFF = 5 # 重试退避基数,秒 def load_checkpoint(path): # 读取上次采集位点,进程重启后从这里继续 try: with open(path, "r") as f: return json.load(f) except FileNotFoundError: return {"offset": 0} def save_checkpoint(path, offset): # 位点落盘,保证至少一次投递 with open(path, "w") as f: json.dump({"offset": offset}, f) def write_batch(rows, dt): # 实际写入逻辑,按分区写入 ODS 表 # 这里用伪代码表示批量写入接口 pass def run(source, checkpoint_path): state = load_checkpoint(checkpoint_path) while True: rows = source.read(state["offset"], BATCH_SIZE) if not rows: time.sleep(10) continue for attempt in range(MAX_RETRY): try: write_batch(rows, dt=rows[0]["dt"]) state["offset"] += len(rows) save_checkpoint(checkpoint_path, state["offset"]) break except Exception as e: if attempt == MAX_RETRY - 1: raise time.sleep(RETRY_BACKOFF * (attempt + 1))逻辑说明:先读位点,再按批次拉取,写入成功后更新位点。写入失败时按退避重试,超过次数才抛出,避免静默丢数据。位点文件放在持久化目录,不要放在临时目录。
参数说明:BATCH_SIZE影响内存占用和写入效率,行宽大时调小;MAX_RETRY不要设太大,否则故障时会长时间卡住;RETRY_BACKOFF用递增退避,避免对下游造成持续压力。
3.3 建 DWD 表并跑通第一条聚合查询
DWD 层做去重和字段标准化,通常按事件时间去重,保留最新一条:
-- DWD 层:去重并标准化字段 CREATE TABLE dwd_event_log AS SELECT event_id, user_id, event_type, event_time, collect_time FROM ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY event_id ORDER BY collect_time DESC ) AS rn FROM ods_event_log WHERE dt = '2025-01-01' ) t WHERE rn = 1;逻辑说明:用窗口函数按event_id分组,按采集时间倒序取第一条,实现去重。实际生产中会按分区逐天处理,避免一次扫描过多数据。
参数说明:PARTITION BY event_id的分组键要和业务唯一键一致,选错会导致误去重。如果事件可能更新,去重规则要改成保留最新状态,而不是简单取第一条。
跑通第一条聚合查询,验证链路是否可用:
-- 按事件类型统计当日数量,验证 DWD 数据可查 SELECT event_type, COUNT(*) AS cnt FROM dwd_event_log WHERE dt = '2025-01-01' GROUP BY event_type ORDER BY cnt DESC LIMIT 20;如果这条查询能在秒级返回,说明采集、落盘、去重、查询这条最小链路已经通了。接下来才是扩容和优化。
4. 扩容与性能:数据涨上来之后先动哪里
4.1 分区和小文件:先合并再调并行度
数据量涨上来后,最先出问题的往往不是存储容量,而是小文件过多。采集批次小、写入频繁,每个分区里堆几千个小文件,查询时打开文件的开销比读数据还大。处理顺序是:先合并小文件,再调查询并行度。
合并任务按分区重写,把多个小文件合成较大文件。常见做法是每天凌晨对前一天分区执行一次合并,目标文件大小控制在 128MB 到 256MB。合并后再看查询,如果还是慢,再考虑提高并行度。
注意:合并任务本身会消耗资源,不要和业务高峰重叠。合并频率按小文件增长速度定,不必每天全量合并。
4.2 冷热分离:把归档数据挪到对象存储
不是所有数据都需要留在高性能存储上。超过一定时间的明细数据,查询频率骤降,可以归档到对象存储,只保留聚合结果在列式库。判断标准是:最近 7 天查询占比超过 90%,那 30 天以前的数据就可以考虑归档。
归档不是简单删除,要保留可回溯路径。常见做法是归档时记录分区清单和校验值,需要时能按分区恢复。恢复流程要提前演练,否则真要用的时候才发现路径对不上。
4.3 资源隔离:别让一个慢查询拖垮整条链路
写入和查询共用资源时,一个全表扫描的慢查询可能把写入也拖慢。隔离手段有两种:一是按队列分组,写入走独立队列;二是按资源组限制查询的并发和内存。优先做队列隔离,成本低、见效快。
如果条件允许,把 ODS 写入和 DWD/DWS 查询分到不同资源组,写入队列优先级高于查询队列。这样即使查询排队,采集也不会积压。
5. 避坑与排查:五个真实踩过的坑
5.1 坑一:分区字段类型不一致导致查不到数据
现象:明明写入了数据,按日期查询却返回空。原因是写入时分区字段是字符串2025-01-01,查询时传了日期类型,两边不匹配。解决:统一分区字段类型,写入和查询都用字符串,并在采集脚本里做格式校验。
5.2 坑二:去重键选错导致数据被误合并
现象:DWD 层数据量比预期少很多。原因是去重键选了user_id,把同一用户的多个事件合并成一条。解决:去重键必须是业务唯一键,通常是event_id,选错会静默丢数据,且很难发现。
5.3 坑三:位点文件放在临时目录,重启后重复消费
现象:任务重启后数据重复。原因是位点文件放在临时目录,重启后被清理,任务从头开始消费。解决:位点文件放在持久化目录,并定期备份。重复消费可以用 DWD 层去重兜底,但不要依赖它。
5.4 坑四:合并任务和写入任务抢资源
现象:合并任务跑起来后,写入延迟明显上升。原因是两者共用同一资源组,合并任务扫描大量文件占用 IO。解决:合并任务放到低优先级队列,错峰执行,并限制单次合并的分区数量。
5.5 坑五:归档后没有保留恢复路径
现象:需要查历史数据时,发现归档文件路径没记录,恢复不了。原因是归档时只删了原数据,没记录分区清单。解决:归档时生成清单文件,记录分区、文件路径和校验值,恢复流程提前演练一次。
6. 进阶技巧:用校验和监控把问题挡在爆发之前
链路跑通之后,真正省心的是提前发现问题。我一般会加两个东西:数据校验和链路监控。
数据校验分两层。第一层是行数校验,采集批次写入后,对比来源行数和落库行数,差异超过阈值就告警。第二层是分区校验,每天对前一天分区做一次抽样比对,检查关键字段的空值率和枚举值分布是否异常。校验结果写入一张监控表,方便回溯。
-- 分区校验:检查关键字段空值率和枚举分布 SELECT dt, COUNT(*) AS total, SUM(CASE WHEN user_id IS NULL THEN 1 ELSE 0 END) AS null_user, COUNT(DISTINCT event_type) AS type_cnt FROM dwd_event_log WHERE dt = '2025-01-01' GROUP BY dt;逻辑说明:null_user反映采集是否丢字段,type_cnt反映事件类型是否突然减少。两个指标同时异常,基本可以定位到采集或解析环节。
参数说明:空值率阈值按业务定,一般超过 1% 就值得看一眼;枚举值数量波动超过 20% 也要告警。
链路监控看三个指标:采集延迟、写入失败率、查询 P99 耗时。采集延迟超过 10 分钟告警,写入失败率超过 1% 告警,查询 P99 超过 5 秒告警。这三个指标能覆盖大部分常见故障。
最后一个习惯:每次调整分区策略或去重规则,先在影子表上跑一天,对比新旧结果,确认口径一致再切换。这个习惯帮我省过好几次后悔药。希望帮到你。
本文还有配套的精品资源,点击获取