高可用、高并发、高性能

93 阅读15分钟

前言

学习优秀的架构,学习优秀的人。

一、高可用

  高可用目标

指标

  • 5个9(99.999%):代表一年不可用时间是5分钟。
  • 4个9(99.99%):代表一年不可用时间是50分钟。

要求

  • 3分钟定位问题。
  • 5分钟恢复业务。
  • 平均2个月最多发生一次问题。

  高可用手段

1、接入层高可用

   1-1、异地多活

  •    跨国多活
  •    跨城多活
  •    同城多活

   1-2、智能DNS

   1-3、KeepAlive保活

  •    KeepAliveLVS
  •    KeepAliveNginx

   1-4、防范DDOS攻击

2、服务层高可用

   2-1、注册中心高可用(Nacos高可用地址切换)

   2-2、微服务实例数大于等于2

3、缓存高可用:代理模式、哨兵模式、cluster模式。

4、数据库高可用:代理型高可用、配置型高可用、客户端高可用、数据定期备份。

5、中间件高可用

    5-1、服务总线高可用

    5-2、反向代理高可用

    5-3、ES高可用

  • MasterNode高可用
  • DataNode高可用
  • 读写分离

    5-4、消息队列高可用

6、容错方案

6-1、客户端容错

6-2、错误重试机制

6-3、备用服务地址重试

7、运维与监控

7-1、立体化监控预警

  •   基础设施:虚拟机监控、容器监控、Nginx、数据库监控、缓存监控
  •   应用层监控预警:核心业务监控、ELK、调用链路监控、慢调用监控

7-2、定期巡检:接口自动巡检、假死自启动

二、高并发

高并发架构分类:

1、业务架构:业务分块、功能分块、功能分级(核心、非核心)。

2、模块架构:模块解耦、系统分层、垂直拆分。

3、数据存储架构:数据流量架构、数据容量架构。

4、流量架构

   4-1、理论分析

  •  流量规划:二八原则。
  • 参考能力:LVS(10w QPS)、Nginx(5w QPS)、Gateway(5k QPS)、MQ(10w QPS)、Tomcat(1k QPS)、Mysql(1.5k QPS)、Redis(5w+ QPS)、Hbase(7w+ QPS)、ES(7w+ QPS)。
  •  中间件规划:10w用户、1亿用户。

   4-2、实时检测:客户端监控、服务端监控。

5、部署架构

高并发架构实施

写多读少系统

“写多读少”场景指系统承受极高的数据写入/生成量,而读取相对少或者以批量分析为主。例如日志收集系统、统计计数系统、大规模订单流水记录等。此类系统面临海量数据写入大数据分析需求,传统关系型数据库往往难以胜任,需要结合大数据技术和特殊存储引擎。本文将介绍写多读少系统的特点、技术方案,包括MySQL在大数据场景的局限、Elasticsearch日志分析、分布式链路追踪、列式存储(ClickHouse)等内容。

1. 写多读少场景与挑战

典型的写多读少案例如日志系统:每秒产生海量日志写入(用户行为日志、系统日志),但查询通常是离线分析或问题排查,读频率低但常涉及大范围扫描。又如实时统计:网站计数器每秒递增更新,但读取可能只在展示报表时发生。主要挑战有:

  • 写入吞吐:每秒成千上万次写操作,高并发插入更新对传统数据库是巨大压力,容易成为瓶颈
  • 数据量巨大:日积月累的数据可能达TB级甚至更高单机数据库容量和索引维护都吃不消。
  • 分析需求:读请求虽然少但往往是聚合分析(如计算某段时间日志条数、搜索特定关键词),需要高效扫描、过滤大量数据的能力。
  • 软实时:有些场景要求近实时分析(如每分钟更新指标),既要海量写入又要及时读出结果。

2. MySQL在大数据写入方面的局限

