Go 语言进阶| 青训营笔记

171 阅读5分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天

从Goroutine开始
依赖管理[三代演进/mod文件/本地工具管理]
测试[单元测试/Mock测试/基准测试]
网页实战为例的项目开发框架

1. 并发Vs并行

1.1 Goroutine

通过go关键字开启协程
需要处理主协程和子协程执行的协调关系(time.sleep阻塞主协程)

所处关联量级
协程用户态轻量级线程栈KB级别
线程内核态线程跑多个协程栈MB级别

1.2 协程之间通信——CSP Communicating Sequential Processes

Go提倡通过通信共享内存 rather than 通过共享内存实现通信(临界区)

1.3 channel

make(chan type [,缓冲大小])
无缓冲
有缓冲 [应对生产和消费速度不一致的场景]

1.4 并发安全 Lock

通过共享内存实现通信时对临界区的权限控制保证并发安全(sync.Mutex),在协程中每次执行操作前用lock获取临界区资源,操作后再释放资源(unlock)。项目开发中应避免对共享内存做读写操作

1.5 sync.WaitGroup实现并发协程的同步

内部维护了一个计数器

  • Add(delta int)计数器+delta
  • Done() 计数器—1
  • Wait() 阻塞到计数器归零

2. 依赖管理【类比java的maven】

2.1 Go的依赖管理演进

2.1.1 GOPATH

  • bin 项目编译的二进制文件
  • pkg 编译的中间产物,加速编译
  • src 项目源码

go set 下载包到src下
弊端:无法实现package的多版本控制

2.1.2 Go Vendor

项目目录下增加vendor文件,存放当前项目依赖的副本(如不存在再找GOPATH)
解决了多个项目需要同一个package依赖的冲突问题
弊端:无法控制版本,更新项目可能出现依赖冲突

2.1.3 Go Module 定义版本规则和管理项目依赖关系

go.mod 管理版本 go mod/get 管理包

2.2 依赖配置

2.2.1 go.mod

  • 依赖管理基本单元
  • 原生库标识
  • 单元依赖
    • 依赖标识:modulePath + version

2.2.2 version

  • 语义化版本 MAJOR.MINOR.PATCH 大版本+兼容的函数增加+bug修复
  • 基于commit伪版本 v+时间戳+12位hash前缀

2.2.3 标识符

//indirect 间接依赖
+incompatible用于没有go.mod且主版本2+的依赖

2.2.4 依赖图

原则:选择最低的兼容版本

