Consul是什么

Consul是HashiCorp公司推出的开源工具,用于实现分布式系统的服务发现与配置。 Consul是分布式的、高可用的、可横向扩展的。它具备以下特性 : service discovery:consul通过DNS或者HTTP接口使服务注册和服务发现变的很容易,一些外部服务,例如saas提供的也可以一样注册。 health checking:健康检测使consul可以快速的
告警在集群中的操作。和服务发现的集成,可以防止服务转发到故障的服务上面。 key/value storage:一个用来存储动态配置的系统。提供简单的HTTP接口,可以在任何地方操作。 multi-datacenter:无需复杂的配置,即可支持任意数量的区域。

Consul Client、Server区别 - 图1

1.png

图比较简单,这边文字描述下。服务A-N把当前自己的网络位置注册
到服务发现模块(这里注册的意思就是告诉),服务发现就以K-V的方式记录下,K一般是服务名,V就是IP:PORT。服务发现模块定时的轮询查看这些服务能不能访问的了(这就是健康检查)。客户端在调用服务A-N的时候,就跑去服务发现模块问下它们的网络位置,然后再调用它们的服务。客户端完全不需要记录这些服务网络位置,客户端和服务端完全解耦!

Consul的安装

Consul用Golang实现,因此具有天然可移植性(支持Linux、windows和macOS)。安装包仅包含一个可执行文件。Consul安装非常简单,只需要下载对应系统的软件包并解压后就可使用。

  1. ## 这里以 Linux系统为例:
  2. $ wget https://releases.hashicorp.com/consul/1.2.0/consul_1.2.0_linux_amd64.zip
  3. $ unzip consul_1.2.0_linux_amd64.zip
  4. $ mv consul /usr/local/bin/ 12345
  5. ## 至于版本可以换成所需的版本,也可以直接去github下载直接解压

验证安装

安装 Consul后,通过执行 consul命令,你可以看到命令列表的输出

  1. $ consul
  2. [root@iZbp1isjfk2rw8fpnxx8wgZ ~]## consul
  3. Usage: consul [--version] [--help] <command> [<args>]
  4. Available commands are:
  5. acl Interact with Consul's ACLs
  6. agent Runs a Consul agent
  7. catalog Interact with the catalog
  8. connect Interact with Consul Connect
  9. debug Records a debugging archive for operators
  10. event Fire a new event
  11. exec Executes a command on Consul nodes
  12. force-leave Forces a member of the cluster to enter the "left" state
  13. info Provides debugging information for operators.
  14. intention Interact with Connect service intentions
  15. join Tell Consul agent to join cluster
  16. keygen Generates a new encryption key
  17. keyring Manages gossip layer encryption keys
  18. kv Interact with the key-value store
  19. leave Gracefully leaves the Consul cluster and shuts down
  20. lock Execute a command holding a lock
  21. maint Controls node or service maintenance mode
  22. members Lists the members of a Consul cluster
  23. monitor Stream logs from a Consul agent
  24. operator Provides cluster-level tools for Consul operators
  25. reload Triggers the agent to reload configuration files
  26. rtt Estimates network round trip time between nodes
  27. services Interact with services
  28. snapshot Saves, restores and inspects snapshots of Consul server state
  29. validate Validate config files/directories
  30. version Prints the Consul version
  31. watch Watch for changes in Consul

Consul的四大核心特性:

  • 服务发现:可以方便的实现服务注册,通过DNS或者HTTP应用程序可以很容易的找到他所依赖的服务.
  • Key/Value存储:使用Key/Value进行数据存储。
  • 多数据中心:Consul支持开箱即用的多数据中心。这意味着用户不需要担心建立额外的抽象层让业务扩展到多个区域
  • 健康检查:可以对指定服务进行健康检查例如,ResponseStatus是否为200,避免将流量转发到不健康的服务上。

Consul 的角色

client: 客户端, 无状态, 将 HTTP 和 DNS 接口请求转发给局域网内的服务端集群. server: 服务端, 保存配置信息, 高
可用集群, 在局域网内与本地客户端通讯, 通过广域网与其他数据中心通讯. 每个数据中心的 server 数量推荐为 3 个
或是 5 个.

运行 Consul代理

Consul是典型的 C/S架构,可以运行服务模式或客户模式。每一个数据中心必须有至少一个服务节点, 3到5个服
务节点最好。非常不建议只运行一个服务节点,因为在节点失效的情况下数据有极大的丢失风险。

