Go 工程实践
一、并发与并行
1、高并发goroutine,通过高效的调度模型实现高并发操作:
协程:用户态,轻量级线程,KB级别
线程:内核态,线程并发跑多个协程,MB级别
2、通过管道Channel实现共享内存:
go通过通信共享内存而不是通过共享内存而实现通信,因此引入了一个通道的概念:
make(chan int)无缓冲通道(收发是同步的)
make(chan int,2)有缓冲通道(收发不是同步的)
3、sync关键字,实现并发安全操作与协程间的同步:
注意,虽然go是通过通信实现共享内存,但依然有锁lock的概念:
lock sync.Mutex
...
lock.lock()
…
lock.unlock()
另外,为了能知晓协程运行时间,精准管理,不使用sleep,go定义了Waitroup:
var wg sync.WaitGroup
wg.Add(5)//添加五个线程,计数器+5
defer wg.Done()//每开启一个协程,计数器-1
wg.Wait()//主协程阻塞,直到计数器减到0
二、依赖管理:
Go的依赖管理主要经历了3个阶段,分别是,GOPATH-->Go Vendor-->Go Module
1、GOPATH环境变量
三个关键目录:
bin项目编译的二进制文件;
pkg项目编译的中间产物,加速编译;
src项目源码。
弊端:不同项目不能依赖同一个库的不同版本,无法实现pkg的不同版本控制
2、Vendor
通过每个项目引入一份依赖的副本,保存在vendor中,解决了多个项目需要同一个pkg依赖的冲突问题。
(先去vendor中找,没有的话去GOPATH)
弊端:无法很好解决依赖包的版本变动问题和一个项目依赖同一个包的不同版本的问题,依赖的版本无法控制,更新项目后可能有依赖冲突:A依赖B,C,BC的依赖D有两个不同版本,不兼容。
3、Go Module
go.mod文件管理依赖包版本;
go get/go mod管理依赖包
4、管理三要素:
(1)配置文件、描述依赖go.mod
version:1.3.2第一个MAJOR表示不兼容的库;第二个MINOR表示新增函数、功能,向后兼容;第三个表示DEBUG版本
patch版本还会多一个时间戳和校验位
indirect表示非直接依赖
incompatible是为了兼容在go module出现之前,主版本在2以上的依赖
(2)中心仓库管理依赖库Proxy
对于go.mod中定义的依赖,则直接可以从对应仓库中下载指定软件依赖,从而完成依赖分发。但直接使用版本管理仓库下载依赖,存在多个问题,首先无法保证构建确定性:软件作者可以直接代码平台增加修改/删除软件版本,导致下次构建使用另外版本的依赖,或者找不到依赖版本。无法保证依赖可用性:依赖软件作者可以直接代码平台删除软件,导致依赖不可用;大幅增加第三方代码托管平台压力。
Go Proxy是一个服务站点, 它会缓源站中的软件内容,缓存的软件版本不会改变,并且在源站软件删除之后依然可用,构建时会直接从Go Proxy站点拉取依赖。
GOPROXY="proxy1.cn, proxy2.cn ,direct'服务站点URL列表,“direct" 表示源站,整体的依赖寻址路径, 会优先从proxy1下载依赖, 如果proxy1不存在,后下钻proxy2寻找,如果proxy2, 中不存在则会回源到源站直接下载依赖,缓存到proxy站点中。
(3)本地工具go get/go mod
三、项目测试:
回归测试->集成测试->单元测试。回归测试一般是QA同学 手动通过终端回归一些固定的主流程场景,集成测试是对系统功能维度做测试验证,而单元测试测试开发阶段,开发者对单独的函数、模块做功能验证,层级从上至下,测试成本逐渐减低,而测试覆盖率确逐步上升,所以单元测试的覆盖率-定程度上决定这代码的质量。
1、单元测试:
测试单元:函数、模块等等,保证质量、提升效率
测试规则:文件_test.go结尾;函数名func TestXxxx(t *testing.T);初始逻辑放在func TestMain(m *testing.M)中
(测试判断两个量是否相等可以使用包”github.com/stretchr/testify/assert”,使用时assert.Equal(t,exa,a))
测试覆盖率:go test a_test.go test.go –cover
一般覆盖率50%-60%
测试分支相互独立、全面覆盖
测试单元粒度足够小、函数单一职责
测试的MOCK机制: 要保证稳定性和幕等性,稳定是指相互隔离,能在任何时间,任何环境,运行测试。幂等是指每一次测试运行都应该产生与之前一样的结果。而要实现这一 目的就要用到mock机制。
比如测试文件没有了,打不开了,这时候使用MOCK机制为一个函数/方法打桩,使用不需要测试文件的内部函数。可以使用”bou.ke/monkey”包:
monkey.Patch(func(),new func())//测试时用一个新的函数代替原函数测试
defer monkey.Unpatch(func())
2、基准测试
Go语言还提供了基准测试框架,基准测试是指测试一段程序的运行性能及耗费CPU的程度。而我们在实际项目开发中,经常会遇到代码性能瓶颈,为了定位问题经常要对代码做性能分析,这就用到了基准测试。
命令行go test -bench=.
编写测试函数:在测试文件中,编写一个以 Benchmark 为前缀的函数,后面跟上一个或多个字符或字符组合来标识测试用例的名称(一般使用被测的函数名称),参数必须是 b *testing.B
三、项目开发
1、需求设计
E-R图,用来典型的分层结构设计模型。有了模型实体,属性以及之间的联系,对我们后续做开发就提供了比较清晰的思路。
整体分为三层,repository数据层, service逻辑层, controoler视图层。具体不再阐述
2、代码开发
介绍下开发涉及的基础组件和工具,首先是gin, 高性能开源的go web框架,我们基于gin搭建web服务器,在课程手册应该提到了,这里我们只是简单的使用,主要涉及路由分发,不会涉及其他复杂的概念。
因为我们引入了web框架,所以就涉及go module依赖管理,如前面依赖管理课程内容讲解,我们首先通过go mod是初始化go mod管理配置文件,然后go get下载gin依赖,这里显示用了V1.3.0版本。
有了框架依赖,我们只需要关注业务本身的实现,从reposity --> service --> contoller我们一步步实现。
3、测试运行