go-zero 微服务 demo

标签:微服务首次发布:2023-12-10最近修改:2023-12-10

demo 的内容是搭建两个服务,一个是user服务,用来提供用户信息;另一个是order服务,需要调用 user 服务获取用户信息,最后把订单信息返回给前端。所以在这个 demo 中,user 服务需要提供 RPC 接口,而 order 服务需要进行 RPC 接口调用并提供 HTTP 接口。

user 服务搭建

1. 模板生成

使用 go-zero 框架提供了 goctl 命令可以快熟搭建 RPC 服务

text
# 生成user服务模板goctl rpc new user# 解决依赖go mod tidy

2. proto 文件编写和 RPC 接口生成

user.proto 文件的内容如下。

text
syntax = "proto3";option go_package = "./user";package user;message UserRequest {  string id = 1;}message UserResponse {  string name = 1;  string sex = 2;  int32 age = 3;}service User {  rpc GetUserInfo(UserRequest) returns (UserResponse);}

使用命令:goctl rpc protoc user.proto --go_out=. --go-grpc_out=. --zrpc_out=.生成相应的 go 代码。完成后在 user 服务下的目录结构如下:

text
├─etc		# 配置文件夹,里面有一个.yaml的配置文件├─internal│  ├─config   	# 包含的 config.go 通常会定义一个结构体,用来映射 etc 目录下的 yaml 配置文件内容。│  ├─logic	# 包含业务逻辑处理的代码,通常每个 RPC 方法会对应一个逻辑处理文件。│  ├─server	# 包含服务实现的代码,如服务注册、方法实现等。│  └─svc	# 包含 ServiceContext 结构,它是用来传递服务运行时的依赖项,如数据库连接、缓存、配置对象等。├─user		# 包含由 user.proto 自动生成的代码,例如 user.pb.go 和 user_grpc.pb.go。└─userclient	# 包含构建 RPC 客户端的代码,这使得其他服务或客户端可以方便地调用该 RPC 服务。

除此之外,在 user 服务的根目录下,还有两个文件,分别是user.gouser.proto。作用如下:

  • user.proto:定义了 RPC 服务接口和消息结构。
  • user.go:服务的入口点,包含 main 函数,用来启动 RPC 服务。

3. 编写 logic 代码

由于 order 服务会调用 user 服务,调用 user 服务的时候会传入用户 id,那么 logic 就可以根据用户 id 做出响应即可。

