Variti专门致力于防御僵尸程序和DDoS攻击,并进行压力和负载测试。 由于我们是一项国际服务,因此对于我们而言,确保服务器与群集之间实时信息的不间断交换非常重要。 在Saint HighLoad ++ 2019大会上,Variti开发人员Anton Barabanov讲述了我们如何使用UDP和Tarantool,为什么要使用这么多的对象以及如何将Tarantool模块从Lua重写为C.您也可以
通过链接阅读报告
摘要 ,并在扰流板下方查看下面的视频。
当我们开始提供流量过滤服务时,我们立即决定不处理IP传输,而是保护HTTP,API和游戏服务。 因此,我们终止了TCP协议中L7级别的流量,并将其继续传递。 L3和4上的保护同时自动发生。 下图显示了服务图:来自人的请求经过集群,即服务器和网络设备,并且对机器人(显示为幽灵)进行了过滤。

为了进行过滤,有必要将流量分成单独的请求,准确而快速地分析会话,并且由于我们没有按IP地址进行阻止,因此请从同一IP地址定义连接内的漫游器和人员。
集群内部会发生什么
在集群内部,我们有独立的过滤器节点,也就是说,每个节点都可以单独工作,并且只能处理自己的流量。 流量在节点之间随机分配:例如,如果从一个用户收到10个连接,则它们都在不同的服务器上分散。
由于客户位于不同的国家/地区,因此我们对性能有非常严格的要求。 并且,例如,如果来自瑞士的用户访问法国站点,则由于交通路线的增加,他已经面临15毫秒的网络延迟。 因此,我们无权在处理中心内再增加15到20毫秒-请求将持续很长时间。 另外,如果我们将每个HTTP请求处理15到20毫秒,那么2万RPS的简单攻击将加总整个群集。 当然,这是不可接受的。
对我们来说,另一个要求不仅是跟踪请求,而且还要了解上下文。 假设用户打开网页并发送斜线请求。 此后,将加载页面,如果页面是HTTP / 1.1,则浏览器将打开10个与后端的连接,并在10个流中请求静态和动态请求,进行ajax请求和子查询。 如果在返回页面的过程中而不是代理子查询,而是开始与浏览器进行交互并尝试为子查询提供诸如JS Challenge的功能,则很可能会破坏页面。 根据第一个请求,您可以给CAPTCHA(尽管这很糟糕)或JS挑战,进行重定向,然后任何浏览器都将正确处理所有内容。 测试之后,有必要在所有群集上传播会话合法的信息。 如果集群之间没有信息交换,则其他节点将从中间接收会话,并且不知道是否跳过该会话。
快速应对所有负载激增和流量变化也很重要。 如果某个节点上发生了跳跃,则在50-100毫秒后,所有其他节点上都会发生跳跃。 因此,最好让节点事先知道变化并预先设置保护参数,以使所有其他节点上都不会发生跳跃。
标记后服务是防止僵尸程序的另一项服务:我们在网站上放置一个像素,写入僵尸程序/人员信息,然后通过API发送此数据。 这些裁决必须保留在某个地方。 就是说,如果早先我们讨论了集群内的同步,那么现在我们也在集群之间添加信息的同步。 下面我们显示了L7级别的服务方案。

