这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天.
高质量编码与性能调优
- 正确可靠,简洁清晰
- 各种边界问题是否考虑完备
- 异常情况处理,稳定性保证
- 易读易维护
编程原则
- 简单性:逻辑简单清晰
- 可读性:可维护
- 生产力:团队合作
编码规范
代码格式
- 格式化工具 gofmt,goimports
注释
- 解释代码作用
- 如何做的
- 实现的原因
- 什么情况会出错
命名规范
- 简洁胜于冗长
变量
- 缩略词全大写,但当期位于变量开头且不需要导出时,使用全小写。例:ServerHTTP,xmlHTTPRequest。
- 变量距离其被使用的地方越远,需要越多的上下文信息。
函数
- 函数名不携带包名,尽量简短
包
- 只有小写字母组成,不包含大写和下划线等字符。
- 简短并包含一定上下文信息,不要与标准库同名。
控制流程
- 线性原理,避免嵌套,保证正常流程清晰,
- 正常流程代码沿着屏幕向下移动,
- 易读易维护。
错误和异常处理
简单错误
- 仅出现一次的错误,优先使用
error.New来创建匿名变量来直接表示错误,如果有格式化的需求,使用fmt.Errorf。
错误的Wrap和Unwrap
- wrap实际上提供了一个error嵌套另一个error的能力,从而生成一个error的跟踪链
- 在
fmt.Errorf中使用%w关键字将错误关联至错误链中
错误判定
- 判断一个错误是否为特定错误,使用
errors.ls - 不同于使用==,该方法可以判定错误链上的所有错误是否含有特定的错误。
- 在错误链上获取特定种类的错误使用
error.As
panic
- 不建议在业务代码中使用
panic - 调用函数不包含
recover会造成程序崩溃 - 若问题可以被屏蔽或解决,建议使用
error代替panic - 当程序启动阶段发生不可逆转的错误时,可以在init或者main函数中使用
panic
recover
recover只能被defer的函数中使用- 嵌套无法生效
- 只在当前goroutine生效
defer的语句是后进先出- 如果需要更多的上下文信息,可以在
recover后再log中记录当前的调用栈
性能优化建议
benchmark
slice 预分配内存
- 尽可能在使用
make()初始化切片时提供容量信息。 - 可使用
copy替代re-slice防止大内存未释放。
map 预分配内存
字符串处理
- 用
string.Builder处理字符串拼接,代替+,byte.Buffer
空结构体struct{}
- 空结构体本身具有很强的语义,即这里不需要任何值,不占据任何的内存空间,可作为各种场景下的占位符使用,节省内存。
- 可实现
set
atomic包
- 并发时替换加锁操作
性能调优原则
- 依靠数据不是猜测
- 定位最大瓶颈而不是细枝末节
- 不要过早,过度优化
性能分析工具pprof
性能调优案例
业务服务优化
基本概念
- 服务:能单独部署,承载一定功能的程序
- 依赖:Service A 的功能实现依赖Service B的相应结果,称为Service A依赖Service B
- 调用链路:能支持一个接口请求的相关服务集合及其相互之间的依赖关系
- 基础库:公共的工具包、中间件
流程
- 建立服务性能评估手段
- 分析性能数据,定位性能瓶颈
- 重点优化改造
- 优化效果验证
建立服务性能评估手段
-
服务性能评估方式
-
单独 benchmark 无法满足复杂逻辑分析
-
不同负载情况下性能表现差异
-
-
请求流量构造
-
不同请求参数覆盖逻辑不同
-
线上真实流量情况
-
-
压测范围
- 单机器压测
- 集群压测
-
性能数据采集
- 单机性能数据
- 集群性能数据
分析性能数据,定位性能瓶颈
- 使用库不规范
- 高并发场景优化不足
重点优化改造
- 正确性是基础
- 响应数据 diff
- 线上请求数据录制回放
- 新旧逻辑接口数据 diff
优化效果验证
- 重复压测验证
- 上线评估优化效果
- 关注服务监控
- 逐步放量
- 收集性能数据
进一步优化,服务整体链路分析
- 规范上游服务调用接口,明确场景需求
- 分析链路,通过业务流程优化提升服务性能