Go语言上手-工程实践|青训营笔记

154 阅读3分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记。

一、本堂课重点内容:

  1. 并发VS并行
  2. 依赖管理
  3. 测试
  4. 项目实战

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)

image-20220508125951930

协程之间通信

提倡通过通信共享内存(像传输队列,先进先出,保证顺序),而不是通过共享内存而实现通信(有可能发生数据静态的问题,影响效率)

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个协程并发执行

image-20220508125925932

不加锁: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

image-20220508125836720

项目目录下增加vender文件,所有依赖包副本形式放在$ProjectRoot/vender

依赖包寻址方式:vender->GOPATH

通过每个项目引入一份依赖的副本,解决了多个项目需要同一个package依赖的冲突问题

弊端:

无法控制依赖的版本

更新项目又有可能出现依赖冲突,导致编译错误

image-20220508130046769

2.1.3 Go Module

通过go.mod文件管理依赖包版本

通过go get/go mod 指令工具管理依赖包

终极目标:定义版本规则和管理项目依赖关系

2.2 Go Module依赖管理三要素

  1. 配置文件,描述依赖 (go.mod)
  2. 中心仓库管理依赖包 (Proxy)
  3. 本地工具 (go get/mod)

依赖配置-go.mod

image-20220508130016235

依赖配置-version

image-20220508130548653

依赖配置-indirect

image-20220508130822365

image-20220508130850753

依赖配置-incompatible

image-20220508130948562

主版本2+模块会在模块路径添加/vN后缀

对于没有go.mod文件并且主版本2+的依赖,会+incompatible(表示出来可能存在一些不兼容的代码逻辑)

依赖配置-依赖图

image-20220508132208309

如果X项目依赖了A、B两个项目,且A、B分别依赖了C项目的v1.3、v1.4两个版本,最终编译时使用的C项目的版本为:v1.4(选择最低的兼容版本)

依赖分发-回源

image-20220508132225220

无法保证构建稳定性(增、改、删软件版本)

无法保证依赖可用性(删除软件)

增加第三方压力(代码托管平台负载问题)

依赖分发-Proxy

image-20220508132242895

依赖分发-变量GOPROXY

GOPROXY=“proxu1.cn,https://2.cn,direc…

服务站点URL列表,“direct”表示源站

image-20220508132800015

工具-go get

image-20220508132922473

工具-go mod

image-20220508133011477

3. 测试

image-20220508133716236

测试是避免事故的最后一道屏障

image-20220508133912781

从上到下,覆盖率逐层变大,成本却逐层降低

3.1 单元测试

image-20220508134051295

定位问题较容易

3.1.1 单元测试-规则

所有测试函数文件以_test.go结尾

image-20220508134535476

func TestXxx(*testing.T)

image-20220508134550054

初始化逻辑放到TestMain中

image-20220508134607485

3.1.2 单元测试-代码覆盖率

image-20220508135043937

image-20220508135059893

image-20220508135130715

上面的例子里只测试到了前两行代码所以代码覆盖率=2/3=66.7%

3.1.3 单元测试-Tips

一般覆盖率:50%~60%,较高覆盖率80%+

测试分支相互独立、全面覆盖

测试单元粒度足够小,函数单一职责

3.2 单元测试-依赖

image-20220508135807346

3.3 单元测试-文件处理

image-20220508140202314

image-20220508140251897

3.4 单元测试-Mock

monkey:github.com/bouk/monkry

快速Mock函数

  • 为一个函数打桩
  • 为一个方法打桩
  • image-20220508140312786

image-20220508140534102

对ReadFirstLine打桩测试,不再依赖本地文件

3.5 基准测试

优化代码,需要对当前代码分析

内置的测试框架提供了基准测试的能力

3.5.1 基准测试-例子

随机选择执行服务器

image-20220508140906674

3.5.2 基准测试-运行

image-20220508141140660

image-20220508141151787

并行基准测试,性能劣化(因为用到了rand函数)

3.5.3 基准测试-优化

使用github上开源的fastrand函数(链接:github.com/bytedance/g…

image-20220508141518544

image-20220508141539350

4. 项目

ER图

话题+帖子

image-20220508150732772

分层结构

image-20220508150824441

数据层:数据Model,外部数据的增删查改

逻辑层:业务Entity,处理核心业务逻辑输出

视图层:视图view,处理和外部的交互逻辑

Repository

image-20220508151511830

如何查询?

Repository-index

image-20220508151621826

image-20220508151632223

初始化话题数据索引

image-20220508151711812

Repository-查询

通过话题ID查找话题

image-20220508151838965

Service

image-20220508152225875

image-20220508152200865

image-20220508152259235

并行处理

image-20220508152318335

image-20220508152343592

Controller

创建View对象

业务错误码

image-20220508152427379

Router

初始化数据索引

初始化引擎配置

构建路由

启动服务

image-20220508152715811

运行

运行测试go run server.go