关于做一些疯狂或新鲜的事情,我意识到的一件事是,除非你深入其中,否则你不会真正理解其中的可能性。例如,我很早就不知道这个项目是否会成功。或者,您将能够嵌入SQLite数据库并在边缘上以非常高的并发级别生成动态内容。
Varish目前使用VCL作为一种配置语言,用于编程Varish如何处理请求、响应、缓存和其他事情。
有了Workers,我们已经能够将路由和高速缓存推向边缘,使我们能够进一步扩展,同时为数百万开发人员改善NPM的性能。离开VCL意味着我们可以花更多的时间在JavaScript上,这是我们喜欢的。“。
毫无疑问,VCL缺乏通用的计算功能、合适的对象以及许多其他现代功能。但它有天然的速度。它是目前最快的缓存配置语言。不过,我还是能感同身受。然而,问题是,想出一个只需几毫秒就能启动的解决方案并不是答案。也不是完全脱离VCL。在不透露太多内容的情况下,我将讨论我用Varish实现微型虚拟机的项目的当前状态。
虚拟机允许您在任何地方运行不受信任的代码,包括在边缘。它可以承载任何计算机语言,真的,只要该语言可以输出您的虚拟机可以处理的机器代码。微小的虚拟机在任何意义上都不必是传统的虚拟机。在这种情况下,它们被编写为具有低开销、快速启动和拆卸,并且它们不必模拟任何特定的系统(例如Linux用户空间)。在本例中,它们使用为HTTP缓存编写的有限API。他们不能读/写文件。他们不能连接到任何东西,也不能修改系统设置。他们能做的就是修改HTTP报头字段并生成动态内容。
HTTP缓存是以各种方式交付缓存内容的高并发野兽。它们充当传统Web服务器的加速器,如今到处都在使用。在HTTP缓存中编程是一种奇怪的体验,因为一切都被推到了极限。例如,内存缓存总是被丢弃,因此您的所有合成基准测试都没有意义。如果你在大域名上使用-v键,你无疑会看到一些Via Varish弹出。
那么,我到底在做什么呢?嗯,我想探索使用仅针对单个请求存在的微型虚拟机(无论是在HTTP缓存的前端还是后端)所提供的可能性。这是一项非常具有挑战性的任务,因为这些东西会让互联网做出反应。我不能就这么扔进一个仿真器,然后说“去他妈的,多几毫秒不会伤害任何人。”谢天谢地,我一开始的开销是70微秒。这是什么意思很难解释,但我会试一试。看,有一台为每个租户创建的主计算机,除此之外,我们还为每个请求创建了一个快速副本。然后再删除它。这提供了重要的保证,并使人们更容易对安全性和生命周期进行推理。现在,机器的开销是在外部测量的,即为来自该机器的合成响应(例如403禁止响应)提供服务所需的时间。如果我们在内部测量时间,我们得到的各种好看的数字都低得离谱,但我想把苹果与苹果进行比较。
在这方面工作了一个月后,我最终获得了8-13微秒的开销,与本机代码相比,可伸缩性提高了2.75倍。一点也不坏,因为对于任何正常的程序,您都可以在100微秒内轻松完成。当然,如果没有系统调用形式的本机代码的帮助,您就不能调整大图像的大小。为什么不行?嗯,因为CDN操作符对在虚拟机内执行的代码施加限制。那样的话,没有租户可以坐在那里,只剩下我的比特币。不过,我想如果你为这项特权付钱,你很有可能做到!
为了解决合成基准无法解决的高并发环境中的问题,必须应用许多疯狂的优化。我有一个合成基准测试套件,那里的所有东西都是以纳秒为单位测量的。在300k请求/秒期间,只需将几个字节从A复制到B,就会耗尽500 ns。不过,我不会多谈这一点。当我在做这件事的时候,一些有趣的想法浮现在我的脑海里。
我注意到,我也许可以添加一个更新函数,在该函数中,您向租户提供二进制文件,租户将在运行时立即开始使用它。CDN可以随心所欲地实现更新方法,但一种方法是只接受POST请求。当然要经过一些认证。因此,现在有了租户逻辑的实时更新,允许CDN让路,让租户直接更新自己的逻辑。它立即生效。如果更新初始化失败,旧的更新将继续运行。
我测量了一次更新大约需要1毫秒来初始化等等。这并不重要,因为它不会影响缓存的操作。不过,密切关注这件事是很好的。
/*实时更新机制*/if(req.method==";post";){std.cache_req_body(15MB);set req.backend_hint=tenant.live_update();return(Pass);}。
通过实现一个简单地调用VM的假后端,可以让租户动态生成内容。这和人们想象的完全一样。该函数只返回内容类型和内容。
由于VM几乎可以运行任何语言,例如,它还可以运行内存中的数据库。当然是只读的。有了实时更新功能,它就不会过时。这带来了挑战:大型二进制文件占用大量内存。
函数的作用是:告诉Varish/cdn下一步要做什么。在本例中,我们将返回my_page以生成动态响应。API实际上只是碰巧可以工作的示例代码。
通常,当您考虑共享内存时,您会认为是在主机和虚拟机之间共享。或者在线程之间,或者在CPU之间共享页表。在本例中,我必须在每个租户的所有机器之间共享可执行文件和只读段,以确保大型二进制文件不会耗尽每个请求的工作区预算。请求不能有很多预算,因为它们太多了,而且根本无法扩展。相反,这个内存应该只属于原始机器,不应该复制。毕竟它是只读的。
好的,应该够了,对吧?不是的。原来,在带有溢出分配器的固定大小的红黑树中,每个页面都有80字节大,这也是一个问题。看看这个计算:
15MB/每页4096字节*每页80字节=~300KB。如果每个页面都是零字节,那么它仍然会使用48字节,所以尝试缩小页面不会有什么好处(尽管它们确实从80开始,并且我设法减少了一些脂肪)。300KB太大了,无法对每个请求进行初始化。因此,剩下的唯一一件事就是验证二进制文件是否有一个顺序的执行段,然后是一个顺序的只读段,然后与每个分叉机器顺序地共享这些页面(不是数据,只是页面信息)。最后,在我的大多数基准测试中,它实际上是一个优化,现在二进制大小根本不影响请求!几乎是怪异的。
我目前正在做的一件事就是如何调试这些东西。我编写了类似strace的日志记录功能,它可以在VM内部运行,而无需任何人真正知道或关心:
>;>;>;ypizza.com:[trace]HTTP(REQ)::Append(X-VM:客户端请求)>;>;>;ypizza.com:[trace]HeaderField(REQ,9)>;>;>;ypizza.com:[trace]name()=";ypizza.com";>;>;ypitza.com:[trace]name()=";ypizza.com";>;>;ypitza.com:[trace]name()=";ypizza.com";>;>;>;ypizza.com:[trace]HeaderField(REQ,10)>;>;>;ypizza.com:[trace]Decision(synth,200)>;>;>;ypizza.com:[trace]HeaderField(REQ,1)>;>;>;ypizza.com:[trace]HeaderField(REQ,1)::regsub(0,
Varish本身也已经有了大量的日志记录解决方案,应该真的会涵盖其余的解决方案。尽管如此,我还是计划做一些API模拟,让人们可以直接从Linux运行他们的代码。不过不能保证。
我不知道它能支持什么,但我想我必须选择一种语言并坚持下去。我一直在看围棋,但我不确定它是否可以成型。较旧的语言是,Rust也可能满足这些要求。
现在我使用C++,因为使用newlib构建非常容易,覆盖所有内存处理C函数以将它们加速为系统调用,并编写内联汇编来优化通用API。顶部的抽象使得代码易于使用。但是,C++有一个难以理解的错误消息问题。所以我还不确定。毕竟,这不是真的为我写的。
另外,我可能只编写一个KVM虚拟机管理程序。这是一个很好的概念验证,真的远远超出了我的预期,但我认为VCL已经开创了本机速度的先例。好的,KVM_EXIT_HLT:)