MySQL等传统关系数据库擅长事务型OLTP,但在超大规模写入和数据量面前会逐渐力不从心。原因包括:

  • 存储引擎: InnoDB采用B+树索引,更新需要随机IO和维护树结构。当数据非常大时,频繁插入会导致索引页分裂、磁盘寻址性能下降。对持续写入的大数据集,B+树写放大明显,不如其他写优化结构。
  • 硬件限制:单机磁盘IO、CPU有上限,单节点承载的数据和写TPS有限。即使分库分表横向扩展,每个分片MySQL仍有容量瓶颈,管理众多分片也复杂。
  • Schema和事务开销:MySQL强一致ACID特性在大批量写场景反而是一种负担。比如每条写都有redo/undo日志、锁等,保证了可靠性但拖累了吞吐。而很多大数据写入(如日志)其实对强一致要求没那么高,更看重吞吐量。
  • 查询能力:MySQL对复杂分析查询(多表JOIN、大量数据聚合)效率不高,需要借助索引,但对高维度分析或全文搜索就很乏力。所以常见做法是将数据抽取到专门分析系统(如Hadoop、Spark或NoSQL)再处理。

业界经验是:MySQL不适合存储和检索海量日志或时序数据。当数据规模达到上亿上百亿行,通常需要引入针对大数据优化的存储,例如Hadoop/Hive、NoSQL数据库,或新型HTAP数据库等。

当然,MySQL也在不断演进支持大数据场景,例如分区表、压缩、以及一些使用LSM树的存储引擎(如RocksDB引擎)。但总体来说,针对写多读少的大数据,往往另辟蹊径更有效。

3. LSM树 vs B+树:优化写入

针对高写入场景,许多现代存储引擎采用了Log-Structured Merge Tree (LSM树)架构,这是一种优化写性能的数据结构。LSM树的设计思想是将随机写转化为顺序写,提高磁盘写吞吐。其核心是:

  • 新数据首先写入内存结构(memtable),顺序记录操作日志(appendlog)以便恢复。内存满或周期性将数据批量flush成有序文件(SSTable)存储在磁盘。
  • 磁盘上维护多级有序SSTable文件组,后台线程不断将小文件合并为大文件(compaction),清理过期数据。这种写模式主要顺序追加和合并,避免随机写盘。
  • 查询时,需要查内存表和多个SSTable,可借助布隆过滤器快速跳过无关文件。LSM牺牲部分读性能和会引入写放大(合并重写数据代价),换取大幅提升写吞吐。

许多NoSQL和新型库都基于LSM,例如HBase、Cassandra、LevelDB/RocksDB等。相比之下,B+树在大量随机写时需要频繁磁盘寻址和页分裂,性能下降明显。LSM通过批量顺序IO显著提高写入能力,可支持极高的写QPS。当然,LSM读性能相对稍差,特别是查不存在的Key可能需要在多级文件中搜寻,因此常配合布隆过滤器、缓存等减轻读开销。应用场景:LSM类引擎非常适合日志、时间序列等写多情况。例如每秒几十万条日志写入,通过LSM可以顺畅写入磁盘而不会像B+树那样瓶颈在随机写IO。这也是为什么Cassandra、ClickHouse等采用LSM或类似思想支撑大数据写入。4. 行存 vs 列存:优化分析查询

除了写入结构,数据存储布局对读性能也有重大影响。行存储(Row Store)将一行所有列存放在一起,适合OLTP点查;列存储(Column Store)则将每列数据分别存放,适合OLAP聚合查询。

在写多读少系统,读取需求通常是批量分析少数列(例如统计日志中某字段出现次数)。列存优势在于:

  • 只读取查询涉及的列,大幅减少IO。行存需要整行都读出,即使只用到其中一两个字段。
  • 同列数据类型相同,压缩效果好。列存数据库常内置高效压缩,同一列重复值多压缩比高,这进一步加快扫描速度。
  • 向量化处理。列存利于利用CPU SIMD指令批量算术运算,比如对某列一亿条记录求和,可以一批批连续内存加总,效率极高。

