目录
- 引入
- 开发派系
- 标准库/自研派系——不要让框架束缚开发
- web框架派系——gin+grpc
- 大一统框架
- go-zero
- go-zero快速实现一个微服务
- user service
- order api server
- 启动
- goctl
- 安装
- 生成的api网关目录
- 生成的pb目录
- api语法
- syntax
- import语法块
- info
- type
- service
- 注释
- 命令大全
引入
该图片来自微软开源的一个容器商城项目:eShopOnContainers,其中红框部分就是各个微服务模块。
每一个微服务都是一个独立的生态,比较直观的一点是各自拥有独立的数据库。
微服务化后,带来了诸多好处,比如弹性,敏捷,灵活扩展,易于部署,可重用代码等。
但也带来了复杂性,让整个架构变得不易于维护,所以诞生出来很多组件用于辅助,这些组件同样成为了微服务架构中的一员:
- 服务网关:确保服务提供者对客户端的透明,这一层可以进行反向路由,安全认证,灰度发布,日志监控等前置动作。
- 服务发现:注册并维护远程服务及服务提供者的地址,供服务消费者发现和调用,如:etcd,consul等
- 服务框架:实现了rpc框架,包含服务接口描述和实现,向注册中心发布服务等功能,比如grpc等
- 服务监控:对服务消费者与提供者之间的的调用情况进行监控和数据展示比如:普罗米修斯(prometheus)
- 服务追踪:记录每个请求的为服务器调用完整链路,以便进行问题定位和故障分析,比如jeager,zipkin等
- 服务治理:服务治理就是通过一系列的手段来保证在各种意外情况下,服务调用仍然能够正常进行,这些手段包括熔断,隔离,限流,降级,负载均衡等,比如:Sentinel等.
- 基础设施:用以提供服务底层的基础数据服务,比如分布式消息队列,日志存储,数据库,缓存,文件服务器,搜索集群。比如:kafka,mysql,pgsql,mongodb,redis,minio,elasticSearch等。
- 分布式配置中心:统一配置,比如nacos,consul,zk,apollo等
- 分布式事务:dtm,seata等
- 容器以及容器编排:docker,k8s等
- 定时任务
综上,容器时微服务架构的绝佳示例,现代云原生应用使用容器来构建微服务。
开发派系
标准库/自研派系——不要让框架束缚开发
- 对于go标准库的强大(稳定,高性能),让很多开发者不使用框架也可以写出高效的应用程序。
- 微服务的基础是通信,也就是rpc框架的选择,大部分会选择grpc或者基于grpc的基础进行自研rpc框架的开发
- 其他组件需要的时候,进行集成就可以了,而不是非得用某个框架定义的组件。
- 如果部署采用k8s,并且使用服务网格,比如Istio来处理,那么开发者只需要关心业务逻辑即可,不需要关心服务发现,熔断,流量控制,负载均衡。
web框架派系——gin+grpc
- 由于标准库到web框架开发仍然需要一定量的开发工作,所以选择成熟的gin框架,出现了grpc+gin核心,其他组件集成进来的微服务框架。
- gin在这里可以作为grpc网关使用,写一些限流中间件,认证中间件。通过在api view中调用内部的微服务,对外提供服务。
- 同样可以使用k8s+istio.
大一统框架
- 使用框架能减轻工作量,达到快速开发的目的。代价就是遵循框架的规则。
- go的微服务框架比较多,如:go-zero,go-micro,go-kit…
- go-zero是一个不错的选择,其社区活跃,文档齐全。
go-zero
go-zero 是一个集成了各种工程实践的包含 web 和 rpc 框架,有如下主要特点:
- 强大的工具支持,尽可能少的代码编写
- 极简的接口
- 完全兼容 net/http
- 支持中间件,方便扩展
- 高性能
- 面向故障编程,弹性设计
- 内建服务发现、负载均衡
- 内建限流、熔断、降载,且自动触发,自动恢复
- API 参数自动校验
- 超时级联控制
- 自动缓存控制
- 链路跟踪、统计报警等
- 高并发支撑,稳定保障了疫情期间每天的流量洪峰
go-zero快速实现一个微服务
- 订单服务(order)提供一个查询接口
- 用户服务(user)提供一个方法供订单服务获取用户信息
user service
-
创建user目录
-
编写proto文件:
syntax = "proto3";package user;option go_package = "./user";message IdRequest {string id = 1; }message UserResponse {// 用户idstring id = 1;// 用户名称string name = 2;// 用户性别string gender = 3; }service User {rpc getUser(IdRequest) returns(UserResponse); }
-
使用goctl生成go-zero模板和pb文件:
goctl rpc protoc user.proto --go_out=./types --go-grpc_out=./types --zrpc_out=.
-
实现查询用户的功能:
func (l *GetUserLogic) GetUser(in *user.IdRequest) (*user.UserResponse, error) {return &user.UserResponse{Id: in.Id,Name: "generalzy",Gender: "武装直升机",}, nil }
-
go build编译
order api server
-
创建order目录
-
编写api文件:
type (OrderReq {Id string `path:"id"`}OrderReply {Id string `json:"id"`Name string `json:"name"`} ) service order {@handler getOrderget /api/order/get/:id (OrderReq) returns (OrderReply) }
-
使用goctl生成api:
goctl api go -api order.api -dir .
-
配置user config:
type Config struct {rest.RestConfUserRpc zrpc.RpcClientConf }
-
配置yaml:
Name: order Host: 0.0.0.0 Port: 8888 UserRpc:Etcd:Hosts:- 127.0.0.1:2379Key: user.rpc
-
完善服务依赖(将user放入到order的上下文)
type ServiceContext struct {Config config.ConfigUserRpc user.User }func NewServiceContext(c config.Config) *ServiceContext {return &ServiceContext{Config: c,UserRpc: user.NewUser(zrpc.MustNewClient(c.UserRpc)),} }
-
编写order业务逻辑
func (l *GetOrderLogic) GetOrder(req *types.OrderReq) (resp *types.OrderReply, err error) {u, err := l.svcCtx.UserRpc.GetUser(l.ctx, &user.IdRequest{Id: "1",})if err != nil {return nil, err}if u.Name != "generalzy" {return nil, errors.New("用户不存在")}return &types.OrderReply{Id: req.Id,Name: "test order",}, nil
}
- go build编译
启动
- 启动etcd:
etcd.exe
- 启动user:
user.exe -f ./etc/user.yaml
- 启动order:
order.exe -f ./etc/order.yaml
- 访问url:
http://localhost:8888/api/order/get/1
返回:{"id":"1","name":"test order"}
goctl
goctl是go-zero微服务框架下的代码生成工具。使用 goctl 可显著提升开发效率,让开发人员将时间重点放在业务开发上,其功能有:
- api服务生成
- rpc服务生成
- model代码生成
- 模板管理
安装
go install github.com/zeromicro/go-zero/tools/goctl@latest
生成的api网关目录
.
├── etc
│ └── greet-api.yaml // 配置文件
├── go.mod // mod文件
├── greet.api // api描述文件
├── greet.go // main函数入口
└── internal ├── config │ └── config.go // 配置声明type├── handler // 路由及handler转发│ ├── greethandler.go│ └── routes.go├── logic // 业务逻辑│ └── greetlogic.go├── middleware // 中间件文件│ └── greetmiddleware.go├── svc // logic所依赖的资源池│ └── servicecontext.go└── types // request、response的struct,根据api自动生成,不建议编辑└── types.go
生成的pb目录
.
├── etc
│ └── greet.yaml
├── go.mod
├── go.sum
├── greet // [1]
│ ├── greet.pb.go
│ └── greet_grpc.pb.go
├── greet.go
├── greet.proto
├── internal
│ ├── config
│ │ └── config.go
│ ├── logic
│ │ └── greetlogic.go
│ ├── server
│ │ └── streamgreeterserver.go
│ └── svc
│ └── servicecontext.go
└── streamgreeter└── streamgreeter.go
api语法
- syntax语法声明
- import语法块
- info语法块
- type语法块
- service语法块
- 隐藏通道
syntax
一个api语法文件只能有0或者1个syntax语法声明,如果没有syntax,则默认为v1版本
// api语法版本
syntax = "v1"
import语法块
import语法块可以导入拆分的api文件, 不同的api文件按照一定规则声明,可以降低阅读难度和维护难度。
import "foo.api"
import "foo/bar.api"import("bar.api""foo/bar/foo.api"
)
info
info语法块是一个包含了多个键值对的语法体,其作用相当于一个api服务的描述
info(foo: "foo value"bar: "bar value"desc: "long long long long long long text"
)
type
在api服务中,需要用到一个结构体(类)来作为请求体,响应体的载体,因此需要声明一些结构体来完成这件事情, type语法块由golang的type演变而来,保留着一些golang type的特性,沿用golang特性有:
- 保留了golang内置数据类型bool,int,int8,int16,int32,int64,uint,uint8,uint16,uint32,uint64,uintptr ,float32,float64,complex64,complex128,string,byte,rune,
- 兼容golang struct风格声明
- 保留golang关键字
- 不支持time.Time数据类型,用int64表示,因为api支持客户端代码生成,并非所有客户端语言都有time.Time对应的类型
- 结构体名称、字段名称、不能为golang关键字
type Foo{Id int `path:"id"`Foo int `json:"foo"`
}type Bar{Bar int `form:"bar"`
}type(FooBar{FooBar int `json:"fooBar"`}
)
tag定义和golang中json tag语法一样,除了json tag外,go-zero还提供了另外一些tag来实现对字段的描述, 详情见下表。(tag修饰符需要在tag value后以英文逗号,隔开)
tag表
绑定参数时,以下四个tag只能选择其中一个
tag key 描述 提供方 有效范围 示例 json json序列化tag golang request、response json:"fooo"
path 路由path,如 /foo/:id
go-zero request path:"id"
form 标志请求体是一个form(POST方法时)或者一个query(GET方法时 /search?name=keyword
)go-zero request form:"name"
header HTTP header,如 Name: value
go-zero request header:"name"
tag修饰符
常见参数校验描述
tag key 描述 提供方 有效范围 示例 optional 定义当前字段为可选参数 go-zero request json:"name,optional"
options 定义当前字段的枚举值,多个以竖线|隔开 go-zero request json:"gender,options=male"
default 定义当前字段默认值 go-zero request json:"gender,default=male"
range 定义当前字段数值范围 go-zero request json:"age,range=[0:120]"
service
service语法块用于定义api服务,包含服务名称,服务metadata,中间件声明,路由,handler等。
语法:
-
serviceSpec:包含了一个可选语法块atServer和serviceApi语法块,其遵循序列模式(编写service必须要按照顺序,否则会解析出错)
-
atServer: 可选语法块,定义key-value结构的server metadata,‘@server’ 表示这一个server语法块的开始,其可以用于描述serviceApi或者route语法块,其用于描述不同语法块时有一些特殊关键key 需要值得注意,见 atServer关键key描述说明。
-
serviceApi:包含了1到多个serviceRoute语法块
-
serviceRoute:按照序列模式包含了atDoc,handler和route
-
atDoc:可选语法块,一个路由的key-value描述,其在解析后会传递到spec.Spec结构体,如果不关心传递到spec.Spec, 推荐用单行注释替代。
-
handler:是对路由的handler层描述,可以通过atServer指定handler key来指定handler名称, 也可以直接用atHandler语法块来定义handler名称
-
atHandler:‘@handler’ 固定token,后接一个遵循正则[a-zA-Z][a-zA-Z-]*)的值,用于声明一个handler名称
-
route:路由,有httpMethod、path、可选request、可选response组成,httpMethod是必须是小写。
-
body:api请求体语法定义,必须要由()包裹的可选的ID值
-
replyBody:api响应体语法定义,必须由()包裹的struct、array(向前兼容处理,后续可能会废弃,强烈推荐以struct包裹,不要直接用array作为响应体)
-
kvLit: 同info key-value
-
serviceName: 可以有多个’-'join的ID值
-
path:api请求路径,必须以’/‘或者’/:‘开头,切不能以’/‘结尾,中间可包含ID或者多个以’-'join的ID字符串
atServer关键key:
修饰service时
key | 描述 | 示例 |
jwt | 声明当前service下所有路由需要jwt鉴权,且会自动生成包含jwt逻辑的代码 | jwt: Auth |
group | 声明当前service或者路由文件分组 | group: login |
middleware | 声明当前service需要开启中间件 | middleware: AuthMiddleware |
prefix | 添加路由分组 | prefix: api |
修饰route时
key | 描述 | 示例 |
handler | 声明一个handler | - |
@server(jwt: Authgroup: foomiddleware: AuthMiddlewareprefix: api
)
service foo-api{@doc "foo"@handler foopost /foo/:id (Foo) returns (Bar)
}service foo-api{@handler pingget /ping@doc "foo"@handler barpost /bar/:id (Foo)
}
注释
// doc
// comment