zoukankan      html  css  js  c++  java
  • 1小时完成应用程序健康检查组件

    一、一个故事(虽然没有事故)

    某天运维的同学通知我,云服务集群要加一台机器,过程是从当前线上集群中克隆一份服务器镜像,启动并加入集群,由于应用依赖的数据库服务器设置了白名单,新加的服务器需要加入白名单,悲剧的是,运维同学并不知道应用依赖了哪些数据库。

    运维同学只好登录服务器,检查每个应用的web.config文件查看数据库配置,并在对应的数据库服务器添加白名单。

    一个小时后,运维同学告诉我,白名单添加完成,请协助验证应用是否正常工作

    此时我内心是纠结的,由于云服务集群服务器部署的都是无界面的WEBAPI,要验证它是否正常需要模拟HTTP请求,幸运的是,我对这些API对于数据库的依赖还算熟悉,一番操作后,终于验证完毕,耗时1小时。

    最终,服务器上线了,过程还算顺利,并未发生意外。

    二、懒鬼的思考

    作为一个资深懒鬼,我觉得做这样的工作即费力又没有收益,而且还有发生意外导致背锅的风险,实在恶心至极,为了避免再做这样的事情,我决定做点什么。

    首先,分析这次维护过程,不足之处:

    1.缺失应用程序依赖管理及服务器依赖管理,导致集群新增服务器时,对应的白名单添加操作需要人工确认和验证;

    2.应用程序(尤其时无UI应用)没有很好的依赖自检功能,无法很方便检查应用程序的依赖项健康状况;

    基于以上两点,可以我可以做什么事情:

    1.建议运维团队建立应用、服务器等依赖管理

    2.做一个健康检查组件供各个应用使用,方便检查应用程序的健康状况

    三、健康检查组件

    1.检查器

    由于我们并不知道每个应用程序需要检查哪些依赖以及怎么检查,因此我们将检查器设计成接口:

        /// <summary>
        /// 健康检查器接口
        /// </summary>
        public interface IHealthChecker
        {
            /// <summary>
            /// 名称
            /// </summary>
            string Name { get; }
            /// <summary>
            /// 描述
            /// </summary>
            string Description { get; }
            /// <summary>
            /// 检查方法
            /// </summary>
            void Check();
        }

    2.检查结果对象

    检查结果包括名称、描述、耗时、是否成功、错误信息

        /// <summary>
        /// 检查结果
        /// </summary>
        public class CheckResult
        {
            /// <summary>
            /// 名字
            /// </summary>
            public string Name { get; set; }
            /// <summary>
            /// 描述
            /// </summary>
            public string Description { get; set; }
            /// <summary>
            /// 状态-success,-failed
            /// </summary>
            public bool Success { get; set; }
            /// <summary>
            /// 错误信息
            /// </summary>
            public string Error { get; set; }
            /// <summary>
            /// 耗时
            /// </summary>
            public long Elapsed { get; set; }
        }

    3.检查器管理

    一般场景下,我们的应用程序不仅仅有一个依赖项,因此会创建多个检查器,就需要一个类来管理这些检查器,集中地调用检查器的Check方法来得到检查结果,并且处理各种异常。

    我们将这个类命名为HealthCheckManger,这里我们设计为单例模式:

        /// <summary>
        /// 健康检查管理器
        /// </summary>
        public class HealthCheckManger
        {
            static HealthCheckManger manager = new HealthCheckManger();
            private static HealthCheckManger Instance
            {
                get
                {
                    return manager;
                }
            }
            /// <summary>
            /// 注册checker,通过注册checker
            /// </summary>
            /// <param name="checker"></param>
            public static void RegisterChecker(IHealthChecker checker)
            {
                manager.checkerList.Add(checker);
            }
            /// <summary>
            /// 注册checker,通过注册名字,描述,函数
            /// </summary>
            /// <param name="name"></param>
            /// <param name="description"></param>
            /// <param name="action"></param>
            public static void RegisterChecker(string name, string description, Action action)
            {
                manager.checkerList.Add(new AddHealthChecker(name, description, action));
            }
            /// <summary>
            /// 公开检查方法
            /// </summary>
            /// <returns></returns>
            public static List<CheckResult> CheckAll()
            {
                return manager.DoCheckAll().Result;
            }
            /// <summary>
            /// 异步检查
            /// </summary>
            /// <returns></returns>
            public static async Task<List<CheckResult>> CheckAllAsync()
            {
                return await manager.DoCheckAll().ConfigureAwait(false);
            }/// <summary>
            /// 检查器列表
            /// </summary>
            List<IHealthChecker> checkerList = new List<IHealthChecker>();
            /// <summary>
            /// 多线程跑所有check方法
            /// </summary>
            /// <returns></returns>
            private async Task<List<CheckResult>> DoCheckAll()
            {
                var tasks = new List<Task<CheckResult>>();
                foreach (var checker in checkerList)
                {
                    tasks.Add(Task.Run(() => { return DoCheck(checker); }));
                }
                var t = await Task.WhenAll(tasks.ToArray()).ConfigureAwait(false);
                return t.ToList();
            }
            /// <summary>
            /// 单个check方法
            /// </summary>
            /// <param name="checker"></param>
            /// <returns></returns>
            private CheckResult DoCheck(IHealthChecker checker)
            {
                Stopwatch sw = new Stopwatch();
                sw.Start();
                try
                {
                    checker.Check();
                    sw.Stop();
                    return new CheckResult { Name = checker.Name, Description = checker.Description, Success = true, Elapsed = sw.ElapsedMilliseconds };
                }
                catch (Exception ex)
                {
                    sw.Stop();
                    var error = "";
                    if (ex.InnerException != null)
                    {
                        error = string.Format("message:{0} 
    stacktrace:{1} 
    InnerException:message:{2}
    stacktrace:{3}", ex.Message, ex.StackTrace, ex.InnerException.Message, ex.InnerException.StackTrace);
    
                    }
                    else
                    {
                        error = string.Format("message:{0} 
    stacktrace:{1}", ex.Message, ex.StackTrace);
                    }
                    return new CheckResult { Name = checker.Name, Description = checker.Description, Success = false, Elapsed = sw.ElapsedMilliseconds, Error = error };
                }
            }
        }

    4.内置检查器

    通常情况,我们的应用都会依赖数据库,因此,我们设计一个内置的数据库连接检查器,此时不得不感慨ADO.NET设计的精妙,我们可以通过很少的代码就实现一个支持多种数据库的检查器:

        /// <summary>
        /// 数据库健康检查器
        /// </summary>
        public class DatabaseHealthChecker:IHealthChecker
        {
            private DbProviderFactory dbFactory = null;
    
            private string connectionString = null;
            /// <summary>
            /// 构造函数
            /// </summary>
            /// <param name="connectionStringName"></param>
            public DatabaseHealthChecker(string connectionStringName)
            {
                this.Name = connectionStringName;
                this.Description = "数据库连接";
                var providerName = ConfigurationManager.ConnectionStrings[Name].ProviderName;
                this.connectionString = ConfigurationManager.ConnectionStrings[Name].ConnectionString;
    
                if (!string.IsNullOrEmpty(providerName))
                {
                    this.dbFactory = DbProviderFactories.GetFactory(providerName);
                }
                else
                {
                    throw new ArgumentNullException("ProviderName", "数据库提供名称参数不能为空");
                }
            }
            /// <summary>
            /// 名称
            /// </summary>
            public string Name { get; private set; }
            /// <summary>
            /// 描述
            /// </summary>
            public string Description { get; private set; }
            /// <summary>
            /// 检查方法
            /// </summary>
            public void Check()
            {
                using (var con = dbFactory.CreateConnection())
                {
                    con.ConnectionString = this.connectionString;
                    con.Open();
                }
    
            }
        }

    有了内置的数据库连接检查器,我们还可以给HealthCheckManger添加一个方法,来帮助我们根据web.config的connectionStrings配置快速注册检查器实例:

            public static void RegisterAllDatabaseHealthChecker()
            {
                foreach (ConnectionStringSettings con in ConfigurationManager.ConnectionStrings)
                {
                    manager.RegisterChecker(new DatabaseHealthChecker(con.Name));
                }
            }

    至此,咱们的组件就完成了,根据各位读者的实力,大家肯定能在1个小时内完成这些代码(虽然我们花费了一天)。

    四、健康检查组件for ASP.NET MVC

    在我们的ASP.NET MVC项目Global.cs文件中添加如下代码,注册检查器,也可以注册自己的检查器:

    HealthCheckManger.RegisterAllDatabaseHealthChecker();
    //HealthCheckManger.RegisterChecker(new MyChecker()); //自己定义的checker

    我们可以写一个controller用来输出检查结果,当然也可以输出一个更加漂亮的页面显示具体信息:

        public class HealthCheckController : Controller
        {
            /// <summary>
            /// 首页
            /// </summary>
            /// <returns></returns>
            public ActionResult Index()
            {
                var result = HealthCheckManger.CheckAll();
                return Json(result, JsonRequestBehavior.AllowGet);
            }
        }

    五、总结

    1.我们用到了单例模式,HealthCheckManger类;

    2.我们用到了多线程并行运算,HealthCheckManger在调用检查器的Check方法时,使用了Task.WhenAll方法,这样我们可以尽早拿到最终检查结果,而不是一个一个排队check

    3.我们使用接口定义检查器,保证了组件的可扩展性

    4.我们可以写更多的内置检查器,提高代码复用

    5.我们将组件打包为NuGet包,就可以让全世界的同学使用啦

    六、延伸思考

    1.目前我们的组件只是被动地接收检查命令,可以考虑做一个Job定期检查并记录日志和报警

    2.基于健康检查结果,我们可以对数据库、Redis等依赖对象进行熔断策略,当依赖项挂掉(超时)的时候,不至于应用整个由于处理连接响应过慢而雪崩;

    PS:以上代码均由我们一位刚毕业的工程师编写。

  • 相关阅读:
    flask框架-wtforms
    flask框架-蓝图
    flask框架-请求扩展
    flask框架-中间件
    flask框架-闪现
    flask框架-session
    flask框架-请求和响应
    flask框架-模板语言
    flask框架-路由
    flask框架-配置文件
  • 原文地址:https://www.cnblogs.com/xiaoweiyu/p/6587397.html
Copyright © 2011-2022 走看看