一个真实的事件溯源案例

架构师成长系列

Posted by 薛以致用 on July 26, 2020

本文是探讨“事件”机制的第三篇,第一篇《重新认识事件驱动架构》,我们讲解了如何理解”事件“本身,及常见的架构范式;第二篇《微服务和事件驱动》一起探讨了,事件驱动的微服务应用场景,尤其是目前比较流行的无服务器架构的事件微服务;本文重点跟大家一起认识一个真实的实现”事件溯源“模式的案例,奈飞的用户视频下载场景;

离线观看业务背景

早在 2016年,奈飞内部团队就收到客户反馈,”如果允许会员下载和离线观看视频会怎么样?“ 对于播放许可团队来说,需要提供一个新的内容许可系统,允许会员设备可以下载、存储和解密下载的内容以便于离线观看;要安全的做到这一点,需要一个新的服务来处理一组复杂的尚待定义的业务规则,以及用于客户端和服务器交互的新接口,另外,当奈飞所有现有系统都是无状态的,而这项服务是需要有状态的。

2016年11月底,在提出该提议后的短短 9个月,奈飞团队就成功推出了新的下载功能,允许全球所有会员在其移动设备商下载和播放视频。

Netflix 与全球各地的工作室合作伙伴合作,为我们的会员获取最佳内容;对下载内容的限制通常比流式传输更复杂,而且工作室之间的差异很大;除了可以观看多长时间的要求外,我们还根据设备或每个成员的下载次数提供各种不同的上限,以及在指定时间段内可以下载或观看视频的次数可能受到限制。

除此之外,奈飞内部还有与下载观看相关的业务限制,例如可以下载内容的设备数量;

我们必须将所有这些限制应用于 Netflix 会员下载的所有视频内容;每当成员在初始许可证到期后下载视频或希望延长查看时间时,我们都必须根据合作伙伴的所有可能限制验证请求,同时考虑到成员过去的下载交互;如果会员不满足任何这些要求,则后端会发回响应,说明下载请求失败的原因。

下载场景

当用户请求下载一个视频时,必须通过一个严格的许可申请和检查流程;

  • Netflix 客户端首先请求许可证
  • 获得许可证后,Netflix 客户端将下载内容,该成员可以播放其新下载的内容
  • 根据成员操作,许可证的状态在整个生命周期中会发生变化
    • 成员可以开始、暂停、恢复或停止观看内容
    • 成员可以删除下载的内容
  • 这些操作中的每一个都可能导致许可证的状态更改。许可证由会员显式创建、可能多次续订,最后释放(删除),或者根据业务规则隐式调整

Netflix Downloads Scenario

事件溯源模式

事件溯源(Event Sourcing)模式的实现依赖于三个不同的服务层:命令、事件和聚合,跟《微服务如何对齐业务架构》一文中的领域驱动的战术实现一致;

  • 命令 Command 表示用户更改聚合状态的请求。命令处理程序通过命令来确定如何创建满足命令所需的事件列表
  • 事件 Event 是聚合状态变化的不可变表示,即为更改状态而采取的操作,事件总是通过过去时态来表达
  • 聚合 Aggregate 是领域模型当前状态的表示形式。聚合响应事件流,并确定如何为用户请求的业务逻辑表示聚合状态数据

下图是奈飞在执行下载业务场景系统中应用事件溯源模式的高级视图,有若干组件参与实现这一模式。

Pattern

  • REST 服务 是接受来自客户端的请求并将其传递给聚合服务的应用层。
  • 聚合服务(AggregateService) 处理客户端请求。聚合服务查询并返回现有聚合实体,如果该聚合不存在,则创建空聚合。然后,聚合服务生成与请求关联的命令,并将命令与聚合一起传递给命令处理程序
  • 命令处理程序(CommandHandler) 接受聚合和命令输入,并根据状态转换有效性检查评估命令是否可以在其当前状态下应用于聚合。如果状态转换有效,命令处理程序将创建一个事件,并将事件和聚合传递给事件处理程序
  • 事件处理程序(EventHandler)事件应用于聚合,从而产生新的聚合状态,并将事件列表传递给存储库服务
  • 存储库服务(RespositoryService) 接受新创建的事件并应用于聚合来管理状态。随后,事件将保存到事件存储区,从而使聚合的新状态在我们的系统中可用。
  • 事件存储区(EventStore) 是与数据库进行事件读/写功能的交互抽象实现

