4. 数据持久化策略:关系型数据库 vs NoSQL、时序数据库选型、缓存层设计
数据持久化,说白了就是你的交易数据到底要存哪儿、怎么存。我见过不少团队,一上来就拍脑袋选MySQL,结果遇到高并发写入直接崩了。也见过有人盲目上NoSQL,最后发现事务一致性根本搞不定。嗯,这里头门道不少。
我个人习惯,先把数据分个类:
- 交易核心数据:订单、账户余额、成交记录。这类数据要求强一致性,ACID是底线。
- 行为与日志数据:用户点击流、系统日志、监控指标。这类数据量大,写多读少,对一致性要求不高。
- 高频访问的热数据:行情快照、用户持仓摘要。这类数据需要极低延迟,扛得住高并发。
不同类型的业务场景,对应的存储方案完全不同。咱们一个一个拆开看。
4.1 关系型数据库:核心交易的基石
交易系统的核心,比如订单簿、资金账户,我建议你老老实实用关系型数据库。为什么?因为交易最怕数据错乱。你想想看,一个订单被重复成交,或者账户余额对不上,那是要出大事的。
我在项目中遇到过,有人想用MongoDB存订单,结果并发一高,订单状态更新丢失了。排查了半天,最后发现是MongoDB的写操作不是原子性的。从那以后,核心交易数据我只信任MySQL或PostgreSQL。
选型上,我一般这么看:
| 特性 | MySQL | PostgreSQL |
|---|---|---|
| 事务支持 | 强(InnoDB) | 更强(MVCC) |
| 高并发写入 | 一般,需分库分表 | 较好,但也要分片 |
| 扩展性 | 主从复制、分片 | 流复制、逻辑复制 |
| 运维成本 | 低,生态成熟 | 中等,功能丰富 |
4.2 NoSQL:灵活与性能的取舍
非核心交易数据,比如用户画像、历史订单查询、风控规则配置,用NoSQL会更舒服。NoSQL的强项是灵活的数据模型和水平扩展能力。
但这里有个坑——不要用NoSQL存需要事务的数据。我曾经见过一个项目,用Cassandra存用户资产,结果因为Cassandra的最终一致性,用户提现时余额显示不对,被投诉到监管部门。嗯,这个教训挺深刻的。
常见的NoSQL选型场景:
- MongoDB:适合文档型数据,比如用户配置、风控规则。注意,它的事务是单文档级别的,跨文档事务性能很差。
- Cassandra:适合写多读少、无强一致性要求的场景,比如日志存储。它的写入吞吐量极高,但读性能一般。
- HBase:适合超大规模数据,比如历史交易流水。但运维成本高,不建议小团队用。
4.3 时序数据库:监控与分析的利器
交易系统里,监控数据是海量的。每秒成千上万的订单、行情、系统指标,如果用关系型数据库存,查询性能会非常差。这时候,时序数据库就派上用场了。
我常用的两个时序数据库:InfluxDB和ClickHouse。它们各有千秋。
InfluxDB:轻量级监控首选
InfluxDB的设计理念就是“写快查慢”。它针对时序数据做了大量优化,比如数据压缩、自动过期、连续查询。适合存储短期的监控指标,比如CPU使用率、网络延迟、订单处理耗时。
举个例子,我们监控系统每秒钟上报一次订单处理延迟:
-- 写入数据
INSERT INTO order_latency (tag=order_type, value=12.5) VALUES (12.5)
-- 查询最近5分钟的平均延迟
SELECT MEAN(value) FROM order_latency WHERE time > now() - 5m
InfluxDB的查询语法很简洁,但它的集群版是收费的,单机版性能有限。如果数据量超过单机容量,就得考虑其他方案了。
ClickHouse:分析型查询的王者
ClickHouse是列式存储数据库,适合做大规模数据的聚合分析。比如,你要统计过去一个月每个交易对的成交量、最高价、最低价,用ClickHouse几秒钟就能出结果。
我在项目中遇到过,用MySQL跑一个月的交易统计,跑了10分钟还没出结果。换成ClickHouse后,同样的查询只需要2秒。差距就是这么明显。
ClickHouse的典型用法:
-- 创建表
CREATE TABLE trade_stats (
trade_date Date,
symbol String,
volume Float64,
price Float64
) ENGINE = MergeTree()
ORDER BY (trade_date, symbol);
-- 查询每个交易对的总成交量
SELECT symbol, SUM(volume) AS total_volume
FROM trade_stats
WHERE trade_date >= '2024-01-01'
GROUP BY symbol;
- 实时监控、短周期数据 → InfluxDB
- 历史分析、长周期聚合 → ClickHouse
- 两者可以配合使用:InfluxDB存最近7天的数据,ClickHouse存全量历史数据。
4.4 缓存层设计:Redis的妙用
交易系统里,缓存不是可选项,而是必选项。为什么?因为数据库扛不住高频读。你想想看,行情数据每秒变化几百次,如果每次都查数据库,数据库早就挂了。
Redis是我最常用的缓存方案。它支持多种数据结构,性能极高,单机QPS能到10万+。但用好Redis,有几个关键点要注意。
缓存什么?
- 行情快照:最新价格、买卖盘口。用Hash结构存,key是交易对,field是价格类型。
- 用户持仓摘要:用户当前持有的资产数量。用String或Hash存,注意设置过期时间。
- 热点数据:比如最近成交的100笔订单。用List或Sorted Set存,方便快速展示。
缓存策略
我一般用两种策略:
- 旁路缓存(Cache-Aside):读的时候先查缓存,缓存没有就查数据库,然后回填缓存。写的时候先更新数据库,再删除缓存。这是最常用的模式,简单可靠。
- 写穿透(Write-Through):写操作直接写缓存,由缓存同步到数据库。适合写少读多的场景,但要注意缓存和数据库的一致性问题。
缓存穿透、击穿、雪崩
这三个问题,是缓存设计的经典坑。我一个个说:
- 缓存穿透:查询一个不存在的数据,缓存没有,数据库也没有。每次请求都打到数据库。解决方案:布隆过滤器,或者缓存空值(设置短过期时间)。
- 缓存击穿:某个热点key过期,大量请求同时打到数据库。解决方案:互斥锁(SETNX),或者设置热点key永不过期。
- 缓存雪崩:大量key同时过期,导致数据库压力暴增。解决方案:过期时间加随机值,避免同时过期。
我曾经在项目中遇到过缓存雪崩。当时所有行情数据的缓存过期时间都设成了1小时,结果整点一到,数据库瞬间被打满。嗯,从那以后,我每次设过期时间都会加一个随机偏移量。
4.5 整体架构图
下面这张图,展示了交易系统中数据持久化层的整体架构。你可以看到,不同数据流向不同的存储,缓存层在最前面挡着,时序数据库负责监控和分析。
这张图里,数据流向很清晰:交易应用先查Redis,缓存命中就直接返回。缓存没命中,再根据数据类型去查对应的数据库。时序数据库独立于核心链路,专门处理监控和分析请求。
1. 核心交易数据 → 关系型数据库(强一致性)
2. 非核心数据 → NoSQL(灵活扩展)
3. 监控与分析 → 时序数据库(高性能写入与聚合)
4. 热数据 → Redis缓存(低延迟高并发)
好了,数据持久化这块就聊到这儿。记住,没有银弹。每种存储方案都有自己的适用场景,关键是要搞清楚你的业务到底需要什么。下一章,咱们聊聊交易系统的网络通信与协议设计。
公众号:蓝海资料掘金营,微信deep3321