02-Go TestBenchmark

Go TestBenchmark笔记
Table of Contents =================

Go Test Benchmark

  • 在 Go 中,通过撰写 Benchmark 函数可以很方便地对某个功能点进行性能检测。对于重要的函数,我们可以在 CI/CD 中添加相应的测试流程,当函数性能发生变化时能够及时感知。那问题来了,如何检测函数的性能变化?
  • 换个说法,你编写了某功能函数但发现它运行很慢,需要对该函数进行优化,当你在谷歌搜索找到更好的实现方式,通过 Benchmark 函数发现它的确变快了。但你说不清楚具体变快了多少,你想知道函数优化前后的性能对比,提高多少百分点,可信度高吗?
  • 针对以上的需求场景,有一个工具可以帮助到你,它就是 benchstat

1.1 go test benchmark 示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// fib.go
func FibSolution(n int) int {
 if n < 2 {
  return n
 }

 return FibSolution(n-1) + FibSolution(n-2)
}

// fib_test.go
func BenchmarkFib20(b *testing.B) {
 for i := 0; i < b.N; i++ {
  FibSolution(20)
 }
}

// 命令行执行go test -bench=BenchmarkFib20得到性能结果

执行 go test -bench=BenchmarkFib20 得到性能结果:

01-消息队列Kafka和Pulsar选型

消息队列Kafka和Pulsar选型

Table of Contents

1: 业务场景

1.1 业务场景描述

  • 我们需要一个把实时的交易数据进行持久化保存,并且能快速的给其他服务提供最新的交易数据;
  • 当服务启动或者重启后,能快速提供完整的并且最新的交易数据;
  • 现有的机制是每个订单使用 go 协程直接写入 Mysql 数据库,对数据库的压力比较大,且系统的延迟比较高;

1.2 业务场景 feature

  • 解耦,降低数据数据库性能导致的整个服务延迟;
  • 数据完整的,及时地进行持久化;
  • 交易数据能,迅速的提供给其他需要数据的服务;
  • 流量消峰,减少峰值流量直接对数据库服务造成的冲击, 提高系统的稳定性;

2:消息中间件

  • 适合用消息中间件解决上述问题;
    • (1) 解耦: 将一个流程的上游和下游拆开,上游专注生产消息,下游专注处理消息;
    • (2) 广播: 一个上游生产的消息轻松被多个下游服务消费处理;
    • (3) 缓冲(流量削峰): 如果上游服务流量突然暴涨,mq 可以做一个缓冲器的作用,下游根据消费能力对消息进行消费,避免暴涨的流量直接对下游服务造成冲击;
    • (4) 异步: 生产者生产消息之后可以马上直接返回,消费者可以异步处理消息;
    • (5) 冗余: 保留历史消息,处理失败或者当出现异常的时候可以进行重试或者回溯,防止消息丢失。
  • 近几年出现了一些关注度较高的消息队列中间件选型,如 Kafka、Pulsar、RocketMQ 等,首先从宏观上做一些对比:
    常见MQ对比
    MQ特性描述KafkaPulsarRocketMQRabbitMQNSQ我们的业务场景是否需要
    推出时间2012 年(Scala 和 Java)2016 年(Java)2012 年(Java)2007 年(Erlang)2013 年(Go)
    组织Linkin 开源,ApacheYahoo 开源,Apache阿里开源,ApachePivotal 开源,MozillaMIT
    功能消费模式consumer消费消息的方式pullpushpullpushpush?
    延迟队列消息投递延迟NoYesYesYesYes
    死信队列NoYesYesYesNo?
    优先级队列NoNoNoYesNo
    消息回溯YesYesYesNoNo需要
    消息持久化YesYesYesYesYes需要
    消息确认机制offsetoffset+单条offset单条单条需要
    消息TTL消息TTL表示一条消息的生存时间,如果消息发出来后,在TTL的时间内没有消费者进行消费,消息队列会将消息删除或者放入死信队列中YesYesYesYesNo需要
    多租户隔离NoYesNoNoNo?
    消息顺序性消息顺序性是指保证消息有序。消息消费顺序跟生产的顺序保持一致分区有序stream模式有序consumer加锁NoNo?
    消息查询查看MQ中消息的内容,比如通过某个MessageKey/ID,查询到MQ的具体消息NoYesYesYesNo需要
    消费模式stream模式流模式+队列模式广播模式+集群模式队列模式队列模式需要,根据场景选择消费模式
    消息可靠性以生产的消息,发送到mq,防止丢失request.required.acksAck Quorum Size(Qa)RocketMQ与Kafka类似RabbitMQ是主从架构,通过镜像环形队列实现多副本及强一致性语义的NSQ会通过go-diskqueue组件将消息落盘到本地文件中,通过mem-queue-size参数控制内存中队列大小,如果mem-queue-size=0每条消息都会存储到磁盘里,不用担心节点重启引起的消息丢失。但由于是存储在本地磁盘中,如果节点离线,堆积在节点磁盘里的消息会丢失非常重要
    性能单机吞吐量605MB/S605MB/S大概500MB/S38MB/S?重要
    消息延迟5ms5msms级us级非常重要
    支持Topics数百~千,过多会影响性能百万个百~千几千?
    运维与可靠性高可用分布式架构分布式架构Master/SlaveMaster/Slave分布式架构非常重要
    集群扩容增加node,node之间会复制数据均衡增加node,通过新增加分片进行负载均衡增加节点增加节点增加节点
    异地容灾

