consul集群搭建

Consul是HashiCorp公司推出的使用go语言开发的开源工具,用于实现分布式系统的服务发现与配置,内置了服务注册与发现框架、分布一致性协议实现、健康检查、Key/Value存储、多数据中心方案,使用起来较为简单。使用docker命令创建注册中心比较麻烦,并且不好维护,这里使用docker-compose来实现。registrator保证了,如果服务已停止,则从注册中心中移除。docker-compose.yaml如下

version: "3.0"

services:
# consul server,对外暴露的ui接口为8500,只有在2台consul服务器的情况下集群才起作用
consulserver:
image: progrium/consul:latest
hostname: consulserver
ports:
- ""
- ""
- "8500:8500"
- ""
command: -server -ui-dir /ui -data-dir /tmp/consul --bootstrap-expect= # consul server1在consul server服务起来后,加入集群中
consulserver1:
image: progrium/consul:latest
hostname: consulserver1
depends_on:
- "consulserver"
ports:
- ""
- ""
- ""
- ""
command: -server -data-dir /tmp/consul -join consulserver # consul server2在consul server服务起来后,加入集群中
consulserver2:
image: progrium/consul:latest
hostname: consulserver2
depends_on:
- "consulserver"
ports:
- ""
- ""
- ""
- ""
command: -server -data-dir /tmp/consul -join consulserver
registrator:
image: gliderlabs/registrator:master
hostname: registrator
depends_on:
- "consulserver"
volumes:
- "/var/run/docker.sock:/tmp/docker.sock"
command: -internal consul://consulserver:8500

然后运行docker-compose up -d

ASP.NET

注册服务

创建一个ServiceA(asp.net core 2.2) 项目,需要安装Consul,Consul包中提供了一个IConsulClient类,我们可以通过它来调用Consul进行服务的注册,以及发现等。我们需要在服务启动的时候,将自身的地址等信息注册到Consul中,并在服务关闭的时候从Consul撤销。这种行为就非常适合使用 IHostedService 来实现。这里要注意的是,我们需要保证_serviceId对于同一个实例的唯一,避免重复性的注册。关闭时撤销服务:ConsulHostedService.cs

namespace ServiceA
{
using Consul;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks; public class ConsulHostedService : IHostedService
{
private readonly IConsulClient _consulClient;
private readonly ILogger _logger;
private readonly IServer _server; public ConsulHostedService(IConsulClient consulClient, ILogger<ConsulHostedService> logger, IServer server)
{
_consulClient = consulClient;
_logger = logger;
_server = server;
} private CancellationTokenSource _cts;
private string _serviceId; public async Task StartAsync(CancellationToken cancellationToken)
{
// Create a linked token so we can trigger cancellation outside of this token's cancellation
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); var features = _server.Features;
var address = features.Get<IServerAddressesFeature>().Addresses.First();
var uri = new Uri(address); _serviceId = "Service-v1-" + Dns.GetHostName() + "-" + uri.Authority; var registration = new AgentServiceRegistration()
{
ID = _serviceId,
Name = "Service",
Address = uri.Host,
Port = uri.Port,
Tags = new[] { "api" },
Check = new AgentServiceCheck()
{
// HTTP = $"{uri.Scheme}://{uri.Host}:{uri.Port}/api/Health/Status",
HTTP = $"{uri.Scheme}://{uri.Host}:{uri.Port}/healthz",
Timeout = TimeSpan.FromSeconds(),
Interval = TimeSpan.FromSeconds()
}
}; _logger.LogInformation("Registering in Consul"); // 首先移除服务,避免重复注册
await _consulClient.Agent.ServiceDeregister(registration.ID, _cts.Token);
await _consulClient.Agent.ServiceRegister(registration, _cts.Token);
} public async Task StopAsync(CancellationToken cancellationToken)
{
_cts.Cancel();
_logger.LogInformation("Deregistering from Consul");
try
{
await _consulClient.Agent.ServiceDeregister(_serviceId, cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex, $"Deregisteration failed");
}
}
}
}

StartupConfigureServices方法中来配置IConsulClient到ASP.NET Core的依赖注入系统中,healthz地址,我使用了ASP.NET Core 2.2中自带的健康检查,它需要在Startup中添加如下配置

