安装Consul
我用的是Win 10的系统,但是安装了Docker Desktop, 然后用powershell进行docker的相关操作。
pull一个consul镜像到本地
PS C:\Users\Zhanwei> docker pull consul
Using default tag: latest
latest: Pulling from library/consul
31603596830f: Pull complete
e424a2a1e4f7: Pull complete
2472846eab82: Pull complete
24473b564e4a: Pull complete
b3caf5938c68: Pull complete
b6f50757bb0c: Pull complete
Digest: sha256:1cb6f7247472638c470c1bcf059a1f74a4d54c6b49c57c80651feb08cc0a80cf
Status: Downloaded newer image for consul:latest
docker.io/library/consul:latest
PS C:\Users\Zhanwei>
运行一个Consul
PS C:\Users\Zhanwei> docker run -d -p 8500:8500 -v data/consul:/consul/data -e CONSUL_BIND_INTERFACE='eth0' --name=consul_server_1 consul:latest agent -server -bootstrap -ui -node=node-1 -client='0.0.0.0' -datacenter=hangzhou
9d5e1c54ee6e44efc100e5a6fdf4e4f22ad2a177e0f0d937e54bb9b245e657cd
参数说明:
server:以server身份启动。默认是client
bootstrap-expect:集群要求的最少server数量,当低于这个数量,集群即失效。
data-dir:data存放的目录,更多信息请参阅consul数据同步机制
node:节点id,集群中的每个node必须有一个唯一的名称。默认情况下,Consul使用机器的hostname
bind:监听的ip地址。默认绑定0.0.0.0,可以不指定。表示Consul监听的地址,而且它必须能够被集群中的其他节点访问。Consul默认会监听第一个private IP,但最好还是提供一个。生产设备上的服务器通常有好几个网卡,所以指定一个不会出错
client: 客户端的ip地址,0.0.0.0是指谁都可以访问(不加这个,下面的ui :8500无法访问)
ui: 可以访问UI界面
-config-dir指定配置文件夹,Consul会加载其中的所有文件
-datacenter 指定数据中心名称,默认是dc1
打开浏览器,如下所示,IP换成自己的IP

这样单机版的Consul就启动完成了,接下来我们新建个API服务。
新建WEB API服务项目
新建一个名为CustomMiddleware.api01的API项目。
安装Consul依赖包。
PM>Install-Package Consul -Version 0.7.2.6
自动注册服务到Consul,退出时取消注册。
如果是主动退出服务,会执行取消注册的方法,好处是比心跳响应更快,但是心跳也是必不可少的,因为在微服务中,各种网络原因都有可能导致服务异常中断。我这里是封装了一个方法来注册,代码如下所示:
public static void RegisterConsul(this IApplicationBuilder app,IApplicationLifetime lifetime, ServiceEntity serviceEntity)
{
var consulClient = new ConsulClient(x => x.Address = new Uri($"http://{serviceEntity.ConsulIP}:{serviceEntity.ConsulPort}"));//请求注册的 Consul 地址
var httpCheck = new AgentServiceCheck()
{
DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5),//服务启动多久后注册
Interval = TimeSpan.FromSeconds(10),//健康检查时间间隔,或者称为心跳间隔
HTTP = $"http://{serviceEntity.IP}:{serviceEntity.Port}/api/health",//健康检查地址
Timeout = TimeSpan.FromSeconds(5)
};
// Register service with consul
var registration = new AgentServiceRegistration()
{
Checks = new[] { httpCheck },
ID = Guid.NewGuid().ToString(),
Name = serviceEntity.ServiceName,
Address = serviceEntity.IP,
Port = serviceEntity.Port,
Tags = new[] { $"urlprefix-/{serviceEntity.ServiceName}" }//添加 urlprefix-/servicename 格式的 tag 标签,以便 Fabio 识别
};
consulClient.Agent.ServiceRegister(registration).Wait();
lifetime.ApplicationStopping.Register(() =>
{
consulClient.Agent.ServiceDeregister(registration.ID).Wait();//服务停止时取消注册
});
}
ID:正常环境中最好不要用guid,因为每次启动id都不一样,正常来讲没啥问题,但是快速启动时就会有个坑了。建议用,这样也是唯一的,并且不会重复。
ID=$"{serviceEntity.ServiceName}_{serviceEntity.IP}_{serviceEntity.Port}"
修改Program
我想在用dotnet运行项目时,能够传入url,ip和port,所以在这里加了命令行参数。
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(build =>
{
build.AddCommandLine(args);//启动时可读取命令行参数
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
修改Configure方法,在最后加上RegisterConsul()
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, Microsoft.AspNetCore.Hosting.IApplicationLifetime lifetime)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
//获取环境变量
var ip = Configuration.GetValue(typeof(String), "IP");
var port = Configuration.GetValue(typeof(String), "Port");
ServiceEntity serviceEntity = new ServiceEntity
{
IP = ip.ToString(),
Port = Convert.ToInt32(port),
ServiceName = Configuration["Service:Name"],
ConsulIP = Configuration["Consul:IP"],
ConsulPort = Convert.ToInt32(Configuration["Consul:Port"])
};
app.RegisterConsul(lifetime, serviceEntity);
}
新建一个控制器,添加一个接口,用来测试。
[Route("api/[controller]")]
[ApiController]
public class DefaultController : ControllerBase
{
// GET: api/Default
[HttpGet]
public ResultInfo Get()
{
ResultInfo result = new ResultInfo();
result.Code = 200;
result.CreateTime = DateTime.Now;
result.Message = $"{HttpContext.Request.Scheme}://{HttpContext.Request.Host}{HttpContext.Request.Path}";
result.Data = new {
name="zhanwei",
server="Api01"
};
return result;
}
}
加上Health健康检查
[Route("api/[controller]")]
[ApiController]
public class HealthController : ControllerBase
{
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "result", "ok" };
}
}
ServiceEntity实体类
public class ServiceEntity
{
public string IP { get; set; }
public int Port { get; set; }
public string ServiceName { get; set; }
public string ConsulIP { get; set; }
public int ConsulPort { get; set; }
}
修改配置文件,增加如下内容
"Service": {
"Name": "Api01"//注册到Consul中显示的服务名称
},
"Consul": {
"IP": "192.168.10.134",//改成实际Consul地址
"Port": "8500"//Consul的端口
}
测试
启动两个实例,只不过换一下端口号,我们看一下效果,用下面这个命令传入
dotnet CustomMiddleware.api01.dll --urls="http://*:5001/" --ip="192.168.10.134" --port="5001"
如下图所示,我们起来了两个实例。

打开Consul网页客户端,我们看到Api01有两个实例

验证下两个服务的接口是否正常的?


我们可以看到分别访问两个接口都是能正常返回的,这两个接口后面介绍网关负载均衡的时候还会用到,可以从返回的信息里区别是从哪个实例返回的。
再新建一个API服务
重复的过程就不再赘述了,我只是修改了下接口的路径,还有服务名称。
启动一个实例

打开网页端,可以看到Api02也已经注册进去了。

下期预告
现在就完成了服务的自动注册功能,其实光注册进去是没什么作用的,这只是为后续做准备而已,后面我会利用Ocelot组件做一个简单的网关,网关又是什么?
服务自动发现、路由转发、负载均衡、限流、熔断等功能的讲解。
Consul单机版挂掉了不就完蛋了吗,所以后面还会介绍集群的搭建。
总之后面很精彩,这才刚起步而已。




