Golang技术札记

全当一个适合自己的golang快捷手册(•̀⌄•́)
                              —— by JiHan


Golang条件编译

可参考:golang交叉编译和条件编译的实际应用

编译test程序

-c 参数就能满足:

1
go test -mod vendor -c <your_test.go>

编译完成后,可以像go test的方式一样使用该可执行程序:

1
go test -mod vendor ipset_test.go -test.run TestDoor

等同于

1
./ipset.test -test.run TestDoor

参考

开源学习

微服务框架:
完善的微服务框架:go-micro 文档 手把手教学文档
提供微服务所需工具集:go-kit 文档
服务框架:go-zero

go mod及依赖包升级

首先我们要清楚,golang对于如何寻找第三方包:

  1. 直接使用go环境变量中的GOPATH变量来作为第三方包存储根目录,第三方包也只能同时存在一个版本。
  2. 使用go mod后,根据go.sum中的依赖在GOMODCACHEvender(如果编译指定vendor依赖)中寻找相应版本的包。

go mod的基本命令:
参考(更多详情)
标准看go help mod

命令 作用
go mod download 下载依赖包到本地(默认为 GOPATH/pkg/mod 目录)
go mod edit 编辑 go.mod 文件
go mod graph 打印模块依赖图
go mod init 初始化当前文件夹,创建 go.mod 文件
go mod tidy 增加缺少的包,删除无用的包
go mod vendor 将依赖复制到 vendor 目录下
go mod verify 校验依赖
go mod why 解释为什么需要依赖

第三方包升级:

  1. 首先获取升级包(默认情况下都是下载到GOMODCACHE路径下)
    1. 可以使用go get 命令进行升级
      go get -u all 将项目中的包升级到最新的次要版本或者修订版本;
      go get -u [包名] 将项目中的包升级到最新的修订版本;
      go get [包名]@[版本号] 下载对应包的指定版本或者将对应包升级到指定的版本。
      go list -m -mod=mod -versions [包名]可以查看该包支持版本
    2. 也可以使用go-mod-upgrade第三方可视化组件升级,参考
      1. go get -u github.com/oligot/go-mod-upgrade 下载该组件
      2. 在需要的项目下执行go-mod-upgrade
      3. 按照提示选择升级
  2. 如果有第三方依赖,需要修改依赖项,可以使用replace进行相应版本包的替换,比如:replace github.com/ozgio/strutil v0.3.0 => github.com/shiweifu/strutil v0.3.0
  3. 执行go mod tidygo mod vendor(如果有必要)

golang性能分析

gin中使用pprof
go性能诊断
不同的路径参数,可以获取不同的性能结果:

  • allocs: A sampling of all past memory allocations
  • block: Stack traces that led to blocking on synchronization primitives
  • cmdline: The command line invocation of the current program
  • goroutine: Stack traces of all current goroutines
  • heap: A sampling of memory allocations of live objects. You can specify the gc GET parameter to run GC before taking the heap sample.
  • mutex: Stack traces of holders of contended mutexes
  • profile: CPU profile. You can specify the duration in the seconds GET parameter. After you get the profile file, use the go tool pprof command to investigate the profile.
  • threadcreate: Stack traces that led to the creation of new OS threads
  • trace: A trace of execution of the current program. You can specify the duration in the seconds GET parameter. After you get the trace file, use the go tool trace command to investigate the trace.

看性能:go tool pprof --seconds 60 http://127.0.0.1:45/debug/pprof/profile
查阅结果:

  1. 安装graphvizyum install graphviz
  2. 执行:go tool pprof -http 0.0.0.0:3001 file
  3. 在Chrome浏览器访问:http://host:port
  4. 生成火焰图:
    1. 下载工具:go install github.com/uber/go-torch@latest
    2. 输出火焰图(默认生成torch.svg):go-torch <file>

业务跟踪:curl http://127.0.0.1:45/debug/pprof/trace?seconds=30 > /data/trace.out
查阅结果:

  1. go tool trace -http 0.0.0.0:3001 trace.out

go-micro学习

手把手教学文档

生成proto相关代码

这里有一个路径问题,那么执行的命令要做适当修改:
protoc --proto_path=. --go_opt=Mproto/greeter.proto=./proto --micro_out=. --go_out=. proto/greeter.proto
有关 --go_opt参数以及protoc-gen-go: unable to determine Go import path for "proto/greeter.proto"问题处理方法

字符串拼接

1
2
3
4
5
6
7
8
9
10
11
isFirst := true
var b strings.Builder
for k, _ := range oneMap {
if !isFirst {
b.WriteString(",")
} else {
isFirst = false
}
b.WriteString(k.String())
}
idsStr := b.String()

有关panic

go在触发panic的时候,程序退出码和C程序退出码不一致。golang遇到panic是自己内部触发的,并非接收到内核信号,退出码是2。而c语言触发段错误,内核会发送一个信号给该进程SIGSEGV,然后程序退出,退出码是139

有关golang内存泄漏

go内存泄漏分类:

golang内存也存在泄漏,也就是没有按照想要的方式释放。一般分为以下几个方面:

  1. goroutine泄漏,即有些协程一直阻塞,无法得到释放,导致里面的变量无法释放,从而引起内存泄漏。比如:
    1. goroutine过多
    2. goroutine阻塞
      1. IO阻塞
      2. channel阻塞
      3. select阻塞
      4. 互斥锁,或信号量阻塞
  2. 代码内存泄漏,代码中申请了一些内存,没有及时释放,或者被其他引用导致内存不断增加而泄漏。比如:
    1. time.After中函数延迟过大,这个After等到结束后才会释放
    2. time.ticker 未stop
    3. slice被全局变量引用导致不能释放。
  3. GC释放问题,在一些低版本中如1.12-1.15版本,gc采用了一些惰性回收机制,要等到内核压力大时才进行回收和释放。不过在1.16后的版本,gc基本比较稳定了。

