高质量编程与性能调优实战
高质量编程
注释
- 代码作用(能用来做什么)
- 代码如何做的
- 代码实现原因
- 代码什么时候会出错
- 公共符号始终要注释(常量、变量、结构、函数等都要,任何不明显也不简短的公共功能必须予以注释,库中任何函数都必须进行注释),但是不需要注释实现接口的方法
命名规范
变量:
- 缩略词全大写,但当其位于变量开头且不需要导出时,用全小写
- 变量距离使用的位置越远,则需要携带更多上下文信息
- 简洁>冗余,但是尽量不减少必要的传递信息或者造成误会
函数:
- 尽量不携带包名的上下文信息
- 尽量简短
- 返回非包名的类型时,可以在函数名内加入类型信息
package:
- 只由小写字母组成,不含大写字母和下划线字母
- 简短并包含一定上下文信息
- 不和标准库同名
- 用单数不用复数
控制流程
- 避免嵌套,保持流程清晰
- 尽量保持代码缩进为最小缩进,优先处理错误/特殊情况,尽早返回或继续循环来减少嵌套
错误和异常处理
- 优先使用erros.New来创建匿名变量来直接表示简单错误(仅出现一次而且其他地方不用捕获)
- 有格式化需求用fmt.Errorf,用%w关键字来将错误关联到错误链中
- 错误的wrap提供实现一个error嵌套另一个error的能力,从而形成error的跟踪链
- 判定一个错误是否为特定错误,使用errors.ls,与==不同,该方法可判定错误链上是否含有特定错误
- 在错误链上获取特定种类的错误,可以使用errors.As
- 在业务内不建议使用panic(会导致程序宕机,停止运行并输出日志信息,defer会在panic前优先运行输出结果)
- recover只能在defer函数内使用,而且嵌套无法生效,只在当前goroutine生效
性能优化建议
-
-benchmark
go test -bench=. -benchmem
-
slice
- 尽量在make()初始化切片时提供容量信息
- 创建一个新的切片会复用原来切片的底层数组
- 在已有切片基础上创建切片,不会创建新的底层数组场景
- 可以用copy替代re-slice
-
Map
- 尽量预分配内存
-
字符串处理
- 使用strings.Builder
-
使用空结构体节省内存
- 空结构体实际不占任何空间
- 可作为占位符使用
- 可以代替map存的值,会比bool更节省内存
-
使用atomic包
- 锁的实现通过系统实现,属于系统调用
- 通过硬件实现,效率较高
- sync.Mutex应该用来保护逻辑而不是变量
- 对于非数值操作,可以使用atomic.Value,能承载一个interface
性能调优实战
原则:
- 不要过早优化
- 不要过度优化
- 要依靠数据而不是猜测
- 要定位最大瓶颈而不是细枝末节
性能分析工具pprof
看文档
性能调优案例
-
业务服务优化
- 建立服务性能评估手段
- 分析性能数据,定位性能瓶颈
- 重点优化改造
- 优化效果验证
建立服务性能评估手段
-
服务性能评估方式
- 单纯benchmark无法满足复杂逻辑分析
- 不同负载情况下性能表现差异
-
请求流量构造
- 不同请求参数逻辑覆盖不同
- 线上真实流量情况
-
压测范围
- 单机器压测
- 集群压测
-
性能数据采集
- 单机性能数据
- 集群性能数据
分析性能数据,定位性能瓶颈
- 使用库不规范
- 高并发场景优化不足
重点优化改造
-
正确性是基础
-
相应数据diff
- 线上数据录制回放
- 新旧逻辑接口数据diff
优化效果认证
-
重复压测验证
-
上线评估优化效果
- 关注服务监控
- 逐步放量
- 收集性能数据
进一步优化,服务整体链路分析
- 规范上游服务调用接口,明确场景需求
- 分析链路,通过链路流程优化提升服务性能
基础库优化
-
分析基础库核心逻辑和性能瓶颈
- 设计完善改造方案
- 数据按需获取
- 数据序列化协议优化
-
内部压测验证
-
推广业务服务落地验证
Go 语言优化
- 优化内存分配策略
- 优化代码编译流程,生成更高效的程序
- 内部压测验证
- 推广业务服务落地验证