集群之间
建立集群后,我们开始扩展。 我们通过BGP任意播进行工作,也就是说,从所有群集中通告我们的子网,并且流量到达最近的子网。 简而言之,请求从法国发送到法兰克福的集群,从圣彼得堡发送到莫斯科的集群。 集群应该是独立的。 网络流是允许独立的。
为什么这很重要? 假设有人开车,通过移动互联网在一个网站上工作并穿过某个Rubicon,然后流量突然切换到另一个集群。 或另一种情况:由于交换机或路由器烧坏了,某处掉了下来,网段断开了,所以重新建立了流量路由。 在这种情况下,我们为浏览器(例如,以cookie形式)提供了足够的信息,以便在切换到另一个群集时,可以通知有关通过或失败测试的必要参数。
此外,您必须在群集之间同步保护模式。 这在低流量攻击(这通常是在洪水掩护下进行)的情况下非常重要。 由于攻击是并行进行的,因此人们认为他们的站点正在打破洪灾,因此不会看到数量很少的攻击。 对于一种情况,当一个集群的容量较小时,又向另一个集群泛洪,保护模式必须同步。
就像已经提到的,我们在集群之间同步累积的累积结论,并由API给出。 在这种情况下,可能会有很多判决,并且必须可靠地同步它们。 在保护模式下,您可能会在群集内部丢失某些内容,但在群集之间不会丢失。
值得注意的是,群集之间存在较大的延迟:对于莫斯科和法兰克福而言,这是20毫秒。 在这里不能进行同步请求;所有交互都必须以异步模式进行。
下面我们显示了群集之间的交互。 M,l,p是交换的一些技术参数。 U1,u2是用户标记,属于非法和合法。

节点之间的内部交互
最初,当我们提供服务时,仅在一个节点上开始了L7级别的过滤。 这对于两个客户来说效果很好,但仅此而已。 扩展时,我们希望获得最大的响应速度和最小的延迟。
最小化用于处理数据包的CPU资源非常重要,因此,例如通过HTTP进行交互将不适合。 还必须确保不仅计算资源而且包速率最小的开销消耗。 尽管如此,我们正在谈论过滤攻击,并且在这些情况下显然性能不足。 通常,在构建Web项目时,x3或x4足以应付负载,但是我们总是拥有x1,因为总会发生大规模攻击。
交互界面的另一个要求是要存在一个地方,我们将在其中编写信息,然后可以从中计算出我们现在所处的状态。 众所周知,C ++通常用于开发过滤系统。 但是不幸的是,用C ++编写的程序有时会崩溃。 有时,此类程序需要重新启动以进行更新,或者,例如,因为未重新读取配置。 而且,如果我们重新启动受攻击的节点,那么我们需要获取该节点所在的上下文。 也就是说,服务不应是无状态的,它应该记住,有一定数量的人被我们阻止,检查。 内部通信必须相同,以便服务可以接收主要信息。 我们曾考虑过将某个数据库(例如SQLite)放置在附近,但是我们很快放弃了这种解决方案,因为在每台服务器上写入Input-Output很奇怪,这在内存中将无法正常工作。
实际上,我们仅进行三个操作。 第一个功能是“发送”到所有节点。 例如,这适用于有关当前负载同步的消息:每个节点必须知道群集内资源的总负载才能跟踪峰值。 第二项操作是“保存”;它涉及验证的结论。 第三个操作是“发送给所有人”和“保存”的组合。 在这里,我们讨论的是状态更改消息,该消息将发送到所有节点,然后保存以便能够减去。 下面是生成的交互方案,其中我们需要添加参数以进行保存。

选项和结果
我们查看了哪些保留裁决的选项? 首先,我们在考虑经典的RabbitMQ,RedisMQ和我们自己的基于TCP的服务。 我们拒绝了这些决定,因为它们运作缓慢。 相同的TCP将x2加到数据包速率上。 另外,如果我们从一个节点向所有其他节点发送消息,那么我们要么需要有很多发送节点,要么此节点可能毒害16台机器可以发送给它的消息中的1/16。 显然,这是不可接受的。
结果,我们采用了UDP多播,因为在这种情况下,发送中心是网络设备,它的性能不受限制,可以完全解决发送和接收速度方面的问题。 显然,对于UDP,我们不考虑文本格式,而是发送二进制数据。
此外,我们立即添加了包装和数据库。 我们之所以选择Tarantool,是因为,首先,公司的所有三位创始人都有使用该数据库的经验,其次,它尽可能地灵活,也就是某种应用程序服务。 另外,Tarantool具有CAPI,并且用C编写代码的能力对我们来说是个原则问题,因为需要最大程度的保护才能防御DDoS。 与C不同,没有任何一种解释语言可以提供足够的性能。
在下图中,我们在集群内部添加了一个数据库,用于存储内部通信的状态。