运行Agent

完成Consul的安装后,必须运行agent.agent可以运行为server或client模式.每个数据中心至少必须拥有一台server.建议在一个集群中有3或者5个server.部署单一的server,在出现失败时会不可避免的造成数据丢失.其他的agent运行为client模式.一个client是一个非常轻量级的进程.用于注册服务,运行健康检查和转发对server的查询.agent必须在集群中的每个主机上运行.

服务注册与发现

在进行服务注册之前先确认集群是否建立,关于服务注册可以看上篇的介绍,两种注册方式:

  • 一种是注册HTTP API.(例如node有consul客户端模块,不需要写配置文件,但是server该启动还是要启动)
  • 另一种是通过配置文件定义,下面讲解的是基于后者配置文件定义的形式,也是Consul官方所建议的方式。(该方式其实是编码和配置完全分开,例如3000端口有个服务正常运行,在代码中完全看不到和consul有任何关系;但是因为consul启动client时候指定了对应的ip+port则会自动去找这个3000端口的服务)

方式二

启动 Consul Server

  1. #S1: $ consul agent -server -bootstrap-expect 2 -data-dir /tmp/consul -node=S2 - bind=192.168.110.123 -ui -config-dir /etc/consul.d -rejoin -join 192.168.110.123 - client 0.0.0.0
  2. #运行cosnul agent以server模式
  3. -server 定义agent运行在server模式
  4. -bootstrap-expect :在一个datacenter中期望提供的server节点数目,当该值提供的时候,consul一直 等到达到指定sever数目的时候才会引导整个集群,该标记不能和bootstrap共用
  5. -data-dir:提供一个目录用来存放agent的状态,所有的agent允许都需要该目录,该目录必须是稳定的,系统 重启后都继续存在 -node:节点在集群中的名称,在一个集群中必须是唯一的,默认是该节点的主机名 -bind:该地址用来在集群内部的通讯,集群内的所有节点到地址都必须是可达的,默认是0.0.0.0
  6. -ui 启动web界面
  7. -config-dir::配置文件目录,里面所有以.json结尾的文件都会被加载 -rejoin:使consul忽略先前的离开,在再次启动后仍旧尝试加入集群中。 -clientconsul服务侦听地址,这个地址提供HTTPDNSRPC等服务,默认是127.0.0.1所以不对外提供服 务,如果你要对外提供服务改成0.0.0.0
  1. #n2: $ consul agent -server -bootstrap-expect 2 -data-dir /tmp/consul -node=n1 - bind=192.168.110.7 -ui -rejoin -join 192.168.110.123
  2. -server 定义agent运行在server模式
  3. -bootstrap-expect :在一个datacenter中期望提供的server节点数目,当该值提供的时候,consul一直 等到达到指定sever数目的时候才会引导整个集群,该标记不能和bootstrap共用
  4. -bind:该地址用来在集群内部的通讯,集群内的所有节点到地址都必须是可达的,默认是0.0.0.0
  5. -node:节点在集群中的名称,在一个集群中必须是唯一的,默认是该节点的主机名 -ui 启动web界面
  6. -rejoin:使consul忽略先前的离开,在再次启动后仍旧尝试加入集群中。
  7. -config-dir::配置文件目录,里面所有以.json结尾的文件都会被加载 -clientconsul服务侦听地址,这个地址提供HTTPDNSRPC等服务,默认是127.0.0.1所以不对外提供服 务,如果你要对外提供服务改成0.0.0.0
  8. -join 192.168.110.121 启动时加入这个集群

启动 Consul Client

  1. #n3 $ consul agent -data-dir /tmp/consul -node=n3 -bind=192.168.110.124 -config-dir /etc/consul.d -rejoin -join 192.168.110.123
  2. 运行cosnul agentclient模式,-join 加入到已有的集群中去。

查看集群成员

新开一个终端窗口运行consul members, 你可以看到Consul集群的成员.

  1. $ consul members
  2. Node Address Status Type Build Protocol DC Segment
  3. n1 192.168.110.7:8301 alive server 1.1.0 2 dc1 <all>
  4. n2 192.168.110.121:8301 alive server 1.1.0 2 dc1 <all>
  5. n3 192.168.110.122:8301 alive client 1.1.0 2 dc1 <default>

停止Agent

你可以使用Ctrl-C 优雅的关闭Agent.中断Agent之后你可以看到他离开了集群并关闭.

