consul服务发现

Consul是由HashiCorp开发的一个支持多数据中心的分布式服务发现和键值对存储服务的开源软件,被大量应用于基于微服务的软件架构当中。

服务发现流程图

image-20210607214102612

  • 服务发现,也可以看做一个“服务”,是给“服务”提供服务的

服务发现种类

  • consul:常用于go-micro中
  • mdns:go-micro中默认的服务发现
  • etcd:k8s内嵌的服务发现
  • zookeeper:java中常用

Consul关键特性

  • 服务发现:consul提供服务,服务端主动向consul发起注册。
  • 健康检查:定时发送消息,类似于“心跳包”,保证客户端获取到的一定是健康的服务。
  • 键值存储:consul提供,但常用于redis。
  • 多数据中心:可以轻松加入集群。

Consul 参数

  • 安装好 Consul 后,在启动程序之前,需要掌握一些配置参数,通过掌握这些参数,可以一次性的成功运行 Consul 服务器集群,常用的参数如下:
参数名称用途
-server此标志用于控制代理是运行于服务器/客户端模式,每个 Consul 集群至少有一个服务器,正常情况下不超过5个,使用此标记的服务器参与 Raft一致性算法、选举等事务性工作
-http-port=8500consul自带的一个web访问端口,默认为8500
-client表示 Consul 绑定客户端接口的IP地址,默认值为:127.0.0.1,当你有多块网卡的时候,最好指定IP地址,不要使用默认值
-bootstrap-expect预期的服务器集群的数量,整数,如 -bootstrap-expect=3,表示集群服务器数量为3台,设置该参数后,Consul将等待指定数量的服务器全部加入集群可用后,才开始引导集群正式开始工作,此参数必须与 -server 一起使用
-data-dir存储数据的目录,该目录在 Consul 程序重启后数据不会丢失,指定此目录时,应确保运行 Consul 程序的用户对该目录具有读写权限
-config-dir=XX所有服务主动注册的配置文件
- node当前服务器在集群中的名称,该值在整个 Consul 集群中必须唯一,默认值为当前主机名称
- bindConsul 在当前服务器侦听的地址,如果您有多块网卡,请务必指定一个IP地址(IPv4/IPv6),默认值为:0.0.0.0,也可用使用[::]

启动consul

1
consul agent -server -bootstrap-expect 1 -data-dir /tmp/consul -node=consul1 -bind=172.16.249.181 -ui -rejoin -config-dir /etc/consul.d/ -client 0.0.0.0
  • 如果第一次启动 请先创建 /etc/consul.d/ 文件夹
  • consul leave — 优雅退出consul
  • consul members — 查看consul成员
  • consul info — 查看consul信息

注册服务到consul

  • 增加配置在 /etc/consul.d/demo.json
1
2
3
4
5
6
7
8
{
        "service":{
                "name":"luenci",
                "tags":["study"],
                "port":8880

        }
}
  • curl 命令查看注册的服务
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
root@Luenci:~# curl -s 127.0.0.1:8500/v1/catalog/service/luenci | python -m json.tool
[
    {
        "Address": "172.16.249.181",
        "CreateIndex": 44,
        "Datacenter": "dc1",
        "ID": "c3908dfa-4fdf-4a57-686f-a3fc620fc5b7",
        "ModifyIndex": 44,
        "Node": "consul1",
        "NodeMeta": {
            "consul-network-segment": ""
        },
        "ServiceAddress": "",
        "ServiceConnect": {},
        "ServiceEnableTagOverride": false,
        "ServiceID": "luenci",
        "ServiceKind": "",
        "ServiceMeta": {},
        "ServiceName": "luenci",
        "ServicePort": 8880,
        "ServiceProxy": {},
        "ServiceProxyDestination": "",
        "ServiceTags": [
            "study"
        ],
        "ServiceWeights": {
            "Passing": 1,
            "Warning": 1
        },
        "TaggedAddresses": {
            "lan": "172.16.249.181",
            "wan": "172.16.249.181"
        }
    }
]

