Serverless 风格微服务的持续交付(上):架构案例

GitChat 做者:顾宇
原文:Serverless 风格微服务的持续交付(上):架构案例
关注微信公众号:「GitChat技术杂谈」 一本正经的讲技术html

无服务器架构 (Serverless Architectures) 简介

Serverless 架构最先能够追溯到 Ken Fromm 发表的文章《Why The Future Of Software And Apps Is Serverless》。在这篇文章里, Ken Fromm 描述了将来云计算基础设施成熟的条件下应用程序是不须要服务器端的。在无武器场景下构建应用程序的时候。开发人员和运维人员无需担忧服务器如何安装配置,如何设置网络和负载均衡,无需监控状态,甚至再也不会出现服务器相关的工做内容。这样可让本来建设机房的时间成本和货币成本从按年计算缩短至按秒计算。前端

在 Martin Fowler 的博客《Serverless Architectures》中,他将无服务器架构分为两种:java

第一种无服务器架构被称为被称为 __BaaS(Backend as a Service,后端应用即服务)__。即应用的架构是由一大堆第三方 API 来组织的。一切状态和逻辑都由这些服务提供方来管理。随着移动应用和单页 Web 应用这样的富客户端(Rich Client)应用的普及,先后端的通讯渐渐以 API 调用为主,而所需的服务再也不由 服务端应用开发工程师和运维工程师来维护,只须要调用提供服务的第三方 API 就能够完成相应的功能。例如云上的数据库服务和用户认证服务。node

另外一种无服务器架构被称为 __FaaS(Function as a Service,函数即服务)__。这一架构的兴起源于 AWS Lambda 的发展。 AWS Lambda 是一种无状态的代码运行时服务,这项服务提供最小的代码运行资源。你可使用 Java,Node.js,Python 和 C# 编写程序处理 AWS 各类服务的事件。无需初始化一台服务器,安装操做系统并配置程序运行环境。因为运行资源不多,完成的计算有限,使得这种应用没法保存状态,所以这类程序以函数的方式存在。nginx

本文所介绍的 Serverless 架构主要是以 AWS Lambda 以及 Amazon API Gateway 架构的应用,它同时也具有 BaaS 的特征。git

AWS Lambda 的编程模型

AWS Lambda 运行在一个假想的虚拟容器里,但你没法经过 API 配置这个容器。此外,这个虚拟的容器有一些资源限制,主要限制以下:数据库

  1. 5 分钟(300 秒)的程序运行时间。
  2. 512 MB 的文件系统空间。(在 /tmp 目录下)
  3. 最大1536 MB 的内存。(最小 128 MB,以 64 MB 做为增量)
  4. 最多 1024 个文件描述符。
  5. 最大 1024 个内部线程。

AWS Lambda 的编程模型以下所示:编程

enter image description here

Lambda 的执行流程:json

  1. 当事件触发 Lambda 执行的时候,Lambda 会将事件所携带的信息经过上下文对象(Context Object)传给处理函数(Handler)。此外,Lambda 还能够读取预先设置的环境变量。
  2. 执行处理函数,并将日志经过 CloudWatch 记录下来。
  3. 执行完毕后经过事件返回执行结果,或者抛出异常。
  4. 执行结果和对应的异常能够绑定其它资源继续处理。

当事件请求大批量发生的时候。Lambda 会为每个事件单独执行一次 。这意味着每个请求之间的执行期间,内容是不能共享的。(经本人亲测,内存中存储的是能够共享的,但内容保留的有效时间和状态没法保证。)后端

Amazon API Gateway + AWS Lambda 的微服务架构

根据 Martin Fowler 对微服务的描述性定义,咱们能够认为微服务从技术层面包含如下特征:

  1. 每一个服务运行在本身的进程中。
  2. 服务间通讯采用轻量级通讯机制(一般用HTTP资源API)。
  3. 这些服务围绕业务能力构建而且可经过全自动部署机制独立部署。
  4. 这些服务共用一个最小型的集中式的管理。
  5. 服务可用不一样的语言开发,使用不一样的数据存储技术。

