golang工程实践:将所学运用起来 | 青训营笔记

203 阅读5分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第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依赖分发-回源

image.png

  • 无法保证构建稳定性
  • 无法保证依赖可用性
  • 增加第三方压力

2.3.6依赖分发-Proxy

image.png

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图

image.png

4.3分层结构

image.png 数据层:数据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更多依赖系统调度。