本篇是整个系列的最初一篇了,原本打算在系列的最初一两篇写一下对于k8s部署相干的内容,在构思的过程中感觉本人对k8s常识的把握还很有余,在本人没有了解把握的前提下我感觉也很难写出本人称心的文章,大家看了可能也会感觉内容没有干货。我最近也在学习k8s的一些最佳实际以及浏览k8s的源码,期待时机成熟的时候可能会思考独自写一个k8s实战系列文章。

内容回顾

上面列出了整个系列的每篇文章,这个系列文章的次要特点是贴近实在的开发场景,并针对高并发申请以及常见问题进行优化,文章的内容也是循序渐进的,先是介绍了我的项目的背景,接着进行服务的拆分,拆分完服务进行API的定义和表构造的设计,这和咱们理论在公司中的开发流程是相似的,紧接着就是做一些数据库的CRUD基本操作,前面用三篇文章来解说了缓存,因为缓存是高并发的根底,没有缓存高并发零碎就无从谈起,缓存次要是应答高并发的读,接下来又用两篇文章来对高并发的写进行优化,最初通过分布式事务保障为服务间的数据一致性。如果大家可能对每一篇文章都能了解透彻,我感觉对于工作中的绝大多数场景都能轻松应答。

对于文章配套的示例代码并没有写的很欠缺,有几点起因,一是商城的性能点十分多,很难把所有的逻辑都笼罩到;二是少数都是反复的业务逻辑,只有大家把握了外围的示例代码,其余的业务逻辑能够本人把代码down下来进行补充欠缺,这样我感觉才会提高。如果有不了解的中央大家能够在社区群中问我,每个社区群都能够找到我。

go-zero微服务实战系列(一、开篇)

go-zero微服务实战系列(二、服务拆分)

go-zero微服务实战系列(三、API定义和表结构设计)

go-zero微服务实战系列(四、CRUD热身)

go-zero微服务实战系列(五、缓存代码怎么写)

go-zero微服务实战系列(六、缓存一致性保障)

go-zero微服务实战系列(七、申请量这么高该如何优化)

go-zero微服务实战系列(八、如何解决每秒上万次的下单申请)

go-zero微服务实战系列(九、极致优化秒杀性能)

go-zero微服务实战系列(十、分布式事务如何实现)

单元测试

软件测试由单元测试开始(unit test)。更简单的测试都是在单元测试之上进行的。如下所示测试的层级模型:

单元测试(unit test)是最小、最简略的软件测试模式、这些测试用来评估某一个独立的软件单元,比方一个类,或者一个函数的正确性。这些测试不思考蕴含该软件单元的整体零碎的正确定。单元测试同时也是一种标准,用来保障某个函数或者模块完全符合系统对其的行为要求。单元测试常常被用来引入测试驱动开发的概念。

go test工具

go语言的测试依赖go test工具,它是一个依照肯定约定和组织的测试代码的驱动程序。在包目录内,所有以_test.go为后缀的源代码文件都是 go test 测试的一部分,不会被go build编译到最终的可执行文件。

*_test.go文件中有三种类型的函数,单元测试函数、基准测试函数和示例函数:

类型格局作用
测试复数函数名前缀为Test测试程序的一些逻辑行为是否正确
基准函数函数名前缀为Benchmark测试函数的性能
示例函数函数名前缀为Example提供示例

go test会遍历所有*_test.go文件中合乎上述命名规定的函数,而后生成一个长期的main包用于调用相应的测试函数。

单测格局

每个测试函数必须导入testing包,测试函数的根本格局如下:

