浅析云平台中的“胖前端”设计思维

举报
HW007 发表于 2021/07/22 11:21:34 2021/07/22
【摘要】 作为一名AI云平台研发工程师,之前更多地,都是以“服务化”的角度去看云平台,就是怎么把应用改造成http等服务,然后部署到云上。较少从“版本管理”、“稳定性”这样一个视角去看云平台。直到最近对SageMaker平台进行了一番深度体验和分析,加上本身的承担的集成解耦工作,有点阴差阳错地,把这个视角开启了。

作为一名AI云平台研发工程师,之前更多地,都是以“服务化”的角度去看云平台,就是怎么把应用改造成http等服务,然后部署到云上。较少从“版本管理”、“稳定性”这样一个视角去看云平台。直到最近对SageMaker平台进行了一番深度体验和分析,加上本身的承担的集成解耦工作,有点阴差阳错地,把这个视角开启了。

一、软件设计中的变与不变

首先得从一句老话说起,“唯一不变的就是变化”。

第一次听到这句话,是在大学的JAVA课上,充满哲学智慧的一句话,若是配上网上产品经理与码农间因“改需求”引发的段子或动图,又多了几分码农自黑的味道。当然也正是因为这句话,才会出现“软件架构设计”、“软件可扩展性”等概念,才有了架构师这样一个面向“变化”编程的工种。在我的理解中,面向“变化”编程,无非就是“业务抽象”、“组件重用”、“分层解耦”三板斧。下面我们来聊聊“分层解耦”。

在软件建模的世界里,没有什么是加一层解决不了的,如果有,那就加两层。MVC三层架构、DDD的四层模型、OSI网络七层协议,莫不如是。

绕了一大圈,总算从“变化”引出“分层”这个主角了。一般地,“分层”主要有两大功能,一个是分边界划权责,使层次更清晰。另一个是隔离变化,解耦合。复杂软件最担心的是“蝴蝶效应”,牵一发动全身。

假如我问你,在上图的分层架构中,最怕出现变化的是哪一层?相信很多人会不假思索地回答是“领域模型”层,做为分层结构中的最底层,这里的变化是最有可能形成“蝴蝶效应”的。我也是这样认为的,所以在此之前,我最关心的是“领域模型”层,一个好的软件,对业务领域的理解和抽象建模应该是有一定的超前、预见性的,这是显而易见的。

直到近来搞解耦,一边被下游服务追着怼,一边追着上游服务怼,前些天和主管讨论一个问题的时候,才意识到,当从系统的外面看进来,好像“接口层”的稳定性要比“领域模型层”的稳定性要重要。刚意识到这点时,有点懵圈,仿佛三观被刷新了一样,在软件中,不应该是越底层,功能便越基础越简单,越上层,功能便越是丰富和复杂么?不应该是越简单的东西,便越可能稳定么?

既要上面的“接口层”稳定(简单),又要底层的“模型层”稳定(简单),夹逼原理,量中间层再复杂也复杂不到哪里去了。所以,复杂都是自找的?所以加班都是自作的?答案显然是否定的,好的架构设计能减少不必要的工作量,却不能消灭必要的工作量,一如“变化”不能被消灭,“唯一不变的就是变化”。

二、软件设计中的繁与简

在上节中,我们从“变化”,引出“分层”,从分层引出“接口层”和“模型层”都要简单这样一个近乎“五彩斑斓的黑”这样的诉求。诚然,“五彩斑斓的黑”是不可能的了,这辈子都不可能的,但“接口层”和“模型层”都要稳定这样的诉求,还是可以尽力试下的。

在我看来,解决系统复杂度的方法主要可以分为以下三大类:

  • 【化繁为简】:也即“抽象”大法,就像小学数学中的合并同类项等等,在这类系统本身就是简单的,呈现出来的复杂性都是人为引入的,比如说,不熟悉系统的业务场景,对系统建模的切入角度错了,抓不到系统的常变点和稳定点等等。

  • 【删繁就简】:也即“砍需求”大法,此策略常用于拥有较多的平行应用场景的软件系统中,这时候就可以通过在文档中标明该系统支持和不支持的场景范围来降低系统复杂度,这种“砍”法比较粗暴,还有一种比较优雅的“砍”法,即通过增加系统的可扩展性来砍掉需求,所谓“这个需求不是我不支持,是需要你通过自己开发插件的方式来支持”。

  • 【分而治之】:也即“拆分”大法,此策略常用于应用场景的垂直栈特别深的系统,由于每部分间不是平行关系,且缺少任意一个,整个系统的功能就不是完备的,导致了这种系统根本无法通过“砍需求”大法来简化,只能通过划分边界来使其局部简单化。