go
func (l *GetUserInfoLogic) GetUserInfo(in *user.UserRequest) (*user.UserResponse, error) {    // todo: add your logic here and delete this line    id := in.GetId()    if id == "1" {        return &user.UserResponse{            Name: "abc",            Sex:  "男",            Age:  20,        }, nil    } else {        return &user.UserResponse{            Name: "xyz",            Sex:  "女",            Age:  22,        }, nil    }}

4. etcd 启动和 user 服务运行

由于 go-zero 内部使用 etcd 做服务注册和发现,所以在启动 user 服务之前需要安装 etcd 并启动。etcd 的连接配置可以在 user.yaml 中修改,我的 etcd 配置如下:

text
Etcd:  Hosts:  - 192.169.11.10:2379  Key: user.rpc

etcd 配置并启动完成后就可以运行 user 服务,在 user 服务的根目录下,执行 go run user.go 命令即可运行 user 服务。

5. 测试

可以使用 ApiPost 软件来测试刚刚编写的 RPC 服务。测试结果如下:

image-20231210111343533

image-20231210111413095

order 服务搭建

1. 模板生成和 HTTP 服务结构搭建

使用命令:goctl api new order可以快速生成一个 order 服务的模板。模板生成后可以看到在 order 服务下有一个 order.api 的文件,可以利用该文件编写 api 接口,最后使用命令生成 HTTP 服务的基本结构。order.api 文件的内容如下:

text
syntax = "v1"type OrderRequest {    Id string `path:"id"`}type OrderResponse {    Name  string  `json:"name"`    Sex   string  `json:"sex"`    Age   int32   `json:"age"`    Price float32 `json:"price"`}service order-api {    @handler ReturnOrderInfo    get /order/:id (OrderRequest) returns (OrderResponse)}

使用命令:goctl api go --api ./order.api --dir .生成基本的 HTTP 服务结构,命令的执行结果如下:

bash
PS D:\GolandProjects\go-zeroDemo\order> goctl api go --api ./order.api --dir .etc/order-api.yaml exists, ignored generationinternal/config/config.go exists, ignored generationorder.go exists, ignored generationinternal/svc/servicecontext.go exists, ignored generationDone.

看到 Done 就表示执行成功。生成的目录结构如下:

text
├─etc		# 配置文件夹,里面有一个.yaml的配置文件└─internal    ├─config    # 把 YAML 或其他格式的配置文件解析成 Go 的配置结构体,便于应用程序中其他部分使用。    ├─handler	# 处理具体的业务逻辑请求,通常一个路由对应一个 Handler 函数。    ├─logic	# 包含业务逻辑的主体,每个接口通常对应一个 Logic 结构体和相关方法。    ├─svc	# Service Context 结构体定义,在这里初始化你的业务需要的依赖,比如数据库连接、外部服务客户端等。    └─types	# 定义了 API 请求和响应的数据结构,通常由 API 文件中定义的类型自动生成。

除此之外,还有两个文件:order.apiorder.go,其作用如下:

  • order.api:这个文件是使用 go-zero 框架的 API 设计语言编写的。它定义了 API 服务器的接口,包括路由、请求和响应的数据结构、服务名称等。goctl 根据这个 .api 文件的定义来生成相应的模型、逻辑和处理器等代码。
  • order.go:这个文件是服务的入口点,包含 main 函数。它负责启动 HTTP 服务器,设置路由,以及初始化服务所需的各种资源,如数据库连接、缓存、依赖注入等。

2. logic 代码编写

在 orde 服务中,需要调用 user 服务,得到用户数据,并把订单数据一起返回给前端。所以首先要知道 userRPC 服务的地址,可以在 config.yaml 文件中配置 userRPC 的地址(其实本质上是添加配置注册中心的地址,order 服务通过询问 etcd 才知道 user 服务的地址),config.yaml 文件的内容如下:

yaml
Name: order-apiHost: 0.0.0.0Port: 8888UserRPC:  Etcd:    Hosts:      - 192.168.11.10:2379    Key: user.rpc

然后需要在 config 文件夹的 config.go 文件中将刚刚的配置信息映射到结构体中,config.go 内容如下:

go
type Config struct {    rest.RestConf    UserRPC zrpc.RpcClientConf}

补充:这个 RpcClientConf 其实包含很多配置信息,其定义如下:

go
RpcClientConf struct {    Etcd          discov.EtcdConf `json:",optional,inherit"`    Endpoints     []string        `json:",optional"`    Target        string          `json:",optional"`    App           string          `json:",optional"`    Token         string          `json:",optional"`    NonBlock      bool            `json:",optional"`    Timeout       int64           `json:",default=2000"`    KeepaliveTime time.Duration   `json:",optional"`    Middlewares   ClientMiddlewaresConf}type EtcdConf struct {    Hosts              []string    Key                string    ID                 int64  `json:",optional"`    User               string `json:",optional"`    Pass               string `json:",optional"`    CertFile           string `json:",optional"`    CertKeyFile        string `json:",optional=CertFile"`    CACertFile         string `json:",optional=CertFile"`    InsecureSkipVerify bool   `json:",optional"`}

RpcClientConf 结构体定义了 RPC 客户端的配置:

  • Etcd:这里配置的是 etcd 相关的参数,用于服务发现。discov.EtcdConf 是 etcd 客户端的配置,用于定位 etcd 服务端点。
  • Endpoints:手动指定的 RPC 服务端点,如果不使用 etcd 进行服务发现,则可能会用到。
  • Target:指定连接的目标地址。
  • App:应用名,可以用于日志或其他应用内部标识。
  • Token:用于 RPC 调用的认证令牌。
  • NonBlock:指定连接是否为非阻塞。
  • Timeout:RPC 请求的超时时间,默认为 2000 毫秒。
  • KeepaliveTime:连接保持活跃的时间间隔,用于避免连接由于长时间不活跃而被关闭。
  • Middlewares:客户端中间件配置,可以定义一系列中间件来处理 RPC 调用的前后逻辑。

EtcdConf 结构体是 etcd 客户端的配置,用于服务发现和配置管理:

  • Hosts:etcd 集群的主机地址列表。
  • Key:用于 etcd 中键值存储的键名,常用于服务发现。
  • ID:客户端标识。
  • User:登录 etcd 服务器的用户名。
  • Pass:登录 etcd 服务器的密码。
  • CertFile:TLS 认证的证书文件路径。
  • CertKeyFile:TLS 认证的证书密钥文件路径。如果未指定,默认与 CertFile 相同。
  • CACertFile:TLS 认证的 CA 证书文件路径。如果未指定,默认与 CertFile 相同。
  • InsecureSkipVerify:是否跳过 TLS 认证的安全验证,这在自签名证书或测试环境中有时会用到。

接下来需要完善服务的依赖,在 svc 文件夹下的 servicecontext.go 文件中进行关于 userRPC 依赖完善。servicecontext.go 文件内容如下:

go
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)),    }}

ServiceContext 结构体被设计用来管理和传递服务依赖,也就是将组件的依赖关系从组件的实现中解耦出来。这是一种依赖注入的设计模式,在这个模式中,一个对象(在这里是 ServiceContext)负责提供其他组件所需的依赖。这样就不需要在每个函数或方法中手动传递这些依赖,而是可以把它们封装在上下文中。

