某些系统设计选择的几何影响

2021-01-05 14:58:51

假设您决定以某种出于某种原因并没有真正执行线程的语言来编写服务。也许没有线程。也许它有一个解释器,一次只能执行一项操作,而不管您有多少个线程。也许您已经创建了一种情况,即使该语言支持线程,也可能出于其他一些原因而无法使用线程。

假设您仍然希望服务器主机一次可以处理多个请求,那么最终可能会通过并行运行这些服务器进程来结束其他工作。这可能是通过fork()发生的,其中其中一个启动,打开套接字以侦听网络或其他内容,然后启动一堆孩子来实际完成工作。

这为您提供了一堆截然不同的过程,这些过程恰好继承了侦听网络的公用文件描述符。让我们进一步假设您设法解决了整个雷电群。他们每个人都为单个传入连接醒来的问题,他们都调用accept4()或类似方法,但是只有一个" wins&#34 ;;而其他人却一无所获。

您也已经过去了。现在,您有许多试图充分利用一切的流程。他们可能需要在生产环境中的其他地方与其他人交谈,他们将如何做到这一点?

好吧,如果您是做出上述选择的地方,那么猜测您将在内部使用HTTP进行传输就不费吹灰之力了。您的程序可能使用libcurl或您的语言的本地等效项在您的" network"网络中发出标准的HTTP请求。到其他地方的另一个服务器进程。

我想很多人在这里点头,等待另一双鞋掉下来。他们做了所有这些事情,并且正在工作,那么帖子的内容是什么?

该帖子是关于他们何时停止工作的信息,是对继续走这条路的人们的展望。

那么,所有这些事情都在执行HTTP请求,对吧?他们是否以某种方式连接到主机名?也就是说,他们是否尝试将某些内容发布到http://other.service.internal:1234 / api / v1 / ListUsers&#34 ;?我敢打赌他们是,而且他们可能是故意这样做的,因为某处某人正在用运行该服务的实例的IP地址填充DNS。也许是一些云DNS他们拥有的东西,或者可能与它们正在运行的任何容器相关联。他们执行该DNS请求,取回一大堆可能的实例,选择一个实例并前往城镇。

麻烦的是,这些答案越来越大,很快它们就无法适合一个不错的UDP查询,并且EDNS0可能不是一个选择,或者没有帮助。这些HTTP客户端使用的解析器开始必须建立TCP连接,以找出这些主机名后面的内容。他们不会缓存任何内容,因此每当有请求转到公司中的其他地方时,他们便出去询问已配置的解析器。

几乎不会看到第一次中断是由于DNS服务完全崩溃而造成的,因为每个服务器实例加在一起就构成了一个巨大的无意僵尸网络。 DNS发生故障,现在没有人可以解析内部实例,因此整个公司都崩溃了。呵呵,暂时不再有猫的照片。

在此之后,也许是" DNS基础设施;规模有所扩大,但最终变得很明显,脱离主机解析器也不是解决问题的办法。一个项目开始在每个系统上安装一个缓存解析器,以便http客户端现在可以从本地主机端口53而不是internal.botnet.victim端口53退回其请求,并且不会把所有请求都放到上游端口上非常。

这可能会起作用,但是如果流量持续增长,那么下一次中断就不会落后了。这次,进程发现由于某种原因无法连接到本地DNS服务器!他们开始说类似"地址已在使用中"尝试连接“向外”时,起初没有任何意义。毕竟,这不是意味着我正在尝试侦听端口,并且已经有人在吗?

好吧,当然,您有点在这里这样做。想一想:系统需要确保给定的(srcip,srcport,dstip,dstport)方形是唯一的,以便可以区分连接。如果您出现并说给我一个可以在任何地方连接的套接字,则需要确保您不能掉头并造成问题。那&#34.0.0.0.0:31843"用于传出连接的套接字是有代价的:没有其他人可以使用它。

在此中断继续发生的同时,某人最终可能会发现一些东西,并注意到EADDRINUSE与试图将其SOCK_STREAM文件描述符绑定到地址的过程有关。也许他们然后查看&netstat'或&ss'看到灾难:每个临时端口都消失了。为什么?好吧,请考虑发生了什么。每个进程都从127.0.0.1端口(某物)连接到127.0.0.1端口53。考虑到三个参数是固定的,用尽所有这些可能性并不需要很长时间,尤其是考虑到通常的TIME_WAIT和缠绵套接字所做的事情。一开始它只是一个16位空间,您的系统可能没有全部使用它-例如,它可能只使用上半部分,大约为32K到61K。

在此附近的某个地方,有人发现一个sysctl旋钮,如果它是回送连接,则它会忽略TIME_WAIT,因为它可能是安全的,并且因为它,我们不会再进行真正的连接; s从我们到我们。然后他们进行了设置,现在又重新运行了,但是它们感觉很脏...哦,真脏。

显然,这最终也会崩溃。即使您可以尽可能快地回收以前的连接,似乎不可避免地会最终同时使用所有实际可能使用的连接(" ESTABLISHED"),然后您该怎么办?

有人决定构建一个系统,该系统故意分叉,因此(并不能)共享存根解析器或RPC子系统之类的常见事物。这意味着每个实例都必须独立承担所有这些工作,并且实际上没有任何协调。

是的,协调。猜猜当副本丢失时会发生什么?一个主机上的那个分叉的孩子发现了并可能将其标记下来,但是其他人都必须做同样的事情。由于没有使用通用的RPC机制,因此无法在所有工作人员之间共享此知识。它们不是线程,请记住,它们是完全分叉的孩子。

顺便说一句,您知道每个工作人员向那里的一个宕机实例发出一个请求,这是如何发现该实例宕机的?是的,我敢打赌,该请求是d-e-a-d DEAD,并且无法重试。您发现那里有一个已死的副本,但是这样做却要付出致命的代价。请求,并希望您上游的人可以重试。即使您可以从技术上重试,您也可能没有时间预算来重新执行整个跳跃-跳过-跳跃解决+连接操作。娱乐时间!

顺便说一句,任何建议您使用SysV共享内存或mmap或其他类似东西来在这些分叉之间共享状态的人都被放逐到一个境界,在这里您可以每天吃掉所有想要的糊状物。

幼稚的http客户方法并没有帮助。不跟踪谁在那儿,并且对进入另一个服务的每个请求都必须进行DNS请求,绝对不会使任何人的生活变得更轻松。在一次部署灾难中,将这与所有工作人员的所有费用相乘,简直是锦上添花。

这些粗略的设计决策中的任何一个都可能孤立地工作得很好,但是当它们相互影响时,您最终可能会遇到五级技术债务风暴。 单个进程本来可以使连接池可供任何客户端使用的。 不必为每个单个请求都重新解析它们。 它甚至可以做keepalive来确定远端是否真正存在,然后再向深处提交请求,再也不会出现。 当它确实了解到端点关闭并吞下一个请求的困难方式时,所有工作人员都将从中受益,因为他们都在通过这个共享子系统。 他们不必不必要地重复一遍。 分叉的工作人员和将内部传输作为HTTP请求运行似乎很容易,但是在路上有很多讨厌的怪物在等着您。 现在您已经知道了这一点,请在看到它们临到时请转弯。