在 AWS 现有的服务状况下,AWS Lambda 知足了上面的第 一、三、5 点,这只是一个微服务的处理单元,非管理单元。而 2 和 4 则须要另外的服务做为管理单元共同构成微服务,这个任务通常交由 API 网关实现。

Amazon API Gateway 是一种彻底托管的 API 网关服务,能够帮助开发者轻松建立、发布、维护、监控和保护任意规模的 API。它集成了不少 API 网关的功能,诸如缓存、用户认证等功能。而且支持经过 HAML 和 Swagger 配置,这样就能够用代码管理系统配置 API 了。

Amazon API Gateway 能够根据不一样的 Restful API 访问点将请求的数据传递给不一样的资源进行处理。通常的 AWS API 架构以下所示:

enter image description here

  1. 当请求经过域名访问到应用的时候,应用会将 HTTP 请求转发给 CDN (CloudFornt)。
  2. CloudFront 会根据转发规则把对应的 API 请求转发到 API Gateway 上。
  3. API Gateway 会根据请求的访问点和内容交给对应的 AWS Lambda 或者 EC2 服务处理,也能够发送给其它可访问的服务。
  4. 处理完成后将返回请求结果给客户端。在返回的时候,API Gateway 也能够经过 Lambda 对返回内容进行处理。

相较于传统的微服务架构,经过 API Gateway 和 Lambda 的这种集成方式能够获得更轻量级的微服务。团队只须要规划好 API 访问并完成函数的开发,就能够快速的构建出一个最简单的微服务,使得微服务基础设施的搭建时间从几周缩短为几个小时。此外,大大提高了微服务架构的开发效率和稳定性。

一次微服务架构的奇遇

2016年12月初,当时我正在以一名 DevOps 咨询师的身份参与悉尼某一移动电话运营商的 Digital (电子渠道)部门的 DevOps 转型项目。这个项目是提高该部门在 AWS (Amazon Web Services)云计算平台上的 DevOps 能力。

Digital 部门负责该电信运营商全部的互联网和移动设备应用开发。这些应用主要是用来为用户提供诸如 SIM 卡激活,话费查询,话费充值,优惠套餐订购等自助服务(Self service),从而下降营业厅和人工话务客服的成本。

自助服务的应用系统基于 Ruby on Rails 框架开发,前端部分采用 AngularJS 1.0,可是没有采用先后端分离的设计,页面代码仍然是经过 ERB 组合而成。yi移动端则采用 Cordova 开发。为了下降开发难度和工做量, 移动端的应用内容其实是把 AngularJS 所生成的 Web 页面经过响应式样式的方式嵌入到移动端。但由于常常超时,因此这款 APP 体验并很差。

整套 Rails 应用部署在 AWS 上,而且经过网关和内部业务 BOSS (Business Operating Support System) 系统隔离。BOSS 系统采用 SOAP 对外暴露服务,并由另一个部门负责。所以,云上的应用所作的业务是给用户展示一个使用友好的界面,并经过数据的转化和内部 BOSS 系统进行交互。系统架构以下图所示:

enter image description here

应用的交互流程以下

  1. 浏览器或者移动端经过域名(由 AWS Route 53托管)转向 CDN(采用 AWS Cloudfront)。
  2. CDN 根据请求的内容类别进行区分,静态文件(图片,JS,CSS 样式等),会转向 AWS S3 存储。动态请求会直接发给负载均衡器 (AWS Elastic Load Balancer)。
  3. 负载均衡器会根据各 EC2 计算实例的负载状态将请求转发到不一样的实例上的 Ruby On Rails 应用上。每个应用都是一个典型的 MVC Web 应用。
  4. EC2 上的应用会将一部分数据存储在关系型数据服务(AWS RDS,Relational Database ServiceS)上,一部分存储在本地文件里。
  5. 通过应用的处理,转换成 SOAP 请求经过 网关发送给 BOSS 系统处理。BOSS 系统处理完成后会返回对应的消息。
  6. 根据业务的须要,一部分数据会采用 AWS ElasiCache 的 Redis 服务做为缓存以优化业务响应速度。

