0x04高质量编程与性能调优实战| 青训营笔记

217 阅读3分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第4篇笔记


高质量编程

编写的代码能够达到正确可靠、简洁清晰的目标可称之为高质量代码

  • 各种边界条件是否考虑完备
  • 异常情况处理,稳定性保证
  • 易读易维护

编程原则

  • 简单性
    • 消除“多余的复杂性”,以简单清晰的逻辑编写代码
    • 不理解的代码无法修复改进(看不懂)
  • 可读性
    • 代码是写给人看的
    • 编写可维护代码的第一步是确保代码可读
  • 生产力
    • 团队整体工作效率(团队看代码、加速测试等等链路的时间,提高整体开发时间)

编码规范

代码格式

使用格式化工具

  • gofmt——管理码风
  • goimports——管理包

注释

注释应该:

  • 解释代码的作用
  • 解释代码是如何做的
  • 解释代码实现的原因
  • 解释代码什么情况会出错

命名规范

  • varible:简洁、对于不影响上下文的变量名可以省略、对于缩略词可以大写(不暴露给外部的名字开头是缩略词可以小写)、尽量不采用特定含义的名字在一般意义的变量中
  • function:不携带上下文信息;函数名尽量简短;当包与返回类型一致则省略类型信息,否则加入类型信息(http.Serve(HTTP))
    • package:小写字母;简短且包含上下文信息;使用单数;谨慎使用缩写;不用常用变量名做包名,防止歧义

控制流程

流程控制避免嵌套

image-20220511160829548.png

优先处理错误/特殊情况,尽早返回或继续循环来减少嵌套

func Func() error{
    err := doSomthing()
    if err == nil {
        err := doAnotherThing()
        if err == nil{
            return nil
        }
        return err
    }
    return err
}

这样的代码是不好的,嵌套判断显得非常乱且臃肿

func Func() error{
    if err := doSomeThing(); err != nil {
        return err
    }
    if err := doAnotherThing(); err != nil {
        
    }
    return nil;
}

image-20220511193629462.png

  • 线性原理,处理逻辑尽量走直线
  • 避免复杂的嵌套分支

错误与异常处理

简单错误

是只出现一次的多无,且在其他地方不需要捕获该错误

优先使用errors.New来直接表示简单错误,格式化需求用fmt.Errorf

image-20220511193941016.png

错误的wrap和unwrap

把错误打包和解包,形成错误跟踪链,携带上下文信息,更好的排查错误

image-20220511194153560.png

错误判定

判断错误链上是否包含某个错误errors.Is

image-20220511194310133.png

获取特定种类的错误,使用errors.As

image-20220511194346677.png

panic

image-20220511194515713.png

recover

image-20220511194638187.png

image-20220511194700007.png

性能优化

Benchmark

image-20220511201606221.png

func BenchmarkFib(b *testing.B) {
	for n := 0; n < b.N; n++ {
		Fib(10)
	}
}

image-20220511202814490.png

Slice预分配内存

尽可能的在使用make()初始化切片时提供容量信息

提前分配可以有效的减少内存分配次数,可以加快时间,避免额外的分配

image-20220511203203586.png

对于slice的切片

image-20220511203849064.png

map预分配内存

如果有预分配的话,性能会很好

image-20220511203923811.png 提前分配好空间可以减少内存拷贝和Rehash的消耗

字符串处理

strings.Builder

如果使用string来+拼接字符串,相当于创建新的string重新赋值,string不可变,会涉及到重新分配内存

image-20220511204043354.png

BuilderBuffer底层都是[]byte数组,有内存扩容策略,不需要每次拼接重新分配内存

如果还想更快,同样的Builder也有预分配的情况``

image-20220511204531878

使用空结构体实现set

m := make(map[int]struct{})

m[i] = struct{}{}

先比值设为bool来说,减少内存,空结构体仅作为占位符使用

m := make(map[int]bool)

m[i] = false

使用atomic来实现多线程锁

使用这个包来实现原子的计数,同样加锁也可以

image-20220511205254776.png

  • 锁的实现是通过操作系统来实现,属于系统调用,atomic 操作是通过硬件实现的,效率比锁高很多
  • sync.Mutex 应该用来保护一段逻辑,不仅仅用于保护一个变量(使用atomic)
  • 对于非数值系列,可以使用 atomic.Value,atomic.Value 能承载一个 interface{}

实战

原则:

  • 靠数据而不是猜测
  • 定位最大瓶颈而不是细枝末节
  • 不要过早优化
  • 不要过度优化

pprof

  • pprof 是用于可视化和分析性能分析数据的工具
  • 可以知道应用在什么地方耗费了多少 CPU、memory 等运行指标

1.jpg

使用浏览器查看指标

http://localhost:6060/debug/pprof/

image-20220511213503118.png

image-20220511213536495.png

CPU

image-20220511213733069.png 将采集十秒的数据,汇总到文件输出

go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10"

image-20220511214658443.png

接着输入top命令,来查看占用资源最多的函数

image-20220511220306655.png

  • flat 当前函数本身的执行耗时
  • flat% flat占cpu总时间的比例
  • sum% 上面每一行的flat%总和
  • cum 当前函数本身加上其调用函数的总耗时
  • cum% cum站cpu总时间的比例

我们发现go-pprof-practice/animal/felidae/tiger.(*Tiger).Eat这个函数是最占用的

image-20220511220659137.png

  1. 函数中没调用其他函数
  2. 函数中只有其他函数的调用

list命令,根据指定的正则表达式查找代码行

image-20220511221151425.png


web命令,调用关系可视化

go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/cpu"

这个会更加可视化一点

heap堆内存

如果查看内存,我们可以用上面的命令改成heap就好了

go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap"

image-20220511222804662.png

网页端也可以更直观

image-20220511222851489.png

sample展示对于不同的指标所得到的结果不一定一样

image-20220511223117587.png

image-20220511223122898.png

image-20220511223147992.png

goruntine协程

go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/goroutine"

image-20220511223257361.png

火焰图详解

image-20220511223328760.png

从上到下表示调用的顺序

每一个小块代表一个函数,长度代表占用cpu的时间

也是类似的

go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/mutex"

阻塞问题

go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/block"

服务调优

服务:能单独部署,承载一定功能的程序

依赖:Service A 的功能实现依赖 Service B 的响应结果,称为 Service A 依赖 Service B

调用链路:能支持一个接口请求的相关服务集合及其相互之间的依赖关系

基础库:公共的工具包、中间件

业务服务优化

image-20220511230713575.png

image-20220511230831274.png

image-20220511231000727.png

image-20220511231206028.png

当修改落实后一般不要立即上传,需要先保证修改的正确性,可以通过录制修改前的数据来对新程序的回放来比较结果,结果一致才能上传(差的不多)

image-20220511231337003.png

image-20220511231612815.png

基础库优化

image-20220511231915343.png

image-20220511231915343.png

go语言优化

比如更新go的sdk

image-20220511232048207.png

image-20220511232048207.png