服务发现consul示例
一.服务发现简介
在微服务开发和部署过程中,每个服务都运行在一个独立的进程中,服务于服务之间通过轻量级的通信机制进行沟通(如RESTful API),那么怎样管理这些服务呢,当用户发送一个请求,应该去请求哪个服务来满足用户的请求呢?这时候就用到了服务发现功能.服务发现相当于一个注册中心,它记录了分布式系统中全部的服务信息,以便其他服务能快速找到已注册的服务,其作用类似于中介.
流程描述:
1. 每个服务启动时都会将自己的端口,地址,服务名等信息在服务发现功能中注册,当然为了服务的稳定性一个功能可能开起了多个服务,如上图的登录功能;
2. 当客户端向服务发现发出请求时,服务发现会找到对应请求的注册信息返回给客户端,客户端就可以拿着注册信息访问想要的服务.
服务发现的工具有很多,如:zookeeper,etcd,eureka,consul等,各有优劣,大家可以根据自己的需求做选择,这里主要介绍consul.
二.consul安装及介绍
1.安装
官网直接下载: https://www.consul.io/downloads
百度网盘: https://pan.baidu.com/s/1NLEskDQOtipX5BB9v4HeoA 提取码: v09o
2.consul特性
服务发现(Service Discovery): Consul提供了通过DNS或者HTTP接口的方式来注册服务和发现服务.一些外部的服务通过Consul很容易的找到它所依赖的服务.
健康检查(Health Checking): Consul的Client可以提供任意数量的健康检查,既可以与给定的服务相关联("webserver是否返回200 OK")",也可以与本地节点相关联("内存利用率是否低于90%"). 操作员可以使用这些信息来监视集群的健康状况,服务发现组件可以使用这些信息将流量从不健康的主机路由出去.
Key/Value存储: 应用程序可以根据自己的需要使用Consul提供的Key/Value存储. Consul提供了简单易用的HTTP接口,结合其他工具可以实现动态配置、功能标记、领袖选举等等功能.
安全服务通信: Consul可以为服务生成和分发TLS证书,以建立相互的TLS连接.意图可用于定义允许哪些服务通信.服务分割可以很容易地进行管理,其目的是可以实时更改的,而不是使用复杂的网络拓扑和静态防火墙规则.
多数据中心: Consul支持开箱即用的多数据中心. 这意味着用户不需要担心需要建立额外的抽象层让业务扩展到多个区域.
3.consul常用命令
consul agent
-
- -bind=0.0.0.0 指定consul所在机器的IP地址
- -http-port=8500 consul使用web访问时的默认端口.默认值: 8500
- -client=127.0.0.1 指定哪些机器可以访问consul.指定为: 0.0.0.0 所有机器均可访问
- -config-dir=/etc/consul.d/ 主动注册服务时读取的配置路径,自定义
- -dat-dir=/tmp/consul/ 注册过的服务信息存储路径,自定义
- -dev 开发者模式,直接以默认配置启动consul
- -node=hostname 服务发现的名字
- -rejoin consul启动时加入到consul集群
- -server 以服务方式开起consul,允许其他consul连接到此服务,形成集群.
- -ui 可以通过使用web来查看服务发现详情
三.consul主动注册服务
1.consul启动
consul agent -dev -server -client=0.0.0.0 -config-dir=/etc/consul.d/ -data-dir=/tmp/consul/ -node=n1 -ui -bind=192.168.79.134
启动成功时会看到下边信息;
==> Starting Consul agent... Version: 'v1.5.2' Node ID: '6738f862-b9ef-3fc0-115a-a0c9778847b6' Node name: 'n1' Datacenter: 'dc1' (Segment: '<all>') Server: true (Bootstrap: false) Client Addr: [0.0.0.0] (HTTP: 8500, HTTPS: -1, gRPC: 8502, DNS: 8600) Cluster Addr: 192.168.79.134 (LAN: 8301, WAN: 8302) Encrypt: Gossip: false, TLS-Outgoing: false, TLS-Incoming: false, Auto-Encrypt-TLS: false ==> Log data will now stream in as it occurs: 2022/03/10 09:52:52 [DEBUG] tlsutil: Update with version 1 2022/03/10 09:52:52 [DEBUG] tlsutil: OutgoingRPCWrapper with version 1 2022/03/10 09:52:52 [INFO] raft: Initial configuration (index=1): [{Suffrage:Voter ID:6738f862-b9ef-3fc0-115a-a0c9778847b6 Address:192.168.79.134:8300}] 2022/03/10 09:52:52 [INFO] raft: Node at 192.168.79.134:8300 [Follower] entering Follower state (Leader: "") 2022/03/10 09:52:52 [INFO] serf: EventMemberJoin: n1.dc1 192.168.79.134 2022/03/10 09:52:52 [INFO] serf: EventMemberJoin: n1 192.168.79.134 2022/03/10 09:52:52 [DEBUG] tlsutil: OutgoingTLSConfigForCheck with version 1 2022/03/10 09:52:52 [INFO] consul: Handled member-join event for server "n1.dc1" in area "wan" 2022/03/10 09:52:52 [INFO] consul: Adding LAN server n1 (Addr: tcp/192.168.79.134:8300) (DC: dc1) 2022/03/10 09:52:52 [DEBUG] agent/proxy: managed Connect proxy manager started 2022/03/10 09:52:52 [WARN] agent/proxy: running as root, will not start managed proxies 2022/03/10 09:52:52 [INFO] agent: Started DNS server 0.0.0.0:8600 (udp) 2022/03/10 09:52:52 [INFO] agent: Started DNS server 0.0.0.0:8600 (tcp) 2022/03/10 09:52:52 [INFO] agent: Started HTTP server on [::]:8500 (tcp) 2022/03/10 09:52:52 [INFO] agent: started state syncer ==> Consul agent running!
我的虚拟机ip为192.168.79.134,通过访问http://192.168.79.134:8500可以看到所有注册信息:
2.主动注册服务
现在我们要主动注册一个服务,在1中的启动命令中指定的配置路径为/etc/consul.d/,所以我们在此目录下创建一个json文件并写入服务配置:
root@master ~ > cd /etc/consul.d/
root@master /etc/consul.d > vim web.josn
{ "service": { "name": "test", # 服务名称 "tags": ["IT"], # 标签(可添加多个) "port": 5000, # 端口 } }
3. 保存重启consul服务
看到一个叫test的服务已经被注册成功.
4.健康检查
consul不仅统计所有服务的信息,还可以定时检查服务是否健康,保证向客户端提供的服务是可以正常请求的服务.consul健康检查有五种方式:
-
-
- script: 通过外部脚本检查
- http: 基于http请求,定时向服务发送心跳包,如果没有正常返回则判断为服务不健康
- tcp: 定时和服务建了tcp连接,连接成功表示服务健康
- grpc: 基于grpc协议判定
- docker: 结合script使用,通过判断容器是否正常运行判定服务的健康性
-
我们使用http来判断2中注册的服务是否正常,打开2中创建的web.json文件添加check属性:
{ "service": { "name": "test", "tags": ["IT"], "port": 5000, "check": { "id": "mytest", "name": "consulTest", # 健康检查服务名称 "http": "http://192.168.79.134:5000", # 服务地址 "interval": "5s", # 请求周期 "timeout": "1s" # 超时时长 } } }
5.重启consul服务
root@master /etc/consul.d > consul reload # 重启consul服务
看到刚刚注册的服务Health下有个红色的×,因为我们只是注册了一个服务,并没有真正启动,所以在consul请求"http://192.168.79.134:5000"时并未收到回复,所以判定此服务为不健康的,感兴趣的可以自己启动一个服务看consul有什么变化.
四. protobuf+grpc+consul
现在我们使用go语言,结合protobuf和grpc进行一个服务注册:
protobuf实现grpc服务参考上一篇文章,这里就不再细讲: go使用grpc通信示例
上文中已经通过proto文件创建了grpc服务,并实现了server端和客户端,接下来我们要做的就是引入服务发现功能.
1.启动consul服务:
这里我们直接使用默认配置启动
命令: consul agent -dev -client 0.0.0.0
2.grpc服务端注册:
func main() { config := api.DefaultConfig() // 初始化consul配置 config.Address = "192.168.79.134:8500" // 因为我的consul服务在虚拟机中,grpc服务在windows中,所以必须制定consul地址 client, err := api.NewClient(config) // 初始化consul,将上边初始化的配置传入 if err != nil { fmt.Println(err) return } // 服务注册配置 reg := api.AgentServiceRegistration{ Name: "grpcTest", // 服务名称 Tags: []string{"grpc"}, // 标签 Port: 8080, Address: "192.168.1.51", // 配置健康检查服务 Check: &api.AgentServiceCheck{ CheckID: "grpcCheck", Name: "IT", TCP: "192.168.1.51:8080", // 使用TCP连接检查,由于在proto文件中未定义健康检查,所以此处不能使用grpc check Interval: "5s", Timeout: "1s", }, } err = client.Agent().ServiceRegister(®) // 携带配置注册服务 if err != nil { fmt.Println(err) return } ///////////////////////////////////////////////////////////////// // 初始化一个grpc对象 // 注册服务 // 设置监听 }
3. grpc客户端:
func main() { consulConfig := api.DefaultConfig() // 初始化配置 consulConfig.Address = "192.168.79.134:8500" // 指定consul地址 consulClient, err := api.NewClient(consulConfig) // 获取consul操作对象 if err != nil { fmt.Println("client, api.newclient err:", err) return } // 获取服务地址 // 参数: service: 访问的服务名称; tag: 访问的服务标签; passingOnly: 是否必须通过健康检查,一般用true; q: 其它想获取的服务信息,没用用nil // 返回值: s[]*ServiceEntry: services切片(一个功能可能开起了多个服务,返回服务列表客户端可通过负载均衡算法选择最优服务,此处不做详解) // *QueryMeta 如果请求参数中q有其他信息,可通过QueryMeta获取 // error 错误信息 services, _, err := consulClient.Health().Service("grpcTest", "grpc", true, nil) // 拼接出返回的服务地址:端口 addr := services[0].Service.Address + ":" + strconv.Itoa(services[0].Service.Port) /////////////////////////////////////////////////////////////////////////////////////////// // 连接grpc服务, 添加证书,使用consul返回的地址 clientConn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { fmt.Println(err) return } // 初始化grpc客户端 // 调用方法 }
4.测试:
-
- 启动grpc服务端
- 启动grpc客户端
- 网页查看consul服务
看到一个名为grpcTest的服务已经注册完成,并且Health Checks下为绿色,表示服务健康,'2'表示有两项检查,点进去可以看到包括Node Checks和Service Checks.客户端启动后返回结果和上文中返回的结果也是一样的,所以说在使用了服务注册后用户体验上并没有什么太大的变化,但在程序实现上却有很大的区别.
五.服务注销
直接上代码:
func main() { consulConfig := api.DefaultConfig() consulConfig.Address = "192.168.79.134:8500" consulClient, err := api.NewClient(consulConfig) if err != nil { log.Println(err) return } err = consulClient.Agent().ServiceDeregister("grpcTest") if err != nil { log.Println(err) return } }
注销相对来说比较简单,初始化完成后直接调用ServiceDeregister方法即可,注意和客户端调用的Service方法不同的是,此处使用的是服务ID,Service方法使用的是Name,如果在服务端注册服务时未指定ID,consul会默认生成与Name同名的ID,所以此处使用"grpcTest",执行完注销服务后,再去浏览器查看发现grpcTest服务已经被踢除.