在上篇服务端性能与成本优化经验总结 (上) - 掘金 (juejin.cn)中,我比较务虚地写了一些性能优化的方法论方面的东西。本篇是一些比较务实的。
先足够了解再开始优化。
一定要在对系统有足够了解的前提下再开始优化。大忌把性能优化作为新接手一个系统不久时的立项。如果是新接手一个系统,请耐下心来先跟至少半年业务需求,再开始考虑进行优化。
优化前不妨先梳理一下整个系统完整的架构/拓扑。包括但不限于代码结构、系统部署方式、使用的服务商、与外部系统的交互方式等。
重新审视整个系统,思考整个系统中每一个模块的作用、必要性、可替代性。如果能直接精简掉一部分模块或流程,除了性能上的帮助,对简化业务,提升业务效率也有帮助。 比如当你发现系统中既有SLB又有nginx,而nginx起的作用也是进行负载均衡,就可以考虑能不能省掉其中一层。
数据结构上的优化空间
对于一些耗性能的部分,换用更优的数据结构。
比如在某些场景下,可以使用Array代替HashTable, 将对key的索引改为对数组下标的访问。
比如在某些场景中,使用一些特定的Tree代替HashTable, 比如MySQL的B+Tree索引。
小HashTable往往也可以用数组来替换。因为遍历一个小数组比计算完一个hash function要快得多。
另外如skiplist, bitmap, timewheel, 也都是一些经典的案例。
语法层面的优化细节(Golang)
我在工作中主要使用Golang, 因此语法层面只能列举Golang的一些优化细节。
- 如果map的value是一个指针,会造成比较大的GC负担。可以尝试将map[string]*struct 优化为map[string]int + []*struct
- 对slice预分配内存是个好习惯。在高频调用下,大量的grow slice也会消耗比较多的资源。
- 如无必要就不做类型转换,保留原类型进行传递
- Golang中大量字符串拼接最快的方法是先生成对应size的[]byte, 再不断向其中copy
- chan 底层是有锁的,高频调用时注意其性能
关于云商
这方面在网上能搜到的相关技术分享很少,可能是因为这种知识体现不出自己的厉害之处吧。但是,对云商提供的服务进行使用上的优化所带来的提升也是巨大的。现简单列举几条:
- 同云商同类型机器,越新的版本性价比越高。也可以尝试一下ARM和AMD版本的机型,往往比Intel 的便宜。同样的,多关注一下云商有没有什么新产品,说不定有惊喜
- 对照自己的系统仔细分析服务器的详细账单,或许里面有一些东西超出自己原来的认知。比如网络成本可能高出你的预期, 大内存比高CPU便宜得多,各种杂项东一点西一点攒起来也有不少。
- 换服务商的成本很高,但也不是完全不能考虑。比如CDN、VPN这类相对独立的产品,可多找几家进行PK或互为备份。哪怕最终不换,作为冷备进行提前演练也是好的。
- 当系统规模足够大时,使用混合云既能提升容灾能力,也能在价格谈判中谈到更低的折扣。
懒惰与不精确
核心思想只有一个,刚好够用就是最好的。
懒惰,即不要做多余的事情。做的事情满足业务需要即可。
不精确,即通过允许少量的计算误差来换取性能。BloomFilter 就是这个思想最经典的案例。
基于这两个思想可玩的空间有很大,这里也只简单列举几个我所用过或知道的。
- 前端们常用的懒加载,就是“懒惰”思想的典型。
- 如果需求是取一个数组的前N个元素,即经典的TopK问题,就不需要全排序,直接使用TopK对应算法即可(八股文入门题目)
- 如果场景需要用到的时间只需要精确到毫秒,可专门提供一个“低精度版本”的“time.Now()”
- 对于某些非线程安全的操作,如果数据竞争带来的后果仅是小概率的误差,而这个误差又在可接受范围内,无需加锁。比如用来监控某些指标的累加器
优化一定要在保证满足业务需要和高可用的前提下进行,也要充分理解需求的“潜台词”,尽可能向下摸到要求的下限。假设有需求“数据在更新后用户要立刻能看到”,这里的“立刻”究竟是多少? 5秒的误差能接受吗?1分钟的误差能接受吗?比如某个日志要求存一年后才清理但实际是拍脑袋定的,那就追问,为什么是一年? 一个月就清理能接受吗?不同的需求在实现上的难度和对性能的要求也是不同的。