添加数据库
在数据库中,我们以呼叫日志的形式存储状态。 当我们想到如何保存信息时,有两种选择。 可以通过不断的更新和更改来存储某些状态,但是很难实现。 因此,我们使用了不同的方法。
事实是,通过UDP发送的数据结构是统一的:有时序,某种代码,三个或四个数据字段。 因此,我们开始在空间Tarantool中编写此结构,并在其中添加了TTL记录,这清楚表明该结构已过时,需要删除。 因此,一条消息日志会累积在Tarantool中,我们会在指定的时间清除它。 要删除旧数据,我们最初已过期。 随后,我们不得不放弃它,因为它引起了某些问题,我们将在下面讨论。 到目前为止,该方案:在该方案上,将两个数据库添加到我们的结构中。

正如我们已经提到的,除了存储集群状态外,还必须同步判决。 判定我们同步集群间。 因此,有必要添加额外的Tarantool安装。 使用另一种解决方案会很奇怪,因为Tarantool已经存在,并且非常适合我们的服务。 在新安装中,我们开始编写判决并将其复制到其他集群。 在这种情况下,我们使用的不是master / slave,而是master / master。 现在,在Tarantool中只有一个异步主服务器/主服务器,在许多情况下,这是不合适的,但对我们而言,此模型是最佳的。 在群集之间的延迟最小的情况下,将采用同步复制的方式,而异步复制不会引起问题。
问题所在
但是我们有很多问题。
第一个复杂性与UDP有关 :协议可以击败和丢失数据包已经不是秘密。 我们通过鸵鸟方法解决了这些问题,也就是说,我们只是将头埋在沙子里。 然而,由于通信是在单个交换机的框架内进行的,并且没有不稳定的连接和不稳定的网络设备,因此对于我们来说,数据包的损坏和位置的重新布置是不可能的。
如果机器死机,某处发生输入输出或节点过载,则可能存在丢包的问题。 如果这种挂起发生的时间很短,例如50毫秒,那么这很糟糕,但是可以通过增加sysctl队列来解决。 也就是说,我们采用sysctl,配置队列的大小,并获得一个缓冲区,所有内容都位于该缓冲区中,直到节点再次开始工作。 如果发生更长时间的冻结,那么问题将不会是失去连接,而是流向该节点的部分流量。 到目前为止,我们还没有这种情况。
Tarantool异步复制问题要复杂得多。 最初,我们没有采用主/主设备,而是采用了更为传统的操作主/从设备的模型。 直到从属服务器长时间接管了主服务器负载为止,一切都可以正常工作。 结果,过期的有效数据在主服务器上删除了,而在从属服务器上则没有。 因此,当我们多次从主机切换到从属主机并返回时,从属主机上累积了太多数据,从而有时一切都中断了。 因此,为了完全容错,我不得不切换到异步主/主复制。
这里又出现了困难。 首先,密钥可能在不同的副本之间相交。 假设在集群中,我们将数据写入一个主服务器,此时连接断开,我们将所有内容都写入了第二个主服务器,然后执行了异步复制,结果发现空间中的同一主键和复制失败了。
我们简单地解决了这个问题:我们采用了一个模型,其中主键必须包含我们要写入的Tarantool节点的名称。 因此,不再出现冲突,但是当用户数据被复制时,情况变得可能。 这是一种极为罕见的情况,因此我们再次忽略了它。 如果重复发生频繁,则Tarantool具有许多不同的索引,因此您始终可以进行重复数据删除。
另一个问题涉及判决的保留,当一个主机上记录的数据尚未出现在另一个主机上,并且请求已经到达第一主机时,就会出现问题。 老实说,我们尚未解决此问题,只是在推迟裁决。 如果这是不可接受的,那么我们将组织有关数据就绪性的推送。 这就是我们处理主/主复制及其问题的方式。
存在与Tarantool ,其驱动程序和过期模块
直接相关的问题 。 启动后的一段时间,每天分别开始有攻击发生,我们保存在数据库中用于同步和上下文存储的消息数量变得非常大。 在剥离期间,大量数据开始被删除,以致垃圾收集器停止了应对。 我们通过用C编写自己的过期模块IExpire解决了这个问题。
但是,到期后,还有一个我们还没有解决的困难,那就是到期仅在一个主机上起作用。 而且,如果过期的节点掉落,集群将失去关键功能。 假设我们清除所有早于一小时的数据-很明显,如果一个节点位于五个小时内,那么数据量将是通常的5倍。 如果在那一刻发生了一次大规模攻击,也就是说,两个恶劣情况同时发生,那么集群将崩溃。 我们尚不知道如何处理。
最终,用于C的Tarantool驱动程序仍然存在困难。当我们中断服务时(例如,由于竞争条件),花了很长时间才找到原因并进行调试。 因此,我们只编写了Tarantool驱动程序。 我们花了五天的时间来实现协议以及测试,调试和在生产环境中运行,但是我们已经有了自己的代码来与网络一起工作。
外面的问题
回想一下,我们已经准备好了Tarantool复制,已经知道如何同步判决,但是没有用于在群集之间传输有关攻击或问题的消息的基础结构。
关于基础架构,我们有很多不同的想法,包括编写自己的TCP服务的想法。 但是,仍然有来自Tarantool团队的Tarantool Queue模块。 另外,我们已经有了带有跨集群复制的Tarantool,“漏洞”被扭曲了,也就是说,无需去找管理员来要求打开端口或增加流量。 再次,可以集成到软件过滤中。
主机节点存在困难。 假设集群中有n个独立的节点,您需要选择一个将与写入队列进行交互的节点。 因为否则将发送16条消息或从队列中减去16次相同的消息。 我们简单地解决了这个问题:我们在Tarantool空间中注册了一个负责任的节点,如果该节点耗尽了,只要我们不忘记,就只需更改空间。 但是,如果我们忘记了,那么这就是我们将来还要解决的问题。
下面是带有交互界面的集群的详细图。

