Go Micro微服务框架

 

Go Micro是一种go语言微服务开发框架。

Go Micro整体设计是一种插件化的架构,只为分布式系统开发提供了一个基础框架,还有核心功能,当然这些核心功能也是以插件的形式开发,可以轻易替换掉,剩下的都是靠各种组件增强功能。

1.Go Micro整体架构设计

go架构 yearning go micro 架构图_go架构 yearning

上图,描述的架构设计主要有三层:

  • Micro Runtime - 应用层
  • Mico Service - 服务层
  • Cloud Platform - 技术平台

Micro Runtime - 可以理解为应用层,就是一些基于微服务的应用组件,我们可以根据业务需要选择应用组件,例如:api网关负责将微服务接口暴露到公网中,web应用,处理一些网页应用。

Mico Service - 服务层,这里就是我们开发的各种微服务了。

Cloud Platform - 云平台,或者叫技术平台,负责支撑微服务运行的环境,例如:我们的容器平台、消息队列、数据库等等,当然如果你的微服务系统不复杂,弄两台服务器外加redis、mysql组成技术平台也可以,并不一定要搞的很复杂。

在这个分层设计中,应用层和服务层属于Go Micro微服务框架要支持的地方。

2.Go Micro核心模块设计

下图是Go Micro框架的核心模块:

go架构 yearning go micro 架构图_Go_02

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.开发用户服务

用户服务,负责用户相关的业务,下面演示一个简单的用户服务。

实现一个微服务的步骤:

  1. 使用protobuf协议定义服务接口
  2. 使用protoc编译器根据我们定义的接口,生成go语言骨架代码
  3. 根据生成的骨架代码实现服务接口
  4. 初始化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架构 yearning go micro 架构图_Go_03

接口测试页面:

go架构 yearning go micro 架构图_服务发现_04

遗留问题:客户端通过服务名为什么可以调用微服务,微服务部署在不同的机器怎么通信?后面的教程会解答这个问题。

Go Micro服务发现

服务发现,就是通过什么办法可以找到需要调用的服务的地址(ip和端口),因为只有拿到服务的地址,我们才可以连接服务,发送接口调用请求。

上一个章节,我们没说明两个服务直接是如何找到对方的,只是使用服务名,就可以调用远程服务api,本章将介绍Go Micro如何通过服务名找到服务的地址。

Go Micro的Register模块负责服务发现,Go Micro 框架以插件的形式内置了几种常用的服务发现组件,下面分别介绍三种常用的服务发现组件:mDNS、Consul、Etcd

Consul和Etcd都是基于注册中心实现服务发现,大致原理如下图:

go架构 yearning go micro 架构图_go架构 yearning_05

  1. 服务启动的时候,主动向注册中间注册自己的服务信息(服务名、Ip、port)
  2. 客户端,通过服务名去注册中心,查询服务信息,拿到对应的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网关
  • 第二层 聚合业务层
  • 第三层 基础服务层

go架构 yearning go micro 架构图_go架构 yearning_06

下面分别介绍,将系统划分为这三层的作用。

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

后台截图

后台首页截图,直接展示当前注册的服务有哪些。

go架构 yearning go micro 架构图_微服务_07

下图是测试服务接口的页面,在这里可以模拟调用所有的接口。

go架构 yearning go micro 架构图_go架构 yearning_08

下图是服务的详情。

go架构 yearning go micro 架构图_go架构 yearning_09