目前在建立一个接口平台,宿主在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)