之前的文章中,介绍了如何将RabbitMQ以WCF方式进行发布。今天就介绍一下我们产品中如何使用RabbitMQ的!
     
     在Discuz!NT企业版中,提供了对HTTP错误日志的记录功能,这一点对企业版非常重要,另外存储错误日志使用了MongoDB,理由很简单,MongoDB的添加操作飞快,即使数量过亿之后插入速度依旧不减。
     
     在开始正文之前,先说明一下本文的代码分析顺序,即:程序入口==》RabbitMQ客户端===>RabbitMQ服务端。好了,闲话少说,开始正文!     
     
     首先是程序入口,也就是WCF+RabbitMQ客户端实现:     
     
     因为Discuz!NT使用了HttpModule方式来接管HTTP链接请求,而在.NET的HttpModule模板中,可以通过如下方法来接管程序运行时发生的ERROR,如下:

context.Error += new EventHandler(Application_OnError);

而“记录错误日志"的功能入口就在这里:


public void Application_OnError(Object sender, EventArgs e)
    {
        string requestUrl = DNTRequest.GetUrl();
        HttpApplication application = (HttpApplication)sender;
        HttpContext context = application.Context; #if EntLib
        if (RabbitMQConfigs.GetConfig() != null && RabbitMQConfigs.GetConfig().HttpModuleErrLog.Enable)//当开启errlog错误日志记录功能时
        {
            RabbitMQClientHelper.GetHttpModuleErrLogClient().AsyncAddLog(new HttpModuleErrLogData(LogLevel.High, context.Server.GetLastError().ToString()));//异步方式
            //RabbitMQHelper.GetHttpModuleErrLogClient().AddLog(new HttpModuleErrLogData(LogLevel.High, "wrong message infomation!"));//同步方式
            return;
        }
#endif
        ...
     }

当然从代码可以看出,记录日志的工作基本是通过配置文件控制的,即“HttpModuleErrLog.Enable”。
     
     而RabbitMQClientHelper是一个封装类,主要用于反射生成IHttpModuleErrlogClient接口实例,该实例就是“基于WCF发布的RabbitMQ”的客户端访问对象。


/// <summary>
/// RabbitMQ
/// </summary>
public class RabbitMQClientHelper
{
    static IHttpModuleErrlogClient ihttpModuleErrLogClient;     private static object lockHelper = new object();     public static IHttpModuleErrlogClient GetHttpModuleErrLogClient()
    {
        if (ihttpModuleErrLogClient == null)
        {
            lock (lockHelper)
            {
                if (ihttpModuleErrLogClient == null)
                {
                    try
                    {
                        if (RabbitMQConfigs.GetConfig().HttpModuleErrLog.Enable)
                        {
                            ihttpModuleErrLogClient = (IHttpModuleErrlogClient)Activator.CreateInstance(Type.GetType(
                                  "Discuz.EntLib.RabbitMQ.Client.HttpModuleErrLogClient, Discuz.EntLib.RabbitMQ.Client", false, true));
                        }
                    }
                    catch
                    {
                        throw new Exception("请检查 Discuz.EntLib.RabbitMQ.dll 文件是否被放置到了bin目录下!");
                    }
                }
            }
        }
        return ihttpModuleErrLogClient;
    }
}