3: Kafka 特性

4: Pulsar 特性

5: Kafka 和 Pulsar 对比

01-git仓库gomod私有化

git仓库gomod私有化

1: go module 私有仓库使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
(1) 修改hosts
(2)
go env -w GOINSECURE="gitlab.yuliangtec.cn"
go env -w GONOSUMDB="gitlab.yuliangtec.cn"
go env -w GONOPROXY="gitlab.yuliangtec.cn"
go env -w GOPRIVATE="gitlab.yuliangtec.cn"

// (3) go get
go get com.yuliangtec.luna.proto


// (4) git tag --> go get
go get com.yuliangtec.luna.proto@v1.0.0

2: 补充

2.1 协议

2.2 设置环境变量(上 1)

https://blog.csdn.net/L_K1028/article/details/105313293

02-GRPC_Makefile

GRPC Makefile脚本
  • Makefile文件内容如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76

GOPATH:=$(shell go env GOPATH)
VERSION=$(shell git describe --tags --always)
INTERNAL_PROTO_FILES=$(shell find internal -name *.proto)
API_PROTO_FILES=$(shell find proto -name *.proto)



.PHONY: init
# init env
init:
	go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
	go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
	go install go.unistack.org/protoc-gen-go-micro/v3@latest
	go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway@latest
	go install github.com/google/gnostic/cmd/protoc-gen-openapi@latest


.PHONY: config
# generate internal proto

.PHONY: api
# generate api proto
api:
	protoc --proto_path=. \
	       --proto_path=./third_party \
 	       --go_out=paths=import:. \
 	       --go-http_out=paths=import:. \
 	       --go-grpc_out=paths=import:. \
 	       --go-micro_out=debug=true,,components="micro",paths=import:. \
	       $(API_PROTO_FILES)

 	    #    --go-micro_out=debug=true,,components="micro|http",paths=source_relative:./go_proto
		#    --openapi_out==paths=source_relative:./go_proto
		#    --swagger_out=logtostderr=true:./go_proto

.PHONY: clean
# rm -rf "./go_proto/pb"
clean:
	find ./go_proto -name "*.pb.go" | xargs rm -f

.PHONY: build
# build
build:
	mkdir -p bin/ && go build -ldflags "-X main.Version=$(VERSION)" -o ./bin/ ./...

