利用AppMetrics对Web进行监控教程

一、基础准备

1. 安装依赖

这里可以通过nuget或使用命令行进行安装,具体需要安装的类库如下(注意版本):

Install-Package App.Metrics.AspNetCore.Mvc -Version 2.0.0

由于我们需要兼容Prometheus进行监控,所以我们还需要安装对应的格式化库,具体如下:

Install-Package App.Metrics.Formatters.Prometheus -Version 2.0.0

以上就是需要的类库,接下来我们开始进行其他初始化部分。

2. 初始配置

为了保证其能够正常工作,我们需要根据不同的环境设定对应的appsettings.json文件从而让度量指标可以根据不同的环境进行输出,这里考虑到实际情况尚未存在不同的配置可能性故统一配置即可,打开appsettings.json输入下配置项:

{"MetricsOptions": {"DefaultContextLabel": "MetricsApplication","Enabled": true},"MetricsWebTrackingOptions": {"ApdexTrackingEnabled": true,"ApdexTSeconds": 0.3,"IgnoredHttpStatusCodes": [ 404 ],"IgnoreRoutesRegexPatterns": [],"OAuth2TrackingEnabled": false},"MetricEndpointsOptions": {"MetricsEndpointEnabled": true,"MetricsTextEndpointEnabled": true,"EnvironmentInfoEndpointEnabled": true}
}

参数DefaultContextLabel可以设定为我们期望其他名称,这里建议采用项目的简写名称,保证项目之间不存在冲突即可。参数ApdexTSeconds用于设定应用的响应能力标准,其采用了当前流行的Apdex标准,这里使用者可以根据自身应用的实际情况调整对应的参数,其他相关参数建议默认即可。

3. 启用度量指标

因为我们的数据需要符合Promethues格式,所以后续教程我们会替换默认的格式采用符合的格式。首先我们需要Program.cs里输入以下内容:

        public static IWebHost BuildWebHost(string[] args){Metrics = AppMetrics.CreateDefaultBuilder().OutputMetrics.AsPrometheusPlainText().OutputMetrics.AsPrometheusProtobuf().Build();return WebHost.CreateDefaultBuilder(args).ConfigureMetrics(Metrics).UseMetrics(options =>{options.EndpointOptions = endpointsOptions =>{endpointsOptions.MetricsTextEndpointOutputFormatter = Metrics.OutputMetricsFormatters.OfType<MetricsPrometheusTextOutputFormatter>().First();endpointsOptions.MetricsEndpointOutputFormatter = Metrics.OutputMetricsFormatters.OfType<MetricsPrometheusProtobufOutputFormatter>().First();};}).UseStartup<Startup>().Build();}

其中为了能够支持其他格式,我们需要手动实例化Metrics对象完成相关初始化然后将其注入到asp.net core中,其中相关格式的代码主要是由以下这几部分组成:

OutputMetrics.AsPrometheusPlainText()
OutputMetrics.AsPrometheusProtobuf()endpointsOptions.MetricsTextEndpointOutputFormatter = Metrics.OutputMetricsFormatters.OfType<MetricsPrometheusTextOutputFormatter>().First();
endpointsOptions.MetricsEndpointOutputFormatter = Metrics.OutputMetricsFormatters.OfType<MetricsPrometheusProtobufOutputFormatter>().First();

完成以上操作后,我们最后还需要进行其他配置,打开Startup.cs文件增加如下内容:

services.AddMvc().AddMetrics();

至此我们就完成了基本的初始化了,通过启动程序并访问localhost:5000/metrics-text即可查看最终的输出内容。

二、自定义指标

由于其内部已经默认提供了若干的指标,但是并不能符合实际业务的需求故以下将对常用的度量指标类型以及用法进行介绍,这里这里大家通过注入IMetrics接口对象即可访问,所以下面这部分代码不在阐述。

1. 仪表盘(Gauge)

最常见的类型,主要是用于直接反应当前的指标情况,比如我们常见的CPU和内存基本都是使用这种方式进行显示的,可以直观的看到当前的实际的状态情况。对于所有的指标我们都需要定义对应的Options,当然这可以完成携程静态变量供应用程序全局使用。

比如下面我们定义一个表示当前发生错误次数的指标:

GaugeOptions Errors = new GaugeOptions()
{Name = "Errors"
};

完成指标的定义后,我们就可以在需要使用的地方进行指标数据的修改,比如下面我们将错误数量设置为10:

metrics.Measure.Gauge.SetValue(MyMetricsRegistry.Errors, 10);

这样我们就完成了指标的设定,但是有时候我们还想却分具体的Error是那个层面发起的,这个时候我们需要使用到Tag了,下面我们在设定值的同时设定指标,当然也可以在新建指标的时候通过Tags变量,并且通用于其他所有指标:

var tags = new MetricTags("fromt", "db");
metrics.Measure.Gauge.SetValue(MyMetricsRegistry.Errors, tags, 10);

至此我们就完成了一个基本的指标,下面我们继续其他类型指标。

2. 计数值(Counter)

对于HTTP类型的网站来说,存在非常多的数量需要统计记录,所以计数值此时就特别适合这类情况,比如我们需要统计请求数量就可以利用这类指标类型,下面我们就以请求数来定义这个指标:

var requestCounter = new CounterOptions()
{Name = "httpRequest",MeasurementUnit = Unit.Calls
};

以上我们定义了一个计数指标,其中我们可以看到我们这里使用了一个新变量MeasurementUnit主要是用于定义指标单位的,当然这个只是辅助信息会一同输出到结果,下面我们需要进行增加和减少,考虑到大多数情况都是减1和增1的情况:

metrics.Measure.Counter.Increment(requestCounter);

实际情况可能我们都是统计请求但是期望还能单独统计特定接口的请求,这个时候我们在原本调用方式基础上增加额外的参数:

metrics.Measure.Counter.Increment(requestCounter, "api");

如果嫌每次增长1比较慢,我们通过其函数的重载形式填写我们希望增长的具体值。

3. 计量值(Meter)

有点类似于计数值但是相比来说,它可以提供更加丰富的信息,比如每1、5、15分钟的增长率等,所以对于一些需要通过增长率观察的数据特别时候,这里我们以请求的反应状态码进行记录来体现其用途:

var httpStatusMeter = new MeterOptions()
{Name = "Http Status",MeasurementUnit = Unit.Calls
};

以上我们完成了一个指标的定义,下面我们开始使用其并且定义不同的状态的码的发生情况,具体如下:

metrics.Measure.Meter.Mark(httpStatusMeter, "200");
metrics.Measure.Meter.Mark(httpStatusMeter, "500");
metrics.Measure.Meter.Mark(httpStatusMeter, "401");

当然如果希望增加的数量自定控制也可以使用其提供的重载形式进行。

4. 柱状图(Histogram)

顾名思义,主要反应数据的分布情况,所以这里不在重复阐述,大家对于这种数据表现形式还是比较了解的,所以下面就直接以实际代码的实列进行介绍,便于大家的理解:

var postAndPutRequestSize = new HistogramOptions()
{Name = "Web Request Post & Put Size",MeasurementUnit = Unit.Bytes
};

以上我们定义一个体现Post和Put请求的数据尺寸的指标,下面我们利用随机数来进行数据的模拟对其进行数据填充,便于显示数据:

var rnd = new Random();foreach (var i in Enumerable.Range(0, 50))
{var t = rnd.Next(0, 10);metrics.Measure.Histogram.Update(postAndPutRequestSize, t);
}

5. 时间线(Timer)

对应指标的监控闭然少不了对于时间的记录,特别对于HTTP来说,直接影响到用户的体验就是响应时间,素以我们也需要时刻关于这类指标的变化情况及时做出反应,下面我们就以数据库的响应时间的情况作为指标进行监控:

TimerOptions DatabaseTimer = new TimerOptions()
{Name = "Database Timer",MeasurementUnit = Unit.Items,DurationUnit = TimeUnit.Milliseconds,RateUnit = TimeUnit.Milliseconds
};

上面我们通过特别的属性指定了改指标记录时间的单位,下面我们使用其指标进行数据的记录:

using(metrics.Measure.Timer.Time(DatabaseTimer))
{//to do sonmething
}

我们可以看到为了方便的记录请求的时间,我们使用using进行涵括,并将需要记录耗时的请求操作放入其中,在请求完成操作后就可以正确的记录其需要的时间。

6. apdex

采用了一种标准的性能指标计算方式,用法类似与上述,这里仅仅列举用法:

ApdexOptions SampleApdex = new ApdexOptions
{Name = "Sample Apdex"
};using(metrics.Measure.Apdex.Track(SampleApdex))
{Thread.Sleep(100);
}

三、高级指标

1. 平均响应

