文章出處

在微服務架構里,你的一個任務可以需要經過多次中轉,去多個接口獲取數據,而在這個過程中,出現問題后的解決就成了一個大難點,你無法定位它的問題,這時,大叔的分布式消息樹就出現了,費話不多說,主要看一下實現的邏輯。

大叔對分布式消息鏈的一些想法

事情是這樣的,前段時間在做接口開發時,可能出現這種情況,一個接口返回的數據,可能來自多個接口,這就出現了一些鏈條式的調用,這在面向服務SOA開發和微服務開發中經常會遇到,因為你的VO數據來源,可能真的不是一個接口能滿足的,這就需要有一個HTTP的鏈條,而如何對這些請求進行跟蹤,就是大叔的消息鏈要做的事了!

頁面VO

=>

接口A請求

=>

接口B請求

=>

接口C接口

=>

接口C返回

=>

接口B返回

=>

接口A返回

=>

請求結束!

大叔的設計圖

在消息傳遞過程中,使用這個消息上下文

    /// <summary>
    /// 消息上下文
    /// </summary>
    public class LoggerContext
    {
        /// <summary>
        /// 消息根ID(完整請求)
        /// </summary>
        [BsonId]
        [BsonRepresentation(BsonType.ObjectId)]
        public string Id { get; set; }
        public string RootId { get; set; }
        /// <summary>
        /// 上級消息ID(前一個請求)
        /// </summary>
        public string ParentId { get; set; }
        /// <summary>
        /// 當前消息ID(當前請求)
        /// </summary>
        public string ChildId { get; set; }
        /// <summary>
        /// 消息體
        /// </summary>
        public string MessageBody { get; set; }
        /// <summary>
        /// 當前url
        /// </summary>
        public string Url { get; set; }
        /// <summary>
        /// 時間
        /// </summary>
        public DateTime AddTime { get; set; }
    }

