一個快速生成web和微服務程式碼工具

zhuyasen發表於2022-12-15

sponge 是一個微服務框架,一個快速建立web和微服務程式碼工具。sponge擁有豐富的生成程式碼命令,一共生成12種不同功能程式碼,這些功能程式碼可以組合成完整的服務(類似人為打散的海綿細胞可以自動重組成一個新的海綿)。微服務程式碼功能包括日誌、服務註冊與發現、註冊中心、限流、熔斷、鏈路跟蹤、指標監控、pprof效能分析、統計、快取、CICD等功能,開箱即用。程式碼使用解耦分層結構,很容易的新增或替換功能程式碼。作為一個提升效率工具,常用的重複程式碼基本是自動生成,只需要根據生成的模板程式碼示例填充業務邏輯程式碼。

github:github.com/zhufuyi/sponge


1 sponge功能介紹

1.1 sponge 命令框架

生成程式碼基於YamlSQL DDLProtocol buffers三種方式,每種方式擁有生成不同功能程式碼,生成程式碼的框架圖如圖1-1所示:
sponge-framework
圖1-1 spong生成程式碼框架圖


  • Yaml生成配置檔案對應go struct程式碼。
  • SQL DDL生成的程式碼包括 http、handler、 dao、 model、 proto、rpc、service,分為web和rpc兩大型別,web和rpc型別都擁有自己的子模組,每個模組程式碼都可以單獨生成,模組之間像洋蔥一層一層獨立解耦。程式碼包括了標準化的CRUD(增刪改查)業務邏輯,可以直接編譯就使用。
    • web型別程式碼: 生成http服務程式碼包括 handler、 dao、model 三個子模組程式碼,如圖所示向內包含,同樣原理,生成handler模組程式碼包含dao、 model兩個子模組程式碼。
    • rpc型別程式碼:生成rpc服務程式碼包括 service、dao、model、protocol buffers 四個子模組,如圖所示向內包含,生成service模組程式碼包括 dao、model、protocol buffers 三個子模組程式碼。
  • Protocol buffers生成的程式碼包括 http-pb, rpc-pb, rpc-gw-pb,同樣分為web和rpc兩大型別,其中 http-pb, rpc-pb 通常結合SQL DDL生成的dao、model程式碼使用。
    • http-pb:http服務程式碼包括router、handler模板兩個子模組,不包括運算元據庫子模組,後續的業務邏輯程式碼填寫到handler模板檔案上。
    • rpc-pb:rpc服務程式碼包括service模板一個子模組,不包括運算元據庫模組,後續的業務邏輯程式碼填寫到service模板檔案上。
    • rpc-gw-pb:rpc閘道器其實是http服務,包括router和service模板兩個子模組,這裡的service模板程式碼是呼叫rpc服務相關的業務邏輯程式碼。

在同一個資料夾內,如果發現最新生成程式碼和原來程式碼衝突,sponge會取消此次生成流程,不會對原來程式碼有任何修改,因此不必擔心寫的業務邏輯程式碼被覆蓋問題。


1.2 微服務框架

sponge建立的微服務程式碼框架如圖1-2所示,這是典型的微服務分層結構,包含常用的服務治理功能。

microservices-framework
圖1-2 微服務框架圖


微服務主要功能:


程式碼目錄結構遵循 project-layout,程式碼目錄結構如下所示:

.
├── api            # proto檔案和生成的*pb.go目錄
├── assets         # 其他與資源庫一起使用的資產(圖片、logo等)目錄
├── build          # 打包和持續整合目錄
├── cmd            # 程式入口目錄
├── configs        # 配置檔案的目錄
├── deployments    # IaaS、PaaS、系統和容器協調部署的配置和模板目錄
├─ docs            # 設計文件和介面文件目錄
├── internal       # 私有應用程式和庫的程式碼目錄
│ ├── cache        # 基於業務包裝的快取目錄
│ ├── config       # Go結構的配置檔案目錄
│ ├── dao          # 資料訪問目錄
│ ├── ecode        # 自定義業務錯誤程式碼目錄
│ ├── handler      # http的業務功能實現目錄
│ ├── model        # 資料庫模型目錄
│ ├── routers      # http路由目錄
│ ├── rpcclient    # 連線rpc服務的客戶端目錄
│ ├── server       # 服務入口,包括http、rpc等
│ ├── service      # rpc的業務功能實現目錄
│ └── types        # http的請求和響應型別目錄
├── pkg            # 外部應用程式可以使用的庫目錄
├── scripts        # 用於執行各種構建、安裝、分析等操作的指令碼目錄
├── test           # 額外的外部測試程式和測試資料
└── third_party    # 外部幫助程式、分叉程式碼和其他第三方工具

web服務和rpc服務目錄結構基本一致,其中有一些目錄是web服務獨有(internal目錄下的routers、handler、types),有一些目錄是rpc服務獨有(internal目錄下的service)。



2 安裝sponge和依賴工具

2.1 window環境安裝依賴工具

如果使用windows環境,需要先安裝相關依賴工具,其他環境忽略即可。

(1) 安裝mingw64

mingw64是Minimalist GNUfor Windows的縮寫,它是一個可自由使用和自由釋出的Windows特定標頭檔案和使用GNU工具集匯入庫的集合,下載預編譯原始碼生成的二進位制檔案,下載地址:

sourceforge.net/projects/mingw-w64...

下載後解壓到D:\Program Files\mingw64目錄下,修改系統環境變數PATH,新增D:\Program Files\mingw64\bin

安裝make命令

切換到D:\Program Files\mingw64\bin目錄,找到mingw32-make.exe可執行檔案,複製並改名為make.exe


(2) 安裝cmder

cmder 是一個增強型命令列工具,包含一些sponge依賴的命令(bash、git等),cmder下載地址:

github.com/cmderdev/cmder/releases...

下載後解壓到D:\Program Files\cmder目錄下,修改系統環境變數PATH,新增D:\Program Files\cmder


2.2 安裝 sponge

(1) 安裝 go

下載地址 go.dev/dl/golang.google.cn/dl/ 選擇版本(>=1.16)安裝,把 $GOROOT/bin新增到系統path下。

注:如果沒有科學上網,建議設定國內代理 go env -w GOPROXY=https://goproxy.cn,direct


(2) 安裝 protoc

下載地址 github.com/protocolbuffers/protobu... ,把protoc檔案所在目錄新增系統path下。


(3) 安裝 sponge

go install github.com/zhufuyi/sponge/cmd/sponge@latest

注:sponge二進位制檔案所在目錄必須在系統path下。


(4) 安裝依賴外掛和工具

sponge init

執行命令後自動安裝了依賴外掛和工具:protoc-gen-goprotoc-gen-go-grpcprotoc-gen-validateprotoc-gen-gotagprotoc-gen-go-ginprotoc-gen-go-rpc-tmplprotoc-gen-openapiv2protoc-gen-docgolangci-lintswaggo-callvis

如果有依賴工具安裝出錯,執行命令重試:

sponge tools --install

檢視依賴工具安裝情況:

# linux 環境
sponge tools

# windows環境,需要指定bash.exe位置
sponge tools --executor="D:\Program Files\cmder\vendor\git-for-windows\bin\bash.exe"

所有依賴工具和外掛:

Installed dependency tools:
    ✔  go
    ✔  protoc
    ✔  protoc-gen-go
    ✔  protoc-gen-go-grpc
    ✔  protoc-gen-validate
    ✔  protoc-gen-gotag
    ✔  protoc-gen-go-gin
    ✔  protoc-gen-go-rpc-tmpl
    ✔  protoc-gen-openapiv2
    ✔  protoc-gen-doc
    ✔  swag
    ✔  golangci-lint
    ✔  go-callvis


sponge命令的幫助資訊有詳細的使用示例,在命令後面新增-h檢視,例如sponge web model -h,這是根據mysql表生成gorm的model程式碼返回的幫助資訊。



3 快速建立web專案

3.1 根據mysql建立http服務

3.1.1 建立一個表

根據mysql的資料表來生成程式碼,先準備一個mysql服務(docker安裝mysql),例如mysql有一個資料庫school,資料庫下有一個資料表teacher,如下面sql所示:

CREATE DATABASE IF NOT EXISTS school DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;

use school;

create table teacher
(
    id          bigint unsigned auto_increment
        primary key,
    created_at  datetime     null,
    updated_at  datetime     null,
    deleted_at  datetime     null,
    name        varchar(50)  not null comment '使用者名稱',
    password    varchar(100) not null comment '密碼',
    email       varchar(50)  not null comment '郵件',
    phone       varchar(30)  not null comment '手機號碼',
    avatar      varchar(200) not null comment '頭像',
    gender      tinyint      not null comment '性別,1:男,2:女,其他值:未知',
    age         tinyint      not null comment '年齡',
    birthday    varchar(30)  not null comment '出生日期',
    school_name varchar(50)  not null comment '學校名稱',
    college     varchar(50)  not null comment '學院',
    title       varchar(10)  not null comment '職稱',
    profile     text         not null comment '個人簡介'
)
    comment '老師';

create index teacher_deleted_at_index
    on teacher (deleted_at);

把SQL DDL匯入mysql建立一個資料庫school,school下面有一個表teacher。


3.1.2 生成http服務程式碼

開啟終端,執行命令:

sponge web http \
  --module-name=edusys \
  --server-name=edusys \
  --project-name=edusys \
  --repo-addr=zhufuyi \
  --db-dsn=root:123456@(192.168.3.37:3306)/school \
  --db-table=teacher \
  --out=./edusys

檢視引數說明命令sponge web http -h,注意引數repo-addr是映象倉庫地址,這個引數用在構建docker映象、k8s的部署指令碼上,預設值是image-repo-host,如果使用docker官方映象倉庫,只需填寫註冊docker倉庫的使用者名稱,如果使用私有倉庫地址,需要填寫完整倉庫地址。


生成完整的http服務程式碼是在當前目錄edusys下,目錄結構如下:

.
├── build
├── cmd
│    └── edusys
│          └── initial
├── configs
├── deployments
│    ├── docker-compose
│    └── kubernetes
├── docs
├── internal
│    ├── cache
│    ├── config
│    ├── dao
│    ├── ecode
│    ├── handler
│    ├── model
│    ├── routers
│    ├── server
│    └── types
└── scripts

在edusys目錄下的Makefile檔案,整合了編譯、測試、執行、部署等相關命令,切換到edusys目錄下執行服務:

# 更新swagger文件
make docs

# 編譯和執行服務
make run

複製 localhost:8080/swagger/index.html 到瀏覽器測試CRUD介面,如圖3-1所示。

http-swag
圖3-1 http swagger文件介面


服務預設只開啟了指標採集介面、每分鐘的資源統計資訊,其他服務治理預設是關閉。在實際應用中,根據需要做一些修改:

  • 使用redis作為快取,開啟配置檔案configs/edusys.yml,把cacheType欄位值改為redis,並且填寫redis配置地址和埠。
  • 預設限流、熔斷、鏈路跟蹤、服務註冊與發現功能是關閉的,可以開啟配置檔案configs/edusys.yml開啟相關功能,如果開啟鏈路跟蹤功能,必須填寫jaeger配置資訊;如果開啟服務註冊與發現功能,必須填寫consul、etcd、nacos其中一種配置資訊。
  • 如果增加或修改了配置欄位名稱,執行命令 sponge config --server-dir=./edusys更新對應的go struct,只修改欄位值不需要執行更新命令。
  • 修改CRUD介面對應的錯誤碼資訊,開啟ingernal/ecode/teacher_http.go,修改變數teacherNO值,這是唯一不重複的數值,返回資訊說明根據自己需要修改,對teacher表操作的介面自定義錯誤碼都在這裡新增。


3.1.3 生成handler程式碼

一個服務中,通常不止一個資料表,如果新增了新資料表,生成的handler程式碼如何自動填充到已存在的服務程式碼中呢,需要用到sponge web handler命令,例如新增了兩個新資料表courseteach

create table course
(
    id         bigint unsigned auto_increment
        primary key,
    created_at datetime    null,
    updated_at datetime    null,
    deleted_at datetime    null,
    code       varchar(10) not null comment '課程代號',
    name       varchar(50) not null comment '課程名稱',
    credit     tinyint     not null comment '學分',
    college    varchar(50) not null comment '學院',
    semester   varchar(20) not null comment '學期',
    time       varchar(30) not null comment '上課時間',
    place      varchar(30) not null comment '上課地點'
)
    comment '課程';

create index course_deleted_at_index
    on course (deleted_at);


create table teach
(
    id           bigint unsigned auto_increment
        primary key,
    created_at   datetime    null,
    updated_at   datetime    null,
    deleted_at   datetime    null,
    teacher_id   bigint      not null comment '老師id',
    teacher_name varchar(50) not null comment '老師名稱',
    course_id    bigint      not null comment '課程id',
    course_name  varchar(50) not null comment '課程名稱',
    score        char(5)     not null comment '學生評價教學質量,5個等級:A,B,C,D,E'
)
    comment '老師課程';

create index teach_course_id_index
    on teach (course_id);

create index teach_deleted_at_index
    on teach (deleted_at);

create index teach_teacher_id_index
    on teach (teacher_id);


生成包含CRUD業務邏輯的handler程式碼:

sponge web handler \
  --db-dsn=root:123456@(192.168.3.37:3306)/school \
  --db-table=course,teach \
  --out=./edusys

檢視引數說明命令sponge web handler -h,引數out是指定已存在的服務資料夾edusys,如果引數out為空,必須指定module-name引數,在當前目錄生成handler子模組程式碼,然後把handler程式碼複製到資料夾edusys,兩種方式效果都一樣。

執行命令後,在edusys/internal目錄下生成了course和teach相關的程式碼:

.
└── internal
      ├── cache
      ├── dao
      ├── ecode
      ├── handler
      ├── model
      ├── routers
      └── types


切換到edusys目錄下執行命令執行服務:

# 更新swagger文件
make docs

# 編譯和執行服務
make run

複製 localhost:8080/swagger/index.html 到瀏覽器測試CRUD介面,如圖3-2所示。

http-swag2

圖3-2 http swagger文件介面

實際使用中需要修改自定義的CRUD介面返回錯誤碼和資訊,開啟檔案ingernal/ecode/course_http.go修改變數courseNO值,開啟檔案ingernal/ecode/teach_http.go修改變數teachNO值。

雖然生成了每個資料表的CRUD介面,不一定適合實際業務邏輯,就需要手動新增業務邏輯程式碼了,資料庫操作程式碼填寫到internal/dao目錄下,業務邏輯程式碼填寫到internal/handler目錄下。


3.2 根據proto檔案建立http服務

如果不需要標準CRUD介面的http服務程式碼,可以在proto檔案自定義介面,使用spong命令生成http服務和介面模板程式碼。

3.2.1 自定義介面

protocol buffers 語法規則看官方文件 ,下面是一個示例檔案 teacher.proto 內容,每個方法定義了路由和swagger文件的描述資訊,實際應用中根據需要在 message 新增 tag 和 validate 的描述資訊。

syntax = "proto3";

package api.edusys.v1;

import "google/api/annotations.proto";
import "protoc-gen-openapiv2/options/annotations.proto";

option go_package = "edusys/api/edusys/v1;v1";

// 生成*.swagger.json基本資訊
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
  host: "localhost:8080"
  base_path: ""
  info: {
    title: "edusys api docs";
    version: "v0.0.0";
  };
  schemes: HTTP;
  schemes: HTTPS;
  consumes: "application/json";
  produces: "application/json";
};

service teacher {
  rpc Register(RegisterRequest) returns (RegisterReply) {
    // 設定路由
    option (google.api.http) = {
      post: "/api/v1/Register"
      body: "*"
    };
    // 設定路由對應的swagger文件
    option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
      summary: "註冊使用者",
      description: "提交資訊註冊",
      tags: "teacher",
    };
  }

  rpc Login(LoginRequest) returns (LoginReply) {
    option (google.api.http) = {
      post: "/api/v1/login"
      body: "*"
    };
    option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
      summary: "登入",
      description: "登入",
      tags: "teacher",
    };
  }
}

