这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记
语言进阶
并发编程
并发VS并行
并发:运行在CPU的一个核上切换交替运行
并发:多个程序在多核CPU上运行
Go可以充分发挥多核优势
协程 Goroutine
线程:内核态、MB
协程:用户态、KB、线程跑多个协程
调用函数时在前面加go关键字即可开启一个协程
CSP
协程间通信
Go提倡通过通信共享内存,而不是通过共享内存实现通信(需要加锁,影响性能)
Channel
make(chan 元素类型,[缓冲大小])
根据是否设定缓冲大小可以分为无缓冲通道(make(chan int))和有缓冲通道(make(chan int, 2)),并发安全
无缓冲通道又可以被称为同步通道
有缓冲通道可以解决生产和消费速度产生的问题
并发安全 Lock
对变量2000次+1,5个协程并发
不加锁会产生非预知结果
WaitGroup
//开启协程+1,执行结束###1,主协程阻塞直到计数器为0
var wg sync.WaitGroup
// 协程开启前,指定协程数量
wg.Add(delta int) // 计数器加delta
// 子协程执行结束后调用,计数器减1
wg.Done() //计数器###1
// 主协程等待子协程
wg.Wait() //阻塞直到计数器为0
依赖管理
依赖管理演进
GOPATH、Go Vendor、Go Module
不同环境依赖的版本不同
控制依赖库的版本
GOPATH
环境变量
bin:项目编译的二进制
pkg:项目编译的中间产物、加速编译
src:项目源码
项目代码直接依赖src下的代码
go get下载最新版本的包到src目录下
弊端:几个项目依赖同一个package不同版本,无法实现package的版本控制
Go Vendor
项目目录下增加vendor文件,所有依赖包副本形式放在$ProjectRoot/vendor
依赖寻址方式:先vendor后GOPATH
弊端:无法控制依赖的版本
更新项目又可能出现依赖冲突、导致编译出错
Go Module
通过go.mod文件管理依赖包版本
通过go get/go mod指令工具管理依赖包
定义版本规则和管理项目依赖关系
依赖管理三要素
- 配置文件、描述依赖: go.mod
- 中心仓库管理依赖库: Proxy
- 本地工具: go get/go mod
相关知识
- go.mod
依赖管理基本单元
原生库
单元依赖
依赖表示: [Module Path][Version/Pseudo-version]
eg:example/lib1 v1.1
- 依赖配置
- 语义化版本
${MAJOR}.${MINOR}.${PATCH}
大版本、新增函数、代码bug修复
- 基于commit伪版本
前缀、时间戳、hash校验码
- 依赖配置-indirect
- 依赖配置-incompatible
- 依赖配置-依赖图:选择最低的兼容版本
- 依赖分发-回源:
无法保证构建稳定性
无法保证依赖可用性
增加第三方压力
所以可以考虑增加Proxy 来增加稳定与可靠的分发
GOPROXY环境变量,是一个服务站点的URL列表,direct表示源站
- 工具 go get
go get example.org/pkg
@update 默认
@none 删除依赖
@v1.1.2 tag版本,语义版本
@23dfdd5 特定的commit
@master 分支的最新commit
- 工具 go mod
go mod
init 初始化,创建go.mod文件
download 下载模块到本地缓存
tidy 增加需要的依赖,删除不需要的依赖
测试
回归测试、集成测试、单元测试
从上到下,覆盖逐层变大,成本逐层降低
单元测试
输入、测试单元(函数、模块、...)、输出
期望
从而实现质量保证与效率提升
- 规则
所有测试文件以_test.go结尾
func TestXxxx(*testing.T)
初始化逻辑放到TestMain中
func TestMain(m *testing.M){
// 测试前:装载数据、配置初始化等前置工作
code := m.Run()
// 测试后:释放资源等收尾工作
os.Exit(code)
}
go test [flags][package]
- 可以借助一些assert依赖包
- 覆盖率 -- cover
- Tips
一般覆盖率 50-60%,较高覆盖率80%+
测试分支相互独立,全面覆盖
测试单元粒度足够小,函数单一职责
- 依赖
稳定、幂等
Mock测试
快速Mock函数:为一个函数打桩、为一个方法打桩
Patch
Unpatch
基准测试
优化代码,需要对当前代码分析
内置的测试框架提供了基准测试的能力
BenchmarkXxxx(b *testing.B)
b.ResetTimer() 可以重置基准测试的计时器
b.RunParallel(func(pb *testing.PB) {
for pb.Next(){
Select()
}
})
rand 性能存在问题,使用全局锁,造成性能劣化(优化建议fastrand)
项目实战
需求设计
- 需求描述
- 需求用例
分析实体
E-R图辅助分析
-
分层结构
- 数据层:数据Model,外部数据的增删改查
- 逻辑层:业务Entity,处理核心业务逻辑输出
- 视图层:识图view,处理和外部的交互逻辑
-
组件工具
-
Gin高性能go web框架
-
Go Mod
- go mod init
- go get gopkg.in/gin-gonic/gin.v1@v1.3.0
-
代码开发
- Repository对于结构体实现基本的查询方法
QueryToicById
QueryPostsByParentId
Repository-index 元数据->索引 即 数据行->内存Map
查询
Repository查询
sync.Once关键字,高并发场景下只执行一次的场景,单例
- Service
实体
type PageInfo struct {
Topic *repository.Topic
PostList []*repository.Post
}
流程
参数校验->准备数据->组装实体
- Controller
构建View对象
业务错误码
- Router
初始化数据索引
初始化引擎配置
构建路由
启动服务
测试运行
QA
- 进程、线程、协程的通信常用方法
Go通过通信共享内存,通过共享内存实现通信
- 登录 端上Session cookie、服务器间JWToken
- 消息队列应用场景 强依赖,非强依赖(可以用消息队列进行解耦)
- 多核优势