健康检查

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
root@Luenci:~# cat /etc/consul.d/sweetie.json | python -m json.tool
{
    "service": {
        "check": {
            "http": "<http://172.16.249.181:8001>",
            "id": "api",
            "interval": "10s",
            "name": "check sweetie api",
            "timeout": "2s"
        },
        "name": "sweetie",
        "port": 8001,
        "tags": [
            "fast-api"
        ]
    }
}
  • consul 健康检查必须是Script, HTTP , TCP , TTL 中的一种

使用go注册服务,python做服务发现

服务注册,就是将提供某个服务的模块信息(通常是这个服务的ip和端口)注册到1个公共的组件上去。

服务发现,就是新注册的这个服务模块能够及时的被其他调用者发现。不管是服务新增和服务删减都能实现自动发现。

protoc文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
syntax = "proto3";
// 指定所在包名
package study.consul;

option go_package = "./proto";

message Human {
  string name = 1;
  int32 age = 2;
}

service SayName{
  rpc Hello (Human) returns (Human);
}

server-consul(go实现)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package main

import (
   "context"
   "fmt"
   "github.com/hashicorp/consul/api"
   "google.golang.org/grpc"
   "log"
   "net"
   "study.consul/proto"
)

type Person struct {
   proto.UnimplementedSayNameServer
}

func (p *Person) Hello(ctx context.Context, args *proto.Human) (*proto.Human, error) {
   // 通过编译出来的rpc.pb.go解析String类型数据
   args.Name += " is coding!"
   args.Age += 3
   return args, nil
}

func main() {
   /* ------------------- 注册服务到consul ---------------------*/
   // 初始化consul配置
   consulConfig := api.DefaultConfig()

   // 创建consul对象
   consulClient, err := api.NewClient(consulConfig)
   if err != nil {
      fmt.Println("api NewClient err", err)
      return
   }

   // 注册服务,服务的常规信息
   regInfo := api.AgentServiceRegistration{
      ID:      "go grpc",
      Tags:    []string{"grpc", "consul"},
      Name:    "go grpc server",
      Address: "127.0.0.1",
      Port:    5001,
      Check: &api.AgentServiceCheck{
         CheckID:  "consul check grpc test",
         TCP:      "127.0.0.1:5001",
         Timeout:  "2s",
         Interval: "3s",
      },
   }

   // 注册 grpc 服务到 consul 上
   consulClient.Agent().ServiceRegister(&regInfo)

   fmt.Println("注册成功...")

   /* ------------------- grpc 远程调用 ---------------------*/
   // 初始化框架
   grpcServer := grpc.NewServer()

   // 注册服务
   proto.RegisterSayNameServer(grpcServer, new(Person))

   // 定义端口监听服务
   lis, err := net.Listen("tcp", "127.0.0.1:5001")
   if err != nil {
      log.Fatal(err)
   }
   defer lis.Close()

   fmt.Println("服务启动...")
   // 开启监听
   grpcServer.Serve(lis)

}

clien-consul(python实现)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# -*- coding: UTF-8 -*-

# pip install python-consul
# python 编译 protoc
# python -m grpc_tools.protoc --python_out=. --grpc_python_out=. -I. rpc.proto

import consul
import grpc

import test_pb2
import test_pb2_grpc

c = consul.Consul("XX.XX.XX.XX", "port")

# print(c.catalog.service("go grpc server"))

# 从 consul 中拿到健康的服务
service = c.health.service("go grpc server")

# print(type(service))
addr = service[1][0]["Service"]["Address"]
port = service[1][0]["Service"]["Port"]

# 获取健康的服务 ip + port
health_service = str(addr) + ":" + str(port)
print(health_service)

# 连接 rpc 服务器

channel = grpc.insecure_channel(health_service)

# 调用rpc服务,通过编译出来的rpc_pb2_grpc的HelloService接口定义HelloServiceStub接口,接收来自channel的数据
stub = test_pb2_grpc.SayNameStub(channel)
print(stub)

# 通过接口的rpc获取String类型数据,并获取值
response = stub.Hello(test_pb2.Human(name='luenci', age=19))

print("Greeter client received: " + response.name)
print("Greeter client received: ", response.age)

扩展阅读:python与golang通过grpc进行通信