这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记
一、语言进阶
1、并发VS并行
协程:用户态,轻量级线程,栈MB级别 线程:内核态,线程跑多个协程
1.1goroutine
1.2CSP
go提倡通过通信共享内存而不是通过共享内存而实现通信。
1.3Channel
无缓冲通道make(chan int) 有缓冲通道make(chan int ,8)
1.4并发安全Lock
操作某全局变量前对该变量加锁(在循环内)
1.5WaitGroup
var wg sync.WaitGroup
wg.Add(5)//最大并发数
defer wg.Done()//协程执行完任务后协程数-1
wg.Wait()//等待所有协程执行完毕
二、依赖管理
2.1go依赖管理演进
Go path-->Go Vendor-->Go Module
2.1.1Go path
|__bin 项目编译的二进制文件
|__pkg 项目编译的中间产物,加速编译
|__src 项目源码
项目代码直接依赖src下的代码 go get下载的最新版本的包到src目录下
弊端:无法实现package的多版本控制
2.1.2Go Vendor
项目目录下增加vendor文件,所有依赖包副本形式放在 项目所在目录/vendor下
依赖寻址方式:vendor=>GoPath
|__ README.md
|__dao
|__handler
|__main.go
|__service
|__vendor
通过每个项目引入一份依赖副本,解决了多个项目需要同一个package依赖的冲突问题
弊端:
- 无法控制依赖的版本
- 更新项目又可能出现依赖冲突,导致编译出错
2.1.3Go Module(目前默认开启)
- 通过go.mod文件管理依赖包版本
- 通过go get/go mod指令工具管理依赖包
2.2依赖管理三要素
- 配置文件,描述依赖 go.mod
- 中心仓库管理依赖库 Proxy
- 本地工具 go get/mod
2.3 GO Module依赖管理
2.3.1依赖配置-go.mod
module example/project/app 依赖管理基本单元
go 1.16 原生库
require( 单元依赖
)
2.3.2依赖配置-version
- 语义化版本
V1.2.0
- 基于commit伪版本
vx.0.0-yyyymmddhhmmss-abcdefgh1234
2.3.3依赖配置-indirect
A-->B-->C
- A-->B直接依赖
- A-->C间接依赖
- 简介依赖会在后面表注indirect
2.3.4依赖配置-incompatible
- 主版本2+模块会在模块路径增加
/vN后缀 - 对于没有go.mod文件并且主版本 2+ 的依赖,会+incompatible(在加入gomodule之前的代码,添加标注,可能存在不兼容情况)
- 对于一个项目中存在依赖一个库的两个不同版本,在保证两个版本相互兼容的情况下,选择最低兼容版本。即:v1.3和v1.4选择1.4.
2.3.5依赖分发-回源
- 无法保证构建稳定性
- 无法保证依赖可用性
- 增加第三方压力
2.3.6依赖分发-Proxy
2.3.7依赖分发-GoProxy
GOPROXY='"proxy1.cn,https://proxy2.cn,…" 服务站点URL列表,direct表示源站
2.3.8工具-go get
go get example.org/pkg
- @update 默认
- @none 删除依赖
- @v1.1.2 tag版本,语义版本
- @23dfdd5 特定的commit
- @master 分支的最新commit
2.3.9工具-go mod
go mod +
- init 初始化
- download 下载模块到本地缓存
- tidy 增加需要的依赖,删除不需要的依赖
三、测试
回归测试、集成测试、单元测试 ——————————————> 覆盖率逐层变大,成本逐层降低
3.1单元测试(代码质量)
3.1.1单元测试-规则
- 所有传的是文件以_test.go结尾
- func TestXxx(*testing.T)
- 初始化逻辑放到TestMain中
func TestMain(m *testing.M){
//测试前:数据装载、配置初始化等前置工作
code:=m.Run()
//测试后:释放资源等收尾工作
os.Exit(code)
}
example:
func HelloTom()string{
return "Jerry"//模拟错误,返回jerry
}
func TestHelloTom(t *testing.T){
output:=HelloTom()
expectOutput:="Tom"
if output!=expectOutput{
t.Errorf("Expected %s do not match actual %s",expectOutput,output)
}
assert.Equal(t,expectOutput,output)//另外一种比较方法
}
运行:RUN TestHelloTom
3.1.2单元测试-覆盖率
func JudgePassLine(score int16)bool{
if score>=60{
return true
}
return false
}
func TextJudgePassLineTrue(t *testing.T){
isPass:=JudgePassLine(70)
assert.Equal(t,true,isPass)
}
运行:go test judgment_test.go judgment.go --cover
会给出一个运行代码行数的覆盖率=本次测试运行的行数 / 总行数
(会运行当前测试文件中所有的测试例子)
- 一般覆盖率:50%~60%,与资金相关的要求覆盖率到80%+
- 测试分支相互独立、全面覆盖
- 测试单元粒度足够小
3.2Mock测试(有外部依赖时使用Mock)
3.2.1单元测试-Mock
monkey:github.com/bouk/monkey 快速Mock函数
- 为一个函数打桩
- 为一个方法打桩
//对ReadFirstLine函数打桩测试,不再依赖本地文件
func TestProcessFirstLineWithMock(t *testing.T){
monkey.Patch(ReadFirstLine,func()string{
return "line110"
})
defer monkey.Unpatch(ReadFirstLine)
line:=ProcessFirstLine()
assert.Equal(t,"line000",line)
}
3.3基准测试(性能测试)
- 优化代码,需要对当前代码分析
- 内置的测试框架提供了基准测试的能力
import(
"math/rand"
)
var ServerIndex [10]int
func InitServerIndex(){
for i:=0;i<10;i++{
ServerIndex[i]=i+100
}
}
//随机选择执行服务器
func Select()int{
return ServerIndex[rand.Intn(10)]
//rand在实现过程中加了全局锁,降低了性能,多线程执行下性能劣化
}
func FastSelect()int{
//牺牲了随机数列的一致性,但是在大多数场景都是适用
return ServerIndex[fastrand.Intn(10)]
}
基准测试文件
func BenchmarkSelect(b *testing.B){
InitServerIndex()
b.ResetRimer()//定时器重置,上述的初始化不属于时间测试范围
for i:=0;i<b.N;i++{
Select()
}
}
func BenchmarkSelectParallel(b *testing.PB){
InitServerIndex()
b.ResetTimer()
b.RunParallel(func(pb *testing.Pb){
for pb.Next(){
Select()
}
})
}
四、项目实战
4.1需求描述
- 展示话题(标题,文字描述)和回帖列表
- 本地web服务
- 话题和回帖数据用文件存储
4.2话题-帖子ER图
4.3分层结构
数据层:数据Model,外部数据的增删改查。这里把数据存在文件中,逻辑层不需要关注具体的实现方式。
逻辑层:业务Entity,处理核心业务逻辑输出
视图层:视图view,处理和外部的交互逻辑。包装一些数据格式。
4.4代码运行
go run server.go
curl --location --request GET 'http://127.0.0.1:8080/community/page/get/1'
其中1位id
ssison+cookie:涉及到后台的数据查询(端到端的交互) jwt+token:后端直接解密(后台服务之间的交互)
Go可以在用户态实现自己的调度模型,可以控制自己的并发度。java更多依赖系统调度。