列存储适用于宽表大数据分析场景:当表有很多列且查询常聚焦部分列时,列式性能远胜行式。典型列式数据库如Vertica、Parquet(Hadoop列式文件)、以及后述的ClickHouse等。

需要注意列存一般不擅长单行事务操作:插入一行需要分别写入多列存,更新单行一个列需要定位并改动多个文件。如果应用需要频繁点查或小范围更新,行存仍有价值。因此出现了一些混合存储方案(如Oracle混合列存、或者HTAP数据库同时支持行列存)。

HTAP(Hybrid Transaction/Analytical Processing)概念正是希望结合行存与列存优势。例如TiDB + TiFlash架构:TiDB行存负责事务,TiFlash维护列存副本供分析查询,实现一份数据兼顾OLTP和OLAP。这类方案对写多读少的系统很诱人,可以在不停写入的同时跑分析。但实现复杂,需要底层强同步保证两种存储一致,并解决实时性。

5. 日志与链路追踪:ELK栈

写多读少系统一个常见用途是日志收集与分析。业界经典方案是 ELK 技术栈:Elasticsearch + Logstash + Kibana。其架构通常如下:

  • 大量应用日志通过Logstash/Beats等Agent实时采集,经过必要过滤、组装后写入Elasticsearch索引。
  • Elasticsearch(ES)作为分布式搜索引擎,存储日志数据并提供全文检索和聚合分析功能。ES底层基于倒排索引和Lucene引擎,能快速搜索关键词、按照字段聚合统计日志数量等。
  • Kibana提供友好的Web界面,可视化查询ES中的日志,实现日志搜索、筛选、可视化图表等功能。

在高并发微服务环境,除了日志,还强调分布式链路追踪(Tracing)。这意味着为一次用户请求跨多个服务的调用链生成统一ID,日志中记录traceId、spanId等,通过这些关联ID可以串联起一次调用的全链路日志。这样当出现问题,可在Kibana中按照traceId检索,看到从入口服务到各下游服务的日志,迅速定位瓶颈或错误。

ELK可以在一定程度上做链路追踪,例如在日志中加入RPC调用ID并在Kibana利用这些字段过滤。不过专门的链路追踪系统(如Zipkin、Jaeger)在UI和分析时延方面更专业。很多公司也将Trace + Metrics + Logs三大运维数据结合使用,以全面监控分布式系统性能。其中Trace用于时序关系,Metrics提供定量指标(QPS、延时),Log提供细节记录。

ES在日志分析领域长袖善舞,但其扩展性与成本也是需要考虑的。索引大量日志会占用非常多的存储和内存;分片分配、集群管理也复杂。而查询方面,ES擅长关键词搜索和简单聚合,但对于超大型数据集复杂分析,效率不如专门的列式仓库。此外ES每条记录都有倒排索引,写入也要异步将数据刷新为segment并合并,写入吞吐虽然不错(尤其搭配批量),但当日志量极大时成本和性能会遇到挑战。

6. 新型列式引擎:ClickHouse

近年来,针对日志和分析,一些团队开始使用ClickHouse等列式数据库替代或补充ELK。在日志场景,ClickHouse展现出惊人的查询性能和高压写入能力。它的特点:

  • 真正的列式存储:ClickHouse把数据按列存储并压缩,查询只读需要的列对于按时间、类型等过滤日志并做聚合,非常高效。
  • LSM + 索引:ClickHouse底层使用类似LSM树的MergeTree引擎,将插入批量写入排序文件,并有稀疏索引加速范围查询。写入可以非常快,同时在查询时利用排序和索引减少扫描范围。
  • 并行和向量化:CH是为OLAP设计的MPP数据库,可利用多核并行扫描。其查询执行以向量方式批处理数据,充分利用CPU缓存和SIMD指令。
  • 高压测试:Yandex开源报告显示,ClickHouse在数十亿级数据上查询速度比传统方案快两个数量级。有对比称CH比Hive快数百倍,比MySQL快上千倍。
  • 即席查询:支持标准SQL,能够直接对实时导入的数据做分析。对于实时监控、用户行为分析非常方便,不用像Hadoop那样批处理才能得到结果。

