【Go】GO工程实践

228 阅读7分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第19天,点击查看活动详情

并发编程

1.1 Go Routine

并发&并行 并发主要是在一个CPU核各线程的来回切换 并行主要是多核并行 协程&线程 协程:用户态,轻量级线程,栈为MB级别 线程:用户态,一个线程可以跑多个协程,栈为KB级别

func closure1() {
	for i := 0; i < 3; i++ {
		go func(j int) {//加上go,就能为函数创建了一个协程
			println(j)//为的是快速打印
		}(i)
	}
	time.Sleep(3 * time.Second)//子协程执行完之前,父协程不退出
}

1.2 CSP(Communicating Sequential Processes)

image.png 提倡实现通信->共享内存 Goroutine是一个Go实现并发的执行体,通道进行链接,遵循FCFS 但是仍保留旧方式,通过互斥量给进程加锁获取临界区权限,参考操作系统进程线程相关内容

1.3 Channel

这是一种引用类型,用make关键字创建 make(chan元素类型,缓冲大小) ·无缓冲通道 make(chan int) ·有缓冲通道 make(chan int,2)

image.png 无缓冲通道会导致同步化,有缓冲通道会发生阻塞(生产者消费者模型)

1.4 并发安全Lock

image.png 避免对共享内存做非并发安全读写操作

1.5 WaitGroup

1.6 总结

3个方面,一个是协程,通过高效调度模型实现高并发排作,一个是通道channel,通过通信实现共享内存,最后sync相关关键字,实现并发安全操作和协程间的同步。

2 依赖管理

依赖演进: GOPATH -> GO Vendor -> Go Module 目的是为了定义版本规则和管理项目依赖关系

2.1 GOPATH

image.png

场景:方法A和方法B依赖于某一package的不同版本。

问题:无法实现package的多版本控制

2.2 Go Vender

image.png

弊端:

image.png 问题:

  • 无法控制依赖的版本。
  • 更新项目又可能出现依赖冲突,导致编译出错。

2.3 Go Module 管理方案(现行)

  • 通过go.mod 文件管理依赖包版本
  • 通过go get/go mod 指令工具管理依赖包

2.4 依赖管理三要素

1.配置文件,描述依赖 go.mod
2.中心仓库管理依赖库 Proxy
3.本地工具 go get/mod

2.5 依赖配置 go.mod

image.png 后面路径+版本号 //indirect 表示非直接依赖

image.png 前面v1.0.0是语义化版本,后面是基于commit的伪版本 ${MAJOR}.${MINOR}.${PATCH}
v×.0.0-yyyymmddhhmmss-abcdefgh1234 12位的哈希校验码前缀

  • 主版本2+模块会在.模块路径增加/vN后缀。
  • 对于没有go.mod文件并且主版本2+的依赖,会+incompatible,用来兼容在加/vN之前已经打上2以上tag的依赖

image.png

2.6 依赖分发-变量 GO PROXY

go proxy是一个服务站点,它会缓存源站中的软件内容,缓存的软件版本不会改变,并且在源站删除之后依然可用,从而实现了供immutability和available的依赖分发,使用Go Proxy之后,构建时会直接从Go Proxy站点拉取依赖,而不用从代码托管平台拉取。

image.png

按Proxy1->Proxy2->Direct 查找依赖,两个Proxy都没有回源到第三方托管平台

2.7 go get

image.png

2.8 go mod(重点)

  • init 初始化,创建go.mod文件
  • download 下载模块到本地缓存
  • tidy 增加需要的依赖,删除不需要的依赖(每次提交代码之前都可以执行一下)
go mod init "项目名称"    // 初始化modules  
go mod tidy  //检测依赖  
go mod download //下载依赖

3.测试

测试一般分为,回归测试一般是叫同学手动通过终端回归一些固定的主流程场景
集成测试是对系统功能维度做测试验证
单元测试测试开发阶段,开发者对单独的函数、模块做功能验证,层级从上至下
成本逐渐减低,而测试覆盖率确逐步上升,所以单元测试的覆盖率一定程度上决定这代码的质量。

3.1 单元测试

image.png

image.png

如何衡量代码是否经过了足够的测试?
如何评价项目的测试水准?
如何评估项目是否达到了高水准测试等级? 覆盖率

  • 一般覆盖率:50%~60%,较高覆盖率80%+
  • 测试分支相互独立、全面覆盖
  • 测试单元粒度足够小,函数单一职责

3.2 单元测试-依赖

image.png

3.3 mock

如果我们的单测需要依赖本地的文件,如果文件被修改或者删除测试就会fail。为了保证测试case的稳定性,我们对读取文件函数进行mock,并屏蔽对于文件的依赖(在任何时间任何地点执行)。

函数原型 image.png

这里我们用了Monkey,monkey是一个开源的mock测试库,可以对method,或者实例的方法进行mock,反射和指针赋值
Mockey Patch的作用域在Runtime,在运行时通过通过Go的unsafe包,能够将内存中函数的地址替换为运行时函数的地址。将待打桩函数或方法的实现跳转到。

基准测试

基准测试是指测试一段程序的运行性能及耗费CPU的程度。而我们在实际项目开发中,经常会遇到代码性能瓶颈,为了定位问题经常要对代码做性能分析,这就用到了基准测试。

例子:

image.png 基准测试以Benchmark开头入参是testing.B,用b中的N值反复递增循环测试 (对一个测试用例的默认测试时间是1秒,当测试用例函数返回时还不到1秒,那么testingB中的N值将按1、2、5、10、20、50…递增,并以递增后的值重新进行用例函数测试。) ResetTimer重置计时器,我们在reset之前做了其他的准备操作,这些操作不位该作为基准测试的范围 runparallel是多协程并发测试,执行2个基街财试,发现代码在并发情况下存在劣化、主要原因是rand为了保证全局的随机性和并发安全,持有了1把全局锁。

高性能随机数方法fastrand,牺牲了数列一致性github.com/bytedance/g…

func FastSelect() int {
 return ServerIndex[fastrand.Intn( n: 10)]
}

4.项目实战

需求:社区话题页面 √展示话题(标题,文字描述)和回帖列表 √暂不考虑前端页面实现,仅仅实现一个本地web服务 √话题和回帖数据用文件存储

用例图 image.png ER图

image.png

分层结构

image.png 整体分为三层,repository数据层,service逻辑层,controler视图层

repository数据层关联底层数据模型,封装外部数据CRUD,我门的数据存储在本地文件,通过文件操作拉取话题,帖子数据,数据是面向逻辑层,对service层透明,屏蔽下游数据差异,不论下游是文件,还是数据库,还是微服务等,对service层的接口模型是不变的。

Servcie逻辑层处理核心业务逻辑,计算打包业务实体entiy,对应我们的需求,就是话题页面,包括话题和回例表,并上送给视图层;

Controller视图层负责处理和外部的交互逻辑,以view视图的形式返回给客户端,对于我们需求,我们封装json格式化的请求结果,api形式访问就好。

用到的工具

Gin高性能go web框架 github.com/gin-goniclg…

Go Mod go mod init go get gopkg.in/g in-goniclgin. v l @ v l.3.0

基于gin搭建web服务器,这里我们主要涉及路由分发,不会涉及其他复杂的概念。
因为我们引入了web框架,所以就涉及go module管理,我们首先通过go mod是初始go mod管理配置文件,然后go get下载gin依赖

QA

协程可以理解为用户线程,为了和内核态线程区分,go在用户态实现了 token鉴权直接服务端,cookie和session要后台