前言

小明是个单纯的.NET开发,一天大哥叫住他,安排了一项任务:

“小明,分析一下我们超牛逼网站上个月的所有AWS ELB流量日志,这些日志保存在AWS S3上,你分析下,看哪个API的响应时间中位数最长。”

“对了,别用Excel,哥给你写好了一段Python脚本,可以自动解析统计一个AWS ELB文件的日志,你可以利用一下。”

“好的,大哥真厉害!”。

小明看了一下,然后傻眼了,在管理控制台中,九月份AWS ELB日志文件翻了好几页都没翻完,大概算算,大概有1000个文件不止。想想自己又不懂Python,又不是搞数据分析专业出身的,这个“看似简单”的工作完不成,这周怕是陪不了女朋友,搞不好还要996.ICU,小明几乎要流下了没有技术的泪水……

不怕!会.NET就行!

要完成这项工作,光老老实实将文件从管理控制台下载到本地,估计都够喝一壶。若小明稍机灵点,他可能会找到AWS S3的文件管理器,然后……发现只有付费版才有批量下载功能。

其实要完成这项工作,只需做好两项基本任务即可:

  • AWS S3下载9月份的所有ELB日志
  • 聚合并分析这1000多个日志文件,然后按响应时间中位数倒排序

AWS资源

能在管理控制台上看到的AWS资源,AWS都提供了各语言的SDK可供操作(可在SDK上操作的东西,如批量下载,反倒不一定能在界面上看到)。SDK支持多种语言,其中(显然)也包括.NET

对于AWS S3的访问,Amazon提供的NuGet包叫:AWSSDK.S3,在Visual Studio中下载并安装,即可运行本文的示例。

要使用AWSSDK.S3,首先需要实例化一个AmazonS3Client,并传入aws access keyaws secret keyAWS区域等参数:

var credentials = new BasicAWSCredentials(
Util.GetPassword("aws_live_access_key"),
Util.GetPassword("aws_live_secret_key"));
var s3 = new AmazonS3Client(credentials, RegionEndpoint.USEast1);

注意:本文的所有代码全部共享这一个s3的实例。因为根据文档,AmazonS3Client实例是设计为线程安全的。

在下载AWS S3的文件(对象)之前,首先需要知道有哪些对象可供下载,可通过ListObjectsV2Async方法列出某个bucket的文件列表。注意该方法是分页的,经我的测试,无论MaxKeys参数设置多大,该接口最多一次性返回1000条数据,但这显然不够,因此需要循环分页去拿。

分页时该响应对象中包含了NextContinuationTokenIsTruncated属性,如果IsTruncated=true,则NextContinuationToken必定有值,此时下次调用ListObjectsV2Async时的请求参数传入NextContinuationToken即可实现分页获取S3文件列表的功能。

这个过程说起来有点绕,但感谢C#提供了yield关键字来实现协程-coroutine,代码写起来非常简单:

IEnumerable<List<S3Object>> Load201909SuperCoolData(AmazonS3Client s3)
{
ListObjectsV2Response response = null;
do
{
response = s3.ListObjectsV2Async(new ListObjectsV2Request
{
BucketName = "supercool-website",
Prefix = "AWSLogs/1383838438/elasticloadbalancing/us-east-1/2019/09",
ContinuationToken = response?.NextContinuationToken,
MaxKeys = 100,
}).Result;
yield return response.S3Objects;
} while (response.IsTruncated);
}

注意:Prefix为前缀,AWS ELB日志都会按时间会有一个前缀模式,从文件列表中找到这一模式后填入该参数。

接下来就简单了,通过GetObjectAsync方法即可下载某个对象,要直接分析,最好先转换为字符串,拿到文件流stream后,最简单的方式是使用StreamReader将其转换为字符串:

IEnumerable<string> ReadS3Object(AmazonS3Client s3, S3Object x)
{
using GetObjectResponse obj = s3.GetObjectAsync(x.BucketName, x.Key).Result;
using var reader = new StreamReader(obj.ResponseStream);
while (!reader.EndOfStream)
{
yield return reader.ReadLine();
}
}

