demo 的内容是搭建两个服务,一个是
user服务,用来提供用户信息;另一个是order服务,需要调用 user 服务获取用户信息,最后把订单信息返回给前端。所以在这个 demo 中,user 服务需要提供 RPC 接口,而 order 服务需要进行 RPC 接口调用并提供 HTTP 接口。
user 服务搭建
1. 模板生成
使用 go-zero 框架提供了 goctl 命令可以快熟搭建 RPC 服务
# 生成user服务模板goctl rpc new user# 解决依赖go mod tidy2. proto 文件编写和 RPC 接口生成
user.proto 文件的内容如下。
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 服务下的目录结构如下:
├─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.go和user.proto。作用如下:
- user.proto:定义了 RPC 服务接口和消息结构。
- user.go:服务的入口点,包含 main 函数,用来启动 RPC 服务。
3. 编写 logic 代码
由于 order 服务会调用 user 服务,调用 user 服务的时候会传入用户 id,那么 logic 就可以根据用户 id 做出响应即可。
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 配置如下:
Etcd: Hosts: - 192.169.11.10:2379 Key: user.rpcetcd 配置并启动完成后就可以运行 user 服务,在 user 服务的根目录下,执行 go run user.go 命令即可运行 user 服务。
5. 测试
可以使用 ApiPost 软件来测试刚刚编写的 RPC 服务。测试结果如下:


order 服务搭建
1. 模板生成和 HTTP 服务结构搭建
使用命令:goctl api new order可以快速生成一个 order 服务的模板。模板生成后可以看到在 order 服务下有一个 order.api 的文件,可以利用该文件编写 api 接口,最后使用命令生成 HTTP 服务的基本结构。order.api 文件的内容如下:
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 服务结构,命令的执行结果如下:
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 就表示执行成功。生成的目录结构如下:
├─etc # 配置文件夹,里面有一个.yaml的配置文件└─internal ├─config # 把 YAML 或其他格式的配置文件解析成 Go 的配置结构体,便于应用程序中其他部分使用。 ├─handler # 处理具体的业务逻辑请求,通常一个路由对应一个 Handler 函数。 ├─logic # 包含业务逻辑的主体,每个接口通常对应一个 Logic 结构体和相关方法。 ├─svc # Service Context 结构体定义,在这里初始化你的业务需要的依赖,比如数据库连接、外部服务客户端等。 └─types # 定义了 API 请求和响应的数据结构,通常由 API 文件中定义的类型自动生成。除此之外,还有两个文件:order.api和order.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 文件的内容如下:
Name: order-apiHost: 0.0.0.0Port: 8888UserRPC: Etcd: Hosts: - 192.168.11.10:2379 Key: user.rpc然后需要在 config 文件夹的 config.go 文件中将刚刚的配置信息映射到结构体中,config.go 内容如下:
type Config struct { rest.RestConf UserRPC zrpc.RpcClientConf}补充:这个 RpcClientConf 其实包含很多配置信息,其定义如下:
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 文件内容如下:
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 结构体实例。这个函数做了以下几件事:
- 将传入的配置参数 c 直接赋值给 ServiceContext 的 Config 字段,这样 ServiceContext 里的其他部分就可以使用这些配置了。
- 调用 userclient.NewUser 函数来创建一个用户服务的 RPC 客户端。userclient.NewUser 需要一个 zrpc.Client 类型的参数,这里通过 zrpc.MustNewClient(c.UserRPC) 创建。MustNewClient 函数根据提供的配置(c.UserRPC)来构造 RPC 客户端。
解决了依赖环境等问题后就可以编写业务逻辑的代码了。调用 user 服务的 GetUserInfo 函数就像在本地调用一样,非常方便。returnorderinfologic.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. 测试
测试结果如下:


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