这是我参与「第三届青训营 -后端场」笔记创作活动的第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无法满足复杂逻辑分析 不同负载情况下性能表现差异
-
请求流量构造 不同请求参数覆盖逻辑不同
-
线上真实流量情况
-
压测范围 单机器压测 集群压测 性能数据采集 单机性能数据 集群性能数据