后端青训营笔记1(go 语法及特性)

119 阅读6分钟

go 基础语法

简介

  1. 高性能,高并发
  2. 标准库和工具链完善
  3. 静态链接,快速编译
  4. 跨平台
  5. gc
  6. 市场大

基础语法

字符串操作

stringCount , Contains , HasPrefix , HasSuffix , Index , Join , Repeat , Replace , Split , ToLower , ToUpper

格式化

能用 %v 打印任意类型变量。

JSON 操作

JSON.Marshaler 序列化为JSON字符串,用 JSON.unmarshaler 反序列化为结构体。默认序列化的字符串风格是大写字母而不是下划线。

字符串转换数字

strconv 库。

进程信息

os.argv 得到命令行参数,用 os.getenv 读取环境变量

用 defer 关闭流

例子

猜词游戏

在线词典

主要是看一下代码生成

用浏览器的 copy as curl 功能获得 curl 命令

curlconvert.com 上进行转换成 go 代码

要序列化获得的 JSON 字符串也可以用 oktools.net/json2go 转个结构体

socks5 代理

socks5 机制:

浏览器和 socks5 代理建立TCP连接,代理再和真正的服务器建立TCP连接

  1. 建立一个基础的 echo server

  2. auth处理,client 给代理服务器发包,包括version,methods数量和每个method的编码。写一个函数来验证是否valid。

// +----+----------+----------+
// |VER | NMETHODS | METHODS |
// +----+----------+----------+
// | 1 | 1 | 1 to 255 |
// +----+----------+----------+
// VER: 协议版本,socks5为0x05
// NMETHODS: 支持认证的方法数量
// METHODS: 对应NMETHODS,NMETHODS的值为多少,METHODS就有多少个字节。RFC定义了一些值的含义,内容如下:
// X’00’ NO AUTHENTICATION REQUIRED
// X’02’ USERNAME/PASSWORD
  1. 请求阶段,与 auth 阶段类似,需要解析 clients 在 auth 成功后向 socks5 发出的请求包,这里要注意由 ADDR 选择 ATYP 的处理方式。
// +----+-----+-------+------+----------+----------+
// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
// VER 版本号,socks5的值为0x05
// CMD 0x01表示CONNECT请求
// RSV 保留字段,值为0x00
// ATYP 目标地址类型,DST.ADDR的数据对应这个字段的类型。
// 0x01表示IPv4地址,DST.ADDR为4个字节
// 0x03表示域名,DST.ADDR是一个可变长度的域名
// DST.ADDR 一个可变长度的值
// DST.PORT 目标端口,固定2个字节
  1. reply 阶段,通过请求包中的 ATYP 和 PORT 得到真实的服务器地址,并在其中起到转发的作用。

这里需要注意,建立 client 到下游 server 的双向数据转发的实现方式:

由标准库的 io.Copy 来实现单向数据转发,启动两个 goroutinue 来实现双向数据转发。同时要注意,由于 connect 的立即返回机制,返回时连接就会被关闭。所以需要等待任意一个方向的 Copy 被中断,再返回 connect 函数。这里运用标准库的 context 机制,用 context.WithCancel 建立一个 context ,在最后等待 ctx.Done (会在 cancel 被调用后返回) 后再返回。

浏览器的代理测试需要安装 switchchomega 插件,在里面新建一个情景模式。

语言进阶

go 可以充分发挥多核优势,高效运行

通过用户态的协程(goroutine),线程算内核态

协程即轻量级的线程,开销是kb级的,即可以同时开甚至上万协程。

go func(){
//some code here
}()

协程通信

go提倡通过通信共享内存而不是通过共享内存实现通信,即channel (先入先出的通信队列)

make(chan [type],[buffer size] )

可以用通道实现生产消费模型

通道当然是并发安全的

消费者的队列带缓冲就可以减低因消费者消费速率慢对生产者生产的影响。

lock

若要使用共享内存,要加锁才能保证并发安全,go的锁是sync.Mutex(当然还有更多种锁)

sync.WithGroup

计数器,由Add(int),Done(),Wait()三个方法,由计数器实现并发。开始先由 Add 确定计数器大小,通过Done 来使计数器减1,在主线程里用 Wait 阻塞至计数器为0

依赖管理

管理依赖库

历史

gopath -> govendor -> gomodule

实现不同项目依赖版本不同,且不同依赖版本可以不同

gopath

相当于 go 里面的工作区

|-bin
|-pkg
 -src

弊端:无法实现package的不同版本

govendor

在项目下增加vendor文件夹,所有依赖包副本放在这下面,找不到的再去找gopath

弊端:无法控制依赖版本,更新项目可能又出现依赖冲突。

gomodule

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

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

go.mod

module example/projects/app //依赖管理的基本单元


go 1.20 //原生库


require( //单元依赖
    example/lib1 v1.0.1 //indirect(此关键字表示间接依赖)
    example/lib2 v3.2.0+incompatible(没有go.mod文件且主版本2+的依赖)
)

依赖配置

语义化版本

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

major是不兼容的

minor是新功能

patch是修复补丁

基于 commit 伪版本

vX.0.0-yyyymmddhhmmss-abcdefgh1234

版本选择

选择最低的兼容版本

依赖分发 Proxy

github等管理平台,SVN等

第三方仓库问题:

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

go的解决方法:通过官方的goProxy,直接在goProxy拉取依赖。

go mod通过GOPROXY环境变量,是个用逗号分割的url列表。

在设计缓存的场景是一致的。

go get 和 go mod

go get 默认拉取最新版本依赖,@none是删除依赖,@具体版本就是那一个版本的依赖

go mod 实际上是一个项目工具,init是初始化,download是下载模块到本地缓存,tidy是增加需要依赖,删除不需要的依赖。

测试

开发 -> 测试 -避免-> 事故

回归测试:测试人员上线时对app

集成测试:测试人员上线前对app,统一

单元测试:开发者编写时对函数

单元测试

package abs

  
import "testing"

  
func TestAbs(t *testing.T) {
    got := Abs(-1)
    if got != 1 {
        t.Errorf("Abs(-1) = %d; want 1", got)
    }
}

通过单元测试,分模块进行

保证代码质量,提升错误定位效率

对 go, 所有文件以 _test.go 结尾,函数为 TestXxx(*testing T),初始化逻辑在 TestMain 里面

assert 包进行判断

覆盖率

评估单元测试的指标,提升覆盖率 -> 提升单元测试完备度

一般覆盖率在50-60%,较高覆盖率80%+

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

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

Mock测试

外部(文件,包)依赖 => 稳定&幂等 -> mock 机制

github.com/bouk/monkey

为一个函数或变量打桩(用一个函数临时替换到另一个函数)

func main() {
    monkey.Patch(fmt.Println, func(a ...interface{}) (n int, err error) {
        s := make([]interface{}, len(a))
        for i, v := range a {
            s[i] = strings.Replace(fmt.Sprint(v), "hell", "*bleep*", -1)
        }
        return fmt.Fprintln(os.Stdout, s...)
    })
    fmt.Println("what the hell?") // what the *bleep*?
}

就可以不依赖本地文件(可以)

基准测试

优化代码,对当前代码性能分析,内置测试框架提供了基准测试的能力。

func BenchmarkSearch(b *testing.B) {
for i := 0; i < b.N; i++ {
Search("hello", "hello world")
}
}
go test -bench=. -benchmem

项目实战

实现社区话题页面,展示话题和回帖列表,话题和回帖数据用文件。

留给下一篇。