简介:我们主要负责 PC 端 1688.com 以及手机端阿里巴巴 APP,是目前国内最大的 B 类电商交易平台,主要面向 B2B 电商业务的场景,为中小企业提供零售、批发、分销以及加工定制等电商交易渠道。
前言
首先为大家简单介绍一下我们的业务场景,1688 隶属于阿里集团的国内贸易事业部(CBU),是阿里最早起家的业务,已有十几年的历史。我们主要负责 PC 端 1688.com 以及手机端阿里巴巴 APP,是目前国内最大的 B 类电商交易平台,主要面向 B2B 电商业务的场景,为中小企业提供零售、批发、分销以及加工定制等电商交易渠道。
我本人是在 1688 团队的无线服务端技术团队,团队主要是给 App 端提供业务支持,负责 1688 手机端 App 内部的各种场景构建,比如首页推荐、商品详情等等,也是典型的电商业务场景。
(电商业务场景)
Serverless 落地选型
(FaaS 在 1688 复杂业务场景下的提效实践)
1688 对 FaaS (Function as a Serverles) 技术的探索要追溯到 2015 年左右。当时整个阿里集团最大的业务目标就是 “ALL IN 无线”,移动互联网刚刚兴起,需要快速地把存在 PC 端的业务,无论是淘宝也好;还是 1688,需要能够迅速地把它移植到移动端上面,生成 App 来抢占移动端流量。
在这样一个大的业务背景下,1688 的解决方法是设立无线服务端。通过微服务体系调用 PC 端存量的业务接口,然后面向前台的移动端业务去进行一些轻量的业务逻辑编排和 UI 层的映射,最后通过移动网关,就可以快速为 APP 端提供拥有同样业务能力的服务接口。
移动互联网端的功能迭代非常之快,在这种模式下,我们很快遇到了问题:传统的微服务体系构建、发挥、部署和调试时间都非常漫长,而面向前台的业务改动又非常频繁,速度要求很快。技术能力和业务诉求之间产生的错位,push 着我们去寻找和探索更好的解决方案。
FaaS 能力落地的两个阶段
FaaS 在 CBU 内部的落地经历了两个大阶段。第一个阶段是 2015 年,部门内部自研了一套基于 JVM 的动态加载系统,来实现快速发布、快速上线、热部署等等,基本实现了 FaaS 的效果。第二阶段则是从去年开始,我们与阿里云 FC 团队进行共建,将整个 FaaS 能力的底座更换为阿里云的函数计算,以获得更好的弹性伸缩、容器隔离等能力。
(1) MBOX:基于 JVM 动态加载能力的 FaaS 系统
前文说到,在整个无线端业务快速迭代的背景下,我们需要一种能够实现快速发布变更服务端接口的能力。在 2015 年左右,Java 工程界主流的思路是充分利用 JVM 的动态加载特性:即在不重启 JVM 的前提下,通过一定的机制将外部的代码实时编译成 class 字节码,然后动态地载入正在运行的 JVM 实例中,从而实现热加载的效果。
于是,基于上述的思路,我们打造了一套基于 JVM 的动态服务加载系统 —— MBOX。在 MBOX 中,我们构建了一个通用的轻量服务容器,该容器可以接收来自外部的一段代码(可能是一个 Java 类或者是一个简单的 groovy 脚本),并对代码进行实时的编译,生成 class 字节码。之后容器本身还会对生成的字节码进行一定的安全加固操作(如消除死循环等),最后通过一个自定义 class loader 把它加载成线上正在运行的 JVM 中的一个 class,生成对象实例、注入中间件代理,便可以对外提供服务。
基于 MBOX,我们实现了在线编码、在线预览和秒级发布的能力,以现在的视角来看,它就是一个非常典型的 FaaS 服务平台,并具备以下特点:
一、相比于传统微服务,它是在线开发的模式,随写随用,所见及所得,研发效率非常高。
二、热加载更新机制。它可以做到秒级的发布,整个业务上线的迭代效率十分高。
三、对于使用平台的开发者来讲,它带来了 Serverless 的体验,因为所有的运维、机器部署等等,全部是由 MBOX 平台来承担,开发只需要关心其业务逻辑的实现即可。(在这里,我们扮演了类似现在云服务厂商的角色)
在长达五年的时间里,MBOX 系统承载了整个 1688 超过 10 万 QPS 的业务调用,巅峰时期有超过 1500 个线上函数,节省了大量人力资源,在整个无线业务扩张阶段立下了汗马功劳,也为我们的 Serverless 技术探索之路打开了一扇大门。
说完了优点,我们再谈一谈这套系统的缺点和风险:
首先,第一点是隔离性问题,因为是 MBOX 基于 JVM 的,JVM 本身没有办法提供有效的资源隔离机制(如 CPU、内存等),所以存在比较大的安全风险:即同一个业务容器中加载的多个服务之间会彼此产生影响。比如今天这个业务集群 A 上面有一个人写的代码发生了内存泄露,结果可能是整个集群的性能都被拖慢,上面所有的服务都会受到影响,这是一个非常严重的安全隐患。
第二点是代码的开发模式太轻,纯脚本式开发,只能写个代码片段,虽然开发起来很快很爽,但是没有工程结构,不能使用框架、不能使用任何的设计模式,这样导致可应用场景非常受限,代码本身的质量也很差。
第三点对于 MBOX 的维护方来说,资源的管理是一个非常头疼的问题,经常遇到整个集群水位飙升,但是又没有办法判断出来到底是哪一个服务在里面占用了资源的情况。而系统本身又无法很好的进行弹性伸缩,只能依赖人肉手动扩容,到了平台维护的后期,运维成本是非常高的。
到了 2019 年左右,MBOX 的一些问题已经比较凸显了,而这时业界在 Kubernetes 的影响之下,掀起了 Serverless 和云原生的技术浪潮,我们立刻开始了对应的技术调研,最终在 2020 年底与阿里云函数计算团队展开共建,希望能够打造一套真正面向云原生的 FaaS 平台。
(2) 阿里云 FC:基于 Serverless + Sidecar 的 FaaS 系统
阿里云函数计算(FC)在 Kubernetes 容器自动运维能力的基础上,打造出了一套高弹性、强隔离,且易于开放定制的 FaaS 基建,目前已经基本成为整个阿里集团内部 FaaS 能力的统一解决方案。
除了底层高度成熟和强大的弹性自动运维能力之外,FC 还提供了开放度非常高的 Runtime 设计,任何语言甚至是任何团队都可以定制自己的运行时框架,从而最大限度满足业务一线开发人员的诉求。
对于跨语言的老大难问题 —— 中间件调用上,FC 团队和中间件团队,结合微软最新开源的 DAPR 技术,实现了一套标准化的 Sidecar 能力,涵盖了 RPC、缓存、消息队列、配置中心等常见中间件,抹平多语言差异的同时进一步精简了用户的运行时容器,让函数的冷启动和弹性速度得以进一步提升。
最终在 FC 底层强大技术的支撑下,我们一同共建了面向集团内 Java 研发者通用的 Runtime 框架及研发运维配套设施,替换了原有的 JVM MBOX 系统,实现了 FaaS 能力的技术换代。
回顾 CBU 的 Serverless 演进之路,从最早的微服务架构,到自主研发的 JVM FaaS 系统,再到现在的 FC 函数计算,逐步探索出了最适合业务场景的技术方案,同时也算是在业界率先迈出了大规模落地 FaaS 的一步:作为集团第一批大规模落地 Serverless 理念的部门,我们的 FaaS 系统在部门内拥有超过 80% 的业务渗透率,使用时间更是达到了 5 年以上。
FaaS 落地的灵魂三问
在了解了 FaaS 的能力和实现之后,我们接下来讨论大家最关心的问题:如何在业务系统中落地 FaaS 能力?
以我们过往的实践经验,在实际业务中落地 FaaS 并非大部分人想象中那么 “丝滑”,势必会遇到一些问题,这里提炼了三个我们认为最核心的 “灵魂拷问”:
一是:存量业务的改造。对于存量的业务(尤其是我们这种已经持续运行了很多年的业务),有大量的历史包袱不能抛掉。这就面临一个问题,线上大量的传统 Serverful App,怎么去转换成 Serverless Function?直接进行改造风险是非常大的,是否有办法在保障现有业务稳定运行的前提下,以最小的成本获得 FaaS 带来的优势?
二是:FaaS 的碎片化问题。传统 Serverful 应用的构建思路是非常高内聚的,某个业务的核心能力基本都聚合在几个微服务应用中,数量相对较少。但是函数不同,它轻量化的特点会导致数量膨胀非常快,如果不加以设计和控制,那么很容易出现一个业务背后对应着几十个函数的碎片化情景出现。
三是:研发提效的问题。业界目前普遍认为 Serverless 能够大幅提升研发效率,但是真正落地之后会发现研发提效这件事情,并没有那么简单,只是将传统的 Serverful 应用换成 Serverless 函数能够带来的研发提效幅度是非常有限的。
存量的复杂业务如何与 FaaS 结合?
首先回答第一个问题,在存量复杂业务和 FaaS 能力结合这件事上,通过实践探索,我们总结出了两种比较落地的模式,分别是 BFF 模式和扩展点模式。
(1) BFF 模式
BFF 模式是现在 FaaS 落地比较常见的一种主流做法。我们可以把传统 Serverful App 里面的逻辑进行一些抽象,一般来说在我们可以根据变更的频繁度,将业务场景中的逻辑分为两层:一部分的代码逻辑比较轻,同时没有一些复杂的依赖,但是产品的需求可能大部分集中在这部分,称其为变动层。
另一部分的代码可能是业务的应用框架,中间件二方依赖,以及一些核心的重业务逻辑,相对来说改动不会太多,但是改造风险非常大,改造收益也可能不尽理想,称其为稳定层。
如果你的业务应用可以按照上述的思路拆分,那就非常适合采用 BFF 模式,把变动层抽象出来,放到 Serverless 函数中去,这样就实现了一个类似 BFF 层的效果,前台的消费方其实是直接通过函数再去消费 Serverful App 里面稳定层的 API。这样就可以构造一个业务的缓冲区,能够实现更快地发布 & 交付以及更少的运维。虽然有相当一部分的老代码包袱还是丢不掉的,但是可以集中 80% 的精力提效。
这种模式比较适用于前台类的业务场景,比如说传统应用 M-V-C 架构当中的 controller 层,就非常适合使用 FaaS 来替换。
(2) 扩展点模式
第二种模式是扩展点模式,扩展点模式比较适合于偏中后台的场景,或者说业务里面的一些中台系统,例如我们的商品中心,就用到了这个模式。
对于中后台类的应用,一般本身就十分复杂,且业务逻辑非常多,加上历史比较悠久,不适合进行大刀阔斧地改造。但是我们可以把复杂的业务逻辑层进行一定的抽象,面向未来设计一些关键的扩展点,同时提供扩展点的 FaaS 适配方案。
这样对于一些后续增量的业务逻辑,就可以使用 FaaS 的能力来提供,而对于存量的业务逻辑,则可以基本保持不变,只需要在代码结构上稍作适配,也可以成为标准的扩展点实现。
扩展点模式的另一个好处是可以让原来封闭式的架构变得更加开放化,采用这种模式后,即便是中台性质的应用,只要制定好扩展点的对接规范,任何的业务方都可以通过提供自定义的 FaaS 函数来实现自己想要的扩展能力。在 1688 的商品中台系统中,就通过这种扩展点模式实现了商品价格计算逻辑的业务开放化定制能力。
避免 FaaS 的碎片化问题
(1) 编程界面的权衡
早期我们在 MBOX 系统中定义函数的编程界面时,采用的是脚本式编程,用户的编程粒度就是一段代码,一个 Java 类。这种方式虽然写起来非常轻量,但是会导致函数数量非常多 (为了实现一个稍复杂的业务,可能需要写很多个脚本,同时由于没有工程结构,代码质量非常低下,也无法使用一些设计模式)。
因此在基于 FC 制定函数的编程界面时,我们基于上述经验设定了一个规则,那就是用户函数的运行粒度应该是一个 “Micro App”,而不是 “Single Function”;编程界面的粒度应该是一个 “Code Project”,而不是 “Single Script”。
基于这样的原则,对于开发者来说,一个函数实例更接近于一个微应用,整体保留了最精简的工程结构,即可以以较低的成本进行单一功能点的实现,同时也可以进行复杂逻辑的开发,引入各种二、三方库,不至于产生非常严重的函数数量膨胀和碎片化问题。
(2) 建设内部服务市场
尽管采用了 Micro App 式的函数粒度定义,在业务中使用到的函数数量仍然会比传统微服务应用多出好几个量级,为了解决这个问题,我们设计了【业务域 】-【函数组】- 【函数】- 【接口】四层纬度的函数分类定义,并在函数的工程模板里埋入了插件,在函数完成构建发布的时候自动采集上报这些分组分类信息,最终建设出面向内部研发人员的函数服务市场,让大家能够直观的看到当前所存在的函数分类,以及各个接口 API 的归属信息。
研发效率的瓶颈在哪里?
让开发者能够 “Only focus on business”,这是 Serverless 从诞生之初就标榜的核心理念,但是如果只是简单将运维等底层基础设置切换到 Serverless 的基建之上,引入 FaaS 等相关技术能力,其实对研发人员而言离真正意义上的 “Only focus on business” 还差很远。
在我们初期落地 Serverless 时,确实发现研发同学编写代码的效率好像高了很多,但是从整体的业务团队的业务需求交付角度来讲,研发效能没有发生质变的提升:大部分的需求研发流程仍然比较冗长,需求推进过程中的沟通成本、协作成本依然巨高不下。仔细审视我们会发现,研发效能的关键瓶颈很多时候可能并不在于 “研发” 本身,而在于代码之外。
那么 Serverless 能够为研发提效是否是一个伪命题呢?当然也不是,首先,Serverless 和 FaaS 确实可以大幅度降低运维和编码的成本,提升效率;其次,Serverless 技术的出现让服务端的技术门槛降低,使得一些非服务端专业的研发人员也能够有能力开发一些简单的业务逻辑,这使得需求的全栈式开发成为可能。
从整体需求交付的效率来考量,假设研发人员能够独立完成整个需求的所有开发工作,无需和他人进行联调沟通,那么效率势必是最大化的 — Serverless 为这种全新研发模式的落地和普及带来了可能性,或许才是 “Only Focus on Business” 真正的意义所在。
总之,Serverless 也并不是提效的银弹,事实上,没有任何一门技术是银弹,当我们期望研发效能提升的时候,更多的还要从全局的视角来进行审视,而不是将思路仅仅聚焦在“研发”阶段。
复杂业务场景的提效实践
最后我们以一个实际的业务场景为例子,给大家介绍一下 1688 在存量复杂业务场景下,是如何结合 FaaS 能力进行研发提效的。
这里先简单介绍一下 1688 的商品详情业务场景:商品详情就是商品面向买家的最终展示页面,承载了大量的商品信息。1688 的商品详情页和其他普通 C 类电商不一样的地方在于:面向 B 类的交易会存在多种交易渠道,例如现货批发 、分销铺货、加工定制等等,其中每个渠道的交易方式、价格、库存逻辑都不一样;除此之外 B 类电商对于消费品、工业品等不同行业的商品,在表达上也会存在巨大差异;不同渠道和行业定制,再叠加各类电商营销活动玩法,使得 1688 的商品详情页面业务复杂度非常之高。
原本的技术架构中涉及到了多个团队:
- 其中包括底层的商品基础团队 :对接集团中台商品的能力,沉淀领域模型的服务和一些核心的商品逻辑;
- 无线服务端团队 :将底层商品基础服务,面向客户端和前端封装成 — 专用的商品详情接口;
- 搭建投放团队 :负责为页面提供一定的搭建、投放横向能力支持,以及最终展现侧的 ios、安卓、前端三端。
- 除此之外,在面对一些业务定制类的需求 (如分销等) 时,还需要对应的业务团队参与。
这种模式下,一个需求最多会有 5、6 个团队在同时协作,沟通成本非常高;再加上服务端侧采用了比较重的微服务应用模式,研发和运维效率都比较低下。
前台业务逻辑收拢:BFF 模式
我们首先是在业务核心变化最多最快的前台业务逻辑层,以 BFF 模式引入了 FaaS 能力,并且将所有的 UI 相关逻辑都上行收拢到了 FaaS 函数中。这样做一方面提升了服务端对应逻辑的研发和部署效率,另一方面使得前端和客户端的组件只需处理最简单的展示逻辑,从而尽可能抹平多端技术差异,让一些跨端能力得以实现。
商品后端:扩展点模式支持定义
商品后端侧面对的主要问题是各种定制业务逻辑的接入成本过高,因此我们采用了 FaaS 扩展点模式来进行优化:将商品信息的核心逻辑 (如价格、库存) 抽象成一个个标准的扩展点,并对接函数网关,允许任意的业务方按照模板编写一个函数来定制其业务逻辑,从而实现封闭架构的开放化。
经过上述的技术架构改造后,我们可以看到整个需求研发的模式和链路发生了非常大的改变。在之前老的模式下,如果要定制一个业务的商品详情,需要从业务后端到客户端多个团队全部参与,非常多的链路都需要进行改动,成本是非常高的。
而在新的模式下,从核心业务逻辑的定制,到前台展现侧的业务逻辑实现,都可以通过编写简单的 FaaS 函数来实现,甚至完全可以仅由一位同学完成所有后台业务逻辑的变更。(如果前端和客户端的组件能够实现低代码 + 跨端开发,那么完全可以实现业务需求的全栈开发!)
最后我们来看一下整体业务场景完成改造后所带来的成效,在结合了 FaaS 的研发模式下,商品详情相关的需求发待时长降低了 80%、发布频次提升 300% +,需求的吞吐量也有提升;最关键的是,研发人员的投入减少了 50%,整个后端侧由原来的 2 个正式员工人力投入降低为:仅需半个正式员工 + 1 个外包员工支持,参与需求开发的相关团队和人员减少了许多,整个研发交付链路变得十分简洁明了。
总结与展望
最后还是站在当下的时间节点,以业务团队的视角,简单的对 Serverless 技术做一个总结和展望。
以我们过往业务场景落地的经验总结几个关键结论:
- Serverless 无疑是一轮新的技术革命,FaaS 等核心技术所带来的生产力提升已经在很多场景得到有力的证明。
- Serverless 的落地要结合实际的业务场景有的放矢,而不是说一杆捅到底。
- 任何技术都不是提效 “银弹”,研发效能的提升更多还要是站在团队的组织架构和人的角度去思考来做业务和技术的结合。
而对于 Serverless 和 FaaS 后续的发展,我也在此作出一些比较个人的看法,欢迎大家理性讨论:
- FaaS 将会持续进化:虽然目前 Serverless 的 FaaS 能力已经比较成熟,但是仍有很大进步空间。可以拥有更好的弹性能力以及冷启动速度。我们可以看到业界正在 WASM、eBPF 等新的方向上不断的探索,有理由相信接下来我们将在这个领域看到持续的突破。
- FaaS 不会完全替代传统微服务:传统微服务 (包括运行在 Kubernetes 上的)与 FaaS 这两种形态,至少在未来很长一段时间里仍然会共存,业务团队应该做好这种准备,因地制宜地在业务场景中结合两种技术,发挥它们的优势。
- 全栈和低代码 (或者说,低门槛)研发将会成为一种趋势:我们看到,Serverless 的兴起给研发提效带来了一种全新的思路,全栈式的业务需求开发能够大幅降低项目中的沟通和协作成本、提升需求的吞吐量。随着 Serverless 技术的进一步成熟和推广,这将可能改写现在研发团队的形态。
原文链接
本文为阿里云原创内容,未经允许不得转载。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。