基于.NetCore3.1系列 —— 日志记录之日志核心要素揭秘

前言

在上一篇中,我们已经了解了内置系统的默认配置和自定义配置的方式,在学习了配置的基础上,我们进一步的对日志在程序中是如何使用的深入了解学习。所以在这一篇中,主要是对日志记录的核心机制进行学习说明。

说明

在上一篇中,我们留下了两个问题

  1. 日志记录的输出可以在哪里查看?而又由什么实现决定的呢?

  2. 如何管理输出不同的日志呢?都有哪些方式呢?

第一个问题:在官方的实现有:Console 、Debug 、EventSource 、EventLog 、TraceSource 、Azure App Service,还有一些第三方实现,当然了我们自己也是可以实现的。是由ILoggerProvider接口来决定实现的。

第二个问题:由 log Level、EventId、Logger Provider、Log filtering、Log category、Log scopes 合作解决。

由上面的问题可以发现,我们可以实现多种不同的输出目标方式来实现写日志记录,但是又如何控制在写日志这个操作不变的情况下,实现不同的输入目标,这个时候我们就会想到,可以通过抽象的方式,将写日志这个操作动作抽象出来,而输出目标依赖这个动作实现具体的操作。所以当我们调用写日志操作方法的时候,由此依次调用对应的具体实现方法,把日志写到具体的目标上。

这个过程具体是怎么实现的呢?我们接着往下看。

开始

其实在学习之前,我们应该都已经了解.net core框架有一个重要的特征就是依赖注入,通过在应用启动时候,将各种定义好的实现类型放入到一个集合容器中,通过在运行时,将从集合容器中取出放入对应的类型中。

日志记录的的实现方式也离不开这个。下面让我们一起来看看。

日志记录器工厂

   1. ILoggerFactory接口

public interface ILoggerFactory : IDisposable
{ILogger CreateLogger(string categoryName);void AddProvider(ILoggerProvider provider);
}

ILoggerFactory是日志记录器的工厂接口类,用于配置日志记录系统并创建Logger实例的类,默认实现两个接口方法为,通过CreateLogger()方法来创建ILogger实例,(其中参数categoryName是一个日志类别,用于调用Logger所在类的全名,类别指明日志消息是谁写入的,一般我们将日志所属的的组件、服务或者消息类型名称作为日志类别。)  而AddProvider()添加日志记录提供程序,向日志系统注册添加一个ILoggerProvider。工厂接口类的默认实现类为LoggerFactory, 我们继续往下看:

2. LoggerFactory实现

ILoggerFactory 的默认实现是 LoggerFactory ,在构造函数中,如下:

    public class LoggerFactory : ILoggerFactory{private static readonly LoggerRuleSelector RuleSelector = new LoggerRuleSelector();private readonly Dictionary<string, Logger> _loggers = new Dictionary<string, Logger>(StringComparer.Ordinal);private readonly List<ProviderRegistration> _providerRegistrations = new List<ProviderRegistration>();private readonly object _sync = new object();private volatile bool _disposed;private IDisposable _changeTokenRegistration;private LoggerFilterOptions _filterOptions;private LoggerExternalScopeProvider _scopeProvider;public LoggerFactory() : this(Enumerable.Empty<ILoggerProvider>()){}public LoggerFactory(IEnumerable<ILoggerProvider> providers) : this(providers, new StaticFilterOptionsMonitor(new LoggerFilterOptions())){}public LoggerFactory(IEnumerable<ILoggerProvider> providers, LoggerFilterOptions filterOptions) : this(providers, new StaticFilterOptionsMonitor(filterOptions)){}public LoggerFactory(IEnumerable<ILoggerProvider> providers, IOptionsMonitor<LoggerFilterOptions> filterOption){foreach (var provider in providers){AddProviderRegistration(provider, dispose: false);}_changeTokenRegistration = filterOption.OnChange(RefreshFilters);RefreshFilters(filterOption.CurrentValue);}private void AddProviderRegistration(ILoggerProvider provider, bool dispose){_providerRegistrations.Add(new ProviderRegistration{Provider = provider,ShouldDispose = dispose});if (provider is ISupportExternalScope supportsExternalScope){if (_scopeProvider == null){_scopeProvider = new LoggerExternalScopeProvider();}supportsExternalScope.SetScopeProvider(_scopeProvider);}}}

