DDD Nlog、工作单元线程内唯一

从零开始用 .NET Core 写一个领域模型的框架

每篇文章都会打一个对应的 tag

Github 仓库地址

这一版代码中

引入 Nlog

之前版本是通过 ThreadLocal 实现工作单元线程内唯一,但是和 Task 异步方法配套使用的时候经常出现问题,非常的不稳定。这版使用 IHttpContextAccessor 实现工作单元线程内唯一

 

引入Nlog

------Program.cs

using Autofac.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using NLog.Web;

namespace Core2022.API
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>()
                    .ConfigureLogging(logging =>
                    {
                        logging.ClearProviders();
#if DEBUG
                        logging.SetMinimumLevel(LogLevel.Debug);
#else
                        logging.SetMinimumLevel(LogLevel.Trace);
#endif
                    }).UseNLog();
                })
                //注册Autofac
                .UseServiceProviderFactory(new AutofacServiceProviderFactory());
    }
}
------------LogHelper.cs

using NLog;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

namespace Core2022.Framework.Nlog
{
    public static class LogHelper
    {
        private static readonly Logger logger;

        static LogHelper()
        {
            logger = LogManager.GetCurrentClassLogger();
        }

        public static void Error(string title, Exception exception, [CallerMemberName] string methodName = "")
        {
            logger.Error(exception, title.AppendDic(new Dictionary<string, string>
            {
                {"method",methodName}
            }));
        }

        public static void Error(string title, string msg, [CallerMemberName] string methodName = "")
        {
            logger.Error(title + msg.AppendDic(new Dictionary<string, string>
            {
                {"method",methodName}
            }));
        }

        /// <summary>
        ///  调用外部接口 出错 统一用Warn
        /// </summary>
        /// <param name="title"></param>
        /// <param name="exception"></param>
        /// <param name="methodName"></param>
        public static void Warn(string title, Exception exception, [CallerMemberName] string methodName = "")
        {
            logger.Warn(exception, title.AppendDic(new Dictionary<string, string>
            {
                {"method",methodName}
            }));

        }

        /// <summary>
        /// 调用外部接口 出错 统一用Warn
        /// </summary>
        /// <param name="title"></param>
        /// <param name="msg"></param>
        /// <param name="methodName"></param>
        public static void Warn(string title, string msg, [CallerMemberName] string methodName = "")
        {
            logger.Warn(title + msg.AppendDic(new Dictionary<string, string>
            {
                {"method",methodName}
            }));
        }

        public static void Warn(string title, string msg, Exception exception, [CallerMemberName] string methodName = "")
        {
            logger.Warn(exception, title + msg.AppendDic(new Dictionary<string, string>
            {
                {"method",methodName}
            }));

        }
        public static void Warn(string title, string msg, Dictionary<string, string> addInfo, [CallerMemberName] string methodName = "")
        {
            if (addInfo != null)
            {
                addInfo.Add("method", methodName);
                logger.Warn(title + msg.AppendDic(addInfo));
            }
            else
            {
                logger.Warn(title + msg.AppendDic(new Dictionary<string, string>
                {
                    {"method",methodName}
                }));
            }
        }
        public static void Warn(string title, Exception ex, Dictionary<string, string> addInfo, [CallerMemberName] string methodName = "")
        {
            if (addInfo != null)
            {
                addInfo.Add("method", methodName);
                logger.Warn(ex, title.AppendDic(addInfo));
            }
            else
            {
                logger.Warn(ex, title.AppendDic(new Dictionary<string, string>
                {
                    {"method",methodName}
                }));
            }
        }
        /// <summary>
        /// 内部接口出错 或者 打Debug日志 用Info
        /// </summary>
        /// <param name="title"></param>
        /// <param name="message"></param>
        /// <param name="methodName"></param>
        /// <param name="addInfo"></param>
        public static void Info(string title, string message, Dictionary<string, string> addInfo = null, [CallerMemberName] string methodName = "")
        {
            if (addInfo != null)
            {
                addInfo.Add("method", methodName);
                logger.Info(title + message.AppendDic(addInfo));
            }
            else
            {
                logger.Info(title + message.AppendDic(new Dictionary<string, string>
            {
                {"method",methodName}
            }));
            }
        }

        public static void Debug(string title, string message = "", Dictionary<string, string> addInfo = null, [CallerMemberName] string methodName = "")
        {
            if (addInfo != null)
            {
                addInfo.Add("method", methodName);
                logger.Debug(title + message.AppendDic(addInfo));
            }
            else
            {
                logger.Debug(title + message.AppendDic(new Dictionary<string, string>
                {
                    {"method",methodName}
                }));
            }
        }

