全球网络安全公司DataDome是如何做到每秒在Elasticsearch中存储5000万个事件?

19-10-23 banq
                   

DataDome是一家全球网络安全公司,提供SaaS解决方案,旨在保护客户网站免受OWASP自动化威胁:凭据填充、第7层DDoS攻击、SQL注入和密集式抓取。该解决方案通过尖端的人工智能技术保护我们所有客户的漏洞点(网络,移动应用和API),提供实时的自动程序检测和自动阻止决策。

DataDome解决方案使用Apache Flink进行实时事件分析以检测新威胁,并使用Elasticsearch存储来自所有客户访问者的请求。我们从客户的Web服务器接收这些请求,并使用它们在用户仪表板中提供漫游器流量统计信息,反馈循环,业务洞察力和漫游器攻击详细信息。

几个数字:我们的集群存储了超过150 TB的数据,600亿个文档中的15万亿个事件,分布在80个节点上的3000个索引和1.5万个分片。每个文档在单独的字段中存储250个事件。

每天,在高峰期间,我们的Elasticsearch集群每秒写入超过20万个文档,搜索速度每秒超过2万个请求。我们的索引是基于每天的,并且每个客户都有一个索引,以便对数据进行逻辑分离。

分片策略挑战

一年前,我们的集群由30个专用服务器组成,这些服务器被拆分为一个hot-warm架构。我们的hot层由15台服务器组成,这些服务器具有20个线程的CPU和RAID0中的5个SSD磁盘,而我们的warm层由15台具有较低CPU的服务器组成,以降低成本,因为对warm层内部的数据的要求较少。

我们为客户提供长达30天的数据保留。前7天的数据存储在热层中,其余的存储在热层中。

常见的最佳做法是将碎片大小保持在50GB左右。如上所述,我们为每个客户都有专用的索引,但是我们所有的客户都没有相同的工作量。我们最大的客户每秒写入数万个文档,而最小的客户每秒写入数百个。此外,我们必须不断调整分片的数量以适应客户流量的变化,以遵守每个分片50GB的最佳实践。

为了解决此问题,我们引入了一项每天运行的作业,以更新映射模板并创建明天的索引,并根据客户在前一天获得的点击次数为您分配合适的分片数量。因为我们知道一个索引的文档重约1000个字节,所以我们可以预测每个索引需要的分片数量,以便遵守每个分片50 GB的最佳实践。

如果我们将文档放在不存在的索引中,则文档将触发索引的创建。但是,由于我们设计了基于每日的索引,这可能给我们的集群带来一些麻烦(黄色状态,未分配的分片……),因为我们有很多吞吐量,并且主节点需要在同一时间(午夜左右)将分片分配给从节点。

但是,由于我们的工作特点,我们可以提前(一天之前)创建索引并避免这些问题。

通过这种分片策略,我们为最大的客户提供了将近72个分片,为较小的客户提供了仅1个分片。

随着使用我们解决方案的新客户的不断涌入,我们的集群变得分崩离析,并且在读取和写入方面,性能都下降了。我们还看到数据节点上的平均负载显着增加。

因此,我们对一些搜索和写入请求进行了基准测试,发现我们的分片在一天中增长得越多,我们的搜索和写入性能下降的幅度就越大。在傍晚,当我们的流量激增并且碎片比早上更大时,我们的Elasticsearch性能特别差。 

每当节点发生故障并发生故障时,我们的集群就会遭受损失,因为重新定位一个大索引(72个50GB的分片)在写入线程,io磁盘,CPU和带宽上的开销很大,尤其是在写入期间。

我们的基准测试表明,考虑到我们的用例,文档大小,流量,索引映射和节点类型,对于我们来说,理想的分片大小约为20GB。超过此大小,性能(读取和写入)均开始下降。

那么我们如何将碎片保持在20GB左右呢? 

  • 通过向索引添加更多分片,并使集群更加分片?当然不是! 
  • 通过使用rollover

我们如何解决分片策略挑战

多亏了rollover,我们将分片数量减少了三倍,还减少了节点上的负载和CPU消耗。rollover允许我们在写入期间同时使用更少的分片(即减少平均负载和CPU使用率)。现在,最大索引在每个热节点上最多有一个分片(因此总共有15个),而中小型索引可以根据其工作负载使用1到6个分片。

rollover还通过更有效地使用缓存来帮助优化我们的读取性能,因为对索引的每次写入都会使整个缓存无效。

此外,当节点崩溃且大量分片重定位时,我们会感到更自在,因为较小的分片意味着更少的恢复时间,更少的带宽和更少的资源消耗。

您可以在此处找到rollover的官方文档:https : //www.elastic.co/guide/zh-cn/elasticsearch/reference/current/indices-rollover-index.html

