我们将领英内部的云管理门户称之为 Nuage(法语中的“云”)。领英的开发者可以借此快速在领英的数据中心内新建数据存储,例如 Kafka 话题、 Voldemort 存储,以及 Espresso 数据库等。该产品包含一个供领英的开发者使用的 HTTP 前段,以及一个与 Kafka 等底层系统通信的 rest.li 后端服务。
与领英的底层存储和流系统类似,Nuage 本身也是一种分布式系统,包含分布在多个数据中心内的数个前端和后端。与其他任何分布式系统类似,这个系统的调试不仅困难而且需要耗费大量时间。对于某些事件,可能需要找到每个服务涉及到的所有相关计算机,从这些计算机中读取特定时段内的日志,然后进行汇总和分析。具体到 Nuage 则需要找到相关数据中心,确定该数据中心内有关的前端和后端计算机,然后提取特定时段内的日志。
为了简化 Nuage 系统的调试并降低平均恢复时间(MTTR),我们执行了日志汇总、请求标记,以及用户体验异常报表等工作。下文将介绍日志汇总的相关设置,但首先想谈谈请求标记和用户体验异常报表,因为这些是进行日志汇总的前提条件。
请求标记和异常报表
如上文所属,Nuage 包含运行在不同宿主机上的前端和后端服务。为了简化调试工作,我们希望能轻松地将前端和后端事件信息关联在一起。为此我们会在前端生成一个随机的请求 ID,将其作为 HTTP 标头传递给 Nuage 后端。前端会在日志中记录发送至后端的请求(包括请求 ID)以及所获得的响应。在收到传入的请求后,后端会检查 HTTP 标头中的请求 ID,并将该请求 ID 添加至所关联的每一条日志信息中。后一个操作是通过 log4j 的映射诊断上下文环境(Mapped Diagnostic Context,MDC)实现的。MDC 实际上是一种“每线程”的映射,可以在日志输出格式化程序(Formatter)中引用。这样我们就可以将请求 ID 包含在与该请求有关的每一条日志项中,借此可解决一系列问题:
- 当有多个线程同时写入日志时,可以更简单地判断每条日志项与哪个请求有关。
- 使用 Grep 命令在日志中查找请求 ID 可找到所有相关日志项。
- 可以更方便地分析请求 ID 并将其插入 Elasticsearch 中一个独立的列,具体做法请参阅下一节。
Webserver.java – 为请求对应的线程加入请求 ID。
import org.apache.log4j.MDC; public void onRequest(request) { String requestId = request.get(“x-req-id”); MDC.put(“requestId”, requestId); }
Log4j.xml – 将请求 ID 写入每条日志项。
<layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d{yyyy/MM/dd HH:mm:ss.SSS} [req-id-%X{requestId}] ..." /> </layout>
日志范例
除了这些宝贵的信息外,我们还设置了在前端或后端遇到非预期异常后向我们发送邮件通知,并在通知中包含方便进行调查所需的细节信息。选择邮件作为通知方式是因为这些异常本身没必要让我们立刻着手处理,现有的其他警报机制即可应对各种紧急问题。从下文的邮件范例中可以看到,我们可以直观地看到前端和后端的堆栈跟踪情况。邮件中还包含了触发这一异常的请求所对应的请求 ID,这样就可以更容易地找到日志中的所有相关日志项。
用户遇到异常后发送给 Nuage 开发团队的邮件范例。
Nuage 发现了非预期异常 收件人:nuage-debugging 请求 ID:123456 用户:lideveloper 前端信息 所发送请求: Rest.li 端点:d2://nuage ... 后端信息 响应:创建 Voldemort 存储 XYZ 时出现异常 ... 状态代码:500
这样的设置已被证明在降低我们的平均检测时间(MTTD)方面非常有用。我们不再依赖用户上报自己使用前端过程中遇到的问题,可以在问题发生后立刻开始进行调试。借助邮件中包含的信息,我们可以通过下文将要介绍的 Elasticsearch、Logstash 和 Kibana(ELK)快速着手深入分析。
使用 Elasticsearch、Logstash 和 Kibana(ELK)进行日志汇总
ELK 堆栈使得我们可以将来自多个产品和宿主机的日志汇总在一起并进行集中搜索,借此可实现很多用例,例如可以回答注入“错误 X 的发生频率和每次发生位置是什么”或“上周以来异常率有何变化”等问题。ELK 各种组件的用途如下:
- Elasticsearch:底层数据库;
- Logstash:将日志解析为结构化格式并将其放入 Elasticsearch;
- Kibana:一种对 Elasticsearch 数据进行可视化的 Web UI。
Logstash 可以在应用程序服务器上运行,或读取其他来源的日志。我们在领英内部的用法通常是在运行应用程序的服务器上使用 Kafka 生成器(Producer)发送日志,随后由 Logstash 使用 Kafka 消耗器(Consumer)读取日志,并在解析后将其发送至 Elasticsearch 集群。由于日志可以在其它计算机上处理,这种做法可以降低对服务器的影响。下图展示了 Nuage 的 ELK 工作流。
(点击放大图像)
作为我们Logstash 配置的一部分,我们会通过解析日志找到请求ID 并将其以一个单独的字段发送至Elasticsearch 的日志行中。
Logstash – 解析日志行并将请求 ID 放入相应字段。
# Add the request ID to a separate field if one is present. grok { match => ["message", "\[req[\-]id[\-]\]$"] }
借助这样的设置,直接在 Kibana 仪表盘中输入类似“request_id: ”这样简单的查询即可从前端和后端计算机获得与该请求有关的所有相关日志,这一过程中甚至不需要知道这个请求是由哪台计算机处理的。由于不再需要确定运行请求的计算机,无需通过 Grep 命令查找特定计算机的日志,无需确定特定请求相关的其他日志行还有哪些,我们的调试过程得以大幅简化,进而显著缩短了我们的 MTTR。
Kibana – 从 Elasticsearch 中获得与前端和后端有关的所有相关日志行。
搜索:request_id: 123456
结果:
日期
服务
宿主机
请求 ID
消息
…
nuage-frontend
host1
123456
用户 lidev 正在创建 Espresso 数据库
…
nuage-backend
host2
123456
正在处理来自 nuage-frontend 的新请求
…
nuage-backend
host2
123456
用户 lidev 被授权创建数据库
…
nuage-backend
host2
123456
正在将数据库定义上传至 Espresso host3
…
nuage-backend
host2
123456
数据库创建失败:到 host3 的连接超时
未来扩展
目前我们只将请求 ID 与 Nuage 前端请求和 Nuage 后端的处理和响应关联在一起。考虑到这个产品的本质,Espresso 或 Voldemort 等底层系统经常会遇到各种异常,因此我们可以对其进行一个简单的改进,让 Nuage 后端对收到的请求 ID 进行转发并在底层系统之间进行传递。随后来自该系统的响应也可以附加至调试邮件中,并可以使用 ELK,这样我们就可以在前端、后端,以及底层系统中查找与该请求 ID 有关的所有日志,在调试过程中针对日志信息获得更完整的视图。这就要求所有这些系统的日志必须全部发送至同一个 Elasticsearch 集群,或能够通过一个 Tribe 节点与多个 Elasticsearch 集群通信,这样即可在一个位置查看所有系统的日志。
结论
通过这样的 ELK 设置和请求标记,我们可以针对遇到的问题轻松查看所有相关日志。通过将这样的能力与包含触发用户体验异常的请求 ID 的邮件提醒配合使用,我们的私有云管理系统 Nuage 的 MTTD 和 MTTR 均有显著改善。如果你想进一步了解 Nuage 及其目标,建议阅读 Alex Vauthey 有关“隐形”基础架构的文章。
作者: Gustaf Helgesson ,阅读英文原文: Reducing the MTTD and MTTR of LinkedIn’s Private Cloud
感谢陈兴璐对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ , @丁晓昀),微信(微信号: InfoQChina )关注我们。
评论