前言

小明是个单纯的.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. Linux samba的配置和使用

    samba是什么 samba是什么?能干什么?什么场合需要用到它? samba 是基于SMB协议(ServerMessage Block,信息服务块)的开源软件,samba也可以是SMB协议的商标.S ...

  2. mariadb5.5.56二进制离线安装

    在生产环境中一般使用发布好的二进制版本,简单概括一下安装过程: 1. 下载 地址为:https://downloads.mariadb.org/mariadb/5.5.56/ 这里选择最新版本的5.5 ...

  3. 技术如何通过API接口获取自己想要同款商品的数据

    确定数据源: 首先,你需要确定哪些平台或服务提供商提供了你感兴趣的商品数据.例如,电商平台.品牌商.市场调研公司等. 了解API文档: 访问提供商的开发者门户网站,阅读API文档.文档会详细介绍如何使 ...

  4. 【测试平台开发】——01Vue前端框架实操

    一.VScode官网地址 https://code.visualstudio.com/ 但是官网下载贼慢,需要修改下国内地址: 原地址:https://az764295.vo.msecnd.net/s ...

  5. LOTO示波器统计曲线和故障分析pass/fail测试

    LOTO示波器统计曲线和故障分析pass/fail测试 虚拟示波器可以应用在工业自动化检测中,除了常规的检测波形和测量值参数以外,由多个行业客户定制和验证的统计曲线和故障分析(pass/fail)功能 ...

  6. python之re库,正则表达

    一.前言 为什么要学re库呢?这里主要学他的正则表达,在编写安全脚本的时候肯定要遇到一些不规则的匹配规则,当然编写爬虫也少不了正则匹配去找到一些具有特殊特征的字符串.因此这是十分必要的,然而.re库使 ...

  7. docker 参数'--privileged' 的作用

    在Docker中,--privileged 参数给予容器内的进程几乎相同的权限,就像它们在宿主机上一样.这意味着容器可以访问宿主机的所有设备,并且可以执行一些通常需要高级权限的操作,例如加载内核模块. ...

  8. t-io 学习笔记(一)

        基础介绍理解篇 序:本文也是在t-io官网学习的基础上写的理解学习笔记:1.什么是t-io? t-io是基于JVM的网络编程框架,和netty属同类,所以netty能做的t-io都能做,考虑到 ...

  9. Hugging Face NLP课程学习记录 - 2. 使用 Hugging Face Transformers

    Hugging Face NLP课程学习记录 - 2. 使用 Hugging Face Transformers 说明: 首次发表日期:2024-09-19 官网: https://huggingfa ...

  10. Google 应用出海计划 | 指南针 第六期强势回归启动报名