简而言之,rollover由别名alias 组成,该别名同时接收读和写请求。在别名的后面,我们有一个或多个索引。读请求转发到所有索引,而写请求仅转发到标志“ is_write_index”设置为true的索引。 

最后但并非最不重要的一点是,我们应用了“ max_size”策略类型:每当索引达到400GB时,将触发一个rollover并创建新索引。

如您所见,写入“ index_10_2019-01-01-000002”不会使“ index_10_2019-01-01-000001”的缓存无效。而且,我们的索引比我们的碎片小。

结果,我们的分片不超过20GB,并且我们降低了平均负载并优化了读写性能。

热点挑战

这样,我们是否能够设计出完美的集群?不幸的是没有。

在几周内我们的群集运行良好之后,一个热节点发生故障后,它变得不稳定,我们将其恢复并放回群集中。 

发生了什么? 

在将节点恢复到群集中的状态后几个小时,许多索引触发了一个rollover,所有新的分片都到达了该节点。我们得到了一个不平衡的群集,其中只有一个节点接收几乎所有的写入流量。

为什么? 

因为默认情况下,Elasticsearch会注意平衡同一层中每个节点的分片数量。结果,几乎所有新碎片都被rollover了,即使是大索引中的14个碎片也是如此。

 让我们看一个示例,该示例显示我们的集群如何变得不平衡。我们假设我们是2019年1月3日。我们在索引设置中设置了“副本0” –请记住,我们每天和每个客户都有一个索引(10、20和30是我们的客户ID)–在这种情况下平衡是完美的,因为写入平均分布在我们的节点上。每个节点有一个分片。

现在,假设节点3发生故障,正如预期的那样,所有来自节点3的碎片都移到了节点1和节点2。现在,如果节点3恢复工作并在几秒钟后触发rollover会发生什么?

由于默认的Elasticsearch分片放置启发法,我们现在将所有分片写入节点3上!

如何解决呢?我们可以更改启发式算法https://www.elastic.co/guide/en/elasticsearch/reference/current/shards-allocation.html吗? 

不幸的是,这对我们没有帮助。就像我说的那样,默认情况下,Elasticsearch尝试平衡每个节点的分片数量。更改此设置可以帮助我们平衡每个索引和每个节点的分片数量,而不是每个节点的分片数量,但这只会对每个节点具有一个分片的大索引有所帮助。对于其余索引(其碎片数少于热节点)(例如10个碎片),此设置不会阻止Elasticsearch仅将所有碎片放在前十个节点上。

即使我们尝试按索引和按节点而不是仅按节点散布碎片,我们也会发现集群不平衡的情况。

在这里,一种解决方案可以是将分片的数量设置为等于节点的数量,但是如上所述,分片具有成本。

我们如何解决热点问题

在2019年4月,Elasticsearch发布了7.0版,其中引入了一个新功能:索引生命周期管理(aka ILM)。 

由于有了这项新功能,我们现在可以将数据节点分为三层:hot,warm和cold。

索引生命周期管理功能的主要好处是,它使我们可以在索引翻转后立即将碎片从hot移到warm。我们可以将热层仅保留为写入索引,将warm层的最后7天数据保留为只读,将冷层的最后7天数据保留为只读(或更多)。

因为写操作占我们活动的80%,所以我们希望有一个仅包含分片的热层。这将使我们能够保持平衡的集群,并且我们不必担心热点问题。

如何设置ILM 

首先,制定策略:https : //www.elastic.co/guide/zh-CN/elasticsearch/reference/current/getting-started-index-lifecycle-management.html#ilm-gs-create-policy

接下来,将此政策链接到您的模板:https : //www.elastic.co/guide/en/elasticsearch/reference/current/getting-started-index-lifecycle-management.html#ilm-gs-apply-policy

最后,以“ -000001”为后缀的过渡模式创建索引

让我们为从hot节点到warm节点到热节点的索引建立过程:

结论

借助rollover和索引生命周期管理功能,我们解决了Elasticsearch集群的所有主要性能和稳定性问题。

改善监控使我们能够更好地了解集群内部正在发生的事情。

对于每个索引,无论其大小如何,我们现在都具有不超过25GB数据的碎片。具有较小的分片还可以在需要时实现更好的重新平衡和重定位。

我们避免了热点问题,因为我们的热层只在写入中包含分片,并且热,暖和冷的体系结构提高了读取请求的缓存利用率。

话虽如此,集群仍然不是完美的。我们仍然需要:

  • 避免在warm层和cold层内部出现热点(即使我们主要关注的是写操作的可伸缩性) 
  • 密切监视我们的工作:创建rollover别名 
  • 在使用索引前一天创建索引。

下一个是什么?不断扩大规模!随着越来越多的安全专业人员意识到对行为检测和实时bot保护的需求,我们处理的请求量呈指数级增长。因此,我们的下一个里程碑是能够每秒处理500.000个写请求

点击标题见原文