当前位置: 首页 > 工具软件 > go-zero > 使用案例 >

go-zero 基础

毕黎昕
2023-12-01

官网
github

开发规范

1、环境准备

1.1 goctl安装

1.2 protoc & protoc-gen-go安装

# 方式一
goctl env check -i -f --verbose   

# 方式二: 源文件安装

2、快速开始

本节主要通过对 api/rpc 等服务快速开始来让大家对使用 go-zero 开发的工程有一个宏观概念,更加详细的介绍我们将在后续一一展开。

2.1 单体服务

由于go-zero集成了web/rpc于一体,社区有部分小伙伴会问我,go-zero的定位是否是一款微服务框架,答案是不止于此, go-zero虽然集众多功能于一身,但你可以将其中任何一个功能独立出来去单独使用,也可以开发单体服务, 不是说每个服务上来就一定要采用微服务的架构的设计,这点大家可以看看作者(kevin)的第四期开源说 ,其中对此有详细的讲解。

2.1.1 创建 greet 服务

$ mkdir go-zero-demo
$ cd go-zero-demo
$ go mod init go-zero-demo
$ goctl api new greet
$ go mod tidy
Done.

查看一下greet服务的目录结构

$ tree greet
greet
├── etc
│   └── greet-api.yaml
├── greet.api
├── greet.go
└── internal
    ├── config
    │   └── config.go
    ├── handler
    │   ├── greethandler.go
    │   └── routes.go
    ├── logic
    │   └── greetlogic.go
    ├── svc
    │   └── servicecontext.go
    └── types
        └── types.go

由以上目录结构可以观察到,greet服务虽小,但"五脏俱全"。接下来我们就可以在greetlogic.go中编写业务代码了。

2.1.2 编写logic

# greet/internal/logic/greetlogic.go 	
func (l *GreetLogic) Greet(req *types.Request) (*types.Response, error) {
    return &types.Response{
        Message: "Hello go-zero",
    }, nil
}

2.1.3 启动并访问服务

启动服务

$ cd greet
$ go run greet.go -f etc/greet-api.yaml

输出如下,服务启动并侦听在8888端口:

Starting server at 0.0.0.0:8888...

访问服务

curl -i -X GET http://localhost:8888/from/you

返回如下:

HTTP/1.1 200 OK
Content-Type: application/json
Date: Sun, 07 Feb 2021 04:31:25 GMT
Content-Length: 27
  
{"message":"Hello go-zero"}

2.2 微服务

现在我们来演示一下如何快速创建微服务, 在本小节中,api部分其实和单体服务的创建逻辑是一样的,只是在单体服务中没有服务间的通讯而已, 且微服务中api服务会多一些rpc调用的配置。

本小节将以一个订单服务调用用户服务来简单演示一下,演示代码仅传递思路,其中有些环节不会一一列举。

2.2.1 演示功能目标

假设我们在开发一个商城项目,而开发者小明负责用户模块(user)和订单模块(order)的开发,我们姑且将这两个模块拆分成两个微服务

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

user rpc
order api

2.2.2 创建mall工程

$ mkdir mall
$ cd mall
$ go mod init mall

无 cd 改变目录, 所有操作均在 mall 目录执行

2.2.3 创建user rpc服务

  • 创建user rpc服务
$ mkdir -p user/rpc
  • 添加user.proto文件,增加getUser方法
vim mall/user/rpc/user.proto

增加如下代码:

syntax = "proto3";

package user;
  
// protoc-gen-go 版本大于1.4.0, proto文件需要加上go_package,否则无法生成
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);
}

注意: 1、每一个 *.proto文件只允许有一个service error: only one service expected

$ cd user/rpc
$ goctl rpc protoc user.proto --go_out=./types --go-grpc_out=./types --zrpc_out=.
Done.
  • 填充业务逻辑
// internal/logic/getuserlogic.go

package logic

import (
    "context"

    "go-zero-demo/mall/user/rpc/internal/svc"
    "go-zero-demo/mall/user/rpc/types/user"

    "github.com/zeromicro/go-zero/core/logx"
)

