事件驱动架构在vivo内容平台的实践(代码片段)

vivo互联网技术 vivo互联网技术     2022-11-28     802

关键词:

作者:vivo互联网服务器团队-Gao Xiang


一、什么是事件驱动架构


当下,随着微服务的兴起,容器化技术的发展,以及云原生、serverless 概念的普及,事件驱动再次引起业界的广泛关注。


所谓事件驱动的架构,也就是使用事件来实现跨多个服务的业务逻辑。事件驱动架构是一种设计应用的软件架构和模型,可以最大程度减少耦合度,很好地扩展与适配不同类型的服务组件。在这一架构里,当有重要事件发生时,比如更新业务数据,某个服务会发布事件,其它服务则订阅这些事件;当某一服务接收到事件就可以执行自己的业务流程,更新业务数据,同时发布新的事件触发下一步。


事件的发布与订阅,需要依赖于一个可靠的消息代理。见下图:



当然,事实上有不少软件项目都使用了消息队列,但是这里需要明确的是,对消息队列的使用并不意味着你的项目就一定是事件驱动架构,很多项目只是由于技术方面的驱动,小范围地采用了某些消息队列的产品而已。偌大一个系统,如果你的消息队列只是用作邮件发送的通知,那么这样系统自然谈不上采用了事件驱动架构。


在采用事件驱动架构时,我们需要考虑业务的建模、事件的设计、上下文的边界以及更多技术方面的因素,这个系统工程应该如何从头到尾的落地,是需要经过思考和推敲的。总而言之,“事件驱动架构”的设计并不是一件易事。本文在后面有个例子供参考。