LoggerFactory 中 的构造函数中可以发现,通过注入的方式获取到ILoggerProvider(这个在下文中会说明),并调用AddProviderRegistration方法添加注册程序,将ILoggerProvider保存到ProviderRegistration集合中。

AddProviderRegistration 方法:

这是一个日志程序提供器,将ILoggerProvider保存到ProviderRegistration集合中。当日志提供器实现 ISupportExternalScope 接口将单例 LoggerExternalScopeProvider 保存到 provider._scopeProvider 中。

ProviderRegistration集合:

private struct ProviderRegistration
{public ILoggerProvider Provider;public bool ShouldDispose;
}

其中的 ShouldDispose 字段标识在在LoggerFactory生命周期结束之后,该ILoggerProvider是否需要释放。虽然在系统中LoggerFactory为单例模式,但是其提供了一个静态方法生成一个可释放的DisposingLoggerFactory

LoggerFactory 实现默认的接口方法CreateLogger(),AddProvider()

查看源码如下:

CreateLogger

创建ILogger实例,CreateLogger() 源码如下:

    public class LoggerFactory : ILoggerFactory{private readonly Dictionary<string, Logger> _loggers = new Dictionary<string, Logger>(StringComparer.Ordinal);private readonly List<ProviderRegistration> _providerRegistrations = new List<ProviderRegistration>();private struct ProviderRegistration{public ILoggerProvider Provider;public bool ShouldDispose;}public ILogger CreateLogger(string categoryName){if (CheckDisposed()){throw new ObjectDisposedException(nameof(LoggerFactory));}lock (_sync){if (!_loggers.TryGetValue(categoryName, out var logger)){logger = new Logger{Loggers = CreateLoggers(categoryName),};(logger.MessageLoggers, logger.ScopeLoggers) = ApplyFilters(logger.Loggers);_loggers[categoryName] = logger;}return logger;}}private LoggerInformation[] CreateLoggers(string categoryName){var loggers = new LoggerInformation[_providerRegistrations.Count];for (var i = 0; i < _providerRegistrations.Count; i++){loggers[i] = new LoggerInformation(_providerRegistrations[i].Provider, categoryName);}return loggers;}}

从源码可以看出,CreateLogger方法中,会检测资源是否被释放,在方法中,根据内部定义的字典集合Dictionary<string, Logger> _loggers,判断字典中是否存在对应的Logger属性对象,如果不存在,会调用CreateLoggers方法根据之前注册的的所有ILoggerProvider所创建出来 ProviderRegistration 集合来实现创建Logger属性集合(根据日志类别生成了对应实际的日志写入类FileLoggerConsoleLogger等),并通过字典集合的方式保存categoryName和对应的Logger

创建 Logger 需要的 LoggerInformation[]

internal readonly struct LoggerInformation
{
public LoggerInformation(ILoggerProvider provider, string category) : this()
{
ProviderType = provider.GetType();
Logger = provider.CreateLogger(category);
Category = category;
ExternalScope = provider is ISupportExternalScope;
}public ILogger Logger { get; }public string Category { get; }public Type ProviderType { get; }public bool ExternalScope { get; }
}

根据注册的ILoggerProvider,创建ILogger 其中的字段说明:

Logger :具体日志类别写入途径实现类

Category :日志类别名称

ProviderType :日志提供器Type

ExternalScope  :是否支持 ExternalScope

继续看CreateLogger方法,在创建Logger之后,还调用了ApplyFilters方法:

        private (MessageLogger[] MessageLoggers, ScopeLogger[] ScopeLoggers) ApplyFilters(LoggerInformation[] loggers){var messageLoggers = new List<MessageLogger>();var scopeLoggers = _filterOptions.CaptureScopes ? new List<ScopeLogger>() : null;foreach (var loggerInformation in loggers){RuleSelector.Select(_filterOptions,loggerInformation.ProviderType,loggerInformation.Category,out var minLevel,out var filter);if (minLevel != null && minLevel > LogLevel.Critical){continue;}messageLoggers.Add(new MessageLogger(loggerInformation.Logger, loggerInformation.Category, loggerInformation.ProviderType.FullName, minLevel, filter));if (!loggerInformation.ExternalScope){scopeLoggers?.Add(new ScopeLogger(logger: loggerInformation.Logger, externalScopeProvider: null));}}if (_scopeProvider != null){scopeLoggers?.Add(new ScopeLogger(logger: null, externalScopeProvider: _scopeProvider));}return (messageLoggers.ToArray(), scopeLoggers?.ToArray());}

