Consul 学习笔记—服务发现

前言:

  上一篇文章简单实用Consul试下服务注册,本篇继续学习Consul中的另外特性:服务发现、KV操作 ;以及对上篇文章中存在的问题进行解决

问题解决

 在上一篇文章中,注册服务提示检查失败。

  通过排查发现为在docker 中运行的容器中配置的心跳检查api地址配置错误:

  "Consul": {
    "Address": "//host.docker.internal:8500",
    "HealthCheck": "/api/healthcheck",//心跳检查api地址
    "Name": "czapigoods",
    "Ip": "host.docker.internal",
    "Port": "5602" //未指定成当前docker运行对于端口
  }

 解决方法(docker修改配置方式):修改docker中配置文件appsettings.json:

  1. 进入docker命令行:docker exec -it 容器id /bin/bash  例如:docker exec -it f38a7a2ddfba /bin/bash
  2. 更新软件列表:apt-get update 
  3. 安装vim命令:apt-get install vim
  4. 进入appsettings.json 修改:vim appsettings.json
  5. 修改appsettings中Consul.Port节点为对于docker映射端口
  6. 按Esc键,并输入:wq命令(退出保存修改)
  7. 重启对于容器效果如下

  

  Ps:Doker相关操作后面单独详细

服务发现

  服务注册问题解决了,接下来我们了解下服务如何发现;首先创建一个web项目Consul.Client并添加Consul包引用

Install-Package Consul

 1、添加一个服务调用接口ICallService.cs用于调用我们添加的服务

public interface ICallService
{
    /// <summary>
    /// 获取 Goods Service 返回数据
    /// </summary>
    /// <returns></returns>
    Task<string> GetGoodsService();

    /// <summary>
    /// 获取 Order Service 返回数据
    /// </summary>
    /// <returns></returns>
    Task<string> GetOrderService();

    /// <summary>
    /// 初始化服务
    /// </summary>
    void InitServices();
}

 

 2、实现该接口CallService.cs调用Goods、Order的api方法  

public class CallService : ICallService
{
    private readonly IConfiguration _configuration;
    private readonly ConsulClient _consulClient;

    private ConcurrentBag<string> _serviceAUrls;
    private ConcurrentBag<string> _serviceBUrls;

    private IHttpClientFactory _httpClient;

    public CallService(IConfiguration configuration, IHttpClientFactory httpClient)
    {
        _configuration = configuration;

        _consulClient = new ConsulClient(options =>
        {
            options.Address = new Uri(_configuration["Consul:Address"]);
        });

        _httpClient = httpClient;
    }

    public async Task<string> GetGoodsService()
    {
        if (_serviceAUrls == null)
            return await Task.FromResult("Goods Service Initializing...");

        using var httpClient = _httpClient.CreateClient();

        //随机获取一个服务地址调用
        var serviceUrl = _serviceAUrls.ElementAt(new Random().Next(_serviceAUrls.Count()));

        Console.WriteLine("Goods Service:" + serviceUrl);

        var result = await httpClient.GetStringAsync($"{serviceUrl}/goods");

        return result;
    }

    public async Task<string> GetOrderService()
    {
        if (_serviceBUrls == null)
            return await Task.FromResult("Order Service Initializing...");

        using var httpClient = _httpClient.CreateClient();

        //随机获取一个服务地址调用
        var serviceUrl = _serviceBUrls.ElementAt(new Random().Next(_serviceBUrls.Count()));

        Console.WriteLine("Order Service:" + serviceUrl);

        var result = await httpClient.GetStringAsync($"{serviceUrl}/order");

        return result;
    }

    public void InitServiceList()
    {
        var serviceNames = new string[] { "czapigoods", "czapiorder" };

        foreach (var item in serviceNames)
        {
            Task.Run(async () =>
            {
                var queryOptions = new QueryOptions
                {
                    WaitTime = TimeSpan.FromMinutes(5)
                };
                while (true)
                {
                    await InitServicesAsync(queryOptions, item);
                }
            });
        }
    }
    private async Task InitServicesAsync(QueryOptions queryOptions, string serviceName)
    {
        //获取心跳检查服务
        var result = await _consulClient.Health.Service(serviceName, null, true, queryOptions);

        if (queryOptions.WaitIndex != result.LastIndex)
        {
            queryOptions.WaitIndex = result.LastIndex;

            var services = result.Response.Select(x => $"//{x.Service.Address}:{x.Service.Port}");

            if (serviceName == "czapigoods")
            {
                _serviceAUrls = new ConcurrentBag<string>(services);
            }
            else if (serviceName == "czapiorder")
            {
                _serviceBUrls = new ConcurrentBag<string>(services);
            }
        }
    }
}

 3、接下来添加接口依赖注入、以及服务初始化调用  

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

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();

        services.AddHttpClient();
     //依赖注入CallService
        services.AddSingleton<ICallService, CallService>();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ICallService service)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
     //初始化服务列表调用
        service.InitServiceList();
    }
}  

 4、使用Postman测试调用

  

KV操作

 除了提供服务发现和健康检查的集成.Consul提供了一个易用的键/值存储.这可以用来保持动态配置,协助服务协调,领袖选举,做开发者可以想到的任何事情.

 1、创建/修改

  • 命令方式:consul kv put key val 如:consul kv put port 9000 –添加key为port值为9000
  • Http方式:

 

 2、查询  

  • 命令方式:consul kv get key 如:consul kv get port –查询key为port的KV
  • Http方式:value为baisc

 3、删除

  • 命令方式:consul kv delete key 如:consul kv delete port –删除key为port的KV
  • Http方式:

 其他

 网上找了下:常用服务发现框架consul、zookeeper及etcd比较:

参考:

consul手册://blog.csdn.net/liuzhuchen/article/details/81913562 

//www.consul.io/docs

//www.consul.io/api/kv.html

github:

//github.com/cwsheng/Consul.Demo.git