注意:

  1. GetObjectAsync方法返回的GetObjectResponse类实现了IDisposable接口,因为它的ResponseStream实际上是非托管资源,需要单独释放。因此需要使用using关键字来实现资源的正确释放。
  2. 可以直接调用StreamReader.ReadToEnd()方法直接获取全部字符串,然后再通过Split将字符串按行分隔,但这样会浪费大量内存,影响性能。

这时一般会将这个stream缓存到本地磁盘以供慢慢分析,但也可以一鼓作气直接将该stream转换为字符串直接分析。本文将采取后者做法。

分析1000多个文件

每个ELB日志文件的格式如下:

2019-08-31T23:08:36.637570Z SUPER-COOLELB 10.0.2.127:59737 10.0.3.142:86 0.000038 0.621249 0.000041 200 200 6359 291 "POST http://super-coolelb-10086.us-east-1.elb.amazonaws.com:80/api/Super/Cool HTTP/1.1" "-" - -
2019-08-31T23:28:36.264848Z SUPER-COOLELB 10.0.3.236:54141 10.0.3.249:86 0.00004 0.622208 0.000045 200 200 6359 291 "POST http://super-coolelb-10086.us-east-1.elb.amazonaws.com:80/api/Super/Cool HTTP/1.1" "-" - -

可见该日志有一定格式,Amazon提供了该日志的详细文档中文说明:https://docs.aws.amazon.com/zh_cn/elasticloadbalancing/latest/application/load-balancer-access-logs.html#access-log-entry-format

根据文档,这种日志可以通过按简单的空格分隔来解析,但后面的RequestInfoUserAgent字段稍微麻烦点,这种可以使用正则表达式来实现比较精致的效果:

public static LogEntry Parse(string line)
{
MatchCollection s = Regex.Matches(line, @"[\""].+?[\""]|[^ ]+");
string[] requestInfo = s[11].Value.Replace("\"", "").Split(' ');
return new
{
Timestamp = DateTime.Parse(s[0].Value),
ElbName = s[1].Value,
ClientEndpoint = s[2].Value,
BackendEndpoint = s[3].Value,
RequestTime = decimal.Parse(s[4].Value),
BackendTime = decimal.Parse(s[5].Value),
ResponseTime = decimal.Parse(s[6].Value),
ElbStatusCode = int.Parse(s[7].Value),
BackendStatusCode = int.Parse(s[8].Value),
ReceivedBytes = long.Parse(s[9].Value),
SentBytes = long.Parse(s[10].Value),
Method = requestInfo[0],
Url = requestInfo[1],
Protocol = requestInfo[2],
UserAgent = s[12].Value.Replace("\"", ""),
SslCypher = s[13].Value,
SslProtocol = s[14].Value,
};
}

LINQ

数据下载好了,解析也成功了,这时即可通过强大的LINQ来进行分析。这里将用到以下的操作符:

  • SelectMany 数据“打平”(和js数组的.flatMap方法类似)
  • Select 数据转换(和js数组的.map方法类似)
  • GroupBy 数据分组

首先,通过AWSSDKListObjectsV2Async方法,获取的是文件列表,可以通过.SelectMany方法将多个下载批次“打平”:

Load201909SuperCoolData(s3)
.SelectMany(x => x)

然后通过Select,将单个文件Key下载并读为字符串:

Load201909SuperCoolData(s3)
.SelectMany(x => x)
.SelectMany(x => ReadS3Object(s3, x))

然后再通过Select,将文件每一行日志转换为一条.NET对象:

Load201909SuperCoolData(s3)
.SelectMany(x => x)
.SelectMany(x => ReadS3Object(s3, x))
.Select(LogEntry.Parse)

有了.NET对象,即可利用LINQ进行愉快地分析了,如小明需要求,只需加一个GroupBySelect,即可求得根据Url分组的响应时间中位数,然后再通过OrderByDescending即按该数字排序,最后通过.Dump显示出来:

Load201909SuperCoolData(s3)
.SelectMany(x => x)
.SelectMany(x => ReadS3Object(s3, x))
.Select(LogEntry.Parse)
.GroupBy(x => x.Url)
.Select(x => new
{
Url = x.Key,
Median = x.OrderBy(x => x.BackendTime).ElementAt(x.Count() / 2)
})
.OrderByDescending(x => x.Median)
.Dump();

运行效果如下:

多线程下载

解析和分析都在内存中进行,因此本代码的瓶颈在于下载速度。

上文中的代码是串行、单线程下载,带宽利用率低,下载速度慢。可以改成并行、多线程下载,以提高带宽利用率。

传统的多线程需要非常大的功力,需要很好的技巧才能完成。但.NET 4.0发布了Parallel LINQ,只需极少的代码改动,即可享受到多线程的便利。在这里,只需将在第二个SelectMany后加上一个AsParallel(),即可瞬间获取多线程下载优势:

Load201909SuperCoolData(s3)
.SelectMany(x => x)
.AsParallel() // 重点
.SelectMany(x => ReadS3Object(s3, x))
.Select(LogEntry.Parse)
.GroupBy(x => x.Url)
.Select(x => new
{
Url = x.Key,
Median = x.OrderBy(x => x.BackendTime).ElementAt(x.Count() / 2)
})
.OrderByDescending(x => x.Median).Take(15)
.Dump();

注意:写AsParallel()的位置有讲究,这取决于你对性能瓶颈的把控。总的来说:

  • 太靠后了不行,因为AsParallel之前的语句都是串行的;
  • 靠前了也不行,因为靠前的代码往往数据量还没扩大,并行没意义;

扩展

到了这一步,如果小明足够机灵,其实还能再扩展扩展,将平均值,总响应时间一并求出来,改动代码也不大,只需将下方那个Select改成如下即可:

    .Select(x => new
{
Url = x.Key,
Median = x.OrderBy(x => x.BackendTime).ElementAt(x.Count() / 2),
Avg = x.Average(x => x.BackendTime),
Sum = x.Sum(x => x.BackendTime),
})

运行效果如下:

总结

看来并不需要python,有了.NETLINQ两大法宝,看来小明周末又可以陪女朋友了

喜欢的请关注我的微信公众号:【DotNet骚操作】

.NET LINQ分析AWS ELB日志的更多相关文章

  1. .NET LINQ分析AWS ELB日志避免996

    前言 小明是个单纯的.NET开发,一天大哥叫住他,安排了一项任务: "小明,分析一下我们超牛逼网站上个月的所有AWS ELB流量日志,这些日志保存在AWS S3上,你分析下,看哪个API的响 ...

  2. angular代码分析之异常日志设计

    angular代码分析之异常日志设计 错误异常是面向对象开发中的记录提示程序执行问题的一种重要机制,在程序执行发生问题的条件下,异常会在中断程序执行,同时会沿着代码的执行路径一步一步的向上抛出异常,最 ...

  3. elk收集分析nginx access日志

    elk收集分析nginx access日志 首先elk的搭建按照这篇文章使用elk+redis搭建nginx日志分析平台说的,使用redis的push和pop做队列,然后有个logstash_inde ...

  4. [日志分析] Access Log 日志分析

    0x00.前言: 如何知道自己所在的公司或单位是否被入侵了?是没人来“黑”,还是因自身感知能力不足,暂时还没发现?入侵检测是每个安全运维人员都要面临的严峻挑战.安全无小事,一旦入侵成功,后果不堪设想. ...

  5. 详细分析MySQL事务日志(redo log和undo log)

    innodb事务日志包括redo log和undo log.redo log是重做日志,提供前滚操作,undo log是回滚日志,提供回滚操作. undo log不是redo log的逆向过程,其实它 ...

  6. 【转】python模块分析之logging日志(四)

    [转]python模块分析之logging日志(四) python的logging模块是用来写日志的,是python的标准模块. 系列文章 python模块分析之random(一) python模块分 ...

  7. 详细分析MySQL事务日志(redo log和undo log) 表明了为何mysql不会丢数据

    innodb事务日志包括redo log和undo log.redo log是重做日志,提供前滚操作,undo log是回滚日志,提供回滚操作. undo log不是redo log的逆向过程,其实它 ...

  8. python模块分析之logging日志(四)

    前言 python的logging模块是用来设置日志的,是python的标准模块. 系列文章 python模块分析之random(一) python模块分析之hashlib加密(二) python模块 ...

  9. 日志分析工具、日志管理系统、syslog分析

    日志分析工具.日志管理系统.syslog分析 系统日志(Syslog)管理是几乎所有企业的重要需求.系统管理员将syslog看作是解决网络上系统日志支持的系统和设备性能问题的关键资源.人们往往低估了对 ...

  10. Eventlog Analyzer日志管理系统、日志分析工具、日志服务器的功能及作用

    Eventlog Analyzer日志管理系统.日志分析工具.日志服务器的功能及作用 Eventlog Analyzer是用来分析和审计系统及事件日志的管理软件,能够对全网范围内的主机.服务器.网络设 ...