由源码可以看出,

MessageLogger[] 集合取值:

在获取LoggerInformation[]后进行传参,进行遍历,根据RuleSelector过滤器,从配置文件中读取对应的日志级别,过滤器会返回获取最低级别和对应的一条过滤规则,如果配置文件中没有对应的配置,默认取全局最低级别(MinLevel),如果读取到的日志级别大于LogLevel.Critical,则将其加入MessageLogger[]

过滤器的规则:

  1. 选择当前记录器类型的规则,如果没有,请选择未指定记录器类型的规则

  2. 选择最长匹配类别的规则

  3. 如果没有与类别匹配的内容,则采用所有没有类别的规则

  4. 如果只有一条规则,则使用它的级别和过滤器

  5. 如果有多个规则,请选择使用最后一条。

  6. 如果没有适用的规则,请使用全局最低级别

通过MessageLogger[]添加消息日志集合

internal readonly struct MessageLogger
{public MessageLogger(ILogger logger, string category, string providerTypeFullName, LogLevel? minLevel, Func<string, string, LogLevel, bool> filter){Logger = logger;Category = category;ProviderTypeFullName = providerTypeFullName;MinLevel = minLevel;Filter = filter;}public ILogger Logger { get; }public string Category { get; }private string ProviderTypeFullName { get; }public LogLevel? MinLevel { get; }public Func<string, string, LogLevel, bool> Filter { get; }public bool IsEnabled(LogLevel level){if (MinLevel != null && level < MinLevel){return false;}if (Filter != null){return Filter(ProviderTypeFullName, Category, level);}return true;}
}internal readonly struct ScopeLogger
{public ScopeLogger(ILogger logger, IExternalScopeProvider externalScopeProvider){Logger = logger;ExternalScopeProvider = externalScopeProvider;}public ILogger Logger { get; }public IExternalScopeProvider ExternalScopeProvider { get; }public IDisposable CreateScope<TState>(TState state){if (ExternalScopeProvider != null){return ExternalScopeProvider.Push(state);}return Logger.BeginScope<TState>(state);}
}

MessageLogger[]中带有MinLevel属性和Filter委托两种过滤配置,而这两种配置的来源,在上一章中可以看到,分别是从配置文件(AddConfiguration)和直接使用委托(AddFilter)来进行配置的。

再由上面的IsEnabled方法可以看出,会先使用 MinLevel 过滤,再使用 Filter 进行过滤。所以这两者存在优先级。

ScopeLogger[ ] 取值 :

如果 ILoggerProvider实现了ISupportExternalScope接口,那么使用LoggerExternalScopeProvider作为Scope功能的实现。反之,使用ILogger作为其Scope功能的实现。

LoggerExternalScopeProvider  :

  • 通过 Scope 组成了一个单向链表,每次 beginscope 向链表末端增加一个新的元素,Dispose的时候,删除链表最末端的元素。我们知道LoggerExternalScopeProvider 在系统中是单例模式,多个请求进来,加入线程池处理。通过使用AsyncLoca来实现不同线程间数据独立。

  • 有两个地方开启了日志作用域:

  • 1、通过socket监听到请求后,将KestrelConnection加入线程池,线程池调度执行IThreadPoolWorkItem.Execute()方法。在这里开启了一次

  • 2、在构建请求上下文对象的时候(HostingApplication.CreateContext()),开启了一次

由上源码可以得出:在工厂记录器类中,通过系统依赖注入的方式解析所有注册的ILoggerProvider,然后调用其中的CreateLogger方法实现创建一个Logger实例对象,而这个Logger实例对象会根据根据注册的ILoggerProvider创建需要的LoggerInformation[],并将此对象作为参数进行ApplyFilters过滤器筛选,得到对应的最低等级或过滤规则,最后通过调用Log方法日志记录的时候,会遍历MessageLogger[]集合,根据logger日志类别对应实际不同的日志写入类,调用ILoggerProvider具体实现类 (可以看下文说明) 中的Log方法。   

AddProviderRegistration→CreateLoggers→LoggerInformation[]→ApplyFilters→MessageLogger[]→Log→ILoggerProvider ( 执行具体类中的Log方法 )

