分布式系统稳定性建设-性能优化篇
系统稳定性建设是系统工程的核心内容之一。以下是一些重要的方面:
-
架构设计:
- 采用模块化、松耦合的架构设计,以提高系统的可扩展性和可维护性。
- 合理划分系统功能模块,降低单个模块的复杂度。
- 定义清晰的接口和数据交换标准,确保各模块之间协调工作。
-
容错机制:
- 建立完善的异常处理和容错机制,及时检测并隔离故障,防止故障传播。
- 实现关键功能的冗余备份,提高系统的可用性。
- 设计自动恢复和自修复机制,缩短系统故障的恢复时间。
-
性能优化:
- 进行系统性能测试和瓶颈分析,确定系统的承载能力。
- 优化关键模块的性能,提高系统的整体响应速度。
- 采用缓存、异步处理等手段,缓解系统的瞬时压力。
性能优化
-
在当今高并发、大流量的互联网环境中,分布式系统的性能和稳定性至关重要。本文将深入探讨分布式系统性能优化的关键策略和实践。
-
性能优化可以从:代码优化、缓存、消息队列、并发、容量评估、压测、监控预警等来提高系统性能
代码优化
代码优化是提升系统性能的重要手段。
时间复杂度优化
- 选择合适的算法和数据结构、避免嵌套循环、使用空间换时间策略
缓存优化
- 想要提高服务的性能,基本会想到可以使用缓存来提高性能、应用服务的缓存一般就是存储一些使用频繁、但是存储空间占用不大,且基本不怎么变更的数据,或者作为多级缓存中的一级。
- 缓存是提升系统性能的关键技术之一,通过减少数据访问延迟和降低后端存储压力,显著加快数据检索速度。在现代应用架构中,缓存不仅用于提升用户体验,也是实现高并发处理的基础。
- 在分布式的场景在,应用服务缓存需要注意多节点缓存更新问题、一致性问题、LRU 缓存淘汰策略。
常见的缓存实现方式
- 内存缓存:利用服务器的RAM来存储热点数据,提供快速的数据访问能力。常见的内存缓存实现包括Ehcache、Memcached等。
- 分布式缓存:通过多台服务器共享缓存数据,解决了单机内存限制的问题,并提供了更好的扩展性和可用性。例如,Redis和Hazelcast是流行的分布式缓存解决方案。
- 浏览器缓存:利用客户端的存储能力,减少网络传输的数据量,加快页面加载速度。通过设置合适的HTTP头信息,如Cache-Control和Expires,可以控制浏览器缓存的生命周期。
- 内容分发网络(CDN):通过在网络边缘节点存储静态资源,使用户可以就近获取数据,从而减少延迟和带宽消耗。
缓存可能带来的问题
在使用缓存的过程中,需要考虑到一些相对极致的场景问题,比如:缓存的穿透、击穿、雪崩、等问题。
- 缓存穿透: 是指查询一个一定不存在的数据时,由于缓存中没有此数据,每次查询都会落到数据库层。恶意攻击或程序bug可能会不断尝试不存在的数据查询,使得数据库承受巨大压力。
- 缓存击穿: 特指对于那些热点key,在缓存失效的瞬间,大量并发请求同时到达,这些请求全部穿透缓存直达数据库,对数据库形成巨大压力。
- 缓存雪崩: 是指在一段时间内,大量缓存在同一时刻集体失效,后续请求均直接打到数据库,可能导致数据库瞬间压力过大,服务不可用。
- 大Key: 避免在redis中存储大量数据,导致内存大量使用,以及大key操作导致堵塞,分布式部署下节点内存使用不均,等问题。
- Key命名: key长度不宜太长,尽可能简短且清晰表达用途,key需要能够根据业务领域、模块区分前缀,易于后续治理区分业务,尤其是对于redis集群共用的业务,后续做大key清理、redis拆分迁移。
- 过期时间: 一般使用redis做热数据存储,也存在一些做持久化的场景,但是大部分都是需要对key设置过期时间,避免冷缓存,长期占用内存空间,资源被浪费。
- 数据一致性: 数据DB更新,未能及时同步redis,导致数据不一致问题,对于一致性要求高的业务,需要重点考虑更新策略,保障一致性。
- 缓存预热:缓存预热是在系统启动或低负载时段预先加载热点数据到缓存中,避免在高负载时发生缓存缺失。
消息队列
- 在过往的业务开发过程中,经常会有一些功能需求,它需要在核心接口中拓展,但是非接口主要流程内容,同时可以接受一定延迟处理,我们往往会引入消息队列的使用,
- 消息队列(如RabbitMQ、Kafka)在分布式系统中用于解耦服务,实现异步通信,提高系统的可扩展性和容错性。
- 业务 解耦:生产者和消费者不需要直接知道对方的存在,降低了系统的耦合度。
- 削峰填谷:帮助平滑系统负载,处理请求峰值,防止系统过载。
- 异步处理:生产者无需等待消息处理完成即可继续工作,提高了系统响应速度和吞吐量。
- 可扩展性和灵活性:容易添加或更改消费者,适应系统变化。
消息队列可能带来的问题
- ACK 机制: 消息的执行需要确认被消费者成功消费后,才能从消息队列中移除。
- 容错机制: 需要保障消费结果最终成功,通过异常兜底,或者消息的死信队列,保障异常情况下消息也能够兜底处理。
- 结果幂等: 不能完全信任生产者的消息不重复,所以需要保障多次执行相同消息后结果幂等。
- 执行时序/顺序: 当存在多个消费者同时消费的时候,需要考虑消费时序问题,可能后进的会被先执行,需要保障逻辑不被影响,如果存在影响,就需要考虑将消息进行分片路由到不同队列中,进行多消费者消费,提高吞吐的同时,也能保障相同标识的消息执行时序是正确的。
并发
在面对一些性能问题的时候,往往都是有一些耗时的操作(如I/O操作、网络请求、数据库访问等),除了将耗时的操作本身进行优化以外,我们还可以使用并发编程,golang 里面最常用的就是使用协程实现并发编程
并发可能带来的问题
- 负载评估: 需要对并行操作的发起方和目标方服务器进行负载、服务器资源利用率的情况进行评估,在一些高并发、高峰期场景下,服务器本身负载就高,再对其并行改造,可能会事倍功半,可能会发展成压死骆驼的最后一根稻草,把服务搞崩溃了。
容量评估
根据应用的DAU、MAU,模块接口请求QPS、业务未来拓展方向、等信息,进行容量评估,作为设计的前置信息,衡量技术方案可行性。
容量评估包含这些内容:
- QPS : 评估每秒请求数量,平均值、最高值,用于方案设计,以及性能压测。
- DAU: 评估功能日访问量。
- 数据量: 评估用户大概日生产数据量,用于用户表设计,如:是否纵向拆分列、横向分表、索引设计。
- 资源利用: 评估数据存储占用多少空间、应用服务占用多少资源(cpu、mem、i/o、load、…),用于评估部署机器配置、是否扩容 。
- 预测增长: 评估未来增长情况,用于方案设计考虑未来可拓展、可伸缩。
服务压测
压测需要制定一套标准,如:什么接口需要性能测试?性能测试需要关注哪些指标?以及指标的基线要求。
以下是一些压测标准,仅供参考
- 是否 压测 : P0、P1核心业务接口、高 QPS 接口、无依赖第三方 / 内部耗时服务(算法/搜索…) 接口 。
- 压测 配置: 最低48线程数,支持按倍数增长、压测时间、压测倍数、等。
- 关注指标:请求数、QPS、TPS、CPU、MEM、I/O、Load、响应时间、错误数、错误率。
- 判断依据: 性能测试基线 + 容量评估。
- 单容器 压测 通过基线:
- 响应时间 < 500ms
- TPS > 500,QPS> 1000
- 成功率 100%;
- 在达到接口处理能力预期指标值时,数据库无慢查询出现;
- 平均 CPU < 75%,平均负载小于 CPU 的核数
- 趋势上在并发数增长情况下,TPS 跟随增长,响应时间 < 500ms。
监控预警
实现实时监控,当性能指标超出预设阈值时,系统应能自动发出告警,以便及时采取措施。
- 高性能的系统通常需要实时、准确的性能监控。通过监控关键性能指标(如CPU使用率、内存占用、磁盘I/O、网络带宽、响应时间等),可以及时发现系统瓶颈和异常状况。这些数据是衡量系统是否运行在高性能状态的重要依据,也是调优的出发点。
- 监控系统不仅要收集数据,还需要具备预警功能。当监控到的性能指标超过预设阈值时,预警系统会自动触发警告,通过邮件、短信、电话或集成的消息系统通知运维人员。这样可以在问题影响用户体验或造成系统故障之前,及时采取行动进行干预,保障系统的高性能运行。
- 在可观测性的内容中,可以抽象出三大元素:日志(Logs) 、跟踪(Traces) 、指标(Metrics) ,这三大元素就是可观测性的三大支柱。
USE 方法
USE是utilization、saturation、erros(利用率、饱和度、错误)三个词的缩写,应用于性能研究,用来识别系统瓶颈,一言以蔽之,就是:对于所有的资源,查看它的使用率、饱和度和错误。
- 识别系统中有哪些资源(CPU/内存/磁盘/IO带宽等)
- 分别查看这些资源的三个指标(使用率、饱和度和错误)跟进现象,分析并缩小性能瓶颈的范围(数据中心,服务集群,单机节点,进程,线程,函数,指令)
- 定位瓶颈,使用适当的优化策略进行优化
- 观测系统,验证优化收益
- Utilization:整个系统的平均 CPU 利用率
- Saturation:一个近似值是第 99 个之间的差异延迟百分位和平均延迟(假设第 99 个是饱和驱动的)
- Errors:每秒失败的请求数
RED 方法
RED 方法定义了应为体系结构中的每个微服务度量的三个关键指标。这些指标是:
*(请求)Rate - 您服务每秒提供的请求数
*(请求)Errors - 每秒失败的请求数
*(请求)Duration - 每个请求所花时间的分布
一般来说,RED方法只适用于请求驱动的服务,它不适用于面向批处理或流式服务。 它也不是包罗万象的。 而 USE 方法应用于主机 CPU 和内存或缓存等资源时就是一个很好的例子。