高质量编程与性能调优实战(三)|青训营笔记

高质量编程与性能调优实战(三)|青训营笔记

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

本章目录:

image-20220601134443420

这一节更偏向于文档,没有代码,这里就可以看PPT理解。

高质量编程

高质量编程简介

什么是高质量? --编写的代码能够达到正确可靠、简洁清晰的代码是高质量代码。

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

编程原则

image-20220601135008968

​ --Go语言开发者 Dave Cheney

编码规范

如何编写高质量的Go代码

代码格式

image-20220601135446657

通过使用统一的管理工具,来让所有人的代码看起来一样。

注释

注释应该做的:

  • 注释应该解释代码作用
    image-20220601140518828

  • 注释应该解释代码如何做的

    在代码块儿上写注释。

    image-20220601140532704

  • 注释应该解释代码实现的原因

    有些东西脱离上下文会很难理解,因此要加上上下文和外部因素。
    image-20220601140430265

  • 注释应该解释代码什么情况会出错

    image-20220601140642446

  • 公共符号始终要注释
    image-20220601140732002

    反例:
    image-20220601140804250

好的代码有很多注释,坏代码需要很多注释。

命名规范

  • variable变量
    image-20220601141044634
  • function函数
    image-20220601141644738
  • package包
    image-20220601141718253

核心目标是降低阅读理解代码的成本;重点考虑上下文信息,设计简洁清晰的名称。

好的命名就像一个好笑话,如果你必须解释它,那就不好笑了。

控制流程

  • 避免嵌套,保持正常流程清晰。
    image-20220601142132815
    如果两个分支中都有return语句,则可以去除冗余的else。
  • 尽量保持正常代码路径为最小缩进
    image-20220601142303293
    这里嵌套太多,逻辑不清晰了,调整后变成这样。
    image-20220601144233271

控制流程小结:

  • 线性原理,处理逻辑尽量走直线,避免复杂的嵌套分支。
  • 正常流程代码沿着屏幕向下移动。
  • 提升代码可维护性和可读性。
  • 故障问题大多出现在复杂的条件语句和循环语句中。

错误和异常处理

  • 简单错误

    • 简单的错误指的是仅出现一次的错误,且在其他地方不需要捕获该错误

    • 优先使用errors.New来创建匿名变量来直接表示简单错误

    • 如果有格式化的需求,使用fmt.Errorf

      image-20220601144816730

  • 错误的Wrap和Unwrap
    image-20220601144952295

  • 错误判定

    errors.is

    用来替代==
    image-20220601145015394

    errors.As
    可以看到错误信息。

    image-20220601145108706

  • panic

    无法处理的错误,程序直接终止。
    image-20220601145133964

  • recover
    当引用别人的包出现panic时,避免影响到自己的逻辑
    image-20220601145339274

    image-20220601145418075

错误和异常处理小结:

image-20220601145447198

性能优化建议

Benchmark

这里的示例建议下载代码跟着走。

  • 代码运行
    我们要对strat文件夹下的fib.go进行基准测试,可以把终端切换到fib.go目录下,使用命令

    go test -bench=. -benchmem
    

    image-20220601152440855
    也可以通过运行fib_test.go,里面引入了testing包,可以相当于运行了上面的命令。

    package start
    
    import "testing"
    
    // BenchmarkFib10 run 'go test -bench=. -benchmem' to get the benchmark result
    //这里就说明testing包可以替代语句的意思
    func BenchmarkFib10(b *testing.B) {
    	// run the Fib function b.N times
    	for n := 0; n < b.N; n++ {
    		Fib(10)
    	}
    }
    
  • 结果说明

    image-20220601153702944

Slice

  • Slice 预分配内存
    image-20220601154019460
    image-20220601154404062

    slice有点像java里的ArrayList,当容量不够的时候会自行进行扩容,如果cap容量够的话,直接加就行了。
    扩容也是比较耗费时间的。

  • 另一个陷阱:大内容未释放
    image-20220601155018863
    不要用[:2],而是用copy复制。
    这种test的不知道为啥,得用命令去测试,也是切换到路径下直接跑就行,没有左边的对勾可以点。

  • map 预分配内存
    image-20220601155511657
    image-20220601155555026