我要改进和添加的内容
首先,我们要发布在开源IExpire中。 在我们看来,这是一个有用的模块,因为它允许您执行所有与到期相同的操作,但开销几乎为零。 在那里,您应该添加一个排序索引,以仅删除最旧的元组。 到目前为止,我们还没有这样做,因为Tarantool对我们来说主要的操作是“写”,并且额外的索引由于其支持而会导致额外的负载。 我们还希望重写CAPI中的大多数方法,以避免折叠数据库。
问题仍然在于逻辑主机的选择,但是看来这个问题完全不可能解决。 也就是说,如果到期节点跌倒,则仅保留手动选择另一个节点并在其上运行到期节点的权利。 这不太可能自动发生,因为复制是异步的。 尽管我们可能会与Tarantool团队协商。
如果集群呈指数级增长,我们还必须向Tarantool团队寻求帮助。 事实是,将全部复制用于Tarantool Queue和集群间的判决保存。 例如,当有三个群集时,这很好用,但是当有100个群集时,需要监视的连接数将变得非常大,并且某些情况将不断中断。 其次,Tarantool可以承受这样的负载并不是事实。
结论
第一个结论涉及UDP多播和Tarantool。 Multicast , — , . , , 50 , . , , . UDP multicast , .
— Tarantool. go, php , Tarantool . , . , : Oracle, PostgeSQL.
, , , , : Redis , go, python . 这是不对的。 , , open source, , , , , . , . Tarantool, , , Redis, .