.PHONY: generate
# generate
generate:
	go generate ./...

.PHONY: all
# generate all
all:
	make api;
	# make generate;

.PHONY: help
# show help
help:
	@echo ''
	@echo 'Usage:'
	@echo ' make [target]'
	@echo ''
	@echo 'Targets:'
	@awk '/^[a-zA-Z\-\_0-9]+:/ { \
	helpMessage = match(lastLine, /^# (.*)/); \
		if (helpMessage) { \
			helpCommand = substr($$1, 0, index($$1, ":")-1); \
			helpMessage = substr(lastLine, RSTART + 2, RLENGTH); \
			printf "\033[36m%-22s\033[0m %s\n", helpCommand,helpMessage; \
		} \
	} \
	{ lastLine = $$0 }' $(MAKEFILE_LIST)

.DEFAULT_GOAL := help

01-Go Dockerfile

Go Dockerfile模板

1:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
FROM golang:alpine AS builder

LABEL stage=gobuilder

ENV CGO_ENABLED 0
ENV GOOS linux
ENV GOPROXY https://goproxy.cn,direct

WORKDIR /build

ADD go.mod .
ADD go.sum .
RUN go mod download
COPY . .
RUN go build -ldflags="-s -w" -o /app/hello ./hello.go


FROM alpine

RUN apk update --no-cache && apk add --no-cache ca-certificates tzdata
ENV TZ Asia/Shanghai

WORKDIR /app
COPY --from=builder /app/hello /app/hello

CMD ["./hello"]

2: 说明

  • 默认禁用了 cgo
  • 启用了 GOPROXY
  • 去掉了调试信息 -ldflags="-s -w" 以减小镜像尺寸
  • 安装了 ca-certificates,这样使用 TLS 证书就没问题了
  • 自动设置了本地时区,这样我们在日志里看到的是北京时间了

02-适用于Go项目的Makefile指南

适用于Go项目的Makefile指南
Table of Contents =================

1: Premise

  • 会使用 go 写项目
  • 会使用 Makefile

2: Makefile 语法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
PROJECT="example"

default:
    echo ${PROJECT}

install:
    @govendor sync -v

test: install
    @go test ./...

.PHONY: default install test
  • makefile 格式介绍
    1
    2
    
    <target> : <prerequisites>
    [tab] <command>
    
  • target : 即自定义的想要执行的命令;
  • prerequisites: 前置条件,即执行 target 命令之前执行的命令;
  • commands : 具体的执行的命令;
  • .PHONY 伪指令,内置的关键字;
  • 不带参数,默认执行第一个 target;
  • @ 表示禁止回声,即终端不会打印真实的执行命令;
  • # 表示注释;
  • ${val}表示变量,和 shell 脚本中的变量的声明和使用一致;
  • 允许使用 通配符;

3: Go 项目

  • Go 中支持内置的 go 命令,可以用来执行:测试、编译、运行、语法检查等命令
  • 一个完善的 Go 项目经常会执行哪些命令?
    • go vet 静态检查
    • go test 运行单元测试
    • go fmt 格式化
    • go build 编译
    • go run 运行 …
  • 所以一个适用于 Go 项目的 Makefile 也应该支持这些命令。
    • make default : 编译
    • make fmt: 格式化
    • make vet: 静态检查
    • make test: 运行测试
    • make install: 下载依赖库
    • make clean: 移除编译的二进制文件
  • 所以整体可以如下安排:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
BINARY="example"
VERSION=1.0.0
BUILD=`date +%FT%T%z`

PACKAGES=`go list ./... | grep -v /vendor/`
VETPACKAGES=`go list ./... | grep -v /vendor/ | grep -v /examples/`
GOFILES=`find . -name "*.go" -type f -not -path "./vendor/*"`

default:
    @go build -o ${BINARY} -tags=jsoniter

