文章出處
文章列表
在微服務架構里,你的一個任務可以需要經過多次中轉,去多個接口獲取數據,而在這個過程中,出現問題后的解決就成了一個大難點,你無法定位它的問題,這時,大叔的分布式消息樹就出現了,費話不多說,主要看一下實現的邏輯。
大叔對分布式消息鏈的一些想法
事情是這樣的,前段時間在做接口開發時,可能出現這種情況,一個接口返回的數據,可能來自多個接口,這就出現了一些鏈條式的調用,這在面向服務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 }
文章列表
全站熱搜