ILoggerFactory 来源

在上一篇中我们在对日志配置进行说明的时候,应用程序在启动初始化的时候会通过注入的方式CreateDefaultBuilderConfigureLoggingAddLogging

public static IServiceCollection AddLogging(this IServiceCollection services, Action<ILoggingBuilder> configure)
{
if (services == null)
{throw new ArgumentNullException(nameof(services));
}services.AddOptions();
services.TryAdd(ServiceDescriptor.Singleton<ILoggerFactory, LoggerFactory>());
services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(Logger<>)));services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<LoggerFilterOptions>>(new DefaultLoggerLevelConfigureOptions(LogLevel.Information)));configure(new LoggingBuilder(services));
return services;
}

实现将把ILoggerFactory对象以依赖注入的方式托管到集合容器中,为程序调用提供使用。

日志记录提供器

  1. ILoggerProvider 接口

创建ILogger实例的类型,根据日志类别名称创建一个新的ILogger实例

public interface ILoggerProvider : IDisposable
{ILogger CreateLogger(string categoryName);
}

这个是具体的日志写入类,在工厂记录器中我们已经提到了这个,在LoggerInformation[]中会根据日志类别注册对应的ILoggerProvider,在系统中我们就可以通过ILogger同时向多个途经写入日志信息。(这也是对上一篇中留下的问题进行再次说明)

ILoogerProvider继承了IDisposable接口,如果某个具体的ILoggerProvider对象需要释放资源,就可以将相关的操作实现在Dispose方法中。

默认的实现方式为多个,官方实现的由ConsoleLoggerProviderDebugLoggerProviderEventSourceLoggerProviderEventLogLoggerProviderTraceSourceLoggerProvider

ConsoleLoggerProvider为列

    [ProviderAlias("Console")]public class ConsoleLoggerProvider : ILoggerProvider, ISupportExternalScope{private readonly IOptionsMonitor<ConsoleLoggerOptions> _options;private readonly ConcurrentDictionary<string, ConsoleLogger> _loggers;private readonly ConsoleLoggerProcessor _messageQueue;private IDisposable _optionsReloadToken;private IExternalScopeProvider _scopeProvider = NullExternalScopeProvider.Instance;public ConsoleLoggerProvider(IOptionsMonitor<ConsoleLoggerOptions> options){_options = options;_loggers = new ConcurrentDictionary<string, ConsoleLogger>();ReloadLoggerOptions(options.CurrentValue);_optionsReloadToken = _options.OnChange(ReloadLoggerOptions);_messageQueue = new ConsoleLoggerProcessor();if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)){_messageQueue.Console = new WindowsLogConsole();_messageQueue.ErrorConsole = new WindowsLogConsole(stdErr: true);}else{_messageQueue.Console = new AnsiLogConsole(new AnsiSystemConsole());_messageQueue.ErrorConsole = new AnsiLogConsole(new AnsiSystemConsole(stdErr: true));}}private void ReloadLoggerOptions(ConsoleLoggerOptions options){foreach (var logger in _loggers){logger.Value.Options = options;}}public ILogger CreateLogger(string name){return _loggers.GetOrAdd(name, loggerName => new ConsoleLogger(name, _messageQueue){Options = _options.CurrentValue,ScopeProvider = _scopeProvider});}public void Dispose(){_optionsReloadToken?.Dispose();_messageQueue.Dispose();}public void SetScopeProvider(IExternalScopeProvider scopeProvider){_scopeProvider = scopeProvider;foreach (var logger in _loggers){logger.Value.ScopeProvider = _scopeProvider;}}}

ConsoleLoggerProvider类型定义中,标注了ProviderAliasAttribute特性,并设置别名为Console,所以在配置过滤规则的时候,可以直接使用这个名称。ILogger的创建实现了具体日志类ConsoleLogger

日志记录器

     1. ILogger接口

表示用于执行日志记录的类型,是系统中写入日志的统一入口。

public interface ILogger
{void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter);bool IsEnabled(LogLevel logLevel);IDisposable BeginScope<TState>(TState state);
}

定义了三个方法,Log<TState>() 用于写入日志,IsEnabled()用于检查判断日志级别是否开启,BeginScope() 用于指日志作用域。

2. Logger实现