很多时候我们仅仅依靠一个指标很难完成一个实际的需求,是所以我们就需要将多个指标进行组合进行,比如我们期望得到请求次数,同时还有请求的总时间和平均响应时间等,为此我们可以特殊的指标将多个指标进行组合,具体操作如下:

var cacheHitRatioGauge = new GaugeOptions
{Name = "Cache Gauge",MeasurementUnit = Unit.Calls
};var cacheHitsMeter = new MeterOptions
{Name = "Cache Hits Meter",MeasurementUnit = Unit.Calls
};var databaseQueryTimer = new TimerOptions
{Name = "Database Query Timer",MeasurementUnit = Unit.Calls,DurationUnit = TimeUnit.Milliseconds,RateUnit = TimeUnit.Milliseconds
};var cacheHits = metrics.Provider.Meter.Instance(cacheHitsMeter);
var calls = metrics.Provider.Timer.Instance(databaseQueryTimer);var cacheHit = new Random().Next(0, 2) == 0;using(calls.NewContext())
{if (cacheHit){cacheHits.Mark(5);}Thread.Sleep(cacheHit ? 10 : 100);
}metrics.Measure.Gauge.SetValue(cacheHitRatioGauge, () => new HitRatioGauge(cacheHits, calls, m => m.OneMinuteRate));

四、利用Promethues和Grafana进行监控

1. 环境准备

这里需要使用到PrometheusGrafana,为了避免版本导致的区别这里提供了对应百度云的下载地址,大家可以自行进行下载。

Prometheus对应提取码为2b1r

Grafana对应提取码为mjym

完成以上下载后需要解压到对应文件夹下即可。

2. 配置服务

首先我们需要针对Prometheus进行配置,我们打开prometheus.yml文件新增基于AppMetrics的监控指标。

  - job_name: 'appweb'scrape_interval: 5smetrics_path: '/metrics-text'static_configs:- targets: ['localhost:5000']

完成之后我们可以先打开采集让其在后台持续采集,后面我们需要针对AppMetrics暴露的数据进行调整。

3. 应用指标输出

通过实际的测试发现基于2.0.0版本的Prometheus存在问题,因为指标类型被大写了,导致Prometheus无法正确读取,所以我们需要将源码复制出来进行操作,这里直接给出了对应的源码文件,
主要的工作就是将AsciiFormatter.cs中的HELPTYPE进行了小写而已,对应文件如下。

PS:考虑到很多基于2.0的所以这里保留了基于HTTP的文本实现方式发布了一个对应的版本库:

Install-Package Sino.Metrics.Formatters.Prometheus -Version 0.1.2
  • AsciiFormatter.cs

    internal static class AsciiFormatter{public static void Format(Stream destination, IEnumerable<MetricFamily> metrics){var metricFamilys = metrics.ToArray();using (var streamWriter = new StreamWriter(destination, Encoding.UTF8)){streamWriter.NewLine = "\n";foreach (var metricFamily in metricFamilys){WriteFamily(streamWriter, metricFamily);}}}internal static string Format(IEnumerable<MetricFamily> metrics, NewLineFormat newLine){var newLineChar = GetNewLineChar(newLine);var metricFamilys = metrics.ToArray();var s = new StringBuilder();foreach (var metricFamily in metricFamilys){s.Append(WriteFamily(metricFamily, newLineChar));}return s.ToString();}private static void WriteFamily(StreamWriter streamWriter, MetricFamily metricFamily){streamWriter.WriteLine("# HELP {0} {1}", metricFamily.name, metricFamily.help.ToLower());streamWriter.WriteLine("# TYPE {0} {1}", metricFamily.name, metricFamily.type.ToString().ToLower());foreach (var metric in metricFamily.metric){WriteMetric(streamWriter, metricFamily, metric);}}private static string WriteFamily(MetricFamily metricFamily, string newLine){var s = new StringBuilder();s.Append(string.Format("# HELP {0} {1}", metricFamily.name, metricFamily.help.ToLower()), newLine);s.Append(string.Format("# TYPE {0} {1}", metricFamily.name, metricFamily.type.ToString().ToLower()), newLine);foreach (var metric in metricFamily.metric){s.Append(WriteMetric(metricFamily, metric, newLine), newLine);}return s.ToString();}private static void WriteMetric(StreamWriter streamWriter, MetricFamily family, Metric metric){var familyName = family.name;if (metric.gauge != null){streamWriter.WriteLine(SimpleValue(familyName, metric.gauge.value, metric.label));}else if (metric.counter != null){streamWriter.WriteLine(SimpleValue(familyName, metric.counter.value, metric.label));}else if (metric.summary != null){streamWriter.WriteLine(SimpleValue(familyName, metric.summary.sample_sum, metric.label, "_sum"));streamWriter.WriteLine(SimpleValue(familyName, metric.summary.sample_count, metric.label, "_count"));foreach (var quantileValuePair in metric.summary.quantile){var quantile = double.IsPositiveInfinity(quantileValuePair.quantile)? "+Inf": quantileValuePair.quantile.ToString(CultureInfo.InvariantCulture);streamWriter.WriteLine(SimpleValue(familyName,quantileValuePair.value,metric.label.Concat(new[] { new LabelPair { name = "quantile", value = quantile } })));}}else if (metric.histogram != null){streamWriter.WriteLine(SimpleValue(familyName, metric.histogram.sample_sum, metric.label, "_sum"));streamWriter.WriteLine(SimpleValue(familyName, metric.histogram.sample_count, metric.label, "_count"));foreach (var bucket in metric.histogram.bucket){var value = double.IsPositiveInfinity(bucket.upper_bound) ? "+Inf" : bucket.upper_bound.ToString(CultureInfo.InvariantCulture);streamWriter.WriteLine(SimpleValue(familyName,bucket.cumulative_count,metric.label.Concat(new[] { new LabelPair { name = "le", value = value } }),"_bucket"));}}else{// not supported}}private static string WriteMetric(MetricFamily family, Metric metric, string newLine){var s = new StringBuilder();var familyName = family.name;if (metric.gauge != null){s.Append(SimpleValue(familyName, metric.gauge.value, metric.label), newLine);}else if (metric.counter != null){s.Append(SimpleValue(familyName, metric.counter.value, metric.label), newLine);}else if (metric.summary != null){s.Append(SimpleValue(familyName, metric.summary.sample_sum, metric.label, "_sum"), newLine);s.Append(SimpleValue(familyName, metric.summary.sample_count, metric.label, "_count"), newLine);foreach (var quantileValuePair in metric.summary.quantile){var quantile = double.IsPositiveInfinity(quantileValuePair.quantile)? "+Inf": quantileValuePair.quantile.ToString(CultureInfo.InvariantCulture);s.Append(SimpleValue(familyName,quantileValuePair.value,metric.label.Concat(new[] { new LabelPair { name = "quantile", value = quantile } })), newLine);}}else if (metric.histogram != null){s.Append(SimpleValue(familyName, metric.histogram.sample_sum, metric.label, "_sum"), newLine);s.Append(SimpleValue(familyName, metric.histogram.sample_count, metric.label, "_count"), newLine);foreach (var bucket in metric.histogram.bucket){var value = double.IsPositiveInfinity(bucket.upper_bound) ? "+Inf" : bucket.upper_bound.ToString(CultureInfo.InvariantCulture);s.Append(SimpleValue(familyName,bucket.cumulative_count,metric.label.Concat(new[] { new LabelPair { name = "le", value = value } }),"_bucket"), newLine);}}else{// not supported}return s.ToString();}private static string WithLabels(string familyName, IEnumerable<LabelPair> labels){var labelPairs = labels as LabelPair[] ?? labels.ToArray();if (labelPairs.Length == 0){return familyName;}return string.Format("{0}{{{1}}}", familyName, string.Join(",", labelPairs.Select(l => string.Format("{0}=\"{1}\"", l.name, l.value))));}private static string SimpleValue(string family, double value, IEnumerable<LabelPair> labels, string namePostfix = null){return string.Format("{0} {1}", WithLabels(family + (namePostfix ?? string.Empty), labels), value.ToString(CultureInfo.InvariantCulture));}private static string GetNewLineChar(NewLineFormat newLine){switch (newLine){case NewLineFormat.Auto:return Environment.NewLine;case NewLineFormat.Windows:return "\r\n";case NewLineFormat.Unix:case NewLineFormat.Default:return "\n";default:throw new ArgumentOutOfRangeException(nameof(newLine), newLine, null);}}private static void Append(this StringBuilder sb, string line, string newLineChar){sb.Append(line + newLineChar);}}
  • MetricsPrometheusTextOutputFormatter.cs

    public class MetricsPrometheusTextOutputFormatter : IMetricsOutputFormatter{private readonly MetricsPrometheusOptions _options;public MetricsPrometheusTextOutputFormatter(){_options = new MetricsPrometheusOptions();}public MetricsPrometheusTextOutputFormatter(MetricsPrometheusOptions options) { _options = options ?? throw new ArgumentNullException(nameof(options)); }/// <inheritdoc/>public MetricsMediaTypeValue MediaType => new MetricsMediaTypeValue("text", "vnd.appmetrics.metrics.prometheus", "v1", "plain");/// <inheritdoc/>public async Task WriteAsync(Stream output,MetricsDataValueSource metricsData,CancellationToken cancellationToken = default(CancellationToken)){if (output == null){throw new ArgumentNullException(nameof(output));}using (var streamWriter = new StreamWriter(output)){await streamWriter.WriteAsync(AsciiFormatter.Format(metricsData.GetPrometheusMetricsSnapshot(_options.MetricNameFormatter), _options.NewLineFormat));}}}

新建好以上两个文件后我们接着需要修改Program.cs文件,具体内容如下:

        public static IWebHost BuildWebHost(string[] args){Metrics = AppMetrics.CreateDefaultBuilder().OutputMetrics.AsPrometheusPlainText().Build();return WebHost.CreateDefaultBuilder(args).ConfigureMetrics(Metrics).UseMetrics(options =>{options.EndpointOptions = endpointsOptions =>{endpointsOptions.MetricsTextEndpointOutputFormatter = new MetricsPrometheusTextOutputFormatter();};}).UseStartup<Startup>().Build();}

完成以上操作后我们可以启用应用,此时可以看到不断用请求到/metrics-text表示已经开始采集指标了。

4. 指标可视化

此时我们打开Grafana文件夹,通过其中的bin目录下的grafana-server.exe启动服务,然后访问localhost:3000利用初始账户密码进行登录(admin/admin)。
进入后添加Prometheus数据源。由于AppMetrics已经提供了对应的看板所以我们可以通过ID2204直接导入,并选择正确的数据源就可以看到最终的效果了。

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

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

相关文章

matlab贝叶斯优化工具箱_经济学人的神器——BEAR(贝叶斯估计、分析和回归工具包)...

武林至尊&#xff0c;宝刀屠龙&#xff0c;号令天下&#xff0c;莫敢不从&#xff0c;倚天不出&#xff0c;谁与争锋。——金庸《倚天屠龙记》Bayesian Estimation, Analysis and Regression(简写为BEAR) Toolbox是欧央行(ECB)发布的一个综合 (面板)VAR工具箱&#xff0c;通过图…

计算机网络----wireshark抓包

计算机网络----wireshark抓包 抓包截图&#xff1a; 先了解&#xff0c;后期将细讲&#xff01;&#xff01;&#xff01; 如果存在什么问题&#xff0c;欢迎批评指正&#xff01;谢谢&#xff01;

如何为.NETCore安装汉化包智能感知

引言具体不记得是在群里还是什么地方有人问过&#xff0c;.NETCore有没有汉化包&#xff0c;答案是有&#xff0c;目前微软已经为我们提供了.NETCore多种语言的语言包。下面看看如何安装与使用吧。本文介绍&#xff1a;如何安装这些下载的本地化版本。如何修改Visual Studio安装…

[召集令]-Dijkstra的单源最短路径算法

2021.3.10 题目背景 墨家家主发出召集令&#xff0c;所有弟子得迅速到指定地点集合。 题目描述 给定一张地图&#xff0c;含有n个地点(n<10000)&#xff0c;地点从1开始编号&#xff0c;地图上还含有m条单向路(m<100000)连接着这些地点&#xff0c;墨家家主在1号位置&am…

word List 43

word List 43 如果存在什么问题&#xff0c;欢迎批评指正&#xff01;谢谢&#xff01;

小微技术团队的DevOps体系折腾之路(顺带发布52ABP新版本)

加个“星标★”&#xff0c;好文必达&#xff01;本想纯粹说下52ABP新版本发布&#xff0c;但是写着写着,过去的时间更多折腾的是DevOps体系的建设。这篇同样适用于小公司或者个人开发者脱离人肉运维的方法。说下52ABP&#xff0c;我们发布了新版本&#xff0c;版本号3.1.0&…

word List44

word List44 如果存在什么问题&#xff0c;欢迎批评指正&#xff01;谢谢&#xff01;

python序列数据类型_Python 数据类型 之 序列类型

序列&#xff1a;表示索引为非负整数的有序对象集合&#xff0c;所有序列都支持迭代 序列类型有&#xff1a;字符串&#xff0c;列表&#xff0c;元组 三种 字符串也是一种序列 列表和元组是任意python对象的序列或叫有序集合 字符串和元组不可变序列&#xff0c;列表支持插入、…

word List 45

word List 45 One of the pressing problems our nation faces today is how to strike a balance between economic development and environmental protection. 我们国家当今面临的最紧迫的问题之一就是如何平衡经济发展和环境保护之间的关系。 Corruption has existed since…

2020 年 中国.NET开发者调查报告

微信公众号dotnet跨平台2020年初做的一个关于中国.NET开发者调查收到了开发者近 1400 条回复。这份调查报告涵盖了开发者工具链的所有部分&#xff0c;包括编程语言、应用架构、应用服务器、运行时平台、框架技术、框架配置、IDE、.NET/.NET Core 发行版部署模式、构建工具和Ku…

列名无效如何解决_XSKY ClickHouse如何实现存算分离

在介绍ClickHouse之前&#xff0c;说一下OLAP。OLAP也叫联机分析处理(Online Analytical Processing)。OLAP系统以维度模型来存储历史数据&#xff0c;其主要存储描述性的数据并且在结构上都是同质的。01ClickHouseOLAP应用有如下特点&#xff1a;1、大多数的请求是读请求&…

word List 46

word List 46 如果存在什么问题&#xff0c;欢迎批评指正&#xff01;谢谢&#xff01;

.NET Core开发实战(第6课:作用域与对象释放行为)--学习笔记(下)

06 | 作用域与对象释放行为接下来&#xff0c;把服务切换为单例模式&#xff0c;通过工厂的方式services.AddSingleton<IOrderService>(p > new DisposableOrderService());启动程序&#xff0c;输出如下&#xff1a;1 2 接口请求处理结束可以看到代码实际上不会被释放…

C++实现邻接表存储的图及bfs遍历

#include <iostream> #include <queue> using namespace std; typedef char VerTexType; #define MVNum 100 typedef char OtherInfo; bool vis[MVNum];//邻接表 typedef struct ArcNode {int adjvex;struct ArcNode *nextarc;OtherInfo info; } ArcNode;//弧(边)t…

cmd小游戏_使用pygame制作Flappy bird小游戏

原文链接&#xff1a;【Python】使用Pygame做一个Flappy bird小游戏&#xff08;一&#xff09;​mp.weixin.qq.com最近看到很多大佬用强化学习玩Flappy bird。所以打算也上手玩一玩&#xff0c;但是苦于没找到pc上的这个游戏&#xff0c;找了点资料&#xff0c;发现并不是很难…

word List 47

word List 47 如果存在什么问题&#xff0c;欢迎批评指正&#xff01;谢谢&#xff01;

使用BeetleX构建基础的SSL网络通讯

BeetleX的使用非常简单&#xff0c;通过Stream的数据流模式可以让你轻松处理网络数据&#xff1b;在处理SSL加密通讯的时候组件的使用也是非常方便&#xff0c;只需要简单的配置证书即可完成基于SSL的网络安全通讯&#xff0c;接下来介绍一下通过组件快速构建一个安全可靠的网络…

调用其他app 的lib_ButterKnife执行效率为什么比其他注入框架高?它的原理是什么...

面试官: ButterKnife为什么执行效率为什么比其他注入框架高&#xff1f;它的原理是什么心理分析&#xff1a; ButterKnife框架一直都是使用&#xff0c;很少又开发者对butterknife深入研究的&#xff0c;既然你是面试Android高级岗位&#xff0c;自然需要有相应被问到原理的准备…

算法---会议最大安排问题

算法—会议最大合理安排问题 参考&#xff1a;趣学算法 代码&#xff1a; #include <stdio.h> #include <stdlib.h> typedef struct meet {int beg;//开始int end;//结束int num;//会议编号 }meet; int cmp44(meet m1,meet m2) {//越早结束的越优先&#xff0c;…

小cookie,大智慧

Cookie是什么&#xff1f;cookies是你访问网站时创建的数据片段文件&#xff0c;通过保存浏览信息&#xff0c;它们使你的在线体验更加轻松。使用cookies&#xff0c;可以使你保持在线登录状态&#xff0c;记录你的站点偏好&#xff0c;并为你提供本地化支持。First-party cook…