C# HttpClient 如何使用 Consul 发现服务

  试用了Overt.Core.Grpc, 把 GRPC 的使用改造得像 WCF, 性能测试也非常不错, 非常推荐各位使用.
  但已有项目大多是 http 请求, 改造成 GRPC 的话, 工作量比较大, 于是又找到了 Steeltoe.Discovery, 在 Startup 给 HttpClient 添加 DelegatingHandler, 动态改变请求url中的 host 和 port, 将http请求指向consul 发现的服务实例, 这样就实现了服务的动态发现.
  经过性能测试, Steeltoe.Discovery 只有 Overt.Core.Grpc 的20%, 非常难以接受, 于是自己实现了一套基于 consul 的服务发现工具. 嗯, 名字好难取啊, 暂定为 ConsulDiscovery.HttpClient 吧
  功能很简单:

  1. webapi 从json中读取配置信息 ConsulDiscoveryOptions;
  2. 如果自己是一个服务, 则将自己注册到consul中并设置健康检查Url;
  3. ConsulDiscovery.HttpClient 内有一个consul client 定时刷新所有服务的url访问地址.

  比较核心的两个类

using Consul;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace ConsulDiscovery.HttpClient
{
  public class DiscoveryClient : IDisposable
  {
    private readonly ConsulDiscoveryOptions consulDiscoveryOptions;
    private readonly Timer timer;
    private readonly ConsulClient consulClient;
    private readonly string serviceIdInConsul;

    public Dictionary<string, List<string>> AllServices { get; private set; } = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);

    public DiscoveryClient(IOptions<ConsulDiscoveryOptions> options)
    {
      consulDiscoveryOptions = options.Value;
      consulClient = new ConsulClient(x => x.Address = new Uri($"http://{consulDiscoveryOptions.ConsulServerSetting.IP}:{consulDiscoveryOptions.ConsulServerSetting.Port}"));
      timer = new Timer(Refresh);

      if (consulDiscoveryOptions.ServiceRegisterSetting != null)
      {
        serviceIdInConsul = Guid.NewGuid().ToString();
      }
    }

    public void Start()
    {
      var checkErrorMsg = CheckParams();
      if (checkErrorMsg != null)
      {
        throw new ArgumentException(checkErrorMsg);
      }
      RegisterToConsul();
      timer.Change(0, consulDiscoveryOptions.ConsulServerSetting.RefreshIntervalInMilliseconds);
    }

    public void Stop()
    {
      Dispose();
    }

    private string CheckParams()
    {
      if (string.IsNullOrWhiteSpace(consulDiscoveryOptions.ConsulServerSetting.IP))
      {
        return "Consul服务器地址 ConsulDiscoveryOptions.ConsulServerSetting.IP 不能为空";
      }

      if (consulDiscoveryOptions.ServiceRegisterSetting != null)
      {
        var registerSetting = consulDiscoveryOptions.ServiceRegisterSetting;
        if (string.IsNullOrWhiteSpace(registerSetting.ServiceName))
        {
          return "服务名称 ConsulDiscoveryOptions.ServiceRegisterSetting.ServiceName 不能为空";
        }
        if (string.IsNullOrWhiteSpace(registerSetting.ServiceIP))
        {
          return "服务地址 ConsulDiscoveryOptions.ServiceRegisterSetting.ServiceIP 不能为空";
        }
      }
      return null;
    }

    private void RegisterToConsul()
    {
      if (string.IsNullOrEmpty(serviceIdInConsul))
      {
        return;
      }

      var registerSetting = consulDiscoveryOptions.ServiceRegisterSetting;
      var httpCheck = new AgentServiceCheck()
      {
        HTTP = $"{registerSetting.ServiceScheme}{Uri.SchemeDelimiter}{registerSetting.ServiceIP}:{registerSetting.ServicePort}/{registerSetting.HealthCheckRelativeUrl.TrimStart('/')}",
        Interval = TimeSpan.FromMilliseconds(registerSetting.HealthCheckIntervalInMilliseconds),
        Timeout = TimeSpan.FromMilliseconds(registerSetting.HealthCheckTimeOutInMilliseconds),
        DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(10),
      };
      var registration = new AgentServiceRegistration()
      {
        ID = serviceIdInConsul,
        Name = registerSetting.ServiceName,
        Address = registerSetting.ServiceIP,
        Port = registerSetting.ServicePort,
        Check = httpCheck,
        Meta = new Dictionary<string, string>() { ["scheme"] = registerSetting.ServiceScheme },
      };
      consulClient.Agent.ServiceRegister(registration).Wait();
    }

    private void DeregisterFromConsul()
    {
      if (string.IsNullOrEmpty(serviceIdInConsul))
      {
        return;
      }
      try
      {
        consulClient.Agent.ServiceDeregister(serviceIdInConsul).Wait();
      }
      catch
      { }
    }

    private void Refresh(object state)
    {
      Dictionary<string, AgentService>.ValueCollection serversInConsul;
      try
      {
        serversInConsul = consulClient.Agent.Services().Result.Response.Values;
      }
      catch // (Exception ex)
      {
        // 如果连接consul出错, 则不更新服务列表. 继续使用以前获取到的服务列表
        // 但是如果很长时间都不能连接consul, 服务列表里的一些实例已经不可用了, 还一直提供这样旧的列表也不合理, 所以要不要在这里实现 健康检查? 这样的话, 就得把检查地址变成不能设置的
        return;
      }

      // 1. 更新服务列表
      // 2. 如果这个程序提供了服务, 还要检测 服务Id 是否在服务列表里
      var tempServices = new Dictionary<string, HashSet<string>>();
      bool needReregisterToConsul = true;
      foreach (var service in serversInConsul)
      {
        var serviceName = service.Service;
        if (!service.Meta.TryGetValue("scheme", out var serviceScheme))
        {
          serviceScheme = Uri.UriSchemeHttp;
        }
        var serviceHost = $"{serviceScheme}{Uri.SchemeDelimiter}{service.Address}:{service.Port}";
        if (!tempServices.TryGetValue(serviceName, out var serviceHosts))
        {
          serviceHosts = new HashSet<string>();
          tempServices[serviceName] = serviceHosts;
        }
        serviceHosts.Add(serviceHost);

        if (needReregisterToConsul && !string.IsNullOrEmpty(serviceIdInConsul) && serviceIdInConsul == service.ID)
        {
          needReregisterToConsul = false;
        }
      }

      if (needReregisterToConsul)
      {
        RegisterToConsul();
      }

      var tempAllServices = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
      foreach (var item in tempServices)
      {
        tempAllServices[item.Key] = item.Value.ToList();
      }
      AllServices = tempAllServices;
    }

    public void Dispose()
    {
      DeregisterFromConsul();
      consulClient.Dispose();
      timer.Dispose();
    }
  }
}
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace ConsulDiscovery.HttpClient
{
  public class DiscoveryHttpMessageHandler : DelegatingHandler
  {
    private static readonly Random random = new Random((int)DateTime.Now.Ticks);

    private readonly DiscoveryClient discoveryClient;

    public DiscoveryHttpMessageHandler(DiscoveryClient discoveryClient)
    {
      this.discoveryClient = discoveryClient;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
      if (discoveryClient.AllServices.TryGetValue(request.RequestUri.Host, out var serviceHosts))
      {
        if (serviceHosts.Count > 0)
        {
          var index = random.Next(serviceHosts.Count);
          request.RequestUri = new Uri(new Uri(serviceHosts[index]), request.RequestUri.PathAndQuery);
        }
      }
      return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
    }
  }
}

  使用方法

  为了简单, 我为新建的WebApi 增加了一个 HelloController, 提供 SayHelloService 服务, 并把自己注册到Consul.

  当我们访问这个WebApi的 /WeatherForecast 时, 其Get()方法会访问 http://SayHelloService/Hello/NetCore, 这就相当于一次远程调用, 只是调用的就是这个WebApi的/Hello/NetCore

  1. appsettings.json 增加