2.3 依赖分发(中心仓库管理依赖库

2.3.1 回源

直接从第三方代码托管平台拉取的问题:无法保证构建稳定性、依赖可用性,增加第三方压力

2.3.2 proxy代理 GOPROXY

缓存内容 稳定可靠的依赖分发
环境变量GOPROXY = "proxy1.cn, proxy1.cn, direct" 服务站点URL列表(类似于缓存设计,寻找路径), direct表示源站

2.4 本地工具

2.4.1 go get example.org/pkg

  • @update 默认拉取major版本的最新提交
  • @none 删除依赖
  • @v1.1.2 tag版本,语义版本
  • @23ddfd5 指定commit
  • @master 分支的最新commit

2.4.2 go mod

  • init 初始化 创建go.mod文件
  • download 下载模块到本地缓存
  • tidy 按需要增删依赖

3.测试

go test

-v 打印每个测试函数的名字和运行时间
-run = 一个正则表达式,被正确匹配的测试函数才会被go test测试命令运行

  • 测试文件以 _test.go 结尾
  • 测试函数
    • 需导入"testing"包
    • 签名: func TestName(t *testing.T) //t参数用于报告测试失败和附加的日志信息
func TestMain(m *testing.M) {
   // do init 数据装载、配置初始化等工作
   code := m.Run() // 执行测试
   // do close 释放资源等收尾工作
   os.Exit(code)
}

回归(关键性模块)--集成(系统功能的自动化测试)--单元(覆盖率最高,成本最低)

3.1 单元测试

输入测试单元,输出结果与期望校对
可在较短周期内定位修复问题。

import(
    "github.com/stretchr/testify/assert"
    "testing"
)

func TestHelloIllTamer(t *testing.T) {
    output := HelloIllTamer()
    expectOutput := "IllTamer"
    //if output != expectOutput {
        //t.Errorf("Not match: expected %s but %s", expectOutput, output)
    //}
    assert.Equal(t, expectOutput, output)
}
  • 覆盖率: 经过验证的代码行比例 可累计
    • go命令行的最后加上 --cover
    • 建议:测试分支相互独立 全面覆盖; 测试单元粒度够小,函数单一职责

3.2 Mock测试

mock 意为伪造,利用反射机制替换某些如读取文件之类的函数,达到测试稳定且幂等的结果(屏蔽对本地文件的依赖) 例:使用开源的github.com/bouk/monkey, 可为函数/方法打桩

func TestProcessFirstLineMock(t *testing.T) {
   monkey.Patch(ReadFirstLine, func() string) {
      return "line110"
   }
   defer monkey.Unpatch(ReadFirstLine)//卸载桩函数
   output := ProcessFirstLine()
   assert.Equal(t, "line000", output)
}

3.3 基准测试

测试代码性能

  • 测试
    • go test -bench=.
      
  • 测试函数
    • 签名: func BenchmarkName(b *testing.B)
// 串行测试
func BenchmarkRandom(b *testing.B) {
   Init()
   b.ResetTimer()//重置计时器,之前的init不作为基准测试的范围
   for i := 0; i < b.N; i++ {//用b中的N值反复循环测试直到
      //dosth
   }
}

// 并行测试(rand函数为保证全局的随机性和并发安全,内置了全局锁,并发性能下降)
//可使用fastRand代替rand github.com/bytedance/gopkg
func BenchmarkRandomParallel(b *testing.B) {
   b.ResetTimer()
   b.RunParallel(func(pb *testing.PB) {//多协程并发测试
      for pb.Next() {
         //dosth
      }
   })
}

4. 项目实战

4.1 需求设计

  • ER图 entity-relationship
  • 分层结构 [通用]
    • 数据层 Model, 关联底层数据模型,封装外部数据的增删查改
      • 对Service层透明(接口模型不变),屏蔽下游数据的差异
    • 逻辑层 Entity, 处理业务核心逻辑输出,计算打包业务实体Entity上送给view
    • 视图层 View 处理和外部的交互逻辑,以view视图的形式返回给客户端
      • 此处要求实现 封装JSON格式化的请求结果,api形式访问 image.png

4.2 代码开发

4.2.1 Repository

结构体定义

type Topic struct {
   Id         int64  `json:"id"`
   Title      string `json:"title"`
   Content    string `json:"content"`
   CreateTime int64  `json:"create_time"`
}

type Post struct {
   Id         int64  `json:"id"`
   ParentId   int64  `json:"parent_id"`
   Content    string `json:"content"`
   CreateTime int64  `json:"create_time"`
}
//gorm操作数据库

index——map实现内存索引,在服务对外暴露前,利用文件元数据初始化全局内存索引,实现O(1)的时间复杂度查找操作。

var (
    topicIndexMap map[int64]*Topic
    postIndexMap map[int64][]*Post
)

查询操作:直接根据key获得map中的value

  • sync.once主要适用于高并发场景下只执行一次的场景 减少内存浪费

4.2.2 Service

流程:参数校验——准备数据——组装实体(可并行处理)

type PageInfo struct{
    Topic      *repository.Topic
    PostList   []*repository.Post
}

4.2.3 Controller

构建view对象
注意PageData中的code和msg反映业务执行状态

4.2.4 Router

通过Gin搭建外部框架:

  • 初始化数据索引
  • 初始化引擎配置
  • 构建路由
  • 启动服务

4.3 测试运行

go run server.go
配合终端curl --location --request GET 'http://0.0.0.0:8080/community/page/get/2' | json