ILogger执行记录接口类的具体实现Logger如下:

internal class Logger : ILogger
{public LoggerInformation[] Loggers { get; set; }public MessageLogger[] MessageLoggers { get; set; }public ScopeLogger[] ScopeLoggers { get; set; }public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter){var loggers = MessageLoggers;if (loggers == null){return;}List<Exception> exceptions = null;for (var i = 0; i < loggers.Length; i++){ref readonly var loggerInfo = ref loggers[i];if (!loggerInfo.IsEnabled(logLevel)){continue;}LoggerLog(logLevel, eventId, loggerInfo.Logger, exception, formatter, ref exceptions, state);}if (exceptions != null && exceptions.Count > 0){ThrowLoggingError(exceptions);}static void LoggerLog(LogLevel logLevel, EventId eventId, ILogger logger, Exception exception, Func<TState, Exception, string> formatter, ref List<Exception> exceptions, in TState state){try{logger.Log(logLevel, eventId, state, exception, formatter);}catch (Exception ex){if (exceptions == null){exceptions = new List<Exception>();}exceptions.Add(ex);}}}public bool IsEnabled(LogLevel logLevel){var loggers = MessageLoggers;if (loggers == null){return false;}List<Exception> exceptions = null;var i = 0;for (; i < loggers.Length; i++){ref readonly var loggerInfo = ref loggers[i];if (!loggerInfo.IsEnabled(logLevel)){continue;}if (LoggerIsEnabled(logLevel, loggerInfo.Logger, ref exceptions)){break;}}if (exceptions != null && exceptions.Count > 0){ThrowLoggingError(exceptions);}return i < loggers.Length ? true : false;static bool LoggerIsEnabled(LogLevel logLevel, ILogger logger, ref List<Exception> exceptions){try{if (logger.IsEnabled(logLevel)){return true;}}catch (Exception ex){if (exceptions == null){exceptions = new List<Exception>();}exceptions.Add(ex);}return false;}}
}

源码中MessageLogger[]在上文已经提到了,其中保存了在配置中启用的那些对应的ILogger

需要注意的是,由于配置文件更改后,会调用ApplyFilters()方法,并为MessageLogger[]赋新值,所以在遍历之前,需要保存当前值,再进行处理。否则会出现修改异常。

在系统中统一写入日志的入口,通过日志等级作为参数调用其IsEnabled方法来确定当前日志是否执行对应具体日志的实现类,当符合条件执行具体日志输出到对应的写入途径中会调用对应的Log方法(需要提供一个EventId来标识当前日志事件)

ILogger默认的实现方式为多个,官方实现的由ConsoleLoggerDebugLoggerEventSourceLoggerEventLogLoggerTraceSourceLogger 具体日志实现类代表不同的日志写入途径。

总结

   1. 在ILoggerFactoryILoggerProvider中都会通过方法创建ILogger对象,但两者是不相同的。在工厂默认实现LoggerFactory类型中它创建的ILogger对象是由注册到LoggerFactory对象上的所有ILoggerProvider对象提供一组 ILogger对象组合而成。而日志提供器ILoggerProvider创建的ILogger是日志实现输出到对应的渠道目标,写入日志。

   2. 日志记录器ILogger中的Log()方法会记录执行日志,在日志记录器工厂ILoggerFactory和日志记录提供器ILoggerProvider中两种不同的ILogger实现对应的Log()方法实现的意思也是不同的。在ILoggerFactory产生的是ILogger类型(也就是我们最终使用的Logger),其Log()方法是依次调用Logger中包含的LoggerInformation[]数组中的ILogger。而ILoggerProvider产生的为各类不同的XxxLogger(也就是上面说的Logger中的LoggerInformation数组包含的如ConsoleLogger、DebugLogger),其Log()方法是把日志写到具体的目标上去。

   3. 由上文可以发现,在asp.net core提供的日志记录的组件,通过工厂的一种方式,将日志记录器和日志记录提供器都放入到工厂这样的容器中,满足定义多个不同的记录方式。在后续我们可以通过自定义ILoggerProvider集成到Logger中,实现自己需要的日志记录输出方式。

    4. 如果有不对的或不理解的地方,希望大家可以多多指正,提出问题,一起讨论,不断学习,共同进步。

   5. 推荐搜索关注公众号 --【DotNet技术谷】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/308421.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

