1. 云原生架构的演进历程
2020 年,我们的技术架构比较薄弱,存在较多问题。面对这些问题,团队开始了架构演进,存在的问题主要是以下多个方面。
第一,运维能力和可观测性比较差,因为公司大部分都是业务研发人员,专业的 DBA 和运维都没有。
第二,在业务刚刚开始起步时,业务迭代速度非常快,每天都会有很频繁的发布,线上经常因为发布导致一些稳定性的问题,作为平台型公司连接 B 端和 C 端两端,对线上可用性要求非常高,但早期的架构设计也不太合理,所以很多时候应用是无法支持高可用的。
第三,因为线上的资源部署不太合理,比较浪费资源,资源成本较高。
第四,部署模式比较粗放,日志、告警都没有,所以出了问题之后排查非常困难,研发人员通常需要很长时间才能把这些问题弄清楚,响应时间比较长,相信大部分初创型公司在业务早期发展的阶段都存在这种问题。
那我们如何去应对这些挑战呢?答案就是使用云原生架构来重塑架构体系,不断地对架构进行演进,选择合适的技术栈,配合使用云的基础设施,来构建业务平台,让系统达到好的水准。
从下图中可以看到,青团社的业务架构演变遵循从单体架构到 SOA 架构再到微服务。基础架构物理机是没有的,团队从 2014 年开始就直接使用阿里云 ECS,在 2021 年开始容器化,最终到达期望的云原生架构形态。
云原生基金会官方对云原生的定义是 5 部分,第一是容器化,第二是不可变基础设施,第三个是声明式 API,然后是服务网格和微服务,其中微服务作为承载业务应用的核心,围绕这个,会有像调度编排、流量管理、可观测、DevOps 这些领域的一些能力,这是技术架构要做的事情。
从技术角度看,云原生架构是基于云原生技术的一组架构原则和一些设计模式的组合,将应用中的非核心的业务逻辑剥离出去,然后下沉到云原生基础设施这一层来统一处理,这样业务开发人员只需要关心自己的业务开发,让我们的业务应用变得更加轻量,高效。
因为公司是 2013 年成立的,早期的业务非常简单,运维能力也比较差,所以单体架构是优选。经过四五年的发展,业务有了长足的进步,平台的功能和模块都已经比较多了,因此,就升级到 Spring Cloud 微服务架构。当时用的是一套标准的用法,比如服务注册中心、配置中心用的是 Eureka,网关用的是 Spring Cloud Gateway。
此时,虽然实现了微服务架构,但也存在诸多问题,例如服务不稳定、排查效率低下、资源利用率低等。基于这些问题,开始做业务容器化改造。
首先,选用成熟的 K8s 平台去做业务容器平台,先解决部署与排查问题。 一开始我们选型的是阿里云容器服务 ACK Serverless 版,主要是看中了它开箱即用,不需要额外去维护,比较简单的优点。在使用的过程中,主要是先在开发侧去验证它,用了一段时间到 2021 年的时候,就完成了线上的迁移。
之后选用阿里云 ACK 的技术底座去完成线上容器化改造的原因是,当时在用 Serverless 过程中出现一些问题,比如调度比较慢等,在当时我们是没有这些能力去解决的。改造之后,大概是 300 多个微服务。此外,还涉及到一些基础架构的改造,比如服务注册中心,我们原先用的是 Eureka,后来迁移到 MSE 的 Nacos 里,配置中心使用了 MSE Nacos,来提升性能和稳定性。
在可观测性和应用性的架构上,开始用阿里云应用实时监控服务 ARMS 的应用监控能力和 MSE 产品来对应用服务进行性能观测与流量治理,这些都是在 2021 年这一年去完成的。
2021 年 8 月,我们将 Spring Cloud Gateway 进行了迁移,因为当时 Spring Cloud Gateway 的问题比较多,性能也比较差,所以就基于开源做了自己的 API 网关,再结合 MSE 的微服务套件,实现了流量管理。2022 年之后,开始做业务指标监控和稳定性建设这方面的工作。
2. 青团社云原生架构实践
接下来是实践的部分,这张图是线上的部署图。
前面这 4 个部分都是技术接入层,如 BGP 高防、WAF、BFF 等,主要提供基础的安全防护和路由转发功能。到了虚线这一部分是 K8s 集群,是基于 ACK 构建的容器运行平台,我们所有的业务容器都运行在 ACK 里面。
大家可以看到,我们运用了很多阿里云的中间件服务,比如 Kafka、RocketMQ。这些都是在使用 MSE 微服务引擎之后最新的一版架构,由于我们人手有限,所以为了线上的服务稳定性和降低运维成本,会直接用阿里云的这些云产品。网关方面,微服务内部的东西向流量主要由微服务引擎 MSE 来做流量管理,它给我们提供了一些能力,比如服务优雅上线下线,流量灰度,还有同可用区域这些部署能力。
接下来,我们将从调度编排、流量管理、可观测 3 个维度展开我们的实践。
2.1 云原生架构实践调度编排
第一部分是调度编排方面,我们有三个手段。首先是使用阿里云 ACK 作为技术底座来构建容器平台,再配合资源隔离的部署模式,保证多条业务线互不影响,因为我们有两条不同的业务线,所以无论是在成本核算还是服务稳定性,都会做到资源隔离。弹性伸缩方面是通过一些弹性伸缩策略取得成本和稳定性之间的平衡,降成本的同时不能损失稳定性。
接下来,通过两个案例阐述一下,第一是我们怎么样用 ACK 来实现高效弹性部署。
首先,在部署的时候是多可用区的,有杭州 H 区和 K 区,这是两个主可用区,所有的服务在部署的时候,在 K8s 的节点分隔上和调度策略上加一些标记,让它能够把业务负载给打散,这样一个服务有两个实例的话就是 K 区和 H 区各一个,一个挂了另一个还是好的。
在发布阶段,因为滚动更新是 K8s 内置的机制,可以通过简单的配一些健康检查的机制来得到好的发布体验,保证服务在发布的过程中不会出现业务受损。通过资源监控和业务容器监控,可以灵活伸缩业务容器的规模。节点池设计是 ACK 独有的,它相当于能为不同的业务线配专属节点,这样就可以达到物理隔离,有了节点池,要去做集群的弹性伸缩容的时候就非常方便,集群资源不够时可以通过简单的通过节点池的扩容操作,瞬间把节点池资源能力提升上去。
CSI 插件主要是用在开发测试环境。我们为了节省成本,很多开发测试环境,像中间件都是用容器自建的,容器自建属于有状态应用,有状态应用需要存储来支持它。CSI 插件,阿里云提供了很多对应的对接,比如阿里云的云盘 OSS、NAS 等。我们主要是用云盘插件来对接,把云盘挂到容器里,这样像数据库、Redis 等就可以得到稳定高效的存储。
VPC 方面,因为 ACK 里 VPC 网络是直通的,我们用的是阿里云的 Terway 插件,这个插件可以把容器内部的网络和我们的 VPC 互相打通,这个能力我们觉得比较重要,是因为没有上容器平台之前,我们的服务都是部署在 ECS 机器上的,现在如果要迁进去,我们的服务发现组件它在集群之外,就必然要让网络能够互通,否则它发现不了也就无法迁移了,所以 VPC 直通功能对我们是非常有用的。
另外还有集群监控,主要是体现在 ACK 提供了开箱即用的集群,借助与 ACK 深度集成的可观测监控 Prometheus 版,对 K8s 集群和业务容器轻松构建资源监控体系。一方面,可以及时了解容器运行情况和集群运行情况,另一方面,这些集群监控的技术指标可以作为我们判断系统是否需要扩容缩容的数据依据。通过部署基于指标或者是事件的弹性深度机制,能够快速对容器进行扩容。
以上这些使用场景,大部分公司应该都是类似的,可以总结一点:通过容器化来部署,充分运用云的能力,降低应用成本,保障应用高可用。这是第一个案例。
第二个案例是如何利用云来实现业务弹性。
这里有三个小的业务场景,第一是埋点,第二个是广告投放,第三个是热点活动。
埋点大家应该都不陌生,每个互联网公司都会需要埋点,通过埋点数据来了解用户,分析用户。埋点数据作为分析用户行为的数据资源,在整个产品的生命周期里面都非常重要。但是埋点数据的量通常都是非常巨大的,用户每一次点击,页面的曝光,还有用户的交互都会产生很多埋点数据。这些数据可以上传到后台去分析产品的使用情况,分析用户的行为和使用习惯,还可以延伸出做用户画像,用户偏好及用户转化路径这一系列数据产品。
我们的埋点有很明显的潮汐特征,流量通常是早上七八点钟开始逐渐上升,到了中午十一点、下午两点到达一天的顶峰,然后再开始逐渐下降,到夜晚四五点钟的时候就达到一天中最低,埋点数据量的规模也遵循这样的规律。
埋点数据,我们通常是先把数据发到后台,后台有埋点接收数据的处理程序,先简单的把它处理一下,然后给它丢到 Kafka 里,再由大数据的一个平台的组件去消费,消费完之后存储进去。
我们的大数据团队会通过埋点数据来构建不同的数据层,供不同的业务场景使用,在部署埋点接收程序的时候,就会充分考虑埋点流量的潮汐特征,去把它做一些定时扩容缩容的机制,比如可以加一些定时任务,早上九点、十点开始扩容扩一倍,就可以应对一天的流量高峰,到晚上再把它缩回去。
广告投放方面也很重要。我们平台也和很多互联网平台有广告投放的合作。在他们平台里面去投放广告吸引新用户。作为一项日常的运营工作,投放的时间点可能不太固定,它的效果也不一定能按照预期来,有时候出现爆点,流量就可能会被打爆。同时,伴随流量而来的一些点击数据或埋点数据都会剧增。
为了做好日常广告投放的工作,我们做了基于指标和事件的动态扩缩容机制。如果是因为应用容器资源,比如 CPU 内存爆了或者是达到了警戒线,又或者是处理数据的程序,有 MQ 消息积压的事件出现了之后,就去对事件进行监控,然后动态的去扩容。
我们用开源的 KEDA 工具配置业务指标进行监听,KEDA 工具通过监听 Prometheus 里面的指标(我们所有的指标都存在 Prometheus 里面),如果触发条件了之后会向 K8s 的 API server 发起一个 Pod 的扩容请求,这样就完成了扩容操作,整个过程都是自动化的。
最后的热点活动,类似于电商里面的促销活动,这个场景的特点是它的开始时间和结束时间都是已经提前知道的,这样只需要配备提前扩容的程序就可以了,比如到体验活动开始前的半小时先把容器给扩上去,结束之后就缩回去,我们主要是基于指标和事件这两点来做弹性扩容。
2.2 流量管理
这部分主要分享一下怎么去对流量进行管理。我们主要分三个部分:网关、流量服务治理引擎 MSE、消息队列。
这里有两个案例,因为作为头部的灵活用工企业,我们有很多端,比如有 C 端,B端,有安卓平台,有 iOS 平台。各大互联网平台,像支付宝、抖音、快手、百度、QQ 等都有对应的投放、产品和小程序在上面。
行业上也主要聚焦于 8 大行业,像餐饮、物流、商超这些。在这种在多端口,多行业的场景下,我们的报名岗位在类目上,或者是端口上都有很多差异,无论展示逻辑还是流量分发的规则都是有差异的。我们通过 API 网关和 BFF 来实现流量编排和不同端口的差异化处理。
网关这块主要是把流量引进来,BFF 主要是适配各端,它自己可能需要独有的一些数据格式,然后再调用后端的数据返回去。在网关里, BFF 本身是作为后端服务存在的,流量先经过网关,再经过 BFF 后通过后端把数据返回去。BFF 具体的实践架构不再赘述,但在青团社是用这个模式来做多端口的适配。
第二个案例是基于事件驱动构建灵活响应系统,案例是我们灵工管家这个产品,该产品主要是提供一系列的管理服务,比如考勤、发薪,还有排班等服务,这些服务是企业用到的一些企业级服务。平台用户可以做到薪资日结,像很多兼职岗位,商家都是提供兼职日,你早上去上班打卡,然后下班打完卡,还没到你回家,工资就到账了,这个就职体验是非常好的。
这就是基于事件来驱动,下班打卡之后会触发下班打卡的事件,触发算工时、算薪资的逻辑,再到提前到账,整个流程是全部事件驱动的,快的话可能几秒钟你的薪资就能到账,这也是灵工管家的比较好的一个产品卖点。当然消息 MQ 的场景是很常见的消息解耦,但我们还有别的场景,我们把 MQ 用在数据同步和消息分发上。数据同步用了一些插件和组件做一些数据同步的工作, MQ可以给它做一个缓冲,避免数据很多,一下子把对端给打爆了。消息分发的话,我们消息平台会有一些定时需要触发的定时消息,比如给用户发推送短信,我们会利用 MQ 的延迟消息能力去做,相当于到点之后,你就去触发这一消息,然后给用户发短信,发 push,这是一个具体的案例。
下面的案例介绍一下怎么实现灰度。早期我们没有灰度,所有的服务只要一上线,就相当于是全量状态,没法去验证服务到底对用户有多大影响。在 2021 年完成业务容器化改造之后,我们就引入了 MSE 微服务引擎来帮助我们实现全链路的灰度。
所谓全链路,指的是在流量链路这一层,网关进来之后到服务再到数据库,或者到经过消息中间件的这个链路。还有一个就是消息方面,要对流量的特征进行打标,然后链路的下一跳可能也需要打标,标识一下它要去到哪里。网关要负责南北流量的分发,在外部,我们通过开源的 APISIX 网关,它是有流量分拆插件的,可以在发布的时候,通过部署多个版本,在发布的时候,部署两个版本或者或多个版本,叫 deployment。部署完了之后,再给它打标,然后网关上面会有个流量分拆插件,你可以配一些特征,然后让流量进到指定的版本。
进来之后,这里面东西向的流量就属于 MSE 微服务引擎来做的事情。它通过 agent 插装的方式,把整个链路给串起来,实现了整个链路的灰度。
比如说像 HTTP 请求,它可以在请求头上加 gray 标识,然后依次透传到底层去。到消息中间件这一块,它对 MQ 这块做一些增强,相当于是埋点一样,把这些流量的灰度标记埋进去。然后在消费端做一下控制,这样就可以保证你的流量能按照你预定的规则顺利的进来。
重要的线上服务要发布的时候,先走一下灰度,可以明显的增强我们的信心。原先没有灰度的情况下,发布的时候都是很忐忑的,一般白天不敢发,都是夜晚才发,就是为了防止故障,因为你如果有问题的话,可能会影响很多用户。现在我们已经有 7300 万用户了,虽然说日活不是很高,但月活也有 1000 多万,所以说白天的流量还是非常大的。通过灰度发布,有问题的话在灰度阶段就能发现把它解决掉。
下面的案例是使用微服务引擎实现优雅上下线,这个能力也比较重要。因为早期很多线上故障都是因为发布导致的,经常在发布中发现很多服务调不通了,是因为它在下线过程中,但调用方这边不知道还去请求,就会报错。因此,在 2021 年,我们用微服务引擎 MSE 把服务期间不平滑的问题解决了。
优雅下线的原理其实非常简单,比如说服务提供者,我们称为 provider,他如果发了新的实例,当然流程还跟以前 K8s 滚动更新机制是一样的。他先滚动更新一个新版本的过程中,会触发老版本的下线动作,老版本下线动作就会执行 pre stop 钩子函数,这个函数是 K8s 提供了内置的机制,这个函数里 MSE 会提供一个接口,通过调用这个接口,让 MSE 的 agent 感知到服务要下线了,赶紧触发下线的事件。
事件触发之后,它的调用方可以感受到事件,然后在自己本地的 ribbon 服务列表缓存里面把 IP 给摘掉,摘掉之后,后面的请求就不再调用了,相当于我知道你要下线就把你提前摘掉,不调用就不会报错了。
服务上线的原理也差不多,上线之前先延迟一会儿,先不那么快到注册中心,而是放一点点流量进来,这个节奏是通过 K8s 健康检查机制来实现的。MSE 提供健康检查的接口,它会通过接口去向 K8s 暴露服务到底有没有准备就绪。现在没有准备就绪,流量就不能全部放进,可以放一点点,比如百分之零点几的流量进来,我先把服务预热一下,该建立的链接先建立起来,等到服务完全就绪之后,再把流量放进来,这样无论是下线还是上线,服务都是处于相对平稳的状态,下线调用端提前知道的话就不会再调用了。上线是先给你一段时间,让你先准备好,准备好之后,我再把流量放进来,这是优雅上线的原理。
2.3 可观测和监控
接下来我讲一下可观测的实践,可观测主要用到阿里云 ARMS 应用监控 + Prometheus + Grafana 以及云监控这个组合。
这个图就是用阿里云的云监控进行基础资源监控,如 ECS、云数据库 PolarDB、云消息队列,这些基础监控都有些对应的指标。
那如果要进行更精细、全面的指标监控,阿里云也提供 Prometheus 指标监控能力,它会把你的指标放到 Prometheus 里面,利用预置模板快速构建告警体系的同时,也可以通过自己的业务需求去配置相应的自定义告警规则。
第二个是应用实时监控服务 ARMS,作为一个具备诸多监控模块的云原生可观测平台,我们用其中的应用监控来解决 Java 应用性能的监控问题,我们的应用在线上跑的时候可能会突然发现有几个请求报错,或者是响应极慢,那研发人员要要怎么去排查呢?
通过 ARMS 应用监控去观测整个调用链路,及时发现整个链路到底哪一环节执行的比较慢。通过耗时数看得到,看得到之后定位一下的问题,比如说像这个实例里面,这是高德的三方接口比较慢,因为三方接口耗时确实是不可控的,这种问题我们可以提前知道,还有一些超时、异常问题,这些都可以通过 ARMS 应用监控发现,当然也可以配相应的一些告警规则。
除了云基础设施以及应用性能,我们希望进一步对业务指标进行监控,实现全栈可观测。针对业务指标监控,我们主要是用 Prometheus 和 Grafana 来进行加工与呈现。这里有两个案例,一个案例是基于业务指标,也就是消息中心的监控大盘,这里面的所有的数据都是通过 Java 端用 Prometheus 的客户端把指标数据暴露出来,然后 Prometheus 服务端把这些数据采集上来,再给它配成这种图表。
配成图表之后就可以可视化了,能看到现在的业务运行状况,这个曲线和两个图例应该都是实际的业务运营状况,有了这些数据,就有了监控告警的条件,没有这些东西就不知道业务到底运行怎么样。
然后日志告警中心这部分,像一些严重的日志报错,Error 日志这些比较重要的日志报错,我们会配对应的一些告警规则,在出现问题的时候进行及时告警。
经过这么多年业务发展,我们的业务一直是在云上不断的发展和壮大,充分用了云的各项能力来构建平台。我们主要考虑的是成本,稳定性和研发效率这三块,希望能达到平衡。因为我们团队本身也比较小,所以用云的优势是非常明显的,可以利用云的弹性有效应对日益剧增的业务增长规模。云原生之路我们还将继续,在此先汇报我们的成果。
通过容器化部署,提高部署密度的方式,把原先的 ECS 成本降低了 50%,因为原先 ECS 部署的比较粗放,可能一台机器上只部署一两个或者两三个服务。通过容器化的部署,可以提高部署密度。
第二个是应用实现了高可用和弹性调度,使我们可以从容的应对未来的业务增长。
第三个是通过全面实施基础资源、应用服务及业务监控,快速了解系统运行状态。过去,系统运行状况对我们来说是一个黑盒。即使有一部分数据,也无法有效的对不同类型数据进行加工与利用,一切都是未知。通过这些能力,从无到有的建设,我们现在可以非常快速的去了解、观测系统的状态。
第四个成果是运维成本大幅降低,把机器给省下去了,像一些人工需要做的事情,现在通过高度的自动化可以实现大规模的集群管理。
3. 总结与展望
展望未来,可能会更多的考虑这以下三个方向,这些也是我们未来要做的事情。
- 我们现在比较关心的服务网格,因为以后也会有更多语言,像一些应用比如说 Java、Python、Go 这些,MSE 目前可能对 Java 的支持非常好,后面我们也会探索基于服务网格的通用的流量治理能力。
- 因为 Java 占我们整个应用的体量大概是 80%,后续会考虑用一些新的技术,比如用 GraaLVM native 来实现原生镜像部署,这样可以进一步降低应用的资源占用情况,提高应用的响应峰值性能。
- 通过混沌工程的实施,进一步提高线上的稳定性。目前我们的稳定性可能还没有达到理想的目标,这是后面的努力方向。
作者:杨磊