# 方式一
goctl env check -i -f --verbose
# 方式二: 源文件安装
本节主要通过对 api/rpc 等服务快速开始来让大家对使用 go-zero 开发的工程有一个宏观概念,更加详细的介绍我们将在后续一一展开。
由于go-zero集成了web/rpc于一体,社区有部分小伙伴会问我,go-zero的定位是否是一款微服务框架,答案是不止于此, go-zero虽然集众多功能于一身,但你可以将其中任何一个功能独立出来去单独使用,也可以开发单体服务, 不是说每个服务上来就一定要采用微服务的架构的设计,这点大家可以看看作者(kevin)的第四期开源说 ,其中对此有详细的讲解。
$ 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
中编写业务代码了。
# greet/internal/logic/greetlogic.go
func (l *GreetLogic) Greet(req *types.Request) (*types.Response, error) {
return &types.Response{
Message: "Hello go-zero",
}, nil
}
启动服务
$ 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"}
现在我们来演示一下如何快速创建微服务, 在本小节中,api部分其实和单体服务的创建逻辑是一样的,只是在单体服务中没有服务间的通讯而已, 且微服务中api服务会多一些rpc调用的配置。
本小节将以一个订单服务
调用用户服务
来简单演示一下,演示代码仅传递思路,其中有些环节不会一一列举。
假设我们在开发一个商城项目,而开发者小明负责用户模块(user)和订单模块(order)的开发,我们姑且将这两个模块拆分成两个微服务
user rpc
order api
$ mkdir mall
$ cd mall
$ go mod init mall
无 cd 改变目录, 所有操作均在 mall 目录执行
$ 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
}
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
}
etcd
# 在 mall/user/rpc 目录
$ go run user.go -f etc/user.yaml
Starting rpc server at 127.0.0.1:8080...
# 在 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"}
go-zero 提供了一系列的组件,包括日志、高并发处理、消息队列等,本分组将为对组件进行详细剖析。
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
服务中,名称将被自动设置为 rest
或zrpc
的名称。
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
:多少毫秒后再次写入堆栈跟踪。用来避免堆栈跟踪日志过多
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
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
}