        /// <summary>
        /// 内部接口出错 或者 打Debug日志 用Info
        /// </summary>
        /// <param name="title"></param>
        /// <param name="exception"></param>
        /// <param name="methodName"></param>
        public static void Info(string title, Exception exception, [CallerMemberName] string methodName = "")
        {
            logger.Info(exception, title.AppendDic(new Dictionary<string, string>
            {
                {"method",methodName}
            }));
        }

        public static void Debug(string title, Exception exception, [CallerMemberName] string methodName = "")
        {
            logger.Debug(exception, title.AppendDic(new Dictionary<string, string>
            {
                {"method",methodName}
            }));
        }

    }
}
-------------nlog.config

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <!-- enable asp.net core layout renderers -->
  <extensions>
    <add assembly="NLog.Web.AspNetCore"/>
  </extensions>

  <targets>
    <target name="asyncFile" xsi:type="AsyncWrapper">
      <target name="log_file" xsi:type="File"
              fileName="${basedir}/Logs/${shortdate}/${logger}-${level}-${shortdate}.txt"
              layout="${longdate} | ${message} ${onexception:${exception:format=message} ${newline} ${stacktrace} ${newline}"
              archiveFileName="${basedir}/archives/${logger}-${level}-${shortdate}-{#####}.txt"
              archiveAboveSize="102400"
              archiveNumbering="Sequence"
              concurrentWrites="true"
              keepFileOpen="false" />
      <target xsi:type="File" name="ownFile-web" fileName="${basedir}/Logs/nlog-all-${shortdate}.log"
              layout="${longdate}|${event-properties:item=EventId_Id}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}" />
    </target>
  </targets>

  <rules>
    <logger name="Microsoft.*" minlevel="Info" final="true" />
    <logger name="*" minlevel="Info" writeTo="asyncFile" />
  </rules>
</nlog>

项目内统一调用 LogHelper 中的方法

通过配置文件控制日志输出的级别

 

 

工作单元线程内唯一

由于项目中使用的都是 异步方法,导致从 ThreadLocal 获取工作单元的时候经常出现问题

---------------------HttpContext.cs

using Autofac;
using Microsoft.AspNetCore.Http;

namespace Core2022.Framework.Web
{
    public static class HttpContext
    {
        /// <summary>
        /// Startup.Configure 中对该属性初始化
        /// </summary>
        public static ILifetimeScope ServiceProvider;

        public static Microsoft.AspNetCore.Http.HttpContext Current
        {
            get
            {
                IHttpContextAccessor factory = ServiceProvider.Resolve(typeof(IHttpContextAccessor)) as IHttpContextAccessor;
                Microsoft.AspNetCore.Http.HttpContext context = factory?.HttpContext;
                return context;
            }
        }
    }
}

 

--------------------------------AppUnitOfWorkFactory.cs
using Autofac;
using Core2022.Framework.Web;

namespace Core2022.Framework.UnitOfWork
{
    /// <summary>
    /// 创建工作单元
    /// 保证线程内唯一
    /// </summary>
    public class AppUnitOfWorkFactory
    {
        public static IAppUnitOfWork GetAppUnitOfWorkRepository()
        {
            IAppUnitOfWork appUnitOfWork = HttpContext.Current.Items["AppUnitOfWork"] as IAppUnitOfWork;
            if (appUnitOfWork == null)
            {
                appUnitOfWork = CreateUnitOfWork();
                HttpContext.Current.Items["AppUnitOfWork"] = appUnitOfWork;
            }
            return appUnitOfWork;
        }

        private static IAppUnitOfWork CreateUnitOfWork()
        {
            return Global.AutofacContainer.Resolve<IAppUnitOfWork>();
        }

        #region 只读
        public static IReadUnitOfWork GetReadUnitOfWorkRepository()
        {
            IReadUnitOfWork readUnitOfWork = HttpContext.Current.Items["ReadUnitOfWork"] as IReadUnitOfWork;
            if (readUnitOfWork == null)
            {
                readUnitOfWork = CreateReadUnitOfWork();
                HttpContext.Current.Items["ReadUnitOfWork"] = readUnitOfWork;
            }
            return readUnitOfWork;
        }

        private static IReadUnitOfWork CreateReadUnitOfWork()
        {
            return Global.AutofacContainer.Resolve<IReadUnitOfWork>();
        }
        #endregion
    }
}

 

--------------------------------Startup.Configure 方法

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
        ...
        ...
    HttpContext.ServiceProvider = app.ApplicationServices.GetAutofacRoot();
}

最后进行性能测试

 

 

 

 

 

 

 

 

从性能测试的结果和数据库中的数据综合来看,还行

10 个虚拟用户,压测30秒

每秒可以执行最少400次测试脚本

每次执行脚本最少要调用一个接口,最多要调用五个接口

每次测试完查询数据库最少能新增1W条数据

 

posted @ 2022-03-08 18:29  乔安生  阅读(73)  评论(0编辑  收藏  举报