message RegisterRequest {
  string name = 1;
  string email = 2;
  string password = 3;
}

message RegisterReply {
  int64   id = 1;
}

message LoginRequest {
  string email = 1;
  string password = 2;
}

message LoginReply {
  string token = 1;
}


3.2.2 生成http服務程式碼

開啟終端,執行命令:

sponge web http-pb \
  --module-name=edusys \
  --server-name=edusys \
  --project-name=edusys \
  --repo-addr=zhufuyi \
  --protobuf-file=./teacher.proto \
  --out=./edusys

檢視引數說明命令 sponge web http-pb -h,支援*號匹配(示例--protobuf-file=*.proto),表示根據批次proto檔案生成程式碼,多個proto檔案中至少包括一個service,否則不允許生成程式碼。

生成http服務程式碼的目錄如下所示,與sponge web http生成的http服務程式碼目錄有一些區別,新增了proto檔案相關的apithird_party目錄,internal目錄下沒有cachedaomodelhandlertypes目錄,其中handler是存放業務邏輯模板程式碼目錄,透過命令會自動生成。

.
├── api
│    └── edusys
│          └──v1
├── build
├── cmd
│    └── edusys
│          └── initial
├── configs
├── deployments
│    ├── docker-compose
│    └── kubernetes
├── docs
├── internal
│    ├── config
│    ├── ecode
│    ├── routers
│    └── server
├── scripts
└── third_party

切換到edusys目錄下執行命令執行服務:

# 生成*pb.go檔案、生成handler模板程式碼、更新swagger文件
make proto

# 編譯和執行服務
make run

複製 localhost:8080/apis/swagger/index.h... 到瀏覽器測試介面,如圖3-3所示,請求會返回500錯誤,因為模板程式碼(internal/handler/teacher_logic.go檔案)直接呼叫panic("implement me"),這是為了提示要填寫業務邏輯程式碼。

http-pb-swag
圖3-3 http swagger文件介面


3.2.3 新增新的介面

根據業務需求,需要新增新介面,分為兩種情況:

(1) 在原來proto檔案新增新介面

開啟 api/edusys/v1/teacher.proto,例如新增bindPhone方法,並填寫路由和swagger文件描述資訊,完成新增一個新介面。

執行命令:

# 生成*pb.go檔案、生成handler模板程式碼、更新swagger文件
make proto

internal/handlerinternal/ecode兩個目錄下生成新的模板檔案,然後複製最新生成的模板程式碼到業務邏輯程式碼區:

  • internal/handler目錄下生成了字尾為 .gen.日期時間 模板程式碼檔案(示例teacher_logic.go.gen.xxxx225619),因為teacher_logic.go已經存在,不會把寫的業務邏輯程式碼覆蓋,所以生成新檔案。開啟檔案 teacher_logic.go.gen.xxxx225619,把新增方法bindPhone介面的模板程式碼複製到teacher_logic.go檔案中,然後填寫業務邏輯程式碼。
  • internal/ecode目錄下生成了字尾為 .gen.日期時間 模板程式碼檔案,把 bindPhone 介面錯誤碼複製到 teacher_http.go 檔案中。
  • 刪除所有字尾名為 .gen.日期時間 檔案。


(2) 在新proto檔案新增介面

例如新新增了course.proto檔案,course.proto下的介面必須包括路由和swagger文件描述資訊,檢視章節3.2.1,把course.proto檔案複製到api/edusys/v1目錄下,完成新新增介面。

執行命令:

# 生成*pb.go檔案、生成handler模板程式碼、更新swagger文件
make proto

internal/handlerinternal/ecodeinternal/routers 三個目錄下生成course名稱字首的程式碼檔案,只需做下面兩個操作:

  • internal/handler/course.go檔案填寫業務程式碼。
  • internal/ecode/course_http.go檔案修改自定義錯誤碼和資訊說明。


3.2.4 完善http服務

sponge web http-pb命令生成的http服務程式碼沒有daocachemodel等運算元據的相關程式碼,使用者可以自己實現,如果使用mysql資料庫和redis快取,可以使用sponge工具直接生成daocachemodel程式碼。

生成CRUD運算元據庫程式碼命令:

sponge web dao \
  --db-dsn=root:123456@(192.168.3.37:3306)/school \
  --db-table=teacher \
  --include-init-db=true \
  --out=./edusys

檢視引數說明命令 sponge web dao -h,引數--include-init-db在一個服務中只使用一次,下一次生成dao程式碼時去掉引數--include-init-db,否則會造成無法生成最新的dao程式碼,原因是db初始化程式碼已經存在。

使用sponge生成的dao程式碼,需要做一些操作:

  • 在服務的初始化和釋放資原始碼中加入mysql和redis,開啟cmd/edusys/initial/initApp.go檔案,把呼叫mysql和redis初始化程式碼反註釋掉,開啟cmd/edusys/initial/registerClose.go檔案,把呼叫mysql和redis釋放資原始碼反註釋掉,初始程式碼是一次性更改。
  • 生成的dao程式碼,並不能和自定義方法registerlogin完全對應,需要手動在檔案internal/dao/teacher.go補充程式碼,然後在internal/handler/teacher.go填寫業務邏輯程式碼,業務程式碼中返回錯誤使用internal/ecode目錄下定義的錯誤碼,如果直接返回錯誤資訊,請求端會收到unknown錯誤資訊,也就是未定義錯誤資訊。
  • 預設使用了本地記憶體做快取,改為使用redis作為快取,在配置檔案configs/edusys.yml修改欄位cacheType值為redis,並填寫redis地址和埠。

切換到edusys目錄下再次執行服務:

go mod tidy

# 編譯和執行服務
make run

開啟 localhost:8080/apis/swagger/index.h... 再次請求介面,可以正常返回資料了。


3.3 總結

生成http服務程式碼有mysql和proto檔案兩種方式:

  • 根據mysql生成的http服務程式碼包括每個資料表的CRUD介面程式碼,後續新增新介面,可以參考CRUD方式新增業務邏輯程式碼,新新增的介面需要手動填寫swagger描述資訊。
  • 根據proto檔案生成的http服務雖然不包括運算元據庫程式碼,也沒有CRUD介面邏輯程式碼,根據需要可以使用sponge web dao命令生成運算元據庫程式碼。新增了新的介面,除了生成handler模板程式碼,swagger文件、路由註冊程式碼、介面的錯誤碼會自動生成。

兩種方式都可以完成同樣的http服務介面,根據實際應用選擇其中一種,如果做後臺管理服務,使用mysql直接生產CRUD介面程式碼,可以少寫程式碼。對於多數需要自定義介面服務,使用proto檔案方式生成的http服務,這種方式自由度也比較高,寫好proto檔案之後,除了業務邏輯程式碼,其他程式碼都是透過外掛生成。



4 快速建立微服務

4.1 根據mysql建立rpc服務

4.1.1 生成rpc服務程式碼

章節3.1.1 的teacher表為例,建立rpc服務:

sponge micro rpc \
  --module-name=edusys \
  --server-name=edusys \
  --project-name=edusys \
  --repo-addr=zhufuyi \
  --db-dsn=root:123456@(192.168.3.37:3306)/school \
  --db-table=teacher \
  --out=./edusys

檢視引數說明命令 sponge micro rpc -h,生成rpc服務程式碼在當前edusys目錄下,目錄結構如下:

.
├── api
│    ├── edusys
│    │    └── v1
│    └── types
├── build
├── cmd
│    └── edusys
│          └── initial
├── configs
├── deployments
│    ├── docker-compose
│    └── kubernetes
├── docs
├── internal
│    ├── cache
│    ├── config
│    ├── dao
│    ├── ecode
│    ├── model
│    ├── server
│    └── service
├── scripts
└── third_party

在edusys目錄下的Makefile檔案,整合了編譯、測試、執行、部署等相關命令,切換到edusys目錄下執行命令執行服務:

# 生成*pb.go
make proto

# 編譯和執行服務
make run