很多公司已将日志系统从ES部分迁移到ClickHouse。ES仍擅长全文搜索,但对于结构化日志的统计分析,ClickHouse往往更快且成本更低。例如美团在大规模日志平台实践中,用CH承载海量埋点数据,进行用户行为的OLAP分析,取得很好的查询性能和成本优势。

ClickHouse也有局限,如对更新不友好(典型的批量insert追加,不适合频繁update/delete),不支持事务。不过在日志/指标这类append-only场景,这些都不是问题。需要考虑的是CH内存使用(查询时比较吃内存)和集群运维(虽然支持分布式表扩展节点,但生态成熟度不如ES)。

7. 实时统计与算法

写多读少系统常用于实时统计,例如计算UV(独立访客)、PV、在线用户数、热点排行等。为了高效处理这些需求,一些专门的数据结构和算法被采用:

  • Bitmap位图:利用位数组表示ID集合,非常适合统计布尔属性或者去重计数。例如有1亿用户,用1亿位的位图可以表示某用户是否发生过某行为,然后按位或/与运算快速合并天级的行为数据。Roaring Bitmap是流行的压缩位图实现,压缩连续的0或1,显著减少内存,并支持快速集合操作。实际案例如UV去重:每天初始化一个长度按用户ID范围的位图,用户访问时将对应位设1,统计UV时只需数位图中1的数量即可。Bitmap操作在C/C++层非常快,适合嵌入ClickHouse等作为函数使用。
  • Bloom Filter:前文缓存部分提到,用于判断某元素是否存在(允许误判不存在→存在)。在实时系统中Bloom Filter常用于过滤重复或者加速查找。例如在日志流处理中,用一个布隆过滤器维护近期出现过的请求ID,快速判断一条日志是否重复出现。又如防止缓存穿透,我们在应用启动时加载所有有效ID到布隆过滤器,查询先检查布隆过滤器,不在则直接认为无效。
  • 滑动窗口计数:限流或时间窗口统计中,使用滑动窗口算法精确或近似计算单位时间请求数。实现上可以用固定粒度计数加权滑移,或者更简洁地用令牌桶**、漏桶算法限速。**
  • Approximate Algorithms:在极大规模数据统计时,为降低资源,有一些近似算法如HyperLogLog(基数估计,用很小内存估算独立元素数)、Count-Min Sketch(流量热点统计,耗memory小但有可控误差)。例如Redis的HyperLogLog结构,可以很小代价估算UV。这些算法牺牲一点精度换取高性能,对于统计报表很有价值(通常允许万分之一误差的话)。

举例来说,要实时统计某网站过去一小时的独立访问用户数(UV)并每分钟更新:可以维护6个10分钟的Bitmap片段循环使用,每个片段cover10分钟用户访问。一小时窗口内UV就是并集这6个位图的1数,通过位或得到。每过10分钟淘汰最老片段重新置空。这样每分钟UV更新的计算代价较小(6个bitmap or)。而如果直接用数据库distinct统计一小时用户,会非常慢。

总而言之,写多读少系统为了在“海量写入+批量读取”中生存,充分利用了专用的数据结构、分布式存储、和水平扩展。这类系统的设计思路与传统OLTP不同,更接近于大数据处理:强调吞吐多于单次事务延迟,强调最终结果正确多于中间过程一致,在架构上体现为使用消息队列、批处理、异步流等解耦生产和消费。

三、高性能

参考:

尼恩架构师的职业发展路径

尼恩架构师知识图谱

尼恩架构师哲学

尼恩3高架构知识宇宙

高并发系统架构设计与核心概念——写多读少系统