在了解go-micro之前,我们先来了解一下什么是micro。
Micro是一个专注于简化分布式系统开发的微服务生态系统。由开源库和工具组成。主要包含以下几种库:
go-micro用于编写微服务的可插入Go-RPC框架; 服务发现,客户端/服务器rpc,pub/sub等,是整个Micro的核心。
默认使用mdns做服务发现,可以在插件中替换成consul,etcd,k8s等
go-pluginsgo-micro的插件,包括etcd,kubernetes(k8s),nats,rabbitmq,grpc等
micro一个包含传统入口点的微服务工具包; API网关,CLI,Slack Bot,Sidecar和Web UI。
其他各种库和服务可以在github.com/micro找到。
我们先来了解一下服务发现是个什么东西?有什么作用?
我们在做微服务开发的时候,客户端的一个接口可能需要调用N个服务,客户端必须知道所有服务的网络位置(ip+port),如下图所示
以往的做法是把服务的地址放在配置文件活数据库中,这样就有以下几个问题:
总结起来一句话:服务多了,配置很麻烦,问题一大堆
所以现在就选择服务发现来解决这些问题。我们来看一下,服务发现如何解决这个问题,具体设计如下:
与之前解决方法不同的是,加了个服务发现模块。服务端把当前自己的网络位置注册到服务发现模块(这里注册的意思就是告诉),服务发现就以K-V的方式记录下,K一般是服务名,V就是IP:PORT。服务发现模块定时的轮询查看这些服务能不能访问的了(这就是健康检查)。客户端在调用服务A-N的时候,就跑去服务发现模块问下它们的网络位置,然后再调用它们的服务。这样的方式是不是就可以解决上面的问题了呢?客户端完全不需要记录这些服务的网络位置,客户端和服务端完全解耦!
常见的服务发现框架有:Etcd、Eureka、Consul、Zookeeper
这里我们选择go-micro默认的服务发现框架consul来做一个详细介绍。
Consul是HashiCorp公司推出的开源工具,用于实现分布式系统的服务发现与配置。包含多个组件,但是作为一个整体,为你的基础设施提供服务发现和服务配置的工具.他提供以下关键特性:
服务发现:consul通过DNS或者HTTP接口使服务注册和服务发现变的很容易,一些外部服务,例如saas提供的也可以一样注册。
健康检查:健康检测使consul可以快速的告警在集群中的操作。和服务发现的集成,可以防止服务转发到故障的服务上面。(心跳机制)
键/值存储:一个用来存储动态配置的系统。提供简单的HTTP接口,可以在任何地方操作。
多数据中心:无需复杂的配置,即可支持任意数量的区域。
官方建议:最好是三台或者三台以上的consul在运行,同名服务最好是三台或三台以上,默认可以搭建集群
Consul用Golang实现,因此具有天然可移植性 (支持 Linux、windows和macOS)。安装包仅包含一个可执行文件。 Consul安装非常简单,只需要下载对应系统的软件包并解压后就可使用。
安装步骤如下:
# 这里以 ubuntu系统为例:
$ wget https://releases.hashicorp.com/consul/1.5.2/consul_1.5.2_linux_amd64.zip
$ unzip consul_1.5.2_linux_amd64.zip
$ sudo mv consul /usr/local/bin/
也可用离线包进行解压安装
链接:https://pan.baidu.com/s/1OQoAdn2_IrWbYbQUCg3_zQ
提取码:a4rd
安装验证:
安装 Consul后,通过执行 consul命令,你可以看到命令列表的输出
~ consul
无报错即为成功
consul安装好之后,我们来使用一下吧。首先我们来看一下consul都有哪些命令。使用命令consul -h
可以查看consul支持的所有参数,而且每个参数里面还支持其他参数,下面我们来具体看看。
agent指令是consul的核心,它运行agent来维护成员的重要信息、运行检查、服务宣布、查询处理等等。
-bind=0.0.0.0 指定 consul所在机器的 IP地址。 默认值:0.0.0.0
-http-port=8500 consul 自带一个web访问的默认端口:8500
-client=127.0.0.1 表明哪些机器可以访问consul 。 默认本机。0.0.0.0 所有机器均可访问。
-config-dir=foo 所有主动注册服务的 描述信息
-data-dir=path 储存所有注册过来的srv机器的详细信息。
-dev 开发者模式,直接以默认配置启动 consul
-node=hostname 服务发现的名字。
-rejoin consul 启动的时候,加入到的 consul集群
-server 以服务方式开启consul, 允许其他的consul 连接到开启的 consul上 (形成集群)。如果不加 -server, 表示以 “客户端” 的方式开启。不能被连接。
-ui 可以使用 web 页面 来查看服务发现的详情
测试上述命令:
# 在终端中,键入:
consul agent -server -bootstrap-expect 1 -data-dir /tmp/consul -node=n1 -bind=192.168.6.108 -ui -rejoin -config-dir=/etc/consul.d/ -client 0.0.0.0
#看到提示:
==> Consul agent running!
需要先在/etc/下面创建consul.d目录
-server
: 定义agent运行在server模式-bootstrap-expect
:在一个datacenter中期望提供的server节点数目,当该值提供的时候,consul一直等到达到指定sever数目的时候才会引导整个集群,该标记不能和bootstrap共用-bind
:该地址用来在集群内部的通讯,集群内的所有节点到地址都必须是可达的,默认是0.0.0.0-node
:节点在集群中的名称,在一个集群中必须是唯一的,默认是该节点的主机名-ui
: 启动web界面 :8500-rejoin
:使consul忽略先前的离开,在再次启动后仍旧尝试加入集群中。-config-dir
:配置文件目录,里面所有以.json结尾的文件都会被加载-client
:consul服务侦听地址,这个地址提供HTTP、DNS、RPC等服务,默认是127.0.0.1所以不对外提供服务,如果你要对外提供服务改成0.0.0.0data-dir
:提供一个目录用来存放agent的状态,所有的agent允许都需要该目录,该目录必须是稳定的,系统重启后都继续存在
你可以使用Ctrl-C 不优雅的关闭Agent. 中断Agent之后你可以看到他离开了集群并关闭.
在退出中,Consul提醒其他集群成员,这个节点离开了.如果你强行杀掉进程.集群的其他成员应该能检测到这个节点失效了.当一个成员离开,他的服务和检测也会从目录中移除.当一个成员失效了,他的健康状况被简单的标记为危险,但是不会从目录中移除.Consul会自动尝试对失效的节点进行重连.允许他从某些网络条件下恢复过来.离开的节点则不会再继续联系.
此外,如果一个agent作为一个服务器,一个优雅的离开是很重要的,可以避免引起潜在的可用性故障影响达成一致性协议.
consul优雅的退出:
$ consul leave
搭建好conusl集群后,用户或者程序就能到consul中去查询或者注册服务。可以通过提供服务定义文件或者调用HTTP API来注册一个服务.
这里我们使用定义服务文件来注册一个服务:
{"service": {
"name": "Faceid",
"tags": ["rails"],
"port": 9000
}
}
服务定义文件在我们的配置目录下面,
/etc/consul.d/
,文件都是以.json结尾。
注册完服务之后,我们重启consul。
一旦agent启动并且服务同步了.我们可以通过DNS或者HTTP的API来查询服务.这里我们通过HTTP来查询服务:
$ curl -s 127.0.0.1:8500/v1/catalog/service/faceid
可以得到一串json数据,可拿去进行json格式解析,还可以打开web页面,查看注册的服务。默认consul对应的端口是8500,在浏览器输入地址`localhost:8500,也可以看到注册的服务
健康检查是服务发现的关键组件.预防使用到不健康的服务.和服务注册类似,一个检查可以通过检查定义或HTTP API请求来注册.我们将使用和检查定义来注册检查.和服务类似,因为这是建立检查最常用的方式.
在/etc/consul.d/目录下面创建文件web2.json,内容如下:
{"service": {
"name": "web",
"tags": ["extract", "verify", "compare", "idcard"],
"address": "192.168.137.130",
"port": 9000,
"check": {
"id": "api",
"name": "HTTP API on port 9000",
"http": "http://localhost:9000",
"interval": "10s",
"timeout": "1s"
}
}
}
如果我们没有开启服务,那么健康检查一定会报错
consul做健康检查的必须是Script、HTTP、TCP、TTL中的一种。
Script类型需要提供Script脚本和interval变量。**具体配置如下:
{
"check": {
"id": "mem-util",
"name": "Memory utilization",
"script": "/usr/local/bin/check_mem.py",
"interval": "10s",
"timeout": "1s"
}
}
通过执行外部应用进行健康检查:这种外部程序具有退出代码,并可能产生一些输出;脚本按照指预置时间间隔来调用(比如,每30秒调用一次),类似于Nagios插件系统,脚本输出限制在4K以内,输出大于4K将截断。默认情况下,脚本超时时间为30秒——可通过timeout来配置。
HTTP类型必须提供http和Interval字段。具体代码如下:
{
"check": {
"id": "api",
"name": "HTTP API on port 5000",
"http": "http://localhost:5000/health",
"interval": "10s",
"timeout": "1s"
}
}
这种检查将按照预设的时间间隔创建一个HTTP “get”请求。HTTP响应代码来标示服务所处状态:任何2xx代码视为正常,429表示警告——有很多请求;其他值表示失败。
这种类型的检查应使用curl或外部程序来处理HTTP操作。默认情况下,HTTP Checks中,请求超时时间等于调用请求的间隔时间,最大10秒。有可能使用客制的HTTP check,可以自由配置timeout时间,输出限制在4K以内,输出大于4K将截断。
TCP类型需要提供tcp和Interval字段。具体代码如下:
{
"check": {
"id": "ssh",
"name": "SSH TCP on port 22",
"tcp": "localhost:22",
"interval": "10s",
"timeout": "1s"
}
}
这种检查将按照预设的时间间隔与指定的IP/Hostname和端口创建一个TCP连接。服务的状态依赖于TCP连接是否成功——如果连接成功,则状态是“success”;否则状态是“critical”。如果一个Hostname解析为一个IPv4和一个IPv6,将尝试连接这两个地址,第一次连接成功则服务状态是“success”。默认情况下,TCP checks中,请求超时时间等于调用请求的间隔时间,最大10秒。也是可以自由配置的。
TTL(Timeto Live生存时间)类型只需提供ttl,具体配置如下:
{
"check": {
"id": "web-app",
"name": "Web App Status",
"notes": "Web app does a curl internally every 10 seconds",
"ttl": "30s"
}
}
这种checks为给定TTL保留了最后一种状态,checks的状态必须通过HTTP接口周期性更新,如果外部接口没有更新状态,那么状态就会被认定为不正常。 TTL checks同时会将其最后已知状态更新至磁盘,这允许Agent通过重启后恢复到已知的状态。通过TTL端上一次check来维持健康状态的有效性。
其他更多consul功能,我们可以参考http://www.liangxiansen.cn/2017/04/06/consul/
先从GitHub下载consul包
$ go get -u -v github.com/hashicorp/consul
代码示例:
proto文件:
syntax = "proto3";
package pb;
message Person {
string name = 1;
int32 age = 2;
}
// 添加 rpc服务
service hello {
rpc sayHello (Person) returns (Person);
}
服务端:
package main
import (
"google.golang.org/grpc"
"day02/pb"
"context"
"net"
"fmt"
"github.com/hashicorp/consul/api"
)
// 定义类
type Children struct {
}
// 绑定类方法, 实现借口
func (this *Children)SayHello(ctx context.Context, p *pb.Person) (*pb.Person, error) {
p.Name = "hello " + p.Name
return p, nil
}
func main() {
// 把grpc服务,注册到consul上.
// 1. 初始化consul 配置
consulConfig := api.DefaultConfig()
// 2. 创建 consul 对象
consulClient, err := api.NewClient(consulConfig)
if err != nil {
fmt.Println("api.NewClient err:", err)
return
}
// 3. 告诉consul, 即将注册的服务的配置信息
reg := api.AgentServiceRegistration {
ID:"bj38",
Tags:[]string{"grcp", "consul"},
Name:"grpc And Consul",
Address:"127.0.0.1",
Port:8800,
Check:&api.AgentServiceCheck{
CheckID:"consul grpc test",
TCP:"127.0.0.1:8800",
Timeout:"1s",
Interval:"5s",
},
}
// 4. 注册 grpc 服务到 consul 上
consulClient.Agent().ServiceRegister(®)
//////////////////////以下为 grpc 服务远程调用////////////////////////
// 1.初始化 grpc 对象,
grpcServer := grpc.NewServer()
// 2.注册服务
pb.RegisterHelloServer(grpcServer, new(Children))
// 3.设置监听, 指定 IP/port
listener, err := net.Listen("tcp", "127.0.0.1:8800")
if err != nil {
fmt.Println("Listen err:", err)
return
}
defer listener.Close()
fmt.Println("服务启动... ")
// 4. 启动服务
grpcServer.Serve(listener)
}
客户端:
package main
import (
"google.golang.org/grpc"
"day02/pb"
"context"
"fmt"
"github.com/hashicorp/consul/api"
"strconv"
)
func main() {
// 初始化 consul 配置
consulConfig := api.DefaultConfig()
// 创建consul对象 -- (可以重新指定 consul 属性: IP/Port , 也可以使用默认)
consulClient, err := api.NewClient(consulConfig)
// 服务发现. 从consuL上, 获取健康的服务
services, _, err := consulClient.Health().Service("grpc And Consul", "grcp", true, nil)
// 简单的负载均衡.
addr := services[0].Service.Address + ":" + strconv.Itoa(services[0].Service.Port)
//////////////////////以下为 grpc 服务远程调用///////////////////////////
// 1. 链接服务
//grpcConn, _ := grpc.Dial("127.0.0.1:8800", grpc.WithInsecure())
// 使用 服务发现consul 上的 IP/port 来与服务建立链接
grpcConn, _ := grpc.Dial(addr, grpc.WithInsecure())
// 2. 初始化 grpc 客户端
grpcClient := pb.NewHelloClient(grpcConn)
var person pb.Person
person.Name = "Andy"
person.Age = 18
// 3. 调用远程函数
p, err := grpcClient.SayHello(context.TODO(), &person)
fmt.Println(p, err)
}
做了这么久的铺垫,接着让我们来进入主题,go-micro的学习。
首先我们先来安装一下go-micro开发环境。安装步骤如下:
#安装go-micro
go get -u -v github.com/micro/go-micro
#安装工具集
go get -u -v github.com/micro/micro
#安装protobuf插件
go get -u github.com/golang/protobuf/{proto,protoc-gen-go}
go get -u github.com/micro/protoc-gen-micro
或者通过docker镜像安装:
$ docker pull microhq/micro
首先我们先来创建一个go micro框架的项目,创建项目我们使用micro命令,micro类似于beego框架中的beego,可以创建微服务,web项目等,具体用法如下:
new Create a new Micro service by specifying a directory path relative to your $GOPATH
#创建 通过指定相对于$GOPATH的目录路径,创建一个新的微服务。
USAGE:
#用法
micro new [command options][arguments...]
#指定服务的命名空间
--namespace "go.micro" Namespace for the service e.g com.example
#服务类型,可以是微服务src,或者web项目web,或者是api等
--type "srv" Type of service e.g api, fnc, srv, web
#服务的正式定义全面
--fqdn FQDN of service e.g com.example.srv.service (defaults to namespace.type.alias)
#别名是在指定时作为组合名的一部分使用的短名称 别名
--alias Alias is the short name used as part of combined name if specified
micro new --type "web" micro/rpc/web
Creating service go.micro.web.web in /home/itcast/go/src/micro/rpc/web
.
#主函数
├── main.go
#插件文件
├── plugin.go
#被调用处理函数
├── handler
│ └── handler.go
#前端页面
├── html
│ └── index.html
#docker生成文件
├── Dockerfile
├── Makefile
└── README.md
#编译后将web端呼叫srv端的客户端连接内容修改为srv的内容
#需要进行调通
$micro new --type "srv" t1/t1
#"srv" 是表示当前创建的微服务类型
#micro是相对于go/src下的文件夹名称 可以根据项目进行设置
#srv是当前创建的微服务的文件名
Creating service go.micro.srv.srv in /home/itcast/go/src/t1/t1
.
#主函数存放位置
├── main.go
#插件
├── plugin.go
#服务提供函数的实现
├── handler
│ └── example.go
#订阅服务
├── subscriber
│ └── example.go
#proto协议
├── proto/example
│ └── example.proto
#docker生成文件
├── Dockerfile
#编译文件
├── Makefile
└── README.md
#插件提示,已安装,可忽略
download protobuf for micro:
brew install protobuf
go get -u github.com/golang/protobuf/{proto,protoc-gen-go}
go get -u github.com/micro/protoc-gen-micro
compile the proto file example.proto:
cd /home/itcast/go/src/micro/rpc/srv
protoc --proto_path=. --go_out=. --micro_out=. proto/example/example.proto
go-micro框架创建完成后即可对其文件中的代码进行相应修改,实现自己的项目需求。