您现在的位置是:首页 >其他 >go-zero网站首页其他

go-zero

Generalzy 2023-07-13 08:00:02
简介go-zero

引入

在这里插入图片描述
该图片来自微软开源的一个容器商城项目:eShopOnContainers,其中红框部分就是各个微服务模块。

每一个微服务都是一个独立的生态,比较直观的一点是各自拥有独立的数据库。

微服务化后,带来了诸多好处,比如弹性,敏捷,灵活扩展,易于部署,可重用代码等。

但也带来了复杂性,让整个架构变得不易于维护,所以诞生出来很多组件用于辅助,这些组件同样成为了微服务架构中的一员:

  1. 服务网关:确保服务提供者对客户端的透明,这一层可以进行反向路由,安全认证,灰度发布,日志监控等前置动作。
    在这里插入图片描述
  2. 服务发现:注册并维护远程服务及服务提供者的地址,供服务消费者发现和调用,如:etcd,consul等
  3. 服务框架:实现了rpc框架,包含服务接口描述和实现,向注册中心发布服务等功能,比如grpc等
  4. 服务监控:对服务消费者与提供者之间的的调用情况进行监控和数据展示比如:普罗米修斯(prometheus)
  5. 服务追踪:记录每个请求的为服务器调用完整链路,以便进行问题定位和故障分析,比如jeager,zipkin等
  6. 服务治理:服务治理就是通过一系列的手段来保证在各种意外情况下,服务调用仍然能够正常进行,这些手段包括熔断,隔离,限流,降级,负载均衡等,比如:Sentinel等.
  7. 基础设施:用以提供服务底层的基础数据服务,比如分布式消息队列,日志存储,数据库,缓存,文件服务器,搜索集群。比如:kafka,mysql,pgsql,mongodb,redis,minio,elasticSearch等。
  8. 分布式配置中心:统一配置,比如nacos,consul,zk,apollo等
  9. 分布式事务:dtm,seata等
  10. 容器以及容器编排:docker,k8s等
  11. 定时任务

综上,容器时微服务架构的绝佳示例,现代云原生应用使用容器来构建微服务。

开发派系

标准库/自研派系——不要让框架束缚开发

  1. 对于go标准库的强大(稳定,高性能),让很多开发者不使用框架也可以写出高效的应用程序。
  2. 微服务的基础是通信,也就是rpc框架的选择,大部分会选择grpc或者基于grpc的基础进行自研rpc框架的开发
  3. 其他组件需要的时候,进行集成就可以了,而不是非得用某个框架定义的组件。
  4. 如果部署采用k8s,并且使用服务网格,比如Istio来处理,那么开发者只需要关心业务逻辑即可,不需要关心服务发现,熔断,流量控制,负载均衡。

web框架派系——gin+grpc

  1. 由于标准库到web框架开发仍然需要一定量的开发工作,所以选择成熟的gin框架,出现了grpc+gin核心,其他组件集成进来的微服务框架。
  2. gin在这里可以作为grpc网关使用,写一些限流中间件,认证中间件。通过在api view中调用内部的微服务,对外提供服务。
  3. 同样可以使用k8s+istio.

大一统框架

  1. 使用框架能减轻工作量,达到快速开发的目的。代价就是遵循框架的规则。
  2. go的微服务框架比较多,如:go-zero,go-micro,go-kit…
  3. go-zero是一个不错的选择,其社区活跃,文档齐全。

go-zero

在这里插入图片描述

go-zero 是一个集成了各种工程实践的包含 web 和 rpc 框架,有如下主要特点:

  1. 强大的工具支持,尽可能少的代码编写
  2. 极简的接口
  3. 完全兼容 net/http
  4. 支持中间件,方便扩展
  5. 高性能
  6. 面向故障编程,弹性设计
  7. 内建服务发现、负载均衡
  8. 内建限流、熔断、降载,且自动触发,自动恢复
  9. API 参数自动校验
  10. 超时级联控制
  11. 自动缓存控制
  12. 链路跟踪、统计报警等
  13. 高并发支撑,稳定保障了疫情期间每天的流量洪峰

在这里插入图片描述

go-zero快速实现一个微服务

  1. 订单服务(order)提供一个查询接口
  2. 用户服务(user)提供一个方法供订单服务获取用户信息

user service

  1. 创建user目录

  2. 编写proto文件:

    syntax = "proto3";
    
    package user;
    
    option go_package = "./user";
    
    message IdRequest {
      string id = 1;
    }
    
    message UserResponse {
      // 用户id
      string id = 1;
      // 用户名称
      string name = 2;
      // 用户性别
      string gender = 3;
    }
    
    service User {
      rpc getUser(IdRequest) returns(UserResponse);
    }
    
  3. 使用goctl生成go-zero模板和pb文件:goctl rpc protoc user.proto --go_out=./types --go-grpc_out=./types --zrpc_out=.

  4. 实现查询用户的功能:

    func (l *GetUserLogic) GetUser(in *user.IdRequest) (*user.UserResponse, error) {
    	return &user.UserResponse{
    		Id:     in.Id,
    		Name:   "generalzy",
    		Gender: "武装直升机",
    	}, nil
    }
    

    在这里插入图片描述

  5. go build编译

