浅谈 asp.net core 中的 healthCheck 并结合 consul 改造 web api

本篇已收录至 asp.net core 随笔系列

为什么我们需要"应用程序健康监测".

很多问题是在其真正发生之前可以规避的, 我们的应用程序不可能一直稳定运行, 所以需要一定的检测机制去对其进行保驾护航. 在出现异常端倪之前今早发现问题, 排查问题.

link: https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/health-checks?view=aspnetcore-3.1

能做什么

  • 检测应用的状态, 并对无效的应用的状态做出一些措施.

  • 对系统的内存使用, 磁盘空间使用和其他的物理资源的使用进行监控.

  • 同样可以检测应用程序所依赖的服务的健康与否.

为什么

  • 目前微服务架构是最火热的架构技术, 所以一般微服务中的单服务都会提供一个 health 接口, 用于 loadbalance 机制去检测服务节点是否还在正常工作.

  • 单纯的接口如果只返回一个OK, 那就没意思了, 因为你的服务要保证业务正常的前提下返回OK才算真正意义上的OK.

  • 所以在 anc 应用中配置健康监测需要监测的实际内容, 最后都没有问题了. 再返回OK, 负载均衡机制才会放心的把 traffic 发到你的实例中.

健康监测的原则

  • 监测机制不要搞很久, 这样负载均衡每次ping你一次都得很久的话, 那样的监测实际上意义不大

  • 一次检测尽量将结果返回的全一些

  • 可以但不是必须持久化你的监测记录

  • 不提供深度细节的检测

  • 不提供额外的log

  • 不提供用于debug的数据

anc的health check

  • anc框架在2.2以后提供了开箱即用的health check中间件. 只需要在startup.cs文件中配置如下简单的代码, 但是实际应用程序中要复杂的很多

    // startup.cs 中 configureservice 方法, 添加
    services.AddHealthChecks();
    
    // startup.cs 中 configure 方法, 添加
    var options = new HealthCheckOptions();
            options.ResponseWriter = async (c, r) => {
    
                c.Response.ContentType = "application/json";
    
                var result = JsonConvert.SerializeObject(new
                {
                    status = r.Status.ToString(),
                    errors = r.Entries.Select(e => new { key = e.Key, value = e.Value.Status.ToString() })
                });
    
                await c.Response.WriteAsync(result);
            };
    
            app.UseHealthChecks("/health", options);
    
  • 带有UI的例子: https://www.telerik.com/blogs/health-checks-in-aspnet-core

  • base 概念: https://gunnarpeipman.com/aspnet-core-health-checks/

结合 consul 来对 web api 健康监测

consul 是一个服务治理的开源工具. 本文不涉及其内容.

使用 consul 需要为你的 web api 项目添加 consul nuget package.

项目中添加一个 IApplicationBuilder extension 类, 代码如下:

using Consul;
using DennisBlog.WebAPI.Utils;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Hosting;
using System;

namespace DennisBlog.WebAPI.Extensions
{
    public static class AppBuilderExtension
    {
        public static IApplicationBuilder RegisterConsul(this IApplicationBuilder app, IHostApplicationLifetime lifetime, ServiceEntity serviceEntity) 
        {
            Action<ConsulClientConfiguration> action = (x=> x.Address = new Uri($"http://{serviceEntity.ConsulIP}:{serviceEntity.ConsulPort}"));
            var consulClient = new ConsulClient(action);

            var httpCheck = new AgentServiceCheck()
            {
                DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5),//服务启动多久后注册
                Interval = TimeSpan.FromSeconds(10),//健康检查时间间隔,或者称为心跳间隔
                HTTP = $"http://{serviceEntity.IP}:{serviceEntity.Port}/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();//服务启动时注册,内部实现其实就是使用 Consul API 进行注册(HttpClient发起)
            lifetime.ApplicationStopping.Register(() =>
            {
                consulClient.Agent.ServiceDeregister(registration.ID).Wait();//服务停止时取消注册
            });

            return app;
        }
    }
}

修改 startup.cs 的 configure 方法 , 向 consul 注册你的 web api:

var ip = _configuration["ip"] ?? "localhost";
            var port = Convert.ToInt32(_configuration["port"] ?? "8080");
            var serviceName = _configuration["Service:Name"] ?? "Dennis.Blog.WebApiService";
            var consulIp = _configuration["Consul:IP"] ?? "localhost";
            var consulPort = Convert.ToInt32(_configuration["Consul:Port"] ?? "8500");
            ServiceEntity service = new ServiceEntity
            {
                IP = ip,
                Port = port,
                ServiceName = serviceName,
                ConsulIP = consulIp,
                ConsulPort = consulPort
            };
            app.RegisterConsul(lifetime, service);
posted @ 2020-03-12 11:35  YanyuWu  阅读(584)  评论(0编辑  收藏  举报