namespace ServiceA
{
using System;
using Consul;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
} public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
//配置IConsulClient到ASP.NET Core的依赖注入系统中
string consulAddress = "http://192.168.100.5:8500";
services.AddSingleton<IConsulClient, ConsulClient>(p => new ConsulClient(consulConfig =>
{
consulConfig.Address = new Uri(consulAddress);
}));
services.AddSingleton<IHostedService, ConsulHostedService>(); services.AddHealthChecks();//自带的健康检查 services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHealthChecks("/healthz");
app.UseMvc();
}
}
}

当然也可以自己写一个HealthController:

using Microsoft.AspNetCore.Mvc;

namespace ServiceA.Controllers
{
[Route("api/[controller]")]
[Produces("application/json")]
[ApiController]
public class HealthController : Controller
{
[HttpGet("status")]
public IActionResult Status() => Ok();
}
}
using Microsoft.AspNetCore.Mvc;

namespace ServiceA.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public ActionResult<string> Get()
{
return "value1AAA";
}
}
}

可以在Program.cs指定端口:

namespace ServiceA
{
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
} public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args).UseUrls("http://192.168.100.2:6002")
.UseStartup<Startup>();
}
}

这里简要说明一下我的环境, 代码在win10物理机上,consul集群是win10虚拟机上ubuntu18的docker 环境,所以指定ip便于docker里面访问,还有就是win10的防火墙要关闭。

把新建ServiceB和ServiceA一样 只是修改一个端口然后用 dotnet run 运行如下:

把ServiceB关闭后

发现服务

现在来看看服务消费者如何从Consul来获取可用的服务列表。

我们创建一个ConsoleApp,做为服务的调用端,添加ConsulNuget包,然后,我们创建一个ConsulClient实例,直接调用consuleClient.Health.Service就可以获取到可用的服务列表了,然后使用HttpClient就可以发起对服务的调用。

但我们需要思考一个问题,我们什么时候从Consul获取服务呢?最为简单的便是在每次调用服务时,都先从Consul来获取一下服务列表,这样做的好处是我们得到的服务列表是最新的,能及时获取到新注册的服务以及过滤掉挂掉的服务。但是这样每次请求都增加了一次对Consul的调用,对性能有稍微的损耗,不过我们可以在每个调用端的机器上都部署一个Consul Agent,这样对性能的影响就微乎其微了。另外一种方式,可以在调用端做服务列表的本地缓存,并定时与Consul同步。其实现也非常简单,通过一个Timer来定时从Consul拉取最新的服务列表,创建一个ConsulServiceProvider.cs类,实现如下:

namespace ConsoleApp
{
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Consul;
public interface IServiceDiscoveryProvider
{
Task<List<string>> GetServicesAsync();
}
public class ConsulServiceProvider : IServiceDiscoveryProvider
{
private string consulAddres;
public ConsulServiceProvider(string url) {
consulAddres = url;
}
public async Task<List<string>> GetServicesAsync()
{
var consuleClient = new ConsulClient(consulConfig =>
{
consulConfig.Address = new Uri(consulAddres);
}); var queryResult = await consuleClient.Health.Service("Service", string.Empty, true); while (queryResult.Response.Length == )
{
Console.WriteLine("No services found, wait 1s....");
await Task.Delay();
queryResult = await consuleClient.Health.Service("Service", string.Empty, true);
} var result = new List<string>();
foreach (var serviceEntry in queryResult.Response)
{
result.Add(serviceEntry.Service.Address + ":" + serviceEntry.Service.Port);
}
return result;
}
} public class PollingConsulServiceProvider : IServiceDiscoveryProvider
{
private List<string> _services = new List<string>();
private bool _polling;
private string consulAddres;
public PollingConsulServiceProvider(string url)
{
consulAddres = url;
var _timer = new Timer(async _ =>
{
if (_polling)
{
return;
} _polling = true;
await Poll();
_polling = false; }, null, , );
} public async Task<List<string>> GetServicesAsync()
{
if (_services.Count == ) await Poll();
return _services;
} private async Task Poll()
{
_services = await new ConsulServiceProvider(consulAddres).GetServicesAsync();
}
}
}

负载均衡

如何将不同的用户的流量分发到不同的服务器上面呢,早期的方法是使用DNS做负载,通过给客户端解析不同的IP地址,让客户端的流量直接到达各个服务器。但是这种方法有一个很大的缺点就是延时性问题,在做出调度策略改变以后,由于DNS各级节点的缓存并不会及时的在客户端生效,而且DNS负载的调度策略比较简单,无法满足业务需求,因此就出现了负载均衡器。

