偏爱MySQL,Nifty使用4个Web Server支撑5400万个用户网站
英文原文: Nifty Architecture Tricks From Wix - Building A Publishing Platform At Scale
Nifty 运营网站已经有很长一段时间,而在基于 HTML5 的 WYSIWYG 网页制作平台推出后,用户在该公司建立的网站已超过 5400 万个,同时其中大部分网站的日 PV 都不到 100。鉴于每个网页的 PV 都很低,因此传统的缓存策略并不适用。然而即使是这样,该公司也只使用了 4 个 Web Server 就完成了这些工作。近日,Wix 首席后端工程师 Aviran Mordo 在“ Wix Architecture at Scale”的演讲中分享了他们的策略,下面我们一起看 High Scalability 创始人 Todd Hoff 的总结:
以下为译文
Wix 围绕扩展性上的努力可以用“定制化”三个字来总结——在仔细地审视了系统之后,以高可用和高性能为目标对系统进行了改善。
Wix 使用了多数据中心和云服务,这在通常情况下非常少见,他们将数据同时复制到 Google Compute Engine 和 AWS。对于故障转移,他们有专门的应对策略。
从始至终,Wix 都没有使用事务。取而代之,所有数据都是不可变的,他们为用例使用了一个非常简单的最终一致性策略。Wix 并不是缓存策略爱好者,简而言之他们并没有打造一个非常高端的缓存层。取而代之,他们将大部分的精力放在了路径渲染优化上,让每个页面的显示时间不超过 100 毫秒。
Wix 开始于一个非常小的系统,使用了单片架构;而在业务发展过程中,他们很自然地过渡到一个面向服务的架构。在整个架构中,他们使用了一个非常成熟的服务识别策略,从而可以很轻易的将所有精力都集中到一个事件上来。
系统统计
- 5400 个网站,每个月都会新增 100 万个
- 800+TB 的静态数据,每天 1.5TB 的新文件
- 3 个数据中心+两个云服务(谷歌和亚马逊)
- 300 个服务器
- 每天 7 亿个 HTTP 请求
- 总计 600 员工,200 人的研发团队
- 系统内服务数量达 50 个
- 4 个公共 Web Server 来支撑 4500 万个网站
系统组件
- MySQL
- Google 和 Amazon 云服务
- CDN(内容分发网络)
- Chef
系统衍变
1. 系统始于简单的单片架构,开始时只有一个应用服务器,对于任何人来说,这都是最简单的初始策略,非常灵活且易于更新。
- Tomcat、Hibernate、定制网络框架。
- 使用有状态的登录。
- 无视任何性能和扩展性相关。
2. 两年后。
- 仍然使用单片服务器支撑所有事情。
- 拥有了一定规模的开发团队,且需要支撑一定规模的用户。
- 依赖性产生的问题。某点的改变通常会造成整个系统的变更,无关领域的故障通常会造成整个系统大范围崩溃。
3. 系统拆分的时候到了。
- 到面向服务的架构转变,但是这并不是件容易的事情。比如,你如何将某个功能分离到两个服务中?
- 聚焦用户在系统中的行为,并将之主要归结为 3 类:修改网站、查看 Wix 建立的网站以及媒体服务。
- 网站更新包括服务器数据的数据有效性、安全和验证、数据一致性以及大量的数据修改操作。
- 一旦某个网站建立完成,用户就会进行查看。因此,对于整个系统来说,访客的数量十倍于修改者。因此关注点会转换为:
- 高可用性。因为用户业务行为,HA 成为系统最大的特性。
- 高性能。
- 高流量值。
- 长尾问题。平台上已经有了大量的网站,但是它们通常都非常小。单独看某个网站,每天可能只有 10 或 100 个 PV。鉴于这种特性,缓存对于系统扩展来说并不会起到太大的作用。因此,缓存变得非常低效。
- 媒体支撑是第二大服务,包括 HTML、javascript、css 及 images。他们需要一个途径来支撑 800TB 数据上的大量请求,其中缓存静态内容成为制胜的关键。
- 新系统看起来像一个网络层,网站被切分为 3 个部分服务:修改部分(任何对数据产生修改的操作),媒体部分(支撑静态内容,只读),公共部分(一个文件被访问的首部分,只读)。 </ul>
- 每个服务都有自己独立的数据库,每个数据库只能一个被一个服务写入。
- 数据库只能被服务的 API 访问,这样可以将关注点分离,并将数据模型对其他服务透明。
- 鉴于性能原因,其他服务只被赋予数据库的只读权限,一个数据库只能被一个服务写入。
- 服务都是无状态的,这让水平扩展非常便捷,业务的增长只需要添加更多服务器就可以支撑。
- 不使用事务。除下 billing/financial 事务以外,所有其他服务都不使用事务,这里的理念是避免数据库事务所带来的开销,从而提升性能。鉴于不使用事务,开发者必须考虑设计合适的数据模型来完成事务逻辑特性,从而避免不一致状态。
- 在新服务设计时,缓存并不是所需要考虑的因素。首先,尽可能的考虑服务性能,然后快速的部署到生产环境,查看服务的运行情况。只有在代码无法优化的情况下,才使用缓存来解决性能问题。
- 更新服务必须处理大量的文件。
- 数据被使用不可变的 JSON pages 在 MySQL 中存储,每天大约 250 万个。
- MySQL 是个非常棒的键值存储。键的设定基于文件的哈希函数,因此键是不可变的,通过主键来访问 MySQL 可以获得非常理想的性能。
- 可接受的扩展性。在扩展性方面,Wix 又做了什么样的权衡?Wix 之所以不使用 NoSQL 的原因是 NoSQL 往往会牺牲一致性,而通常开发者并不具备处理这种情况的能力,所以坚持 MySQL 也并非不可。
- 动态数据库。为了给那些经常访问的网站让路,所有网站的冷数据(通常是建立时间超过 3 个月以上的数据)都会被转移到其他的数据库,这些数据库的性能往往会非常低,但是容量很高。
- 给用户增长留有容量空间。大型档案数据库是非常缓慢的,但是鉴于数据使用的频率这不会出现任何问题。但是一旦这个数据被访问,在下次访问之前这个数据就会被转移到活跃数据库。
- 大数据体积达到一定程度时,任何事情的高可用都是难以保证的。因此,着眼关键路径,在网站领域无疑就是网站的内容。如果网站的一个装饰部分问题,它对网站的可用性不会造成任何致命影响。因此对一个网站来说,关键路径才是唯一关注点。
- 防止数据库崩溃。如果你想尽可能快的完成故障转移,务必做好数据库的备份,并在故障恢复时快速切换到从数据库。
- 数据完整性保护。这里并不一定是恶意破坏,一个 bug 可能就会对数据存储产生影响。所有数据都是不可变的,为任何数据保存校订版本。最坏的情况下,即使数据被破坏到无法修复,我们也可以将之恢复到修订版本。
- 阻止不可用情况发生。区别于桌面应用程序,网站必须可以被随时随地地访问。因此,在不同地理位置的数据中心,不同云环境中对数据进行备份非常重要,这将赋予系统足够的弹性。
- 在一个网站上点击“保存”按钮,修改会话会给修改服务器发送一个 JSON 文件。
- 服务器会给活跃 MySQL 服务器发送页面,同时它会在另一个数据中心进行备份。
- 当数据在本地修改后,一个异步的进程会将修改上传到一个静态网格,也就是所谓的媒体部分。
- 当数据被传输到静态网格后,一个通知会发送给保存在 Google Compute Engine 上的存档服务。存档服务会连接到这个静态网格,下载这个修改页面,并将之保存在谷歌云服务中。
- 然后,一个通知会发送到修改器,告知页面已经存储到 GCE。
- 同时,系统会根据 GCE 的数据在 Amazon 中保存另一个副本。
- 当最后一个通知收到后,这意味着这个数据已经保存了 3 个副本:一个数据库,一个静态网格以及一个 GCE。
- 对于新版本来说是 3 个副本,而对于旧版本来说则会存在两个副本。
- 这个过程具备自我修复的特性。如果这里存在一个错误,当用户下一次更新其网站内容时,所有未完成的修改会被重新上传。
- 停用文件会做垃圾收集处理。
- 对于服务拥有者来说,他们从来都不期望发生这样的情况:用户同时对两个页面进行修改,结果只有一个页面被存储到了数据库中,这就造成了不一致状态。
- 取得所有 JSON 文件,随后按照顺序将他们保存到数据库。当所有数据被保存后,一个命令会被发布,它包含了上传到这个静态服务器上所有被保存页面的 ID 清单(静态服务器中文件名称的哈希值)。
- 存储了大量文件。800TB 的用户媒体文件,平均每天 300 万个文件,5 亿条元记录。
- 对图像进行修改。它们会针对不同设备和屏幕对图像进行修改。在这里,可以根据需求插入水印,同时还可以对音频格式进行转换。
- 建立一个一致性分布式文件系统,使用多数据中心备份模式,并且实现跨数据中心的故障恢复。
- 运行的痛苦。32 个服务器,每 9 个月翻一倍。
- 计划迁移到云中以获得更好的扩展性。
- 让供应商锁定见鬼。因为都使用了 API,只需要改变实现方式就可以在数周内跨云服务供应商进行迁移。
- 在 Google Compute Engine 中遭遇失败。当他们从数据中心迁移到 GCE 时,很快就受到了谷歌云服务的限制。而在谷歌做出了一些改变后,系统得以正常运行。
- 数据是不可变的,因此非常有利于缓存。
- 图像请求会首先发送到 CDN。如果所请求的图像在 CDN 中并不存在,请求会被直接传递给他们奥斯丁的主数据中心。如果在主数据中心也没有发现这个图像,随后寻找的地点就是谷歌云服务。如果谷歌云服务中仍然未发 现所请求的图像,那么下一个寻找地点则是坦帕市的数据中心。
- 解析 URL(在 4500 万网站中),并将之分配给指定的渲染程序,然后转换成 HTML、sitemap XML 或者 robots TXT 等。
- 公用的 SLA,峰值时响应时间低于 100 毫秒。网站必须是高可用的,同时也需要非常高的性能,但是缓存却并不能发挥作用。
- 当一个用户修改某个页面并进行发布后,包括这个页面元素的清单会被推送到公用环境,同时推送的还有路由表。
- 最小化宕机情况。解析一次路由需要促发一个数据库调用。将请求分配个渲染器需要 1 次 RPC 调用。获得网站清单也需要一次数据库调用。
- 查询表会在内存中进行缓存,每 5 分钟修改一次。
- 因为需要传送给编辑器,数据不可能保存为同一种格式。数据使用非规范化格式进行存储,通过主键进行优化,所有需求的内容都会在单一请求中返回。
- 最小化业务逻辑。数据是非规范化的,并且进行预计算。大规模场景下,每秒内发生的每个操作都会乘以 4500 万次,因此发生在公共服务器上的每个操作都需要被调整。
- 页面渲染
- 由公共服务器返回的 html 是 bootstrap html 类型的,它使用了一个 JavaScript Shell,并包含了所有网站清单和动态数据相关的 JSON 数据。
- 渲染会被放在客户端进行。当下,笔记本电脑和移动设备已经拥有了很强大的性能,它们完全可以从事这个工作。
- 之所以选择 JSON,因为解析和压缩都非常方便。
- 客户端上的 bug 非常容易修补。修补客户端 bug 只需要重新部署一个客户端代码,如果在服务器端进行渲染,html 则会被缓存,因此修补一个 bug 需要重新渲染上千万个网站。
- 虽然目标是一直可用,但是总会发生一些意外情况
- 通常情况下:请求由浏览器发出,随之会被传输到一个数据中心,通过一个负载均衡器,它将会给发送到一个公共服务器,解析路由,传送给渲染器,随后 返回到浏览器,并使用浏览器运行 javascript。随后,浏览器会对档案服务发送请求,档案服务会做与浏览器相同的操作,然后将数据储存到缓存。
- 数据中心丢失发生的情况:这时候,所有 UPS 都会挂掉,数据中心也会丢失。所有 DNS 都会被改变,请求会发送给次数据中心。
- 公用部分丢失的情况:当负载均衡器配置只进行一半发生这个问题时,所有公共服务器都会丢失。或者当部署错误版本时,服务器则会抛出故障。Wix 通过定制负载均衡器代码来解决这个问题,在公共服务器丢失时,他们会将档案服务器路由到高速缓存,即使系统在警报后已经进行故障恢复。
- 在网络连通性很烂的情况:请求由浏览器发出,随之会被传输到一个数据中心,通过一个负载均衡器,并返回对应的 html。现在 JavaScript 代码必须取回所有的 JSON 数据和页面。随后进入内容分发网络,发送到静态网格,并获得所有的文件进行网站渲染。在网络很卡的情况下,文件返回可能无法进行。JavaScript 则会做出选择:如果主要位置无法获得文件,代码则会在档案服务中获取。
- 识别业务的关键路劲和关注点,仔细了解产品运行的方式,开发使用场景,尽力让你工作物有所值。
- 使用多云和多数据中心。为了更好的可用性,在关键路径上建立冗余。
- 对数据进行转换,最小化进程外跳,一切只为了性能。预计算并做一切可以做的事情来减少网络抖动。
- 利用好客户端的 CPU,为可用性建立关键路径上的冗余。
- 从小做起,先跑起来,然后寻找下一个决策。从始至终,Wix 首要解决的都是如何才能让服务可以良好运行的工作,然后有条不紊的转移到面向服务的架构。
- 长尾需要不同的途径进行解决。取代缓存一切,Wix 通过优化渲染途径来提升服务,并将数据在活跃和档案数据库中同时进行备份。
- 使用不可变的方式。不可变会对服务的架构产生深远影响,覆盖后端到客户端的所有处理,对于许多问题来说,这都是个优雅的解决方案。
- 供应商锁定根本不存在。所有功能都通过 API 实现,只需要修改实现就可以在数周内完成不同云供应商的迁移。
- 最大的瓶颈来自数据。在不同云环境中转移大量数据异常困难。
服务打造的指导方针
更新服务
打造更新服务的高可用性
使用无数据库事务方式给数据建模
媒体部分
公用部分
公用部分的高可用性
学到的知识