现实中,系统复杂度的分类与解决方法并不如上述分类中那么泾渭分明,更多的是你中有我我中有你,举个栗子,像大名鼎鼎的领域驱动建模(DDD),试图通过整合领域专家和程序员的经验知识来捕捉系统中最本质的元素,减少因为认知偏差人为引入的复杂度,很明显针对的是上述的【化繁为简】;像在“微服务”这种架构,很明显对的是上述的【分而治之】,但在现实中,我们可以看到“领域驱动建模”和“微服务架构设计”的概念经常会一起出现,DDD被很多的人用来指导微服务的拆分。

铺垫了这么多,此时我们回到本文一开始关注的问题。

如上图,从上到下,为从前台到后台,往往,前台的功能比后台更丰富。比如基于JAVA开发的应用的数目和功能是要比Java的JDK接口的功能丰富得多的。从广义上说,某门语言的应用可以看做该语言的前台,把朴素的语言功能包装成一个个场景的解决方案。但是,根据我们上面的分析,要求接口层尽可能稳定(简单),由【删繁就简】大法,那就删掉一些接口和功能吧,又因为这个垂直的应用栈,去除的功能会影响整个应用场景的功能,不能删,故这部分“待删除”的功能往往会被整合成另一个相对独立的组件(胖前端)。【删繁就简】大法最终演变成了【分而治之】。

如果你是一个传统应用软件(非服务化发布)的工程师,对上述的这个演变描述也许会持反对意见,以tensorflow为例,对外暴露的接口确实很重要,我在改动的接口的时候同时更新下版本号不就好了么,比如从1.12改到1.13,完全没有必要为了保持接口的稳定,限制新功能的加入,甚至要把新功能拆成另一个组件来引入,这个有必要么?

对于传统应用软件来说,确实没有上述如此拆分的必要,这主要是因为本文讨论的场景的前提是“接口层”的稳定性很重要,甚至比“模型层”的稳定性还要重要。这一般会出现在资源共用的场景中,比如云服务,比如硬件驱动等。最主要的区别在于,不出现资源共用的场景中,选择权在客户手上,比如tensorflow从1.X升级到2.X,连“模型层”都有调整,但是他没有强迫用户一定要去用2.X,1.X也依然可以下载可以安装使用。这个在云服务的场景中就不一样了,云服务的接口更新有些是非此即彼的更新(可以做到同时支持新旧版,维护成本比非云服务软件大),选择权不在客户,比如某某网站换新版了,此时客户的选择是被动的。

从版本管理的角度来看,传统软件以软件安装包的形式发布,其版本发布没有试图去修改历史,即发布了1.0.0版本,如果1.0.0版本有BUG,他是通过发布1.0.1版本来修复这个BUG的,此时就算有人依赖于1.0.0的BUG实现了自己的业务逻辑也是不要紧的,只要不切1.0.1就好。而云服务以服务的形式发布,其版本发布更倾向于去删除历史,即便在云服务版本更新的时候会有支持新旧两个版本的的策略,但这些都只是缓兵之计,最终的目的都是在某天把前面犯下错(BUG)抹掉。

所以,在云平台、云服务这类软件中“接口层”的稳定性往往要重于“模型层”的稳定性,导致了它可能出现本文上述的“胖客户”端演进方式,把易变的功能抽到“客户端”,客户端以软件安装包的形式发布,减少因“接口”变更要维护多套接口的成本。同时服务端的接口也越来越简单,最后变成特别稳定的基础服务。从服务端的角度看,这也是一个“职责单一化”的演进思路,服务端只要管好基本功能就好了,不要因为管得太多引入不稳定因素。

三、从SageMaker的镜像管理来看云服务的“胖前端”设计

