日志记录是任何应用程序的重要组成部分。 任何测井系统都要经历三个主要的演变步骤。 第一个是输出到控制台,第二个是记录到文件和用于结构化记录的框架的外观,第三个是分布式记录或在单个中心收集各种服务的日志。
如果日志记录井井有条,则可以让您了解发生错误的原因,时间和方式,并将必要的信息传达给必须纠正这些错误的人员。 对于一个在190个国家/地区的10个数据中心每秒发送10万条消息并且每天有350名工程师进行部署的系统而言,日志记录系统尤其重要。
Ivan Letenko是
Infobip的团队负责人兼开发人员。 为了解决如此巨大的负载下微服务架构中的集中处理和日志跟踪问题,该公司尝试了ELK,Graylog,Neo4j和MongoDB堆栈的各种组合。 结果,经过大量的耙齿之后,他们在Elasticsearch上编写了日志服务,而PostgreSQL被用作获取更多信息的数据库。
在详细情况下,带有示例和图形:系统的体系结构和演进,耙,日志和跟踪,指标和监视,与Elasticsearch集群一起使用并使用有限的资源进行管理的实践。
为了向您介绍背景信息,我将向您简要介绍一下该公司。 我们帮助客户组织向其客户传递消息:出租车服务消息,银行关于取消的短信或输入VC时的一次性密码。 每天有
350百万条消息通过190个国家/地区的客户。 我们接受,处理,开票,路由,调整,发送给操作员,并以相反的方向处理交付报告并形成分析。
为了使所有这些工作量如此大,我们拥有:
- 全球36个数据中心;
- 5000多个虚拟机
- 350多名工程师;
- 730多种不同的微服务。
这是一个复杂的系统,没有一个大师可以单手理解整个规模。 我们公司的主要目标之一是为企业提供新功能和新版本的高速交付。 在这种情况下,一切都应该起作用,而不是掉下来。 我们正在努力:2017年部署40,000个,2018年部署80,000个,每天300个部署。
我们有350名工程师-事实证明,
每位工程师每天都会部署一些东西 。 就在几年前,公司中只有一个人拥有这样的生产力-我们的首席工程师克雷希米尔(Kreshimir)。 但是,我们确保每个工程师在按下Deploy按钮或运行脚本时都像Kresimir一样充满信心。
为此需要什么? 首先,要
确信我们了解系统中正在发生的事情以及它处于什么状态。 在事件发生时以及在代码开发过程中,向系统提出问题并找出问题原因的能力赋予了信心。
为了获得这种信任,我们在
可观察性上进行了投资。 传统上,该术语包含三个部分:
我们将讨论这个。 首先,让我们看一下我们的日志记录解决方案,但我们还将介绍指标和跟踪。
发展历程
几乎所有的日志记录应用程序或系统,包括我们的日志记录系统,都经历了几个发展阶段。
第一步是
输出到控制台 。
其次-我们开始
将日志写入文件 ,出现了用于结构化输出到文件的
框架 。 我们通常使用Logback,因为我们生活在JVM中。 在此阶段,将出现对文件的结构化日志记录,了解不同的日志应具有不同的级别,警告和错误。
一旦
我们的服务或其他服务
有多个 实例,就会出现开发人员和支持人员
集中访问日志的任务。 我们继续进行分布式日志记录-我们将各种服务组合到一个日志记录服务中。
分布式日志
最著名的选择是ELK堆栈:Elasticsearch,Logstash和Kibana,但是我们选择了
Graylog 。 它具有一个很酷的界面,专门用于日志记录。 免费版本中已经开箱即用了警报,例如,在Kibana中则没有。 对我们来说,这是一个很好的日志选择,而且在内部是相同的Elasticsearch。
在Graylog中,您可以构建警报,Kibana之类的图表,甚至记录指标。问题所在
我们的公司正在成长,在某个时候,很明显Graylog出了点问题。
负载过大 。 存在性能问题。 许多开发人员开始使用Graylog的出色功能:他们建立了执行数据聚合的指标和仪表板。 这不是在繁重的记录负载下在Elasticsearch集群上构建复杂分析的最佳选择。
碰撞 有很多团队,没有单一的计划。 传统上,当一个ID长时间打到Graylog时,会自动发生映射。 如果另一个团队决定应该将UUID编写为字符串-这将破坏系统。
首要决定
分开的应用程序日志和通信日志 。 不同的日志具有不同的方案和应用方法。 例如,存在一些应用程序日志,不同的团队对于这些应用程序对不同的参数有不同的要求:根据系统中的存储时间,取决于搜索速度。
因此,我们要做的第一件事是将应用程序日志和通信日志分开。 第二种类型是重要的日志,用于存储有关平台与外界的交互以及平台内的交互的信息。 我们将进一步讨论。
用指标替换了大部分日志 。 在我们公司中,标准选择是Prometheus和Grafana。 一些团队使用其他解决方案。 但是重要的是,我们要摆脱掉大量在Graylog内部具有汇总功能的仪表板,然后将所有内容转移到Prometheus和Grafana。 这大大减轻了服务器上的负载。
让我们看一下应用日志,指标和跟踪的方案。
日志
高维度,调试和研究 。 什么是好日志?
日志是我们记录的事件。
它们可以具有较大的维度:您可以记录请求ID,用户ID,请求属性和其他数据,但其大小不受限制。 它们也有助于调试和研究,向系统询问发生了什么问题,并寻找原因和结果。
指标
低维度,聚合,监视和警报 。 所有度量标准收集系统都包含时间序列数据库。 这些数据库在聚合方面表现出色,因此指标适用于聚合,监视和构建警报。
指标对数据维度非常敏感。
对于度量,数据的维数不应超过一千。 如果我们添加一些请求ID,但其中的值的大小不受限制,那么我们将很快遇到严重的问题。 我们已经踩到了这把耙子。
相关和跟踪
日志应相互关联。
结构化日志不足以让我们方便地按数据搜索。 字段中应包含某些值:请求ID,用户ID和来自日志的服务中的其他数据。
传统解决方案是在系统入口处为事务(日志)分配唯一的ID。 然后,此ID(上下文)将通过服务内或服务之间的调用链在整个系统中转发。
关联和跟踪。有公认的条款。 跟踪分为多个范围,并演示了一项服务相对于另一项服务的调用堆栈,一个方法相对于另一项相对于时间线的调用堆栈。 您可以清楚地跟踪所有时间的消息路径。
首先,我们使用Zipkin。 早在2015年,我们就对这些解决方案进行了概念验证(试点项目)。
分布式跟踪为了获得这样的图像,
需要检测代码 。 如果您已经在使用现有的代码库,则需要进行遍历-需要进行更改。
为了获得全貌并从跟踪中受益,您需要对
链中的所有服务进行检测 ,而不仅仅是当前正在使用的一项服务。
这是一个功能强大的工具,但是需要大量的管理和硬件成本,因此我们从Zipkin切换到了另一个解决方案,该解决方案由“即服务”提供。
交货报告
日志应相互关联。 痕迹也必须关联。 我们需要一个ID-可以在整个呼叫链中转发的公共上下文。 但是通常这是不可能的-
系统运行会在系统内发生关联 。 当我们开始一个或多个事务时,我们仍然不知道它们是单个大整体的一部分。
考虑第一个例子。
交货报告。- 客户发送了一条消息请求,我们的内部平台对其进行了处理。
- 与操作员进行交互的服务将该消息发送给操作员-日志系统中出现一个条目。
- 稍后,操作员向我们发送交货报告。
- 处理服务不知道此传递报告与哪个消息有关。 稍后将在我们的平台中创建此关系。
两个相关的交易是单个整体交易的一部分。 此信息对于支持工程师和集成开发人员非常重要。 但这完全不可能基于单个跟踪或单个ID看到。
第二种情况是相似的-客户端以大包形式向我们发送消息,然后我们将其拆解,然后它们也分批返回。 包装的数量甚至可能有所不同,但随后将它们全部合并。

