这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天
高质量编程
- 正确可靠
- 简洁清晰
要求:
- 简单性
- 可读性
- 生产力
编码规范
- 公共符号始终要注释(特殊:不需要注释实现接口的方法)
- 采用gofmt自动格式化代码
- 注释:解释代码作用、如何做、实现原因、什么情况下出错
命名规范
- 变量:简洁,缩略词全大写(开头特殊)、变量距离使用的地方越远,需携带更多上下文信息
- 形参:使用具有特定含义的
- 函数名:不携带包名的上下文信息、短
- 包名:只包含小写字母、短+包含上下文信息、避免重名
控制流程
- 避免嵌套
- 尽量保持正常代码路径为最小缩进
错误和异常处理
- 优先使用errors.New来创建匿名变量来直接表示简单错误
- 若有格式化要求,使用fmt.Errorf
- 错误的Wrap和Unwrap
- 判断特殊错误,使用errors.Is/As
- 不建议在业务代码中使用panic
- recover只能在被defer的函数中使用、嵌套无法生效、只在当前goroutine有效、defer后进先出
性能优化
go test -bench=. -benchmen
go test -run=. -v
slice
切片的本质是一个数组片段的描述(数组指针、片段长度、片段容量),切片操作并不复制切片指向的元素、创建新切片会复用原来切片的底层数组
- 尽可能使用make初始化容量信息
- 在已有切片基础上创建切片,不会创建新的底层数组,可能会导致大内存未释放/原底层数组在内存中有引用,得不到释放(可以使用copy替代re-slice)
map
- 预分配内存,向map中添加元素会触发map的扩容,预分配可以减少内存拷贝和rehash的消耗
string
- 字符串在Go中是不可变类型,占用内存大小固定,
- 使用+每次会重新分配内存,
- strings.Builder/Buffer底层是[]byte数组,内部维护了扩容策略
- 使用strings.Builder/Buffer拼接字符串,builder较快,因为buf转化为字符串时重新申请了一块空间,builder直接将底层的[]byte转换成了字符串类型返回
- 提前申请空间buf.Grow()
空结构体
- 不占用内存空间
- 作为占位符使用
- 用来通过map实现set
atomic包
多线程安全
- 锁通过os实现,系统调用,消耗高
- atomic硬件实现,效率高
性能调优
原则
- 依靠数据而不是猜测
- 定位最大瓶颈,而不是细枝末节
- 不要过早优化
- 不要过度优化
go test
-bench=. # 进行性能测试,“.”是正则匹配,匹配了所有的测试函数
-benchmem # 打印出申请内存的次数。一般用于简单的性能测试,不会导出数据文件。
-blockprofile block.out # 将协程的阻塞数据写入特定的文件(block.out)。如果-c,则写成二进制文件。
-cpuprofile cpu.out # 将协程的CPU使用数据写入特定的文件(cpu.out)。如果-c,则写成二进制文件。
-memprofile mem.out # 将协程的内存申请数据写入特定的文件(mem.out)。如果-c,则写成二进制文件。
-mutexprofile mutex.out # 将协程的互斥数据写入特定的文件(mutex.out)。如果-c,则写成二进制文件。
-trace trace.out # 将执行调用链写入特定文件(trace.out)。
pprof
功能
分析、展示、工具、采样
使用
github.com/wolfogre/go… http://localhost:6060/debug/pprof/ http://localhost:port/debug/pprof/profile,这个地址会收集30秒内服务的运行情况,这个结果会返回一个profile文件
- 有过滤策略
环境配置
学习链接:blog.wolfogre.com/posts/go-pp…
package main
import (
// 略
_ "net/http/pprof" // 会自动注册 handler 到 http server,方便通过 http 接口获取程序运行采样报告
// 略
)
func main() {
// 略
runtime.GOMAXPROCS(1) // 限制 CPU 使用数,避免过载
runtime.SetMutexProfileFraction(1) // 开启对锁调用的跟踪
runtime.SetBlockProfileRate(1) // 开启对阻塞操作的跟踪
go func() {
// 启动一个 http server,注意 pprof 相关的 handler 已经自动注册过了
if err := http.ListenAndServe(":6060", nil); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
// 略
}
cpu
- 采样对象:函数调用和它们占用的时间
- 采样率:100次/s,固定值
- 采样时间:从手动启动到手动结束
go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10" # 默认30s
go tool pprof cpu.out
top命令 展示cpu占用较高的调用
flat:当前函数本身的执行时间
flat%:flat占CPU总时间的比例
sum%:从上往下每一行的flat%总和
cum:当前函数本身加上其调用函数的总耗时
cum%:cum占CPU总时间的比例
list Eat(函数名)命令,产看具体代码位置
web命令
traces 显示栈信息
heap
使用web需要先安装graphviz: windowns:www.graphviz.org/download/
并且设置默认使用浏览器打开.svg文件
- 采样率:512KB依次,可在运行开头修改
- 采样时间:从程序运行开始到采样时
- 采样指标:alloc_space...
- 计算方式:inuse = alloc - free
基本排查
sudo apt install graphviz # 安装graphviz
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap" # 查看堆内存
top视图和source视图
go tool pprof "http://localhost:6060/debug/pprof/heap
top list
排查频繁内存回收
# 程序启动前设置环境变量
go build # 先gobuild
GODEBUG=gctrace=1 ./go-pprof-practice | grep gc # ./go-pprof-practice是二进制文件
# 内存的申请与释放频度需要一段时间统计,保证程序运行了几分钟再运行
go tool pprof http://localhost:6060/debug/pprof/allocs
top list web
tips:golang对象使用堆内存还是栈内存,由编译器进行逃逸分析,若对象不会逃逸(申请的内存较小),便可以使用栈内存,不涉及GC
goroutine
协程可能泄露(协程失控),进而导致内存泄露
- 由上到下表示调用顺序
- 记录所有用户发起且在运行中的goroutine
- 每一块代表一个函数,越长代表占用CPU时间更长
- 火焰图是动态的,支持点击块进行分析
- 支持搜索,在source视图下搜索wolf
- 火焰图的横向长度表示cum,相比下面超出的一截代表flat
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/goroutine"
traces 显示栈信息,找占用最大的一行中最底下的
go tool pprof http://localhost:6060/debug/pprof/goroutine
top list web
锁
- 采样争抢锁的次数和耗时
- 采样率:记录固定比例的锁操作,可以设置为每次都记录
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/mutex"
go tool pprof http://localhost:6060/debug/pprof/mutex
top list web
阻塞block
- 采样阻塞操作的次数和耗时
- 超阈值才记录,可以设置为每次都记录
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/block"
go tool pprof http://localhost:6060/debug/pprof/block
top list web
ThreadCreate
- 记录程序创建的所有系统线程信息
总结
go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10" # 默认30s
top # 查看资源占用函数
list funcName # 展示问题代码片段
tips:heap第二步,用traces查看
业务服务优化
- 建立服务性能评估手段
- 分析性能数据,定位性能瓶颈
- 重点优化项改造
- 优化效果验证
建立服务性能评估手段
- 服务性能评估方式
- 请求流量构造
- 压测范围
- 性能数据采集
分析性能数据,定位性能瓶颈
- 第三方库
- 高并发场景
业务服务优化
工具:Ediff
- 正确性是基础
- 响应数据diff:线上请求数据录制,新旧逻辑接口数据diff
优化压测验证
- 重复压测
进一步,服务整体链路分析
- 规范上游服务调用接口,明确场景需求
- 分析链路,通过业务流程优化提升服务性能
基础库优化
- 分析基础库核心逻辑和性能瓶颈
- 内部压测验证
- 推广业务服务落地验证