每天有大约10亿次边缘函数调用跑在Netlify上。Sunweb用它做页面个性化,Loto-Québec靠一个cookie检查来分流流量,还有几十万个站点在用它做路由、鉴权、内容定制。这些请求全部跑在一个完整的JavaScript运行时上,规模跟着客户的流量一起涨。
问题也随之而来:延迟要压到尽可能低。每秒要处理几万甚至几十万次边缘函数调用,每个请求都得完成处理、正确路由、分配算力、再把平台代码和客户代码一起启动起来。这一整套动作,必须在毫秒内完成。
请求不再离开自家网络
过去几个月,Netlify团队重建了边缘函数背后的基础设施,过程中和Unikraft团队紧密合作。以前请求会被发往一个托管执行服务,现在它们跑在Netlify自己边缘网络内的MicroVM上,中位数速度大约快了5倍。这次架构切换还带来了安全性和可靠性的提升,也为在边缘跑更复杂的计算打开了空间。
对使用者来说,写法和用法没有任何变化。URL导入、npm包、Node内置模块、netlify.toml声明、本地开发,全都和以前一样,只是更快、更有韧性。
一个边缘函数运行在站点前面,每一个匹配到的请求都会触发它。它花掉的时间,就是用户等待的时间,所以这里的每一毫秒都比别处更值钱。
一次请求的完整路径
每个请求会先落到离客户端最近的Netlify边缘节点。节点终止TLS连接,拿请求路径去比对该次部署的边缘函数路由。
如果没有匹配,请求照常走向缓存和源站。如果匹配上了,这里就是分水岭——在旧架构下,请求会离开Netlify的网络,走公网出去执行边缘函数,再回到网络里继续传递。新计算平台则把请求转发到自家网络内的计算节点。
计算节点收到请求时,会带上机器规格和服务ID。它先检查这个ID对应的服务是否已经存在。存在的话,请求被转发进服务,再送进MicroVM。一个服务可以让同一个站点边缘函数对应多个MicroVM,也允许配置MicroVM的扩缩容参数。比如每个服务都被配置为:一个MicroVM处理固定数量的请求后就被关掉,避免它无限期运行下去。同一套参数也用来判断何时提前启动新的MicroVM,以应对旧实例即将关闭的情况。
如果计算节点上还没有这个站点边缘函数对应的服务,就会新建一个,然后检查机器规格里需要的镜像是否都在磁盘上。缺哪个,就从边缘节点拉取并写入磁盘。这样一来,只有在该区域真正接收到流量的边缘函数镜像才会被拉取。
请求出发之前,边缘节点会为将要运行函数的机器写一份规格。规格里指定三个镜像:运行时、平台镜像、边缘函数镜像,同时设定CPU、内存和连接数限制。
这份规格跟着每一个请求走。它的哈希值和站点相关信息一起算出一个服务ID。隔离由此实现:两次部署如果代码不同或环境变量不同,就是不同的服务,永远不会共用同一个MicroVM。
这种隔离在防范某些故障时尤其重要。一个可能已被攻破的部署跑在独立的MicroVM里,即便它逃出了运行时,也无法污染其他客户或计算层本身。V8隔离,无论叫什么名字,都提供不了这个级别的隔离。
粘性与热点的平衡
每个区域有一组计算节点。边缘节点用一致性哈希(rendezvous hashing)为服务挑选节点:同一个服务每次都落在同一个节点上,这样MicroVM才能保持热态,代码一旦被读取就留在磁盘和缓存里。这种粘性本身就是一种缓存策略。如果把请求均匀打散到整个节点群,冷启动的比例反而会更高。
但把某个函数的所有请求都送到同一个计算节点,虽然是快路径,也是热点形成的方式——一个繁忙的函数会和这台机器上其他所有东西抢资源。一个占据某区域大比例流量的服务被钉在单个节点上,就会拖垮这个节点,牺牲其他服务。
Netlify的应对方式是放松粘性。超过某个阈值后,就把这个服务分散到一组节点上,以此吸收流量的突然尖峰。
热门跟贴