Fork me on GitHub

OPPO 技术 | 基于 ark 框架实现推荐策略 jar 包热更新

OPPO数智技术 稿

00 概述

在推荐领域,算法迭代效率也是核心竞争力之一。基于传统的CICD方案在推荐工程里就显得比较“臃肿”,为了满足算法策略的快速迭代需求,基于ark动态部署的方案应运而生。我们不只是简单ark框架的应用,而是在其基于上结合推荐业务的使用场景进行了定制化开发——比如如何区分环境、类加载机制的修改、资源释放机制等。本文首先会先介绍ark的核心功能——类隔离和合并部署,让读者对ark有一定了解。然后会对其和推荐工程结合过程中遇到问题和痛点及其解决方案。

01 ark简介

Ark 阿里开源的是一款基于 Java 实现的轻量级类隔离容器,主要提供类隔离和应用(模块)合并部署能力。

(1) ark类隔离机制

ark自定义了2种类型模块,biz和plugin,分别使用不同的BizClassLoader和PluginClassLoader去独立加载不同的biz和plugin下的class从而达到类隔离的目的。但是biz和plugin之间存在一定的委托关系:

  • BizClassloader之间是相互独立的,BIzClassLoder对PluginClassloder也只能单向委派类加载
  • PluginCLassloder之间可以相互委派,但不能方向加载BizClassloader里面的类

图片

(2) ark类加载机制

1)pluginClassloader 加载

PluginClassLoader之间委派加载是有条件的,用于解决plugin之间依赖冲突的问题

在pluhin项目的maven打包配置里面暴露 export和import 配置,用于表明该插件导出和导入的类(或者package)

  • 导出——该plugin向外提供的类(或package)列表
  • 导入——对配置的类(或package),优先从其他组件导出的类加载,加载失败再尝试从自身的classPath加载

因此plugin的加载顺序如下图所示:

图片

例如.PluginA导出了 a.b.CacheUtils的类,同时PluginB配置了导入a.b.CacheUtils类,这样在PluginB在加载类a.b.CacheUtils的时候,实际是委托给PluginA。(即在PluginB里a.b.CacheUtils类的Classloader是PluginA的ClassLoader),如下图所示

图片

2)BizClassloader加载机制

BizClassLoader加载类的时候优先是从plugin导出的类加载(除jdk和ark框架本身的class),其次再是从当前classpath加载。

图片

这样设计的好处

  • 对于plugin导出的类,都由plugin自身去加载,达到共享的目的
  • 对于plugin未导出的类,能够与biz的类隔离,达到类隔离的目的

比如,工程Project有2个依赖DependencyA和DependencyB, 但是他们都依赖了C的不同版本(他们之间是不可兼容的)。因此基于ark就能做到以下类隔离方案。

图片

(3)动态部署

ark除了静态部署外,还提供一种动态部署BIzJar的方式,主要是通过zk来注册、监听BizJar的变更事件。

另外需要注意:

  • 静态和动态只能选择一种部署方式
  • 一个bizjar的类在卸载的时候,其加载对象不一定真的卸载(比如线程池)

图片

02 在推荐场景架构方案

目前推荐引擎已经是一套比较完整的框架和模块划分(如下图),在推荐引擎基础上,各个推荐业务只需要做策略的相关开发。下面将介绍如何基于既有推荐引擎结合ark热部署加速业务策略的迭代效率。

图片

(1) 整体架构

从上面的模块分布来看,其中大部分模块和代码是引擎提供的,可以作为plugin提供服务;而业务需要开发具体的策略逻辑,这些代码相对比较少且变动频率高,因此作为BizJar进行动态热更新。

此外还需要有一个界面能完成bizjar的管理、线上bizjar的状态查看已经bizJar的部署功能。因此就动态热部署的架构如下图:

图片

下面将介绍里面模块划分的原因

(2) Entry Biz-优雅的替换BizJar

如果把调用入口(如http接口)放到biz jar里面。在启动新的bizJar(这时旧的bizJar服务还没有停止),就会因为端口或者urlPath冲突导致新的bizJar无法启动

因此,通过EntryBIz模块,封装好调用入口,通过替换引用的方式达到优雅替换业务BizJar。如下图



图片

(3)Recommend Dependency—业务和引擎依赖解耦、独立迭代

目前动态更新的业务BizJar和引擎依赖是分别通过ark biz(热部署)和ark plugin方式打包运行,如下

图片

这样做的好处

  • 业务工程和引擎工程独立迭代
  • 业务bizJar通过provide引入引擎依赖,本身打包后jar包更小

下图上半部分是业务和引擎解耦的迭代方式——引擎版本升级和业务版本解耦。

下图下半不符是业务和引擎合并一起打成bizJar——引擎有版本迭代的时候,需要通知业务重新打包发布

图片

