服务发现consul
每个服务建立的时候,都会注册到服务发现。服务发现就以K-V的方式记录下,K一般是服务名,V就是IP:PORT。
有服务发现后,client、server工作流程:
- 每个server启动时,都将自己的IP、port 和 服务名 注册给 ”服务发现“
- 当 client 向服务发现发起服务请求时, “服务发现”会自动找一个可用的 服务,将其 IP/port/服务名返回给 client
- client 再借助服务发现,访问 server。
服务发现的种类:
- 服务发现: consul 提供服务, 服务端 主动向 consul 发起注册。
- 健康检查: 定时发送消息,类似于 “心跳包”,保证 客户端获得到的 一定是 健康的服务。
- 键值存储: consul 提供,但是我们使用 redis
- 多数据中心:可以轻松搭建集群。
consul 常用命令
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 页面 来查看服务发现的详情
服务发现讲义
开发的时候,客户端的一个接口可能需要调用N个服务,客户端必须知道所有服务的网络位置(ip+port),如下图所示
以往的做法是把服务的地址放在配置文件和数据库中,这样就有以下几个问题:
需要配置N个服务的网络位置,加大配置的复杂性
- 服务的网络位置变化,需要改变每个调用者的配置
- 集群的情况下,难以做负载(反向代理的方式除外)
总结起来一句话:服务多了,配置很麻烦,问题一大堆
所以现在就选择服务发现来解决这些问题。我们来看一下,服务发现如何解决这个问题,具体设计如下:
与之前解决方法不同的是,加了个服务发现模块。服务端把当前自己的网络位置注册到服务发现模块(这里注册的意思就是告诉),服务发现就以K-V的方式记录下,K一般是服务名,V就是IP:PORT。服务发现模块定时的轮询查看这些服务能不能访问的了(这就是健康检查)。客户端在调用服务A到N的时候,就跑去服务发现模块问下它们的网络位置,然后再调用它们的服务。这样的方式是不是就可以解决上面的问题了呢?客户端完全不需要记录这些服务的网络位置,客户端和服务端完全解耦!
常见的服务发现框架有:Etcd、Eureka、Consul、Zookeeper
这里我们选择go-micro默认的服务发现框架consul来做一个详细介绍。
了解consul并使用
Consul是HashiCorp公司推出的开源工具,用于实现分布式系统的服务发现与配置。包含多个组件,但是作为一个整体,为你的基础设施提供服务发现和服务配置的工具.他提供以下关键特性:
服务发现:consul通过DNS或者HTTP接口使服务注册和服务发现变的很容易,一些外部服务,例如saas提供的也可以一样注册。
健康检查:健康检测使consul可以快速的告警在集群中的操作。和服务发现的集成,可以防止服务转发到故障的服务上面。(心跳机制)
键/值存储:一个用来存储动态配置的系统。提供简单的HTTP接口,可以在任何地方操作。
多数据中心:无需复杂的配置,即可支持任意数量的区域。
官方建议:最好是三台或者三台以上的consul在运行,同名服务最好是三台或三台以上,默认可以搭建集群
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://www.consul.io/downloads.html
安装验证:
安装 Consul后,通过执行 consul命令,你可以看到命令列表的输出,没有报错就成功了
consul安装好之后,我们来使用一下吧。首先我们来看一下consul都有哪些命令。使用命令consul -h可以查看consul支持的所有参数,而且每个参数里面还支持其他参数,下面我们来具体看看。
agent:指令是consul的核心,它运行agent来维护成员的重要信息、运行检查、服务宣布、查询处理等等。
==> Usage: consul agent [options]
Starts the Consul agent and runs until an interrupt is received. The
agent represents a single node in a cluster.
Options:
-advertise=addr Sets the advertise address to use
-advertise-wan=addr Sets address to advertise on wan instead of
advertise addr
-bootstrap Sets server to bootstrap mode
-bind=0.0.0.0 Sets the bind address for cluster
communication
-http-port=8500 Sets the HTTP API port to listen on
-bootstrap-expect=0 Sets server to expect bootstrap mode.
-client=127.0.0.1 Sets the address to bind for client access.
This includes RPC, DNS, HTTP and HTTPS (if
configured)
-config-file=foo Path to a JSON file to read configuration
from. This can be specified multiple times.
-config-dir=foo Path to a directory to read configuration
files from. This will read every file ending
in ".json" as configuration in this
directory in alphabetical order. This can be
specified multiple times.
-data-dir=path Path to a data directory to store agent
state
-dev Starts the agent in development mode.
-recursor=1.2.3.4 Address of an upstream DNS server.
Can be specified multiple times.
-dc=east-aws Datacenter of the agent (deprecated: use
'datacenter' instead).
-datacenter=east-aws Datacenter of the agent.
-encrypt=key Provides the gossip encryption key
-join=1.2.3.4 Address of an agent to join at start time.
Can be specified multiple times.
-join-wan=1.2.3.4 Address of an agent to join -wan at start
time. Can be specified multiple times.
-retry-join=1.2.3.4 Address of an agent to join at start time
with retries enabled. Can be specified
multiple times.
-retry-interval=30s Time to wait between join attempts.
-retry-max=0 Maximum number of join attempts. Defaults to
0, which will retry indefinitely.
-retry-join-ec2-region EC2 Region to use for discovering servers to
join.
-retry-join-ec2-tag-key EC2 tag key to filter on for server
discovery
-retry-join-ec2-tag-value EC2 tag value to filter on for server
discovery
-retry-join-gce-project-name Google Compute Engine project to discover
servers in
-retry-join-gce-zone-pattern Google Compute Engine region or zone to
discover servers in (regex pattern)
-retry-join-gce-tag-value Google Compute Engine tag value to filter
for server discovery
-retry-join-gce-credentials-file Path to credentials JSON file to use with
Google Compute Engine
-retry-join-wan=1.2.3.4 Address of an agent to join -wan at start
time with retries enabled. Can be specified
multiple times.
-retry-interval-wan=30s Time to wait between join -wan attempts.
-retry-max-wan=0 Maximum number of join -wan attempts.
Defaults to 0, which will retry
indefinitely.
-log-level=info Log level of the agent.
-node=hostname Name of this node. Must be unique in the
cluster
-node-meta=key:value An arbitrary metadata key/value pair for
this node.
This can be specified multiple times.
-protocol=N Sets the protocol version. Defaults to
latest.
-rejoin Ignores a previous leave and attempts to
rejoin the cluster.
-server Switches agent to server mode.
-syslog Enables logging to syslog
-ui Enables the built-in static web UI server
-ui-dir=path Path to directory containing the Web UI
resources
-pid-file=path Path to file to store agent PID
info:指令提供了各种操作时可以用到的debug信息,对于client和server,info有返回不同的子系统信息,目前有以下几个KV信息:agent(提供agent信息),consul(提供consul库的信息),raft(提供raft库的信息),serf_lan(提供LAN gossip pool),serf_wan(提供WAN gossip pool)
leave:指令触发一个优雅的离开动作并关闭agent,节点离开后不会尝试重新加入集群中。运行在server状态的节点,节点会被优雅的删除,这是很严重的,在某些情况下一个不优雅的离开会影响到集群的可用性。
members:指令输出consul agent目前所知道的所有的成员以及它们的状态,节点的状态只有alive、left、failed三种状态。
-detailed:输出每个节点更详细的信息。-rpc-addr:一个rpc地址,agent可以链接上来发送命令,如果没有指定,默认是127.0.0.1:8400。-status:过滤出符合正则规则的节点
reload:指令可以重新加载agent的配置文件。SIGHUP指令在重新加载配置文件时使用,任何重新加载的错误都会写在agent的log文件中,并不会打印到屏幕。
consul简单使用
首先我们要运行consul,运行有两种模式,分别是==server==和==client==,通过下面的命令开启:
consul agent -server
consul agent
每个数据中心至少必须拥有一个server。一个client是一个非常轻量级的进程.用于注册服务,运行健康检查和转发对server的查询.agent必须在集群中的每个主机上运行.
server模式启动
一个consul:
$ consul agent(代理) -server(以server模式启动) -bootstrap-expect(分布式集群1个) 1
-data-dir(指定目录位置) /tmp/consul -node=n1(当前节点名称叫n1)
-bind=192.168.6.108(指定 consul所在机器的 IP地址) -ui(可以使用 web 页面 来查看服务发现的详情)
-rejoin (consul 启动的时候,加入到的 consul集群)
-config-dir=/etc/consul.d/ (所有主动注册服务的描述信息)
-client 0.0.0.0(表明哪些机器可以访问consul 。 默认本机。0.0.0.0 所有机器均可访问。)
需要先在/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.0
- data-dir:提供一个目录用来存放agent的状态,所有的agent允许都需要该目录,该目录必须是稳定的,系统重启后都继续存在
client模式启动
运行cosnul agent以client模式,-join 加入到已有的集群中去。$ consul agent -data-dir /tmp/consul -node=n2 -bind=192.168.137.82 -config-dir /etc/consul.d -rejoin -join 192.168.137.81
查看consul成员
停止agent
你可以使用Ctrl-C 关闭Agent. 中断Agent之后你可以看到他离开了集群并关闭.
在退出中,Consul提醒其他集群成员,这个节点离开了.如果你强行杀掉进程.集群的其他成员应该能检测到这个节点失效了.当一个成员离开,他的服务和检测也会从目录中移除.当一个成员失效了,他的健康状况被简单的标记为危险,但是不会从目录中移除.Consul会自动尝试对失效的节点进行重连.允许他从某些网络条件下恢复过来.离开的节点则不会再继续联系.
此外,如果一个agent作为一个服务器,一个优雅的离开是很重要的,可以避免引起潜在的可用性故障影响达成一致性协议.consul优雅的退出:$ consul leave
注册服务
搭建好conusl集群后,用户或者程序就能到consul中去查询或者注册服务。可以通过提供服务定义文件或者调用HTTP API来注册一个服务
步骤:
- 进入配置文件路径 cd /etc/consul.d/
- 创建 json 文件。 sudo vim web.json
- 按 json 的语法,填写 服务信息。
重新启动 consul
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
查询 服务
健康检查
健康检查是服务发现的关键组件.预防使用到不健康的服务.和服务注册类似,一个检查可以通过检查定义或HTTP API请求来注册.我们将使用和检查定义来注册检查.和服务类似,因为这是建立检查最常用的方式
- sudo vim /etc/consul.d/web.json 打开配置文件
- 写入 服务的配置 信息。
- 执行命令,consul reload。 或者,关闭consul 再重启。
- 使用 浏览器 键入 192.168.6.108:8500 查看 “bj38” 这个服务 的 健康状况
- 不健康!没有服务bj38 给 consul 实时回复!
- 除了 http 实现健康检查外,还可以使用 “脚本”、“tcp”、“ttl” 方式进行健康检查。
在/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",//主机ip
"interval": "10s",//心跳包发送时常,就是多久发一次心跳包
"timeout": "1s"//超过这个时间就认为是不健康的
}
}
}
这时候我们没有开启这个服务,所以这个时候健康检查会出错。打开web界面,如下
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来维持健康状态的有效性。
我们也可以通过页面来查看,在浏览器输入网址:http://192.68.130.137:8500
下面这里这个服务没启动,所以不健康。
consul和grpc结合使用
安装 consul 源码包:$ go get -u -v github.com/hashicorp/consul
使用整体流程
- 创建 proto文件, 指定 rpc 服务
- 启动 consul 服务发现(consul agent -dev)
- 启动server
- 获取consul 对象。
- 使用 consul对象,将 server 信息,注册给 consul
- 启动服务
- 启动client
// 参数: service: 服务名。 — 注册服务时,指定该string tag:外名/别名。 如果有多个, 任选一个 passingOnly:是否通过健康检查。 true q:查询参数。 通常传 nil // 返回值: ServiceEntry: 存储服务的切片。 QueryMeta:额外查询返回值。 nil error: 错误信息
proto文件
```go
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()//这个api是github上的一个包
// 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)
}
服务注销
package main
import "github.com/hashicorp/consul/api"
func main() {
// 1. 初始化 consul 配置
consuConfig := api.DefaultConfig()
// 2. 创建 consul 对象
consulClient, _ := api.NewClient(consuConfig)
// 3. 注销服务
consulClient.Agent().ServiceDeregister("bj38")
}
学完了consul命令行的使用,接着我们来看一下,在代码中如何通过,这里我们用grpc和consul结合使用。
我们操作consul使用的是github.com/hashicorp/consul/包,我们先来下载一下,命令如下:$ go get -u -v github.com/hashicorp/consul
然后我们先注册一个服务到consul上:
把grpc服务注册到consul上
代码如下:
func main(){
//初始化consul配置
consulConfig := api.DefaultConfig()
//获取consul操作对象
registry,_ := api.NewClient(consulConfig)
//注册服务,服务的常规配置
registerService := api.AgentServiceRegistration{
ID:"1",
Tags:[]string{"testHello"},
Name:"HelloService",
Port:8080,
Address:"192.168.137.130",
Check:&api.AgentServiceCheck{
TCP:"192.168.137.130:8080",
Timeout:"5s",
Interval:"5s",
},
}
//注册服务到consul上
registry.Agent().ServiceRegister(®isterService)
//获取grpc服务端对象
grpcServer := grpc.NewServer()
//注册grpc服务
pb.RegisterHelloServiceServer(grpcServer,new(HelloService))
//设置服务端监听
lis,err := net.Listen("tcp",":1234")
if err != nil {
panic(err)
}
//在指定端口上提供grpc服务
grpcServer.Serve(lis)
}
服务发现
func main(){
//初始化consul配置, 客户端服务器需要一致
consulConfig := api.DefaultConfig()
//获取consul操作对象
registerClient,_ := api.NewClient(consulConfig)
//获取地址
serviceEntry,_,_ :=registerClient.Health().Service("HelloService","testHello",false,&api.QueryOptions{})
//和grpc服务建立连接
conn,err := grpc.Dial(serviceEntry[0].Service.Address+":"+strconv.Itoa(serviceEntry[0].Service.Port),grpc.WithInsecure())
if err != nil {
panic(err)
}
defer conn.Close()
client := pb.NewHelloServiceClient(conn)
reply, err := client.Hello(context.Background(),&pb.Person{Name:"lisi",Age:100})
if err != nil {
panic(err)
}
fmt.Println("reply,",reply)
}
注销服务
func main(){
//初始化consul配置,客户端服务器需要一致
consulConfig := api.DefaultConfig()
//获取consul操作对象
registerClient,_ := api.NewClient(consulConfig)
//注销服务
client.Agent().ServiceDeregister("1")
}
好处
没有服务发现,客户端需要访问固定ip的服务,当服务ip地址发生变化则访问不到,需要更改ip。而服务发现只需要查询相应服务名即可,而且不需要记下来各个服务的ip地址。