团队痛点

这个应用经历了多年的开发,先后已经更换过不少技术人员。可是没有人对这个应用代码库有完整的的认识。所以,咱们对整个团队和产品进行了一次痛点总结:

组织结构方面

  1. 运维团队成为瓶颈,60 我的左右的开发团队只有 4 名 Ops 支持。运维团队除了平常的事务之外,还要给开发团队提供各类支持。不少资源的使用权限被限制在这个团队里,就致使各类问题的解决进度进一步拖延。
  2. 随着业务的增加,须要基础设施代码库提供各类各样的能力。然而 Ops 团队的任何更改都会致使全部的开发团队停下手头的进度去修复更新所带来的各类问题。

应用架构方面

  1. 应用架构并无达到先后端分离的效果,仍然须要同一个工程师编写先后端代码。这样的技术栈对于对于开发人员的要求很高,然而市场上缺少合适的 RoR 工程师,致使维护成本进一步上升。通过了三个月,仍然很难招聘到合适的工程师。
  2. 多个团队在一个代码库上工做,新旧功能之间存在各类依赖点。加上 Ruby 的语言特性,使得代码中存在不少隐含的依赖点和类/方法覆盖,致使了开发进度缓慢。咱们一共有 4 个团队在一个代码库上工做,3个团队在开发新的功能。1 个团队须要修复 Bug 和清理技术债,这一切都要同时进行。

技术债方面

  1. 代码库中有大量的重复 cucumber 自动化测试,可是缺少正确的并行测试策略,致使自动化测试会随机失败,持续集成服务器 (Jenkins)的 slave 节点本地难以建立,致使失败缘由更加难以查找。若是走运的话,从提交代码到新的版本发布至少须要 45 分钟。若是不走运的话,两三天都没法完成一次成功的构建,真是依靠人品构建。
  2. 基础设施即代码(Infrastructure As Code)创建在一个混合的遗留的 Ruby 代码库上。这个代码库用来封装一些相似于 Packer 和 AWS CLI 这样的命令行工具,包含一些 CloudFormation 的转化能力。因为缺少长期的规划和编码规范,加之人员变更十分频繁,使得代码库难以维护。
  3. 此外,基础设施代码库做为一个 gem 和应用程序代码库耦合在一块儿,运维团队有惟一的维护权限。所以不少基础设施上的问题开发团队没法解决,也不肯解决。

我参与过不少 Ruby 技术栈遗留系统的维护。在经历了这些 Ruby 项目以后,我发现 Ruby 是一个开发起来很爽可是维护起来很痛苦的技术栈。大部分的维护更改是因为 Ruby 的版本 和 Gem 的版本更新致使的。此外,因为 Ruby 比较灵活,人们都有本身的想法和使用习惯,所以代码库很难维护。

虽然团队已经有比较好的持续交付流程,可是 Ops 能力缺少和应用架构带来的局限阻碍了整个产品的前进。所以,当务之急是可以经过 DevOps 提高团队的 Ops 能力,缓解 Ops 资源不足,削弱 DevOps 矛盾。

DevOps 组织转型中通常有两种方法:一种方法是提高 Dev 的 Ops 能力,另外一种方法是下降 Ops 工做门槛。在时间资源很紧张的状况下,经过技术的改进,下降 Ops 的门槛是短时间内收益最大的方法。

微服务触发点:并购带来的业务功能合并

在我加入这个项目的时候,客户收购了一个本地的宽带/固定电话运营商。所以原有的系统须要须要承载固话和宽带的新业务。恰巧有个订单查询的业务须要让当前的团队完整这样一个需求:经过现有的订单查询功能能够同时查询移动和固网宽带订单。

这要求在起因的订单查询功能上新增添一些选项和内容,能够同时查到移动和固网宽带的订单。经过上述痛点可知,这在当时完成这样一个任务的代价是十分昂贵的。

