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

132 阅读4分钟

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

ppt链接高质量编程与性能调优实战

错误和异常处理

简单错误
  • 简单的错误是指仅出现一次的错误,切在其他地方不需要捕获该错误
  • 优先使用errors.New来创建匿名变量来直接表示简单错误
  • 如果有格式化的需求,使用fmt.Errorf
错误的Wrap和Unwrap
  • 错误的Wrap实际上是提供了一个error嵌套另一个error的能力,从而生成了一个error的跟踪链
  • 在fmt.Errorf中使用:%w关键字来将一个错误关联至错误链中
list,_,err:=c,GetBytes(cache.Subkey(a.actionID,"srcfiles"))
if err!=nil{
    return fmt.errorf("reading srcfiles list :%w",err)
}
panic
  • 不建议在业务代码中使用panic
  • 调用函数不包含recover回早储层程序崩溃
  • 若问题可以被屏蔽或解决,建议使用error代替panic
  • 当程序启动阶段发生不可逆转的错误时,可以在init或main函数中使用panic
recover
  • recover只能在被defer的函数中使用
  • 嵌套无法生效
  • 只能在goroutine生效
  • defer的语句是后进先出
  • 如果需要更多的上下文信息,可以在recover后在log中记录当前的调用栈

错误与异常的处理

  • error尽可能提供简明的上下文信息链,方便定位问题
  • panic 用于真正异常的情况
  • recover 生效范围,在当前goroutine的被defer的函数中生效

性能分析工具pprof

golang pprof 实战 | Wolfogre's Blog

PProf 关注的模块

  • CPU profile:报告程序的 CPU 使用情况,按照一定频率去采集应用程序在 CPU 和寄存器上面的数据
  • Memory Profile(Heap Profile):报告程序的内存使用情况
  • Block Profiling:报告 goroutines 不在运行状态的情况,可以用来分析和查找死锁等性能瓶颈
  • Goroutine Profiling:报告 goroutines 的使用情况,有哪些 goroutine,它们的调用关系是怎样的 两个包中引入PProf
import "net/http/pprof"
import "runtime/pprof"

其中 net/http/pprof 使用 runtime/pprof 包来进行封装,并在 http 端口上暴露出来。runtime/pprof 可以用来产生 dump 文件,再使用 Go Tool PProf 来分析这运行日志。

使用 net/http/pprof 可以做到直接看到当前 web 服务的状态,包括 CPU 占用情况和内存使用情况等。

如果服务是一直运行的,如 web 应用,可以很方便的使用第一种方式 import "net/http/pprof"

image.png

package main

import (
   "log"
   "net/http"
   "sync"
   
   _ "net/http/pprof" // 会自动注册 handler 到 http server,方便通过 http 接口获取程序运行采样报告
   "runtime"
   // 略
)
func main() {
   // 略

   runtime.GOMAXPROCS(1)              // 限制 CPU 使用数,避免过载
   runtime.SetMutexProfileFraction(1) // 开启对锁调用的跟踪
   runtime.SetBlockProfileRate(1)     // 开启对阻塞操作的跟踪
   wg := sync.WaitGroup{}
   wg.Add(1)
   go func() {
      // 启动一个 http server,注意 pprof 相关的 handler 已经自动注册过了
      if err := http.ListenAndServe(":6060", nil); err != nil {
         log.Fatal(err)
      }
      wg.Done()
   }()
   wg.Wait()
   // 略
}

对github上的代码进行了简单的修改,源代码中使用了协程,协程是否运行结束本来是和主线程无关的,所以会导致协程还没运行完主线程结束退出。所以利用等待队列sync.WaitGroup

  1. 执行命令go tool pprof http://localhost:6060/debug/pprof/profile进入交互界面

image.png 2.执行top命令,查看CPU占用较高的调用

image.png 各指标含义

  • flat:当前函数本身的执行耗时
  • flat%:flat占CPU总时间的比例
  • sum%:上面每一行的flat%总和
  • cum:指当前函数本身加上其调用函数的总耗时
  • cum%:cum占CPU总时间的比例 3.list命令

注:list Eat中Eat时正则表达式 这里认为是Tiger结构体的一个方法 根据指定的正则表达式查找代码行

image.png 找出占用CPU 8.14S的代码行 4.web命令

简述Go搭建http服务器

package main

import (
   "fmt"
   "net/http"
)

func HelloHandler(w http.ResponseWriter, r *http.Request) {
   fmt.Fprintf(w, "Hello World")
}

func main() {
   http.HandleFunc("/", HelloHandler)
   http.ListenAndServe(":8000", nil)
}

运行代码之后,在浏览器中打开localhost:8000就可以看到Hello World。这段代码先利用http.HandleFunc在根路由/上注册了一个HelloHandler, 然后利用http.ListenAndServe启动服务器并监听本地的 8000 端口。当有请求过来时,则根据路由执行对应的handler函数。

详细请参考:

深入学习用 Go 编写 HTTP 服务器_kevin_tech的博客-CSDN博客

注册路由
import中导包

golang的import表示导入包,其中. 和 _ 分别有特殊含义。 .表示导入以后,该包的函数和变量不需要再直接写入包名称。 的作用就更特殊。当导入一个包的时候,该包的init和其他函数都会被导入;但不是所有函数都需要导 入,这个时候.,可以只用""符号入init,而不需要导入其他函数。 关于包 runtime

go标准库的学习-runtime