(4) recommend plugin——类隔离、组件复用

推荐引擎内部的模块会根据不同的功能,拆成不同的ark plugin。这样做的目的是:

  • 不同的plugin可以由不同的人员负责开发和维护,最后各个插件集成至推荐引擎中基于类隔离机制而避免依赖冲突
  • 对于组件可以在业务BizJar更新后也能复用,比如缓存、连接池等资源

如下图

  • 不同CachePlugin和PredictorPlugin可以依赖不同的guava版本,避免依赖冲突
  • BizJar更新后,CachePlugin的数据能够复用,避免缓存全部失效带来的风险

图片

1)引擎插件分类

目前引擎插件主要分为以下几类

  • store-plugin——代理redis、cassandra、mongo等不同存储的client
  • common-plugin——提供缓存,线程池,监控等公共组件的插件
  • client-plugin——不同远程调用(predictor client,grpc、http)的插件

2)插件编写规范

因为不同插件里面的类是使用不同的ClassLoader进行加载的,因此为了避免因为ClassLoader不一致导致异常问题——调用插件的入参和出参虽然是同一个class文件,但是由于是不同的ClassLoader加载从而导致并不是同一个类。特别是引入大量插件的情况下这种问题更容易出现,因此制定了以下插件编写原则

  • plugin内部依赖和流程独立——即一个插件能够完成功能的闭环,尽量避免引用其他插件导致的后续插件依赖爆炸的问题。
  • plugin对外暴露尽量少的类——对外提供调用接口的入参和出参尽量是jdk内置的类

如下图是监控组件的插件代码结构,只对外暴露了3个类,MonitorMetricsManager、MetricsTags、MetricsClient

  1. 首先监控plugin暴露了MonitroMetricsManager类,用于完成初始化和获取MetricsClient的方法
  2. 通过获取MetricsClient后,能够进行相关counter、timer等监控打点方法
  3. 此外监控plugin内部的依赖和处理流程完成闭环在plugin内部完成(包括如何注册监控、处理监控信息、上报监控数据等)

图片

03 在ark的基础上的定制化

在使用ark的过程中,也发现了一些无法满足推荐使用场景的情况,下面将介绍一下使用ark遇到的问题和解决的方案

(1)功能定制化

在ark0.6.0版本的基础上,在功能上做了以下变更:

  • 同时支持静态部署和动态部署的方式——ark本身只能选择静态部署和动态部署其中一种方式,因此需要兼容2种方式已完成EntryBiz的静态部署、业务bizJar的动态部署需求。
  • jdk11支持——升级部分依赖、jdk11类的适配
  • zk注册信息增加分组字段,方便分组管理
  • 对新加载的bizJar支持延迟启动

(2)资源释放问题

痛点:当某个bizJar卸载后,某一些资源可能是没有被正确释放的,比如线程池是在某个bizJar里面创建的,在替换新的bizJar时新的线程池还是会创建,但是卸载的Bizjar创建的线程池却没有被shutdown,导致每次更新线程数会不断增加。

解决方案:为了满足资源正常释放的问题,在ark里面增加BizJar增加回调方法,当某个BizJar在初始化过程中注册回调事件,当该对应的BizJar卸载时会调用回调方法

图片

(3)类加载机制

痛点:虽然通过分离业务jar和引擎依赖的pluginJar从而实现依赖解耦,但会出现另外一个问题——引擎的相关依赖如果作为Plugin提供服务,可能会影响其他的Bizjar的依赖。

解决方案:如何能做到引擎依赖plugin只对业务BizJar?对ark类加载机制做了修改,即实现一种特殊的pluginJar和BizJar做绑定。

绑定后的依赖关系是:

  • 作为依赖的pluginJar会暴露内部所有的类,但只对绑定 Bizjar提供服务
  • bizJar在从其他非绑定的pluginJar加载失败后,再从绑定的pluginJar加载

图片

04 总结

目前基于ark动态部署的方案在主题商店推荐业务已经正式使用,从策略迭代速度比较,从1小时降低至10分钟。并且基于类隔离机制,也解决之前存在的各种依赖冲突的问题。但是目前还有些问题待解决。比如目前引擎版本存在历史多版本共存、底层插件还不够完备等原因导致多版本不兼容带来的维护问题和对该框架的使用需要有一定理解成本。目前我们也在致力于推荐引擎底层统一以及引擎组件的丰富,在此基础上,ark动态部署的方案就能实现其真正的价值。

作者简介

Lizo OPPO高级后端工程师

主要负责OPPO推荐系统在线推荐引擎架构的开发和迭代,也同时负责OPPO包括游戏中心、主题商店、锁屏等多业务的推荐服务。对java相关技术和推荐业务有5年以上经验


本文地址:https://www.6aiq.com/article/1661780124851
本文版权归作者和AIQ共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出