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

145 阅读4分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第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
  • 依赖配置
  1. 语义化版本

${MAJOR}.${MINOR}.${PATCH}

大版本、新增函数、代码bug修复

  1. 基于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:github.com/bouk/monkey

快速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
  • 消息队列应用场景 强依赖,非强依赖(可以用消息队列进行解耦)
  • 多核优势