这是我参与「第五届青训营 」笔记创作活动的第3天
本节课程主要介绍了Go语言的高质量编程和性能调优。
1.高质量编程
编程原则
简单性 可读性 生产力
编码规范
注释
公共符号始终要注释,包中每个公共符号、并不简短或简单的功能都需要注释......
Good code has lots of comments, bad code requires lots of comments
注释应该解释代码作用、是如何做的、实现的原因、什么情况下会出错
代码格式
使用gofmt自动格式化代码
或使用goimports格式化代码(同时格式化依赖包的引用)
变量命名
缩略词全大写,其他使用驼峰式命名规则进行区分,但是缩略词在变量名非开头时则不需要:
eg. ServeHttp XMLHTTPRequest xmlHTTPRequest
函数命名
函数命名不携带包名的上下文信息,因为包名和函数名总是成对出现的。
包命名
只由小写字母组成,不包含大写字母和下划线等字符
控制流程
尽量保持正常的代码路径为最小缩进,优先处理错误情况、特殊情况,尽早返回或持续循环来减少嵌套。
错误和异常处理
在fmt.Errotf中使用%w关键字来将一个错误关联至错误链中
2.高性能编程
Benchmark
go test -bench=. -benchmem
Slice预分配内存
尽可能在使用make()初始化切片时提供容量信息
切片的本质是一个数组片段的描述
包括数组指针、片段的长度、片段的容量(在不改变内存分配下的最大长度)
切片操作并不复制切片指向的元素
创建一个新的切片会复用原来切片的底层数组
type slice struct {
array unsafe.Pointer
len int
cap int
}
陷阱:大内存未释放
在已有切片的基础上创建切片,不会创建新的底层数组
// 只使用了origin数组后两个元素的空间,直接根据原数组创建切片会导致新的底层数组产生
func GetLastBySlice(origin []int) []int{
return origin[len(origin)-2:]
}
func GetLastBySlice(origin []int) []int{
result := make([]int,2)
copy(result, origin[len(origin)-2:]
return result
}
同理,使用map也需要预分配内存
字符串处理
使用strings.Builder来进行字符串处理
与java相同,Go语言中字符串类型所占空间都是不变的,使用'+'运算符都会新建一份空间,使用strings.Builder可以达到动态分配空间的目的。
strings.Builder也可以预分配空间,使用Grow()方法可以为其预分配一片空间
使用空结构体节省内存
空结构体实例struct{}不占用任何空间,只是作为占位符号
例:实现Set数据类型,也即只使用Map的键,不使用值,则可以声明为myset := make(map[stirng]sturct{})
对应的值只需要使用struct{}{}即可(前半部分为空结构体实例,后半部分为其实现)
atomic包
使用atomic包可以用于多线程维护一个变量(代替加锁去锁过程)
本质上因为锁是通过操作系统来实现吗,而atomic是通过硬件实现,性能更高。
对于非数值操作,可以使用atomic.Value,可以承载一个interface{}
3.性能调优实战
性能优化原则
依靠数据而不是猜测 定位最大瓶颈而不是细枝末节 不要过早优化 不要过度优化
性能分析工具pprof
为代码增加pprof工具如下:
log.SetFlags(log.Lshortfile | log.LstdFlags)
log.SetOutput(os.Stdout)
runtime.GOMAXPROCS(1)
runtime.SetMutexProfileFraction(1)
runtime.SetBlockProfileRate(1)
go func() {
if err := http.ListenAndServe(":6060", nil); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
浏览器查看指标:pprof会将采样结果输出到localhost:6060/debug/pprof上,也可以使用go tool pprof <url>来直接获取目标目录的信息
cpu排查
使用go tool pprof http://localhost:6060/debug/pprof/profile?seconds=10指令进入pprof cpu排查模式
使用top命令显示cpu当前运行的全部进程
指标解释:
flat:当前函数本身执行耗时
flat%: 当前函数执行占用cpu总时间的比例
sum%:自上到当前行所有flat%的总和
cum:当前函数本身的消耗+其他函数调用耗时
cum%:当前函数本身耗时+其他函数调用耗时占cpu总时间的比例
当flat==cum时,flat没有调用其他函数 flat==0时,函数中只有其他函数的调用
使用list命令根据指定的正则表达式查找代码行
web命令可以调用关系可视化
排查对应函数问题
堆内存排查
执行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"即可进行协程内存的排查
阻塞问题排查
执行go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/block"即可进行阻塞问题的排查
pprof的采样过程和采样原理
cpu
操作系统每10ms向进程发送一次SIGPROF信号,进程每次接收到SIGPROF会记录调用堆栈,每100ms读取已经记录的调用栈并写入输出流。
堆内存
采样程序通过内存分配器在堆上分配和释放的内存,记录分配/释放的大小和数量
协程采样
记录所有用户发起且在运行中的goroutine,用runtime.main的调用栈信息
4.性能调优案例
基本概念:
服务:能单独部署,承载一定功能的程序
依赖:Service A的功能依赖Service B的响应结果则称A依赖B
调用链路:能支持一个接口请求的相关服务集合及其相互之间的依赖关系
基础库:公共的工具包、中间件
建立服务性能评估手段:
根据线上真实流量情况,进行单机器压测和集群压测,获取单机性能数据和集群性能数据。
业务服务优化
线上请求数据录制回放,新旧逻辑接口数据diff
基础库优化
分析基础库核心逻辑和性能瓶颈,内部压测验证,推广业务服务落地验证
总结
本节课从质量和性能两个角度介绍了Go的优化,对Go语言的代码规范进行了进一步的强调,同时对于基本内存分配、代码逻辑、协程管理等多个角度优化了代码选择。之后介绍了pprof库在调优场景的应用和原理,最后结合实际服务情景介绍了系统应对策略。
引用
源码:wolfogre/go-pprof-practice: go pprof practice. (github.com)