在退出中,Consul提醒其他集群成员,这个节点离开了.如果你强行杀掉进程.集群的其他成员应该能检测到这个节点失效了.当一个成员离开,他的服务和检测也会从目录中移除.当一个成员失效了,他的健康状况被简单的标记为危险,但是不会从目录中移除.Consul会自动尝试对失效的节点进行重连.允许他从某些网络条件下恢复过来.离开的节点则不会再继续联系.

此外,如果一个agent作为一个服务器,一个优雅的离开是很重要的,可以避免引起潜在的可用性故障影响达成一致性协议. consul优雅的退出

  1. consul leave

注册服务

搭建好conusl集群后,用户或者程序就能到consul中去查询或者注册服务。可以通过提供服务定义文件或者调用HTTP API来注册一个服务.

首先,为Consul配置创建一个目录.Consul会载入配置文件夹里的所有配置文件.在Unix系统中通常类似/etc/consul.d (.d 后缀意思是这个路径包含了一组配置文件).

  1. mkdir /etc/consul.d

然后,我们将编写服务定义配置文件.假设我们有一个名叫web的服务运行在80端口.另外,我们将给他设置一个标签.这样我们可以使用他作为额外的查询方式:

  1. {
  2. "service": {#服务
  3. "name": "web",#名称 123
  4. "tags": ["master"],#标记
  5. "address": "127.0.0.1",#ip
  6. "port": 10000,#端口
  7. "checks": [{
  8. "http": "http://localhost:10000/health",
  9. "interval": "10s"#检查时间
  10. }]
  11. }
  12. }

测试程序

  1. package main import ( "fmt" "net/http" )
  2. func handler(w http.ResponseWriter, r *http.Request) {
  3. fmt.Println("hello Web3! This is n3或者n2")
  4. fmt.Fprintf(w, "Hello Web3! This is n3或者n2")
  5. }
  6. func healthHandler(w http.ResponseWriter, r *http.Request) { fmt.Println("health check! n3或者n2")
  7. }
  8. func main() {
  9. http.HandleFunc("/", handler) http.HandleFunc("/health", healthHandler) http.ListenAndServe(":10000", nil)
  10. }

查询服务

一旦agent启动并且服务同步了.我们可以通过DNS或者HTTP的API来查询服务.

DNS API

让我们首先使用DNS API来查询.在DNS API中,服务的DNS名字是 NAME.service.consul.虽然是可配置的,但默认的所有DNS名字会都在consul命名空间下.这个子域告诉Consul,我们在查询服务,NAME则是服务的名称.

对于我们上面注册的Web服务.它的域名是 web.service.consul :

  1. dig @127.0.0.1 -p 8600 web.service.consul

有也可用使用 DNS API 来接收包含 地址和端口的 SRV记录:

  1. dig @127.0.0.1 -p 8600 web.service.consul SRV

SRV记录告诉我们 web 这个服务运行于节点dhcp-10-201-102-198 的80端口. DNS额外返回了节点的A记录.

Consul Client、Server区别 - 图2

2.png

Consul架构

我们只看数据中心1,可以看出consul的集群是由N个SERVER,加上M个CLIENT组成的。而不管是SERVER还是CLIENT,都是consul的一个节点,所有的服务都可以注册到这些节点上,正是通过这些节点实现服务注册信息的共享。除了这两个,还有一些小细节,一一简单介绍。 CLIENT CLIENT表示consul的client模式,就是客户端模
式。是consul节点的一种模式,这种模式下,所有注册到当前节点的服务会被转发到SERVER【通过HTTP和DNS接口请求server】,本身是不持久化这些信息。 SERVER SERVER表示consul的server模式,表明这个consul是个server,这种模式下,功能和CLIENT都一样,唯一不同的是,它会把所有的信息持久化的本地,这样遇到故障,信
息是可以被保留的 SERVER-LEADER中间那个SERVER下面有LEADER的字眼,表明这个SERVER是它们的老大,它和其它SERVER不一样的一点是,它需要负责同步注册的信息给其它的SERVER,同时也要负责各个节点的健康监测。

Consul的client mode把请求转向server,那么client的作用是什么?