下面是 ServiceContext 结构体和 NewServiceContext 函数的详细解释:

首先是 ServiceContext 结构体,这个结构体有两个字段:

  • Config:这个 Config 就是项目中的前面 config.go 中的配置信息。从其类型也可以看出来,第一个 config 是项目中的包名,第二个 Config 是结构体类型名。
  • UserRPC:是一个客户端对象,用于调用用户服务的 RPC 接口。

然后是 NewServiceContext 函数,其是一个构造函数,它初始化 ServiceContext 结构体实例。这个函数做了以下几件事:

  1. 将传入的配置参数 c 直接赋值给 ServiceContext 的 Config 字段,这样 ServiceContext 里的其他部分就可以使用这些配置了。
  2. 调用 userclient.NewUser 函数来创建一个用户服务的 RPC 客户端。userclient.NewUser 需要一个 zrpc.Client 类型的参数,这里通过 zrpc.MustNewClient(c.UserRPC) 创建。MustNewClient 函数根据提供的配置(c.UserRPC)来构造 RPC 客户端。

解决了依赖环境等问题后就可以编写业务逻辑的代码了。调用 user 服务的 GetUserInfo 函数就像在本地调用一样,非常方便。returnorderinfologic.go 中的代码如下:

go
func (l *ReturnOrderInfoLogic) ReturnOrderInfo(req *types.OrderRequest) (resp *types.OrderResponse, err error) {    // todo: add your logic here and delete this line    userInfo, err := l.svcCtx.UserRPC.GetUserInfo(l.ctx, &user.UserRequest{        Id: req.Id,    })    if err != nil {        return nil, err    }    return &types.OrderResponse{        Name:  userInfo.Name,        Sex:   userInfo.Sex,        Age:   userInfo.Age,        Price: 99.99,    }, nil}

3. 测试

测试结果如下:

image-20231210151857085

image-20231210151927510

总结

  • 总的来说,整个 demo 搭建的过程还是比较好理解的,这个 demo 涉及到 RPC 服务和 HTTP 服务的搭建以及 HTTP 服务如何调用 RPC 服务。唯一比较难理解的地方可能就是关于依赖注入的代码,其实只要知道了其原理(设计模式),理解依赖注入也并不难。
  • 更加完整的微服务项目需要包含更多的组件和服务,以下是构建微服务架构项目时可能需要考虑的关键组件:
    1. 服务组件:
      • 服务发现: 服务注册与发现机制,容许服务相互发现和通信,常见的实现有 Consul, Etcd, Zookeeper 等。
      • API 网关: 作为系统的入口点,统一处理外部请求并路由到后端的多个微服务。
      • 负载均衡器: 在多个服务实例之间分配请求,确保负载分配均匀。
      • 微服务: 分解的服务单元,每个实现特定的业务功能。
      • 数据库: 每个微服务可能有自己的数据库实例,以保持服务间的数据隔离。
      • 客户端库: 用于服务间通信的客户端,例如 HTTP 客户端、RPC 客户端等。
    2. 数据管理:
      • 配置管理: 服务配置的外部存储,便于管理和动态更新配置。
      • 数据缓存: 提高数据读取速度和减轻后端数据库压力,常见的如 Redis、Memcached。
      • 消息队列: 实现异步通信和缓冲,帮助系统解耦,如 RabbitMQ, Kafka 等。
    3. 安全性:
      • 认证: 确定用户身份,常见的有 OAuth2.0, JWT 等。
      • 授权: 确定用户是否有权限执行操作。
      • 安全通信: 服务间通信加密,如 TLS/SSL。
    4. 监控与日志:
      • 日志管理: 收集系统和应用日志,如 ELK 栈(Elasticsearch, Logstash, Kibana)。
      • 监控与报警: 监控服务健康和性能指标,常见的工具有 Prometheus, Grafana, Zabbix 等。
      • 分布式跟踪: 跟踪请求穿过微服务的路径,如 Zipkin, Jaeger 等。
    5. 开发与部署:
      • 代码仓库: 如 Git, SVN。
      • 持续集成/持续部署(CI/CD): 自动化代码的构建、测试和部署,如 Jenkins, GitLab CI。
      • 容器化: 如 Docker 来容器化服务。
      • 容器编排: 如 Kubernetes, Docker Swarm 进行服务部署、管理和扩展。
    6. 可扩展性与弹性设计:
      • 服务限流: 防止服务过载。
      • 断路器: 避免故障扩散,如 Hystrix。
      • 服务降级: 在服务出现问题时,自动降级服务质量保持系统可用。
    7. 测试:
      • 单元测试: 验证单个组件的行为。
      • 集成测试: 验证组件间的交互。
      • 端到端测试: 验证整个系统的行为。