"ConsulDiscoveryOptions": {
  "ConsulServerSetting": {
   "IP": "127.0.0.1", // 必填
   "Port": 8500, // 必填
   "RefreshIntervalInMilliseconds": 1000
  },
  "ServiceRegisterSetting": {
   "ServiceName": "SayHelloService", // 必填
   "ServiceIP": "127.0.0.1", // 必填
   "ServicePort": 5000, // 必填
   "ServiceScheme": "http", // 只能是http 或者 https, 默认http,
   "HealthCheckRelativeUrl": "/HealthCheck",
   "HealthCheckIntervalInMilliseconds": 500,
   "HealthCheckTimeOutInMilliseconds": 2000
  }
 }

  2.修改Startup.cs

using ConsulDiscovery.HttpClient;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;

namespace WebApplication1
{
  public class Startup
  {
    public Startup(IConfiguration configuration)
    {
      Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
      services.AddControllers();

      // 注册 ConsulDiscovery 相关配置
      services.AddConsulDiscovery(Configuration);
      // 配置 SayHelloService 的HttpClient
      services.AddHttpClient("SayHelloService", c =>
        {
          c.BaseAddress = new Uri("http://SayHelloService");
        })
        .AddHttpMessageHandler<DiscoveryHttpMessageHandler>();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime lifetime)
    {
      if (env.IsDevelopment())
      {
        app.UseDeveloperExceptionPage();
      }

      app.UseRouting();

      app.UseAuthorization();

      app.UseEndpoints(endpoints =>
      {
        endpoints.MapControllers();
      });

      // 启动 ConsulDiscovery
      app.StartConsulDiscovery(lifetime);
    }
  }
}

  3. 添加 HelloController

