Go编码规范以及性能调优pprof实战 | 青训营笔记

143 阅读7分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第2篇笔记

服务性能优化的目标

降低资源占用成本,服务资源,使用简单的代码逻辑,注意代码的质量,编写简洁清晰的代码,容易改动,且知道代码改动之后的影响。

什么是高质量编程

高质量编程,编码规范,性能优化,函数调用,性能调优,性能分析工具

  • 编写的代码能够达到正确可靠,简洁清晰的目标可称之为高质量代码
  • 各种边界情况是否考虑完备
  • 易读易维护(附加功能不要有其他的情况)

编码的原则

实际应用的场景千变万化,各种语言的特性和语法各不相同,但是高质量编程遵循的原则是相同的 简单性 消除多余的复杂性,以简单清晰的逻辑编写代码 可读性 代码是写给人看的,而不是机器 编写可维护代码的第一步就是要确保代码可读 生产力,团队整体效率2非常重要

官方工具

gofmt Go语言官方提供的工具,能够自动格式化Go语言为官方统一风格 goimports Go语言官方提供的工具,实际上等于gofmt机上依赖管理,自动增删依赖的包引用,按依赖包的字母进行排序分类

注释的准则

  • 注释应该解释代码的作用
  • 注释应该解释代码什么情况下会出错
  • 注释应该解释代码是怎么做的
  • 公共符号要注释
  • 变量,常量,函数以及结构体都需要添加注释
  • 任何既不明显也不简短的公共功能必须要注释
  • 无论长度或复杂度,对库中的任何函数都必须要进行注释 代码就是最好的注释,注释应该提供代码未表达的上下文信息

命名规范

简洁胜于冗长 缩略词全大写,但当其位于变量开头且不需要导出时,使用全小写,变量距离其被使用的地方越远,越需要携带越多的上下文信息,全局变量在其名字中需要更多的上下文信息,使得在不同地方可以轻易辨别出其含义

包的命名规范

只有小写字母组成,不包含大写字母和下划线等字符 简短并包含一定的上下文信息,不要与标准库同名

不适用常用变量名作为包名,使用单数而不是复数 谨慎的使用缩写 核心代码的目标就是来降低阅读的成本 重点考虑上下文信息 设计简洁清晰的名称 控制流程 避免嵌套 优先处理错误情况/特殊情况 尽早返回或继续循环来减少嵌套

线性原理

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

错误

  • 简单错误 简单的错误指的是仅出现一次的错误 且在其它地方不需要捕获该错误 优先使用 errors.NEW 来创建匿名变量来直接表示简单错误 如果由格式化的需求 使用fmt.Errorf
  • 错误判定 判定一个错误是否为特定错误,使用errors.Is 不同于使用==,使用该方法可以判定错误链上的所有错误是否含有特定的错误
  • panic 不建议在业务代码中使用panic 调用函数不包含recover会造成程序崩溃 若问题可以被屏蔽或解决,建议使用error代替panic 当程序启动阶段发生不可逆转的错误时,可以在init或main函数中使用panic
  • recover recover只能被defer的函数中使用 嵌套无法生效 只在当前goroutine生效 defer的语句都是后进先出

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

性能分析工具pprof

pprof是一个网页的可视化终端 可以进行采样 CPU 堆内存 协程 锁 阻塞 线程创建 展示方面有火焰图 调用图 源码 Peek CPU Profiling:CPU 分析,按照一定的频率采集所监听的应用程序 CPU(含寄存器)的使用情况,可确定应用程序在主动消耗 CPU 周期时花费时间的位置

Memory Profiling:内存分析,在应用程序进行堆分配时记录堆栈跟踪,用于监视当前和历史内存使用情况,以及检查内存泄漏

Block Profiling:阻塞分析,记录 goroutine 阻塞等待同步(包括定时器通道)的位置

Mutex Profiling:互斥锁分析,报告互斥锁的竞争情况

做性能分析,第一步需要先获取数据,然后对数据进行分析。所以下面展示一下如何进行数据获取。 top 总耗时 占用cpu资源 Flat == Cum 函数中没有调用其它的函数 Flat == 0 函数中只有其他函数的调用

list 查看函数哪里出了问题 web 调用关系可视化 Heap 堆内存 goroutine 协程 go tool pprof -http=:8080 结构图 火焰图是动态的 支持点击块进行分析 支持搜索 在Source视图下搜索wolf、 mutex 锁 http://localhost:6060/debug/pprof/mutex

采样争抢锁的次数和耗时 只记录固定比例的锁操作,1为加锁均记录 block 阻塞 调用视图 解决问题 采样阻塞操作的次数和耗时 采样率 阻塞耗时超过阈值的才会被记录

采样过程

  • 操作系统启动一个进程(定时器),
  • 发送信号给进程,
  • 记录堆栈信息,启动写缓冲,
  • 写入到输出流,停止定时器,结束输出
  • 开始采样 设定信号处理函数 开启定时器
  • 停止采样 取消信号处理函数 关闭定时器

堆内存

  • 采样程序通过内存分配器在堆上分配和释放的缓存,记录分配释放的大小和数量
  • 采样率 每分配512KB记录一次,可运行开头修改,为每次分配均记录
  • 采样时间 从程序运行开始到采样时
  • 采样指标 alloc_space alloc_objiects inuse_space inuse_objiects
  • 计算方式 inuse = alloc - free

Goroutine 协程 ThreadCreate 线程创建 Goroutine Stop The World 遍历Slice切片 输出创建g的堆栈 Start The World ThreadCreate Stop The World 遍历allm链表 输出创建m的堆栈 Start The World

业务服务优化

基础库优化 Go语言优化

业务服务优化

  • 基本概念 服务 能单独部署 承载一定功能的程序 Service A 的功能实现依赖 Service B的响应结果 成为Service A 依赖 Service B

  • 调用链路 能支持一个接口请求的相关服务集合及其相互之间的依赖关系 基础库,公共的工具包,中间件

  • 建立服务性能评估手段

  • 服务性能评估方式 单独benchmark无法满足复杂逻辑分析 不同负载情况下性能表现差异

  • 请求流量构造 不同请求参数覆盖逻辑不同

  • 线上真实流量情况

  • 压测范围 单机器压测 集群压测 性能数据采集 单机性能数据 集群性能数据