当前位置: 首页 > 科技观察

一个serverless风格的微服务架构构建案例

时间:2023-03-12 22:16:48 科技观察

1.微服务架构的一次冒险2016年12月上旬,我作为DevOps顾问参与了客户的DevOps转型项目。该项目旨在提升部门在AWS(AmazonWebServices)云计算平台上的DevOps能力。自助应用系统基于RubyonRails框架开发。前端部分采用了AngularJS1.0,但没有采用前后端分离的设计,页面代码仍然通过ERB进行组合。移动端是用Cordova开发的。为了降低开发难度和工作量,移动端的应用内容实际上是将AngularJS生成的网页以响应式的方式嵌入到移动端。但是因为经常超时,APP体验不好。整个Rails应用部署在AWS上,通过网关与内部业务系统隔离。BOSS系统使用SOAP对外暴露服务,另外一个部门负责。因此,云端应用的业务就是呈现给用户一个友好的界面,通过数据转换与内部BOSS系统进行交互。系统架构如下图所示:应用程序的交互过程如下:浏览器或移动端通过域名(AWSRoute53托管)转至CDN(使用AWSCloudfront)。CDN根据请求的内容类别进行区分。静态文件(图片、JS、CSS样式等)会被传输到AWSS3进行存储。动态请求直接发送到负载均衡器(AWSElasticLoadBalancer)。负载均衡器会根据每个EC2计算实例的负载情况,将请求转发给不同实例上的RubyOnRails应用。每个应用程序都是典型的MVCWeb应用程序。EC2上的应用程序将部分数据存储在关系数据服务(AWSRDS,RelationalDatabaseService)中,部分存储在本地文件中。应用程序处理后,转化为SOAP请求,通过网关发送给BOSS系统处理。BOSS系统在处理完成后会返回相应的消息。根据业务需求,部分数据会使用AWSElastiCache的Redis服务进行缓存,以优化业务响应速度。2.团队痛点这个应用经历了多年的发展,前后更换了很多技术人员。但是没有人完全了解这个应用程序代码库。因此,我们总结了整个团队和产品的痛点:1.在组织架构上,运维团队成为了瓶颈,一个60人左右的开发团队只有4个Ops支持。除了日常事务,运维团队还为开发团队提供各种支持。很多资源的使用都局限于这个团队,进一步拖延了解决各种问题的进度。随着业务的增长,需要基础设施代码库提供各种能力。但是,Ops团队的任何更改都会导致所有开发团队停止进度以修复更新引起的各种问题。2、在应用架构方面,应用架构并没有达到前后端分离的效果,仍然需要同一个工程师来编写前后端代码。这样的技术栈对开发者的要求很高,但市场上却缺乏合适的RoR工程师,进一步增加了维护成本。三个月后,还是很难招到合适的工程师。多个团队在同一个代码库上工作,新旧功能之间存在各种依赖点。再加上Ruby的语言特性,代码中存在很多隐式依赖和类/方法覆盖,导致开发进度缓慢。我们共有4个团队负责代码库,另外3个团队负责新功能。1个团队需要同时修复错误和清理技术债务。3、在技术债方面,代码库中存在大量重复的Cucumber自动化测试,但缺乏正确的并行测试策略导致自动化测试随机失败。很难为持续集成服务器(Jenkins)创建本地从节点,导致更难查找故障原因。如果幸运的话,从提交代码到新版本至少需要45分钟。运气不好的话,两三天也完成不了一次成功的建设,这还真是要看人品建设。基础架构即代码构建在混合的遗留Ruby代码库上。该代码库用于封装一些命令行工具,如Packer和AWSCLI,包括一些CloudFormation转换功能。由于缺乏长期规划和编码标准,加上人员变动频繁,代码库难以维护。此外,基础设施代码库与应用程序代码库作为一个gem耦合,运营团队拥有唯一的维护权限。所以很多基础设施的问题,开发团队解决不了,也不愿意去解决。我曾参与维护Ruby堆栈中的许多遗留系统。在经历了这些Ruby项目后,我发现Ruby是一个开发起来很酷但维护起来很痛苦的技术栈。大多数维护更改是由于Ruby版本和Gem版本更新。另外,由于Ruby比较灵活,人们有自己的想法和使用习惯,所以代码库很难维护。虽然团队已经有了比较完善的持续交付流程,但是Ops能力的缺乏以及应用架构带来的局限性阻碍了整个产品的进步。因此,通过DevOps提升团队的Ops能力,缓解Ops资源短缺,弱化DevOps的矛盾势在必行。DevOps组织转型一般有两种方法:一种方法是提高Dev的Ops能力,另一种方法是降低Ops工作的门槛。在时间资源紧张的情况下,通过技术提升,降低Ops的门槛是短期内最赚钱的方法。3、微服务触发点:并购带来的业务功能的合并。我加入项目后,客户收购了另一家业务相关的公司。因此,原有系统不得不同时承载两种业务。正好有一个订单查询业务,需要当前团队完成这样一个需求:通过已有的订单查询功能,同时查询两个系统的业务订单。这个需求看似很简单,只需要在现有系统中增加一个数据源,然后将输入的订单号进行转换即可。然而,由于上述痛点,完成如此简单的功能,成本非常高。几乎70%的工作量与功能开发本身无关。对开发项目进行DevOps转型,就像给行驶中的汽车换轮子,稍不留神,所有团队都会停止工作。因此,我建议通过建立新的平行团队,同时进行新功能的开发和DevOps转型试点。这是功能拆分和新功能拆分的要求。恰好订单查询在原有系统中是一个相对独立且成熟的功能。以免影响原有功能开发的进度。我们决定采用微服务架构来完成这个功能。四、构建微服务架构的策略我们不想重蹈之前应用架构的覆辙,我们想要前后端分离。它允许相对较小的开发团队并行开发。接口之间的契约只要协商好,以后开发完成后就可以很好的集成。这让我想起了ChrisRichardson的三大微服务架构策略,即:停止挖坑、前后端分离、抽取微服务。停止挖坑的意思是:如果发现自己掉进坑里,立即停止。旧的整体对我们来说是一个焦油坑,所以我们停止在旧的代码库上工作。并为新的应用单元构建代码库。因此,我们将策略模式拆分如下:在我们的架构中,实现新的需求需要改变旧的应用程序。我们的思路是:新建业务页面,生成微服务合约。基于API契约构建新的微服务。将web前端部署到S3,使用S3的StaticWebHosting(静态web服务)进行发布。部署后端微服务上线,使用临时域名和CDN加载点进行测试。通过更新CDN,将原应用的流量导向新的微服务。删除旧的服务代码。我们本来想在原来的应用中增加一个API,来访问之前应用的逻辑。不过想想,这其实也是一种挖矿。在评估业务的复杂性之后。我们发现这个功能如果是新开发的,只需要2人2周(1人月),只占我们预估工作量的不到20%。所以我们放弃了开始遗留代码工作的想法。最后,可以直接通过微服务访问后台系统,不需要原来的应用程序。我们拆解微服务的部分非常简单。对于后端,只需要修改CDN覆盖原有的访问源(Origin)和route.rb中保存的原有功能接入点,即可完成微服务的整合。1、新建业务页面,生成微服务合约结合以上应用痛点和思路,我们在构建微服务技术时确定了以下几个方向:前端框架要有良好的响应式扩展。使用Swagger来描述API需要具有的行为。契约测试通过消费者驱动驱动微服务后端开发。前端代码库与后端代码库是分开的。前端代码框架应该对持续交付友好。所以我们选择了React作为前端技术栈,使用yarn来管理依赖和任务。另一个原因是我们可以为将来通过React-native构建新的应用程序做准备。此外,我们还引入了AWSSDK的nodejs版本。编写一些常见的AWS相关的构建、部署、配置等操作,并通过swagger描述后端API的行为。这样后台只需要满足这个API规范,很容易实现前后台的融合。2.将前端部分部署到S3由于AWSS3服务自带StaticWebHosting(静态页面服务)功能,这大大减少了我们搭建基础环境的时间。如果你还在考虑使用Nginx和Apache作为静态内容的Web服务器,那你还不够CloudNative。虽然AWSS3服务在过去有过失败,但SLA比我们为静态内容自制的EC2实例强得多。此外,还有以下优点:有了独立的URL,很容易做大量的301、302重定向和重写操作。与CDN(CloudFront)很好地集成。很容易与持续集成工具集成。***优点:比EC2便宜。3.根据API契约构建新的微服务在构建微服务之初,我们有两个选择:使用Sinatra(一个用于构建API的Rubygem)构建一个微服务,它可以重用原始Rails代码库中的许多组件。也就是说,你只需要复制一些代码,放到单独的代码库中,就可以完成功能。但是也会面临之前Ruby技术栈带来的各种问题。使用SpringBoot构建微服务,Java作为一门成熟的工程语言仍然是最佳选择,社区和实践都非常成熟。许多在后台进行SOAP处理的JAR包都可以重复使用。另一方面解决了Ruby技术栈带来的问题。但是,这两种方案都有一个共同的问题:都需要通过用ruby语言编写的基础设施工具来搭建一套运行微服务的基础设施。而这个基础设施的建设估计至少需要1个月,这是在帮助运维团队的情况下的乐观估计。因此,有必要想办法减少环境搭建和运维团队的拥堵,避免传统的EC2方式搭建应用。这,只有Lambda能做到!基于以上考虑,我们选择了AmazonAPIGateway+Lambda的组合。并且AmazonAPIGateway+Lambda还有额外的好处:支持使用Swagger规范配置APIGateway。也就是说,只要导入前端Swagger规范就可以生成APIGateway。可以用数据构建MockAPI,从而在很大程度上实现消费者驱动的合约开发。借助AmazonAPIGateway的Stage功能,我们不需要搭建QA环境、UAT环境和Staging环境。只需要指定不同的Stage即可完成相应的切换。Lambda的发布时间很短,反馈很快。最初使用CloudFormation构建的API基础设施至少需要15分钟,但Lambda仅需几秒钟即可生效。Lambda的编写非常方便,可以在线完成。虽然网上的IDE不是很好用,但是写几行代码真的是不可能的。Lambda在不考虑负载平衡的情况下根据请求自动扩展自身。虽然有这么多优点,但也不能忽视一个关键问题:AWSLambda可能不适合你的应用场景!很多业务对同步和强一致性的需求无法满足。因此AWSLambda更适合可以异步处理的业务场景。另外AWSLambda不支持AI、大数据等消耗大量存储空间和CPU的场景。(PS:AWS已经有专门的AI和大数据服务了,不用自己找麻烦了)对于我们的应用场景来说,上面的RubyOnRails应用的主要功能(至少60%)其实只是一个数据转换适配器:对前端输入的数据进行处理,转换成相应的SOAP调用。所以,对于这么简单的场景,AmazonAPIGateway+Lambda完全可以满足需求!4.部署后端微服务选择AmazonAPIGateway+Lambda后,后端微服务的部署看起来很简单:更新Lambda函数。更新API规范并要求API绑定与处理请求的Lambda函数相对应。然而,这不是一件容易的事。我们会在下一篇文章《Serverless 风格微服务的持续交付》详细介绍我们踩过的坑。5.将原应用的请求指向新的微服务。这个时候,在CDN上为新的微服务配置APIGateway作为新的源(Origin),覆盖原来写在route.rb和nginx.conf中的API访问规则就可以了。CDN会对访问请求进行拦截,从而在请求被nginx处理之前,将请求转发给API网关。当然,如果你要做灰度发布,就不能用上面的方式了。CloudFront和ELB负载均衡不具备加权转发功能。因此需要根据访问权重配置nginx在上游使用API??Gateway作为Server。6.删除旧的服务代码不要留下无用的遗留代码!不要留下无用的遗留代码!不要留下无用的遗留代码!重要的和最容易被忽视的事情要说三遍。必须根除杂草,尽管我们可以保持代码不变。但是清理未使用的遗留代码和自动化测试可以为其他团队节省很多不必要的工作。七、最终架构经过6个人两个月的开发(原计划8个人3个月),我们的serverless微服务终于落地了。当然,中间60%的时间都在探索一个新的技术栈。熟练的话估计4个人1个月可以完成。***的架构如下图所示:上图中,请求还是先通过域名发送到CDN(CloudFront),然后:CDN根据请求点将页面请求转发到S3,并将API请求转发给APIGateway。通过蓝绿部署将前端内容放在不同的S3Bucket中,只需更改CDN设置即可完成相应内容的部署。蓝绿Bucket虽然对于部署来说显得有些多余,但它是为生产环境集成上线测试准备的。这使环境不一致尽可能少。APIGateway有自己的VPC,很好的实现了网络级别的隔离。通过API网关转发的API请求分为三类,每一类都可以根据请求状态进行自扩展。鉴权类:第一次访问会请求ElastCache(Redis)。如果Token无效或不存在,则重新进行用户认证过程。数据请求类:数据请求类会通过Lambda访问其他团队开发的Java微服务。这种微服务是后台系统的唯一接入点。操作审计类:请求会被记录在DynamoDB(时间序列数据库)中,跟踪异步请求的各种日志。APIGateway自带一些缓存,可以加快API的访问速度。消息返回后,三种不同类型的请求结果通过API网关统一返回给客户端。5.Serverless风格微服务架构的优势由于没有时间进行EC2设施初始化,我们至少减少了一个月的工作量,即:初始化网络配置的时间。是时候构建EC2配置了。是时候构建反向代理和前端静态内容服务器了。是时候构建后端API应用程序基础架构了。是时候构建负载均衡器了。是时候使用Ruby将上述内容转变为基础架构即代码了。如果想把APIGateway算作基础设施初始化的时间。第一次初始化APIGateway用了一天时间,以后结合持续交付流程修改APIGateway只需要几分钟。Serverless风格的微服务大大降低了基础设施配置和运维的门槛。此外,对于团队来说,AmazonAPIGateway+Lambda的微服务还带来了其他好处:开发效率高,原来至少需要45分钟的开发反馈周期缩短到不到5分钟。无关代码量少,需要维护的代码量也少。除了关注业务本身。上游与API网关的集成,下游与后端服务的集成,都是极简代码。应用程序维护成本低。代码只有几十行,而且都是函数式的,很容易测试。避免了代码库内增加的复杂性。此外,我们还比较了Java与NodeJs。同样的功能开发下,NodeJS的开发效率更高,因为Java需要将请求的json转化为对象,也需要将返回的json转化为对象,不像nodejs是直接处理json。另外,Java需要引入一些其他的JAR包作为依赖。在AWS场景下开发相同功能的微服务时,nodejs的开发效率是java的4倍。6、虽然serverless风格的微服务大大降低了开发工作量和基础设施开发维护工作量。但它也带来了新的挑战:大量功能的管理。SIT、UAT环境的管理。持续交付流水线的配置。面对基础架构集成带来的测试。这让我们重新思考Serverless架构下的微服务如何更好的进行持续交付。【本文为专栏作家《ThoughtWorks》原创稿件,微信公众号:Thinkworker,转载请联系原作者】点此查看该作者更多好文