前言

我们应当忘记小的性能改善,百分之九十的情况下,过早优化都是万恶之源 —— 高德纳

测量和实验是所有改善程序性能尝试的基础。性能优化的基本规则是90/10规则:一个程序花费90%的时间执行其中10%的代码。识别出10%的热点代码是值得花费时间的,但靠猜想选择优化哪些代码可能只是浪费时间 —— 《C++性能优化指南》

架构与设计

RPC 框架

有很多专家给出了各种 RPC 的性能分析,如brpc 性能分析RPC框架性能分析等。但 RPC 框架的选择是根据业务场景、技术累积以及维护成本等多种因素共同决定的,同时也要有预见性的考虑到业务扩展等等。通常情况下为了性能优化,替换一个线上服务的 RPC 框架的代价是比较大的,它涉及到服务端和所有客户端都做出修改和平滑切换;从另一个角度讲,大部分开源 RPC 框架对其性能还是会尽最大程度进行优化的(可能有少量特殊情况下的性能问题,如apache thrift 大量连接断开时发生阻塞),可能更多的性能问题出现在研发同学自身的代码中,性能优化要考虑投入产出比

缓存

缓存的作用无需赘述,其类型通常有远程(分布式)缓存与本地(内存)缓存。缓存的效果由缓存命中率体现,缓存的诸多参数如淘汰策略、过期时长、缓存大小等都会对命中率造成影响

对于淘汰策略,应当尽量避免所有缓存同时失效(它会给缓存后面要访问的服务带来较大瞬间压力),因此在业务场景允许的情况下,可以采用先返回旧数据再异步刷新缓存的方式;对于过期时长,为了充分利用空间,在过期时长内应该能够能够被充满;例如一个有多个步骤的业务场景,可能会用到多个缓存,每一步缓存对应的结果,那么需要分析每个缓存的必要性,根据业务场景设置多个本地缓存的大小以及过期时间,其缓存的内容也需要进行详细分析,尽量做到只缓存必要数据,不缓存无用数据以及对时间超级敏感的数据

负载均衡

在后端服务结点处理能力一致情况下,应该尽量让所有结点接收到的请求 QPS 基本一致,以免个别结点流量过高从而拉高后端服务整体延时。负载均衡又分为在客户端维护可用的后端服务结点列表进行负载均衡,或者服务端之前加一层(如Nginx)服务进行负载均衡

最简单的在客户端采用轮询选择后端结点就可以达到负载均衡目的;当服务端结点使用缓存时,采用一致性哈希选择后端结点也可以进一步提高其缓存命中率;另外当后端结点处理能力不一致的情况下,最小负载(如连接数)方式便派上了用场,但最小连接数的某种实现在 QPS 较低情况下可能会有负载不均匀的问题,见分析

算法与数据结构

索引

索引是一种概念,利用它可以加速对数据结构的访问。如 C++ 中的 std::map、std::unorderd_hap 就属于索引结构,日常开发中其实很容易想到使用这些标准库提供的容器优化代码(如减少遍历操作:if-else、switch-case、for);另外也可以借鉴这种思路,通过建立简单的索引模块提高代码的清晰度

索引也可以是单独的服务,如推荐系统中的索引层为召回层提供高效建库、检索功能,优化以及替换其底层使用的索引模块通常不会被调用方感知。推荐系统中的索引服务通常又分为常见的文本索引(如基于Elasticsearch等)和由于深度学习而兴起的向量索引(如基于nmslib、Faiss)等

高效算法

一致性哈希-虚拟结点生成提到的ketama hash:重复利用md5值的各个位,按虚拟结点每4个一组,可以减少75%的md5计算次数

业务流程

深入理解服务的上下游服务可能会从整个链路上发现性能优化点,而详细分析服务内部的业务相关流程(或计算流程)可能会对性能更加极致地做出优化,包括上述的缓存相关内容,以及提高并发度和离线化等

提高并发度

梳理并解耦互相无关的在线操作,增加并发处理,从而提高资源(如 CPU、网卡)利用率,降低延迟

异步化

将请求处理流程中对当前结果无影响的操作异步化

离线化

梳理并解耦在线、离线操作,将没有必要在线执行的操作离线化;或者通过定期加载离线操作结果减少在线处理的操作步骤,以较小的精度损失换取较大的延迟下降。离线化也并非是性能优化的一个重要角度,在对时间要求较严格但技术上无法满足时,离线化就是一种设计方案

避免过多的操作

日志、监控打点等操作过多也很可能影响服务性能,那么就需要对这些操作进行采样限制等

操作系统级别优化

分离 IO 密集型操作和 CPU 密集型操作,采用线程池连接池等等。更为详细的系统级性能分析可参考:系统性能分析

开发语言级别优化

《C++ 性能优化指南》一书比较系统全面的介绍了 C++ 开发时需要注意的性能优化点。这个级别的优化效果建立在对语言本身的掌握程度之上

避免频繁创建与销毁

比如合理的使用全局变量、静态变量、线程局部变量;减少或避免创建临时std::string;

减少动态内存分配

对频繁调用的操作,减少其动态内存分配对性能提升会有较大效果,如std::string的动态内存分配

避免大量字符串操作

大量的字符串操作很有可能影响性能,因此缓存的key尽量要避免使用std::string类型,可参考其性能测试(等补充)

编译优化

如 gcc 编译器的 O3 优化

计算优化

如使用 SIMD avx2 指令集进行计算、openmp库执行并行计算、高效矩阵计算库 BLAS、多核高性能 CPU 计算、GPU 计算等

服务部署相关

网络延迟

通常同机房访问 < 跨机房访问 < 跨运营商访问,如果将调用量非常大的上下游服务部署在同一机房,不仅会在降低网络延迟上有所获益,网络带宽成本也会有一定节省。但将服务所有结点部署到同一机房会降低其高可用性,所以可以考虑在多个机房将整个链路各个服务都进行部署,链路内不存在或者只存在少量跨机房访问,在最上层进行高可用设计

网卡延迟

分析并消除收发数据中的冗余数据,减少收发数据量;IO 频繁的基础服务通常需要万兆网卡,提高网络 IO 速度

posted on 2020-10-03 22:21  chenguang9239  阅读(175)  评论(0编辑  收藏  举报