using Microsoft.AspNetCore.Mvc;

namespace WebApplication1.Controllers
{
  [ApiController]
  [Route("[controller]")]
  public class HelloController : ControllerBase
  {
    [HttpGet]
    [Route("{name}")]
    public string Get(string name)
    {
      return $"Hello {name}";
    }
  }
}

  4. 修改WeatherForecast

using Microsoft.AspNetCore.Mvc;
using System.Net.Http;
using System.Threading.Tasks;

namespace WebApplication1.Controllers
{
  [ApiController]
  [Route("[controller]")]
  public class WeatherForecastController : ControllerBase
  {
    private readonly IHttpClientFactory httpClientFactory;

    public WeatherForecastController(IHttpClientFactory httpClientFactory)
    {
      this.httpClientFactory = httpClientFactory;
    }

    [HttpGet]
    public async Task<string> Get()
    {
      var httpClient = httpClientFactory.CreateClient("SayHelloService");
      var result = await httpClient.GetStringAsync("Hello/NetCore");
      return $"WeatherForecast return:      {result}";
    }
  }
}

  5. 启动consul

consul agent -dev

  6. 启动 WebApplication1 并访问 http://localhost:5000/weatherforecast

  以上示例可以到 https://github.com/zhouandke/ConsulDiscovery.HttpClient 下载, 请记住一定要 启动consul:    consul agent -dev

  End

以上就是C# HttpClient 如何使用 Consul 发现服务的详细内容,更多关于C# HttpClient使用 Consul 发现服务的资料请关注我们其它相关文章!

(0)