listview在java中的使用_我的Android开发之路——ListView的使用

在Android开发过程中&#xff0c;遇到需要列表显示的时候&#xff0c;这时候就会用到listview。1.首先创建一个ListViewTest项目&#xff0c;选择empty activity类型。修改activity_main.xml的布局文件&#xff0c;添加listview控件&#xff0c;设置宽高和id等属性此时通过预览…

如何利用NLog输出结构化日志,并在Kibana优雅分析日志?

上文我们演示了使用NLog向ElasticSearch写日志的基本过程(输出的是普通文本日志)&#xff0c;今天我们来看下如何向ES输出结构化日志、在Kibana中分析日志。什么是结构化日志&#xff1f;当前互联网、物联网、大数据突飞猛进&#xff0c;软件越复杂&#xff0c;查找任何给定问题…

java打印设备集中管理_Kafka+Log4j实现日志集中管理

记录如何使用KafkaLog4j实现集中日志管理的过程。引言前面写的《SpringLog4jActiveMQ实现远程记录日志——实战分析》得到了许多同学的认可&#xff0c;在认可的同时&#xff0c;也有同学提出可以使用Kafka来集中管理日志&#xff0c;于是今天就来学习一下。特别说明&#xff0…

7-27 家谱处理 (30 分)(详解+map做法)map真香啊

一&#xff1a;题目 人类学研究对于家族很感兴趣&#xff0c;于是研究人员搜集了一些家族的家谱进行研究。实验中&#xff0c;使用计算机处理家谱。为了实现这个目的&#xff0c;研究人员将家谱转换为文本文件。下面为家谱文本文件的实例&#xff1a; John Robert Frank Andr…

微软开源基于 Envoy 的服务网格 Open Service Mesh

原文地址&#xff1a;https://techcrunch.com/2020/08/05/microsoft-launches-open-service-mesh/Open Service Mesh&#xff08;OSM&#xff09;是一个轻量级的、可扩展的、云原生的服务网格&#xff0c;它允许用户对高度动态的微服务环境进行统一管理、安全保护&#xff0c;并…

java servlet jsp javabean关系图_Servlet+JSP+JavaBean开发模式(MVC)介绍

好伤心...写登陆注册之前看见一篇很好的博文&#xff0c;没有收藏&#xff0c;然后找不到了。前几天在知乎上看见一个问题&#xff0c;什么时候感觉最无力。前两天一直想回答&#xff1a;尝试过google到的所有solve case&#xff0c;结果bug依然在。今天想回答&#xff1a;明明…

7-28 搜索树判断 (25 分)(思路加详解) just easy!

一&#xff1a;题目 对于二叉搜索树&#xff0c;我们规定任一结点的左子树仅包含严格小于该结点的键值&#xff0c;而其右子树包含大于或等于该结点的键值。如果我们交换每个节点的左子树和右子树&#xff0c;得到的树叫做镜像二叉搜索树。 现在我们给出一个整数键值序列&…

Azure DevOps+Docker+Asp.NET Core 实现CI/CD(一 .简介与创建自己的代理池)

前言本文主要是讲解如何使用Azure DevOpsDocker 来实现持续集成Asp.NET Core项目(当然 也可以是任意项目).打算用三个篇幅来记录完整的全过程觉得有帮助的朋友~可以左上角点个关注,右下角点个推荐CI/CD简介首先,我们先来简单的介绍一下什么是CI/CDCI全拼Continuous Integration…

7-31 笛卡尔树(25分)(题目分析+简单算法+详解+思路)

一&#xff1a;题目 7-31 笛卡尔树 (25 分) 笛卡尔树是一种特殊的二叉树&#xff0c;其结点包含两个关键字K1和K2。首先笛卡尔树是关于K1的二叉搜索树&#xff0c;即结点左子树的所有K1值都比该结点的K1值小&#xff0c;右子树则大。其次所有结点的K2关键字满足优先队列&#…

不仅性能秒杀Hadoop,现在连分布式集群功能也开源了

就在昨天&#xff08;2020年8月3日&#xff09;&#xff0c;涛思数据团队正式宣布&#xff0c;物联网大数据平台TDengine集群版开源。此次开源&#xff0c;我们在GitHub上传了23.9万行源代码&#xff0c;1198个源文件&#xff0c;包含我自己疫情期间写的一万余行C代码&#xff…

