Go 语言上手-工程实践 | 青训营笔记
这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记
1. 并发介绍
1.1 并发 VS 并行
并发: 多线程程序在一个核的cpu上运行
并行: 多线程程序在多个核的cpu上运行
Go可以充分发挥多核优势,高效运行
1.2 Goroutine
协程: 用户态, 轻量级线程, 栈MB级别
线程: 内核态, 线程跑多个协程, 栈KB级别
例:快速打印
func hello(i int) {
println("Hello Goroutine!" + fmt.Sprint(i))
}
func main() {
for i := 0; i < 10; i++ {
go func(j int) {
hello(j)
}(i)
}
time.Sleep(time.Second)
}
1.3 CSP通信
提倡通过通信共享内存
1.4 Channel
make(chan 元素类型,[缓冲大小])
- 无缓冲通道
make(chan int) - 有缓冲通道
make(chan int, 2)
当多个goroutine同时进行处理的时候,就会遇到同时抢占一个资源的情况(并发都会遇到的问题),所以我们希望某个goroutine等待另一个goroutine处理完某一个步骤之后才能继续。
func CalSquare() {
src := make(chan int)
dest := make(chan int, 3)
go func() {
defer close(src) //资源关闭
for i := 0; i < 10; i++ {
src <- i //发送
}
}()
go func() {
defer close(dest)
for i := range src { //通过src实现通信
dest <- i * i
}
}()
for i := range dest {
//复杂操作
println(i)
}
}
desc 使用缓冲 channel:不会因为消费者的消费速度问题,影响生产者的执行 效率
1.5 WaitGroup
维护了一个计时器,实现了三个方法
Add : 计数器+delta
Done:计数器-1
wait:阻塞直到计数器为0
优化:快速打印
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)
go func(j int) {
defer wg.Done()
hello(j)
}(i)
}
wg.Wait() //进行阻塞
}
2. 依赖管理
2.1 Go依赖管理演进
GOPATH → Go Vendor → Go Module
- 不同环境(项目)依赖的版本不同
- 控制依赖库的版本
2.1.1 GOPATH
-
环境变量$GOPATH
- bin 项目编译的二进制文件
- pkg 编译的中间产物 加速变异
- src 项目源码
-
项目代码直接依赖src下的代码
-
go get 下载最新版本的包到src目录下
弊端 : 无法实现多版本控制
2.1.2 Go Vendor
- 项目目录下增加vendor文件,所有依赖包副本形式放在$ProjectRoot/vendor
- 依赖寻址方式: vendor → GOPATH
通过每个项目引入一份依赖的副本,解决了多个项目需要同一个package依赖的冲突问题。
弊端:
- 无法控制依赖的版本
- 更新项目又可能出现依赖冲突,导致编译出错
2.1.3 Go Module
- 通过go.mod 文件管理依赖包版本
- mod get/go mod指令工具管理依赖包
2.2 依赖管理三要素
- 配置文件,描述依赖 go.mod
- 中心仓库管理依赖库 Proxy
- 本地工具 go get/mod
2.3 依赖配置
2.3.1 依赖配置 - go.mod
//依赖管理基本单元
module github.com/Moonlight-Zhao/go-project-example
//原生库
go 1.16
//单元依赖
require (
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.3.0 // indirect
2.3.2 依赖配置 - version
语义化版本
${MAJOR}.${MINOR}.${PATH}
V1.3.0
V2.3.0
基于commit伪版本
vX.0.0-yyyymmddhhmmss-abcdefgh1234
2.3.3 依赖配置 - indirect
//indirect非直接依赖
2.3.4 依赖配置 - incompatible
- 主版本2+模块会在模块路径增加/vN后缀
- 对于没有go.mod文件并且主版本2+的依赖,会
+incompatible
如果一个项目同时依赖一个项目多个版本 那么go.mod会选择最低兼容版本去依赖
2.3.5 依赖分发
回源
- 无法保证构建稳定性
- 无法保证依赖可用性
- 增加第三方压力
Proxy
服务站点: 缓存软件内容,实现稳定可靠
2.3.6 依赖分发 - 变量 GOPROXY
配置 : 通过GOPRXY环境变量
2.3.7 工具 - go get

2.3.8 工具 - go mod

3. 测试
- 回归测试
- 集成测试
- 单元测试
3.1 单元测试

3.1.1 单元测试 -规则
- 所有测试文件以 _test.go 结尾
- func TestXxx(* testing.T)
- 初始化逻辑放到 TestMain中
3.1.2 单元测试 - assert
assert.Equal*(t, one ,another)
3.1.3 单元测试 -代码覆盖率
go test xxx.go --cover
3.2 单元测试 - 依赖
外部依赖 → 幂等&稳定
3.3 单元测试 - Mock
monkey :github.com/bouk/monkey
快速Mock函数
- 为一个函数打桩
- 为一个方法打桩
Patch(target,replacement interface{}:
target: 原函数replacement: 打桩函数
Unpatch(target interface{}): 测试结束后卸载桩
3.4 基准测试
Benchmarkxxx(b *testing.B)开头
b.ResetTimer时间重置
b.RunParallel并行
字节开源 FastRun: github.com/bytedance/g…
4. 项目实战
4.1 需求描述
社区话题页面
- 展示话题(标题,文字)和回帖列表
- 暂不考虑前端
- 话题和回帖数据用文件存储
4.2 需求用例

4.3 ER图

4.4 分层结构

4.5 组件工具
Gin 高性能go web框架
Go Mod
go mod init
go get gopkg.in/gin-gonic/gin.v1@v1.3.0
4.6 Repository
结构体:
-
Topic
type Topic struct { Id int64 `gorm:"column:id"` UserId int64 `gorm:"column:user_id"` Title string `gorm:"column:title"` Content string `gorm:"column:content"` CreateTime time.Time `gorm:"column:create_time"` }
-
Post
type Post struct { Id int64 `gorm:"column:id"` ParentId int64 `gorm:"parent_id"` UserId int64 `gorm:"column:user_id"` Content string `gorm:"column:content"` DiggCount int32 `gorm:"column:digg_count"` CreateTime time.Time `gorm:"column:create_time"` }
index
元数据 索引
数据行 → 内存Map
4.7 Service
参数校验 → 准备数据 → 组装实体
4.8 Controller
- 构建View对象
- 业务错误码
4.9 Router
- 初始化数据索引
- 初始化引擎配置
- 构建路由
- 启动服务
请求:
curl localhost:8080/community/page/get/2
\