相关推荐

  • C# 使用Fiddler捕获本地HttpClient发出的请求操作

    示例接口定义 为了方便笔者先自己写了个简单的接口用于展示,返回当前时间,具体如下 /// <summary> /// GetToday. /// </summary> /// <returns></returns> [HttpGet] [Route("GetTime/Today")] public MyDateTime GetToday() { var now = DateTime.Now; return new MyDateTime {

  • C# 使用HttpClient上传文件并附带其他参数的步骤

    HttpClient和MultipartFormDataContent(传送门)最低适用于.NET Framework 4.5版本 发送端代码 using (HttpClient client = new HttpClient()) { var content = new MultipartFormDataContent(); //添加字符串参数,参数名为qq content.Add(new StringContent("123456"), "qq"); strin

  • C#中HttpWebRequest、WebClient、HttpClient的使用详解

    HttpWebRequest: 命名空间: System.Net,这是.NET创建者最初开发用于使用HTTP请求的标准类.使用HttpWebRequest可以让开发者控制请求/响应流程的各个方面,如 timeouts, cookies, headers, protocols.另一个好处是HttpWebRequest类不会阻塞UI线程.例如,当您从响应很慢的API服务器下载大文件时,您的应用程序的UI不会停止响应.HttpWebRequest通常和WebResponse一起使用,一个发送请求,一个

  • C# 使用HttpClient模拟请求的案例

    情景描述 将一个时间转换为对应的unix时间戳, 字符集使用UTF-8编码,数据通讯统一采用 HTTP 协议通讯,使用POST 方法请求并传递参数. POST请求Content-Type 设置为application/x-www-form-urlencoded 除此之外,我们对请求添加签名的校验,key设置为X-Sign 接口定义 /// <summary> /// 获取时间戳(毫秒). /// </summary> /// <param name="dateTim

  • C# HttpClient Cookie验证解决方法

    自实现的cookie 验证,远程取值的例子 以下代码配合HttpClient使用可以实现跨域(cookie的读写) //验证 复制代码 代码如下: HttpClient httpClient = new HttpClient(url, null, true); httpClient.PostingData.Add(key,value);//登录用户名 httpClient.PostingData.Add(key,value);//密码 string str = httpClient.GetStr

  • C# HttpClient 如何使用 Consul 发现服务

    试用了Overt.Core.Grpc, 把 GRPC 的使用改造得像 WCF, 性能测试也非常不错, 非常推荐各位使用. 但已有项目大多是 http 请求, 改造成 GRPC 的话, 工作量比较大, 于是又找到了 Steeltoe.Discovery, 在 Startup 给 HttpClient 添加 DelegatingHandler, 动态改变请求url中的 host 和 port, 将http请求指向consul 发现的服务实例, 这样就实现了服务的动态发现. 经过性能测试, Steel

  • 详解Spring Cloud Consul 实现服务注册和发现

    Spring Cloud 是一个基于 Spring Boot 实现的云应用开发工具,它为基于 JVM 的云应用开发中涉及的配置管理.服务发现.断路器.智能路由.微代理.控制总线.全局锁.决策竞选.分布式会话和集群状态管理等操作提供了一种简单的开发方式.通过 Spring Boot 风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂.易部署和易维护的分布式系统开发工具包. Spring Cloud 包含了多个子项目(针对分布式系统中涉及的多个不同开源产品),比如:Sprin

  • Spring Cloud Consul的服务注册与发现

    运行Consul 以Windows为例,下载解压后,以开发模式运行: consul agent --dev 启动成功后,可以访问Consul提供的管理页面,默认端口为8500,页面上显示了已注册服务的列表,包括它们的运行状况等信息. 服务注册 1.添加Spring Cloud Consul依赖: <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artif

  • Python 使用 consul 做服务发现示例详解

    前言 前面一章讲了微服务的一些优点和缺点,那如何做到 一.目标 二.使用步骤 1. 安装 consul 我们可以直接使用官方提供的二进制文件来进行安装部署,其官网地址为 https://www.consul.io/downloads 下载后为可执行文件,在我们开发试验过程中,可以直接使用 consul agent -dev 命令来启动一个单节点的 consul 在启动的打印日志中可以看到 agent: Started HTTP server on 127.0.0.1:8500 (tcp), 我们

  • .Net Core服务治理Consul使用服务发现

    先思考一些问题:它是做什么的.以及怎么使用它.带着这些问题往下走. consul是做什么的 consul用于微服务下的服务治理.服务治理是什么?它包含但不限于:服务发现.服务配置.健康检查.键值存储.安全服务通信.多数据中心等. 为什么需要服务治理?举个例子:最开始的服务比较简单,各服务之间通过API就能访问.后面业务复杂了,服务也跟着复杂了,搞分布式了,而分布式又必然是多服务器部署,这就有一个问题:如果服务之间还是用API访问,那某个服务所在的服务器挂掉以后这个服务就不能用了,也不能自动转移,

  • Asp.Net Core使用Ocelot结合Consul实现服务注册和发现

    目录 1.服务注册与发现(Service Discovery) 2.Consul 3.Asp.Net Core向Consul注册服务实例 4.项目演示 4.1APIGateway项目 4.2Common项目 4.3APIServiceA项目 4.4APIServiceB项目 4.5项目运行 1.服务注册与发现(Service Discovery) 服务注册:我们通过在每个服务实例写入注册代码,实例在启动的时候会先去注册中心(例如Consul.ZooKeeper.etcd.Eureka)注册一下,

  • go-micro使用Consul做服务发现的方法和原理解析

    目录 安装Consul 安装Consul插件 服务端使用Consul 服务注册 注册过程 健康检查 客户端使用Consul 调用服务 发现过程 效果展示 go-micro v4默认使用mdns做服务发现.不过也支持采用其它的服务发现中间件,因为多年来一直使用Consul做服务发现,为了方便和其它服务集成,所以还是选择了Consul.这篇文章将介绍go-micro使用Consul做服务发现的方法.关于Consul的使用方式请参考我的另一篇文章:搭建Consul服务发现与服务网格 . 安装Consu

  • go使用consul实现服务发现及配置共享实现详解

    目录 使用consul四大特性 通过docker安装consul 实现代码 运行结果 使用consul四大特性 1. 服务发现:利用服务注册,服务发现功能来实现服务治理. 2. 健康检查:利用consul注册的检查检查函数或脚本来判断服务是否健康,若服务不存在则从注册中心移除该服务,减少故障服务请求. 3. k/v数据存储:存储kv数据,可以作为服务配置中心来使用. 4. 多数据中心:可以建立多个consul集群通过inter网络进行互联,进一步保证数据可用性. 通过docker安装consul

  • 详解使用Spring Cloud Consul实现服务的注册和发现

    首先安装consul环境,参照之前的文章:https://www.jb51.net/article/141789.htm 项目规划,2个服务端,1个客户端 首先来看服务端, 一:服务端1: 项目依赖 <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-clou

  • C#使用Consul集群进行服务注册与发现

    前言 我个人觉得,中间件的部署与使用是非常难记忆的:也就是说,如果两次使用中间件的时间间隔比较长,那基本上等于要重新学习使用. 所以,我觉得学习中间件的文章,越详细越好:因为,这对作者而言也是一份珍贵的备忘资料. Consul简介 Consul一个什么,我想大家通过搜索引擎一定可以搜索到:所以,我就不在重复他的官方描述了. 这里,我为大家提供一个更加好理解的描述. Consul是什么? Consul本质上是一个Socket通信中间件. 它主要实现了两个功能,服务注册与发现与自身的负载均衡的集群.

随机推荐