在开发的项目上进行 DevOps 转型就像在行进的汽车上换车轮,一不留心就会让全部团队中止工做。所以我建议经过设立并行的新团队来同时完成新功能的开发和 DevOps 转型的试点。

这是一个功能拆分和新功能拆分需求,恰好订单查询是原系统中一个比较独立和成熟的功能。为了不影响原有各功能开发的进度。咱们决定采用微服务架构来完成这个功能。

构建微服务的架构的策略

咱们并不想重蹈以前应用架构的覆辙,咱们要作到先后端分离。使得比较小的开发团队能够并行开发,只要协商好了 接口之间的契约(Contract),将来开发完成以后会很好集成。

这让我想起了 Chris Richardson 提出了三种微服务架构策略,分别是:中止挖坑先后端分离提取微服务

中止挖坑的意思是说:若是发现本身掉坑里,立刻中止。

原先的单体应用对咱们来讲就是一个焦油坑,所以咱们要中止在原来的代码库上继续工做。而且为新应用单首创建一个代码库。因此,咱们拆分策略模式以下所示:

enter image description here

在咱们的架构里,实现新的需求就要变更老的应用。咱们的想法是:

  1. 构建出新的业务页面,生成微服务契约。
  2. 根据 API 契约构建出新的微服务。
  3. 部署 Web 前端到 S3 上,采用 S3 的 Static Web Hosting (静态 Web 服务) 发布。
  4. 部署后端微服务上线,并采用临时的域名和 CDN 加载点进行测试。
  5. 经过更新 CDN 把原应用的流量导向新的微服务。
  6. 删除旧的服务代码。

咱们本来要在原有的应用上增长一个 API 用来访问之前应用的逻辑。但想一想这实际上也是一种挖坑。在评估了业务的复杂性以后。咱们发现这个功能若是全新开发只须要 2人2周(一我的月)的时间,这仅仅占咱们预估工做量的20%不到。所以咱们放弃了对遗留代码动工的念头。最终经过微服务直接访问后台系统,而不须要经过原有的应用。

在咱们拆微服务的部分十分简单。对于后端来讲说只须要修改 CDN 覆盖原先的访问源(Origin)以及保存在 route.rb 里的原功能访问点,就能够完成微服务的集成。

构建出新的业务页面,生成微服务契约

结合上面的应用痛点和思路,在构建微服务的技术选型时咱们肯定了如下方向:

  1. 前端框架要具有很好的 Responsive 扩展。
  2. 采用 Swagger 来描述 API 须要具有的行为。
  3. 经过消费者驱动进行契约测试驱动微服务后端开发。
  4. 前端代码库和后端代码库分开。
  5. 前端代码框架要对持续交付友好。

所以咱们选择了 React 做为前端技术栈而且用 yarn 管理依赖和任务。另一个缘由是咱们可以经过 React-native 为将来构建新的应用作好准备。此外,咱们引入了 AWS SDK 的 nodejs 版本。用编写一些常见的诸如构建、部署、配置等 AWS 相关的操做。而且经过 swagger 描述后端 API 的行为。这样,后端只须要知足这个 API 规范,就很容易作先后端集成。

部署前端部分到 S3 上

因为 AWS S3 服务自带 Static Web Hosting (静态页面服务) 功能,这就大大减小了咱们构建基础环境所花费的时间。若是你还想着用 Nginx 和 Apache 做为静态内容的 Web 服务器,那么你还不够 CloudNative。

虽然 AWS S3 服务曾经发生过故障,但 SLA 也比咱们本身构建的 EC2 实例处理静态内容要强得多。此外还有如下优势:

  1. 拥有独立的 URL,很容易作不少 301 和 302 的重定向和改写操做。
  2. 和 CDN (CloudFront)集成很好。
  3. 很容易和持续集成工具集成。
  4. 最大的优势:比 EC2 便宜

根据 API 契约构建出新的微服务

