4. 数据持久化策略:关系型数据库 vs NoSQL、时序数据库选型、缓存层设计

数据持久化,说白了就是你的交易数据到底要存哪儿、怎么存。我见过不少团队,一上来就拍脑袋选MySQL,结果遇到高并发写入直接崩了。也见过有人盲目上NoSQL,最后发现事务一致性根本搞不定。嗯,这里头门道不少。

我个人习惯,先把数据分个类:

  • 交易核心数据:订单、账户余额、成交记录。这类数据要求强一致性,ACID是底线。
  • 行为与日志数据:用户点击流、系统日志、监控指标。这类数据量大,写多读少,对一致性要求不高。
  • 高频访问的热数据:行情快照、用户持仓摘要。这类数据需要极低延迟,扛得住高并发。

不同类型的业务场景,对应的存储方案完全不同。咱们一个一个拆开看。

4.1 关系型数据库:核心交易的基石

交易系统的核心,比如订单簿、资金账户,我建议你老老实实用关系型数据库。为什么?因为交易最怕数据错乱。你想想看,一个订单被重复成交,或者账户余额对不上,那是要出大事的。

我在项目中遇到过,有人想用MongoDB存订单,结果并发一高,订单状态更新丢失了。排查了半天,最后发现是MongoDB的写操作不是原子性的。从那以后,核心交易数据我只信任MySQL或PostgreSQL。

选型上,我一般这么看:

特性 MySQL PostgreSQL
事务支持 强(InnoDB) 更强(MVCC)
高并发写入 一般,需分库分表 较好,但也要分片
扩展性 主从复制、分片 流复制、逻辑复制
运维成本 低,生态成熟 中等,功能丰富
我的建议:如果团队对MySQL熟悉,就用MySQL + 分库分表方案。如果团队技术栈偏新,PostgreSQL的JSONB和窗口函数在复杂查询上更有优势。

4.2 NoSQL:灵活与性能的取舍

非核心交易数据,比如用户画像、历史订单查询、风控规则配置,用NoSQL会更舒服。NoSQL的强项是灵活的数据模型和水平扩展能力。

但这里有个坑——不要用NoSQL存需要事务的数据。我曾经见过一个项目,用Cassandra存用户资产,结果因为Cassandra的最终一致性,用户提现时余额显示不对,被投诉到监管部门。嗯,这个教训挺深刻的。

常见的NoSQL选型场景:

  • MongoDB:适合文档型数据,比如用户配置、风控规则。注意,它的事务是单文档级别的,跨文档事务性能很差。
  • Cassandra:适合写多读少、无强一致性要求的场景,比如日志存储。它的写入吞吐量极高,但读性能一般。
  • HBase:适合超大规模数据,比如历史交易流水。但运维成本高,不建议小团队用。
避坑指南:我曾经在选型时,被NoSQL的“高性能”宣传迷惑了。后来发现,NoSQL的高性能是有前提的——你得接受它的一致性模型。交易系统里,一致性比性能更重要。所以,核心数据用关系型,非核心数据用NoSQL,这个边界要划清楚。

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存,方便快速展示。

缓存策略

我一般用两种策略:

  1. 旁路缓存(Cache-Aside):读的时候先查缓存,缓存没有就查数据库,然后回填缓存。写的时候先更新数据库,再删除缓存。这是最常用的模式,简单可靠。
  2. 写穿透(Write-Through):写操作直接写缓存,由缓存同步到数据库。适合写少读多的场景,但要注意缓存和数据库的一致性问题。
我的经验:在交易系统里,我倾向于用旁路缓存。因为写穿透一旦缓存挂了,数据就丢了。旁路缓存虽然多一次数据库查询,但数据安全性更高。

缓存穿透、击穿、雪崩

这三个问题,是缓存设计的经典坑。我一个个说:

  • 缓存穿透:查询一个不存在的数据,缓存没有,数据库也没有。每次请求都打到数据库。解决方案:布隆过滤器,或者缓存空值(设置短过期时间)。
  • 缓存击穿:某个热点key过期,大量请求同时打到数据库。解决方案:互斥锁(SETNX),或者设置热点key永不过期。
  • 缓存雪崩:大量key同时过期,导致数据库压力暴增。解决方案:过期时间加随机值,避免同时过期。

我曾经在项目中遇到过缓存雪崩。当时所有行情数据的缓存过期时间都设成了1小时,结果整点一到,数据库瞬间被打满。嗯,从那以后,我每次设过期时间都会加一个随机偏移量。

4.5 整体架构图

下面这张图,展示了交易系统中数据持久化层的整体架构。你可以看到,不同数据流向不同的存储,缓存层在最前面挡着,时序数据库负责监控和分析。

交易系统数据持久化架构 交易应用 Redis 缓存层 MySQL / PostgreSQL 核心交易数据 MongoDB / Cassandra 非核心数据 InfluxDB / ClickHouse 监控与分析数据 数据持久化层(磁盘/SSD) 应用层 缓存层 关系型 NoSQL 时序DB 持久化层

这张图里,数据流向很清晰:交易应用先查Redis,缓存命中就直接返回。缓存没命中,再根据数据类型去查对应的数据库。时序数据库独立于核心链路,专门处理监控和分析请求。

核心原则
1. 核心交易数据 → 关系型数据库(强一致性)
2. 非核心数据 → NoSQL(灵活扩展)
3. 监控与分析 → 时序数据库(高性能写入与聚合)
4. 热数据 → Redis缓存(低延迟高并发)

好了,数据持久化这块就聊到这儿。记住,没有银弹。每种存储方案都有自己的适用场景,关键是要搞清楚你的业务到底需要什么。下一章,咱们聊聊交易系统的网络通信与协议设计。


公众号:蓝海资料掘金营,微信deep3321