func TestName(t *testing.T) {    // ......}

测试函数的名字必须以Test结尾,可选的后缀名必须以大写字母结尾,示例如下:

func TestDo(t *testing.T) { //...... }func TestWrite(t *testing.T) { // ...... }

testing.T 用于报告测试失败和附加的日志信息,领有的次要办法如下:

Name() stringFail()Failed() boolFailNow()logDepth(s string, depth int)Log(args ...any)Logf(format string, args ...any)Error(args ...any)Errorf(format string, args ...any)Fatal(args ...any)Fatalf(format string, args ...any)Skip(args ...any)Skipf(format string, args ...any)SkipNow()Skipped() boolHelper()Cleanup(f func())Setenv(key string, value string)

简略示例

在这个门路下lebron/apps/order/rpc/internal/logic/createorderlogic.go:44 有一个生成订单id的函数,函数如下:

func genOrderID(t time.Time) string {    s := t.Format("20060102150405")    m := t.UnixNano()/1e6 - t.UnixNano()/1e9*1e3    ms := sup(m, 3)    p := os.Getpid() % 1000    ps := sup(int64(p), 3)    i := atomic.AddInt64(&num, 1)    r := i % 10000    rs := sup(r, 4)    n := fmt.Sprintf("%s%s%s%s", s, ms, ps, rs)    return n}

咱们创立createorderlogic_test.go文件并为该办法编写对应的单元测试函数,生成的订单id长度为24,单元测试函数如下:

func TestGenOrderID(t *testing.T) {    oid := genOrderID(time.Now())    if len(oid) != 24 {        t.Errorf("oid len expected 24, got: %d", len(oid))    }}

在以后门路下执行 go test 命令,能够看到输入后果如下:

PASSok      github.com/zhoushuguang/lebron/apps/order/rpc/internal/logic    1.395s

还能够加上 -v 输入更残缺的后果,go test -v 输入后果如下:

=== RUN   TestGenOrderID--- PASS: TestGenOrderID (0.00s)PASSok      github.com/zhoushuguang/lebron/apps/order/rpc/internal/logic    1.305s

go test -run

在执行 go test 命令的时候能够增加 -run 参数,它对应一个正则表达式,又有函数名匹配上的测试函数才会被 go test 命令执行,例如咱们能够应用 go test -run=TestGenOrderID 来值运行 TestGenOrderID 这个单测。

表格驱动测试

表格驱动测试不是工具,它只是编写更清晰测试的一种形式和视角。编写好的测试并不是一件容易的事件,但在很多状况下,表格驱动测试能够涵盖很多方面,表格里的每一个条目都是一个残缺的测试用例,它蕴含输出和预期的后果,有时还蕴含测试名称等附加信息,以使测试输入易于浏览。应用表格测试可能很不便的保护多个测试用例,防止在编写单元测试时频繁的复制粘贴。

lebron/apps/product/rpc/internal/logic/checkandupdatestocklogic.go:53 咱们能够编写如下表格驱动测试:

func TestStockKey(t *testing.T) {    tests := []struct {        name   string        input  int64        output string    }{        {"test one", 1, "stock:1"},        {"test two", 2, "stock:2"},        {"test three", 3, "stock:3"},    }    for _, ts := range tests {        t.Run(ts.name, func(t *testing.T) {            ret := stockKey(ts.input)            if ret != ts.output {                t.Errorf("input: %d expectd: %s got: %s", ts.input, ts.output, ret)            }        })    }}

执行命令 go test -run=TestStockKey -v 输入如下:

=== RUN   TestStockKey=== RUN   TestStockKey/test_one=== RUN   TestStockKey/test_two=== RUN   TestStockKey/test_three--- PASS: TestStockKey (0.00s)    --- PASS: TestStockKey/test_one (0.00s)    --- PASS: TestStockKey/test_two (0.00s)    --- PASS: TestStockKey/test_three (0.00s)PASSok      github.com/zhoushuguang/lebron/apps/product/rpc/internal/logic    1.353s

并行测试

表格驱动测试中通常会定义比拟多的测试用例,而go语言又天生反对并发,所以很容易施展本身劣势将表格驱动测试并行化,能够通过t.Parallel() 来实现:

func TestStockKeyParallel(t *testing.T) {  t.Parallel()    tests := []struct {        name   string        input  int64        output string    }{        {"test one", 1, "stock:1"},        {"test two", 2, "stock:2"},        {"test three", 3, "stock:3"},    }    for _, ts := range tests {        ts := ts        t.Run(ts.name, func(t *testing.T) {            t.Parallel()            ret := stockKey(ts.input)            if ret != ts.output {                t.Errorf("input: %d expectd: %s got: %s", ts.input, ts.output, ret)            }        })    }}

测试覆盖率

测试覆盖率是指代码被测试套件笼罩的百分比。通常咱们应用的都是语句的覆盖率,也就是在测试中至多被运行一次的代码占总的代码的比例。go提供内置的性能来查看代码覆盖率,即应用 go test -cover 来查看测试覆盖率:

PASScoverage: 0.6% of statementsok      github.com/zhoushuguang/lebron/apps/product/rpc/internal/logic    1.381s

能够看到咱们的覆盖率只有 0.6% ,哈哈,这是十分不合格滴,大大的不合格。go还提供了一个 -coverprofile 参数,用来将覆盖率相干的记录输入到文件 go test -cover -coverprofile=cover.out

PASScoverage: 0.6% of statementsok      github.com/zhoushuguang/lebron/apps/product/rpc/internal/logic    1.459s

而后执行 go tool cover -html=cover.out,应用cover工具来解决生成的记录信息,该命令会关上本地的浏览器窗口生成测试报告

解决依赖

对于单测中的依赖,咱们个别采纳mock的形式进行解决,gomock是Go官网提供的测试框架,它在内置的testing包或其余环境中都可能很不便的应用。咱们应用它对代码中的那些接口类型进行mock,不便编写单元测试。对于gomock的应用请参考gomock文档

mock依赖interface,对于非interface场景下的依赖咱们能够采纳打桩的形式进行mock数据,monkey是一个Go单元测试中非常罕用的打桩工具,它在运行时通过汇编语言重写可执行文件,将指标函数或办法的实现跳转到桩实现,其原理相似于热补丁。monkey库很弱小,然而应用时需注意以下事项:

  • monkey不反对内联函数,在测试的时候须要通过命令行参数-gcflags=-l敞开Go语言的内联优化。
  • monkey不是线程平安的,所以不要把它用到并发的单元测试中。

其余

画图工具

社区中常常有人问画图用的是什么工具,本系列文章中的插图工具次要是如下两个

https://www.onemodel.app/

https://whimsical.com/

代码标准

代码不光是要实现性能,很重要的一点是代码是写给他人看的,所以咱们对代码的品质要有肯定的要求,要遵循标准,能够参考go官网的代码review倡议

https://github.com/golang/go/...

谈谈感触

工夫过得贼快,人不知;鬼不觉间这个系列曾经写到十一篇了。依照每周更新两篇的速度也写了一个多月了。写文章是个体力活且十分的耗时,又惟恐有写的不对的中央,对大家产生误导,所以还须要重复的检查和查阅相干材料。均匀一篇文章要写一天左右,平时工作日比较忙,根本都是周六日来写,因而最近一个月周六日根本没有劳动过。

但我感觉播种也十分大,在写文章的过程中,对于本人把握的知识点,是一个温习的过程,能够让本人加深对知识点的了解,对于本人没有把握的知识点就又是一个学习新常识的过程,让本人把握了新的常识,所以我和读者也是一起在学习提高呢。大家都晓得,对于本人了解的常识,想要说进去或者写进去让他人也了解也是不容易的,因而写文章对本人的软实力也是有很大的晋升。

所以,我还是会持续保持写文章,保持输入,和大家一起学习成长。同时,我也欢送大家来 "微服务实际" 公众号来投稿。可能有些人感觉本人的程度不行,放心写的内容不高端,没有逼格,我感觉大可不必,只有能把知识点讲明确就十分棒了,能够是基础知识,也能够是最佳实际等等。kevin会对投稿的每一篇文章都认真审核,写的不对的中央他都会指出来,所有还有和kevin一对一交流学习的机会,小伙伴们放松口头起来呀。

结束语

非常感谢大家这一个多月以来的反对。看到每篇文章有那么多的点赞,我非常的开心,也更加的有能源,所以,也在打算写下个系列的文章,目前有两个待选的主题,别离是《go-zero源码系列》和《gRPC实战源码系列》,欢送小伙伴们在评论区留下你的评论,说出你更期待哪个系列,如果本篇文章点赞数超过66的话,咱就持续开整。

代码仓库: https://github.com/zhoushuguang/lebron

我的项目地址

https://github.com/zeromicro/go-zero

欢送应用 go-zerostar 反对咱们!

微信交换群

关注『微服务实际』公众号并点击 交换群 获取社区群二维码。