order api server

  1. 创建order目录

  2. 编写api文件:

    type (
    	OrderReq {
    		Id string `path:"id"`
    	}
    
    	OrderReply {
    		Id   string `json:"id"`
    		Name string `json:"name"`
    	}
    )
    service order {
    	@handler getOrder
    	get /api/order/get/:id (OrderReq) returns (OrderReply)
    }
    
  3. 使用goctl生成api:goctl api go -api order.api -dir .

  4. 配置user config:

    type Config struct {
        rest.RestConf
        UserRpc zrpc.RpcClientConf
    }
    

    在这里插入图片描述

  5. 配置yaml:

    Name: order
    Host: 0.0.0.0
    Port: 8888
    UserRpc:
      Etcd:
        Hosts:
          - 127.0.0.1:2379
        Key: user.rpc
    
  6. 完善服务依赖(将user放入到order的上下文)

    type ServiceContext struct {
    	Config  config.Config
    	UserRpc user.User
    }
    
    func NewServiceContext(c config.Config) *ServiceContext {
    	return &ServiceContext{
    		Config:  c,
    		UserRpc: user.NewUser(zrpc.MustNewClient(c.UserRpc)),
    	}
    }
    

    在这里插入图片描述

  7. 编写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
}

在这里插入图片描述

  1. go build编译

启动

  1. 启动etcd:etcd.exe
    在这里插入图片描述
  2. 启动user:user.exe -f ./etc/user.yaml
  3. 启动order:order.exe -f ./etc/order.yaml
  4. 访问url:http://localhost:8888/api/order/get/1返回:{"id":"1","name":"test order"}

goctl

goctl是go-zero微服务框架下的代码生成工具。使用 goctl 可显著提升开发效率,让开发人员将时间重点放在业务开发上,其功能有:

  1. api服务生成
  2. rpc服务生成
  3. model代码生成
  4. 模板管理

安装

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特性有:

  1. 保留了golang内置数据类型bool,int,int8,int16,int32,int64,uint,uint8,uint16,uint32,uint64,uintptr ,float32,float64,complex64,complex128,string,byte,rune,
  2. 兼容golang struct风格声明
  3. 保留golang关键字
  4. 不支持time.Time数据类型,用int64表示,因为api支持客户端代码生成,并非所有客户端语言都有time.Time对应的类型
  5. 结构体名称、字段名称、不能为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描述提供方有效范围 示例
    jsonjson序列化taggolangrequest、responsejson:"fooo"
    path路由path,如/foo/:idgo-zerorequestpath:"id"
    form标志请求体是一个form(POST方法时)或者一个query(GET方法时/search?name=keyword)go-zerorequestform:"name"
    headerHTTP header,如 Name: valuego-zerorequestheader:"name"
  • tag修饰符

    常见参数校验描述

    tag key 描述 提供方 有效范围 示例
    optional定义当前字段为可选参数go-zerorequestjson:"name,optional"
    options定义当前字段的枚举值,多个以竖线|隔开go-zerorequestjson:"gender,options=male"
    default定义当前字段默认值go-zerorequestjson:"gender,default=male"
    range定义当前字段数值范围go-zerorequestjson:"age,range=[0:120]"

service

service语法块用于定义api服务,包含服务名称,服务metadata,中间件声明,路由,handler等。

语法:

  1. serviceSpec:包含了一个可选语法块atServer和serviceApi语法块,其遵循序列模式(编写service必须要按照顺序,否则会解析出错)

  2. atServer: 可选语法块,定义key-value结构的server metadata,‘@server’ 表示这一个server语法块的开始,其可以用于描述serviceApi或者route语法块,其用于描述不同语法块时有一些特殊关键key 需要值得注意,见 atServer关键key描述说明。

  3. serviceApi:包含了1到多个serviceRoute语法块

  4. serviceRoute:按照序列模式包含了atDoc,handler和route

  5. atDoc:可选语法块,一个路由的key-value描述,其在解析后会传递到spec.Spec结构体,如果不关心传递到spec.Spec, 推荐用单行注释替代。

  6. handler:是对路由的handler层描述,可以通过atServer指定handler key来指定handler名称, 也可以直接用atHandler语法块来定义handler名称

  7. atHandler:‘@handler’ 固定token,后接一个遵循正则[a-zA-Z][a-zA-Z-]*)的值,用于声明一个handler名称

  8. route:路由,有httpMethod、path、可选request、可选response组成,httpMethod是必须是小写。

  9. body:api请求体语法定义,必须要由()包裹的可选的ID值

  10. replyBody:api响应体语法定义,必须由()包裹的struct、array(向前兼容处理,后续可能会废弃,强烈推荐以struct包裹,不要直接用array作为响应体)

  11. kvLit: 同info key-value

  12. serviceName: 可以有多个’-'join的ID值

  13. 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: Auth
  group: foo
  middleware: AuthMiddleware
  prefix: api
)
service foo-api{
  @doc "foo"
  @handler foo
  post /foo/:id (Foo) returns (Bar)
}

service foo-api{
  @handler ping
  get /ping

  @doc "foo"
  @handler bar
  post /bar/:id (Foo)
}

注释

// doc
// comment

命令大全

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。