不同的聚合场景

Netflix 客户端应用程序会发出不同类型的请求,这些请求被转换为命令、事件和聚合;为了满足许可强制验证的业务诉求,我们抽象出三个相互关联的聚合:许可证、下载的内容和设备;每个聚合有各自独立的服务,处理程序和存储库;通过下面几个”下载”相关的关键用例,我们理解下这些概念如何落地实现。

许可获取场景

当用户第一次请求一段内容进行下载播放时,用户必须申请一个许可才能继续下去;

  • 客户端发起一个 获取许可授权(AquireLicenseRequest) 请求,该请求携带 用户会员ID 以及期望下载的 视频内容ID
  • 许可服务(License Service) 负责判断该操作是否被允许,它查询对应的聚合并根据业务规则进行处理;由于这是会员对此内容的第一次请求,并假设该会员的设备和视频内容满足业务规则,许可服务将创建一个新的空许可证聚合,以及一个创建许可命令发送给许可命令处理程序
  • 许可命令处理程序 接受创建许可命令和许可证聚合,生成一个新的许可已创建事件,并把空的许可证聚合和该事件发送给事件处理程序
  • 事件处理程序 生成并返回一个新的许可证聚合对象给到许可存储库,许可存储库进而将许可已创建事件持久化到事件存储
  • 许可服务 许可存储库最终返回一个许可证聚合 给到许可服务,许可服务再打包该聚合信息返回给客户端

License Acquisition

许可续订场景

在许可证到期之前,设备可能会请求对现有许可证进行延期,称为许可证续订。续订许可证类似于首次许可证获取流程,其主要区别在于当前许可证聚合与续订许可证命令一起传递给许可证命令处理程序。许可证命令处理程序生成相应的事件后,许可证事件处理程序将许可证续订事件应用于许可证聚合。请注意,新的许可证聚合的到期日期是当前日期后的 30 天。这 30 天的续订代表了当前有效的许可证续订业务规则,如果后续业务规则改变了,我们只需将对事件处理程序进行简单的配置更新;

License Renew

下载次数超限场景

每次设备从许可证服务请求新许可证或续订许可证时,“已下载服务” 都会检索该会员的当前聚合,并通过业务规则进行验证。例如,业务要求某些内容每年只能下载两次。当设备提出许可证请求时,许可证服务会检查该内容是否该年度已下载;可以通过检索该年份的所有许可证聚合并筛选内容 ID 来获取此信息;这是涉及大量的数据处理,因此团队决定为内容下载创建一个单独的聚合,在会员和内容 ID 上编制索引;这需要新的 下载事件、服务、聚合和存储库

当我们收到内容的后续事件时,我们可以检查以前的内容下载情况,根据下面的序列图,如果下载服务发现该成员已超出限制,可拒绝该请求;

Download Scenario

总结

本文我们通过介绍奈飞团队实现一个有状态的离线下载播放授权服务,给大家分享一个真实的事件溯源架构的案例,其中,结合了事件风暴相关概念,该架构在奈飞的成功落地,验证了事件风暴架构在事件驱动服务架构的强大能力,相信可以启发大家如何设计一个事件驱动的微服务架构。

参考资料和扩展阅读:

  • Netflix Technology Blog《Scaling Event Sourcing for Netflix Downloads》

申明

本站点所有文章,仅代表个人想法,不代表任何公司立场,所有数据都来自公开资料

转载请注明出处


公众号二维码

诞生于 2019,遇见 2020。

感谢关注,欢迎动动手指标星和置顶;

这样就不会错过少但精彩的技术探讨、团队建设、案例分享!

每周至少一更,转发是对我的最大鼓励!

学习之路漫漫,走走停停,
偶有所感,随心所记,
言由心声,问心无愧!

从客户中来,到客户中去!