可以看出它反射的是Discuz.EntLib.RabbitMQ.dll文件的HttpModuleErrLogClient对象(注:使用反射的原因主要是解决企业版代码与普遍版代码在项目引用上的相互依赖),下面就是其接口和具体要求实现:


    /// <summary>
    /// IHttpModuleErrlogClient客户端接口类,用于反射实例化绑定
    /// </summary>
    public interface IHttpModuleErrlogClient
    {
        void AddLog(HttpModuleErrLogData httpModuleErrLogData);         void AsyncAddLog(HttpModuleErrLogData httpModuleErrLogData);
    }
    
    public class HttpModuleErrLogClient : IHttpModuleErrlogClient
    {
        public void AddLog(HttpModuleErrLogData httpModuleErrLogData)
        {
            try
            {
                //((RabbitMQBinding)binding).OneWayOnly = true;
                ChannelFactory<IHttpModuleErrLogService> m_factory = new ChannelFactory<IHttpModuleErrLogService>(GetBinding(), "soap.amqp:///HttpModuleErrLogService");
                m_factory.Open();
                IHttpModuleErrLogService m_client = m_factory.CreateChannel();
                m_client.AddLog(httpModuleErrLogData);
                ((IClientChannel)m_client).Close();
                m_factory.Close();
            }
            catch (System.Exception e)
            {
                string msg = e.Message;
            }
        }         private delegate void delegateAddLog(HttpModuleErrLogData httpModuleErrLogData);         public void AsyncAddLog(HttpModuleErrLogData httpModuleErrLogData)
        {
            delegateAddLog AddLog_aysncallback = new delegateAddLog(AddLog);
            AddLog_aysncallback.BeginInvoke(httpModuleErrLogData, null, null);
        }         public Binding GetBinding()
        {
            return new RabbitMQBinding(RabbitMQConfigs.GetConfig().HttpModuleErrLog.RabbitMQAddress);
        }
    }

可以看出,AddLog方法与上一篇中的客户端内容基本上没什么太大差别,只不过它提供了同步和异步访问两种方式,这样做的目的主要是用户可根据生产环境来灵活配置。 
    
    下面就来看一下RabbitMQ的服务端实现,首先看一下其运行效果,如下图:
    
    
    
    接着看一下启动rabbitmq服务的代码:


public void StartService(System.ServiceModel.Channels.Binding binding)
    {
        m_host = new ServiceHost(typeof(HttpModuleErrLogService), new Uri("soap.amqp:///"));
        //((RabbitMQBinding)binding).OneWayOnly = true;
        m_host.AddServiceEndpoint(typeof(IHttpModuleErrLogService), binding, "HttpModuleErrLogService");
        m_host.Open();
        m_serviceStarted = true;            
    }    

上面代码会添加IHttpModuleErrLogService接口实现类HttpModuleErrLogService 的Endpoint,并启动它,下面就是该接口声明:


    /// <summary>
    /// IHttpModuleErrLogService接口类
    /// </summary>  
    [ServiceContract]
    public interface IHttpModuleErrLogService
    {
        /// <summary>
        /// 添加httpModuleErrLogData日志信息
        /// </summary>
        /// <param name="httpModuleErrLogData"></param>
        [OperationContract]
        void AddLog(HttpModuleErrLogData httpModuleErrLogData);
    }

代码很简单,就是定义了一个添加日志的方法:void AddLog(HttpModuleErrLogData httpModuleErrLogData)
        
    下面就是接口的具体实现,首先是类声明及初始化代码:


[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] //Single - 为所有客户端调用分配一个服务实例。
    public class HttpModuleErrLogService : IHttpModuleErrLogService
    {    
        /// <summary>
        /// 获取HttpModuleErrLogInfo配置文件对象实例
        /// </summary>
        private static HttpModuleErrLogInfo httpModuleErrorLogInfo = RabbitMQConfigs.GetConfig().HttpModuleErrLog;
        /// <summary>
        /// 定时器对象
        /// </summary>
        private static System.Timers.Timer _timer;
        /// <summary>
        /// 定时器的时间
        /// </summary>
        private static int _elapsed = 0;         public static void Initial(System.Windows.Forms.RichTextBox msgBox, int elapsed)
        {
            _msgBox = msgBox;
            _elapsed = elapsed;             //初始定时器
            if (_elapsed > 0)
            {
                _timer = new System.Timers.Timer() { Interval = elapsed * 1000,  Enabled = true, AutoReset = true };            
                _timer.Elapsed += new System.Timers.ElapsedEventHandler(Timer_Elapsed);
                _timer.Start();
            }
        }         /// <summary>
        /// 时间到时执行出队操作
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {    
            Dequeue();    
        }

可以看出,这里使用了静态定时器对象,来进行定时访问队列信息功能(“非同步出队”操作),这样设计的原因主要是为用户提供适合的配置方式,即如果不使用定时器(为0时),则系统会在日志入队后,就立即启动出队(“同步出队”)操作获取日志信息并插入到MongoDB数据库中。
      下面介绍一下入队操作实现:


        /// <summary>
        /// 添加httpModuleErrLogData日志信息
        /// </summary>
        /// <param name="httpModuleErrLogData"></param>
        public void AddLog(HttpModuleErrLogData httpModuleErrLogData)
        {
            Enqueue(httpModuleErrLogData);             if (_elapsed <=0) //如果使用定时器(为0时),则立即执行出队操作
                Dequeue();
        }            /// <summary>
        /// 交换机名称
        /// </summary>
        private const string EXCHANGE = "ex1";
        /// <summary>
        /// 交换方法,更多内容参见:http://melin.javaeye.com/blog/691265
        /// </summary>
        private const string EXCHANGE_TYPE = "direct";
        /// <summary>
        /// 路由key,更多内容参见:http://sunjun041640.blog.163.com/blog/static/256268322010328102029919/
        /// </summary>
        private const string ROUTING_KEY = "m1";         /// <summary>
        /// 日志入队
        /// </summary>
        /// <param name="httpModuleErrLogData"></param>
        public static void Enqueue(HttpModuleErrLogData httpModuleErrLogData)
        {
            Uri uri = new Uri(httpModuleErrorLogInfo.RabbitMQAddress);         
            ConnectionFactory cf = new ConnectionFactory()
            {
                UserName = httpModuleErrorLogInfo.UserName,
                Password = httpModuleErrorLogInfo.PassWord,
                VirtualHost = "dnt_mq",
                RequestedHeartbeat = 0,
                Endpoint = new AmqpTcpEndpoint(uri)
            };
            using (IConnection conn = cf.CreateConnection())
            {
                using (IModel ch = conn.CreateModel())
                {
                    if (EXCHANGE_TYPE != null)
                    {
                        ch.ExchangeDeclare(EXCHANGE, EXCHANGE_TYPE);//,true,true,false,false, true,null);
                        ch.QueueDeclare(httpModuleErrorLogInfo.QueueName, true);//true, true, true, false, false, null);
                        ch.QueueBind(httpModuleErrorLogInfo.QueueName, EXCHANGE, ROUTING_KEY, false, null);
                    }
                    IMapMessageBuilder b = new MapMessageBuilder(ch);
                    IDictionary target = b.Headers;
                    target["header"] = "HttpErrLog";
                    IDictionary targetBody = b.Body;
                    targetBody["body"] = SerializationHelper.Serialize(httpModuleErrLogData);
                    ((IBasicProperties)b.GetContentHeader()).DeliveryMode = 2;//persistMode                   
                    ch.BasicPublish(EXCHANGE, ROUTING_KEY,
                                               (IBasicProperties)b.GetContentHeader(),
                                               b.GetContentBody());
                }
            }
        }

代码很简单,主要构造rabbitmq链接(ConnectionFactory)并初始化相应参数如用户名,密码,ROUTING_KEY等。
        
        然后将传入的日志对象序列化成字符串对象,赋值给targetBody["body"],这样做主要是因为我没找到更好的方法来赋值(之前尝试直接绑定httpModuleErrLogData到targetBody["body"],但在出队操作中找不到合适方法将httpModuleErrLogData对象解析出来)。
        
        下面就是出队操作:


        /// <summary>
        /// 日志出队
        /// </summary>
        public static void Dequeue()
        {       
            string serverAddress = httpModuleErrorLogInfo.RabbitMQAddress.Replace("amqp://", "").TrimEnd('/'); //"10.0.4.85:5672";
            ConnectionFactory cf = new ConnectionFactory()
            {
                UserName = httpModuleErrorLogInfo.UserName,
                Password = httpModuleErrorLogInfo.PassWord,
                VirtualHost = "dnt_mq",
                RequestedHeartbeat = 0,
                Address = serverAddress
            };
      
            using (IConnection conn = cf.CreateConnection())
            {
                using (IModel ch = conn.CreateModel())
                {
                    while (true)
                    {
                        BasicGetResult res = ch.BasicGet(httpModuleErrorLogInfo.QueueName, false);
                        if (res != null)
                        {
                            try
                            {
                                string objstr = System.Text.UTF8Encoding.UTF8.GetString(res.Body).Replace("\0\0\0body\0\n", "");//去掉头部信息
                                object obj = SerializationHelper.DeSerialize(typeof(HttpModuleErrLogData), objstr);
                                HttpModuleErrLogData httpModuleErrLogData = obj as HttpModuleErrLogData;
                                if (httpModuleErrLogData != null)
                                {
                                    MongoDbHelper.Insert(new Mongo(httpModuleErrorLogInfo.MongoDB), "dnt_httpmoduleerrlog", LoadAttachment(httpModuleErrLogData));
                                    _msgBox.BeginInvoke(new ShowMsg(SetMsgRichBox), "\r发生时间:" + httpModuleErrLogData.TimeStamp + "\r错误等级:" + httpModuleErrLogData.Level + "\r详细信息:" + httpModuleErrLogData.Message);
                                    ch.BasicAck(res.DeliveryTag, false);
                                }
                            }
                            catch { }
                        }
                        else
                            break;
                    }
                }
            }           
        } 

出队操作也是先实例化链接到rabbitmq 的实例,并循环使用BasicGet方法来单条获取队列信息,并最终将res.Body的数据序列化成HttpModuleErrLogData对象,并最终插入到mongodb数据库中。同时将获取的队列消息显示出来:

    _msgBox.BeginInvoke(new ShowMsg(SetMsgRichBox), "\r发生时间:" + httpModuleErrLogData.TimeStamp + "\r错误等级:" + httpModuleErrLogData.Level + "\r详细信息:" + httpModuleErrLogData.Message);

这里使用异步方式显示出队的日志信息,其声明的delegate 方法“ShowMsg”如下:


        /// <summary>
        /// 声明委托
        /// </summary>
        /// <param name="message"></param>
        public delegate void ShowMsg(string message);
        /// <summary>
        /// 绑定到上面delegate的方法
        /// </summary>
        /// <param name="outPut"></param>
        public static void SetMsgRichBox(string outPut)
        {
            _msgBox.Text += "\r==================================\r下列错误信息出队时间=>" + DateTime.Now + outPut + "\r";
        }

同时使用LoadAttachment方法来实现HttpModuleErrLogData到mongodb的Document类型的转换:


        /// <summary>
        /// 将HttpModuleErrLogData转换成Document类型
        /// </summary>
        /// <param name="httpModuleErrLogData"></param>
        /// <returns></returns>
        public static Document LoadAttachment(HttpModuleErrLogData httpModuleErrLogData)
        {
           Document doc = new Document();
            doc["_id"] = httpModuleErrLogData.Oid;
            doc["level"] = httpModuleErrLogData.Level;
            doc["message"] = httpModuleErrLogData.Message;
            doc["timestamp"] = httpModuleErrLogData.TimeStamp;
            return doc;
        }       

到这里,主要的功能介绍就差不多了。当然本文所阐述的只是一个原型,相信会随着对rabbitmq的理解深入而不断完善,感兴趣的朋友欢迎讨论交流,以纠正我认识上的偏差,呵呵。

NET下RabbitMQ实践[实战篇]的更多相关文章

  1. NET下RabbitMQ实践[配置篇]

    这个系列目前计划写四篇,分别是配置,示例,WCF发布,实战.当然不排除加餐情况.  介绍: rabbitMQ是一个在AMQP协议标准基础上完整的,可服用的企业消息系统.他遵循Mozilla Publi ...

  2. NET下RabbitMQ实践[示例篇]

    在上一篇文章中,介绍了在window环境下安装erlang,rabbitmq-server,以免配置用户,权限,虚拟机等内容.         今天将会介绍如果使用rabbitmq进行简单的消息入队, ...

  3. NET下RabbitMQ实践[WCF发布篇]

    在之前的两篇文章中,主要介绍了RabbitMQ环境配置,简单示例的编写.今天将会介绍如何使用WCF将RabbitMQ列队以服务的方式进行发布.          注:因为RabbitMQ的官方.net ...

  4. 实践详细篇-Windows下使用VS2015编译的Caffe训练mnist数据集

    上一篇记录的是学习caffe前的环境准备以及如何创建好自己需要的caffe版本.这一篇记录的是如何使用编译好的caffe做训练mnist数据集,步骤编号延用上一篇 <实践详细篇-Windows下 ...

  5. Android为TV端助力 转载:Android绘图Canvas十八般武器之Shader详解及实战篇(下)

    LinearGradient 线性渐变渲染器 LinearGradient中文翻译过来就是线性渐变的意思.线性渐变通俗来讲就是给起点设置一个颜色值如#faf84d,终点设置一个颜色值如#CC423C, ...

  6. 实践详细篇-Windows下使用Caffe训练自己的Caffemodel数据集并进行图像分类

    三:使用Caffe训练Caffemodel并进行图像分类 上一篇记录的是如何使用别人训练好的MNIST数据做训练测试.上手操作一边后大致了解了配置文件属性.这一篇记录如何使用自己准备的图片素材做图像分 ...

  7. caffe框架下目标检测——faster-rcnn实战篇操作

    原有模型 1.下载fasrer-rcnn源代码并安装 git clone --recursive https://github.com/rbgirshick/py-faster-rcnn.git 1) ...

  8. 【微信小程序】转载:微信小程序实战篇-下拉刷新与加载更多

    下拉刷新 实现下拉刷新目前能想到的有两种方式 1. 调用系统的API,系统有提供下拉刷新的API接口 当然,你可以直接在全局变量app.json的window里面配置上面这个属性,这样整个项目都允许下 ...

  9. 算法---FaceNet在Tf下的实战篇

    FaceNet---Tensorflow下的下的实战篇 @WP20190225 ===============目录=============== 一.FaceNet算法简介 二.FaceNet配置与使 ...

随机推荐

  1. make问题:make[1] entering directory

    执行make distclean命令.

  2. go语言实现的目录共享程序

    其实程序很小,只不过是想写点东西了.后天晚上要回学校考试了,转眼已经出来了69天了,2个月多一点.工资加上老妈赞助的钱,不知道能不能买台电脑,作为程序员一直用着i3-3217u实在难受.回去找同学拷点 ...

  3. easy ui datagrid 增,删,改,查等基本操作

    如下图: ①列表信息图 ②添加信息图 ③修改信息图 html代码: <%@ Page Title="" Language="C#" MasterPageF ...

  4. maven3.1安装及配置

    1.首先下载maven3,并安装 2.环境变量配置与JAVA_HOME的配置一样 3.打开MyEclipse preferences>>Maven4MyEclipse>>Mav ...

  5. L​i​n​u​x​环​境​变​量​的​设​置​和​查​看​方​法

    L​i​n​u​x​环​境​变​量​的​设​置​和​查​看​方​法 1. 显示环境变量HOME [root@AY1404171530212980a0Z ~]# echo $HOME /root 2. ...

  6. c++ 发布动态.so

    原文地址 代码改变世界 Posts - 105, Articles - 0, Comments - 1561 Cnblogs Dashboard Logout Home Contact Gallery ...

  7. 团体程序设计天梯赛-练习集L1-015. 跟奥巴马一起画方块

    L1-015. 跟奥巴马一起画方块 时间限制 400 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 陈越 美国总统奥巴马不仅呼吁所有人都学习编程,甚至 ...

  8. DevSecOps 实施篇!系列(二)

    想在自己公司建立 DevSecOps 计划?没问题,企业规模无论大小,都可轻松实现.这里有5个基本的 DevSecOps 原则可以帮助你启动.当然,如果你对 DevSecOps 还不太熟悉,不妨先看看 ...

  9. HDU2110+母函数

    /* 母函数(生成函数) 题意: 有n种资产,每种资产num份,每份有val的价值 问取出总价值的1/3有多少种方案 */ #include<stdio.h> #include<st ...

  10. linux 模拟延时和丢包

    这是 RHCA 中的一个 BDP 的测试,这也是公司很常用的一种延时和丢包的模拟,现在分享给大家. 我们做的应用软件,还有测试 TCP/UDP  对比,测试 BDP 对 TCP/IP 的影响时,我们都 ...