rpc服務包括了CRUD邏輯程式碼,也包括rpc客戶端測試和壓測程式碼,使用GolandVS Code開啟internal/service/teacher_client_test.go檔案,

  • Test_teacherService_methods 下的方法測試,測試前要先填寫測試引數。
  • Test_teacherService_benchmark 下的方法壓測,測試前要先填寫壓測引數,執行結束後生成壓測報告,複製壓測報告檔案路徑到瀏覽器檢視統計資訊,如圖4-1所示。

performance-test

圖4-1 效能測試報告介面

從服務啟動日誌看到預設監聽8282埠(rpc服務)和8283埠(採集metrics或profile),開啟了每分鐘的列印資源統計資訊。在實際應用中,根據需要做一些修改:

  • 使用redis作為快取,開啟配置檔案configs/edusys.yml,把cacheType欄位值改為redis,並且填寫redis配置地址和埠。
  • 預設限流、熔斷、鏈路跟蹤、服務註冊與發現功能是關閉的,可以開啟配置檔案configs/edusys.yml開啟相關功能,如果開啟鏈路跟蹤功能,需要填寫jaeger配置資訊,如果開啟服務註冊與發現功能,需要填寫consul、etcd、nacos其中一種配置資訊。
  • 如果增加或修改了配置欄位名稱,執行命令 sponge config --server-dir=./edusys 更新對應的go struct,只修改欄位值不需要執行更新命令。
  • 修改CRUD方法對應的錯誤碼和錯誤資訊,開啟ingernal/ecode/teacher_rpc.go,修改變數teacherNO值(數值唯一),返回資訊說明根據自己需要修改,對teacher表操作的介面錯誤資訊都在這裡新增。


4.1.2 生成service程式碼

新增了兩個新表course和teach,資料表的結構看 章節3.1.3,生成service程式碼:

sponge micro service \
  --db-dsn=root:123456@(192.168.3.37:3306)/school \
  --db-table=course,teach \
  --out=./edusys

檢視引數說明命令 sponge micro service -h,引數out指定已存在的rpc服務資料夾edusys,如果引數out為空,必須指定module-nameserver-name兩個引數,在當前目錄下生成service程式碼,然後手動複製到資料夾edusys,兩種方式效果都一樣。

執行命令後,在下面目錄下生成了course和teach相關的程式碼,如果新增自定義方法或新的protocol buffers檔案,也是在下面目錄手動新增程式碼。

.
├── api
│    └── edusys
│          └── v1
└── internal
      ├── cache
      ├── dao
      ├── ecode
      ├── model
      └── service


切換到edusys目錄下執行命令執行服務:

# 更新*.pb.go
make proto

# 編譯和執行服務
make run

使用GolandVS Code開啟internal/service/course_client_test.gointernal/service/teach_client_test.go檔案測試CRUD方法,測試前需要先填寫引數。


4.2 根據proto檔案建立rpc服務

sponge不僅支援基於mysql建立rpc服務,還支援基於proto檔案生成rpc服務。

4.2.1 自定義方法

下面是一個proto示例檔案 teacher.proto 內容:

syntax = "proto3";

package api.edusys.v1;
option go_package = "edusys/api/edusys/v1;v1";

service teacher {
  rpc Register(RegisterRequest) returns (RegisterReply) {}
  rpc Login(LoginRequest) returns (LoginReply) {}
}

message RegisterRequest {
  string name = 1;
  string email = 2;
  string password = 3;
}

message RegisterReply {
  int64   id = 1;
}

message LoginRequest {
  string email = 1;
  string password = 2;
}

message LoginReply {
  string token = 1;
}


4.2.2 生成rpc服務程式碼

開啟終端,執行命令:

sponge micro rpc-pb \
  --module-name=edusys \
  --server-name=edusys \
  --project-name=edusys \
  --repo-addr=zhufuyi \
  --protobuf-file=./teacher.proto \
  --out=./edusys

檢視引數說明命令sponge micro rpc-pb -h,支援*號匹配(示例--protobuf-file=*.proto),表示根據批次proto檔案生成程式碼,多個proto檔案中至少包括一個service,否則無法生成程式碼。

生成rpc服務程式碼目錄如下所示,與sponge micro rpc生成的rpc服務程式碼目錄有一些區別,internal目錄下沒有cachedaomodel子目錄。

.
├── api
│    └── edusys
│          └── v1
├── build
├── cmd
│    └── edusys
│          └── initial
├── configs
├── deployments
│    ├── docker-compose
│    └── kubernetes
├── docs
├── internal
│    ├── config
│    ├── ecode
│    ├── server
│    └── service
├── scripts
└── third_party

切換到edusys目錄下執行命令執行服務:

# 生成*pb.go檔案、生成service模板程式碼
make proto

# 編譯和執行服務
make run

啟動rpc服務之後,使用GolandVS Code開啟internal/service/teacher_client_test.go檔案,對 Test_teacher_methods 下各個方法進行測試,測試前要先填寫測試參,會發現請求返回內部錯誤,因為在模板程式碼檔案internal/service/teacher.go(檔名teacher是proto檔名)插入了程式碼panic("implement me"),這是為了提示要填寫業務邏輯程式碼。


4.2.3 新增新的方法

根據業務需求,需要新增新方法,分為兩種情況操作:

(1) 在原來proto檔案新增新方法

開啟 api/edusys/v1/teacher.proto,例如新增bindPhone方法。

執行命令:

# 生成*pb.go檔案、生成service模板程式碼
make proto

internal/serviceinternal/ecode 兩個目錄下生成模板程式碼,然後複製模板程式碼到業務邏輯程式碼區:

  • internal/service目錄下生成了字尾為 .gen.日期時間 模板程式碼檔案(示例teacher.go.gen.xxxx225732),因為teacher.go已經存在,不會把原來寫的業務邏輯程式碼覆蓋,所以生成了新的檔案,開啟檔案 teacher.go.gen.xxxx225732,把新增bindPhone方法的模板程式碼複製到teacher.go檔案中,然後填寫業務邏輯程式碼。
  • internal/ecode目錄下生成了字尾為 teacher_rpc.go.gen.日期時間 檔案,把bindPhone方法對應的錯誤碼複製到teacher_rpc.go檔案中。
  • 刪除所有字尾名為 .gen.日期時間 檔案。


(2) 在新的proto檔案新增新方法

例如新新增了course.proto檔案,把course.proto檔案複製到api/edusys/v1目錄下,完成新新增介面。

執行命令:

# 生成*pb.go檔案、生成service模板程式碼
make proto

internal/serviceinternal/ecodeinternal/routers 三個目錄下生成course名稱字首的程式碼檔案,只需做下面兩個操作:

  • internal/service/course.go檔案填寫業務程式碼。
  • internal/ecode/course_rpc.go檔案修改自定義錯誤碼和資訊說明。


4.2.4 完善rpc服務程式碼

sponge micro rpc-pb命令生成的rpc服務程式碼沒有daocachemodel等運算元據的相關程式碼,使用者可以自己實現,如果使用mysql資料庫和redis快取,可以使用sponge工具直接生成daocachemodel程式碼。

生成CRUD運算元據庫程式碼命令:

sponge micro dao \
  --db-dsn=root:123456@(192.168.3.37:3306)/school \
  --db-table=teacher \
  --include-init-db=true \
  --out=./edusys

檢視引數說明命令sponge micro dao -h,引數--include-init-db在一個服務中只使用一次,下一次生成dao程式碼時去掉引數--include-init-db,否則會造成無法生成最新的dao程式碼。

使用sponge生成的dao程式碼,需要做一些操作:

  • 在服務的初始化和釋放資原始碼中加入mysql和redis,開啟cmd/edusys/initial/initApp.go檔案,把呼叫mysql和redis初始化程式碼反註釋掉,開啟cmd/edusys/initial/registerClose.go檔案,把呼叫mysql和redis釋放資原始碼反註釋掉,初始程式碼是一次性更改。
  • 生成的dao程式碼,並不能和自定義方法registerlogin完全對應,需要手動在檔案internal/dao/teacher.go補充程式碼,然後在internal/handler/teacher.go填寫業務邏輯程式碼,業務程式碼中返回錯誤使用internal/ecode目錄下定義的錯誤碼,如果直接返回錯誤資訊,請求端會收到unknown錯誤資訊,也就是未定義錯誤資訊。
  • 預設使用了本地記憶體做快取,改為使用redis作為快取,在配置檔案configs/edusys.yml修改欄位cacheType值為redis,並填寫redis地址和埠。