list:
    @echo ${PACKAGES}
    @echo ${VETPACKAGES}
    @echo ${GOFILES}

fmt:
    @gofmt -s -w ${GOFILES}

fmt-check:
    @diff=?(gofmt -s -d $(GOFILES)); \
    if [ -n "$$diff" ]; then \
        echo "Please run 'make fmt' and commit the result:"; \
        echo "$${diff}"; \
        exit 1; \
    fi;

install:
    @govendor sync -v

test:
    @go test -cpu=8 -v -tags integration ./...

vet:
    @go vet $(VETPACKAGES)

docker:
    @docker build -t wuxiaoxiaoshen/example:latest .

clean:
    @if [ -f ${BINARY} ] ; then rm ${BINARY} ; fi

.PHONY: default fmt fmt-check install test vet docker clean

4: supplementary instruction

Makefile 构建工具,大大的简化了构建项目的难度。 真实的生产环境下,需要使用到 CI/CD(持续集成和持续部署), 所以 Makefile 也通常用来和 CI 工具配合使用。 比如新合并的代码,先触发单元测试,静态检查等,在执行 CI 脚本,成功之后,再构建镜像,推送镜像到服务器上,完成持续集成和持续部署一整套流程。 Makefile 通常配合 travis 使用。如下:

01-Go-transaction事务几种方式

Go-transaction事务几种方式

1:方式一

  • 这种写法非常朴实,程序流程也非常明确,但是事务处理与程序流程嵌入太深,容易遗漏,造成严重的问题
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func DoSomething() (err error) {
    tx, err := db.Begin()
    if err != nil {
        return
    }


    defer func() {
        if p := recover(); p != nil {
            tx.Rollback()
            panic(p)  // re-throw panic after Rollback
        }
    }()


    if _, err = tx.Exec(...); err != nil {
        tx.Rollback()
        return
    }
    if _, err = tx.Exec(...); err != nil {
        tx.Rollback()
        return
    }
    // ...


    err = tx.Commit()
    return
}

2: 方式二

  • 下面这种写法把事务处理从程序流程抽离了出来,不容易遗漏,但是作用域是整个函数,程序流程不是很清晰
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func DoSomething() (err error) {
    tx, err := db.Begin()
    if err != nil {
        return
    }


    defer func() {
        if p := recover(); p != nil {
            tx.Rollback()
            panic(p) // re-throw panic after Rollback
        } else if err != nil {
            tx.Rollback()
        } else {
            err = tx.Commit()
        }
    }()


    if _, err = tx.Exec(...); err != nil {
        return
    }
    if _, err = tx.Exec(...); err != nil {
        return
    }
    // ...
    return
}

3: 方式三

  • 写法三是对写法二的进一步封装,写法高级一点,缺点同上
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
func Transact(db *sql.DB, txFunc func(*sql.Tx) error) (err error) {
    tx, err := db.Begin()
    if err != nil {
        return
    }


    defer func() {
        if p := recover(); p != nil {
            tx.Rollback()
            panic(p) // re-throw panic after Rollback
        } else if err != nil {
            tx.Rollback()
        } else {
            err = tx.Commit()
        }
    }()


    err = txFunc(tx)
    return err
}


func DoSomething() error {
    return Transact(db, func (tx *sql.Tx) error {
        if _, err := tx.Exec(...); err != nil {
            return err
        }
        if _, err := tx.Exec(...); err != nil {
            return err
        }
    })
}

01-ElasticSearch7-Restful APIs

