go 高质量编程
简介
高质量代码——正确可靠、简洁清晰的代码
- 边界条件
- 异常处理,保证稳定性
- 易读易维护,便于合作处理
原则
-
简单性
便于修复、改进;消除多余的复杂性
-
可读性
可维护的第一步
-
生产力
通过遵循常见的规范,便于提高团队整体效率
规范
-
代码格式:推荐使用
gofmt自动格式化工具 -
注释:
解释代码 What, How, Why, When以及提示错误情况
代码就是最好的注释
-
命名规范
-
变量
缩略语应全部大写;
全局变量应携带更多信息;
-
函数名
函数不用携带包的信息,因为总是一起出现
-
包
只是用小写字母;
简短但包括一定信息;
不要与标准库相同;
-
好的命名能够降低理解代码的成本;
结合上下文信息,设计简洁清晰的代码
-
控制流程
遵循线性原理,避免复杂的嵌套分支
-
错误和异常处理
-
对于只出现一次的简单错误,通过
error.New,fmt.Errorf就可以处理 -
Wrap和Unwrap提供了error嵌套的能力,从而生成一个 error 的跟踪链
在
fmt.Errorf中使用:%w关键字来将一个错误关联到错误链中- 使用
errors.Is进行错误判定 - 使用
errors.As取出指定类型的错误
- 使用
-
panic
不建议在业务代码中使用 panic
-
recover
只能在 defer 的函数使用; 嵌套无法生效; 满足后进先出,使用时注意顺序
可以 recover 后在 log 中记录当前的调用栈
-
性能优化建议
基准性能测试工具benchmarkgo test -bench=. -benchmem
会展示测试函数名称,执行次数,每次执行花费时间,申请内存大小,申请内存次数
slice
- 在使用
make()初始化slice时,提供容量信息,减少内存分配次数
对 slice 进行扩容时,如果原数组容量不足,则会先进行扩容操作,这会有很大的开销
-
由于切片对源数组的引用,导致源数组得不到释放
此时可以使用
copy代替重新创建切片
map
- 同样应该预分配内存(简单预估)
字符串处理
使用strings.Builder和bytes.Buffer代替直接使用+号拼接字符串
- 字符串是不可变类型,占内存大小是固定的
- 使用 + 每次都会重新分配内存
- strings.Builder,bytes.Buffer 底层都是 []byte 数组
- 内存扩容策略,不需要每次拼接都重新分配内存
空结构体
空结构体不会占用内存,起到占位的作用,同时具有一定的语义信息
例如,如果使用 map 代替 set 的实现,只需要使用 key,此时就可以将 value 设置为空结构体
atomic 包
多线程编程时,使用 atomic 包来保护一个单独的变量,而不使用锁
- 锁的实现是通过操作系统来实现 , 属于系统调用
- atomic 操作(即原子指令)通过硬件实现,效率比锁高
总结
不要一味追求性能,越高级的性能优化方法,越容易出现问题,最重要的是满足正确可靠、简洁清晰的质量要求的前提
性能调优实战
原则
- 依靠数据而不是猜测
- 定位性能瓶颈而不要追究细枝末节
- 不要过早优化,需要等到系统稳定后再分析
- 不要过度优化,容易影响后续迭代
优化最重要的是保证程序正确性
工具——pprof
能知道应用在那些地方耗费了多少系统资源,并且可以可视化分析
功能简介
原理
CPU
- 操作系统:每10ms向进程发送一次SIGPROF信号
- 进程:每次接收到SIGPROF记录调用堆栈
- 写缓冲:每100ms读取以及记录的调用栈并写入输出流
Heap 堆内存
- 采样程序:通过内存分配器在堆上分配和释放的内存,记录分配/释放的大小和数量
- 采样率:每分配 512KB 记录一次,可在运行开头修改,1 为每次分配均记录
- 采样时间:从程序运行开始到采样开始
- 采样指标:
alloc space(分配空间),alloc_objects(分配对象),inuse_space(停止空间),inuse_objects(停止对象) - 计算方式: inuse = alloc - free
协程与线程创建
- 协程:记录所有用户发起且在运行中(入口非runtime)的协程的调用栈信息
- 线程:记录程序创建的所有系统线程的信息
阻塞和锁
-
阻塞
- 采样阻塞操作的次数和耗时
- 采样率:阻塞耗时超过阈值的才会被记录 ,1为每次都记录
-
锁竞争
- 采样争抢锁的次数和耗时
- 采用率:只记录固定比例的锁操作,1为全部记录
实际案例分析
流程
- 建立服务性能评估手段
- 分析性能数据,定位性能瓶颈
- 重点优化项改造
- 优化效果验证
建立性能评估手段
-
服务性能评估方式
- 单独使用benchmark无法满足复杂逻辑分析
- 需要在不同负载下分析性能表现
-
请求流量构造
- 不同请求参数不同,覆盖的逻辑不同
- 线上的真实流量不同
-
压力测试需要进行单机测试和集群测试
-
性能数据采集也需要单机和集群性能数据
分析性能数据,定位性能瓶颈
获取到数据之后开始进行分析
- 使用库不规范导致产生多余预期的资源消耗,影响了服务性能
- 高并发场景优化不足,对对应逻辑进行优化
重点优化项改造
需要保证正确性
- 将线上请求的返回值录制保存下来
- 使用相同的请求值再次请求优化后的服务
- 对比返回值是否相同
如果返回值相同,则此次优化对功能的正确性无影响
优化效果验证
重复进行压力测试验证,查看优化效果
- 关注服务监控
- 逐步放量:逐步增加请求量,便于定位问题
- 收集性能数据
进一步优化,服务整体链路分析
- 规范上游接口,明确场景需求和数据集
- 分析链路,通过业务流程优化提升服务性能