切換到edusys目錄下再次執行服務:

# 編譯和執行服務
make run

啟動rpc服務之後,使用GolandVS Code開啟internal/service/teacher_client_test.go檔案測試各個方法。


4.3 根據proto檔案建立rpc gateway服務

微服務通常提供的是細粒度的API,實際提供給客戶端是粗粒度的API,需要從不同微服務獲取資料聚合在一起組成符合實際需求的API,這是rpc gateway的作用,rpc gateway本身也是一個http服務,如圖4-2所示。

rpc-gateway

圖4-2 rpc gateway框架圖


4.3.1 定義protocol buffers

以電商微服務為例,商品詳情頁面有商品、庫存、商品評價等資訊,這些資訊儲存在不同的微服務中,一般很少請求每個微服務獲取資料,直接請求微服務會造成網路壓力倍增,通常的做法是聚合多個微服務資料一次性返回。

下面四個資料夾,每個資料夾下都有一個簡單的proto檔案。

  • comment: 評論服務的proto目錄
  • inventory: 庫存服務的proto目錄
  • product: 產品服務的proto目錄
  • shopgw: rpc閘道器服務的proto目錄
.
├── comment
│    └── v1
│          └──comment.proto
├── inventory
│    └── v1
│          └── inventory.proto
├── product
│    └── v1
│          └── product.proto
└── shopgw
      └── v1
            └── shopgw.proto

comment.proto檔案內容如下:

syntax = "proto3";

package api.comment.v1;

option go_package = "shopgw/api/comment/v1;v1";

service Comment {
  rpc ListByProductID(ListByProductIDRequest) returns (ListByProductIDReply) {}
}

message ListByProductIDRequest {
  int64 productID = 1;
}

message CommentDetail {
  int64  id=1;
  string username = 2;
  string content = 3;
}

message ListByProductIDReply {
  int32 total = 1;
  int64 productID = 2;
  repeated CommentDetail commentDetails = 3;
}


inventory.proto檔案內容如下:

syntax = "proto3";

package api.inventory.v1;

option go_package = "shopgw/api/inventory/v1;v1";

service Inventory {
  rpc GetByID(GetByIDRequest) returns (GetByIDReply) {}
}

message GetByIDRequest {
  int64 id = 1;
}

message InventoryDetail {
  int64 id = 1;
  float num = 4;
  int32 soldNum =3;
}

message GetByIDReply {
  InventoryDetail inventoryDetail = 1;
}


product.proto檔案內容如下:

syntax = "proto3";

package api.product.v1;

option go_package = "shopgw/api/product/v1;v1";

service Product {
  rpc GetByID(GetByIDRequest) returns (GetByIDReply) {}
}

message GetByIDRequest {
  int64 id = 1;
}

message ProductDetail {
  int64 id = 1;
  string name = 2;
  float price = 3;
  string description = 4;
}

message GetByIDReply {
  ProductDetail productDetail = 1;
  int64 inventoryID = 2;
}


shopgw.proto檔案內容如下,rpc閘道器服務的proto和其他rpc的proto有一點區別,需要指定方法的路由和swagger的描述資訊。

syntax = "proto3";

package api.shopgw.v1;

import "api/product/v1/product.proto";
import "api/comment/v1/comment.proto";
import "api/inventory/v1/inventory.proto";
import "google/api/annotations.proto";
import "protoc-gen-openapiv2/options/annotations.proto";

option go_package = "shopgw/api/shopgw/v1;v1";

// default settings for generating *.swagger.json documents
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
  host: "localhost:8080"
  base_path: ""
  info: {
    title: "eshop api docs";
    version: "v0.0.0";
  };
  schemes: HTTP;
  schemes: HTTPS;
  consumes: "application/json";
  produces: "application/json";
};

service ShopGw {
  rpc GetDetailsByProductID(GetDetailsByProductIDRequest) returns (GetDetailsByProductIDReply) {
    option (google.api.http) = {
      get: "/api/v1/detail"
    };
    option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
      summary: "get detail",
      description: "get detail from product id",
      tags: "shopgw",
    };
  }
}

message GetDetailsByProductIDRequest {
  int64 productID = 1;
}

message GetDetailsByProductIDReply {
  api.product.v1.ProductDetail productDetail = 1;
  api.inventory.v1.InventoryDetail inventoryDetail = 2;
  repeated api.comment.v1.CommentDetail commentDetails = 3;
}


4.3.2 生成rpc gateway服務程式碼

根據shopgw.proto檔案生成rpc gateway服務程式碼:

sponge micro rpc-gw-pb \
  --module-name=shopgw \
  --server-name=shopgw \
  --project-name=eshop \
  --repo-addr=zhufuyi \
  --protobuf-file=./shopgw/v1/shopgw.proto \
  --out=./shopgw

檢視引數說明命令 sponge micro rpc-gw-pb -h,生成的rpc gateway服務程式碼在當前shopgw目錄下,目錄結構如下:

.
├── api
│    └── shopgw
│          └── v1
├── build
├── cmd
│    └── shopgw
│          └── initial
├── configs
├── deployments
│    ├── docker-compose
│    └── kubernetes
├── docs
├── internal
│    ├── config
│    ├── ecode
│    ├── routers
│    ├── rpcclient
│    └── server
├── scripts
└── third_party

因為product.proto依賴product.protoinventory.protocomment.proto檔案,複製三個依賴的proto檔案到api目錄下,api目錄結構如下:

.
├── comment
│    └── v1
│          └── comment.proto
├── inventory
│    └── v1
│          └── inventory.proto
├── product
│    └── v1
│          └── product.proto
└── shopgw
      └── v1
            └── shopgw.proto


切換到shopgw目錄下執行服務:

# 生成*pb.go檔案、生成模板程式碼、更新swagger文件
make proto

# 編譯和執行服務
make run

複製 localhost:8080/apis/swagger/index.h... 到瀏覽器測試介面,如圖4-3所示。請求會返回500錯誤,因為模板程式碼(internal/service/shopgw_logic.go檔案)直接呼叫panic("implement me"),這是為了提示要填寫業務邏輯程式碼。

rpc-gw-swag

圖4-3 rpc gateway的swagger文件介面


4.3.3 完善rpc gateway服務程式碼

(1) 生成連線rpc服務端程式碼

服務還沒有連線rpc服務程式碼,下面是生成連線productinventorycomment三個rpc服務的客戶端程式碼命令:

sponge micro rpc-cli \
  --rpc-server-name=comment,inventory,product \
  --out=./shopgw

檢視引數說明命令 sponge micro rpc-cli -h,引數out指定已存在的服務資料夾shopgw,生成的程式碼在internal/rpcclent目錄下。


(2) 初始化和關閉rpc連線

連線rpc服務端程式碼包括了初始化和關閉函式,根據呼叫模板程式碼填寫:

  • 啟動服務時候初始化,在cmd/shopgw/initial/initApp.go檔案的程式碼段// initializing the rpc server connection下,根據模板呼叫初始化函式。
  • 在關閉服務時候釋放資源,在cmd/shopgw/initial/registerClose.go檔案的程式碼段// close the rpc client connection下,根據模板呼叫釋放資源函式。


(3) 修改配置

連線productinventorycomment三個rpc服務程式碼已經有了,但rpc服務地址還沒配置,需要在配置檔案configs/shopgw.yml的欄位grpcClient下新增連線product、inventory、comment三個rpc服務配置資訊:

grpcClient:
  - name: "product"
    host: "127.0.0.1"
    port: 8201
    registryDiscoveryType: ""
  - name: "inventory"
    host: "127.0.0.1"
    port: 8202
    registryDiscoveryType: ""   
  - name: "comment"
    host: "127.0.0.1"
    port: 8203
    registryDiscoveryType: ""

