Go Micro微服务框架
Go Micro是一种go语言微服务开发框架。
Go Micro整体设计是一种插件化的架构,只为分布式系统开发提供了一个基础框架,还有核心功能,当然这些核心功能也是以插件的形式开发,可以轻易替换掉,剩下的都是靠各种组件增强功能。
1.Go Micro整体架构设计
上图,描述的架构设计主要有三层:
- Micro Runtime - 应用层
- Mico Service - 服务层
- Cloud Platform - 技术平台
Micro Runtime - 可以理解为应用层,就是一些基于微服务的应用组件,我们可以根据业务需要选择应用组件,例如:api网关负责将微服务接口暴露到公网中,web应用,处理一些网页应用。
Mico Service - 服务层,这里就是我们开发的各种微服务了。
Cloud Platform - 云平台,或者叫技术平台,负责支撑微服务运行的环境,例如:我们的容器平台、消息队列、数据库等等,当然如果你的微服务系统不复杂,弄两台服务器外加redis、mysql组成技术平台也可以,并不一定要搞的很复杂。
在这个分层设计中,应用层和服务层属于Go Micro微服务框架要支持的地方。
2.Go Micro核心模块设计
下图是Go Micro框架的核心模块:
service是对微服务的抽象,通常就是一组接口的集合,下面分别介绍各个组件的作用。
Client
客户端,指的就是我们的的rpc客户端,调用微服务接口的客户端。
Server
服务端封装,负责注册服务、处理rpc请求等等
Broker
消息队列的抽象,支持基于消息的事件处理,方便我们处理异步任务,可以根据需要选择各种消息队列,例如:RabbitMQ
Codec
消息编码和解码的组件,客户端和服务端在处理消息的时候都需要codec组件,对消息进行编码或者解码,例如对protobuf格式的消息进行编码和解码。
Register
register提供服务发现功能,支持consul, etcd, zookeeper, dns, gossip等作为注册中心。
Selector
selector是一个负载均衡组件,支持随机、轮训等负载均衡算法,客户端在通过Register组件拿到多个服务端地址的时候,通过Selector组件的负载均衡算法,选择一个服务端进行通信。
Transport
负责客户端和服务端的通信,例如:使用http、websocket协议通信。
提示:Go Micro框架核心模块不包括Go Runtime的应用组件,这些应用组件需要单独安装。
3.Go Micro应用组件介绍
Micro Runtime 实现了一些微服务系统常用的应用组件,我们可以跟需要安装组件,下面介绍一些常用的组件。
3.1.API Gateway
api网关,统一的Http api入口,可以将我们的微服务接口暴露到公网中。
3.2.Interactive CLI
Go Micro微服务的命令行工具,可以用来查询服务,调用服务接口等等。
3.3.Web Dashboard
Go Micro微服务web后台,可以用来查询我们正在运行的微服务状态信息。
go micro微服务框架入门教程
本节介绍如何使用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/访问后台。
接口测试页面:
遗留问题:客户端通过服务名为什么可以调用微服务,微服务部署在不同的机器怎么通信?后面的教程会解答这个问题。
Go Micro服务发现
服务发现,就是通过什么办法可以找到需要调用的服务的地址(ip和端口),因为只有拿到服务的地址,我们才可以连接服务,发送接口调用请求。
上一个章节,我们没说明两个服务直接是如何找到对方的,只是使用服务名,就可以调用远程服务api,本章将介绍Go Micro如何通过服务名找到服务的地址。
Go Micro的Register模块负责服务发现,Go Micro 框架以插件的形式内置了几种常用的服务发现组件,下面分别介绍三种常用的服务发现组件:mDNS、Consul、Etcd
Consul和Etcd都是基于注册中心实现服务发现,大致原理如下图:
- 服务启动的时候,主动向注册中间注册自己的服务信息(服务名、Ip、port)
- 客户端,通过服务名去注册中心,查询服务信息,拿到对应的ip和port,一个服务名可能查询到多个服务地址,默认go micro框架的Selector模块,通过随机算法从多个服务地址中选择一个进行通信(这样也就实现了负载均衡处理),然后向服务发起接口调用。
提示:如果对服务发现还是不太了解,可以看下这篇博客:微服务之服务发现。
1.基于mDNS服务发现
上一章的例子,就是通过mDNS实现服务发现,这也是Go Micro框架默认的机制,不需要任何配置。
mDNS(多播DNS)是一种局域网内使用的DNS机制,他的大致原理如下:当有新的节点加入局域网的时候,如果打开了mDNS,就主动向局域网其他所有节点广播,自己提供的服务(域名是什么、ip地址是什么、端口号是什么), 这样我们任何一个节点都知道局域网提供了什么服务。
例如:
Go Micro微服务A启动的时候,通过mDNS协议,向局域网的所有节点,广播自己的服务名、Ip和端口号。 当另外一个微服务B,需要调用微服务A的,通过微服务A的服务名,去向mDNS查询ip地址和端口号,如果找不到,就向局域网广播询问,谁有微服务A的地址信息,通过这样的机制我们就可以找到同一个局域网内的服务信息。
提示:基于mDNS的服务发现,不需要额外的中间件和配置,适合个人开发做实验,团队多人合作开发,往往有部分微服务是在云服务器上面,通过mDNS无法查询这些服务的信息,生产环境也会选择其他服务发现中间件。
2.基于Consul的服务发现
Consul是解决服务发现、配置中心的分布式中间件,适合生产环境使用。
如果不熟悉Consul,可以点击这里了解下:Consul教程
当安装好Consul后,我们可以通过环境变量设置Go Micro的服务发现中间件配置。
例如:
MICRO_REGISTRY=consul \
MICRO_REGISTRY_ADDRESS=127.0.0.1:8500 \
go run orderservice.go
参数说明:
- MICRO_REGISTRY - 注册中心类型,这里设置为consul
- MICRO_REGISTRY_ADDRESS - 注册中心地址, IP + PORT的形式
运行后,你会发现增加一条类似的日志,说明服务信息已经注册到Consul了。
2019/10/29 22:08:13 Registry [consul] Registering node: go.micro.api.orderservice-da1e90a0-975b-4b55-81bf-7f1d846f559e
注意:使用consul后,所有微服务启动的时候都需要配置使用同一个consul,包括我们使用micro call 命令和启动其他mico组件,都需要配置register环境变量,否则会出现找不到服务的错误。
3.基于Etcd的服务发现
Etcd是强一致性的、分布式键值存储引擎,类似zookeeper。
当你安装好Etcd后,使用Etcd作为注册中心,让go micro微服务基于Etcd实现服务发现,配置方式跟consul类似。
例如:
MICRO_REGISTRY= etcd \
MICRO_REGISTRY_ADDRESS=127.0.0.1:2379 \
go run orderservice.go
参数说明:
- MICRO_REGISTRY - 注册中心类型,这里设置为
etcd
- MICRO_REGISTRY_ADDRESS - 注册中心地址, IP + PORT的形式
Go Micro API网关
使用微服务架构后,后端的微服务有很多个,同一个微服务也会部署很多个实例,服务之间虽然可以通过服务发现,进而互相通信。那么如果我们想让移动app,web app调用我们的服务怎么办?这时候api网关,就提供一个后端api的统一入口,app可以通过这个api网关,访问我们的微服务接口。
Go Micro框架也提供了一个简单的api网关,这个api网关只能处理http请求,然后将请求转发给我们后端的微服务,下面通过一个例子介绍api网关的用法。
1.使用网关的应用架构
下图是一个三层架构的例子:
从左边往右边看,一共分为三层:
- 第一层 Micro api网关
- 第二层 聚合业务层
- 第三层 基础服务层
下面分别介绍,将系统划分为这三层的作用。
1.1. 第一层api网关
第一层就是Go Micro api网关,移动app可以通过api网关的地址,请求后端提供的接口,这一层是不需要开发的,启动micro api就可以代理客户端的请求,后面会介绍micro api网关是如何将请求转发到具体的微服务。
1.2. 第二层聚合业务层
聚合业务层,主要负责组合各种底层的微服务提供的功能,实现业务需求。
如果上图的例子:api网关接受了一个/customer/orders的api请求(查询用户的订单),api网关将请求转发给聚合服务层的Customer api服务(用户api聚合服务),然后Customer api服务分别调用Customer Service(用户服务)和Order Service(订单服务)两个服务,最后Customer api服务将组合底层服务处理的结果返回去。
为什么要多搞一层聚合服务层,而不是直接将这些功能写到后端的某个服务里面?
- 分层架构设计,可以有效的复用代码。
- 确保底层基础服务的单一职责,也是为了代码复用率。
- 扩展性,越是底层的基础服务,越稳定,我们将容易发生变化的逻辑迁移到上层,也就是聚合服务层,我们可以在聚合服务层快速组合底层的基础服务,满足业务需求。
1.3. 第三层基础服务层
基础服务,通常都负责业务最底层的数据处理,通用的业务逻辑,确保服务的单一职责。
提示:这个例子中的第二第三层,最早实现的时候都是微服务,只不过我们按照职责将微服务分为了两层,当然你可以根据自己的需求自己设计系统分层。
2.安装网关
go get github.com/micro/micro
安装成功后,会在$GOPATH/bin目录中多了一个micro命令,micro命令包含了我们需要的网关。
提示:需要将$GOPATH/bin 目录添加到环境变量PATH中,否则会提示找不到micro命令。
3.官方例子
go micro官方提供了很多demo, 下面我们直接通过官方的一个例子,介绍怎么使用网关。
3.1. 下载官方例子源码
git clone https://github.com/micro/examples
3.2. 启动greeter例子
# 切换到例子源码根目录
cd examples
# 启动基础服务
go run greeter/srv/main.go
# 新打开一个命令窗口,启动api服务,这就是我们说的聚合服务
go run greeter/api/api.go
# 新打开一个命令窗口,启动go micro api网关
micro api --handler=api
这样我们相关的服务都启动完成,默认网关的端口是8080
3.3. 测试网关api
请求上面例子的网关接口
curl "http://localhost:8080/greeter/say/hello?name=John"
输出:
{"message":"Hello John"}
URL路由说明:
通过网关请求/greeter/say/hello,这个路径,网关会将请求转发到go.micro.api.greeter服务的Say.Hello方法处理。
go.micro.api是网关的默认服务名的前缀,所以,根据/greeter/say/hello这个url可以拼接出go.micro.api.greeter这个服务名,除了greeter之外的URL路径,作为rpc接口参数。
提示:这里没有把例子代码贴出来,仅仅演示怎么使用网关,代码大家可以打开前面go run运行的源码看一下。
Go Micro Web后台
Go Micro框架自带了一个web后台,可以通过这个后台,查看当前注册的服务,还有测试服务的接口。
安装
go get github.com/micro/micro
安装成功后,会在$GOPATH/bin目录中多了一个micro命令。
提示:需要将$GOPATH/bin 目录添加到环境变量PATH中,否则会提示找不到micro命令。
启动Web后台
micro web
通过http://localhost:8082访问后台
如果你使用了专门的注册中心,可以通过环境变量,设置注册中心服务地址。
例如使用consul作为注册中心,启动web后台的命令
MICRO_REGISTRY=consul \
MICRO_REGISTRY_ADDRESS=127.0.0.1:8500 \
micro web
后台截图
后台首页截图,直接展示当前注册的服务有哪些。
下图是测试服务接口的页面,在这里可以模拟调用所有的接口。
下图是服务的详情。