随机推荐

  1. 在 SQLAlchemy 中实现数据处理的时候,实现表自引用、多对多、联合查询,有序id等常见的一些经验总结

    有时候,我们在使用SQLAlchemy操作某些表的时候,需要使用外键关系来实现一对多或者多对多的关系引用,以及对多表的联合查询,有序列的uuid值或者自增id值,字符串的分拆等常见处理操作. 1.在 ...

  2. Rounding

    前言 以前写过一篇关于 Rouding 的 decimal, double, float, 但有点杂乱, 这篇做一个整理. Why need rouding? 除法会诞生小数. 甚至会诞生无限小数 ( ...

  3. CSS – BEM (Block__Element--Modifier)

    前言 BEM 是一种 CSS class 命名规范. 目的是防止大家名字取太短, 不小撞名字后果很麻烦. 参考: Youtube – You Probably Need BEM CSS in Your ...

  4. C++ cout打印输出 (解决输出乱码)

    cout打印输出 输出单份内容 // 输出单份内容 cout << "Hello World!" << endl; cout << 10 < ...

  5. QT6框架也能开发Web浏览器应用程序:QT6框架如何编译运行测试WebAssembly应用程序?

    QT6框架也能开发Web浏览器应用程序:QT6框架如何编译运行测试WebAssembly应用程序? 简介 本文将介绍如何使用QT6框架开发Web浏览器应用程序,并介绍具体的编译运行测试WebAssem ...

  6. Java序列化、反序列化、反序列化漏洞

    目录 1 序列化和反序列化 1.1 概念 1.2 序列化可以做什么? 3 实现方式 3.1 Java 原生方式 3.2 第三方方式 4 反序列化漏洞 1 序列化和反序列化 1.1 概念 Java 中序 ...

  7. 致敬传奇 Kruskal 重构树题硬控我三小时

    NOI2018 归程 存边的数组拿来干两件事,忘了清空了,其实最好开两个的 dfs 没开 vis 导致不知道为什么出现的绕圈 倍增的 fa[i][j] 定义的时候前面是 \(2^{i}\) 写着写着记 ...

  8. 《Vue.js 设计与实现》读书笔记 - 第12章、组件的实现原理

    第12章.组件的实现原理 12.1 渲染组件 在渲染器内部的实现看,一个组件是一个特殊类型的虚拟 DOM 节点.之前在 patch 我们判断了 VNode 的 type 值来处理,现在来处理类型为对象 ...

  9. USB和CAN都是用差分信号来传输数据,为什么CAN的传输距离能比USB远那么多?

    USB和CAN的区别 今天在看USB项目设计实例的时候,突然想到一个问题,从而引发了一些思考.经过思考加上查阅资料,写出了这一篇文章作为记录. 问题 ​ USB和CAN都是用两条线作为差分线以差分信号 ...

  10. jwt实现登录 和 接口实现动态权限

    [Authorize]   ====   using Microsoft.AspNetCore.Authorization; 登录的 DTO namespace login; public class ...