如果rpc服務使用了註冊與發現,欄位registryDiscoveryType填寫服務註冊發現型別,支援consul、etcd、nacos三種。

生成對應go struct程式碼:

sponge config --server-dir=./shopgw


(4) 填寫業務程式碼

下面是在模板檔案internal/service/shopgw_logic.go填寫的業務邏輯程式碼示例,分別從productinventorycomment三個rpc服務獲取資料聚合在一起返回。

package service

import (
    "context"

    commentV1 "shopgw/api/comment/v1"
    inventoryV1 "shopgw/api/inventory/v1"
    productV1 "shopgw/api/product/v1"
    shopgwV1 "shopgw/api/shopgw/v1"
    "shopgw/internal/rpcclient"
)

var _ shopgwV1.ShopGwLogicer = (*shopGwClient)(nil)

type shopGwClient struct {
    productCli   productV1.ProductClient
    inventoryCli inventoryV1.InventoryClient
    commentCli   commentV1.CommentClient
}

// NewShopGwClient creating rpc clients
func NewShopGwClient() shopgwV1.ShopGwLogicer {
    return &shopGwClient{
        productCli:   productV1.NewProductClient(rpcclient.GetProductRPCConn()),
        inventoryCli: inventoryV1.NewInventoryClient(rpcclient.GetInventoryRPCConn()),
        commentCli:   commentV1.NewCommentClient(rpcclient.GetCommentRPCConn()),
    }
}

func (c *shopGwClient) GetDetailsByProductID(ctx context.Context, req *shopgwV1.GetDetailsByProductIDRequest) (*shopgwV1.GetDetailsByProductIDReply, error) {
    productRep, err := c.productCli.GetByID(ctx, &productV1.GetByIDRequest{
        Id: req.ProductID,
    })
    if err != nil {
        return nil, err
    }

    inventoryRep, err := c.inventoryCli.GetByID(ctx, &inventoryV1.GetByIDRequest{
        Id: productRep.InventoryID,
    })
    if err != nil {
        return nil, err
    }

    commentRep, err := c.commentCli.ListByProductID(ctx, &commentV1.ListByProductIDRequest{
        ProductID: req.ProductID,
    })
    if err != nil {
        return nil, err
    }

    return &shopgwV1.GetDetailsByProductIDReply{
        ProductDetail:   productRep.ProductDetail,
        InventoryDetail: inventoryRep.InventoryDetail,
        CommentDetails:  commentRep.CommentDetails,
    }, nil
}

再次啟動服務:

# 編譯和執行服務
make run

在瀏覽器訪問 localhost:8080/apis/swagger/index.h... ,請求返回503錯誤(服務不可用),原因是productinventorycomment三個rpc服務都還沒執行。

productinventorycomment三個rpc服務程式碼都還沒有,如何正常啟動呢。這三個rpc服務的proto檔案已經有了,根據章節 4.2 根據proto檔案建立rpc服務 步驟生成程式碼和啟動服務就很簡單了。


4.4 總結

生成rpc服務程式碼是基於mysql和proto檔案兩種方式,根據proto檔案方式除了支援生成rpc服務程式碼,還支援生成rpc gateway服務(http)程式碼:

  • 根據mysql生成的rpc服務程式碼包括每個資料表的CRUD方法邏輯程式碼和proto程式碼,後續如果要新增新方法,只需在proto檔案定義,手動新增業務邏輯程式碼可以參考CRUD邏輯程式碼。
  • 根據proto檔案生成rpc服務程式碼不包括運算元據庫程式碼,但可以使用sponge web dao命令生成運算元據庫程式碼,根據proto檔案生成service模板程式碼,在模板程式碼填充業務邏輯程式碼。
  • 根據proto檔案生成rpc gateway服務程式碼,介面定義在proto檔案,根據proto檔案生成service模板程式碼,在模板程式碼填充業務邏輯程式碼,結合sponge micro rpc-cli命令使用。

根據實際場景選擇生成對應服務程式碼,如果主要是對資料表增刪改查,根據mysql生成rpc服務可以少寫程式碼;如果更多的是自定義方法,根據proto生成rpc服務更合適;rpc轉http使用rpc gateway服務。



5 服務治理

5.1 鏈路跟蹤

5.1.1 啟動jaeger和elasticsearch服務

鏈路跟蹤使用jaeger,儲存使用elasticsearch,在本地使用docker-compose啟動兩個服務。

(1) elasticsearch服務

這是 elasticsearch服務的啟動指令碼.env檔案是elasticsearch的啟動配置,啟動elasticsearch服務:

docker-compose up -d


(2) jaeger服務

這是 jaeger服務的啟動指令碼.env檔案是配置jaeger資訊,啟動jaeger服務:

docker-compose up -d

在瀏覽器訪問jaeger查詢主頁 localhost:16686


5.1.2 單服務鏈路跟蹤示例

章節3.1.2 建立的http服務程式碼為例,修改配置檔案configs/edusys.yml,開啟鏈路跟蹤功能(欄位enableTrace),並且填寫jaeger配置資訊。

如果想跟蹤redis,啟用redis快取,把快取型別欄位cacheType值改為redis,並配置redis配置,同時在本地使用docker啟動redis服務,這是redis服務啟動指令碼

啟動http服務:

# 編譯和執行服務
make run

複製 http://localhost:8080/swagger/index.html 到瀏覽器訪問swagger主頁,以請求get查詢為例,連續請求同一個id兩次,鏈路跟蹤如圖5-1所示。

one-server-trace

圖5-1 單服務鏈路跟蹤頁面


從圖中可以看到第一次請求有4個span,分別是:

  • 請求介面 /api/v1/teacher/1
  • 查詢redis
  • 查詢mysql
  • 設定redis快取

說明第一次請求從redis查詢,沒有命中快取,然後從mysql讀取資料,最後置快取。

第二次請求只有2個span,分別是:

  • 請求介面 /api/v1/teacher/1
  • 查詢redis

說明第二次請求直接命中快取,比第一次少了查詢mysql和設定快取過程。

這些span是自動生成的,很多時候需要手動新增自定義span,新增span示例:

import "github.com/zhufuyi/sponge/pkg/tracer"

tags := map[string]interface{}{"foo": "bar"}
_, span := tracer.NewSpan(ctx, "spanName", tags)  
defer span.End()


5.1.3 多服務鏈路跟蹤示例

章節4.3生成的rpc gateway服務程式碼為例,一個共四個服務shopgwproductinventorycomment,分別修改4個服務配置(在configs目錄下),開啟鏈路跟蹤功能,並且填寫jaeger配置資訊。

productinventorycomment 三個服務的internal/service目錄下找到模板檔案,填充程式碼替代panic("implement me"),使得程式碼可以正常執行,並且手動新增一個span,新增隨機延時。

啟動 shopgwproductinventorycomment 四個服務,在瀏覽器訪問 localhost:8080/apis/swagger/index.h... ,執行get請求,鏈路跟蹤介面如圖5-2所示。

multi-servers-trace

圖5-2 多服務鏈路跟蹤頁面


從圖中可以看到共有10個span,主要鏈路:

  • 請求介面/api/v1/detail
  • shopgw 服務呼叫product的rpc客戶端
  • product 的rpc服務端
  • product 服務中手動新增的mockDAO
  • shopgw 服務呼叫inventory的rpc客戶端
  • inventory 的rpc服務端
  • inventory 服務中手動新增的mockDAO
  • shopgw 服務呼叫comment的rpc客戶端
  • comment 的rpc服務端
  • comment 服務中手動新增的mockDAO

shopgw服務序列呼叫了productinventorycomment 三個服務獲取資料,實際中可以改為並行呼叫會更節省時間,但是要注意控制協程數量。


5.2 監控

5.2.1 啟動Prometheus和Grafana服務

採集指標用Prometheus,展示使用Grafana,在本地使用docker啟動兩個服務。

(1) prometheus服務

這是 prometheus服務啟動指令碼,啟動prometheus服務:

docker-compose up -d