在构建微服务的最初,咱们当时有两个选择:

  1. 采用 Sinatra (一个用来构建 API 的 Ruby gem) 构建一个微服务 ,这样能够复用原先 Rails 代码库的不少组件。换句话说,只须要 copy 一些代码,放到一个单独的代码库里,就能够完成功能。但也一样会面临以前 Ruby 技术栈带来的种种问题。
  2. 采用 Spring Boot 构建一个微服务,Java 做为成熟工程语言目前仍是最好的选择,社区和实践都很是成熟。能够复用后台不少用来作 SOAP 处理的 JAR 包。另外一方面是解决了 Ruby 技术栈带来的问题。

然而,这两个方案的都有一个共同的问题:须要经过 ruby 语言编写的基础设施工具构建一套运行微服务的基础设施。而这个基础设施的搭建,前先后后估计得须要至少 1个月,这仍是在运维团队有人帮助的状况下的乐观估计。

因此,要找到一种下降环境构建和运维团队阻塞的方式避开传统的 EC2 搭建应用的方式。

这,只有 Lambda 能够作到!

基于上面的种种考量,咱们选择了 Amazon API Gateway + Lambda 的组合。而 Amazon API Gateway + Lambda 还有额外好处:

  1. 支持用 Swagger 规范配置 API Gateway。也就是说,你只要导入前端的 Swagger 规范,就能够生成 API Gateway。
  2. 能够用数据构建 Mock API,这样就能够很大程度上实现消费者驱动契约开发。
  3. 经过 Amazon API Gateway 的 Stage 功能,咱们无需构建 QA 环境,UAT 环境和 Staging 环境。只须要指定不一样的 Stage,就能够完成对应的切换。
  4. Lambda 的发布生效时间很短,反馈很快。原先用 CloudFormation 构建的 API 基础设施须要至少 15 分钟,而 Lambda 的生效只须要短短几秒钟。
  5. Lambda 的编写很方便,能够采用在线的方式。虽然在线 IDE 并不很好用,可是真的也写不了几行代码。
  6. Lambda 自动根据请求自扩展,无需考虑负载均衡。

虽然有这么多优势,但不能忽略了关键性的问题:AWS Lambda 不必定适合你的应用场景!

根据上文对 AWS Lambda 的介绍,支持 AWS Lambda 运行的资源和时间颇有限。所以不少对同步和强一致性的业务需求是没法知足的。因此,AWS Lambda 更适合可以异步处理的业务场景。此外,AWS Lambda 对消耗存储空间和 CPU 不少的场景支持不是很好,例如 AI 和 大数据。(PS: AWS 已经有专门的 AI 和大数据服务了,因此不须要和本身过不去)

对于咱们的应用场景而言,上文中的 Ruby On Rails 应用中的主要功能(至少60% 以上)实际上只是一个数据转换适配器:把前端输入的数据进行加工,转换成对应的 SOAP 调用。

所以,对于这样一个简单的场景而言,Amazon API Gateway + Lambda 彻底知足需求!

部署后端微服务

选择了Amazon API Gateway + Lambda 后,后端的微服务部署看起来很简单:

  1. 更新 Lambda 函数。
  2. 更新 API 规范,并要求 API 绑定对应 Lambda 函数处理请求。

可是,这却不是很容易的一件事。咱们将在《Serverless 风格微服务的持续交付(中):持续交付的挑战》中对这方面踩过的坑详细介绍。

把原应用的请求导向新的微服务

这时候在 CDN 上给新的微服务配置 API Gateway 做为一个新的源(Origin),覆盖原先写在 route.rb 和 nginx.conf 里的 API 访问规则就能够了。CDN 会拦截访问请求,使得请求在 nginx 处理以前就会把对应的请求转发到 API Gateway。

固然,若是你想作灰度发布的话,就不能按上面这种方式搞了。CloudFront 和 ELB 负载均衡 并不具有带权转发功能。所以你须要经过 nginx 配置,按访问权重把 API Gateway 做为一个 upstream 里的一个 Server 就能够。

删除旧的服务代码

不要留着无用的遗留代码!

不要留着无用的遗留代码!

不要留着无用的遗留代码!

重要且最容易被忽略的事情要说三遍。斩草要除根,虽然咱们能够保持代码不动。可是清理再也不使用的遗留代码和自动化测试能够为其它团队减小不少没必要要的工做量。

