高质量编程与性能调优实践 | 青训营笔记

42 阅读6分钟

高质量编程

简介

编写的代码能够达到正确可靠、简洁清晰的目标可称之为高质量代码。

  • 各种边界条件是否考虑完备
  • 异常情况处理,稳定性保证
  • 易读易维护

编码原则

简单性、可读性、生产力。

  • 代码格式
  • 注释
  • 命名规范
  • 控制流程
  • 错误和异常处理

编写规范

代码格式

推荐使用gofmt自动格式化代码。

注释

  • 解释代码作用(注释公共符号)、解释代码如何做的(注释实现过程)、解释代码实现的原因(注释解释代码的外部因素、提供额外上下文)、解释代码什么情况会出错(解释代码的限制条件)。
  • 公共符号必须要注释。
  • 代码是最好的注释,注释应该提供代码未表达出的上下文信息。

命名规范

  • variable:简洁、缩略词全大写(变量开头全小写)、变量距离其使用的地方越远,则需要携带越多的上下文信息。
  • function:尽量简短、不携带包名的上下文信息,因为包名和函数名总是成对出现的。
  • package:只由小写字母组成、简短并包含一定的上下文信息、不要与标准库同名、不使用常用变量名作为包名、使用单数而不是复数、谨慎使用缩写。

控制流程

  • 线性原理,处理逻辑尽量走直线,避免复杂的嵌套分支。
  • 正常流程代码沿着屏幕向下移动,尽量保持正常代码路径为最小缩进。
  • 提升代码可维护性和可读性。
  • 故障问题大多出现在复杂的条件语句和循环语句中。

错误和异常处理

简单错误

即只出现一次的错误。优先使用errors.New来创建匿名变量来直接表示简单错误,需要格式化则使用fmt.Errorf。

错误的Wrap和Unwrap

错误的Wrap实际上是提供了一个error嵌套另一个error的能力,从而生成一个error的跟踪链。 在fmt.Errorf中使用%w关键字来将一个错误关联到错误链中。

错误判定

  • 使用errors.ls判定一个错误是否为特定错误。
  • 使用errors.As在错误链上获取特定种类的错误。

panic

  • 不建议在业务代码中使用panic,建议使用error代替panic
  • 当程序启动阶段发送不可逆转的错误时,可以在init或main函数中使用panic。

recover

  • recover只能在被defer的函数中使用
  • 嵌套无法生效
  • 只在当前goroutine生效
  • defer的语句是后进先出

总结

  • error尽可能提供简明的上下文信息链,方便定位问题
  • panic用于真正异常的情况
  • recover生效范围,在当前goroutine的被defer的函数中生效

性能优化建议

  • 性能表现需要实际数据衡量
  • Go语言提供了支持基准性能测试的benchmark工具

slice预分配内存

尽可能在使用make()初始化切片时提供容量信息。

  • 陷阱:大内存未释放 在已有切片基础上切片,不会创建新的底层数组。
  • 解决:可使用copy替代re-slice

map预分配内存

不断向map中添加元素的操作会触发map的扩容,提前分配好空间可以减少内存拷贝和Rehash的消耗。建议根据实际需求提前预估好需要的空间。

字符串处理

  • 使用strings.Builder:通过+拼接性能最差,strings.Builder,bytes.Buffer相近,strings.Buffer更快

使用空结构体节省内存

空结构体struct{}实例不占据任何的内存空间,可作为各种场景下的占位符使用。实现Set,可以考虑用map来代替。

automic包

  • 锁是通过操作系统实现,属于系统调用
  • automic操作是通过硬件实现,效率比锁高
  • sync.Mutex应该用来保护一段逻辑,不仅仅用于保护一个变量
  • 对于非数值操作,可以使用automic.Value,能承载一个interface{}

总结

  • 避免常见的性能陷阱可以保证大部分程序的性能
  • 普通应用代码,不要一味追求程序的性能
  • 越高级的性能优化手段越容易出现问题
  • 在满足正确可靠、简洁清晰的质量要求前提下提高程序性能

性能调优实战

简介

性能调优原则

  • 依靠数据而不是猜测
  • 定位最大瓶颈而不是细枝末节
  • 不要过早优化
  • 不要过度优化

性能分析工具 pprof 实战

简介

分为 分析profile,工具tool,展示view 和 采样sample。

分析

  • 网页
  • 可视化终端

工具

  • runtime/pprof
  • net/http/pprof

展示

  • Top
  • 调用图-Graph
  • 火焰图-FlameGraph
  • Peek
  • 源码-Source
  • 反汇编-Disassemble

采样

  • CPU
  • 堆内存-Heap
  • 协程-goroutine:goroutine泄露也会导致内存泄露。
  • 锁-mutex
  • 阻塞-block
  • 线程创建-ThreadCreate

性能调优案例

简介

  • 业务服务优化
  • 基础库优化
  • Go 语言优化

业务服务优化

  • 服务:能单独部署,承载一定功能的程序
  • 依赖:Service A 的功能实现依赖 Service B 的响应结构,称为 Service A 依赖 Service B
  • 调用链路:能支持一个接口请求的相关服务集合及其相互之间的依赖关系
  • 基础库:公共的工具包、中间件
  • 流程:
  1. 建立服务性能评估手段
  2. 分析性能数据,定位性能瓶颈
  3. 重点优化项改造
  4. 优化效果验证

建立服务性能评估手段

服务性能评估方式(单独benchmark无法满足复杂逻辑分析,不同负载情况下性能表现差异)、请求流量构造、压测范围、性能数据采集

分析性能数据,定位性能瓶颈

  • 使用库不规范
  • 高并发场景优化不足

重点优化项改造

  • 正确性是基础
  • 响应数据diff:线上请求数据录制回放、新旧逻辑接口数据diff

优化效果验证

  • 重复压测验证
  • 上线评估优化效果:关注服务监控、逐步放量、收集性能数据

进一步优化,服务整体链路分析

  • 规范上游服务调用接口,明确场景需求
  • 分析链路,通过业务流程优化提升服务性能

基础库优化

AB实验SDK的优化

  • 分析基础库核心逻辑和性能瓶颈:设计完善改造方案、数据按需获取、数据序列化协议优化
  • 内部压测验证
  • 推广业务服务落地验证

Go语言优化

编译器 & 运行时优化

  • 优化内存分配策略
  • 优化代码编译流程,生成更高效的程序
  • 内部压测验证
  • 推广业务服务落地验证
  • 优点:接入简单,只需要调整编译配置;通用性强