常见的负载均衡算法有如下几种:

  • 随机算法:每次从服务列表中随机选取一个服务器。

  • 轮询及加权轮询:按顺序依次调用服务列表中的服务器,也可以指定一个加权值,来增加某个服务器的调用次数。

  • 最小连接:记录每个服务器的连接数,每次选取连接数最少的服务器。

  • 哈希算法:分为普通哈希与一致性哈希等。

  • IP地址散列:通过调用端Ip地址的散列,将来自同一调用端的分组统一转发到相同服务器的算法。

  • URL散列:通过管理调用端请求URL信息的散列,将发送至相同URL的请求转发至同一服务器的算法。

本文中简单模拟前两种来介绍一下。

随机均衡是最为简单粗暴的方式,我们只需根据服务器数量生成一个随机数即可

最简单的轮询实现 使用lock控制并发,每次请求,移动一下服务索引。

RandomLoadBalancer.cs

namespace ConsoleApp
{
using System;
using System.Threading.Tasks;
public interface ILoadBalancer
{
Task<string> GetServiceAsync();
}
public class RandomLoadBalancer : ILoadBalancer
{
private readonly IServiceDiscoveryProvider _sdProvider; public RandomLoadBalancer(IServiceDiscoveryProvider sdProvider)
{
_sdProvider = sdProvider;
} private Random _random = new Random(); public async Task<string> GetServiceAsync()
{
var services = await _sdProvider.GetServicesAsync();
return services[_random.Next(services.Count)];
}
}
public class RoundRobinLoadBalancer : ILoadBalancer
{
private readonly IServiceDiscoveryProvider _sdProvider; public RoundRobinLoadBalancer(IServiceDiscoveryProvider sdProvider)
{
_sdProvider = sdProvider;
} private readonly object _lock = new object();
private int _index = ; public async Task<string> GetServiceAsync()
{
var services = await _sdProvider.GetServicesAsync();
lock (_lock)
{
if (_index >= services.Count)
{
_index = ;
}
return services[_index++];
}
}
}
}

便可以直接使用HttpClient来完成服务的调用了

namespace ConsoleApp
{
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
TestConsul().ConfigureAwait(false);
Console.ReadKey();
}
static async Task TestConsul() {
string url = "http://192.168.100.5:8500";
ILoadBalancer balancer = new RoundRobinLoadBalancer(new PollingConsulServiceProvider(url));
var client = new HttpClient(); Console.WriteLine("Request by RoundRobinLoadBalancer....");
for (int i = ; i < ; i++)
{
var service = await balancer.GetServiceAsync(); Console.WriteLine(DateTime.Now.ToString() + "-RoundRobin:" +
await client.GetStringAsync("http://" + service + "/api/values") + " --> " + "Request from " + service);
} Console.WriteLine("Request by RandomLoadBalancer....");
balancer = new RandomLoadBalancer(new PollingConsulServiceProvider(url));
for (int i = ; i < ; i++)
{
var service = await balancer.GetServiceAsync(); Console.WriteLine(DateTime.Now.ToString() + "-Random:" +
await client.GetStringAsync("http://" + service + "/api/values") + " --> " + "Request from " + service);
}
}
}
}

代码下载

参考:

consul+docker实现服务注册

RainingNight/AspNetCoreSample

微服务(入门二):netcore通过consul注册服务

