这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记。
一、本堂课重点内容:
- 并发VS并行
- 依赖管理
- 测试
- 项目实战
1. 并发VS并行
并发:多线程程序在一个核cpu上运行
并行:在多核cpu上运行
Goroutine
协程:用户态,轻量级线程,栈KB级别
线程:内核态,线程能跑多个协程,栈MB级别
//快速打印hello goroutine:0~hello goroutine:4
package concurrence
import (
"fmt"
"sync"
)
func hello(i int) {
println("hello world : " + fmt.Sprint(i))
}
func helloGoRoutine() {
for i := 0; i < 5; i++ {
go func(j int){
hello(j)
}(i)
}
time.Sleep(time.Second)//不太优雅,之后有改进
}
CSP(Communication Sequence Processes)
协程之间通信
提倡通过通信共享内存(像传输队列,先进先出,保证顺序),而不是通过共享内存而实现通信(有可能发生数据静态的问题,影响效率)
Channel
make(chan元素类型,[缓冲大小])
无缓冲通道(同步通道) make(chan int)
有缓冲通道 make(chan int,2)(类似于有两个格子的货架、生产消费模型)
package concurrence
func CalSquare() {
//无缓冲通道
src := make(chan int)
//有缓冲通道
dest := make(chan int, 3)
go func() {
//A协程发送0~9数字
defer close(src)
for i := 0; i < 10; i++ {
src <- i
}
}()
go func() {
//B协程计算输入数字的平方
defer close(dest)
for i := range src {
dest <- i * i
}
}()
//主协程输出最后的平方数
for i := range dest {
println(i)
}
}
并发安全Lock
对变量执行2000次+1操作,5个协程并发执行
不加锁:8382(随机值、产生并发安全问题)
加锁:10000(结果正确)
WartGroup
计数器:开启协程+1,执行结束-1,主协程阻塞直到计数器为0
//快速打印hello goroutine:0~hello goroutine:4
package concurrence
import (
"fmt"
"sync"
)
func hello(i int) {
println("hello world : " + fmt.Sprint(i))
}
func ManyGo() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)//计数器+1
go func(j int) {
defer wg.Done()//计数器-1
hello(j)
}(i)
}
wg.Wait()//阻塞
}
2. 依赖管理
2.0 背景
工程项目不可能基于标准库0~1编码搭建
管理依赖库很重要
2.1 Go依赖管理演进
GOPATH->Go Vendor->Go Module
不同环境(项目)依赖的版本不同
控制依赖包版本
2.1.1 GOPATH
环境变量$GOPATH
bin(项目编译的二进制文件)
pkg(项目编译的中间产物,加速编译)
src(项目源码)
项目代码直接依赖src下的代码
go get下载最新版本的包到src目录下
弊端:
场景:A和B依赖于某一package的不同版本
问题:无法实现package的多版本控制
2.1.2 Go Vender
项目目录下增加vender文件,所有依赖包副本形式放在$ProjectRoot/vender
依赖包寻址方式:vender->GOPATH
通过每个项目引入一份依赖的副本,解决了多个项目需要同一个package依赖的冲突问题
弊端:
无法控制依赖的版本
更新项目又有可能出现依赖冲突,导致编译错误
2.1.3 Go Module
通过go.mod文件管理依赖包版本
通过go get/go mod 指令工具管理依赖包
终极目标:定义版本规则和管理项目依赖关系
2.2 Go Module依赖管理三要素
- 配置文件,描述依赖 (go.mod)
- 中心仓库管理依赖包 (Proxy)
- 本地工具 (go get/mod)
依赖配置-go.mod
依赖配置-version
依赖配置-indirect
依赖配置-incompatible
主版本2+模块会在模块路径添加/vN后缀
对于没有go.mod文件并且主版本2+的依赖,会+incompatible(表示出来可能存在一些不兼容的代码逻辑)
依赖配置-依赖图
如果X项目依赖了A、B两个项目,且A、B分别依赖了C项目的v1.3、v1.4两个版本,最终编译时使用的C项目的版本为:v1.4(选择最低的兼容版本)
依赖分发-回源
无法保证构建稳定性(增、改、删软件版本)
无法保证依赖可用性(删除软件)
增加第三方压力(代码托管平台负载问题)
依赖分发-Proxy
依赖分发-变量GOPROXY
GOPROXY=“proxu1.cn,https://2.cn,direc…”
服务站点URL列表,“direct”表示源站
工具-go get
工具-go mod
3. 测试
测试是避免事故的最后一道屏障
从上到下,覆盖率逐层变大,成本却逐层降低
3.1 单元测试
定位问题较容易
3.1.1 单元测试-规则
所有测试函数文件以_test.go结尾
func TestXxx(*testing.T)
初始化逻辑放到TestMain中
3.1.2 单元测试-代码覆盖率
上面的例子里只测试到了前两行代码所以代码覆盖率=2/3=66.7%
3.1.3 单元测试-Tips
一般覆盖率:50%~60%,较高覆盖率80%+
测试分支相互独立、全面覆盖
测试单元粒度足够小,函数单一职责
3.2 单元测试-依赖
3.3 单元测试-文件处理
3.4 单元测试-Mock
monkey:github.com/bouk/monkry
快速Mock函数
- 为一个函数打桩
- 为一个方法打桩
对ReadFirstLine打桩测试,不再依赖本地文件
3.5 基准测试
优化代码,需要对当前代码分析
内置的测试框架提供了基准测试的能力
3.5.1 基准测试-例子
随机选择执行服务器
3.5.2 基准测试-运行
并行基准测试,性能劣化(因为用到了rand函数)
3.5.3 基准测试-优化
使用github上开源的fastrand函数(链接:github.com/bytedance/g…)
4. 项目
ER图
话题+帖子
分层结构
数据层:数据Model,外部数据的增删查改
逻辑层:业务Entity,处理核心业务逻辑输出
视图层:视图view,处理和外部的交互逻辑
Repository
如何查询?
Repository-index
初始化话题数据索引
Repository-查询
通过话题ID查找话题
Service
并行处理
Controller
创建View对象
业务错误码
Router
初始化数据索引
初始化引擎配置
构建路由
启动服务
运行
运行测试go run server.go