字符串处理

使用strings.Builder

直接字符相加和builder的对比。
image-20220601155727176
+号 buffer builder对比

image-20220601155749578
image-20220601155821398
底层分析,buffer多申请了一次内存,builder是unsafe的,少申请一次内存。
image-20220601160146055

对buffer和builder进行预分配

image-20220601160244699

空结构体

使用空结构体节省内存
image-20220601160428193

对两种不同的map空间大小比较,int:空结构体和int:boolean

image-20220601160538412

atomic包

如何使用atomic包
image-20220601160639302

对变量的加原子锁,要比正常的锁好很多。
image-20220601160730492

小结

image-20220601160926561

性能调优实战

性能调优简介

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

性能分析工具pprof实战

这里要先安装graphviz-2.44.1-win32软件,不然后面的图像无法使用。

功能简介

image-20220601162320262

搭建pprof实践项目

运行程序

这里需要提前下载对应的代码,链接在图中。

image-20220601162447773

然后go run main.go,程序就跑起来了。

接下来就可以在浏览器中查看指标
image-20220601162952743
需要在网址中输入对应的链接,http://localhost:6060/debug/pprof/

里面的链接具体是什么内容在下面有英文的介绍,可以点进去看一看,会发现数据平铺出来很难直观的去观察。
image-20220601163111718

CPU占用情况

image-20220601163400421
在任务资源管理器中可以看到这个main.exe占用了相当多的CPU和内存。

启动完pprof的示例项目后,在命令行输入 以下命令可以查看CPU的占用

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

命令行会有如下产生:
image-20220601163900274

输入top会得到查看占用资源最多的函数:
image-20220601164800094
可以看到Tiger.eat函数消耗了最多的资源。表头代表的含义如下:image-20220601164716225

Flat0代表直接调用了别的函数,自己啥也没有。
Flat
Cum代表没有调用别的函数,全是自己消耗的。

可以使用list命令,根据指定的正则表达式查找代码行。
因为我们是Tiger的Eat函数占用的多,所以我们使用list Eat命令
image-20220601180444154
因此可以找到了CPU消耗问题最大的代码。

有的时候命令行不够直观,这个时候,我们可以使用web命令
image-20220601180923772
其会打开一个页面,发现这个吃了很多资源的函数。

我们将其注释掉后,重新运行程序并在资源管理器中查看:
image-20220601181343408
发现CPU的占用问题已经解决,而内存还需要解决。

Heap-堆内存

占用了两个多G的内存,刚才我们使用的是pprof终端的方式,不是很明确,这时我们可以通过页面查看。而且里面的内容都是和内存占用相关的,因为最后的后缀是heap。

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

其中http就是要新打开的端口,执行命令后会弹出页面:
image-20220601181901169
image-20220601182003251
VIEW下面的按钮和终端是类似的,可以直观的查询各种数据。

TOP视图:image-20220601182310212

Source视图:
image-20220601182401710

在将Mouse中的多余代码注释后,在任务管理器中可以看到内存占用恢复正常:
image-20220601182639031

可以在最右侧发现目前是Unkown inuse_space。
image-20220601183106680
我们可以通过点击SAMPLE切换到Unkown alloc_space,从而发现新的问题。
image-20220601183136587
切换到Souce可以找到对应位置的代码:
image-20220601183251703
发现每次会申请一个16M的内存

goroutine-协程

goroutine泄露也会导致内存泄露。
image-20220601183649866
我们可以使用命令:

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

来查看协程情况:
image-20220601184048041
这种图过于长了,对于协程来说,我们可以通过火焰图来查看,即点击VIEW,选中Flame Graph:
image-20220601184222218
火焰图的特性:
image-20220601184304471
鼠标悬浮上去发现,wolf占用了93.46%,切换到Source页面并在搜索框搜索wolf:
image-20220601184511004
定位到问题所在,把这行代码注释掉,再看,
image-20220601184744628
此时火焰图中的长度是比较正常的,没有那种协程大量占用CPU的情况。

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

