栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

技术博客|第11期:neutrino - 一个应用于apache spark分布式系统的依赖注入框架(下)...

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

技术博客|第11期:neutrino - 一个应用于apache spark分布式系统的依赖注入框架(下)...

2022年第011篇

本文上篇介绍了一下neutrino的原理和基本用法,下篇介绍扩展功能,包括如何利用neutrino传递任意对象,以及checkpoint恢复,对象生命周期控制,和其他扩展功能。

如果一定要绑定并传递不可继承类如final class,neutrino提供了一个底层API:SerializableProvider接口,可实现无缝传递。

使用过Spring或Guice API的读者一定对Provider接口比较熟悉,SerializableProvider实际上就是一个同时包含Serializable和Provider的接口。

例如,有一段处理点击事件的代码,如下所示:

如果使用前面同样绑定方式,则编译报错,因为无法为final类继承生成一个Proxy。

那么这种情况可以改用如下的方法:

① 绑定final类EventProcessor

② 绑定EventProcessor的SerializableProvider,提供SerializableProvider[EventProvider]注入

那么如何生成EventProcessor实例呢?

这里的Provider包含对应依赖图节点的id,可实现无缝传递。事实上,上篇中自动生成的代理也是基于SerializableProvider实现的。

当然SerializableProvider也可以通过构造函数注入的方式使用,比如:

我们可以看到,neutrino的对于对象传递时序列化的处理就是在worker上利用依赖关系图重新创建对象。这带来一个问题,即如果依赖注入生成对象的状态在driver上被修改,那么这种修改无法通过这种方式传递到worker上。

如果开发者一定要用neutrino框架生成可变状态的对象,可以使用guice原生的方式绑定(即不使用withSerializableProxy或者SerializableProvider绑定),但是仍然可以依赖于以neutrino的API绑定的对象。

例如我们把上篇中的例子修改一下:为了减轻对后台的压力,提供item信息的API服务为后台加了缓存。并在API加了一个参数(1~5之间),用来指示缓存级别。1表示绕过缓存,直接读取后台数据,对后台压力大;5表示1小时缓存,对后台压力小,但数据可能是旧的。平时我们可以采用级别1(即不读缓存);大促销期间压力大,可以通过配置动态改变缓存级别参数,而不需要重启服务。

在具体实现中,我们在spark streaming服务的每一个时间间隔从配置中心读取当前的缓存级别。代码如下:

可以看到此时HttpEventEnrichment增加了一个成员变量用来表示当前缓存级别,该变量在driver上初始化时从配置中心读入。这使得enrichment对象的状态在从依赖注入容器生成之后发生了改变,而该变化又必须被传递到worker上,因时EventEnrichment不能再使用neutrino的绑定方式(即使用withSerializableProxy和SerializableProvider),此时它将作为普通java对象传递,实现了Serializable接口。以下是EnrichmentModule的代码:

此处EventEnrichment按照Guice原生API绑定,这样enrichment不再是代理,而是HttpEventEnrichment实例。

而且由于HttpEventEnrichment依赖于HttpClient,后者实际上会随着enrichment对象被传递到worker,所以我们需要调用withSerializableProxy为HttpClient生成一个代理,保证在worker上利用当前JVM的依赖图生成一个实例。也就是说Java原生序列化对象也可以依赖于neutrino产生的代理或者SerializableProvider。

注意此时因为enrichment对象每个时间间隔的状态可能会发生变化,scope不能再设置为Singleton,此时修改为StreamingBatchScope,即每一个spark streaming周期内共用一个实例。或者可以使用Guice默认的NO_SCOPE,即每次请求自动生成一个新实例。

不过我本人并不推荐通过依赖注入生成可变状态对象。上面的例子中,读取缓存级别实际上可以看成创建EventEnrichment的中间步骤,所以也可以通过抽象工厂模式实现。代码如下:

这种就可以规避对象状态可变的问题。

一般情况下,只在driver上使用的对象不需要被序列化。但是如果使用spark的checkpoint功能,则其中有些对象也需要被序列化。neutrino框架对于这部分也有相应支持。

例如使用上文中提到的RDDHandler,如果改写成支持checkpoint形式,代码如下:

①在创建StreamingContext之前创建injector,上文已介绍

②在spark streaming任务中使用checkpoint功能时,需调用neutrino提供的sparkSession.getOrCreateStreamingContext方法,传入的回调函数会在没有checkpoint数据存在时被调用

③创建StreamingContext,此处不能直接调用StreamingContext构造函数,而应该调用neutrino提供的session.newStreamingContext方法

④设置checkpoint地址

⑤从injector获得handler实例

一般情况下,如果使用了checkpoint,虽然handler对象只在driver上使用(foreachRDD中的回调在driver上执行),但是因为参与了RDD DAG的构建,从checkpoint恢复重建DAG时需要用到这个handler对象,所以它也要被序列化。

但是,如果使用了neutrino这个问题可以不用考虑,唯一需要做的是在绑定RDDHanlder的时候调用withSerializableProxy

neutrino会在spark任务从checkpoint恢复时自动根据依赖关系图重建这个对象。

实际上,neutrino在spark任务从checkpoint恢复时,首先恢复各个JVM上的依赖图,然后根据该图重新创建driver和worker上的对象。

neutrino提供了关键spark对象的注入功能,如SparkSession,SparkContext, StreamingContext都支持注入,当然前提是它们只能在driver上使用。

例如我们可以用这一功能抽象一个Stream的生成:

绑定之后就可以直接从injector得到DStream[ClickEvent]实例:

熟悉Guice和Spring API的读者可能知道,guice injector (或Spring的ApplicationContext)支持继承,即父子injector (parent/child injector),本框架对此也提供支持。具体API用法见开源项目README,在此不再赘述。

下篇首先介绍使用底层API实现任意类型实例(包括final类型绑定实例)的传递,接着综合上篇提到的上层API(自动代理)方法,分析了neutrino的使用限制,以及解决的方法。最后提到了neutrino的一些其他的功能。

Russell Bie,内容发现部门资深软件工程师

内容发现部门(Content Discovery Org.)是迪士尼流媒体核心研发部门,主攻Hulu、Disney+、Star+等迪士尼流媒体产品线的三大业务方向:搜索、个性化推荐、内容推广。在每个业务方向上都和人工智能技术深度融合,涉及AI平台的搭建、前沿算法的研究、以及工程系统的集成,致力于为迪士尼流媒体用户提供最佳的视频观看体验。

自成立伊始,该部门始终将内容的精准传递作为首要业务目标,深入结合工程、算法和数据,利用人才优势与人工智能基础解决业务问题。

职位信息: 职位列表链接

感兴趣的同学发送简历至:bjindustry@disney.com

(烦请标注申请职位+姓名)

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1035883.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号