这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记
channel
golang通过通信共享内存。
func main() {
src := make(chan int)
dest := make(chan int, 3)
//暂且称为协程A
go func() {
defer close(src)
for i := 0; i < 10; i++ {
src <- i
}
}()
//暂且称为协程B
go func() {
defer close(dest)
for i := range src {
dest <- i * i
}
}()
for i := range dest {
fmt.Println(i)
}
}
执行的结果:
0
1
4
9
16
25
36
49
64
81
主协程main中开启了两个协程AB,两个协程之间的通信使用的是无缓冲区的channel,是同步通信的,A往src放入一个元素,阻塞,等待B从src中取出元素。AB是相互阻塞的。 B从src中取出元素,处理之后将处理后的结果放入dest中,如果这个时候dest是满的,B就会阻塞,同理,如果缓冲区是空的,主协程就会阻塞等待数据。
Sync.WaitGroup
- Add(): 计数器+delta
- Done():计数器-1
- Wait(): 主协程阻塞直到计算器为0
依赖管理
GOPATH -> GO Vender -> Go Module
GOPATH
- bin: 项目编译的二进制文件
- pkg:项目编译的中间产物,加速编译
- src:项目的源码
项目代码直接依赖src下的代码,go get下载的所有依赖都存放到src目录下面。
存在的弊端:
-
无法实现包的多版本控制
GOPATH下面存放所有的依赖包,如果项目A和项目B依赖于不同版本的package,同时该package又没有做到完全的向前兼容,往往会导致一些问题。
Go Vender
其实就是在每个项目增加一个vendor文件夹,所有依赖副本存放在这里。
寻址方式:首先去vendor下面寻找,没有的话去GOPATH
这样解决了多个项目需要同一个package依赖的冲突问题
但是,还是无法控制依赖的版本,更新项目可能出现以来冲突,导致编译出错
packageB和packageD同时依赖packageD,但是依赖的是不同的版本,这样就会出现问题。
以上两种依赖的管理方式,归根结底,都是依赖于源码,不能清晰的标识依赖的版本,就会出现问题
Go Module
通过go.mod文件管理依赖包版本,通过go get/go mod指令工具管理依赖包。
终极目标:定义版本规则和管理项目依赖关系。
依赖管理三要素:
-
配置文件,描述依赖 go.mod
-
中心仓库管理依赖库 Proxy
-
本地工具 go get/mod
依赖标识: [Module Path][Version/Pseudo-version]
版本version
版本语义-时间戳(提交的时间戳)-哈希的编码
indirect
A->B->C
- A与B是直接依赖
- A与C是间接依赖
在执行命令go mod tidy时,Go module 会自动整理go.mod 文件,如果有必要会在部分依赖包的后面增加// indirect注释。一般而言,被添加注释的包肯定是间接依赖的包,而没有添加// indirect注释的包则是直接依赖的包,即明确的出现在某个import语句中。
然而,这里需要着重强调的是:并不是所有的间接依赖都会出现在 go.mod文件中。
间接依赖出现在go.mod文件的情况,可能符合下面所列场景的一种或多种:
- 直接依赖未启用 Go module
- 直接依赖go.mod 文件中缺失部分依赖
具体解释参考该链接:【Go 专家编程】go.mod 文件中的indirect准确含义_神以灵的博客-CSDN博客_go indirect
incompatible
引入不兼容的包。
- 主版本2+模块会在模块路径增加/vN后缀
- 对于没有go.mod文件并且主版本2+的依赖,会+incompatible
【Go 专家编程】go.mod 文件中incompatible包意味着什么 - 恋恋美食的个人空间 - OSCHINA - 中文开源技术交流社区
C1.3和C1.4同属于V1版本,会选择最低的兼容版本
依赖分发-回源
go.mod中的依赖其实能对应到代码托管仓库的某个项目的提交,但是如果直接从这里下载容易造成一些问题。
-
无法保证构建的稳定性
删除/修改/删除软件版本
-
无法保证依赖可用性
删除软件
-
增加第三方压力
代码托管平台负载问题
变量-GOPROXY
GOPROXY="proxy1.cn, proxy2.cn, direct"
服务站点URL列表,"direct"表示源站
go get
go mod
测试
单元测试
规则
- 所有测试文件以_test.go结尾
- func TestXxx(*testing.T)
- func 初始化逻辑放到了TestMain中
举例:
assert包
怎么评估项目的测试水准呢?
通过代码覆盖率!
执行的命令上加上--cover参数,就可以输出代码的覆盖率值。
这个66.7%是怎么计算的呢?核心代码只有3行,前两行(score>=60 && return true)在这次的单元测试中被执行,而return false没有执行,那么代码的覆盖率为2/3=66.7%
接下来提高代码的覆盖率,其实就是对代码的每个部分都进行测试。
- 一般覆盖率:50%-60%,较高覆盖率80%+;
- 测试分支相互独立,全面覆盖;
- 测试单元粒度足够小,函数单一职责
依赖
幂等:重复运行,结果是一样的。
Mock测试
开源的mock包:GitHub - bouk/monkey: Monkey patching in Go
安装:go get bou.ke/monkey
一般单元测试重点在于CPU和内存类型的测试,而对IO类型的测试是比较敏感的,针对这种情况,可以使用mock技术。
mock测试,简单来说,就是通过对服务或者函数发送设计好的参数,并且通过构造注入期望返回的结果来方便以上几种测试开发。
有以下情况使用mock比较好:
-
IO类型,本地文件、数据库、网络API、RPC等
-
依赖的服务还没有开发好,这个时候自己模拟一个服务,加速开发进度,提升效率
-
压力性能测试的时候屏蔽外部依赖,专注测试本模块
-
依赖的内部函数非常复杂,要构造数据非常不方便
mock测试是的单元测试的升级,可以把之前无法做的,但是又非常重要的模块进行单元测试,
monkey是一个GO单元测试中十分常用的打桩工具,它在运行时通过汇编语言重写可执行文件,将目标函数或方法的实现跳转到桩实现,其原理类似于热补丁。
monkey库很强大,但是使用时需要注意以下事项:
-
monkey不支持内联函数,在测试的时候需要通过命令行参数
-gcflags=-l关闭go语言的内联优化。 -
monkey不是线程安全的,所以不要把它用到并发的单元测试中。
基准测试
基准测试是对计算机系统的性能的测试。
在程序中。基准测试,是一种测试代码性能的方法,比如有一个问题你有很多不同的方案,你想选择一种性能最好的方案,那么就需要用到基准测试。
基准测试主要是通过测试CPU和内存的效率问题,来评估被测试代码的性能,进而找到更好的解决方案。比如连接池的数量不是越多越好,那么哪个值才是最优值呢,这就需要配合基准测试不断调优了。
rand在高并发时如果不太注意底层实现的话,容易造成性能问题,可以是尝试使用fastrand.
https://github.com/bytedance/gopkg
项目实战
抽离出两个实体:
使用ER图描述实体:
项目分层结构:
数据层:Repository, 与外部数据(数据库)打交道,增删改查,数据Model
逻辑层:Service, 处理核心业务逻辑输出,业务Entity
视图层:Controller, 处理和外部的交互逻辑, View