本节介绍如何使用micro微服务框架开发微服务。
我们以订单服务和用户服务为例子,介绍如何开发微服务、微服务之间如何调用。
业务需求说明:
如果我们要根据订单id,查询订单信息(要求返回用户的地址和电话信息),但是订单信息仅保存了用户id,那么就需要通过用户id去用户服务查询用户信息。
下面一步步演示如何通过微服务完成这个需求。
1.依赖知识点
go micro依赖以下知识点,根据自己的情况,预先了解下面的知识点。
2.安装环境
安装protobuf编译器和对应的go语言插件
根据上面Grpc教程提示安装即可。
安装go micro微服务框架的编译器插件
go get github.com/micro/protoc-gen-micro
提示:记得将$GOPATH/bin目录添加到PATH环境变量中,这样才能直接找到我们安装的命令和插件,而不会提示找不到命令。
3.项目目录结构
为了方便演示,我们仅创建了一个项目,包含了用户服务和订单服务(实际项目中,一般一个微服务就是一个独立的项目)。
tizi/
├── go.mod // go模块配置文件
├── orderservice.go // 订单服务
├── proto // 保存微服务的接口定义文件
│ ├── order.proto // 订单服务的接口定义
│ └── user.proto // 用户服务的接口定义
└── userservice.go // 用户服务
初始化项目命令:
# 创建目录
mkdir tizi
cd tizi
# 创建接口定义目录
mkdir proto
# 初始化go模块, 我们模块名字叫:tizi365.com/tizi
go mod init tizi365.com/tizi
说明:上面是以Linux为例子介绍,windows也是类似的思路。
3.开发用户服务
用户服务,负责用户相关的业务,下面演示一个简单的用户服务。
实现一个微服务的步骤:
- 使用protobuf协议定义服务接口
- 使用protoc编译器根据我们定义的接口,生成go语言骨架代码
- 根据生成的骨架代码实现服务接口
- 初始化go micro微服务的服务端,注册微服务。
3.1.定义用户服务接口
下面使用protobuf协议定义用户服务的接口
文件:proto/user.proto
syntax = "proto3";
// 定义包名
package proto;
// 定义user服务的接口
service UserSrv {
// 获取用户账号信息
rpc GetAccount(GetAccountRequest) returns (Account) {}
}
// 定义获取账号信息的请求消息
message GetAccountRequest {
int32 id = 1; // 用户id
}
// 定义用户账号消息
message Account {
int32 id = 1; // id
string username = 2; // 账号
string address = 3; // 地址
string phone =4; // 电话
}
我们这里定义了一个GetAccount接口,用来读取用户信息。
3.2. 编译接口文件
上面定义了一个proto接口文件,我使用protoc编译器,生成go语言骨架代码。
# 切换到项目目录
cd tizi
# 编译user.proto接口定义文件
protoc --proto_path=proto:. --micro_out=proto/ --go_out=proto/ proto/user.proto
protoc参数说明:
- --proto_path - proto文件目录
- --micro_out - 生成的micro源码保存目录
- --go_out - 生成的go源码保存目录
- proto/user.proto - 最后面的参数就是我们要编译的proto文件
执行上面命令后,在proto目录下面生成了user.pb.go和user.micro.go两个文件,这两个文件包含了,我们定义的接口、请求和响应参数等等。
3.3. 实现用户服务
文件:userservice.go
package main
import (
"context"
"github.com/micro/go-micro"
"tizi365.com/tizi/proto"
)
// 定义用户服务, 实现proto协议定义的接口
type UserService struct {
}
// 实现查询账号信息接口
// 需要注意的是,go micro微服务的rpc接口,请求参数和返回参数,都作为函数参数传入,而grpc的接口,返回参数作为函数返回值。
func (u *UserService) GetAccount (ctx context.Context, request *proto.GetAccountRequest, response *proto.Account) error {
// 作为演示这里直接返回账号信息
response.Id = request.Id // 从request获取请求参数.
response.Username = "tizi365"
response.Address = "深圳市南山区西乡街道101号"
response.Phone = "1300001111"
return nil
}
func main() {
// 定义一个微服务
service := micro.NewService(
micro.Name("go.micro.api.userservice"), // 定义用户服务的服务名
)
// 初始化
service.Init()
// 注册用户服务
proto.RegisterUserSrvHandler(service.Server(), new(UserService))
// 启动服务
if err := service.Run(); err != nil {
panic(err)
}
}
提示:微服务的服务名,必须唯一,客户端通过服务名调用微服务的接口。
3.4. 运行用户服务
go run userservice.go
输出类似如下信息:
2019/10/18 00:08:40 Transport [http] Listening on [::]:51723
2019/10/18 00:08:40 Broker [http] Connected to [::]:51724
2019/10/18 00:08:40 Registry [mdns] Registering node: go.micro.api.userservice-84262902-0070-4820-8067-be76d85e7cf4
到这步说明用户服务启动成功了,下面我们看看订单服务,怎么调用用户服务的接口。
4.开发订单服务
订单服务负责订单相关的业务,我们这里这里仅开发一个查询订单信息的接口
4.1.定义订单服务接口
文件:proto/order.proto
syntax = "proto3";
// 定义包名
package proto;
// 定义订单服务的接口
service OrderSrv {
// 获取订单信息
rpc GetOrder(GetOrderRequest) returns (Order) {}
}
// 定义获取订单的请求消息
message GetOrderRequest {
int32 id = 1; // 订单id
}
// 定义订单消息
message Order {
int32 id = 1; // 订单id
string name = 2; // 商品名
double price = 3; // 价格
string username = 4; //用户
string address = 5; // 用户地址
string phone =6; // 联系电话
string createTime = 7; //创建订单时间
}
4.2.编译接口文件
上面定义了一个proto接口文件,我使用protoc编译器,生成go语言骨架代码。
# 切换到项目目录
cd tizi
# 编译order.proto接口定义文件
protoc --proto_path=proto:. --micro_out=proto/ --go_out=proto/ proto/order.proto
执行命令后,在proto目录生成了order.pb.go和order.micro.go两个go语言的定义文件。
4.3.实现订单服务
文件:orderservice.go
package main
import (
"context"
"github.com/micro/go-micro"
"time"
"tizi365.com/tizi/proto"
)
// 定义用户服务, 实现proto协议定义的接口
type OrderService struct {
}
// 实现查询订单信息的接口
func (u *OrderService) GetOrder (ctx context.Context, request *proto.GetOrderRequest, response *proto.Order) error {
// 通常我们的订单信息都保存了用户的id, 例如用户id如下
userId := 1312
// 初始化用户服务对象
// 初始化用户服务,需要用户服务的名字,这是一个初始化用户服务的时候定义的唯一标识
userSrv := proto.NewUserSrvService("go.micro.api.userservice", service.Client())
// 调用用户服务,查询用户信息,获取用户的电话和地址
user, err := userSrv.GetAccount(context.TODO(), &proto.GetAccountRequest{Id:int32(userId)})
if err == nil {
// 调用成功, 初始化订单返回值
response.Username = user.Username
response.Address = user.Address
response.Phone = user.Phone
}
response.Name = "大瓶可乐"
response.Price = 6.5
response.Id = request.Id // 从request获取请求参数.
// 订单创建时间
response.CreateTime = time.Now().Format(time.RFC3339)
return nil
}
// 声明服务对象
var service micro.Service
func main() {
// 定义一个微服务
service = micro.NewService(
micro.Name("go.micro.api.orderservice"), // 定义订单服务的服务名
)
// 初始化
service.Init()
// 注册订单服务
proto.RegisterOrderSrvHandler(service.Server(), new(OrderService))
// 启动服务
if err := service.Run(); err != nil {
panic(err)
}
}
4.4.运行订单服务
另外打开一个命令窗口,输入运行命令:
go run orderservice.go
输出类似启动信息:
2019/10/18 00:17:50 Transport [http] Listening on [::]:51839
2019/10/18 00:17:50 Broker [http] Connected to [::]:51840
2019/10/18 00:17:50 Registry [mdns] Registering node: go.micro.api.orderservice-9f30f8d8-3c5a-47d7-ac61-5f0766140507
到目前为止,订单服务也成功启动,我们调用订单服务的GetOrder接口,GetOrder接口会去调用用户服务查询用户信息。
那么不写代码的情况下,怎么调用服务的接口呢? 总不能在开发一个客户端调用订单服务的接口。
go micro微服务框架为我们准备可视化的web后台,可以查询所有的注册的微服务,调试微服务接口,下面一节会介绍如何调试微服务接口。
5.调试微服务接口
go micro框架为我们提供了两种调试微服务接口的方法:
- 通过micro命令工具调试服务接口
- 通过micro web后台调试服务接口
我们首先安装micro运行时组件
go get github.com/micro/micro
5.1.通过命令调试
调用服务接口命令语法:
micro call 服务名 接口 参数
参数以json的形式提交。
例子:
调用订单服务:go.micro.api.orderservice 的OrderSrv.GetOrder接口,以json的形式传入了一个参数id
micro call go.micro.api.orderservice OrderSrv.GetOrder '{"id":1}'
提示:服务名是我们启动微服务定义的唯一标识,接口的格式是:proto文件定义的service名字 + rpc接口名
输出:
{
"id": 1,
"name": "大瓶可乐",
"price": 6.5,
"username": "tizi365",
"address": "深圳市南山区西乡街道101号",
"phone": "1300001111",
"createTime": "2019-10-18T00:26:28+08:00"
}
结果以json的形式打印出来了。
5.2.通过micro web调试
启动micro web后台
micro web
启动后,通过http://localhost:8082/访问后台。
接口测试页面:
遗留问题:客户端通过服务名为什么可以调用微服务,微服务部署在不同的机器怎么通信?后面的教程会解答这个问题。