问题提出:

目前在建立一个接口平台,宿主在IIS中,通过WEBSERVICE接口提供统一接入,再由总控程序调用不同的功能模块实现具体服务。在此过程中,需要记录哪个客户端什么时候访问了系统的哪个服务、服务时长、记录访问的简单内容信息,记录访问过程中出现的异常。由于平台处理速度很快(平均每事务200ms),且客户端并发量大,采用什么样的机制来记录这些并发日志信息成了棘手的问题,一要控制好并发;二要不能太多影响事务处理速度。

目前的想法是建立一个共同队列,先进先出,每个并发的访问响应线程都可以往里面写信息,这个队列入口势必成为瓶颈,所以写操作需要尽量简单,把写时间压缩到最小。另一头,由一个专用日志记录进程来批量提取队列中的消息,并记录到文件或数据库中。

本平台使用VS.NET 2003开发,运行于WINDOWS 2003 SERVER上。
我们采用MSMQ(WINDOWS的消息队列服务)来解决以上的问题,实际效果显示,完全达到设计效果!

关于MSMQ的参考资料:
MSMQ(Microsoft Message Queue)介绍
. Net环境下消息队列(MSMQ)对象的应用

具体实现:
1.定义传递消息类,用于承载发送的信息
using System;

namespace NBServiceClassLibrary.Logging
{
 /// <summary>
 /// LogEntry 的摘要说明。
 /// </summary>
 [Serializable()]
 public sealed class LogEntry
 {  
    
  public string log_type = string.Empty;
  public string log_point = string.Empty;
  public long   system_id = -1;
  public string system_name = string.Empty;
  public string service_name = string.Empty;
  public string sub_type = string.Empty;
  public string log_desc = string.Empty;
  public int    log_level = 0;
   }
}

2.定义发送类,封装发送行为
using System;
using System.Messaging;

namespace NBServiceClassLibrary.Logging
{
 /// <summary>
 /// Logger 的摘要说明。
 /// 向消息队列中写入日志信息
 /// 日志的传送对象为LogEntry
 /// </summary>
 public sealed class Logger
 {   
  private static object sync = new object();
  private static object syncWrite = new object();
  static volatile MessageQueue writer = null;

  private static MessageQueue MSQWriter
  {
   get
   {
    if (writer == null)
    {
     lock (sync)
     {
      if (writer == null)
      {
       string queuePath = ".\\Private$\\NBSS_MSQ";
       EnsureQueueExists(queuePath);
       writer = new MessageQueue(queuePath);       
      }
     }
    }    
    return writer;
   }
  }

  private Logger()
  {
  }

  private static void EnsureQueueExists(string path)
  {
   if(!MessageQueue.Exists(path))
   {
    MessageQueue.Create(path);
   }
  }
 
  public static void Write(LogEntry log)
  {
   lock(syncWrite)
   {
    MSQWriter.Send(log);
   }
  }
 }
}

3.凡是要写日志的地方统一用如下方法调用
    //写日志
    LogEntry log = new LogEntry(); 
    log.log_type = "LOG_ASSIGN";
    log.log_point = "ASSIGN";    
    log.service_name = ns.SERVICE_NAME;
    log.log_desc = nsInfo.IP;
    Logger.Write(log);

4.另外,单独建立一个WINDOWS服务,不断侦听消息队列,当有消息写入时,该服务被唤醒,并接收结构化的消息内容,然后写入数据库中
using System;
using System.Messaging;
using System.Net;
using Castle.ActiveRecord;
using Castle.ActiveRecord.Framework;
using Castle.ActiveRecord.Framework.Config;

namespace MessageQueueService
{
 /// <summary>
 /// 接收来自MSMQ的消息,并保存到数据库
 /// </summary>
 public class MessageQueueSer
 {
  public static bool blnStopThread;
  public static string exTemp = string.Empty;

  public MessageQueueSer()
  {
   //
   // TODO: 在此处添加构造函数逻辑
   //
  }

  public static void Init()
  {
   string filename = "CastleAppconfig.config";
   string path = AppDomain.CurrentDomain.SetupInformation.ApplicationBase.ToString() + filename;

   ActiveRecordStarter.Initialize( new XmlConfigurationSource( path ),typeof(NB_LOG));
  }

  public static void Start()
  {
   string queuePath = ".\\Private$\\NBSS_MSQ";
   EnsureQueueExists(queuePath);
   MessageQueue myQueue = new MessageQueue(queuePath);

   myQueue.Formatter = new XmlMessageFormatter(new Type[]
    {typeof(LogEntry)});

   do
   {
    try
    {
     // Receive and format the message.
     Message myMessage =    myQueue.Receive(); //当消息队列空时,线程会挂起
     LogEntry log = (LogEntry)myMessage.Body;

     if(log == null)  return;

     SaveNormalLog(log);//保存到数据库,此处略详细代码
    }
    catch (System.Exception ex)
    { 
     //异常处理
     //……
    }
   }while(blnStopThread==false);

  }

  private static void EnsureQueueExists(string path)
  {
   if(!MessageQueue.Exists(path))
   {
    MessageQueue.Create(path);
   }
  }

  public class LogEntry
  {
   public string log_type = string.Empty;
   public string log_point = string.Empty;
   public long system_id = -1;
   public string system_name = string.Empty;
   public string service_name = string.Empty;
   public string sub_type = string.Empty;
   public string log_desc = string.Empty;
   public int log_level = 0;
  
   public void PrintAll()
   {
    Console.WriteLine("{0}  {1}  {2}  {3} {4} {5} {6} ",log_type,log_point,system_id,
     system_name,service_name,sub_type,log_level);
    Console.WriteLine("{0}\n",log_desc);
   }
  }

  /// <summary>
  /// 获的本地ip
  /// </summary>
  /// <returns></returns>
  public static string GetIp()
  {
   string   strHostIP=""; 
   try
   {
    IPHostEntry   oIPHost=Dns.Resolve(Environment.MachineName);  
    if(oIPHost.AddressList.Length>0)  
     strHostIP=oIPHost.AddressList[0].ToString();
   }
   catch
   {

   }
   return strHostIP;
  }
 }
}

附:日志表结构
 Name         Type           Nullable Default Comments

------------ -------------- -------- ------- -----------------------------------

LOG_ID       NUMBER(20)                      日志标识

LOG_TYPE     VARCHAR2(20)   Y                日志类型( LOG_OP:管理台操作日志 

LOG_ACCESS:系统接入日志 

LOG_ASSIGN:分配日志

LOG_SERVICE:服务过程日志

LOG_GATHER:数据采集日志)

LOG_TIME     DATE           Y                记录日志时间

LOG_DESC     VARCHAR2(1024) Y                日志的操作描述

LOG_POINT    VARCHAR2(50)   Y                接入点(记录抛出异常的部件和函数)

SYSTEM_ID    NUMBER(20)     Y                系统标识

SUB_TYPE     VARCHAR2(20)   Y                该日志自定义关键字

SYSTEM_NAME  VARCHAR2(30)   Y                系统名称

SERVICE_NAME VARCHAR2(30)   Y                服务名称

LOG_LEVEL    NUMBER(1)      Y                日志等级0-9(主要用于异常分类1-9,数值越小等级越高,其它日志统一填0

 

posted on 2007-03-15 13:51  Sunnyflat  阅读(1248)  评论(0编辑  收藏  举报