这是我参与「第三届青训营 -后端场」笔记创作活动的的第 3 篇笔记
一、本堂课重点内容
1. 高质量编程
- 高质量编程的概念
- golang编码规范
- golang程序性能优化
2. 性能调优实战
- 性能调优简介
- 性能分析工具pprof的使用案例
- 性能调优实战案例
二、详细知识点介绍:
1. 高质量编程
简介
高质量的代码首先具备正确性,需要考虑完备各种边界条件,对代码中可能出现的异常情况需要有相应的处理,具备可靠性,最后代码需要简洁清晰,注释完备,易读易维护
编程原则
1. 简单性:消除代码中多余的复杂性,比如复杂的逻辑嵌套,以简单清晰的逻辑编写代码,不理解的代
无法修复改进
2. 可读性:可维护的代码第一步是确保代码的清晰可读
编码规范
-
代码格式:使用gofmt工具格式化代码,gofmt是官方提供的工具,能够将代码自动格式化为官方统一风格,goimports能够自动增删依赖声明
-
代码注释:注释应当解释代码的作用、代码的实现逻辑、代码实现的原因、代码可能的异常情况
-
命名规范:
变量名:简洁优于冗长,缩略词全部大写(但是位于变量开头且不需要导出使用全小写),如果变量使用的地方与变量声明的地方越远,变量名就要带有越多的上下文信息
包名:只由小写字母组成,不包含大写字母和下划线;简短并包含一定的上下文信息;不与标准库同名;不使用常用变量名作为包名;使用单数而不是复数;在不影响含义的情况下使用缩写
-
控制流程:减少嵌套的使用,在异常处理时对出现异常的地方提前return,是一个函数中每个部分也能呢更清晰 5.错误和异常处理:不建议在业务代码中使用panic,因为panic会直接中止程序的进行,业务异常应当通过error向上层抛出,在最上层进行recover的处理;不多在程序启动阶段发生不可逆转的错误,这样的错误完全影响了程序的正常运行,可以用panic方便更快的定位错误;revocer只能在defer函数中使用,嵌套无法生效且只在当前的协程中生效
性能优化建议
- 性能评估:使用go语言官方提供的benchmark工具:
go test -bench=. -benchmem,执行之后可以得到结果:测试函数名-cpu核数 执行次数 每次执行花费的时间 每次执行申请多大内存 每次执行申请几次内存 - slice预分配内存:尽可能在使用make初始化切片时提供容量信息,这样申请内存次数就减少(和Java中ArrayList初始化指定容量一样)
- 使用copy代替re-slice:在大切片上面进行小切片会保留原有的大切片内存不释放,造成的过多的内存损耗,如果原切片不再使用,只用切片中的一部分则使用copy的方式代替切片上的切片
- map预分配内存:向map中添加元素会触发map的扩容,提前分配好空间可以减少内存拷贝的消耗
- 字符串处理:使用strings.Builder这个结构体代替+进行字符串拼接,使用bytes.Buffer相对strings.Builder更快,这两个底层都是byte数组,具有内存扩容策略,不需要每次拼接重新分配内存
- atomic包:锁的实现是通过操作系统来实现的,属于系统调用,而atomic是通过硬件实现,效率比锁高,sync.Mutex应该用来保护一段逻辑而不仅仅是一个变量
2. pprof采样过程和原理
- CPU:操作系统每10ms向进程发送一次SIGPROF信号,进程每次收到SIGPROF都会记录调用对战,每100ms读取已经记录的调用栈并写入输出流
- 堆内存:采样程序通过内存分配器在堆上分配和释放的内存,记录分配/释放的大小和数量,采样率:每分配512kb记录一次;采样时间:从程序运行开始到采样时间;采样指标:alloc_space,alloc_objects,inuse_space,inuse_object
- 协程:记录所有用户发起且在运行中的goroutine tuntime.main调用栈信息
- 线程:记录程序创建的所有系统线程的信息
- 阻塞:采样阻塞操作的次数和耗时,采样率:阻塞耗时超过阈值才会被记录
- 锁:采样争抢锁的次数和耗时,采样率:只记录固定比例的所操作
3. 业务服务优化
基本概念
服务:能够单独部署,承载一定功能的程序
依赖:ServiceA的功能依赖ServiceB的响应结果,称为ServiceA依赖ServiceB
调用链路:能支持一个接口请求的服务集合以及相互之间的依赖关系
基础库:公共的工具包,中间件
流程
- 建立服务性能评估手段:单独benchmark无法满足复杂的逻辑分析,且不同请求参数覆盖逻辑不同,需要模拟线上真实流量情况,需要用到压测平台来对服务进行压力测试;采集单机性能数据和集性能数据;得到压测报告
- 分析性能数据,定位性能瓶颈:根据压测报告,定位到具体性能瓶颈,如基本库使用方式不当、对高低峰场景的火焰图进行对比查看是否是高并发优化不当
- 重点优化项改造:性能优化过程中正确性是基础,可以将线上的请求数据进行记录,在优化使用记录对新的逻辑进行测试
- 优化效果验证:在优化结束之后,再次进行压测;集合服务整体的链路进行进一步优化
三、实践练习例子:
在浏览器输入目标程序的地址后面接/debug/pprof查看各个性能指标情况
- 通过
go tool pprof 项目的地址/profile来排查CPU占用过高的情况 - 输入
top指令查看占用资源最多的函数,top后面接参数表示查看前几个函数 - 根据结果可知Eat函数占用时间最大,通过
list Eat指令(list表示根据指定的正则表达式查找代码行),查找该函数的代码行具体占用情况 - 通过
web指令可以生成图形化的流程图,需要下载graphviz并配置环境变量 - 重新启动,这次以heap结尾
go tool pprof http://localhost:6060/debug/pprof/heap,排查内存占用情况;同样按照上面的流程可以找到对应的内存占用最高的代码
6. 再以goroutine结尾,查看协程:
go tool pprof http://localhost:6060/debug/pprof/goroutine同样根据top、list、web定位到问题代码处
- 排查锁的争用:
go tool pprof http://localhost:6060/debug/pprof/mutex,同样三段操作
8. 根据block后缀,排查阻塞操作
四、课后个人总结:
- 性能调优是必要的,但这也需要建立在系统成型的基础之上,不需要在搭建系统的时候就时刻考虑性能问题,从而投鼠忌器
- 在调优过程中要定位到性能瓶颈,而不是在无关紧要的细节上花费大量成本