排查工具和方法:

  1. 使用pprof工具,一个很强的工具,能排查各种阻塞,协程异常,内存异常等问题。通常使用这一个工具就足够了。使用指南
  2. valgrind进一步分析内存溢出问题,(这个工具对c和c++好使,在go上没有试过)。官方文档

pprof对性能影响:

通过stackoverflow的文章以及引用的文章,可以得到一个待验证的观点:每分钟开启10s的pprof的性能分析,也会导致5%的额外开销。
另外一个讨论组的观点是:脱离具体程序谈性能影响是不切实际的。使用性能分析工具进行问题排查是可靠安全的,如果担心性能影响,可只针对部分业务进行分析(类似集群,只分析一台)。

关于golang的版本特性:

版本 功能 备注
Go 1.5 垃圾收集器优化 并发收集
内部包支持 内部引用的库放在internal文件夹下,外部无法引用
GOMAXPROCS=可用核心数 意味着大部分情况下无需手动设置GOMAXPROCS,旧版本GOMAXPROCS=1
go tool trace命令 支持细粒度的程序执行跟踪
go doc命令 1.13被移除,需要单独下载安装
Go 1.6 支持HTTP/2协议 只要我们使用TLS则会默认启动HTTP/2特性
Go 1.7 Context库和vendor支持优化 context成为重要的控制流、上下文传递工具
Go 1.8 垃圾回收器进一步优化 延迟时间全面降到毫秒级别以下
Go 1.9 type alias支持 当你使用type T2 T1的时候需要考虑是不是使用 type T2 = T1更好
Test Helper函数 新加(T).Helper(B).Helper m, 用来标记调用的函数是一个测试辅助函数
Go 1.10 go build/test增加缓存优化 加速构建/测试性能,当你使用 容器进行构建/测试时,如果效率较低,考虑复用缓存
Go 1.11 引入Go modules 从此go mod逐渐成为主流包管理方式
Go 1.12 go vet工具 go tool vet不再支持
Go 1.13 sync.Pool优化 垃圾回收时,pool中对象不会被完全清理掉。它引入了一个cache,用于在两次GC之前清理pool中未使用的对象实例
defer性能优化 性能提高 30%
新的逃逸分析(escape analysis)器 分析代码,何时分配到stack而不是heap
errors包优化 支持wrappingfmt.Errorf增加%w格式符,errors包增加三个函数(Unwrap、Is、As),很实用
Go modules成为默认值 Go 1.13GOPROXYGOSUMDB都会有默认值
Go 1.14 defer性能再次优化 Go1.14提高了defer的大多数用法的性能,几乎0开销。defer已经可以用于对性能要求很高的场景了
time.Timer性能提升 针对timer性能问题的很多优化不再有必要了
允许嵌入具有重叠方法集的接口 type ReadWriteCloser interface { io.ReadCloser;io.WriteCloser}不会报错
testing包的T、B和TB都加上了CleanUp方法 类似defer,清理测试申请资源
引入基于信号的的异步抢占机制 死循环的goroutine能够被抢占了,不过代价是出现死循环导致的性能下降问题更难排查了
更高效的页分配器 页分配器效率变高,并且在GOMAXPROCS值较高时,导致的锁争用显着减少
Go 1.15 链接器的重大改进,可减少链接器资源的使用 全新链接器,代码健壮性可维护性,开销都有改进
tzdata包 该程序包允许将时区数据库嵌入程序中
Go 1.16 添加了对macOS ARM64的支持 也称为Apple芯片
开始禁止import导入的模块以.开头 import "./tools/image"将不合法
默认使用go mod进行管理 GO111MODULE环境变量现在默认为on
新增embed embed包 提供了对使用new//go:embed指令在编译时访问嵌入在程序中的文件的功能
go installgo get的功能分离 go get不再支持安装,默认就使用-d参数
Go 1.17 简化go mod的依赖图 完整module依赖图->修剪的module依赖图
从基于堆栈的调用惯例到基于寄存器的调用惯例的切换 主要基于arm64架构下,性能得到较大提升
支持切片指针到数组指针的强制转换 减少内存拷贝,以及对切片的断言
更易读的构建约束 //go:build->// +build
Go 1.18 泛型Generics支持 引入了对使用参数化类型的泛型代码的新支持, 达到了算法可复用的目的
模糊测试Fuzzing 提供了一种自动化测试的选择, Go 是第一个将模糊测试完全集成到其标准工具链中的主要语言
Workspaces 解决go mod遗留下来的本地多模块开发依赖问题
Go 1.19 泛型问题fix 更稳定的泛型支持,个人建议正式环境还是观望一波
文档优化 doc comment文档优化
runtime.SetMemoryLimit 一个新的runtime.SetMemoryLimit函数以及一个GOMEMLIMIT环境变量被引入。有了这个memory软限制,Go运行时将通过限制堆的大小,以及更积极地将内存返回给底层os,来试图维持这个内存限制,以尽量避免Go程序因分配heap过多,超出系统内存资源限制而被kill
启动时将默认提高打开文件的限值 就是linux常见的open files
race detector性能提升 race检测性能相对于上一版将提升1.5倍-2倍,内存开销减半,并且没有对goroutine的数量的上限限制
编译约束增加unix标签
其他标准库变化 如:net软件包将使用EDNSflag包增加TextVar函数等
-------------本文结束感谢您的阅读-------------