另外,如果盲目使用事件驱动设计架构,就有可能要承担中断业务逻辑的风险,因为这些业务逻辑具有概念上的高度内聚,却采用了解耦机制将它们联系在一起。换句话说,就是将原本需要组织在一起的代码强行分离,并且这样难于定位处理流程,还有数据一致性保证等问题。为了防止我们的代码变成一堆复杂的逻辑,我们应当在某些明确场景下使用事件驱动架构。以经验来讲,以下三 种场景可以使用事件驱动开发:


  • 组件的解耦

  • 执行异步任务

  • 跟踪状态的变化


  • 二、什么时候使用事件驱动架构


    2.1 组件的解耦


    当服务(或组件) A 需要执行服务 B 中的业务逻辑,相比于直接调用,我们可以向事件代理(事件分发器)中发送一个事件。服务 B 通过监听分发器中的特殊事件类型,然后当这类事件被接收到时去执行它。


    这意味着服务 A 和服务 B 都依赖于事件代理和事件,而无需关注彼此实现:即完成它们的解耦。见下图:



    基于这种松耦合,服务可以用不同的语言实现。解耦后的服务能够轻松地在网络上相互独立地扩展,通过动态添加或删除事件生产者和消费者来修改他们的系统,而不需要更改任何服务中的任何逻辑。


    2.2 执行异步任务


    有时我们会有一系列需要执行的业务逻辑,但是由于它们需要耗费相当长的执行时间,所以我们不想看到用户耗费时间去等待这些逻辑处理完成。在这种情况下,最好将它们作为异步任务来运行,并立即向用户返回一条信息,通知其稍后继续处理相关操作。


    比如,内容字段的检查等入库流程可以采用“同步”执行处理,但是执行内容理解则采用”异步“任务去处理。在这种情况下,我们所要做的是触发一个事件,将事件加入到任务队列中,直到一个服务能够获取并执行这个任务。此时,相关的业务逻辑是否处在同一个上下文中环境中并不重要,不管怎么说,业务逻辑都是被执行了。


    2.3 跟踪状态的变化


    在传统的数据存储方式中,我们通过实体模型存数据。当这些实体模型中的数据发生变化时,我们只需更新数据库中的行记录来表示新的值。这里有个问题,就是业务上我们无法准确存储数据的变更和修改时间。但是在事件驱动架构中,可以通过事件溯源将包含修改的内容存入到事件里。下面会详细讨论“事件溯源“。


    三、为什么使用事件驱动架构


    当大家谈论事件驱动架构时,比如大家说自己恰好在最近的项目中采用了事件驱动架构,实际上,他们可能在谈论下面这四种模式中的一种或者几种:


  • 事件通知

  • 事件承载状态转移

  • 事件溯源

  • CQRS


  • 注:概念来源2017年GOTO Conference上Martin Fowler分享的The many meanings of Event-Driven architecture。


    3.1 事件通知


    假设我们现在想要设计一个简易的内容平台,包含三部分:


  • 内容引入系统

  • 作者微服务

  • 关注中心


  • 当内容创作者通过内容引入系统上传视频之后,会触发如下的一个调用流程见下图:



    1. 内容引入系统收到创作者上传的视频,执行入库流程;

    2. 内容引入系统调用作者微服务的API,增加“视频-创作者”的从属关系;

    3. 作者服务调用关注中心的API,让关注中心给关注了这个创作者的其他用户发送作者视频更新的通知。


    上面这个调用流程,不可避免地创建了下面的依赖关系:


  • 内容引入系统依赖于作者微服务的API,虽然内容引入系统其实不太关心作者微服务的业务。

  • 作者微服务依赖于关注中心的API,虽然作者微服务也不关心关注中心的业务和处理流程。


  • 这种依赖关系很有可能并不是我们所期望的。内容引入系统是一个比较通用的业务,不同类型的内容引入系统很可能会有相似功能,如字段类型检查、入内容库、启动高敏审核等。作者服务则是一个非常专业的系统,如不同源、不同类型的内容关于作者的业务逻辑是不同的。让一个通用的系统依赖于一个专业的系统,不管从设计角度,还是后续系统维护角度,都是不一个好的方案。作者微服务可能会经常根据业务需求做变更,但内容引入系统相对稳定,而上面这种依赖关系让我们难以在“不对内容引入系统做调整的情况”下随意更改作者微服务。


    从架构层面,我们希望让作者微服务依赖于内容引入系统,让一个专业的系统依赖于一个稳定的、通用的系统,增加系统的稳定性。这个时候我们可以借助于“事件通知”。见下图:



    优点

  • 架构更健壮。如果加入队列的事件能够在源组件中执行,但在其它组件中由于 bug 导致其无法执行(由于将其加入到队列任务中,它们可以在 bug 修复后再执行)。

  • 业务处理减少延迟。当用户无需等待所有的逻辑都执行完成时,可以将这类工作加入到事件队列。

  • 便于系统扩展,能够让组件的研发团队独立开发,加快项目进度、降低功能难度、减少问题发生并且更有组织性。

  • 将信息封装在“事件”里,便于系统内传播。


  • 缺点

  • 如果没有合理使用,可能使我们的代码变成“面条式”代码。

  • 数据一致性问题。由于流程依赖于最终的一致性,因此通常不支持ACID事务,因此重复或乱序事件的处理会使服务代码更加复杂,并且难以测试和调试所有情况。


  • “事件通知”的缺点和优点相对应,正是因为它提供了很好的解耦能力,我们会比较难通过阅读代码去得到整个系统和流程的全貌。因为这些逻辑之间的关系不再是之前的依赖关系。这将会是一个挑战。


    3.2 事件承载状态转移


    我们在使用事件通知时,事件里面往往不会包含下游系统处理这个事件需要的所有信息。比如当内容发生下架变更时,内容平台会生成一个“内容下架“的事件,但当下游系统处理这个事件时,往往还需要知道,该内容上个状态是什么,是谁触发下架等信息,才能完成后续处理。所以不可避免地,下游系统在处理这个事件时,往往还需要通过平台服务来获取这些额外信息。


    为了解决这个问题,我们引入一个种新的模式,叫做“事件承载状态转移”。简单来说,就是让事件的消费方自己保留一份在业务处理过程中需要用到的上游系统的数据。比如让下游系统保留一份在处理内容状态变更事件时所需要用到的内容变更前的状态,避免回头去平台查询。


    优点

  • 架构更健壮。减少事件消费方对生产方的额外依赖(获取事件处理所需数据);

  • 业务处理减少延迟。增加事件消费方系统的响应速度,因为不再需要调用平台API以获取事件处理所需数据;

  • 无需担心被查询组件的负载(尤其是远程组件)。


  • 缺点

  • 尽管现在数据存储已经不再是问题根源,依然会保存多个只读的数据副本,一致性进一步被破坏;

  • 增加数据处理的复杂度,即使处理逻辑符合规范,它也需要额外处理和维护外部数据的本地副本业务逻辑。


  • 3.3 事件溯源


    有些时候我们不但关心系统当前的状态,我们还关心如何变成当前这个状态的,但是数据库仅仅简单地保存实体的当前状态。事件溯源可以帮助我们解决这个问题。


    事件溯源是一个特别的思路,它并不持久化实体对象,而是只把初始状态和每次变更的事件记录下来,并在内存中根据事件还原实体对象的最新状态,mysql主从备份用到的binary log以及redis的aof持久化机制,都可以认为是“事件溯源”的实现。


    事件溯源在做完数据库更新之后,它将事件的发送操作转换为往数据库或者日志系统中写入一条事件记录,其它节点通过查询数据库或者文件系统,来得到这些事件,并通过回放来确保数据的最终一致性。


    优点

  • 可以呈现一个完整的变动历史;

  • 提供更方便的debug手段;

  • 可以回溯到任何一个历史状态;

  • 方便修改当前事件;


  • 缺点

  • 要实现一个可靠和高性能的事件仓库(保存的事件记录)并不是一件容易的事情,应用代码需要根据事件库的 API 进行重写。


  • 3.4 CQRS


    CQRS全称是Command Query Responsibility Segregation。简单来说,就是针对系统的读写操作,使用不同的数据模型、API接口、安全机制等,来达到对读写操作的完全隔离,满足不同的业务需求。见下图:



    根据存储在事件库中的事件集合,可以计算得到每个业务实体的状态,这些状态以物化视图的方式存储在一个数据库中。当有新的事件产生时,也同样会自动更新视图。这样,视图查询服务就可以像查询普通的数据库数据一样实现各种查询场景。具体的设计可参考下图所示:



    四、事件驱动架构在内容平台中的实践


    在当今社会,内容“横行”的时代,内容平台企业需要有极强的灵活性和应变能力。特别是在中国这样一个内容行业(如视频)飞速发展的市场里,企业要求平台能够快速地对内容业务需求做出应对,否则就会丧失先发优势。这有点类似于现代战争条件下,各国都要求部队具备快速反应能力,这种能力主要体现在平台能够通过快速开发或者重用 / 整合现有资源来达到快速响应业务需求。


    随着内容行业业务越来越庞大复杂,所涉及的存储类型、处理器、账号体系、效率工具、数据和结算系统等非常多,这就要求平台有很强的整合能力以及对异构环境的适配能力。


    最后,由于内容行业的发展日新月异,特定类型的内容业务(如小视频)都会在其初中期发展后迎来一个快速膨胀期,业务量和业务类型会急剧增加,这也要求平台有很好的可扩展性。相关平台架构见下图:



    4.1 创建事件


    事件其实是DDD(领域驱动设计)中的一个概念,表示的是在一个领域中所发生的一次对业务有价值的事情,落到技术层面就是任何影响业务流程或者状态的改变。事件具有自己的属性,比如发生的时间、发生了什么、事件之间的关系、状态以及变化,事件也可以生成新的事件,根据不同的事件生成新的业务事件。在创建事件时,首先需要记录事件的一些通用信息,比如唯一标识ID和创建时间等,为此创建事件基类ContentEvent:

  • AbstractContentEvent    eventId;   publisher;   receiver;   Long publishTime;         String videoId;   Article article; VideoStatusChangeEvent VideoEvent    preStatus;    status; 
  • vivo 敏感词匹配系统的设计与实践

  • 前端质量提升利器-马可代码覆盖率平台

  • vivo 全球商城:商品系统架构设计与实践

  • vivo全球商城:电商交易平台设计(代码片段)

    ...列文章一、背景vivo官方商城经过了七年的迭代,从单体架构逐步演进到微服务架构,我们的开发团队沉淀了许多宝贵的技术与经验,对电商领域业务也有相当深刻的理解。去年初,团队承接了O2O商城的建设任务,还有即将成立... 查看详情

    serverless工程实践|自建apacheopenwhisk平台(代码片段)

    ...,可以在运行时容器中通过执行扩展的代码响应各种事件,而无须用户关心相关的基础设施架构。OpenWhisk简介OpenWhisk是基于云的分布式事件驱动的编程服务。OpenWhisk提供一种编程模型,将事件处理程序注册到云服务中... 查看详情

    vivo互联网机器学习平台的建设与实践

    vivo互联网产品团队-Wangxiao随着广告和内容等推荐场景的扩展,算法模型也在不断演进迭代中。业务的不断增长,模型的训练、产出迫切需要进行平台化管理。vivo互联网机器学习平台主要业务场景包括游戏分发、商店、商城、内... 查看详情

    dapr在java中的实践之环境准备(代码片段)

    Dapr简介Dapr(DistributedApplicationRuntime)是一个可移植的、事件驱动的运行时,它使任何开发人员都可以轻松地构建运行在云和边缘上的弹性、无状态和有状态的应用程序,并支持语言和开发人员框架的多样性。Dapr利用Sidecar架... 查看详情

    阿里云eventbridge事件驱动架构实践

    ...品研发。今天我的分享主要包括以下几部分:消息与事件、微服务与事件驱动架构阿里云EventBridge:事件驱动架构实践基于RocketMQ内核构建阿里云统一的事件枢纽云原生时代的新趋势:Serverless+事件驱动事件驱动架... 查看详情

    阿里云eventbridge事件驱动架构实践

    ...品研发。今天我的分享主要包括以下几部分:消息与事件、微服务与事件驱动架构阿里云EventBridge:事件驱动架构实践基于RocketMQ内核构建阿里云统一的事件枢纽云原生时代的新趋势:Serverless+事件驱动事件驱动架... 查看详情

    apisix在君润人力云原生平台的架构实践(代码片段)

    ...台的功能需求。君润人力成立于2019年,是一家以科技驱动的人力资源解决方案服务商,依托行业领先的科技水平和服务能力,专注于为客户提供一站式人力资源服务。自研数十家人力资源服务平台,深度链接B端... 查看详情

    vivo服务端监控体系建设实践

    ...器团队-ChenNingning本文根据“2022vivo开发者大会"现场演讲内容整理而成。经过几年的平台建设,vivo监控平台产品矩阵日趋完善,在vivo终端庞大的用户群体下,承载业务运行的服务数量众多,监控服务体系是业务可用性保障的重要... 查看详情

    领域驱动设计领域事件ddd分层架构(代码片段)

    领域事件什么是领域事件为什么需要领域事件微服务场景下的领域事件领域事件的实现DDD的分层架构DDD分层架构如何推动微服务演进领域事件什么是领域事件在事件风暴过程中,会识别出命令、业务操作、实体等,此外还有事件... 查看详情

    架构师日志平台elkstack实践(代码片段)

    步骤1.理解ELKStack0 在设置您自己的ELK堆栈之前,了解一点关于该堆栈及其组件的知识会对您有所帮助。ELKStack由三个组件组成:Logstash、Elasticsearch 和 Kibana。Logstash 是一个用来管理日志的工具。它支持几乎任何类型... 查看详情

    serverless工程实践|快速搭建kubeless平台(代码片段)

    ...llerina语言编写和自定义运行时。KubelessCLI符合AWSLambdaCLI。事件触发器使用Kafka消息系统和HTTP触发器。Prometheus默认监视函数的调用和延时。支持Serverless框架插件。由于Kubeless的功能特性是建立在Kubernetes之上的,因此对于熟悉Ku... 查看详情

    serverless工程实践|自建apacheopenwhisk平台

    ...,可以在运行时容器中通过执行扩展的代码响应各种事件,而无须用户关心相关的基础设施架构。OpenWhisk简介OpenWhisk是基于云的分布式事件驱动的编程服务。OpenWhisk提供一种编程模型,将事件处理程序注册到云服务中... 查看详情

    乐刻大数据平台架构实践(代码片段)

    乐刻运动大数据是基于hadoop体系搭建的,主要满足运营的日常报表,以及公司核心指标为主。随着2016年线上线下的发力,以智能化健身为主的共享经济的提出,数据需求量大幅的激增,数据从最初的GB级到现... 查看详情

    redis在vivo推送平台的应用与优化实践

    作者:vivo互联网服务器团队-YuQuan一、推送平台特点vivo推送平台是vivo公司向开发者提供的消息推送服务,通过在云端与客户端之间建立一条稳定、可靠的长连接,为开发者提供向客户端应用实时推送消息的服务,支持百亿级的... 查看详情

    微服务实践:微服务的事件驱动数据管理

    ...现的可行方案以及实践案例微服务实践(五):微服务的事件驱动数据管理微服务实战(六):选择微服务部署策略微服务实践(七):从单体式架构迁移到微服务架构微服务实践(总)-原文 微服务实践(五):微服务的... 查看详情

    事件驱动架构模式其他模式(代码片段)

    事件驱动架构模式publicclassEventDriven/***事件驱动架构模式:*Sendandnotifystatechangesofyourobjectstootherapplications*usinganEvent-drivenArchitecture.*使用事件驱动的架构来发送通知【此对象状态的变更】给应用程序中的其他组件,解耦了事件源和... 查看详情

    架构自治服务:构建数据驱动的架构洞察(代码片段)

    架构自治服务是一种面向架构分析领域的数据自助服务。它提供了一种集成一体的数据分析方案,让开发人员、架构师、管理者等可以根据不同任务,自由搭配、组合出适用于自身洞察需求的任务/函数。最近,刚好... 查看详情

    7.携程架构实践---iaas&paas(代码片段)

    第7章IaaS&PaaS 如果想要产品交付越来越快,就需要提升基础设施的交付能力。虚拟化技术,通过OpenStack平台统一了虚拟机/物理机的资源交付,提供了真正意义上的IaaS服务。一套稳定的,可靠的,高效的持... 查看详情