这是我参与「第三届青训营 -后端场」笔记创作活动的的第4篇笔记
GO-学习笔记
编码规范
- 简洁、清晰、稳定
- 简单性、可读性、生产力
- 有效注释,解释代码功能,提供额外的上下文
- 代码格式
gofmt,格式化工具goimports增删依赖包
- 命名规范
- 缩略词全部大写
- 重要变量名称携带上下文信息
- 函数方法命名不与包名重复,避免冗余
- 包名只用小写,不与标准库冲突,不用变量名和复数
- 控制流程,避免嵌套,去掉冗余的
else - 错误提示,表明原因
errors.New()fmt.Errorf("%w", err)嵌套错误输出- 业务代码不使用
panic,用于真正异常的情况recover机制,在defer函数中使用,避免嵌套,打印错误上下文
性能调优
- 避免常见性能陷阱
性能基准测试
benchmark工具
go test -bench=. -benchmem
- 方法/执行次数/每次执行时间/每次执行申请内存大小/每次执行申请几次内存
集合指定容量
slice和map使用时,提前给一个容量
func NoPreAlloc(size int) {
data := make([]int, 0)
for i := 0; i < size; i++ {
data = append(data, i)
}
}
func PreAlloc(size int) {
data := make([]int, size)
for i := 0; i < size; i++ {
data = append(data, i)
}
}
字符串拼接测试
go中字符串是不可变,直接string拼接,都会创建一个新的字符串;strings.Builder和bytes.Buffer底层维护了bytes数组,内部维护了扩容的策略,不用每次都创建
func Plus(n int, str string) string {
s := ""
for i := 0; i < n; i++ {
s += str
}
return s
}
func StrBuilder(n int, str string) string {
var builder strings.Builder
for i := 0; i < n; i++ {
builder.WriteString(str)
}
return builder.String()
}
func ByteBuffer(n int, str string) string {
buf := new(bytes.Buffer)
for i := 0; i < n; i++ {
buf.WriteString(str)
}
return buf.String()
}
buffer转字符串,申请了空间
func (b *Buffer) String() string {
if b == nil {
// Special case, useful in debugging.
return "<nil>"
}
return string(b.buf[b.off:])
}
builder转字符串,直接将数组转化为string
func (b *Builder) String() string {
return *(*string)(unsafe.Pointer(&b.buf))
}
Grow预分配大小
- 预分配效率更高
func PreStrBuilder(n int, str string) string {
var builder strings.Builder
builder.Grow(n * len(str))
for i := 0; i < n; i++ {
builder.WriteString(str)
}
return builder.String()
}
func PreByteBuffer(n int, str string) string {
buf := new(bytes.Buffer)
buf.Grow(n * len(str))
for i := 0; i < n; i++ {
buf.WriteString(str)
}
return buf.String()
}
空结构体
- 不占内存空间,仅作为占位符,增强语义
- 结合
map,利用结构体,实现set
atomic
- 多线程下避免使用
sync.Mutex上锁,属于系统调用 atomic维护变量
type atomicCounter struct {
i int32
}
func AtomicAddOne(c *atomicCounter) {
atomic.AddInt32(&c.i, 1)
}
性能调优实战
- 定位最大的瓶颈
- 定位什么地方耗费了多少CPU和内存
PProf
- 默认端口
6060
CPU
- 采集10秒的运行结果,并进行
pprof控制台查看
go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10"
top查看CPU占比较多的函数
flat函数执行的时间,flat == 0当前函数只调用其他函数flat%占CPU总时间的比例sum%向上累加cum当前函数及调用函数的总耗时,flag == cum当前函数没有调用其他函数cum%占CPU总时间的比例
list Eat根据正则表达式查看某个函数的执行情况web以图的形式展示
原理
- 采样对象:函数调用和占用的时间
- 采样率:100次/秒
- 采样时间:从手动启动到结束
- 开始采样时,设置信号处理函数,开启定时器;停止采样时,取消信号处理函数,关闭定时器
内存
- 查看内存情况并以网页的形式展开
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap"
top形式source形式,类似list- 采样类型
alloc_objects累计申请对象数alloc_space累计申请空间大小inuse_objects当前持有对象数inuse_space当前占用内存大小
原理
- 通过内存分配器在堆上分配和释放的内存,记录分配/释放的大小和数量
- 采样率:每分配512KB记录一次
- 采样时间:从开始到结束
- 计算方式:inuse = alloc - free
协程
- 记录所有用户发起且运行中的堆栈调用信息
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/goroutine"flame graph火焰图
原理
- STW,遍历
allg切片,输出创建goroutine的堆栈
线程
- 记录程序创建的所有系统线程的信息
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/threadcreate"
原理
- STW,遍历
allm链表,输出创建线程的堆栈
锁操作
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/mutex"
原理
- 采样争抢锁的次数和耗时
- 采样率:只记录固定比例的锁操作
- 锁竞争时,上报调用栈和消耗时间,是否在当前比例之内
阻塞
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/block"
原理
- 采用阻塞操作的次数和耗时
- 采样率:阻塞耗时超过阈值才会被记录
案例
- 使用库的规范
- 高并发场景的检测
- 正确性是基础,记录请求和响应,保证幂等性
- 上线后,逐步放量,持续收集性能数据
- go语言的本身优化,运行时参数,代码编译流程
评估手段
- 性能评估,不同负载下的性能表现
- 请求流量构造,模仿线上真实情况
- 压测范围,单机和集群压测
- 性能数据采集,单机和集群性能数据