ElasticSearch7-Restful APIs
  * [1: /ES-API/说明](#1-es-api说明)
  * [2: Cluster - APIs](#2-cluster---apis)
     * [2.1 查询集群状态](#21-查询集群状态)
     * [2.2 查询集群索引信息](#22-查询集群索引信息)
     * [2.3 使用 help 参数查询](#23-使用-help-参数查询)
     * [2.4 查询集群中的节点信息](#24-查询集群中的节点信息)
  * [3: Index - APIs](#3-index---apis)
     * [3.1 创建索引](#31-创建索引)
     * [3.2 获取索引](#32-获取索引)
     * [3.3 获取全部索引](#33-获取全部索引)
  * [4: Doc - APIs](#4-doc---apis)
     * [4.1 创建文档](#41-创建文档)
     * [4.2 删除文档](#42-删除文档)
     * [4.3 查看文档](#43-查看文档)
     * [4.4 查看该索引下的全部文档](#44-查看该索引下的全部文档)
     * [4.5 覆盖数据](#45-覆盖数据)
     * [4.6 更新数据](#46-更新数据)
  * [5: Mapping - APIs](#5-mapping---apis)
     * [5.1 创建 Mapping](#51-创建-mapping)
     * [5.2 查询 Mapping](#52-查询-mapping)
     * [5.3 查询 template](#53-查询-template)
  * [6: Alias - APIs](#6-alias---apis)
     * [6.1 添加 Alias](#61-添加-alias)
     * [6.2 查询 Alias](#62-查询-alias)
  * [7:查询 - APIs](#7查询---apis)
     * [7.1 条件查询](#71-条件查询)
     * [7.2 匹配查询](#72-匹配查询)
     * [7.3 全量查询](#73-全量查询)
     * [7.4 分页查询](#74-分页查询)
     * [7.5 分页过滤](#75-分页过滤)
     * [7.6 排序查询](#76-排序查询)
     * [7.7 组合查询](#77-组合查询)
     * [7.3 分词查询](#73-分词查询)
     * [7.9 完全匹配查询](#79-完全匹配查询)

1: /ES-API/说明

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 基本查询模板

​```json
{
  "query": {
    "bool": {
      "filter": [
        //filter 这后面是过滤条件
        { "term": { "uri": "111111" } }, //客户号
        {
          "range": { "rundata_date": { "gte": "20190108", "lte": "20190110" } }
        } //时间过滤,注意此字段类型,string不能过滤
      ]
    }
  },
  "aggregations": {
    //这里我主要关注一个指标,handleTime字段,标示执行时间,主要对它进行监控
    "avg_handleTime": {
      //可以自己命名
      "avg": {
        "field": "handleTime" //平均执行时间
      }
    },
    "percent_handleTime": {
      "percentiles": {
        "field": "handleTime",
        "percents": [50, 95, 99] //这个是现实 50、95、99的线,从小到大,例如到95%执行时间为1.5s,可以看满足预期的比例
      }
    },
    "min_handleTime": {
      "min": {
        "field": "handleTime" //最小执行时间
      }
    },
    "max_handleTime": {
      "max": {
        "field": "handleTime" //最大执行时间
      }
    }
  },
  "size": 0 //显示几条数据,我这里不需要显示,可以根据需要修改
}
1
2
3
## /ES-API/Cluster - APIs
​```text
集群相关APIS
  • 公共 Header 参数
参数名示例值参数描述

暂无参数

01-Go Test笔记

Go Test笔记

Go Test

  • 一个完整的单测指令可以是 go test -v -cover -gcflags=all=-l -coverprofile=coverage.out
  • -gcflags=all=-l 防止编译器内联优化导致单测出现问题

1.1 go test

go test -run=^TestDo -v ./ 这里介绍几个常用的参数:

  • -bench regexp 执行相应的 benchmarks,例如 -bench=.;
  • -cover 开启测试覆盖率;
  • -trace=copy_trace.out 生成 trace.out 文件(go tool trace copy_trace.out)
  • -run regexp 只运行 regexp 匹配的函数,例如 -run=Array 那么就执行包含有 Array 开头的函数;
  • -count 执行次数。
  • -v 显示测试的详细命令。

1.2 go test cover 生成测试覆盖度报告

go tool cover -html=coverage.out