从客户的角度来看,他发送了一条消息并收到了回复。 但是我们有几个需要合并的独立交易。 事实证明是一对多的关系,并带有交付报告(一对一)。 这本质上是一个图。
我们正在建立一个图形。一旦我们看到一个图,那么一个适当的选择就是图数据库,例如Neo4j。 之所以选择是显而易见的,是因为Neo4j在会议上提供了很酷的T恤和免费书籍。
Neo4j
我们实施了概念验证:这是一个16核主机,可以处理1亿个节点和1.5亿个链接的图形。 该图仅占用15 GB的磁盘-然后它适合我们。
我们的决定。 日志架构。除了Neo4j,我们现在还有一个用于查看相关日志的简单界面。 工程师和他一起看到了整个画面。
但是很快,我们对该数据库感到失望。
Neo4j的问题
数据轮换 。 我们拥有强大的容量,必须轮换数据。 但是,当从Neo4j删除节点时,不会清除磁盘上的数据。 我必须构建一个复杂的解决方案并完全重建图形。
表现 。 所有图形数据库都是只读的。 录制时,性能明显下降。 我们的情况恰恰相反:我们写很多书,而很少读-这些是每秒甚至每分钟的请求单位。
高可用性和集群分析需要付费 。 在我们的规模上,这转化为可观的成本。
因此,我们走了另一条路。
PostgreSQL解决方案
我们决定,由于我们很少阅读,因此可以在阅读时动态创建图表。 因此,我们在PostgreSQL关系数据库中以一个带有两列和两个索引的简单板的形式存储ID的邻接列表。 当请求到达时,我们使用熟悉的DFS算法(深度遍历)绕过连接图,并获取所有关联的ID。 但这是必要的。
数据轮换也很容易解决。 对于每一天,我们都会开始一个新的图板,几天之后,我们将其删除并释放数据。 一个简单的解决方案。
我们现在在PostgreSQL中有8.5亿个连接,它们占用100 GB的磁盘。 我们以每秒3万的速度写入那里,为此,在数据库中只有两个具有2个CPU和6 GB RAM的VM。 根据需要,PostgreSQL可以快速编写long。
还有一些用于服务本身的小型机器可以旋转和控制。
我们的架构如何发生变化。Graylog面临的挑战
该公司发展壮大,出现了新的数据中心,即使采用了通信日志解决方案,负载也显着增加。 我们认为Graylog不再是完美的。
统一方案和集中化 。 我想在10个数据中心中使用一个群集管理工具。 同样,出现了统一数据映射方案的问题,因此没有冲突。
API 我们使用自己的界面来显示日志之间的连接,而标准的Graylog API并不总是很方便使用,例如,当您需要显示来自不同数据中心的数据时,请正确地对其进行排序和标记。 因此,我们希望能够根据需要更改API。
性能,很难评估损失 。 我们的流量是每天3 TB的日志,这是不错的。 因此,Graylog并非总是稳定运行,因此有必要深入了解其内部以了解失败原因。 事实证明,我们不再将其用作工具-我们必须对此做些事情。
处理延迟(队列) 。 我们不喜欢Graylog中队列的标准实现。
需要支持MongoDB 。 Graylog拖累了MongoDB,也有必要管理该系统。
我们意识到在这个阶段我们需要自己的解决方案。 也许很少有尚未使用的酷炫警报功能用于仪表板,但它们本身却更好。
我们的决定
我们已经开发了自己的日志服务。
日志服务。那时,我们已经具有服务和维护大型Elasticsearch集群的专业知识,因此我们以Elasticsearch为基础。 公司中的标准堆栈是JVM,但对于后端,我们也使用著名的Kotlin,因此我们将这种语言用于服务。
第一个问题是如何旋转数据以及如何处理映射。 我们使用固定映射。 在Elasticsearch中,最好具有相同大小的索引。 但是,有了这样的索引,我们需要以某种方式映射数据,尤其是对于多个数据中心,一个分布式系统和一个分布式状态。 有一些想法可以固定ZooKeeper,但这又是维护和代码的复杂化。
因此,我们决定简单-及时写信。
一个索引一个小时,在其他数据中心中两个索引一个小时,在第三个索引中三个小时,但是全部是及时的。 索引的大小不同,因为在晚上,流量比白天少,但总的来说,它可以工作。 经验表明,不需要任何并发症。
为了便于迁移并考虑到大量数据,我们选择了GELF协议,这是一个简单的基于TCP的Graylog协议。 因此,我们获得了Netty的GELF服务器和GELF解码器。
然后,对JSON进行编码以写入Elasticsearch。 我们使用Elasticsearch的官方Java API并用Bulk编写。
为了获得高记录速度,您需要写Bulk'ami。
这是一个重要的优化。 该API提供了Bulk处理器,该处理器可自动累积请求,然后将其发送以捆绑或随时间推移进行记录。
批量处理器问题
一切似乎都很好。 但是我们开始并意识到我们依靠Bulk处理器-这是出乎意料的。 我们无法实现我们所指望的价值观-问题来自无处。