type GetUserLogic struct {
    ctx    context.Context
    svcCtx *svc.ServiceContext
    logx.Logger
}

func NewGetUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserLogic {
    return &GetUserLogic{
        ctx:    ctx,
        svcCtx: svcCtx,
        Logger: logx.WithContext(ctx),
    }
}

func (l *GetUserLogic) GetUser(in *user.IdRequest) (*user.UserResponse, error) {
    return &user.UserResponse{
            Id:   "1",
            Name: "test",
    }, nil
}

2.2.4 创建order api服务

  • 创建 order api服务
# 回到 mall 目录
$ mkdir -p order/api && cd order/api
# mall/order/order.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)
}

生成order服务

$ goctl api go -api order.api -dir .

添加user rpc配置

// internal/config/config.go

package config

import (
    "github.com/zeromicro/go-zero/zrpc"
    "github.com/zeromicro/go-zero/rest"
)

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

添加yaml配置

# etc/order.yaml 

Name: order
Host: 0.0.0.0
Port: 8888
UserRpc:
  Etcd:
    Hosts:
    - 127.0.0.1:2379
    Key: user.rpc

完善服务依赖

package svc

import (
    "go-zero-demo/mall/order/api/internal/config"
    "go-zero-demo/mall/user/rpc/userclient"

    "github.com/zeromicro/go-zero/zrpc"
)

type ServiceContext struct {
    Config  config.Config
    UserRpc userclient.User
}

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

添加order演示逻辑
getorderlogic 添加业务逻辑

// internal/logic/getorderlogic.go

package logic

import (
    "context"
    "errors"

    "go-zero-demo/mall/order/api/internal/svc"
    "go-zero-demo/mall/order/api/internal/types"
    "go-zero-demo/mall/user/rpc/types/user"

    "github.com/zeromicro/go-zero/core/logx"
)

type GetOrderLogic struct {
    logx.Logger
    ctx    context.Context
    svcCtx *svc.ServiceContext
}

func NewGetOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) GetOrderLogic {
    return GetOrderLogic{
        Logger: logx.WithContext(ctx),
        ctx:    ctx,
        svcCtx: svcCtx,
    }
}

func (l *GetOrderLogic) GetOrder(req *types.OrderReq) (*types.OrderReply, error) {
    user, err := l.svcCtx.UserRpc.GetUser(l.ctx, &user.IdRequest{
        Id: "1",
    })
    if err != nil {
        return nil, err
    }

    if user.Name != "test" {
        return nil, errors.New("用户不存在")
    }

    return &types.OrderReply{
        Id:   req.Id,
        Name: "test order",
    }, nil
}

2.2.5 启动服务并验证

启动etcd

etcd

启动user rpc

# 在 mall/user/rpc 目录
$ go run user.go -f etc/user.yaml
Starting rpc server at 127.0.0.1:8080...

启动order api

# 在 mall/order/api 目录
$ go run order.go -f etc/order.yaml
Starting server at 0.0.0.0:8888...

访问order api

$ curl -i -X GET http://localhost:8888/api/order/get/1
HTTP/1.1 200 OK
Content-Type: application/json
Date: Sun, 07 Feb 2021 03:45:05 GMT
Content-Length: 30

{"id":"1","name":"test order"}

3、组件剖析

go-zero 提供了一系列的组件,包括日志、高并发处理、消息队列等,本分组将为对组件进行详细剖析。

3.1 logx

3.1.1 logx 配置

type LogConf struct {
    ServiceName         string `json:",optional"`
    Mode                string `json:",default=console,options=[console,file,volume]"`
    Encoding            string `json:",default=json,options=[json,plain]"`
    TimeFormat          string `json:",optional"`
    Path                string `json:",default=logs"`
    Level               string `json:",default=info,options=[info,error,severe]"`
    Compress            bool   `json:",optional"`
    KeepDays            int    `json:",optional"`
    StackCooldownMillis int    `json:",default=100"`
}

