consul是什么我没有发言权,网上有很多的理解和介绍的文章,如果英文好可以去consul官网看下。这里只介绍下打算用consul来做什么。
场景:假设我们有很多api服务,为了保证服务的可靠性或者分担服务压力每个服务部署了多份(集群),这样我们就会有大量的api服务。而这些服务并不是一直不变的,因为每个服务都有可能宕机,我们也会根据需要调整集群里服务的数量。
这种场景下我们怎么管理这一大堆的服务,并保证对外提供的服务都是能用的呢?用consul来管理!
大致流程是这样的:
1:启动consul;
2:每个服务部署启动后,向consul注册(告诉consul有这个服务),并提供健康检查(consul用来测试这服务是否可用);
3:服务正常关闭后,向consul注销(告诉consul这服务不干了);
4:consul服务记下所有注册了的服务,并定期检查服务是否还健在(健康检查),如果不在了就把这服务标记为异常状态不对外提供,下次检查如果发现这服务又复活了,就把这服务标记成健康状态并恢复对外提供;
5:外部需要服务统一找consul要,consul把健康提供所有健康的服务给外部自行选择。
【操作视频:https://www.bilibili.com/video/av82190189/ 】
到consul官网的下载页面找到对应系统的安装包下载。
我这是centos7的系统;
安装包下载到目录:/usr/download;
下载地址是:https://releases.hashicorp.com/consul/1.6.2/consul_1.6.2_linux_amd64.zip(注意:不同时间consul版本不一样,下载地址也会不一样,要去上面的网页找当前的地址)
解压(安装)到目录:/usr/consul
cd /usr/download
wget https://releases.hashicorp.com/consul/1.16.0/consul_1.16.0_linux_amd64.zip
unzip consul_1.16.0_linux_amd64.zip -d /usr/consul
将命令写入环境变量:
ln -sv /usr/consul/consul /usr/bin/
查看consul版本,确认是否已安装成功。(如果能看见版本号就说明成功了)
consul -v
【操作视频(视频有点老旧,文章后来有改动):https://www.bilibili.com/video/av82190379/ 】
参数名 | 说明 |
---|---|
-advertise | 如:-advertise=10.1.22.33。此参数绑定集群内部通讯用的本地外网地址。如果consul都部署在同一个局域网内,这个参数就不需要了。 |
-bootstrap | 如:-bootstrap。此标识会使服务推荐自己成为leader。一个数据中心不能有多台服务用这个标识,否则可能会出现多个leader。集群搭建起来后不推荐使用这个标识。此参数必须与 -server 一起使用。(说白了就是这个标识太自我,会让标识了的服务争着做领导!为了避免冲突,如非必要尽量不要用!) |
-bootstrap-expect | 如:-bootstrap-expect=3。此参数设置数据中心预期的服务数量。如果设置了这个参数,consul会等到指定服务全部加入后才开始启动集群工作。它会自动推荐出一个leader。 此参数不能喝-bootstrap一起使用,且必须与 -server 一起使用。 |
-bind | 如:-bind=192.168.1.100。此参数绑定集群内部通讯用的本地内网地址。默认值是0.0.0.0,如果电脑有多个内网IP需指定一个。 |
-client | 如:-client=0.0.0.0。此参数绑定客户端地址,。默认值是127.0.0.1,只允许本地访问。 |
-config-dir | 如:-config-dir=/usr/consul/consul.json。此参数设置配置文件路径。consul会按字母的顺序加载后缀为“ .json”或“ .hcl”的所有文件。 |
-data-dir | 如:-data-dir=/usr/consul/data。agent持久化数据的目录。 |
-datacenter | 如:-datacenter=dc-mylife。数据中心名。 |
-dev | 如:-dev。此标识启用开发模式。快速启动consul,但不做持久化。 |
-encrypt | 如:-encrypt=zinrVj3m2yrkQ0wB+WW0uljNQvgzs5aCktBiueiMxB8=。此参数指定用于加密Consul网络流量的密钥,该密钥必须是经过Base64编码的32字节。可以用命令[consul keygen]生成。 |
-log-file | 如:-log-file=/usr/consul/log/。此参数设置日志文件名或目录,如果是目录,则文件名默认为consul-{timestamp}.log。目录需要先创建好。 |
-join | 如:-join=192.168.1.20。此参数指定当前服务启动时要加入的另一个代理服务地址。如果加入失败当前服务会启动失败。 |
-retry-join | 如:-retry-join=192.168.1.20。此参数和-join类似,但是如果加入失败就重试。 |
-log-level | 如:-log-level=warn。此参数设置日志级别。可选的级别有:“trace”,“debug”,“info”,“warn”,“err”。默认值为"info"。 |
-node | 如:-node=node-mylife。此参数指定本服务在集群中的节点名,需是唯一的。 |
-server | 如:-server。此标识指定本agent是处于服务器模式还是客户端模式。每个Consul群集至少要有一台服务器。 |
-ui | 如:-ui。此标识设置是否启用web管理页面。 |
我这只有一台服务器,运行命令行: |
consul agent -server -bootstrap-expect=1 -client=0.0.0.0 -ui -data-dir=/usr/consul/data -datacenter=dc-mylife -node=node-mylife -log-file=/usr/consul/log/
(注:日志文件存放的路径要自己手动创建。)
命令行参数 | 配置文件参数名 |
---|---|
-advertise | advertise_addr |
-bootstrap | bootstrap |
-bootstrap-expect | bootstrap_expect |
-bind | bind_addr |
-client | client_addr |
-data-dir | data_dir |
-datacenter | datacenter |
-encrypt | encrypt |
-log-file | log_file |
-retry-join | retry_join |
-log-level | log_level |
-node | node_name |
-server | server |
-ui | ui |
命令行(假设把配置文件放在目录:/usr/consul/consul.json): |
consul agent -config-file=/usr/consul/consul.json
配置文件:
{
"server": true,
"bootstrap_expect": 1,
"client_addr": "0.0.0.0",
"ui": true,
"data_dir": "/usr/consul/data",
"datacenter": "dc-mylife",
"node_name": "node-mylife",
"log_file": "/usr/consul/log/"
}
Consul运行启动后,可以查看其web管理页面:http://172.81.235.6:8500/ui。(这里的IP是我的服务器IP)
【操作视频:https://www.bilibili.com/video/av82190648/ 】
创建业务服务并注册到consul。
[Route("api/[controller]")]
[ApiController]
public class HealthController : ControllerBase
{
///
/// 健康检测
///
///
[HttpGet]
[Route("Check")]
public IActionResult Check()
{
return Ok();
}
}
///
/// 把本服务注册到Consul
///
/// 参数配置
/// 程序生命周期
private void RegisterToConsul(IConfiguration config, IHostApplicationLifetime appLifetime)
{
//Consul地址
var consulClient = new ConsulClient(p => { p.Address = new Uri($"http://127.0.0.1:8500"); });
//本地IP
var localIP = this.GetLocalIP();
//本地服务端口
var localPort = Convert.ToInt32(config["port"]); //端口号从命令行参数获取(注:目前没找到直接获取本服务监听的端口的方法)
//心跳检测设置
var httpCheck = new AgentServiceCheck()
{
DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(15), //心跳检测失败多久后注销
Interval = TimeSpan.FromSeconds(10), //间隔多久心跳检测一次
HTTP = $"http://{localIP}:{localPort}/api/Health/Check", //心跳检查地址,本服务提供的地址
Timeout = TimeSpan.FromSeconds(5) //心跳检测超时时间
};
//服务名(这里通过命令行参数传入不同的服务名,模拟我们有不同的服务[其实只是同一个接口项目的不同运行实例])
var serviceName = config["service"];
//注册信息
var registration = new AgentServiceRegistration()
{
ID = $"{localIP}:{localPort}", //服务ID,唯一
Name = serviceName, //服务名(如果服务搭集群,它们的服务名应该是一样的,但是ID不一样)
Address = $"{localIP}", //服务地址
Port = localPort, //服务端口
Tags = new string[] { }, //服务标签,一般可以用来设置权重等本地服务特有信息
Checks = new[] { httpCheck }, //心跳检测设置
};
//向Consul注册服务
consulClient.Agent.ServiceRegister(registration).Wait();
//关闭程序后注销到Consul
appLifetime.ApplicationStopped.Register(() =>
{
consulClient.Agent.ServiceDeregister(registration.ID).Wait();
});
}
方法:获取本地IP
///
/// 获取本地IP
///
///
private string GetLocalIP()
{
var ip = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces()
.Select(p => p.GetIPProperties())
.SelectMany(p => p.UnicastAddresses)
.Where(p => p.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork && !System.Net.IPAddress.IsLoopback(p.Address))
.FirstOrDefault()?.Address.ToString();
return ip;
}
dotnet ConsulServiceRegister.dll --urls="http://0.0.0.0:20011" --port=20011 --service="service1"
dotnet ConsulServiceRegister.dll --urls="http://0.0.0.0:20012" --port=20012 --service="service1"
dotnet ConsulServiceRegister.dll --urls="http://0.0.0.0:20021" --port=20021 --service="service2"
【操作视频:https://www.bilibili.com/video/av82190906/ 】
从Consul找到已注册到上面的服务。
(注:这里Consul只是做了服务注册和发现,并没有做请求路由和分发!)
[Route("api/[controller]")]
[ApiController]
public class ServicesController : ControllerBase
{
///
/// 获取服务
///
///
///
[HttpGet]
[Route("FindService")]
public List<string> FindService(string name)
{
using (var consul = new ConsulClient(c =>
{
c.Address = new Uri("http://172.81.235.6:8500/"); //Consul地址
}))
{
//获取服务
var services = consul.Catalog.Service(name).Result.Response;
var list = services.Select(m => $"{m.Address}:{m.ServicePort}").ToList();
return list;
}
}
}
(以上2步的代码已放到github上:https://github.com/dabintang/SomeExperiments/tree/master/consul)