DAZN是如何实现每十分钟百万新用户注册请求的

在今年的全球架构师大会上,来自DAZN的方案架构师Ivan Rigoni给我们分享了DAZN是如何做到每十分钟处理百万请求的,本文尝试从笔者自己理解的角度来和大家分享一下DAZN的实现。也许你还没有听说过DAZN这个公司,它是一个体育流媒体,主要提供体育赛事直播和视频点播服务。

问题的概述

这次演讲本身是一个系统设计的面试题,当然也是基于DAZN真实的架构演变进行抽象的。我们知道DAZN作为一个体育赛事的直播平台,每天都有很多新用户慕名而来,尤其是一些大型赛事正在进行的时候。当一个新用户来到DAZN,他需要进行注册付费才能够观看相关的赛事。而这个付费注册的过程需要调用第三方API来实现,是一个同步的call(transactional),需要一定的时间才能完成。而DAZN作为一个同时服务超过200个国家的体育直播平台,通常来说每秒需要新注册的用户超过1600个,当有大型赛事举办的时候,峰值可能会超过3000/s。我们的目标就是如何让新用户在这个量级的QPS下能够快速完成注册,并及时开始观看直播。

我们可以假设最初架构是如下所示的,有一个单体架构的前后端,它需要调用external的同步call来进行payment和subscribe,它的QPS是1600 tps (峰值可能达到3000),目标是能够让用户不被这个流程所block,及时观看直播。

初始架构

我们先来从最直观的初始架构聊起,简单的示意图如下所示:

从流程的角度来看,前端直接调用后端进行注册,这个时候需要首先调用一个外部的付费平台来进行支付,这是一个同步的调用,直到它返回成功,我们就可以把相关的信息保存到注册平台,等注册平台把所有的信息都创建完毕并返回之后,就可以给用户返回一个token,用户就可以播放直播了,流程图如下所示:

这种架构的优点一目了然,大概有下面这几个:

  1. 如果需要扩展,就直接增加单体架构的instance就可以了,比较方便。
  2. 整个系统的交互很简单,所有的都是sequential和transactional,没有什么race condition。

当然缺点也很明显:

  1. 假如你需要扩展,可能需要整体扩展,而不是只扩展瓶颈的部分,这必然会导致资源的浪费。
  2. 从安全性的角度来说,你的代码是一个整体,你可以通过RPC访问所有的信息,比如数据库,storage,外部的调用等等,很难从security角度进行一些分离。
  3. 整个代码的release周期比较长,因为大家都在上面修改,任何有问题的修改都可能导致所有的修改roll back。
  4. 同样的代码库也意味着所有的team都使用同样的语言,很难让不同team使用它们熟悉的语言来进行编程。
  5. 因为整个都是单体架构,所以一个endpoint的性能很容易会成为性能的瓶颈。

优化架构

从上面的架构的缺点来看,我们其实可以从以下几个方面的思路来着手:

  1. 把到外面的external call改成一个async的call,再加入queue来进行处理。这样的优化需要保证最终一致性。
  2. 把单体架构改成单个component,比如微服务。
  3. 注意failure的处理,也就是尽量做到无状态。

基于这个思想,我们来对上面的架构图进行一些修改:

这里我们引入了一个Payment Façade,这个façade的引入就把对external的call和我们之前的单体架构的后端进行了隔离。同时把之前的payment分成了两个部分一个是preauth,一个是真正的payment call,你可以理解preauth就是去验证一下用户输入的信用卡信息是否有效,这个过程相对来说速度要比做一个payment快很多,如下面的流程图所示:当我们知道用户的信用卡信息是有效的,我们会把这个相关的信息加入到subs queue,并同时向用户返回一个可用的token,这个时候用户的最终付费操作并没有完成,但是他就可以进行直播的播放了。

那付费操作如何进行呢,我们从subs queue中取出数据,然后进行真正的扣费的操作,并在扣费成功后加入到subscription相关的平台。这里因为我们信用卡信息是有效的,所以扣费操作百分之八九十的情况下都是会成功的,所以当一切都成功的情况下并不需要让用户感知。而对于那百分之十左右的扣费失败的情况,我们则需要把刚刚给用户的token revoke了,让他不能继续观看直播,并显式的告知用户你的付费信息有问题,可能需要改正,并进而引导用户修改相关的支付信息。

这样修改之后有下面这些好处:

  1. 单体架构和外面的调用进行了隔离,不会受他们的性能影响。
  2. 用户能够更快地开始观看直播,而不需要等待支付信息的完成。
  3. 有了façade之后,我们假如需要改变第三方的payment平台之类的就会非常方便,而且我们可以单独针对façade这一层做一些监控之类的,可以有针对性地进行优化。
  4. 从安全性的角度来说,现在只有façade这个component可以访问第三方,这样一来安全性的控制就会得到提升。

进一步分解单体架构

显然微服务是一个很好的优化,因此从单体架构可以转变成如下所示的微服务架构,一个很重要的点就是一般来说这种转变也伴随着组织架构的转变,当然这个可能超出了技术层面的考虑:

我们可以把前端改成各种小的服务,比如Auth,播放等等;同样后端也可以分成Auth,Checkout等等各种各样的微服务。这样做的好处如下:

  1. 从可靠性的角度来讲,单个服务出问题可能不会导致整个网站的瘫痪。
  2. 从扩展的角度来说,我们只要针对服务层面的traffic进行扩展就好了,比如说修改密码的服务和播放直播的traffic显然不在一个量级,我们可以有针对性的进行扩展。
  3. 当然我们可以针对单个服务进行deploy,这样整个deploy的周期就大大缩短了。
  4. 各个team也可以自由决定他们使用的语言了。
  5. 安全性的角度来看,针对各个服务的security控制会相对更加容易一点。

当然这种架构的改变时间很长,也很复杂。从大的角度来说,这种改变有两种大的方法,一种就是全部重写一个V2,然后在某个时间点进行切换。另外一种则是让两者共存一起开发,经过一段时间的共存之后再最终切换,如下所示:

DAZN使用的是后面这种方法,大概的做法如下:

首先在原有的Legacy的code之上加一层proxy,随着新的架构不断开发,可以通过proxy层来不断切换,从而最终达到只使用新的架构的结果:

一些趟过的坑

最后Ivan分享了DAZN在整个架构演变过程中的一些经验和教训,个人觉得很有启发意义:

  1. 单体架构和微服务。DAZN在早期作为一个startup的时候,面临了一些抉择,他们认为快速推出服务,及时得到用户的反馈更重要,所以他们当时选择了买一个基本的单体架构系统,当有了足够用户之后再进行refactor并不迟。
  2. 架构不是创建的而是应运而生的。说白了我们的系统设计是要符合当下需求的,并不是说开始“完美”的架构就是好的,不同用户量下的架构是不同的,所以一步一步来进行优化架构才是正确的做法。
  3. 在deliver产品到用户之前,做好各种假设的测试。
  4. 花费足够的时间来了解requirement的各个细节,而不是被动的接受就好了。
  5. 在你做任何架构演变的过程中,用文档的方式记录下来你为什么要做这些架构的演变。

总结

本文基于Ivan的演讲总结了DAZN的架构演变,希望你能从中学习到一个startup的技术架构成长过程。

You may also like...

Leave a Reply

Your email address will not be published.