这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天
从Goroutine开始
依赖管理[三代演进/mod文件/本地工具管理]
测试[单元测试/Mock测试/基准测试]
网页实战为例的项目开发框架
1. 并发Vs并行
1.1 Goroutine
通过go关键字开启协程
需要处理主协程和子协程执行的协调关系(time.sleep阻塞主协程)
| 所处 | 关联 | 量级 | |
|---|---|---|---|
| 协程 | 用户态 | 轻量级线程 | 栈KB级别 |
| 线程 | 内核态 | 线程跑多个协程 | 栈MB级别 |
1.2 协程之间通信——CSP Communicating Sequential Processes
Go提倡通过通信共享内存 rather than 通过共享内存实现通信(临界区)
1.3 channel
make(chan type [,缓冲大小])
无缓冲
有缓冲 [应对生产和消费速度不一致的场景]
1.4 并发安全 Lock
通过共享内存实现通信时对临界区的权限控制保证并发安全(sync.Mutex),在协程中每次执行操作前用lock获取临界区资源,操作后再释放资源(unlock)。项目开发中应避免对共享内存做读写操作
1.5 sync.WaitGroup实现并发协程的同步
内部维护了一个计数器
- Add(delta int)计数器+delta
- Done() 计数器—1
- Wait() 阻塞到计数器归零
2. 依赖管理【类比java的maven】
2.1 Go的依赖管理演进
2.1.1 GOPATH
- bin 项目编译的二进制文件
- pkg 编译的中间产物,加速编译
- src 项目源码
go set 下载包到src下
弊端:无法实现package的多版本控制
2.1.2 Go Vendor
项目目录下增加vendor文件,存放当前项目依赖的副本(如不存在再找GOPATH)
解决了多个项目需要同一个package依赖的冲突问题
弊端:无法控制版本,更新项目可能出现依赖冲突
2.1.3 Go Module 定义版本规则和管理项目依赖关系
go.mod 管理版本 go mod/get 管理包
2.2 依赖配置
2.2.1 go.mod
- 依赖管理基本单元
- 原生库标识
- 单元依赖
- 依赖标识:modulePath + version
2.2.2 version
- 语义化版本 MAJOR.MINOR.PATCH 大版本+兼容的函数增加+bug修复
- 基于commit伪版本 v+时间戳+12位hash前缀
2.2.3 标识符
//indirect 间接依赖
+incompatible用于没有go.mod且主版本2+的依赖
2.2.4 依赖图
原则:选择最低的兼容版本
2.3 依赖分发(中心仓库管理依赖库
2.3.1 回源
直接从第三方代码托管平台拉取的问题:无法保证构建稳定性、依赖可用性,增加第三方压力
2.3.2 proxy代理 GOPROXY
缓存内容 稳定可靠的依赖分发
环境变量GOPROXY = "proxy1.cn, proxy1.cn, direct"
服务站点URL列表(类似于缓存设计,寻找路径), direct表示源站
2.4 本地工具
2.4.1 go get example.org/pkg
- @update 默认拉取major版本的最新提交
- @none 删除依赖
- @v1.1.2 tag版本,语义版本
- @23ddfd5 指定commit
- @master 分支的最新commit
2.4.2 go mod
- init 初始化 创建go.mod文件
- download 下载模块到本地缓存
- tidy 按需要增删依赖
3.测试
go test
-v 打印每个测试函数的名字和运行时间
-run = 一个正则表达式,被正确匹配的测试函数才会被go test测试命令运行
- 测试文件以 _test.go 结尾
- 测试函数
- 需导入"testing"包
- 签名: func TestName(t *testing.T) //t参数用于报告测试失败和附加的日志信息
func TestMain(m *testing.M) {
// do init 数据装载、配置初始化等工作
code := m.Run() // 执行测试
// do close 释放资源等收尾工作
os.Exit(code)
}
回归(关键性模块)--集成(系统功能的自动化测试)--单元(覆盖率最高,成本最低)
3.1 单元测试
输入测试单元,输出结果与期望校对
可在较短周期内定位修复问题。
import(
"github.com/stretchr/testify/assert"
"testing"
)
func TestHelloIllTamer(t *testing.T) {
output := HelloIllTamer()
expectOutput := "IllTamer"
//if output != expectOutput {
//t.Errorf("Not match: expected %s but %s", expectOutput, output)
//}
assert.Equal(t, expectOutput, output)
}
- 覆盖率: 经过验证的代码行比例 可累计
- go命令行的最后加上 --cover
- 建议:测试分支相互独立 全面覆盖; 测试单元粒度够小,函数单一职责
3.2 Mock测试
mock 意为伪造,利用反射机制替换某些如读取文件之类的函数,达到测试稳定且幂等的结果(屏蔽对本地文件的依赖) 例:使用开源的github.com/bouk/monkey, 可为函数/方法打桩
func TestProcessFirstLineMock(t *testing.T) {
monkey.Patch(ReadFirstLine, func() string) {
return "line110"
}
defer monkey.Unpatch(ReadFirstLine)//卸载桩函数
output := ProcessFirstLine()
assert.Equal(t, "line000", output)
}
3.3 基准测试
测试代码性能
- 测试
-
go test -bench=.
-
- 测试函数
- 签名: func BenchmarkName(b *testing.B)
// 串行测试
func BenchmarkRandom(b *testing.B) {
Init()
b.ResetTimer()//重置计时器,之前的init不作为基准测试的范围
for i := 0; i < b.N; i++ {//用b中的N值反复循环测试直到
//dosth
}
}
// 并行测试(rand函数为保证全局的随机性和并发安全,内置了全局锁,并发性能下降)
//可使用fastRand代替rand github.com/bytedance/gopkg
func BenchmarkRandomParallel(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {//多协程并发测试
for pb.Next() {
//dosth
}
})
}
4. 项目实战
4.1 需求设计
- ER图 entity-relationship
- 分层结构 [通用]
- 数据层 Model, 关联底层数据模型,封装外部数据的增删查改
- 对Service层透明(接口模型不变),屏蔽下游数据的差异
- 逻辑层 Entity, 处理业务核心逻辑输出,计算打包业务实体Entity上送给view
- 视图层 View 处理和外部的交互逻辑,以view视图的形式返回给客户端
- 此处要求实现 封装JSON格式化的请求结果,api形式访问
- 此处要求实现 封装JSON格式化的请求结果,api形式访问
- 数据层 Model, 关联底层数据模型,封装外部数据的增删查改
4.2 代码开发
- 组件工具
- Gin : 高性能 go web 框架 github.com/gin-gonic/g…
- go mod init / go get
4.2.1 Repository
结构体定义
type Topic struct {
Id int64 `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
CreateTime int64 `json:"create_time"`
}
type Post struct {
Id int64 `json:"id"`
ParentId int64 `json:"parent_id"`
Content string `json:"content"`
CreateTime int64 `json:"create_time"`
}
//gorm操作数据库
index——map实现内存索引,在服务对外暴露前,利用文件元数据初始化全局内存索引,实现O(1)的时间复杂度查找操作。
var (
topicIndexMap map[int64]*Topic
postIndexMap map[int64][]*Post
)
查询操作:直接根据key获得map中的value
sync.once主要适用于高并发场景下只执行一次的场景 减少内存浪费
4.2.2 Service
流程:参数校验——准备数据——组装实体(可并行处理)
type PageInfo struct{
Topic *repository.Topic
PostList []*repository.Post
}
4.2.3 Controller
构建view对象
注意PageData中的code和msg反映业务执行状态
4.2.4 Router
通过Gin搭建外部框架:
- 初始化数据索引
- 初始化引擎配置
- 构建路由
- 启动服务
4.3 测试运行
go run server.go
配合终端curl --location --request GET 'http://0.0.0.0:8080/community/page/get/2' | json