consul可以用来实现分布式系统的服务发现与配置。client把服务请求传递给server,server负责提供服务以及和其他数据中心交互。题主的问题是,既然server端提供了所有服务,那为何还需要多此一举地用client端来接收一次服务请求。我想,采用这种架构有以下几种理由:首先server端的网络连接资源有限。对于一个分布式系统,一般情况下访问量是很大的。如果用户能不通过client直接地访问数据中心,那么数据中心必然要为每个用户提供一个单独的连接资源(线程,端口号等等),那么server端的负担会非常大。所以很有必要用大量的client端来分散用户的连接请求,在client端先统一整合用户的服务请求,然后一次性地通过一个单一的链接发送大量的请求给server端,能够大量减少server端的网络负担。其次,在client端可以对用户的请求进行一些处理来提高服务的效率,比如将相同的请求合并成同一个查询,再比如将之前的查询通过cookie的形式缓存下来。但是这些功能都需要消耗不少的计算和存储资源。如果在server端提供这些功能,必然加重server端的负担,使得server端更加不稳定。而通过client端来进行这些服务就没有这些问题了,因为client端不提供实际服务,有很充足的计算资源来进行这些处理这些工作。最后还有一点,consul规定只要接入一个client就能将自己注册到一个服务网络当中。这种架构使得系统的可扩展性非常的强,网络的拓扑变化可以特别的灵活。这也是依赖于client—server结构的。如果系统中只有几个数据中心存在,那网络的扩张也无从谈起了。

注意

Consul只是服务注册和发现,并没有其他太多附加作用,真实使用还要搭配其他框架,例如:最简单场景,服务A想通过名称调用服务B,但是需要先获取服务A的ip+port虽然代码也可以编写,但是明显添加了负担,更不用说使用服务B的具体函数这样直接表象的调用了,此处和springcloud整体解决方案区别很大。

Node案例

Consul的node模块

其实次模块只是Consul-client,如果使用则不需要编写Consul的对应client配置文件,也可以不使用直接再配置文件中编写。

开发模式

执行consul agent -dev,启动开发模式,这个模式会快速启动一个单节点的Consul。注意,这个模式不能数据持久化,例如key/value的保存,因此,不能用于生产环境。

封装consul

  1. // consul.js
  2. const Consul = require('consul');
  3. class ConsulConfig {
  4. constructor () {
  5. const serviceName = 'consul-demo';
  6. this.consul = new Consul({
  7. host: '192.168.6.128',
  8. port: 8500,
  9. promisify: true,
  10. });
  11. this.consul.agent.service.register({
  12. name: serviceName,
  13. address: '192.168.20.193',
  14. port: 3000,
  15. check: {
  16. http: 'http://192.168.20.193:3000/health',
  17. interval: '10s',
  18. timeout: '5s',
  19. }
  20. }, function(err, result) {
  21. if (err) {
  22. console.error(err);
  23. throw err;
  24. }
  25. console.log(serviceName + ' 注册成功!');
  26. })
  27. }
  28. async getConfig(key) {
  29. const result = await this.consul.kv.get(key);
  30. if (!result) {
  31. return Promise.reject(key + '不存在');
  32. }
  33. return JSON.parse(result.Value);
  34. }
  35. async getUserConfig(key) {
  36. const result = await this.getConfig('develop/user');
  37. if (!key) {
  38. return result;
  39. }
  40. return result[key];
  41. }
  42. async setUserConfig(key, val) {
  43. const user = await this.getConfig('develop/user');
  44. user[key] = val;
  45. return this.consul.kv.set('develop/user', JSON.stringify(user))
  46. }
  47. }
  48. module.exports = ConsulConfig;

编写启动文件

  1. // app.js
  2. const http = require('http');
  3. const ConsulConfig = require('./consul');
  4. const consul = new ConsulConfig();
  5. http.createServer(async (req, res) => {
  6. const {url, method} = req;
  7. // 测试健康检查
  8. if (url === '/health') {
  9. res.end('OK!');
  10. }
  11. // 测试动态读取数据
  12. if (method === 'GET' && url === '/user/info') {
  13. const user = await consul.getUserConfig();
  14. res.end(`你好,我是 ${user.name} 今年 ${user.age}`);
  15. }
  16. // 测试数据更新
  17. if (method === 'POST' && url === '/user') {
  18. try {
  19. await consul.setUserConfig('age', 18) // 将 age 更改为 18
  20. res.end('OK!');
  21. } catch (err) {
  22. console.error(err);
  23. res.end('ERROR!');
  24. }
  25. }
  26. }).listen(3000, '192.168.20.193');