7-32 哥尼斯堡的“七桥问题” (25 分)(思路+详解+题目分析)两种做法任选其一

一&#xff1a;题目&#xff1a; 哥尼斯堡是位于普累格河上的一座城市&#xff0c;它包含两个岛屿及连接它们的七座桥&#xff0c;如下图所示。 可否走过这样的七座桥&#xff0c;而且每桥只走过一次&#xff1f;瑞士数学家欧拉(Leonhard Euler&#xff0c;1707—1783)最终解…

一次简单的服务器 cpu 占用率高的快速排查实战

前两天&#xff0c;朋友遇到一个线上 cpu 占用率很高的问题&#xff0c;我们俩一起快速定位并解决了这个问题。在征求朋友同意后&#xff0c;特发此文分享整个过程。本文以对话的形式展开&#xff0c;加上我的内心独白。文中对话与实际对话略有出入。友&#xff1a; 在吗&#…

7-33 地下迷宫探索 (30 分)(思路加详解)

一&#xff1a;题目 7-33 地下迷宫探索 (30 分)地道战是在抗日战争时期&#xff0c;在华北平原上抗日军民利用地道打击日本侵略者的作战方式。地道网是房连房、街连街、村连村的地下工事&#xff0c;如下图所示。 我们在回顾前辈们艰苦卓绝的战争生活的同时&#xff0c;真心钦…

联通定时休眠5G基站 戳破皇帝的新衣

近年来&#xff0c;5G被欧美政客、大公司、媒体连番炒作&#xff0c;在公开舆论上&#xff0c;5G成为了“科技制高点”&#xff0c;成为决定国家命运的“外星科技”&#xff0c;个别明星企业家还声称&#xff0c;“5G改变社会”&#xff0c;“5G应用后美国将成为落后国家”。但…

java中的线程不安全和实例解析

一&#xff1a;引言&#xff08;特指单核&#xff09; 所谓线程不安全&#xff0c;就是在共享数据时&#xff0c;不同的线程在执行时&#xff0c;出现数据的不准确&#xff0c;&#xff08;以模拟抢票和模拟银行取钱为例&#xff09;&#xff0c;那么我们的线程不安全具体指的…

记近一年线上项目经验及架构变更记录

简介M 项目, 是一个电子社保业务系统&#xff0c;2019.8 月团队接手了这个项目的开发工作&#xff0c;到 2020.7 月客户的业务量翻了&#xff14;倍&#xff0c;工作日同时在线员工数量&#xff14;&#xff10;人&#xff0c;以下记录总结 2019.8-至今项目的架构变化&#xff…

拓扑排序C++实现+实例解析(详解 兄弟们冲呀呀呀呀呀呀呀)

一&#xff1a;引言 既然是一种排序&#xff0c;那么肯定是按照某种规则进行排序&#xff0c;那么这么想的话&#xff0c;先了解基本知识&#xff0c;再来实战演练 1. AOV网&#xff08;Activity On Vertex Network)【顶点——表示活动】 是一个——有向无回路的图 顶点——表…

7-34 任务调度的合理性 (25 分)(思路加详解+兄弟们冲呀)

一&#xff1a;题目 假定一个工程项目由一组子任务构成&#xff0c;子任务之间有的可以并行执行&#xff0c;有的必须在完成了其它一些子任务后才能执行。“任务调度”包括一组子任务、以及每个子任务可以执行所依赖的子任务集。 比如完成一个专业的所有课程学习和毕业设计可…

.NET和.NET Core Web APi FormData多文件上传

【导读】最近因维护.NET和.NET Core项目用到文件上传功能&#xff0c;虽说也做过&#xff0c;但是没做过什么对比&#xff0c;借此将二者利用Ajax通过FormData上传文件做一个总结&#xff0c;通过视图提交表单太简单&#xff0c;这里不做阐述&#xff0c;希望对有需要的童鞋能有…

在ubuntu上实现基于webrtc的多人在线视频聊天服务

最近研究webrtc视频直播技术&#xff0c;网上找了些教程最终都不太能顺利跑起来的&#xff0c;可能是文章写的比较老&#xff0c;使用的一些开源组件已经更新了&#xff0c;有些配置已经不太一样了&#xff0c;所以按照以前的步骤会有问题。折腾了一阵终于跑起来了&#xff0c;…