image-20220601185525460
image-20220601185605075
将对应代码注释后,直接没了,因为没有锁的情况了。

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

image-20220601190226063

后面好像是删了哪里了,复现不了了。。。大致已经了解怎么做了,记得每次去6060的页面查看还有多少需要修改。
image-20220601190514315
image-20220601190556335
发现是http的那些,不用管了。

小结

image-20220601190619198

pprof-采样过程和原理

image-20220601191932370

image-20220601191942778
image-20220601191951616
image-20220601191955582
image-20220601192011233
image-20220601192020438

性能调优案例

介绍实际业务服务性能优化的案例;对逻辑相对复杂的程序如何进行性能调优。
概念介绍:
image-20220601213110680
从客户端和手机端发送请求到网关,网关将请求交给服务。假设ServiceD就是日志记录的库,就是基础库。

  • 业务服务优化
    面向用户提供的一些功能,点赞评论之类的。
    流程分四步:

    1. 建立服务性能评估手段
      image-20220601213645060
      右上角的图:在集群CPU利用率逐渐上升的时候,流量承载能力可能会逐渐到达瓶颈,这个时候可能会需要一些优化之类的。
      右下角是一个线上的项目,火焰图可以点,点开看这个节点的子节点情况,很适合线上项目分析。

      image-20220601214420076

      压测会产生一个压测报告,后面压力升上去后会出现波动,说明需要优化。

    2. 分析性能数据,定位性能瓶颈
      image-20220601215256619
      这个火焰图可以看到某个点占用CPU时间很长,定位到代码发现是json解析每次都做,其实是可以变更的时候再做,这样会优化很多。

      image-20220601215418185
      日志如果打印的很多也会出现CPU过高,可以在pprof搜索log来标记那些用了日志服务,占了多久CPU。

      image-20220601215727344
      CPU占用率80%和40%各记录一次火焰图,可以找到一些高峰期中性能优化不好的地方,可以看到在标记出来的位置,占用CPU时间显著变长。
      原因:matrix库,再上报的时候一直在用同步请求上报,CPU占用率高的时候,打点就比较多,容易触发性能瓶颈,造成阻塞,影响业务处理。
      处理方式:因为监控上报并不是一个实时性要求特别高的场景,其实也提供了异步上报的机制,改成异步后就好了。

    3. 重点优化项改造
      image-20220601220529366
      正确性是基础,因此需要一些工具辅助我们进行判断,是否正确。
      方式:把线上的流量请求和返回值都录制下来,完成性能优化后,看相同的服务是否能获得同样的结果。(类似于单元测试hhh)

    4. 优化效果验证
      image-20220601220541625

    5. 进一步优化,服务整体链路分析
      image-20220601220710874
      在熟悉了上下游后,可以进一步优化整个服务。
      比如在Service A调用Service B的时候,如果有更小的输入集满足Service B的需要,那么这两者之间网络传输的消耗就会更小,就可以获得更快的响应。
      比如Service A是否是实时的需要Service B的数据,是不是可以加一个缓冲,缓解压力。
      因为A的逻辑比较复杂,可以检查是否多次调用了服务B,这一点也可以通过火焰图来观察,如果在不同位置重复调用了,那对A和B都带来了不必要的压力,可以优化。
      这种调用需要对完整的链路进行分析,对具体的场景需要分析。

  • 基础库优化
    监控打点、日志之类的,不会独立的进行部署。
    image-20220601221442547
    如果程序调用到AB实验的代码,会有记录,可以优化。
    右下角就是优化前后的上线后情况。
    如果覆盖率上去的话,节省资源还是挺多的。

  • Go语言优化
    对Go语言本身的优化。

    image-20220601223053359

总结

image-20220601223133621

posted @ 2022-06-01 22:33  杀戒之声  阅读(57)  评论(0编辑  收藏  举报