ASP.NET Core 6框架揭秘实例演示[21]:如何承载你的后台服务
借助 .NET提供的服务承载(Hosting)系统,我们可以将一个或者多个长时间运行的后台服务寄宿或者承载我们创建的应用中。任何需要在后台长时间运行的操作都可以定义成标准化的服务并利用该系统来承载,ASP.NET Core应用最终也体现为这样一个承载服务。(本篇提供的实例已经汇总到《ASP.NET Core 6框架揭秘-实例演示版》)
[S1401]利用承载服务收集性能指标(源代码)
[S1402]依赖注入的应用(源代码)
[S1403]配置选项的应用(源代码)
[S1404]提供针对环境的配置(源代码)
[S1405]日志的应用(源代码)
[S1406]在配置中定义日志过滤规则(源代码)
[S1401]利用承载服务收集性能指标
承载服务的项目一般会采用“Microsoft.NET.Sdk.Worker”这个SDK。服务承载模型涉及的接口和类型大都定义在“Microsoft.Extensions.Hosting.Abstractions”这个NuGet包,而具体实现在由NuGet包“Microsoft.Extensions.Hosting”来提供。我们演示的承载服务会定时采集当前进程的性能指标并将其分发出去。我们只关注处理器使用率、内存使用量和网络吞吐量这三种典型的指标,为此我们定义了如下这个PerformanceMetrics类型。我们并不会实现真正的性能指标收集,定义的静态方法Create会利用随机生成的指标来创建PerformanceMetrics对象。
public class PerformanceMetrics
{
private static readonly Random _random = new(); public int Processor { get; set; }
public long Memory { get; set; }
public long Network { get; set; } public override string ToString() => @$"CPU: {Processor * 100}%; Memory: {Memory / (1024* 1024)}M; Network: {Network / (1024 * 1024)}M/s"; public static PerformanceMetrics Create() => new()
{
Processor = _random.Next(1, 8),
Memory = _random.Next(10, 100) * 1024 * 1024,
Network = _random.Next(10, 100) * 1024 * 1024
};
}
承载服务通过IHostedService接口表示,该接口定义的StartAsync和StopAsync方法可以启动与关闭服务。我们将性能指标采集服务定义成如下这个PerformanceMetricsCollector类型。在实现的StartAsync方法中,我们一个定时器每隔5秒调用Create方法创建一个PerformanceMetrics对象,并将它承载的性能指标输出到控制台上。作为定期是的Timer对象会在StopAsync方法中被释放。
public sealed class PerformanceMetricsCollector : IHostedService
{
private IDisposable? _scheduler;
public Task StartAsync(CancellationToken cancellationToken)
{
_scheduler = new Timer(Callback, null, TimeSpan.FromSeconds(5),TimeSpan.FromSeconds(5));
return Task.CompletedTask; static void Callback(object? state)=> Console.WriteLine($"[{DateTimeOffset.Now}]{PerformanceMetrics.Create()}");
} public Task StopAsync(CancellationToken cancellationToken)
{
_scheduler?.Dispose();
return Task.CompletedTask;
}
}
服务承载系统通过IHost接口表示承载服务的宿主,该对象在应用启动过程中采用Builder模式由对应的IHostBuilder对象来构建。HostBuilder类型是对IHostBuilder接口的默认实现,所以我们采用如下方式创建一个HostBuilder对象,并调用其Build方法来提供作为宿主的IHost对象。在调用Build方法构建IHost对象之前,我们调用了ConfigureServices方法将PerformancceMetricsCollector注册成针对IHostedService接口的服务,并将生命周期模式设置成Singleton。
using App;
new HostBuilder()
.ConfigureServices(svcs => svcs
.AddSingleton<IHostedService, PerformanceMetricsCollector>())
.Build()
.Run();
我们最后调用Run方法启动通过IHost对象表示的承载服务宿主,进而启动由它承载的PerformancceMetricsCollector服务,该服务将以图1所示的形式每隔5秒在控制台上输出“采集”的性能指标。
图1 承载指标采集服务
除了采用一般的服务注册方式,我们还可以按照如下的方式调用IServiceCollection接口的AddHostedService<THostedService>扩展方法来对承载服务PerformanceMetricsCollector进行注册。我们一般也不会通过调用构造函数的方式创建HostBuilder对象,而是使用定义在Host类型中的 工厂方法CreateDefaultBuilder创建来构建IHostBuilder对象。
using App;
Host.CreateDefaultBuilder(args)
.ConfigureServices(svcs => svcs.AddHostedService<PerformanceMetricsCollector>())
.Build()
.Run();
[S1402]依赖注入的应用
服务承载系统整合依赖注入框架,针对承载服务的注册实际上就是将它注册到依赖注入框架中。既然承载服务实例最终是通过依赖注入容器提供的,那么它自身所依赖的服务当然也可以进行注册。我们接下来将PerformanceMetricsCollector提供的性能指标收集功能分解到由四个接口表示的服务中,IProcessorMetricsCollector、IMemoryMetricsCollector和INetworkMetricsCollector接口代表的服务分别用于收集三种对应的性能指标,而IMetricsDeliverer接口表示的服务则负责将收集的性能指标发送出去。
public interface IProcessorMetricsCollector
{
int GetUsage();
}
public interface IMemoryMetricsCollector
{
long GetUsage();
}
public interface INetworkMetricsCollector
{
long GetThroughput();
} public interface IMetricsDeliverer
{
Task DeliverAsync(PerformanceMetrics counter);
}
我们定义的MetricsCollector类型实现了三个性能指标采集接口,采集的性能指标直接来源于通过静态方法Create创建的PerformanceMetrics对象。MetricsDeliverer类型实现了IMetricsDeliverer接口,实现的DeliverAsync方法直接将PerformanceMetrics对象承载的性能指标输出到控制台上。
public class MetricsCollector :
IProcessorMetricsCollector,
IMemoryMetricsCollector,
INetworkMetricsCollector
{
long INetworkMetricsCollector.GetThroughput() => PerformanceMetrics.Create().Network; int IProcessorMetricsCollector.GetUsage() => PerformanceMetrics.Create().Processor; long IMemoryMetricsCollector.GetUsage() => PerformanceMetrics.Create().Memory;
} public class MetricsDeliverer : IMetricsDeliverer
{
public Task DeliverAsync(PerformanceMetrics counter)
{
Console.WriteLine($"[{DateTimeOffset.UtcNow}]{counter}");
return Task.CompletedTask;
}
}
由于整个性能指标的采集工作被分解到四个接口表示的服务之中,所以我们可以采用如下所示的方式重新定义承载服务类型PerformanceMetricsCollector。如代码片段所示,我们在构造函数中注入四个依赖服务,StartAsync方法利用注入的IProcessorMetricsCollector、IMemoryMetricsCollector和INetworkMetricsCollector对象采集对应的性能指标,并利用IMetricsDeliverer对象将其发送出去。
public sealed class PerformanceMetricsCollector : IHostedService
{
private readonly IProcessorMetricsCollector _processorMetricsCollector;
private readonly IMemoryMetricsCollector _memoryMetricsCollector;
private readonly INetworkMetricsCollector _networkMetricsCollector;
private readonly IMetricsDeliverer _MetricsDeliverer;
private IDisposable? _scheduler; public PerformanceMetricsCollector(
IProcessorMetricsCollector processorMetricsCollector,
IMemoryMetricsCollector memoryMetricsCollector,
INetworkMetricsCollector networkMetricsCollector,
IMetricsDeliverer MetricsDeliverer)
{
_processorMetricsCollector = processorMetricsCollector;
_memoryMetricsCollector = memoryMetricsCollector;
_networkMetricsCollector = networkMetricsCollector;
_MetricsDeliverer = MetricsDeliverer;
} public Task StartAsync(CancellationToken cancellationToken)
{
_scheduler = new Timer(Callback, null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5));
return Task.CompletedTask; async void Callback(object? state)
{
var counter = new PerformanceMetrics
{
Processor = _processorMetricsCollector.GetUsage(),
Memory = _memoryMetricsCollector.GetUsage(),
Network = _networkMetricsCollector.GetThroughput()
};
await _MetricsDeliverer.DeliverAsync(counter);
}
} public Task StopAsync(CancellationToken cancellationToken)
{
_scheduler?.Dispose();
return Task.CompletedTask;
}
}
在调用IHostBuilder接口的Build方法将IHost对象构建出来之前,包括承载服务在内的所有服务都可以通过它的ConfigureServices方法进行了注册。修改后的程序启动之后同样会在控制台上看到图14-1所示的输出结果(S1402)。
using App;
var collector = new MetricsCollector();
Host.CreateDefaultBuilder(args)
.ConfigureServices(svcs => svcs
.AddHostedService<PerformanceMetricsCollector>()
.AddSingleton<IProcessorMetricsCollector>(collector)
.AddSingleton<IMemoryMetricsCollector>(collector)
.AddSingleton<INetworkMetricsCollector>(collector)
.AddSingleton<IMetricsDeliverer, MetricsDeliverer>())
.Build()
.Run();
[S1403]配置选项的应用
真正的应用开发基本都会使用到配置选项,比如我们演示程序中性能指标采集的时间间隔就应该采用配置选项来指定。由于涉及对性能指标数据的发送,所以最好将发送的目标地址定义在配置选项中。如果有多种传输协议可供选择,就可以定义相应的配置选项。 .NET应用推荐采用Options模式来使用配置选项,所以可以定义如下这个MetricsCollectionOptions类型来承载三种配置选项。
public class MetricsCollectionOptions
{
public TimeSpan CaptureInterval { get; set; }
public TransportType Transport { get; set; }
public Endpoint DeliverTo { get; set; }
} public enum TransportType
{
Tcp,
Http,
Udp
} public class Endpoint
{
public string Host { get; set; }
public int Port { get; set; }
public override string ToString() => $"{Host}:{Port}";
}
传输协议和目标地址使用在MetricsDeliverer服务中,所以我们对它进行了如下的修改。如代码片段所示,我们在构造函数中利用注入的IOptions<MetricsCollectionOptions>服务来提供上面的两个配置选项。在实现的DeliverAsync方法中,我们将采用的传输协议和目标地址输出到控制台上。
public class MetricsDeliverer : IMetricsDeliverer
{
private readonly TransportType _transport;
private readonly Endpoint _deliverTo; public MetricsDeliverer(IOptions<MetricsCollectionOptions> optionsAccessor)
{
var options = optionsAccessor.Value;
_transport = options.Transport;
_deliverTo = options.DeliverTo;
} public Task DeliverAsync(PerformanceMetrics counter)
{
Console.WriteLine($"[{DateTimeOffset.Now}]Deliver performance counter {counter} to {_deliverTo} via {_transport}");
return Task.CompletedTask;
}
}
承载服务类型PerformanceMetricsCollector同样应该采用这种方式来提取表示性能指标采集频率的配置选项。如下所示的代码片段是PerformanceMetricsCollector采用配置选项后的完整定义。
public sealed class PerformanceMetricsCollector : IHostedService
{
private readonly IProcessorMetricsCollector _processorMetricsCollector;
private readonly IMemoryMetricsCollector _memoryMetricsCollector;
private readonly INetworkMetricsCollector _networkMetricsCollector;
private readonly IMetricsDeliverer _metricsDeliverer;
private readonly TimeSpan _captureInterval;
private IDisposable? _scheduler; public PerformanceMetricsCollector(
IProcessorMetricsCollector processorMetricsCollector,
IMemoryMetricsCollector memoryMetricsCollector,
INetworkMetricsCollector networkMetricsCollector,
IMetricsDeliverer metricsDeliverer,
IOptions<MetricsCollectionOptions> optionsAccessor)
{
_processorMetricsCollector = processorMetricsCollector;
_memoryMetricsCollector = memoryMetricsCollector;
_networkMetricsCollector = networkMetricsCollector;
_metricsDeliverer = metricsDeliverer;
_captureInterval = optionsAccessor.Value.CaptureInterval;
} public Task StartAsync(CancellationToken cancellationToken)
{
_scheduler = new Timer(Callback, null, TimeSpan.FromSeconds(5), _captureInterval);
return Task.CompletedTask; async void Callback(object? state)
{
var counter = new PerformanceMetrics
{
Processor = _processorMetricsCollector.GetUsage(),
Memory = _memoryMetricsCollector.GetUsage(),
Network = _networkMetricsCollector.GetThroughput()
};
await _metricsDeliverer.DeliverAsync(counter);
}
} public Task StopAsync(CancellationToken cancellationToken)
{
_scheduler?.Dispose();
return Task.CompletedTask;
}
}
配置文件配置选项的常用来源,所以我们在根目录下添加了一个名为appsettings.json的配置文件,并在其中定义如下内容来提供上述三个配置选项。由Host类型的CreateDefaultBuilder工厂方法创建的IHostBuilder对象会自动加载这个配置文件。
{
"MetricsCollection": {
"CaptureInterval": "00:00:05",
"Transport": "Udp",
"DeliverTo": {
"Host": "192.168.0.1",
"Port": 3721
}
}
}
我们接下来对演示程序做相应的改动。之前针对依赖服务的注册是通过调用IHostBuilder对象的ConfigureServices方法利用作为参数的Action<IServiceCollection>对象完成的,该接口还有一个ConfigureServices方法重载,它的参数类型为Action<HostBuilderContext, IServiceCollection>,作为输入的HostBuilderContext上下文可以提供表示应用配置的IConfiguration对象。
using App;
var collector = new MetricsCollector();
Host.CreateDefaultBuilder(args)
.ConfigureServices((context, svcs) => svcs
.AddHostedService<PerformanceMetricsCollector>()
.AddSingleton<IProcessorMetricsCollector>(collector)
.AddSingleton<IMemoryMetricsCollector>(collector)
.AddSingleton<INetworkMetricsCollector>(collector)
.AddSingleton<IMetricsDeliverer, MetricsDeliverer>()
.Configure<MetricsCollectionOptions>(context.Configuration.GetSection("MetricsCollection")))
.Build()
.Run();
我们利用提供的Action<HostBuilderContext, IServiceCollection>委托通过调用IServiceCollection接口的Configure<TOptions>扩展方法从提供的HostBuilderContext对象中提取出配置,并对MetricsCollectionOptions配置选项做了绑定。我们修改后的程序运行之后在控制台上会输出如图2所示结果。
图2 引入配置选项
[S1404]提供针对环境的配置
应用程序总是针对某个具体环境进行部署的,开发(Development)、预发(Staging)和产品(Production)是三种典型的部署环境,这里的部署环境在服务承载系统中统称为承载环境(Hosting Environment)。一般来说,不同的承载环境往往具有不同的配置选项,下面我们将演示如何为不同的承载环境提供相应的配置选项。具体的做法很简单:将共享或者默认的配置定义在基础配置文件(如appsettings.json)中,将差异化的部分定义在针对具体环境的配置文件(如appsettings.staging.json和appsettings.production.json)中。对于我们演示的实例来说,我们可以采用图3所示的方式添加额外的两个配置文件来提供针对预发环境和产品环境的差异化配置。
图3 针对承载环境的配置文件
对于演示实例提供的三个配置选项来说,假设针对承载环境的差异化配合仅限于发送的目标终结点(IP地址和端口),我们就可以采用如下方式将它们定义在针对预发环境的appsettings.staging.json和针对产品环境的appsettings.production.json中。
appsettings.staging.json:
{
"MetricsCollection": {
"DeliverTo": {
"Host": "192.168.0.2",
"Port": 3721
}
}
}
appsettings.production.json:
{
"MetricsCollection": {
"DeliverTo": {
"Host": "192.168.0.3",
"Port": 3721
}
}
}
由于我们在调用Host的CreateDefaultBuilder方法时传入了命令行参数(args),所以默认创建的IHostBuilder会将其作为配置源。也正因为如此,我们可以采用命令行参数的形式设置当前的承载环境(对应配置名称为“environment”)。如图4所示,我们分别指定不同的承载环境先后四次运行我们的程序,从输出的IP地址可以看出,应用程序确实是根据当前承载环境加载对应的配置文件的。输出结果还体现了另一个细节,那就是默认采用的是产品(Production)环境。
图4 针对承载环境加载配置文件
[S1405]日志的应用
应用开发中不可避免地会涉及很多针对“诊断日志”的应用,我们接下来就来演示承载服务如何记录日志。对于我们的演示实例来说,用于发送性能指标的MetricsDeliverer对象会将收集的指标数据输出到控制台上,下面将这段文字以日志的形式进行输出,为此我们将这个类型进行了如下的修改。
public class MetricsDeliverer : IMetricsDeliverer
{
private readonly TransportType _transport;
private readonly Endpoint _deliverTo;
private readonly ILogger _logger;
private readonly Action<ILogger, DateTimeOffset, PerformanceMetrics, Endpoint, TransportType, Exception?> _logForDelivery; public MetricsDeliverer(IOptions<MetricsCollectionOptions> optionsAccessor, ILogger<MetricsDeliverer> logger)
{
var options = optionsAccessor.Value;
_transport = options.Transport;
_deliverTo = options.DeliverTo;
_logger = logger;
_logForDelivery = LoggerMessage.Define<DateTimeOffset, PerformanceMetrics, Endpoint, TransportType>(LogLevel.Information, 0, "[{0}]Deliver performance counter {1} to {2} via {3}");
} public Task DeliverAsync(PerformanceMetrics counter)
{
_logForDelivery(_logger, DateTimeOffset.Now, counter, _deliverTo, _transport, null);
return Task.CompletedTask;
}
}
如上面的代码片段所示,我们利用构造函数中注入了的ILogger<MetricsDeliverer>对象并来记录日志。为了避免对同一个消息模板的重复解析,我们可以使用LoggerMessage类型定义的委托对象来输出日志,这也是MetricsDeliverer中采用的编程模式。运行修改后的程序会控制台上的输出如图5所示的结果。由输出结果可以看出,这些文字是由我们注册的ConsoleLoggerProvider提供的ConsoleLogger对象输出到控制台上的。由于承载系统自身在进行服务承载过程中也会输出一些日志,所以它们也会输出到控制台上。
图5 将日志输出到控制台上
[S1406]在配置中定义日志过滤规则
如果需要对输出的日志进行过滤,可以将过滤规则定义在配置文件中。为了避免在“产品”环境因输出过多的日志影响性能,我们在appsettings.production.json配置文件中以如下的形式将类别以“Microsoft.”为前缀的日志(最低)等级设置为 Warning。
{
"MetricsCollection": {
"DeliverTo": {
"Host": "192.168.0.3",
"Port": 3721
}
},
"Logging": {
"LogLevel": {
"Microsoft": "Warning"
}
}
}
如果此时分别针对开发(Development)环境和产品(Production)环境以命令行的形式启动修改后的应用程序,就会发现针对开发环境控制台会输出类型前缀为“Microsoft.”的日志,但是在针对产品环境的控制台上却找不到它们的踪影。
图6 根据承载环境过滤日志
ASP.NET Core 6框架揭秘实例演示[21]:如何承载你的后台服务的更多相关文章
- ASP.NET Core 6框架揭秘实例演示[07]:文件系统
ASP.NET Core应用具有很多读取文件的场景,如读取配置文件.静态Web资源文件(如CSS.JavaScript和图片文件等).MVC应用的视图文件,以及直接编译到程序集中的内嵌资源文件.这些文 ...
- ASP.NET Core 6框架揭秘实例演示[08]:配置的基本编程模式
.NET的配置支持多样化的数据源,我们可以采用内存的变量.环境变量.命令行参数.以及各种格式的配置文件作为配置的数据来源.在对配置系统进行系统介绍之前,我们通过几个简单的实例演示一下如何将具有不同来源 ...
- ASP.NET Core 6框架揭秘实例演示[09]:配置绑定
我们倾向于将IConfiguration对象转换成一个具体的对象,以面向对象的方式来使用配置,我们将这个转换过程称为配置绑定.除了将配置树叶子节点配置节的绑定为某种标量对象外,我们还可以直接将一个配置 ...
- ASP.NET Core 6框架揭秘实例演示[10]:Options基本编程模式
依赖注入使我们可以将依赖的功能定义成服务,最终以一种松耦合的形式注入消费该功能的组件或者服务中.除了可以采用依赖注入的形式消费承载某种功能的服务,还可以采用相同的方式消费承载配置数据的Options对 ...
- ASP.NET Core 6框架揭秘实例演示[11]:诊断跟踪的几种基本编程方式
在整个软件开发维护生命周期内,最难的不是如何将软件系统开发出来,而是在系统上线之后及时解决遇到的问题.一个好的程序员能够在系统出现问题之后马上定位错误的根源并找到正确的解决方案,一个更好的程序员能够根 ...
- ASP.NET Core 6框架揭秘实例演示[12]:诊断跟踪的进阶用法
一个好的程序员能够在系统出现问题之后马上定位错误的根源并找到正确的解决方案,一个更好的程序员能够根据当前的运行状态预知未来可能发生的问题,并将问题扼杀在摇篮中.诊断跟踪能够帮助我们有效地纠错和排错&l ...
- ASP.NET Core 6框架揭秘实例演示[13]:日志的基本编程模式[上篇]
<诊断跟踪的几种基本编程方式>介绍了四种常用的诊断日志框架.其实除了微软提供的这些日志框架,还有很多第三方日志框架可供我们选择,比如Log4Net.NLog和Serilog 等.虽然这些框 ...
- ASP.NET Core 6框架揭秘实例演示[14]:日志的进阶用法
为了对各种日志框架进行整合,微软创建了一个用来提供统一的日志编程模式的日志框架.<日志的基本编程模式>以实例演示的方式介绍了日志的基本编程模式,现在我们来补充几种"进阶" ...
- ASP.NET Core 6框架揭秘实例演示[15]:针对控制台的日志输出
针对控制台的ILogger实现类型为ConsoleLogger,对应的ILoggerProvider实现类型为ConsoleLoggerProvider,这两个类型都定义在 NuGet包"M ...
随机推荐
- Myeclipse设置关键词自动提示功能以及取消空格和"="的自动补全
一.设置Myeclipse从a~z的自动提示功能 1. "window"→"Preferences"2. 选择"Java",展开," ...
- VUE项目部署到线上生产环境,Loading chunk xxx failed
项目部署到生产环境,路由点击无效,报错 Loading chunk chunk-xxxxx failed.(missing xxxx) 加载失败,错误的路径. 话不多说,直接贴代码: vue.conf ...
- Docker创建私有镜像仓库
Docker官方提供了一个工具docker-registry,可以借助这个工具构建私有镜像仓库: 1.拉取registry镜像 # docker pull registry//可以使用 docker ...
- docker错误处理——docker Job for docker.service failed because the control process exited with error code.
(15条消息) docker Job for docker.service failed because the control process exited with error code._Hel ...
- llinux_2
1.显示/etc目录下,以非字母开头,后面跟了一个字母以及其它任意长度任意字符的文件或目录 [root@lhq ~]#ls /etc/ | grep "^[^[:alpha:]][[:alp ...
- python内置模块之re模块
内容概要 re模块常用方法 findall search match re模块其他方法 split sub subn compile finditer findall 对无名分组优先展示 re实战之爬 ...
- 都 2022 了,还不抓紧学 typeScript ?
Hi,我是前端人,今日与君共勉! 本篇文章主要介绍的是什么是 typeScript ? typeScript 与 javaScript 有什么关系呢?我们为什么要学习 typeScript ? 一.什 ...
- 简述对CT,IT,ICT,OT的认识
今天碰到一个关键词:CT.CT领域,所以给自己做一个科普. 网络:简述对CT,IT,ICT,OT的认识 一.通信技术-CT(Communication Technology) 最早的CT业被称为电信业 ...
- 使用并发 ssh 连接来提升捞日志脚本执行效率
问题背景 公司有个简单粗暴的日志服务,它部署在多台机器实例上,收集的日志记录在每台机器本地硬盘,写一个小时自动切换日志文件,硬盘空间写满了自动回卷,大约可以保存两三天的历史数据.为什么说它粗暴呢?原来 ...
- python-利用json模块处理json数据几个函数总结
1.前言 json是一种轻量级的数据交换格式,它是JavaScript的子集,易于人阅读和编写. 前端和后端进行数据交互,其实就是JS和Python进行数据交互. 接口间或者前后端间的语言不一致,不同 ...