asp.net core 和consul的更多相关文章

  1. Asp.net core 向Consul 注册服务

    Consul服务发现的使用方法:1. 在每台电脑上都以Client Mode的方式运行一个Consul代理, 这个代理只负责与Consul Cluster高效地交换最新注册信息(不参与Leader的选 ...

  2. Service Discovery And Health Checks In ASP.NET Core With Consul

    在这篇文章中,我们将快速了解一下服务发现是什么,使用Consul在ASP.NET Core MVC框架中,并结合DnsClient.NET实现基于Dns的客户端服务发现 这篇文章的所有源代码都可以在G ...

  3. 实战中的asp.net core结合Consul集群&Docker实现服务治理

    0.目录 整体架构目录:ASP.NET Core分布式项目实战-目录 一.前言 在写这篇文章之前,我看了很多关于consul的服务治理,但发现基本上都是直接在powershell或者以命令工具的方式在 ...

  4. ASP.NET CORE 使用Consul实现服务治理与健康检查(2)——源码篇

    题外话 笔者有个习惯,就是在接触新的东西时,一定要先搞清楚新事物的基本概念和背景,对之有个相对全面的了解之后再开始进入实际的编码,这样做最主要的原因是尽量避免由于对新事物的认知误区导致更大的缺陷,Bu ...

  5. 【架构篇】ASP.NET Core 基于 Consul 动态配置热更新

    背景 通常,.Net 应用程序中的配置存储在配置文件中,例如 App.config.Web.config 或 appsettings.json.从 ASP.Net Core 开始,出现了一个新的可扩展 ...

  6. ASP.NET CORE 使用Consul实现服务治理与健康检查(1)——概念篇

    背景 笔者所在的公司正在进行微服务改造,这其中服务治理组件是必不可少的组件之一,在一番讨论之后,最终决定放弃 Zookeeper 而采用 Consul 作为服务治理框架基础组件.主要原因是 Consu ...

  7. Ubuntu & Docker & Consul & Fabio & ASP.NET Core 2.0 微服务跨平台实践

    相关博文: Ubuntu 简单安装 Docker Mac OS.Ubuntu 安装及使用 Consul Consul 服务注册与服务发现 Fabio 安装和简单使用 阅读目录: Docker 运行 C ...

  8. Docker & Consul & Fabio & ASP.NET Core 2.0 微服务跨平台实践

    相关博文: Ubuntu 简单安装 Docker Mac OS.Ubuntu 安装及使用 Consul Consul 服务注册与服务发现 Fabio 安装和简单使用 阅读目录: Docker 运行 C ...

  9. asp.net core集成CAP(分布式事务总线)

    一.前言 感谢杨晓东大佬为社区贡献的CAP开源项目,传送门在此:.NET Core 事件总线,分布式事务解决方案:CAP 以及 如何在你的项目中集成 CAP[手把手视频教程],之前也在工作中遇到分布式 ...

随机推荐

  1. 实验吧——忘记密码了(vim备份文件,临时文件(交换文件))

    题目地址:http://ctf5.shiyanbar.com/10/upload/step1.php 前些天突然发现个游戏,于是浪费了好多时间,终于还是忍住了,现在专心学习,从今天开始正式写些学习笔记 ...

  2. Vue+element 修改样式的scoped穿透方法

    我们在修改element的一些样式的时候,在加了scoped的时候会不起作用,下面是解决方案: 解决方法:起一个类名将页面包裹起来,后面加 /deep/ <style scoped> 1 ...

  3. Spring Security :CsrfFilter过滤器

    spring security框架提供的默认登录页面,会有一个name属性值为_csrf的隐藏域: 这是框架在用户访问登录页面之前就生成的,保存在内存中,当用户提交表单的时候会跟着一起提交: 然后会经 ...

  4. javascript之BOM对象(三其他对象)

    一.navigator对象 navigator使用来识别浏览器的,是所有支持javascript的浏览器所共有的.与BOM的其他对象不同,每个浏览器的navigator对象都有一套自己的属性. 常见的 ...

  5. 综合架构之Rsync备份服务,服务端和客户端配置

    服务端配置(即备份服务器) ps:客户端配置见下方 配置一个新服务的步骤: 第一步:先将该服务下载 yum install -y rsync 第二步:编写服务配置文件 配置文件:/etc/rsyncd ...

  6. Java多线程编程核心技术-第4章-Lock的使用-读书笔记

    第 4 章 Lock 的使用 本章主要内容 ReentrantLocal 类的使用. ReentrantReadWriteLock 类的使用. 4.1 使用 ReentrantLock 类 在 Jav ...

  7. Unity错误提示大全(遇到问题就更新)

    记录下使用Unity中遇到的所有错误提示 1.Unhandled Exception: System.Reflection.ReflectionTypeLoadException: The class ...

  8. java添加新表,接口合成流程必看

    生成代码后 创建模块包 先放入 实体类 然后按照 Controller 改成 然后是 service  后impl 然后是mapper  后xml 然后更新 代码

  9. git提交代码时出现was rejected by remote错误

    git常见问题 git是大家在公司基本都项目管理工具,有一次在改了一个bug提交远程提交就出现问题了. 解决方案 首先这个是远程提交的时候被项目权限拦截掉了,一般在我们都用配置ssh公钥的方式操作,那 ...

  10. K8s容器资源限制

    在K8s中定义Pod中运行容器有两个维度的限制: 1. 资源需求:即运行Pod的节点必须满足运行Pod的最基本需求才能运行Pod. 如: Pod运行至少需要2G内存,1核CPU    2. 资源限额: ...