这是我参与「第三届青训营-后端场」笔记创作活动的的第3篇笔记。
高质量编程
正确可靠,简洁清晰。 边界条件完备、异常处理、易读易维护。
原则:
- 简单性
- 可读性
- 生产力
编码规范
- 代码格式。使用官方工具
gofmt - 注释。解释:
- 代码的作用。常量、变量以及对外公开的函数等公共符号。
- 如何做的。对复杂的逻辑进行说明。
- 实现原因。外部原因,提供额外上下文。
- 出错情况。解释代码的限制条件。
- 公共符号始终要注释。
命名规范
- 变量。
- 缩略词全大写如
ServeHTTP,但位于开头又不需要导出时全小写如xmlHTTPRequest; - 距离使用的地方越远越详细,全局变量需要包含更多的上下文信息。
- 函数参数命名对外提供更多信息。
func send(req *Request, deadline time.Time)
- 缩略词全大写如
- 函数。
- 不需要携带包名的上下文信息
- 尽量简短
- 当名为 foo 的包某个函数返回类型 Foo 时,可以省略类型信息而不导致歧义
- 当名为 foo 的包某个函数返回类型 T 时(T 并不是 Foo),可以在函数名中加入类型信息
- 包名。
- 全部小写
- 简短
- 不要与标准库同名
- 其它:不使用常用变量名;使用单数;谨慎使用缩写
控制流程
- 避免嵌套,去除冗余。
- 尽量保持正常代码路径为最小缩进。也就是要优先处理错误/特殊情况,尽早返回或继续循环来减少嵌套。
错误和异常处理
- 简单错误。仅出现一次的错误,优先使用
errors.New创建匿名变量直接表示。或者fmt.Errorf - 在
fmt.Errorf中使用%w关键字将错误关联至错误链。- 使用
errors.Is判定错误链中是否含有特定错误 - 使用
errors.As获取错误链中特定种类的错误。
- 使用
- panic. 比错误更严重,表示程序无法正常工作。
- 不建议在业务代码中使用,如果问题可以被屏蔽或解决,建议使用error代替。
- 在程序启动阶段发生不可逆转错误时,可以在
init或main函数中使用panic().
- recover. 使用的其它库抛出panic时使用recover处理。
- 只在当前goroutine中被defer的函数中生效。嵌套无法生效
- 可以在recover之后的log中记录当前的调用栈
debug.Stack()
性能优化建议
- 使用Benchmark工具
- Slice
- 预分配内存。在初始化时指定容量。因为切片本质上是数组片段的描述,减少重新分配内存的次数。
- 大内存未释放。在已有切片上创建切片不会创建新的底层数组,导致原数组得不到释放。可以使用copy代替re-slice.
- Map
- 预分配内存。
- 字符串处理
- 字符串拼接:使用
+直接拼接性能最差;使用strings.Builder或bytes.Buffer进行拼接性能相近,strings.Builder更快。 - 因为使用
+每次都会重新分配内存。strings.Builder和bytes.Buffer有内存扩容机制,不需要每次都重新分配内存。 bytes.Buffer转化为字符串时重新申请了一块内存,而strings.Builder直接将[]byte类型转换成字符串类型输出。- 还可以利用预分配内存进一步提高性能。涉及
Grow方法。
- 字符串拼接:使用
- 空结构体
- 不占据内存空间,可作为占位符。例如使用
map[int]struct{}实现Set,可以节省资源。
- 不占据内存空间,可作为占位符。例如使用
- atomic包
- 例如
atomic.AddInt32() - 代替互斥锁。锁的实现属于系统调用,而atomic操作通过硬件实现。
sync.Mutex用于保护一段逻辑,不仅仅用于保护一个变量
- 例如
性能调优实战
依靠数据而非猜测;定位最大瓶颈而非细枝末节;不要过早优化;不要过度优化。
使用性能分析工具 pprof. 可以知道应用在什么地方耗费了多少 CPU、memory 等运行指标。
- 功能:工具(Tool),采样(Sa),分析(Profile),展示(View)
- 排查CPU问题
go tool pprof "http://localhost:6060/debug/pprof/profile"toplist
- 排查堆内存问题
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap"- alloc_objects/alloc_space 程序累计申请的对象/内存
- inuse_objects/inuse_space 程序当前使用的对象/内存
- 排查协程问题
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/goroutine"- 使用火焰图(View-Flame graph)
- 排查锁问题
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/mutex"
- 排查阻塞问题
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/block"
- pprof的采样过程和原理
性能调优案例
- 服务:能单独部署,承载一定功能的程序
- 依赖:Service A 的功能实现依赖 Service B 的响应结果,称为 Service A 依赖 Service B
- 调用链路:能支持一个接口请求的相关服务集合及其相互之间的依赖关系
- 基础库:公共的工具包、中间件
业务服务优化
- 建立服务性能评估手段
- 服务性能评估方式。benchmark无法满足复杂逻辑分析;不同负载情况下性能差异;
- 构造请求流量。不同请求参数覆盖的逻辑不同;线上真实流量情况(压测平台)
- 压测范围。单机器/集群压测。
- 性能数据采集。单机/集群性能数据。
- 分析性能数据,定位性能瓶颈
- pprof 火焰图定位到代码。例如使用库不规范、高并发场景优化不足
- 重点优化项改造;
- 优化正确性验证。线上请求数据录制回放,检查新旧逻辑响应数据diff
- 优化效果验证
- 重复压测验证
- 上线评估。逐步放量,服务监控,收集数据
- 进一步优化,服务整体链路分析(非单点服务)
- 分析上下游业务流程,通过业务流程优化提升服务性能
- 规范上游服务调用接口,明确场景需求
基础库优化
适应范围更广,覆盖更多服务
- 分析基础库核心逻辑和性能瓶颈
- 完善改造方案
- 内部压测验证
- 推广业务服务落地验证
Go语言优化
适应范围最广,Go 服务都有收益
- 优化内存分配策略
- 优化代码编译流程,生成更高效的程序
- 内部压测验证
- 推广业务服务落地验证