在瀏覽器訪問prometheus主頁 http://localhost:9090


(2) grafana服務

這是 grafana服務啟動指令碼,啟動grafana服務:

docker-compose up -d

在瀏覽器訪問 grafana 主頁面 localhost:33000 ,設定prometheus的資料來源 http://localhost:9090 ,記住prometheus的資料來源名稱(這裡是Prometheus),後面匯入監控皮膚的json的datasource值要一致。


5.2.2 http服務監控

章節3.1.2生成的http服務程式碼為例,預設提供指標介面 localhost:8080/metrics

(1) 在prometheus新增監控目標

開啟prometheus配置檔案 prometheus.yml,新增採集目標:

  - job_name: 'http-edusys'
    scrape_interval: 10s
    static_configs:
      - targets: ['localhost:8080']

注:如果使用vim修改 prometheus.yml 檔案,修改前必須將檔案 prometheus.yml 許可權改為0777,否則修改配置檔案無法同步到容器中。

執行請求使prometheus配置生效 curl -X POST http://localhost:9090/-/reload,稍等一會,然後在瀏覽器訪問 localhost:9090/targets , 檢查新新增的採集目標是否生效。


(2) 在grafana新增監控皮膚

http 監控皮膚 匯入到grafana,如果監控介面沒有資料顯示,檢查json裡的資料來源名稱與grafana配置prometheus資料來源名稱是否一致。


(3) 壓測介面,觀察監控資料

使用wrk工具壓測介面

# 介面1
wrk -t2 -c10 -d10s http://192.168.3.27:8080/api/v1/teacher/1

# 介面2
wrk -t2 -c10 -d10s http://192.168.3.27:8080/api/v1/course/1

監控介面如圖5-3所示。

http-grafana

圖5-3 http 服務監控介面


5.2.3 rpc服務監控

章節4.1.1生成的rpc服務程式碼為例,預設提供指標介面 localhost:8283/metrics

(1) 在prometheus新增監控目標

開啟prometheus配置檔案 prometheus.yml,新增採集目標:

  - job_name: 'rpc-server-edusys'
    scrape_interval: 10s
    static_configs:
      - targets: ['localhost:8283']

執行請求使prometheus配置生效 curl -X POST http://localhost:9090/-/reload,稍等一會,然後在瀏覽器訪問 localhost:9090/targets 檢查新新增的採集目標是否生效。


(2) 在grafana新增監控皮膚

rpc server 監控皮膚 匯入到grafana,如果監控介面沒有資料顯示,檢查json裡的資料來源名稱與grafana配置prometheus資料來源名稱是否一致。


(3) 壓測rpc方法,觀察監控資料

使用GolandVS Code開啟internal/service/teacher_client_test.go檔案,對Test_teacherService_methodsTest_teacherService_benchmark 下各個方法進行測試。

rpc-grafana

圖5-4 rpc server監控介面


上面是rpc服務端的監控,rpc的客戶端的監控也類似,rpc client 監控皮膚


5.2.4 在prometheus自動新增和移除監控目標

實際使用中服務數量比較多,手動新增監控目標到prometheus比較繁瑣,也容易出錯。prometheus支援使用consul的服務註冊與發現進行動態配置,自動新增和移除監控目標。

在本地啟動 consul 服務,這是 consul 服務啟動指令碼

開啟 prometheus 配置 prometheus.yml,新增consul配置:

  - job_name: 'consul-micro-exporter'
    consul_sd_configs:
      - server: 'localhost:8500'
        services: []  
    relabel_configs:
      - source_labels: [__meta_consul_tags]
        regex: .*edusys.*
        action: keep
      - regex: __meta_consul_service_metadata_(.+)
        action: labelmap

執行請求使prometheus配置生效 curl -X POST http://localhost:9090/-/reload

在prometheus配置好consul服務發現之後,接著把服務的地址資訊推送到consul,推送資訊 edusys_exporter.json 檔案內容如下:

{
  "ID": "edusys-exporter",
  "Name": "edusys",
  "Tags": [
    "edusys-exporter"
  ],
  "Address": "localhost",
  "Port": 8283,
  "Meta": {
    "env": "dev",
    "project": "edusys"
  },
  "EnableTagOverride": false,
  "Check": {
    "HTTP": "http://localhost:8283/metrics",
    "Interval": "10s"
  },
  "Weights": {
    "Passing": 10,
    "Warning": 1
  }
}

curl -XPUT –data @edusys_exporter.json localhost:8500/v1/agent/service/reg...

稍等一會,然後在瀏覽器開啟 localhost:9090/targets 檢查新新增的採集目標是否生效。然後關閉服務,稍等一會,檢查是否自動移除採集目標。


對於自己的服務,通常啟動服務時同時提交資訊到consul,把 edusys_exporter.json 轉為go struct,在程式內部呼叫http client提交給consul。


5.3 採集go程式profile

通常使用pprof工具來發現和定位程式問題,特別是線上go程式出現問題時可以自動把程式執行現場(profile)儲存下來,再使用工具pprof分析定位問題。

sponge生成的服務支援 http介面系統訊號通知 兩種方式採集profile,預設開啟系統訊號通知方式,實際使用一種即可。


5.3.1 透過http採集profile

透過http介面方式採集profile預設是關閉的,如果需要開啟,修改配置裡的欄位enableHTTPProfile為true,通常在開發或測試時使用,如果線上開啟會有一點點效能損耗,根據實際情況是否開啟使用。

預設路由 /debug/pprof,結合go tool pprof工具,任意時刻都可以分析當前程式執行狀況。


5.3.2 透過系統訊號通知採集profile

使用http介面方式,程式後臺一直定時記錄profile相關資訊等,絕大多數時間都不會去讀取這些profile,可以改進一下,只有需要的時候再開始採集profile,採集完後自動關閉,sponge生成的服務支援監聽系統訊號來開啟和停止採集profile,預設使用了 SIGTRAP(5) 系統訊號(建議改為SIGUSR1,windows環境不支援),傳送訊號給服務:

# 透過名稱檢視服務pid(第二列)
ps aux | grep 服務名稱

# 傳送訊號給服務
kill -trap pid值

# kill -usr1 pid值

服務收到系統訊號通知後,開始採集profile並儲存到/tmp/服務名稱_profile目錄,預設採集時長為60秒,60秒後自動停止採集profile,如果只想採集30秒,傳送第一次訊號開始採集,大概30秒後傳送第二次訊號表示停止採集profile,類似開關。預設採集cpumemorygoroutineblockmutexthreadcreate六種型別profile,檔案格式日期時間_pid_服務名稱_profile型別.out,示例:

xxx221809_58546_edusys_cpu.out
xxx221809_58546_edusys_mem.out
xxx221809_58546_edusys_goroutine.out
xxx221809_58546_edusys_block.out
xxx221809_58546_edusys_mutex.out
xxx221809_58546_edusys_threadcreate.out

因為trace的profile檔案相對比較大,因此預設沒有采集,根據實際需要可以開啟採集trace(服務啟動時呼叫prof.EnableTrace())。

獲得離線檔案後,使用pprof工具使用互動式或介面方式進行分析:

# 互動式
go tool pprof [options] source

# 介面
go tool pprof -http=[host]:[port] [options] source


5.3.3 自動採集profile

上面都是手動採集profile,通常都是希望出現問題時自動採集profile。sponge生成的服務預設支援自動採集profile,是結合資源統計的告警功能來實現的,告警條件:

  • 記錄程式的cpu使用率連續3次(預設每分鐘一次),3次平均使用率超過80%時觸發告警。
  • 記錄程式的使用實體記憶體連續3次(預設每分鐘一次),3次平均佔用系統記憶體超過80%時觸發告警。
  • 如果持續超過告警閾值,預設間隔15分鐘告警一次。

觸發告警時,程式內部呼叫kill函式傳送系統訊號通知採集profile,採集的profile檔案儲存到/tmp/服務名_profile目錄,其實就是在透過系統訊號通知採集profile的基礎上把手動觸發改為自動觸發,即使在半夜程式的cpu或記憶體過高,第二天也可以透過分析profile來發現程式哪裡造成cpu或記憶體過高。

