ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

存储选型实战:从MySQL到Redis、ES、ClickHouse的定位与边界

存储选型实战:从MySQL到Redis、ES、ClickHouse的定位与边界 1. 为什么我写这篇对比一次由选型失误引发的深夜故障事情的起因是我维护的一个业务系统在某个大促活动期间突然接口大面积超时。数据库CPU打到100%慢查询刷屏连接数被占满前端页面直接白屏。当时值班的同事第一反应是加内存、加CPU、升级配置结果机器升了一档问题只缓解了半小时随后又崩了。后来我们排查到根因某个运营统计脚本直接在主库上跑了一个多小时的聚合查询把全表几百万行数据扫了一遍又一遍整个OLTP集群被拖垮。那个脚本原本是在另一个分析系统里跑的因为临时要个数据被运营同学直接连到了主库上。这个事故本质上不是代码写错也不是机器不够而是存储系统的边界没有划清楚。同一个项目里业务数据、缓存数据、搜索数据、分析数据本来就应该放在不同的存储系统里每一类存储只能干它最擅长的事。但我见过太多团队从项目第一天起就只用一个MySQL把所有需求都往里塞等到数据量上来、查询模式复杂了才开始手忙脚乱地迁移。这篇文章我想把几类常见存储系统的定位、底层逻辑和真实适用场景放在一起聊一聊。不纠结某一款产品的功能清单而是从你的业务到底在访问数据时做了什么这个角度出发分析它们各自的边界在哪里、彼此之间怎么配合、选型时最容易犯的错是什么。不管你是刚入行的后端开发还是带小团队的技术负责人只要你的系统最终要落到数据库、缓存、搜索或数据分析上这篇内容应该都能帮你少走点弯路。下面的对比不是教科书式的罗列而是我在实际项目中踩过坑、填过土之后的整理。2. 单机关系型数据库三兄弟MySQL、PostgreSQL、SQLite的真实分工2.1 三者的底层差异不只是开源 vs 开源很多人一提到关系型数据库第一反应就是MySQL然后PostgreSQL作为更先进的开源替代SQLite则是那个手机上的小数据库。这个认知不能说错但它忽略了一个重要事实这三样东西在架构上走的完全是三条路各自解决的问题边界也完全不同。MySQL的核心是插件式存储引擎默认InnoDB是行锁、聚簇索引、MVCC那一套它把自己定位成在线事务处理的主力选手。你写一条UPDATE它要保证ACID要保证崩溃恢复要保证并发下不互相踩踏这套能力是被反复打磨过的。PostgreSQL在事务能力上做得更极致它的MVCC实现、复杂查询优化器、扩展机制比如PostGIS、JSONB、自定义类型都比MySQL更丰富。如果你需要处理复杂关联查询、递归查询、地理空间数据PG的表现通常更从容代价是同样的硬件条件下它的吞吐量往往略低于MySQL。SQLite则是完全不同的设计哲学。它不跑在服务器上不监听端口不处理网络协议它就是一个把整个数据库塞进单个文件里的嵌入式库。你写数据时它加锁写完立刻落盘取决于同步模式整个交互方式跟调用一个普通库函数一样。很多人觉得SQLite只配当玩具但它在SQL标准支持、事务原子性、损坏恢复机制上一点都不含糊某些场景下它反而是最优解。还有一个容易被忽略的差异是运维模型。MySQL和PG都需要你管理实例、账号、权限、备份、监控、主从复制SQLite连配置文件都几乎不需要程序启动时自动打开文件进程退出时数据就在那里。这个差异决定了它们的使用场景不是一个维度上的问题。2.2 SQLite 经常被低估的适用边界当我跟别人说某些生产环境我也用SQLite时经常收到诧异的表情。但实际上SQLite在嵌入式设备、桌面应用、内部工具、单机服务里的性能非常出色。它有完整的ACID事务支持并发读写并发虽然受限同一时刻只能一个写者但只要你的应用不是高并发写入场景这个限制根本不构成瓶颈。我做过一个内部的批量任务处理工具需要把几千个文件的解析结果暂存下来供后续进程查询和去重。当时没上MySQL直接在本地生成了一个SQLite文件一个表、两个索引几百万条记录查询都在十几毫秒级别。而且因为数据是本地文件备份就是复制文件删数据就是删除文件整个工具轻量到没有运维负担。SQLite真正不擅长的是高并发写入、跨进程共享、分布式部署。如果你有多个Node服务同时往同一个SQLite文件写迟早会遇到database is locked错误。这一类场景就别硬撑了老老实实上MySQL或PG。2.3 如果你拿不定主意可以按这个思路选我自己的判断顺序是这样的单机工具、嵌入式设备、桌面软件、原型验证直接用SQLite零成本起步。需要网络访问、多客户端并发写、要做主从/读写分离MySQL是更稳的守成选择生态最大运维经验最多。数据模型复杂、查询逻辑重、需要丰富的JSON操作或扩展类型优先PostgreSQL它不会让你失望。团队很小人力不足以维护两套数据库那就在MySQL和PG里选一个千万别为了新鲜感同时上两套那才是灾难。这里有一个常见的误区不少人觉得PG比MySQL高级所以直接用PG就行。实际上如果你做的是电商交易、用户订单这类的核心业务MySQL经过了极其严苛的线上验证相关踩坑案例一搜一大把PG虽然在某些方面更强但它在高并发参数调优、锁等待行为、复制延迟等问题上需要团队有真正的使用经验。选型不是选最强大的而是选你最养得活的。3. 缓存与键值存储Redis 在系统里的位置感3.1 一个被误解的关键点Redis 到底是数据库还是缓存很多项目里Redis的存在感就是加了一层缓存让接口变快。但如果你的认知只停留在这个层面后面大概率会踩两个坑一个是把Redis当万能数据库什么数据都往里塞结果内存爆了另一个是Redis一重启所有数据全丢了业务直接出问题。Redis本质上是一个基于内存的、支持丰富数据结构的键值服务。它速度快的原因不是因为它的网络协议有多厉害也不是因为它的线程模型多先进单线程反而在某些场景下限制了吞吐而是因为它把数据放在内存里内存的随机访问延迟是纳秒级别磁盘是毫秒级别差了五个数量级。这就是为什么Redis做缓存能飞起来的根本原因。但Redis也提供持久化方案RDB快照和AOF日志。很多人看到这个就以为Redis可以做持久化存储了这是另一个大坑。Redis的持久化设计初衷是容灾恢复防止服务器重启后缓存冷启动太慢而不是让你把它当MySQL那样的事务数据库。AOF重写、RDB fork子进程时的内存开销、持久化过程中的阻塞风险任何一个都是需要认真对待的运维课题。3.2 什么数据适合放Redis什么不适合我用一个最简单的判断标准来划界这份数据丢了业务能接受吗能接受、重新计算一下就行session、验证码、热点榜单、商品浏览量计数、接口幂等标识、分布式锁的key都适合放Redis。不能接受、丢了就是事故用户账户余额、交易流水、订单状态、聊天记录这类数据必须落在真正的数据库里。Redis里可以存这些数据的副本用于加速读取但源数据绝不能只放Redis。另外一个容易忽视的点是Redis的淘汰策略。如果你的实例设置了maxmemory又没仔细配置maxmemory-policy默认的noeviction策略在内存写满后会直接返回OOM错误而不是像你想的那样自动删掉最老的key。反过来如果你图省事用了allkeys-lru没设置过期时间的冷数据也可能被淘汰掉。这个细节在压力测试之前不查的话线上出问题后才排查会很痛苦。3.3 内存成本之外的隐藏约束Redis虽然快但内存很贵。你用Redis存1GB数据意味着这台机器的RAM要空出对应的空间还要考虑RDB子进程fork时的Copy-On-Write开销、AOF缓冲、客户端连接的输出缓冲实际内存占用量往往是数据量的1.5到2倍。如果你在云服务器上跑这个成本会逐月累加。我用Redis的另一个原则是尽力控制key的数量和value的大小。一个value达到几百KB甚至几MB时读写耗时、网络传输、序列化开销都会显著上升而且一个大key的过期删除或淘汰会阻塞Redis主线程导致所有请求卡顿。这是一个非常典型的生产事故源。另外Redis的集群模式也不是银弹。Cluster模式把数据分成16384个slot分布在多个节点上涉及多key的操作比如MGET多个key、事务、Lua脚本要求这些key必须落在同一个slot里否则直接报错。这意味着你设计key的时候就要考虑hash tag而不是等写代码时再撞墙。4. 文档与搜索场景MongoDB 和 Elasticsearch 不是一类东西4.1 MongoDB 的灵活是有代价的MongoDB最常见的定位是不用建表、想存什么存什么、存JSON很方便。它的文档模型在处理业务初期确实爽改字段不用发SQL迁移脚本嵌套结构天然贴近对象模型。但这份灵活背后是有代价的而且代价往往在数据量上来之后才显现。首先是查询语义的约束。MongoDB的关联能力很弱虽然有$lookup操作符可以做类似于JOIN的查询但性能相比关系型数据库差很多。如果你的业务模型是强关联的订单-订单明细-商品用户-角色-权限MongoDB的文档嵌套要么让你重复冗余数据要么在查询时写出极其别扭的聚合管道。其次是事务能力和一致性。MongoDB很早就支持了多文档事务但它的事务实现和隔离级别与MySQL/PostgreSQL相比仍然有差距而且在分布式分片集群下的事务支持、对吞吐的影响都需要你在压测阶段仔细验证。如果你做的业务要求强一致、强事务保障MongoDB并不比MySQL更合适。MongoDB真正发光的场景是深层嵌套的文档型数据、快速迭代且字段结构经常变化的业务比如内容管理系统、物联网设备上报的数据、爬虫采集结果、用户行为日志等。它的水平扩展能力sharding在千万级甚至亿级文档量下比MySQL的中间件方案更顺手。4.2 Elasticsearch 的强项与它的脾气Elasticsearch以下简称ES经常和MongoDB被放在一起讨论因为它也是文档型存储、也基于JSON。但两者的本质区别非常大MongoDB的核心是存储与查询ES的核心是索引与搜索。ES底层的Lucene倒排索引才是它最值钱的部分。ES擅长的是什么模糊搜索、全文检索、组合过滤、聚合统计分析、高亮展示这些能力在MySQL里用LIKE %关键字%根本没法做即使做了性能也惨不忍睹。ES在日志分析、应用监控、商品搜索、订单检索等场景下的表现是其他存储系统很难替代的。但ES有它的脾气。最典型的就是索引生命周期管理——写数据不是写完就完事了你需要考虑分片数、副本数、索引滚动策略、段合并参数。很多人一开始图便宜一个索引不分片、不设别名等数据涨到几个TB后查询开始抖动重建索引变成噩梦。ES的字段映射mapping更是不能随意改一旦字段类型定错只能reindex。ES另一个需要警惕的点是写入性能和分片数。分片过少单分片数据量过大查询和恢复都会变慢分片过多每个分片都有固定的内存开销集群内存被元数据吃光反而更慢。这个平衡需要根据实际数据量和节点规格来算不是一个固定公式能解决的。深层分页问题fromsize过大导致内存溢出也是新手容易踩的经典坑。4.3 我的建议别把ES当主存储ES官方有一个说法叫可伸缩的分布式搜索与分析引擎注意它把自己定位成引擎不是系统。我的经验是ES应该作为数据副本或查询层的角色而不是唯一的数据来源。原始业务数据放在MySQL或MongoDB里通过binlog监听或定时任务同步到ES供搜索和聚合查询使用。如果ES集群挂了业务主流程不受影响只是搜索功能临时降级这才是一个健康的架构。用这个方式你可以享受ES强大的搜索能力又不用把数据不能丢这个重担压在它身上。5. 分析型场景ClickHouse 凭什么把普通数据库甩开5.1 一次报表查询引发的重新评估我前面提到的大促事故里运营在MySQL上跑聚合查询把主库拖垮了。事后我们做的第一件事不是禁止运营连库而是给分析需求找一个真正合适的去处最后落地到了ClickHouse上。同样是几百万行的表在MySQL里跑一个GROUP BY多维度聚合可能要几十秒在ClickHouse里同样的数据量基本是几百毫秒级别。这不是ClickHouse优化得有多神奇而是它的底层架构跟MySQL走了完全相反的路MySQL是行式存储每一行数据在磁盘上是连续存放的查一行你要把整行的所有列都读出来ClickHouse是列式存储每一列在磁盘上是连续存放的你计算某几列的聚合时只需要读取这几列的数据IO量直接降了一个数量级。再加上ClickHouse的向量化执行引擎CPU一次可以批量处理多个数据值不再是一条一条循环判断。这两个特性叠加让它在大范围扫描聚合计算的场景下碾压通用OLTP数据库。这不是ClickHouse比MySQL高级而是它们从一开始就为不同场景设计。5.2 什么数据适合进ClickHouseClickHouse不适合当OLTP数据库用——它没有完整的事务支持点查询按主键查单行不是它的强项高并发写入也需要谨慎设计。它适合的是写多读少、聚合为主、很少更新和删除、数据量巨大的分析场景。比如用户行为日志、流量点击流、监控指标、财务流水汇总、订单统计报表这类数据。如果你有实时报表、运营看板、用户分群分析之类的需求用ClickHouse做存储和计算引擎体验会非常舒服。尤其是每天写入几千万条数据按小时维度聚合出趋势曲线这种场景ClickHouse的合并树家族表引擎MergeTree、SummingMergeTree、AggregatingMergeTree几乎就是为你准备的。我特别想提醒一点ClickHouse不是用来替代MySQL或PostgreSQL的而是跟它们互补的。业务主库继续承担在线事务通过同步管道把分析所需的数据搬运到ClickHouse两者各司其职。这样设计之后运营再跑天马行空的报表查询再也不会拖垮主库了。5.3 一个容易忽视的坑查询模式决定建表方案ClickHouse的建表策略比传统数据库复杂得多。同样的原始数据你可以用普通MergeTree按时间分区也可以用SummingMergeTree做预聚合或者物化视图实时汇总。选错方案查询性能和存储成本会差出好几倍。我的习惯是拿到分析需求后先不急着建表而是把最频繁被查询的维度组合列出来再决定主键字段的顺序、分区键的粒度、是否需要预聚合表、是否需要物化视图。比如你天天按天看各渠道的订单量那主键就设计成(day, channel)而不是放任用户在几亿行的明细表上每次扫全表。这些决策直接影响线上效果没有统一标准只能靠你对自己业务查询模式的深刻理解。6. 分布式文件与对象存储HDFS、MinIO、Ceph 的职级划分6.1 HDFS 的定位正在被重新讨论HDFSHadoop分布式文件系统是老牌的大数据文件存储方案为MapReduce时代的批量数据处理而生。它的设计目标是吞吐量优先、容忍节点故障、存储超大文件默认块大小128MB一套集群可以轻松存下几十PB数据。如果你在用Spark跑离线ETL作业HDFS依然是默认依赖。但它的问题也很明显NameNode是单点高可用模式也只是主备元数据放在内存里文件数量一旦超过亿级NameNode的堆内存就成了瓶颈。而且HDFS的吞吐量虽大延迟却偏高不适合在线业务直接读写文件。现在很多新项目甚至直接跳过HDFS把原始数据放在对象存储里计算引擎通过S3协议读取数据省掉了NameNode运维负担。6.2 MinIO 在中小团队里更接地气MinIO是一个兼容S3协议的对象存储系统它可以跑在普通的x86服务器甚至容器里几台机器就能组成一个集群。和Ceph相比MinIO的部署和维护简单得多社区也活跃很多。对象存储的核心思路是扁平化的桶对象没有目录树的层级概念通过HTTP API访问天然适合存储图片、视频、文件包、大数据中间结果。对于中小团队来说用MinIO搭建一个私有化的对象存储做后端文件服务、备份存储、日志归档性价比很高。我自己在一个项目里用MinIO存用户上传的图片和视频配合Nginx做访问加速几个月下来没有出过任何问题。平时只需要关注磁盘剩余空间和桶的生命周期策略运维成本几乎可以忽略。6.3 Ceph 的优势与复杂度阈值Ceph是另一个著名的分布式存储系统它比MinIO强大得多同时复杂得多。Ceph提供三种接口块存储RBD、文件系统CephFS、对象存储RGW/S3一个集群全搞定。它的自愈、自管理能力很强支持在线扩缩容和数据自动均衡。但Ceph的部署和调优门槛非常高至少要十几台服务器才能发挥出它的优势。如果集群网络不稳定、OSD配置不合理很容易出现数据迁移风暴反而影响业务稳定性。我的判断是如果你的团队没有专职的存储工程师系统规模也没到几百TB以上不要轻易碰Ceph。中小团队用MinIO不丢人需要块存储的话用分布式软件定义存储加上成熟的运维方案或者直接上云都比自建Ceph省心得多。7. 排除干扰为什么这些存储经常被混用很多人在做技术方案时总喜欢把选型做成一锤子买卖这个项目用MySQL还是PostgreSQL用MongoDB还是Elasticsearch好像选定了就万事大吉。我在前面几节分别拆解了各大类存储的能力边界这一节我想把视角拉高一点讲清楚混用才是真实的常态以及混用的基础框架到底是什么。7.1 多个存储系统同时存在不等于架构乱七八糟如果用一个词总结我在大量项目里看到的最佳实践就是各司其职。MySQL或PostgreSQL作为系统的系统OfRecord保存所有需要强一致性保障的核心业务数据Redis作为加速层承接高频读取和临时状态Elasticsearch作为检索层承担复杂搜索和分析查询ClickHouse或类似的分析型数据库作为分析层处理大数据量的聚合报表MinIO或云对象存储作为文件层存放图片、视频、附件等不可变文件。这不是架构洁癖而是每一种存储系统都在做自己最擅长的事把短板交给其他系统补位。比如一个电商订单系统订单主表在MySQL购物车实时状态在Redis商品搜索在ES销售统计报表在ClickHouse用户头像在对象存储——每一部分都不需要勉强自己处理不擅长的任务。7.2 混用之前必须先解决的问题数据一致性由谁保证存储系统一旦混用第一个绕不开的问题就是同一个数据在多份副本之间怎么保持一致。以MySQL为主库、Redis为缓存、ES为搜索引擎为例最常见的模式是通过业务代码在写入MySQL之后同步更新或异步刷新Redis和ES。但这样会引入分布式一致性问题——如果Redis或ES更新失败怎么办我的做法是优先保证主库正确性其余副本允许短时间不一致。具体落地时可以借助binlog监听或消息队列异步同步把更新ES和更新缓存当作下游消费者来处理。如果同步失败靠定时任务比对校正如果一致性要求很高则放弃缓存直接查主库。先把这个数据一致性边界设计清楚再谈混用否则只是给自己埋雷。8. 常见选型误判我见到的几类典型错误场景8.1 Redis好用所以什么都放Redis我在3.2里提过判断数据能不能放Redis的标准是丢了能不能接受。但现实是很多人一开始把用户信息、订单快照都塞进Redis确实快直到某一天Redis持久化文件损坏或者集群节点迁移出问题数据没了业务就炸了。这里我再补充一个更实务的建议Redis里所有业务数据都必须设置过期时间哪怕是名义上的永久数据也建议设一个很长的TTL比如90天并配合定期刷新机制。这样做是为了防止脏数据永久残留也是给自己留一条退路。8.2 MongoDB灵活所以什么都能存MongoDB确实很适合存结构不固定的文档但如果你把订单、支付流水这类强事务数据也存进去等到要做对账、要跨集合关联、要处理复杂的多文档更新时会很痛苦。我见过一个项目把交易对账逻辑建立在一组嵌套了两层的文档上每次聚合查询要写几十行Pipeline维护成本和出错率都让人抓狂。灵活的代价是需要你自己约束结构而强约束恰恰是关系型数据库替你做的。8.3 Elasticsearch速度快所以当主库用我理解这种冲动——ES的查询写起来很顺手搜索只要一条DSL就出来了比写SQL拼接JOIN舒服太多。但ES作为主存储最大的风险是它不是一个保证数据不丢的事务型存储。刷新间隔refresh interval默认1秒、translog的同步级别、分片恢复机制任何一个环节出问题都可能导致已写入数据在故障时丢失。如果业务出问题后你告诉老板ES里数据没了这个锅没人替你背。把ES当查询副本用主数据在黑盒数据库里兜底才是稳的做法。9. 从一次真实迁移案例看存储组合的调整过程前面讲了不少理论我拿一个自己经手的案例串一遍。有一个中型SaaS产品后台报表模块早期直接在MySQL上GROUP BY数据量到了几千万行之后报表接口越来越慢最夸张的一张月报要跑将近两分钟页面直接转圈。运营天天投诉研发压力很大。我们当时的改造分三步走第一步把MySQL里的订单、支付流水明细通过binlog增量同步到ClickHouse在ClickHouse里建好按天分区的明细表和一张按月聚合的物化视图。第二步报表服务的查询全部切到ClickHouseMySQL只保留主库写入和事务逻辑。第三步把搜索相关的接口切到ES用户输入关键字查找订单时用ES做全文检索拿到订单ID之后回到MySQL或ClickHouse查详情。整个切换过程持续了两周核心改造其实就是两件事写好同步管道还有把查询重写一遍。上线之后月报查询在ClickHouse里基本都是秒级运营再也没找过我们。这个案例不是什么高深的架构但它真实地反映了存储系统选型组合对一个业务系统稳定性和用户体验的影响有多大。10. 结论性的个人经验选型评估的几个顺序每次做存储选型时我都会按下面的顺序问自己几个问题数据要不要强事务、强一致要就老老实实选关系型数据库优先MySQL或PG。数据会被怎么读是点查、范围查、全文搜还是聚合分析这个问题的答案直接决定要不要引入ES、ClickHouse或专门的分析型产品。数据量级和增长预期是多少万级和亿级是完全不同的选型逻辑。数据量没到一定的量级别为了炫技引入重型组件。团队有没有人能长期维护这个组件如果没人熟悉ES的调优和排障慎用ES没人熟悉ClickHouse的分区策略和副本机制也别急着上ClickHouse。能不能做到存储服务独立部署和优雅降级一旦引入多个存储系统必须设计好如果某个存储挂了主流程不受影响或能降级运行。这五条没有一条是关于哪个数据库更强大的选型的核心从来不是比拼功能清单而是比拼它的运行特性是否与你的业务访问模式匹配以及你的团队能否驾驭它的复杂度。11. 写在最后我踩过几次坑之后的真心话如果你正在纠结我这项目到底用什么存储系统我最大的建议是别先画架构图定技术栈先把你业务里最核心的几个数据访问场景列出来——哪些数据需要实时更新和事务保障哪些数据只需要读得快哪些数据要做模糊搜索和聚合分析哪些数据是永不修改的附件文件。把这些场景摆出来再逐类匹配存储系统思路会清晰很多。另外别只盯着某个技术很火或者某篇文章说它性能碾压XX就冲上去。每个存储系统都有自己擅长的领域也都有各自的性格和脾气。你愿意花时间去了解它们的运作原理、踩坑边界和运维成本它们才能真正成为你工具箱里趁手的工具。说回我开头提到的那次大促事故后来我们在监控系统里加了一条规则任何非主库专用账号发起的聚合查询一律拦截并提示转交分析系统执行。技术架构调整了权限边界也划清楚了从那以后同一个问题没有复发过。存储选型这件事最后拼的其实是谁更懂自己的系统需要什么而不是谁会背更多术语。
返回列表