NoSQL初探之人人都爱Redis:(3)使用Redis作为消息队列服务场景应用案例

浏览 : 12035 次 Mon, 01 Dec 2014 19:26:03 GMT
复制代码
<configSections>
    <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=4.4.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
    <!-- Log4Net配置声明 -->
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net"/>
  </configSections>
  <!-- Log4Net具体配置 -->
  <log4net>
    <!-- OFF, FATAL, ERROR, WARN, INFO, DEBUG, ALL -->
    <!-- Set root logger level to ERROR and its appenders -->
    <root>
      <level value="ALL"/>
      <appender-ref ref="SysAppender"/>
    </root>
    <!-- Print only messages of level DEBUG or above in the packages -->
    <logger name="WebLogger">
      <level value="DEBUG"/>
    </logger>
    <appender name="SysAppender" type="log4net.Appender.RollingFileAppender,log4net" >
      <param name="File" value="App_Data/" />
      <param name="AppendToFile" value="true" />
      <param name="RollingStyle" value="Date" />
      <param name="DatePattern" value="&quot;Logs_&quot;yyyyMMdd&quot;.txt&quot;" />
      <param name="StaticLogFileName" value="false" />
      <layout type="log4net.Layout.PatternLayout,log4net">
        <!--<param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n" />-->
        <param name="ConversionPattern" value="记录时间:%date %n线程ID: [%thread] %n日志级别:%-5level %n出错类:%logger property: [%property{NDC}] - %n错误描述:%message%newline %n" />
        <param name="Header" value="-------------------------------------------------------header-----------------------------------------------------------&#13;&#10;" />
        <param name="Footer" value="-------------------------------------------------------footer-----------------------------------------------------------&#13;&#10;" />
      </layout>
    </appender>
    <appender name="consoleApp" type="log4net.Appender.ConsoleAppender,log4net">
      <layout type="log4net.Layout.PatternLayout,log4net">
        <param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n" />
      </layout>
    </appender>
  </log4net>
复制代码

PS:Log4Net是用来记录日志的一个常用组件(Log4J的移植版本),可以将程序运行过程中的信息输出到一些地方(文件、数据库、EventLog等)。由于Log4Net不是本篇博文介绍的重点,所以对Log4Net不熟悉的朋友,请在博客园首页搜索:Log4Net,浏览其详细的介绍。

  其次,在App_Start文件夹中添加一个类,取名为LogConfig,定义一个静态方法:RegisterLog4NetConfigure,具体代码只有一行,实现了Log4Net配置的初始化操作。

复制代码
    public class LogConfig
    {
        public static void RegisterLog4NetConfigure()
        {
            //获取Log4Net配置信息(配置信息定义在Web.config文件中)
            log4net.Config.XmlConfigurator.Configure();
        }
    }
复制代码

  最后,在Global.asax中的Application_Start方法中添加一行代码,注册Log4Net的配置:

复制代码
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);


            //自定义事件注册
            MessageQueueConfig.RegisterExceptionLogQueue();
            LogConfig.RegisterLog4NetConfigure();
        }
复制代码

  (5)第五步,改写MessageQueueConfig中的RegisterExceptionLogQueue方法。这里就不再需要从预置类型Queue中取任务了,而是Redis中取出任务出队进行相应处理。这里,我们使用了Log4Net进行异常日志的记录工作。PS:注意在代码顶部添加对log4net的引用:using log4net;

复制代码
      public static void RegisterExceptionLogQueue()
        {
            //通过线程池开启线程,不停地从队列中获取异常信息并将其写入日志文件
            ThreadPool.QueueUserWorkItem(o =>
            {
                while (true)
                {
                    try
                    {
                        if (MyExceptionFilterAttribute.redisClient.GetListCount("ExceptionLog") > 0)
                        {
                            //从队列中出队,获取异常对象
                            string errorMsg = MyExceptionFilterAttribute.redisClient.DequeueItemFromList("ExceptionLog");
                            if (!string.IsNullOrEmpty(errorMsg))
                            {
                                //使用Log4Net写入异常日志
                                ILog logger = LogManager.GetLogger("Log");
                                logger.Error(errorMsg);
                            }
                        }
                        else
                        {
                            Thread.Sleep(1000); //为避免CPU空转,在队列为空时休息1秒
                        }
                    }
                    catch (Exception ex)
                    {
                        MyExceptionFilterAttribute.redisClient.EnqueueItemOnList("ExceptionLog", ex.ToString());
                    }
                }
            });
        }
复制代码

   (6)最后一步,调试验证是否能正常写入App_Data文件的日志中,发现写入的异常日志如下,格式好看,信息详细,圆满完成了我们的目的。

四、小结

  使用消息队列将调用异步化,可以改善网站系统的性能:消息队列具有很好的削峰作用,即通过异步处理,将短时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。在电商网站的促销活动中,合理使用消息队列,可以有效地抵御促销活动刚开始大量涌入的订单对系统造成的冲击。本文使用消息队列的思想,借助Redis+Log4Net完成了一个超简单的异常日志队列的应用案例,可以有效地解决在多线程操作中对日志文件的并发操作带来的一些问题。同样地,借助消息队列的思想,我们也可以完成对数据库的高并发的消息队列方案。所以,麻雀虽小五脏俱全,理解好了这个案例,相信对我们这些菜鸟码农是有所裨益的。同样,也请大牛们一笑而过,多多指教菜鸟们一步一步地提高,谢谢了!后边,我们会探索一下Redis的集群、主从复制,以及在VMWare中建立几台虚拟机来构建主从结构,并使用Redis记录网站中重要的Session会话对象,或者是电商项目中常见的商品类目信息等。但是,本人资质尚浅,并且都是一些初探性质的学习,如有错误和不当,还请各位园友多多指教!

参考文献

(1)传智播客.Net学院王承伟,数据优化技术之Redis公开课,http://bbs.itcast.cn/thread-26525-1-1.html

(2)Sanfilippo/贾隆译,《几点建议,让Redis在你的系统中发挥更大作用》,http://database.51cto.com/art/201107/276333.htm

(3)NoSQLFan,《Redis作者谈Redis应用场景》,http://blog.nosqlfan.com/html/2235.html

(4)善心如水,《C#中使用Log4Net记录日志》,http://www.cnblogs.com/wangsaiming/archive/2013/01/11/2856253.html

(5)逆心,《ServiceStack.Redis之IRedisClient》,http://www.cnblogs.com/kissdodog/p/3572084.html

(6)李智慧,《大型网站技术架构-核心原理与案例分析》,http://item.jd.com/11322972.html

附件下载

(1)版本1:使用预置类型的异常日志队列Demo,http://pan.baidu.com/s/1nt5G7Fj

(2)版本2:使用Redis+Log4Net的异常日志队列Demo,http://pan.baidu.com/s/1i3gMnnJ