注:自動採集profile不適合windows環境。


5.4 註冊中心

sponge生成的服務預設支援Nacos配置中心,配置中心作用是對不同環境、不同服務的配置統一管理,有效的解決地靜態配置的缺點。

在本地啟動nacos服務,這是nacos服務啟動配置,啟動nacos服務之後,在瀏覽器開啟管理介面 localhost:8848/nacos/index.html ,登入賬號密碼進入主介面。

章節3.1.2 生成的http服務程式碼為例使用配置中心nacos,在nacos介面建立一個名稱空間edusys,然後新建配置,Data ID值為edusys.yml,Group值為dev,配置內容值configs/edusys.yml檔案內容,如圖5-3所示。

nacos-config

圖5-3 nacos新增服務配置


開啟edusys目錄下配置中心檔案configs/edusys_cc.yml,填寫nacos配置資訊:

# Generate the go struct command: sponge config --server-dir=./serverDir

# nacos settings
nacos:
  ipAddr: "192.168.3.37"    # server address
  port: 8848                      # listening port
  scheme: "http"               # http or https
  contextPath: "/nacos"     # path
  namespaceID: "ecfe0595-cae3-43a2-9e47-216dc92207f9" # namespace id
  group: "dev"                    # group name: dev, prod, test
  dataID: "edusys.yml"        # config file id
  format: "yaml"                 # configuration file type: json,yaml,toml

編譯和啟動edusys服務:

# 切換到main.go位置
cd cmd/edusys

# 編譯
go build

# 執行
./edusys -enable-cc -c=../../configs/edusys_cc.yml

啟動服務引數-c表示指定配置檔案,引數-enable-cc表示從配置中心獲取配置。


5.5 限流和熔斷

sponge建立的服務支援限流和熔斷功能,預設是關閉的,開啟服務配置檔案,修改欄位enableLimit值為true表示開啟限流功能,修改欄位enableCircuitBreaker改為true表示開啟熔斷功能。

限流和熔斷使用第三方庫 aegis,根據系統資源和錯誤率自適應調整,由於不同伺服器的處理能力不一樣,引數也不好設定,使用自適應引數避免每個服務去手動去設定引數麻煩。



6 持續整合部署

sponge建立的服務支援在 jenkins 構建和部署,部署目標可以是docker、 k8s ,部署指令碼在deployments目錄下,下面以使用jenkins部署到k8s為示例。

6.1 搭建 jenkins-go 平臺

為了可以在容器裡編譯go程式碼,需要構建一個 jenkins-go 映象,這是已經構建好的 jenkins-go映象。如果想自己構建 jenkins-go 映象,可以參考docker構建指令碼Dokerfile

準備好 jenkins-go 映象之後,還需要準備一個k8s叢集(網上有很多搭建k8s叢集教程),k8s鑑權檔案和命令列工具kubectl,確保在 jenkins-go 容器中有操作k8s的許可權。

jenkins-go 啟動指令碼 docker-compose.yml 內容如下:

version: "3.7"
services:
  jenkins-go:
    image: zhufuyi/jenkins-go:2.37
    restart: always
    container_name: "jenkins-go"
    ports:
      - 38080:8080
    #- 50000:50000
    volumes:
      - $PWD/jenkins-volume:/var/jenkins_home
      # docker configuration
      - /var/run/docker.sock:/var/run/docker.sock
      - /usr/bin/docker:/usr/bin/docker
      - /root/.docker/:/root/.docker/
      # k8s api configuration directory, including config file
      - /usr/local/bin/kubectl:/usr/local/bin/kubectl
      - /root/.kube/:/root/.kube/
      # go related tools
      - /opt/go/bin/golangci-lint:/usr/local/bin/golangci-lint

啟動jenkis-go服務:

docker-compose up -d

在瀏覽器訪問 localhost:38080 ,第一次啟動需要 admin 金鑰(執行命令獲取 docker exec jenkins-go cat /var/jenkins_home/secrets/initialAdminPassword),然後安裝推薦的外掛和設定管理員賬號密碼,接著安裝一些需要使用到的外掛和一些自定義設定。

(1) 安裝外掛

# 中文外掛
Locale

# 新增引數化構建外掛
Extended Choice Parameter

# 新增git引數外掛
Git Parameter

# 賬號管理
Role-based Authorization Strategy

(2) 設定中文

點選【Manage Jenkins】->【Configure System】選項,找到【Locale】選項,輸入【zh_CN】,勾選下面的選項,最後點選【應用】。

(3) 配置全域性引數

dashboard –> 系統管理 –> 系統配置 –> 勾選環境變數

設定容器映象的倉庫地址:

# 開發環境映象倉庫
DEV_REGISTRY_HOST http://localhost:27070

# 測試環境映象倉庫
TEST_REGISTRY_HOST http://localhost:28080

# 生產環境映象倉庫
PROD_REGISTRY_HOST http://localhost:29090


6.2 建立模板

建立jenkins新任務的一種相對簡單的方法是在建立新任務時匯入現有模板,然後修改git儲存庫地址,第一次使用jenkins還沒有模板,可以按照下面步驟建立一個模板:

(1) 建立新的任務,如圖6-1所示。

create-job

圖6-1 建立任務介面


(2) 引數化構設定,使用引數名GIT_parameter,如圖6-2所示。

param-setting

圖6-2 設定引數化構建介面


(3) 設定流水線,如圖6-3所示。

pipeline-setting

圖6-3 設定流水線介面


(4) 構建專案

單擊左側選單欄上的 Build with Parameters,然後選擇要分支或tag,如圖6-4所示。

start-build

圖6-4 引數化構建介面


6.3 部署到k8s

章節3.1.2的edusys服務為例,使用jenkins構建和部署到k8s。

第一次構建服務需要做一些前期準備:

(1) 把edusys程式碼上傳到程式碼倉庫。

(2) 準備一個docker映象倉庫,確保jenkins-go所在docker有許可權上傳映象到映象倉庫。

(3) 確保在k8s叢集節點有許可權從映象拉取映象,在已登入docker映象倉庫伺服器上執行命令生成金鑰。

kubectl create secret generic docker-auth-secret \
    --from-file=.dockerconfigjson=/root/.docker/config.json \
    --type=kubernetes.io/dockerconfigjson

(4) 在k8s建立edusys相關資源。

# 切換到目錄
cd deployments/kubernetes

# 建立名稱空間,名稱對應spong建立服務引數project-name
kubectl apply -f ./*namespace.yml

# 建立configmap、service
kubectl apply -f ./*configmap.yml
kubectl apply -f ./*svc.yml

(5) 如果想使用釘釘通知檢視構建部署結果,開啟程式碼庫下的 Jenkinsfile 檔案,找到欄位tel_num填寫手機號碼,找到access_token填寫token值。


前期準備好之後,在jenkins介面建立一個新任務(名稱edusys),使用上面建立的模板(名稱sponge),然後修改git倉庫,儲存任務,開始引數化構建,構建結果如圖6-5所示:

jenkins-build

圖6-5 jenkins構建結果介面


使用命令kubectl get all -n edusys檢視edusys服務在k8s執行狀態:

NAME                             READY   STATUS    RESTARTS   AGE
pod/edusys-dm-77b4bcccc5-8xt8v   1/1     Running   0          21m

NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/edusys-svc   ClusterIP   10.108.31.220   <none>        8080/TCP   27m

NAME                        READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/edusys-dm   1/1     1            1           21m

NAME                                   DESIRED   CURRENT   READY   AGE
replicaset.apps/edusys-dm-77b4bcccc5   1         1         1       21m


在本地測試是否可以訪問

# 代理埠
kubectl port-forward --address=0.0.0.0 service/edusys-svc 8080:8080 -n edusys

# 請求
curl http://localhost:8080/api/v1/teacher/1


sponge生成的服務包括了Jenkinsfile、構建和上傳映象指令碼、k8s部署指令碼,基本不需要修改指令碼就可以使用,也可以修改指令碼適合自己場景。


如果對你有用給個star⭐,歡迎加入微信群交流
wechat-group



本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章