大叔對消息處理程序的封裝

    /// <summary>
    /// 分布式消息樹實現
    /// </summary>
    public class LoggerContextImpl
    {
        static ILogger logger = new EmptyLogger();
        #region Fields & Consts
        const string Format_Msg_Before = "請求之前,地址:{0},方式:{1},時間:{2}";
        const string Format_Msg = "響應之后,地址:{0},狀態碼:{1},時間:{2}";
        /// <summary>
        /// HttpContext上存儲的日志上下文
        /// </summary>
        const string LOGGERCONTEXT = "LoggerContext";
        #endregion

        #region Private Methods
        /// <summary>
        /// 從請求頭中拿到當前的消息樹對象
        /// client發布端:SetContextToServer
        /// server接收端:GetContextFromServer
        /// </summary>
        /// <returns></returns>
        static LoggerContext GetContextFromServer()
        {
            try
            {
                var result = System.Web.HttpContext.Current.Request.Headers.GetValues(LOGGERCONTEXT);
                if (result != null && result.Length > 0)
                {
                    var cat = JsonConvert.DeserializeObject<LoggerContext>(result[0].ToString());
                    return cat;
                }
                return null;
            }
            catch (Exception ex)
            {
                logger.Logger_Error(ex);
                return null;
            }

        }
        static LoggerContext GetContextFromServer(HttpClient http)
        {
            try
            {
                IList<string> result = http.DefaultRequestHeaders.GetValues(LOGGERCONTEXT) as IList<string>;
                if (result != null && result.Count > 0)
                {
                    var cat = JsonConvert.DeserializeObject<LoggerContext>(result[0].ToString());
                    return cat;
                }
                return null;
            }
            catch (Exception ex)
            {
                logger.Logger_Error(ex);
                return null;
            }

        }
        /// <summary>
        /// 設置消息樹到當前請求頭
        /// </summary>
        /// <returns></returns>
        internal static void SetContextToRequestHeader(System.Web.HttpContext http, LoggerContext context)
        {
            try
            {
                if (http.Request.Headers.GetValues(LOGGERCONTEXT) != null && http.Request.Headers.GetValues(LOGGERCONTEXT).Length > 0)
                {
                    http.Request.Headers.Remove(LOGGERCONTEXT);
                }
                http.Request.Headers.Add(LOGGERCONTEXT, JsonConvert.SerializeObject(context));
            }
            catch (Exception ex)
            {
                logger.Logger_Error(ex);
            }

        }
        /// <summary>
        /// 設置消息樹到當前請求頭
        /// </summary>
        /// <param name="http"></param>
        /// <param name="context"></param>
        internal static void SetContextToRequestHeader(HttpClient http, LoggerContext context)
        {
            try
            {
                http.DefaultRequestHeaders.Remove(LOGGERCONTEXT);
                http.DefaultRequestHeaders.Add(LOGGERCONTEXT, JsonConvert.SerializeObject(context));
            }
            catch (Exception ex)
            {
                logger.Logger_Error(ex);
            }
        }
        /// <summary>
        /// 設置消息樹到當前請求頭
        /// </summary>
        /// <param name="http"></param>
        /// <param name="context"></param>
        internal static void SetContextToRequestHeader(System.Web.HttpContextBase http, LoggerContext context)
        {
            try
            {
                if (http.Request.Headers.GetValues(LOGGERCONTEXT) != null && http.Request.Headers.GetValues(LOGGERCONTEXT).Length > 0)
                {
                    http.Request.Headers.Remove(LOGGERCONTEXT);
                }

                http.Request.Headers.Add(LOGGERCONTEXT, JsonConvert.SerializeObject(context));
            }
            catch (Exception ex)
            {
                logger.Logger_Error(ex);
            }


        }
        /// <summary>
        /// 設置請求頭,它來自某個響應頭
        /// </summary>
        /// <param name="response"></param>
        internal static void SetContextToRequestHeader(HttpResponseMessage response, string currentUrl = null)
        {
            try
            {
                IEnumerable<string> context = new List<string>();
                if (response.Headers.TryGetValues(LOGGERCONTEXT, out context) || response.RequestMessage.Headers.TryGetValues(LOGGERCONTEXT, out context))
                {
                    if (context != null)
                    {
                        var cat = JsonConvert.DeserializeObject<LoggerContext>((context as string[])[0].ToString());
                        SetContextToRequestHeader(System.Web.HttpContext.Current, cat);
                        GetCurrentContext("響應結束", currentUrl);
                    }
                }
            }
            catch (Exception ex)
            {
                logger.Logger_Error(ex);
            }


        }
        /// <summary>
        /// 設置LoggerContext到響應頭
        /// </summary>
        /// <param name="response"></param>
        /// <param name="context"></param>
        internal static void SetContextToResponseHeader(HttpResponseBase response, LoggerContext context)
        {
            try
            {
                if (response.Headers.GetValues(LOGGERCONTEXT) != null
                               && response.Headers.GetValues(LOGGERCONTEXT).Length > 0)
                {
                    response.Headers.Remove(LOGGERCONTEXT);
                }
                response.Headers.Add(LOGGERCONTEXT, JsonConvert.SerializeObject(context));
            }
            catch (Exception ex)
            {
                logger.Logger_Error(ex);
            }

        }
        /// <summary>
        /// 生產一個ROOTID
        /// </summary>
        /// <returns></returns>
        static string GenerateRootID()
        {
            return DateTime.Now.ToString("yyyyMMddHHmmssfff") + Thread.CurrentThread.ManagedThreadId;
        }
        /// <summary>
        /// 遞歸樹
        /// </summary>
        /// <param name="str"></param>
        /// <param name="id"></param>
        /// <param name="timer"></param>
        static void MsgTree(StringBuilder str, string id, List<DateTime> timer)
        {
            var list = NoSql.MongodbManager<LoggerContext>.Instance.Find(i => i.ParentId == id).ToList();
            if (list != null)
            {
                str.Append("<ul class='treeMsg'>");
                foreach (var item in list)
                {
                    timer.Add(item.AddTime);
                    str.AppendFormat("<li><span style='color:red'>{0}</span><span style='color:green'>{1}</span><span>{2}</span></li>"
                     , item.Url
                     , item.MessageBody
                     , item.AddTime);
                    MsgTree(str, item.ChildId, timer);
                }
                str.Append("</ul>");

            }



        }
        #endregion

        #region 分布式消息樹的封裝(倉儲大叔) 
        /// <summary>
        ///  建立一個上下文對象
        /// </summary>
        /// <param name="rootId">根ID</param>
        /// <param name="parentId">上一請求ID</param>
        /// <param name="url"></param>
        /// <returns></returns>
        public static LoggerContext DoTransaction(string rootId, string parentId, string url)
        {
            if (GlobalConfig.ConfigManager.Config.Logger.IsHttpClientLog != 1)
                return new LoggerContext();

            //建立一個日志,返回rootid,parentid(第一個應該是空),currentid,其中currentid將做為下一次請求的parentid
            var filter = Builders<LoggerContext>.Filter.Eq(i => i.RootId, rootId);
            var context = NoSql.MongodbManager<LoggerContext>.Instance.Find(filter).FirstOrDefault();
            if (!string.IsNullOrWhiteSpace(parentId))
            {
                filter = Builders<LoggerContext>.Filter.Eq(i => i.ParentId, parentId);
                context = NoSql.MongodbManager<LoggerContext>.Instance.Find(filter).FirstOrDefault();
            }
            if (context == null)
            {
                context = new LoggerContext
                {
                    RootId = GenerateRootID(),
                    ParentId = null,
                    ChildId = Domain.PrimaryKey.GenerateNewStringId(),
                    MessageBody = "開啟一個新的請求:" + url,
                    Url = System.Web.HttpContext.Current.Request.Url.AbsoluteUri.Split(new char[] { '?' }, StringSplitOptions.RemoveEmptyEntries)[0],
                    AddTime = DateTime.Now,
                };
                NoSql.MongodbManager<LoggerContext>.Instance.InsertOne(context);
            }
            context.MessageBody = HttpUtility.UrlEncode(context.MessageBody);
            return context;
        }
        /// <summary>
        /// 添加日志,它依賴于一個會話
        /// root->message->message1->message1.1->message1.1.1
        /// </summary>
        /// <param name="parentId">父會話ID</param>
        /// <param name="url"></param>
        /// <param name="message"></param>
        public static LoggerContext LogEvent(string parentId, string url, string message)
        {
            if (GlobalConfig.ConfigManager.Config.Logger.IsHttpClientLog != 1)
                return new LoggerContext();

            var filter = Builders<LoggerContext>.Filter.Eq(i => i.ChildId, parentId);
            var context = NoSql.MongodbManager<LoggerContext>.Instance.Find(filter).FirstOrDefault();
            if (context != null)
            {
                context = new LoggerContext
                {
                    RootId = context.RootId,
                    ParentId = context.ChildId,
                    ChildId = Domain.PrimaryKey.GenerateNewStringId(),
                    MessageBody = message + ":" + url,
                    Url = System.Web.HttpContext.Current.Request.Url.AbsoluteUri.Split(new char[] { '?' }, StringSplitOptions.RemoveEmptyEntries)[0],
                    AddTime = DateTime.Now,
                };
                NoSql.MongodbManager<LoggerContext>.Instance.InsertOne(context);
            }
            return context;
        }
        /// <summary>
        /// 返回當前上下文
        /// </summary>
        /// <returns></returns>
        public static LoggerContext GetCurrentContext(string message, string currentUrl = null)
        {
            try
            {
                currentUrl = (currentUrl ?? System.Web.HttpContext.Current.Request.Url.AbsoluteUri).Split(new char[] { '?' }, StringSplitOptions.RemoveEmptyEntries)[0];
                var context = GetContextFromServer();

                if (context == null)
                {
                    context = DoTransaction("", "", currentUrl);

                }
                else
                {
                    context = LogEvent(context.ChildId, currentUrl, message);
                }
                return context;
            }
            catch (Exception ex)
            {
                logger.Logger_Error(ex);
                return new LoggerContext();
            }

        }

        #endregion

        #region 消息樹UI
        /// <summary>
        /// 返回UI消息樹
        /// </summary>
        /// <returns></returns>
        public static string GetMongoLog(DateTime? fromDate, DateTime? toDate, int page = 1)
        {
            string from = DateTime.Now.AddYears(-1).Date.ToString("yyyy-MM-dd");
            string to = DateTime.Now.Date.AddDays(1).ToString("yyyy-MM-dd");
            if (fromDate.HasValue)
            {
                from = fromDate.Value.ToString("yyyy-MM-dd");

            }
            if (toDate.HasValue)
            {
                to = toDate.Value.ToString("yyyy-MM-dd");
            }
            var stages = new List<IPipelineStageDefinition>();
            stages.Add(new JsonPipelineStageDefinition<BsonDocument, BsonDocument>("{$match:{AddTime:{$gt:ISODate('" + from + "'),$lt:ISODate('" + to + "')}}}"));
            stages.Add(new JsonPipelineStageDefinition<BsonDocument, BsonDocument>("{$group:{_id: \"$RootId\", count: {$sum: 1}}}"));
            stages.Add(new JsonPipelineStageDefinition<BsonDocument, BsonDocument>("{$skip:" + page * 5 + "}"));
            stages.Add(new JsonPipelineStageDefinition<BsonDocument, BsonDocument>("{$limit:5}"));
            var pipeline = new PipelineStagePipelineDefinition<BsonDocument, BsonDocument>(stages);
            var result = NoSql.MongodbManager<LoggerContext>.Collection.Aggregate(pipeline);
            StringBuilder str = new StringBuilder();

            str.Append("<ol class='treeMsg'>");
            foreach (var item in result.ToList())
            {
                var timer = new List<DateTime>();
                var old = NoSql.MongodbManager<LoggerContext>.Instance.Find(i => i.RootId == item.Values.ToArray()[0].ToString() && i.ParentId == null).FirstOrDefault();
                timer.Add(old.AddTime);
                str.Append("<li style='margin:5px;border:1px dashed #aaa'>");
                str.AppendFormat("<span style='color:red;'>{0}</span><span style='color:green'>{1}</span><span>{2}</span>"
                   , old.Url
                   , old.MessageBody
                   , old.AddTime);
                MsgTree(str, old.ChildId, timer);
                str.AppendFormat("<p><b><em>本次請求用時{0}毫秒({1}秒)<em></b></p>"
                    , (timer.Max() - timer.Min()).TotalMilliseconds
                    , (timer.Max() - timer.Min()).TotalSeconds);
                str.Append("</li>");
            }
            str.Append("</ol>");
            return str.ToString();
        }
        #endregion
    }

 


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()