[Go语言工程实践|青训营笔记]

45 阅读5分钟

1.语言进阶 Goroutine Go语言的特点是高性能、高并发,可以充分发挥多核优势,高效运行。主要原因在于Go语言引入了Goroutine(协程)的概念。Goroutine是轻量级的纯用户态线程,一个线程能够跑多个协程。在课程案例中,对于主协程需要进入休眠态time.Sleep(time.Second),以防止子协程还未结束时主协程已经结束。

CSP (Communicating Sequential Processes) 协程之间提倡使用通道共享内存而不是通过共享内存实现通信。通过共享内存会存在临界区以及数据静态(不能修改的数据)。

Channel channel通道通过make关键字进行创建,及make(chan 元素类型, [缓冲大小])这一语法。其中缓冲大小是可选项,定义了缓冲大小的是有缓冲通道,无缓冲通道则是同步通信。

并发安全 Lock 引用sync.Mutex类型,对于没上锁的协程存在并发安全的问题,例如课例中对某一个变量执行2000次+1操作,5个协程并发执行不上锁变量的值将不确定,但是上锁变量能够在原先的值+2000。要注意Lock()完以后需要Unlock(),主协程需要进入睡眠。

WaitGroup 因为不知道子协程会在什么时候运行,主协程阻塞时长不好掌控,因此Go在Sync包里提供了WaitGroup来实现并发的同步。包含三个方法Add(delta int)用于协程开启时计数delta,Done()计数减1常用defer来调用,Wait()表示主协程阻塞到计数器为0。

2.依赖管理 对于hello word 单体函数只需要依赖原生SDK,但是实际工程可能涉及framework、collection、log、driver、collection等一系列依赖会通过sdk方式引入,因此需要对依赖包进行管理。

Go依赖管理演进 经历3个阶段GOPATH -> Go Vendor -> Go Module。主要围绕实现两个目标:不同环境依赖的版本不同以及控制依赖库的版本。

GOPATH是指Go语言支持的环境变量,值是Go项目的工作区,目录结构包括src存放源码,pkg编译中间产物,bin存放Go项目生成的二进制文件。但是GOPATH存在弊端,当两个项目依赖某一package的不同版本,就没办法实现package的多版本控制。

Go Vendor出现解决了上述问题,即工作目录下有个Vendor目录存放依赖的副本,如果依赖存在于Vendor会优先使用该目录的依赖,否则从GOPATH寻找。但是存在无法控制依赖版本以及会出现依赖冲突导致编译出错的问题。

Go Module是Go语言官方推出的依赖管理系统,解决了无法依赖同一库多个版本等问题。通过go.mod文件管理依赖包版本,以及go get/go mod指令工具管理依赖包,实现了定义版本规则和项目依赖关系。

依赖管理三要素 配置文件描述依赖 go.mod 中心仓库管理依赖库 Proxy 本地工具 go get/mod 依赖配置go.mod如下:

image.png

go mod定义了版本语义规则,分为语义化版本和基于commit伪版本如下:

image.png

MAJOR版本表示不兼容的API,即使同一个库,也会被认为是不同的包。MINOR版本通常是新增函数或者功能,向后兼容。patch一般是进行了Bug修复的版本。基于commit伪版本,基础版本前缀和语义版本一致,时间戳表示提交时间,最后校验码是12位哈希前缀,每次commit后Go会默认生成一个伪版本号。

特殊标识indirect表示间接依赖 A->B->C则A间接依赖C。incompatible:主版本在2+模块会在模块路径上增加/vN的后缀,能够以按照是不同模块来处理同一项目不同主版本的依赖。但是在gomod之前,有一些仓库就已经是2或者更高版本tag,为了兼容,因此对于没有go.mod的文件并且主版本在2或以上的依赖,会打上该后缀。

依赖分发Proxy如下:

image.png

主要为了保证软件版本在增删改查的时候依旧能够正确依赖(构建稳定性和依赖可用性),并且减少代码托管平台负载问题,不用存多个版本。Proxy是服务站点,会缓存原站中的软件内容,版本不会改变,源站删除依然可用。

Go Module通过GOPROCY环境变量控制Proxy,GOPROXY是一个url列表用direct表示源站。


image.png go get工具:

image.png

image.png 3.测试 测试分为回归测试,集成测试,单元测试,从上到下覆盖率增大,成本在降低。回归是通过终端回归固定主流程场景,集成是对系统功能测试,但愿是都单独函数模块验证。

单元测试 单元测试文件_test.go结尾,测试函数固定格式TestXxx(*testing.T),初始化逻辑放在TestMain。运行指令:go test [flags] [packages]。开源assert能够帮助减少测试代码量。单元测试通过覆盖率衡量是否经过了足够测试。

在对文件进行测试的时候,如果文件不存在对应的内容可能报错,可以通过Mock来进行测试,开源Mock库github.com/bouk/monkey进行打桩测试,不再依赖本地文件。

基准测试 函数BenchmarkXxx(b *testing.B)b中的N值是反复递增循环测试,举了多个服务器随机选择的例子,并且进行了多协程并发测试,如果在测试前做了init或其他准备操作,注意使用b.ResetTimer()排除不在测试范围的时间花费。随机选择时为优化,开源了fastrand。 cmd命令go test -bench

import (
	"testing"
)
func BenchmarkSelect(b *testing.B) {
	InitServerIndex()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		Select()
	}
}
func BenchmarkSelectParallel(b *testing.B) {
	InitServerIndex()
	b.ResetTimer()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			Select()
		}
	})
}
func BenchmarkFastSelectParallel(b *testing.B) {
	InitServerIndex()
	b.ResetTimer()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			FastSelect()
		}
	})
}
js