最终的架构

通过6我的两个月的开发(原计划8我的3个月),咱们的 Serverless 微服务最终落地了。固然这中间有 60% 的时间是在探索全新的技术栈。若是熟练的话,估计 4 我的一个月就能够完成工做。

最后的架构以下图所示:

enter image description here

在上图中,请求仍然是先到 CDN (CloudFront),而后:

  1. CDN 根据请求点的不一样,把页面请求转发至 S3 ,把 API 请求转发到 API Gateway。
  2. 前端的内容经过蓝绿部署被放到了不一样的 S3 Bucket 里面,只须要改变 CDN 设置就能够完成对应内容的部署。虽然对于部署来讲蓝绿 Bucket 乍看有一点多余,但这是为了可以在生产环境下作集成在线测试准备的。这样可使环境不一致尽量少。
  3. API Gateway 有本身做用的 VPC,很好的实现了网络级别的隔离。
  4. 经过 API Gateway 转发的 API 请求分红了三类,每一类均可以根据请求情况自扩展:

    • 身份验证类:第一次访问会请求 ElastCache(Redis),若是 Token 失效或者不存在,则从新走一遍用户验证流程。
    • 数据请求类:数据请求类会经过 Lambda 访问由其余团队开发的 Java 微服务,这类微服务是后台系统惟一的访问点。
    • 操做审计类:请求会记录到 DynamoDB (一种时间序列数据库)中,用来跟踪异步请求的各类日志。
  5. API Gateway 本身有一些缓存,能够加速 API 的访问。
  6. 消息返回后,再有三类不一样的请求的结果统一经过 API Gateway 返回给客户端。

Serverless 风格微服务架构的优势

因为没有 EC2 设施初始化的时间,咱们减小了至少一个月的工做量,分别是:

  1. 初始化网络配置的时间。
  2. 构建 EC2 配置的时间。
  3. 构建反向代理和前端静态内容服务器的时间。
  4. 构建后端 API 应用基础设施的时间。
  5. 构建负载均衡的时间。
  6. 把上述内容用 Ruby 进行基础设施即代码化的时间。

若是要把 API Gateway 算做是基础设施初始化的时间来看。第一次初始化 API Gateway 用了一天,之后 API Gateway 结合持续交付流程每次修改仅仅须要几分钟。

不管怎么说,Serverless 大大下降了基础设施配置和运维门槛。

此外,对于团队来讲,Amazon API Gateway + Lambda 的微服务还带来其它好处:

  1. 开发效率高,原先至少 45 分钟的开发反馈周期缩短为 5 分钟之内。
  2. 无关的代码量少,须要维护的代码量少。除了专一业务自己。上游和 API Gateway 的集成以及下游和后端服务的集成代码量不多。
  3. 应用维护成本低。代码仅仅几十行,且都为函数式,很容易测试。避免了代码库内部复杂性的增长。

此外,咱们作了 Java 和 NodeJs 比较。在开发一样的功能下,NodeJS 的开发效率更高,缘由是 Java 要把请求的 json 转化为对象,也要把返回的 json 转化为对象,而不像 nodejs 直接处理 json。此外, Java 须要引入一些其它 JAR 包做为依赖。在 AWS 场景下开发一样一个函数式微服务,nodejs 有 4 倍于 java 的开发效率提高。

最后

Serverless 风格的微服务虽然大大减小了开发工做量以及基础设施的开发维护工做量。但也带来了新的挑战:

  1. 大量函数的管理。
  2. SIT,UAT 环境的管理。
  3. 持续交付流水线的配置。
  4. 面对基础设施集成带来的测试。

这让咱们从新思考了 Serverless 架构的微服务如何更好的进行持续交付。

敬请期待下一篇《Serverless 风格微服务的持续交付(中):持续交付的挑战 》


实录:《顾宇:构建Serverless 风格微服务实战解析(上)》


更多精彩内容请关注:

这里写图片描述

相关文章
相关标签/搜索