ServiceName:设置服务名称,可选。在 volume 模式下,该名称用于生成日志文件。在 rest/zrpc 服务中,名称将被自动设置为 restzrpc 的名称。
Mode:输出日志的模式,默认是 console

  • console 模式将日志写到 stdout/stderr
  • file 模式将日志写到 Path 指定目录的文件中
  • volume 模式在 docker 中使用,将日志写入挂载的卷中

Encoding: 指示如何对日志进行编码,默认是 json

  • json模式以 json 格式写日志
  • plain模式用纯文本写日志,并带有终端颜色显示

TimeFormat:自定义时间格式,可选。默认是 2006-01-02T15:04:05.000Z07:00
Path:设置日志路径,默认为 logs
Level: 用于过滤日志的日志级别。默认为 info

  • info,所有日志都被写入
  • error, info 的日志被丢弃
  • severe, info 和 error 日志被丢弃,只有 severe 日志被写入

Compress: 是否压缩日志文件,只在 file 模式下工作
KeepDays:日志文件被保留多少天,在给定的天数之后,过期的文件将被自动删除。对 console 模式没有影响
StackCooldownMillis:多少毫秒后再次写入堆栈跟踪。用来避免堆栈跟踪日志过多

3.1.2 打印日志方法

type Logger interface {
    // Error logs a message at error level.
    Error(...interface{})
    // Errorf logs a message at error level.
    Errorf(string, ...interface{})
    // Errorv logs a message at error level.
    Errorv(interface{})
    // Errorw logs a message at error level.
    Errorw(string, ...LogField)
    // Info logs a message at info level.
    Info(...interface{})
    // Infof logs a message at info level.
    Infof(string, ...interface{})
    // Infov logs a message at info level.
    Infov(interface{})
    // Infow logs a message at info level.
    Infow(string, ...LogField)
    // Slow logs a message at slow level.
    Slow(...interface{})
    // Slowf logs a message at slow level.
    Slowf(string, ...interface{})
    // Slowv logs a message at slow level.
    Slowv(interface{})
    // Sloww logs a message at slow level.
    Sloww(string, ...LogField)
    // WithContext returns a new logger with the given context.
    WithContext(context.Context) Logger
    // WithDuration returns a new logger with the given duration.
    WithDuration(time.Duration) Logger
}

Error, Info, Slow: 将任何类型的信息写进日志,使用 fmt.Sprint(...) 来转换为 string
Errorf, Infof, Slowf: 将指定格式的信息写入日志
Errorv, Infov, Slowv: 将任何类型的信息写入日志,用 json marshal 编码
Errorw, Infow, Sloww: 写日志,并带上给定的 key:value 字段
WithContext:将给定的 ctx 注入日志信息,例如用于记录 trace-id和span-id
WithDuration: 将指定的时间写入日志信息中,字段名为 duration

3.1.3 与第三方日志库集成

3.1.4 将日志写到指定的存储

logx定义了两个接口,方便自定义 logx,将日志写入任何存储。

logx.NewWriter(w io.Writer)
logx.SetWriter(write logx.Writer)

例如,如果我们想把日志写进kafka,而不是控制台或文件,我们可以像下面这样做。

type KafkaWriter struct {
    Pusher *kq.Pusher
}

func NewKafkaWriter(pusher *kq.Pusher) *KafkaWriter {
    return &KafkaWriter{
        Pusher: pusher,
    }
}

func (w *KafkaWriter) Write(p []byte) (n int, err error) {
    // writing log with newlines, trim them.
    if err := w.Pusher.Push(strings.TrimSpace(string(p))); err != nil {
        return 0, err
    }

    return len(p), nil
}

func main() {
    pusher := kq.NewPusher([]string{"localhost:9092"}, "go-zero")
    defer pusher.Close()

    writer := logx.NewWriter(NewKafkaWriter(pusher))
    logx.SetWriter(writer)
  
    // more code
}

3.1.5 过滤敏感字段

 类似资料: