MongoDB学习笔记~监控Http请求的消息链
在微服务架构里,你的一个任务可以需要经过多次中转,去多个接口获取数据,而在这个过程中,出现问题后的解决就成了一个大难点,你无法定位它的问题,这时,大叔的分布式消息树就出现了,费话不多说,主要看一下实现的逻辑。
大叔对分布式消息链的一些想法
事情是这样的,前段时间在做接口开发时,可能出现这种情况,一个接口返回的数据,可能来自多个接口,这就出现了一些链条式的调用,这在面向服务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 }