在标准实现中,尽管存在并行设置,但Bulk处理器是单线程同步的。 那就是问题所在。
我们四处逛逛,结果发现这是一个已知的但尚未解决的错误。 我们稍微更改了Bulk处理器-通过ReentrantLock进行了显式锁定。 仅在五月份,对官方Elasticsearch存储库进行了类似的更改,并且仅从7.3版开始可用。 当前版本是7.1,我们正在使用6.3版。
如果您还使用批量处理器,并且想对Elasticsearch中的条目进行超频-请在
GitHub上查看这些
更改并将其移植回您的版本。 更改仅影响批量处理器。 如果您需要移植到以下版本,将没有任何困难。
一切都很好,Bulk处理器已经消失,速度加快了。

随着时间的推移,Elasticsearch的写入性能会不稳定,因为在那里会发生各种操作:索引合并,刷新。 此外,例如在维护过程中,当部分节点从群集中删除时,性能会降低一会儿。
在这方面,我们意识到不仅需要实现内存中的缓冲区,还需要实现队列。 我们决定只将拒绝的消息发送到队列-仅将Bulk处理器无法写入Elasticsearch的消息发送到队列。
重试后备
这是一个简单的实现。
- 我们将被拒绝的消息保存在文件
RejectedExecutionHandler
。
- 按指定的时间间隔在单独的执行程序中重新提交。
- 但是,我们不会延迟新流量。
对于支持工程师和开发人员而言,系统中的新流量显然比因Elasticsearch高峰或减速而由于某种原因而延迟的流量更为重要。 他挥之不去,但他会晚一点-没什么大不了的。 优先处理新流量。
我们的计划开始看起来像这样。现在让我们讨论一下我们如何准备Elasticsearch,我们使用了哪些参数以及如何设置。
Elasticsearch配置
我们面临的问题是需要超频Elasticsearch并对其进行优化以进行写入,因为读取次数明显减少。
我们使用了几个参数。
"ignore_malformed": true
丢弃类型错误的字段,而不是整个文档 。 即使由于某些原因,映射不正确的字段泄漏到那里,我们仍然要存储数据。 此选项并不完全与性能有关。
对于铁,Elasticsearch有细微差别。 当我们开始要求大型集群时,我们被告知,用于您的卷的SSD驱动器中的RAID阵列非常昂贵。 但是不需要阵列,因为Elasticsearch已经内置了容错和分区功能。 即使在官方网站上,也建议使用便宜的铁而不是便宜的铁。 这适用于磁盘和处理器核心的数量,因为整个Elasticsearch的并行性非常好。
"index.merge.scheduler.max_thread_count": 1
建议用于HDD 。
如果您没有获得SSD,而是普通的HDD,那么请将此参数设置为1。 索引是分段编写的,然后将其冻结。 这样可以节省一点磁盘空间,但是最重要的是,可以加快搜索速度。 另外,当您停止写入索引时,可以执行
force merge
。 当群集上的负载较小时,它将自动冻结。
"index.unassigned.node_left.delayed_timeout": "5m"
-
节点消失时的部署延迟 。 如果节点重新引导,部署或撤回以进行维护,则这段时间过后Elasticsearch将开始实施索引和数据。 但是,如果磁盘和网络上的负担很重,则部署将很困难。 为了不使它们过载,此超时更好地控制和了解需要什么延迟。
"index.refresh_interval": -1
-
"index.refresh_interval": -1
如果没有搜索查询,则不更新索引 。 然后,当搜索查询出现时,索引将被更新。 可以以秒和分钟为单位设置该索引。
"index.translogDurability": "async"
-对每个请求或按时间执行fsync的频率。 提高慢速驱动器的性能。
我们还有一种有趣的使用方式。 支持人员和开发人员希望能够全文搜索并在整个邮件正文中使用regexp'ov。 但是在Elasticsearch中这是不可能的-它只能按其系统中已经存在的令牌进行搜索。 可以使用RegExp和通配符,但是令牌不能以某些RegExp开头。 因此,我们在过滤器中添加了
word_delimiter
:
"tokenizer": "standard" "filter" : [ "word_delimiter" ]
它会自动将单词拆分为标记:
- “ Wi-Fi”→“ Wi”,“ Fi”;
- “ PowerShot”→“ Power”,“ Shot”;
- “ SD500”→“ SD”,“ 500”。
以类似的方式编写类的名称以及各种调试信息。 有了它,我们解决了全文搜索中的一些问题。 我建议您在使用登录名时添加此类设置。
关于集群
分片的数量应等于用于负载平衡的数据节点的数量 。 副本的最小数量为1,则每个节点将具有一个主碎片和一个副本。 但是,如果您拥有有价值的数据(例如金融交易),则最好选择2个或更多。
碎片的大小从几GB到几十GB 。 当然,每1 GB Elasticsearch髋关节上,节点上的分片数量不超过20。 Elasticsearch进一步放慢了速度-我们也对其进行了攻击。 在那些流量很少的数据中心中,数据并没有大量旋转,出现了数千个索引,并且系统崩溃了。
例如,在服务的情况下,使用管理程序的名称来
使用 allocation awareness
。 帮助在不同的管理程序之间分散索引和分片,以便在管理程序退出时不会重叠。
预先创建索引 。 好的做法,尤其是按时写作时。 索引立即很热,准备就绪,没有延迟。
限制每个节点一个索引的分片数量 。
"index.routing.allocation.total_shards_per_node": 4
是每个节点一个索引的最大分片数。 在理想情况下,有2个,如果我们仍然有较少的汽车,我们放4个以防万一。
这是什么问题? 我们使用
allocation awareness
-Elasticsearch知道如何在虚拟机管理程序之间正确
allocation awareness
索引。 但是我们发现,在节点关闭了很长一段时间然后又回到集群之后,Elasticsearch看到它上面的索引正式减少了,并且可以对其进行还原。 在数据同步之前,节点上的索引很少。 如有必要,分配一个新索引,Elasticsearch尝试使用新索引尽可能紧密地锤击该机器。 因此,节点不仅会从数据复制到节点的事实中获得负载,还会从该节点上获取新的流量,索引和新数据。 控制并限制它。
Elasticsearch维护建议
与Elasticsearch合作的人员都熟悉这些建议。
在计划的维护期间,请应用有关滚动升级的建议:禁用分片分配,同步刷新。
禁用分片分配 。 禁用副本分片的分配,保留仅分配主副本的能力。 这显然对Elasticsearch有所帮助-它不会重新分配不需要的数据。 例如,您知道节点将在半小时内上升-为什么将所有分片从一个节点转移到另一个节点? 如果只有主要分片可用,那么如果您与黄色群集一起生活了半小时,那就不会发生任何可怕的事情。
同步冲洗 。 在这种情况下,节点返回群集时的同步速度要快得多。
由于写入索引或恢复的工作量很大,因此可以减少副本数。
如果您下载大量数据(例如,峰值负载),则可以关闭分片,然后在负载已经较小时向Elasticsearch发送命令以创建它们。
这是我要使用的一些命令:
GET _cat/thread_pool?v
使您可以在每个节点上看到thread_pool
:现在很热,什么是写和读队列。
GET _cat/recovery/?active_only=true
哪些索引部署到恢复发生的位置。
GET _cluster/allocation/explain
以一种方便的人类形式解释为什么以及哪些索引或副本未分配。
为了进行监视,我们使用Grafana。
Vincent van Hollebeke有出色的
导出程序和Grafana团队
合作 ,可让您直观地查看群集的状态及其所有主要参数。 从包装盒部署时,我们将其添加到了Docker映像和所有指标中。
记录结论
日志应为:
- 集中式 -开发人员的单一入口;
- 可用 -快速搜索的能力;
- 结构化 -用于快速方便地提取有价值的信息;
- 相关联 -不仅彼此之间,而且还与您使用的其他指标和系统相关。
瑞典
Melodifestivalen竞赛最近举行了。 这是来自瑞典的欧洲电视网的代表。 比赛前,我们的支持服务人员与我们联系:“现在在瑞典,工作量很大。 流量非常敏感,我们想关联一些数据。 您在日志中缺少Grafana仪表板上的数据。 我们有可以从Prometheus获取的指标,但是我们需要有关特定ID请求的数据。”
他们添加了Elasticsearch作为Grafana的来源,并且能够关联此数据,解决问题并足够快地获得良好结果。
利用自己的解决方案要容易得多。
现在,我们有几个服务,而不是用于该解决方案的10个Graylog集群。 这是10个数据中心,但我们甚至没有专门的团队和为他们提供服务的人员。 有几个人在处理它们,并根据需要进行更改。 这个小团队完美地集成到了我们的基础架构中-部署和维护更容易,更便宜。
分开案例并使用适当的工具。
这些是用于日志记录,跟踪和监视的单独工具。 没有可以满足您所有需求的“黄金工具”。
要了解需要哪种工具,要监视什么,在哪里使用日志以及对日志有什么要求,您绝对应该转向
SLI / SLO-服务水平指示器/服务水平目标。 您需要知道什么对您的客户和您的业务很重要,他们看待什么指标。
一周后,SKOLKOVO将举办HighLoad ++ 2019 。 在11月7日晚上,伊万·莱滕科(Ivan Letenko) 将告诉您他如何在产品上与Redis一起生活,该程序中总共有150份有关各种主题的报告。
如果您在访问HighLoad ++ 2019 Live时遇到问题,我们有个好消息。 今年的会议将在莫斯科,新西伯利亚和圣彼得堡三个城市同时举行。 在同一时间。 将会如何以及如何到达-在活动的单独促销页面上查找。