啰嗦了一大堆,下面通过sageMaker来串一遍上述的分析。软件系统的设计受众多因素的影响,本节只是从“接口和版本”的角度来给出一些解读,仅供参考。

下图为sageMaker的模型导入界面,简单到有点简陋,作为一个AI Devops平台,竟然连常见的AI框架的镜像都不提供,只是简单地提供了一个镜像地址的输入框。

仔细分析发现,AWS是有提供AI引擎的预置镜像的,只是这个东西不是SageMaker,是一个比较独立的服务“Deep Learning Containers”(https://docs.aws.amazon.com/deep-learning-containers/latest/devguide/what-is-dlc.html)

从产品文档的组织结构上看,“Deep Learning Containers”归属于“Machine Learning”模块下,与AI平台“SageMaker”平级,如下图所示:

“Deep Learning Containers”对外开源了其镜像构建项目:https://github.com/aws/deep-learning-containers

在开源项目里面用文件同步了目前已发布的镜像的信息:https://github.com/aws/deep-learning-containers/blob/master/available_images.md

SageMaker和“Deep Learning Containers”出现交集的地方有两个,一个是在SageMaker的studio界面中,提供了相关镜像的下拉列表框供选择(没有明确说明是training镜像还是inference镜像,估计是training镜像):

另一个是在SageMaker自己官方的SDK中,可以通过“框架名”、“python版本”等信息检索镜像地址:https://github.com/aws/sagemaker-python-sdk/blob/99c56b273789ea0ed3a2d530c0ac1b66ce43bbb0/src/sagemaker/image_uris.py#L30

各个region提供的镜像的信息也记录在SageMaker SDK的代码库配置文件中(不是在SageMaker的数据库):https://github.com/aws/sagemaker-python-sdk/tree/99c56b273789ea0ed3a2d530c0ac1b66ce43bbb0/src/sagemaker/image_uri_config

从上面可以看到尽管预置镜像和“sageMaker”很相关,AWS还是把它拆解出来了作为一个独立的服务“Deep Learning Containers”,这个点在于保证各个服务都是最基本的,“职责单一”的,避免因为服务组合出现复杂的接口引入不稳定因素。在两个服务的交互上,SageMaker也颇有用心,坚决保持原生网页接口的“简陋”,把双方的交互放到了studio和SDK中。与直接放原生网页相比,studio和SDK更像前文中描述的以软件安装包交付的传统软件,其在版本变动的时候,是在创造历史,而不是试图修改历史。客户无法自己选择云服务的版本,但是可以自主选择SDK的版本,尽管SDK的版本和云服务的版本有一定的配套关系,但相比于把SDK的功能放到云服务内带来的云服务版本频繁变更风险,已经好很多了。

这个“厚重”的SDK和studio,也是上文中一直在说的“胖客户端”,这里要注意studio和网页UI不是等价的,区别在于studio可以通过容器化来做到用户自主选择版本,比如A客户在使用1.0.0版本的studio,同时B用户可以使用1.2.2版本的studio,而AWS的那个网页却不是那么容易做到“千人千面”的(不要抬杠,这里意会就好)。

如果你有仔细分析过SageMaker,会发现从整个产品的演进上,SageMaker的新功能越来越倾向于集成与studio和sdk中,如他的AI project和flow功能等,那个简陋到令人发指的网页界面,会演变成像数据库那样的基础服务,提供SageMaker基本元素的CRUD功能。

其实,最能体现SageMaker的“胖前端”设计思路的,是SageMaker的SDK,鉴于分析的“SDK”所需篇幅太大,本文最后临时选了“Deep Learning Containers”来串一下,后续有机会,可以好好扒扒SageMaker的SDK设计。

以安装包形式发布的软件,羡慕云服务可以集中刷新版本,修BUG于无形的优势;以服务发布的软件,却羡慕以安装包形式发布时,接口的调整负担不那么大的优势。不过羡慕归羡慕,API作为一个产品的门面,即便在以安装包形式发布的软件中,也是要保持版本间接口的稳定的。至于如何设计一个稳定的API接口,给大家推荐一本这方面的好书《API Design for C++》,虽然是以C++为例,设计思维是通用的,电子版自行网上搜索就好。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。