您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center   Code  
会员   
   
 
     
   
 订阅
  捐助
基于Springcloud的服务治理落地实践
 
 
   次浏览      
 2021-12-3
   
 
编辑推荐:
本文主要介绍了一套基于Springcloud的服务治理方案,涉及的内容主要包括注册发现/健康检查/灰度发布/访问鉴权等。
本文来自于微信公众号 ACCESS研发中心,由火龙果软件Linda编辑、推荐。

前言

在微服务盛行的今天,提起服务治理,相信大家都已经不再陌生,许多公司都有自己内部的一套定制化的实现方案, Access也不例外, 接下来, 我来为大家介绍一下我们的一套基于Springcloud的服务治理方案,在本文中,涉及的内容主要包括注册发现/健康检查/灰度发布/访问鉴权等。

演进

业务初期,我们所有的业务运行在一个PHP服务中,通过公有云的ECS虚拟机与Nginx来提供服务,这个并没有持续很久,因为马上我们的系统性能就已经跟不上业务的需求,但凡遇到电商大促等活动,系统都是随时宕机的状态,大规模的扩容也徒增了高额的运维成本。

2019年底,我们开始了正式的重构,一方面将PHP项目重构为基于SpringCloud框架的Springboot应用,另一方面, 从ECS部署迁移到了灵活的K8S部署,在此基础上引入了Consul注册中心来实现微服务间的注册与发现。

2020年中,我们进行了整体服务的云迁移,由于迁移过程的需要,我们去除了Consul注册中心,将注册发现机制下沉,由K8S的 Service与Ingress来实现,我们发现, Consul在这里确实是有点多余。

好景不长,到2020年底,这个架构已经产生了许多治理相关的痛点不能解决,最终,我们在此基础上,引入了全新自研的Sun服务治理平台,到目前为止,我们已经基于此平台实现了:实时的注册发现机制;可靠的访问鉴权机制;全链路灰度发布;精准的监控告警等等治理能力。

背景

在开始之前,请允许我再介绍一下咱们在服务治理之前的技术栈背景。

语言框架:我们经过2年多的重构改造,从开始的PHP转型为Java语言,并且所有服务都基于内部的Springcloud脚手架搭建,使用Maven进行依赖管理, 整体语言和规范统一度较高。

开发规范: 我们所有服务都继承内部定制的父pom文件,架构组定制化包装了诸多开源组件,如:spring-webmvc 等,也自定义了许多必选组件,如:consumer / provider 等,并且这些基础组件版本受父pom管理且版本号统一,这意味着基础架构组可以便捷的切入所有业务服务完成一系列的扩展和升级,对于业务服务来说,大部分时候只需要升级父pom版本即可完成一次升级。

部署运维:我们使用CCE云容器引擎进行部署运维, CCE是基于K8S提供的企业级Kubernetes集群。

流量架构:我们的流量链路大体上分为两个部分, 一是公网请求流量链路, 二是内部调用流量链路。

对于公网流量,我们使用公网ELB(LVS+Nginx)集群,将流量引入我们内部的SpringcloudGateway网关集群,内部gateway对流量进行过滤后路由至业务服务上。

对于内部流量,我们通过K8s ingress,为每一个服务配置了一个专属的域名,服务间通过专属域名进行调用,意味着我们的注册发现/健康检查/负载均衡都是借助于K8s实现的。

痛点

那么,基于以上的技术方案和背景,我们遇到了哪些痛点呢?

发布不平滑

由于依赖K8S集群的注册发现与健康检查机制,虽然我们已经采取了滚动发布与容器就绪检查,但是容器在退出时并没有提前从服务列表中剔除该实例,ingress后端服务器未及时更新,这导致发布期间还是会出现少量502请求异常,并且由于容器没有预热和延迟上线,所以新启动的容器RT非常高,在一些对RT比较敏感的核心链路中产生了较大影响。

不支持灰度发布

作为业务处在快速增长期的发展中公司来说,经常会有比较大的项目需要上线或改动,一次项目发布上线涉及二十个服务都是十分常见的,涉及的服务越多,代码改动越多,意味着发布存在的风险也就越大。这个时候,大家一定能想到一个词:灰度发布。可是基于我们目前的现状,流量统一走ingress路由进行负载,别说全链路灰度了,就连实现单个应用的灰发都是天方夜谭。

敏感接口无鉴权

在众多微服务中,不可避免会存在一些敏感的数据与服务,比如财务/用户信息相关的服务,因为在集群内部网络都是互通的,如果不加以鉴权的话,很容易造成的敏感数据泄露,这种事情,没发生的时候都不以为意,一旦发生才后悔莫及。

注册中心选型

针对以上问题,我们意识到,只有重新引入注册发现机制,这些问题才有机会解决。Consul是我们最先考虑的,但是它的几个特性却让我们望而却步。

部署成本:由于高可用集群部署架构下,每一个服务实例容器下都需要运行一个Consul Agent守护进程,来监控客户端实例的状态,每一个Agent进程都需要额外占用运维资源,细算下来是一笔不小的成本。

有状态:Consul服务节点间基于Raft协议集群部署,这意味着各个节点需要提前了解其他所有存在节点,它使得我们在对Consul进行扩缩容或发布迭代时显得不那么灵活。

非Java:我们知道Consul是由Go语言开发而成,这与我们的语言栈多少有些不和谐,因为后期我们需要针对性的扩展一系列治理功能,以及集成我们的基础组件,我们知道Golang很牛,但还是算了吧~

Eureka怎么样,我们选它了吗?我们没有~

健康检查:Eureka采用的是定时心跳的健康检查机制,当服务端超过一定时间未收到心跳,则认为此客户端实例已经下线,基于这个机制,如果客户端实例意外宕机,在很长一段时间内,调用端仍然会去请求这个实例,导致线上稳定性被破坏。当大批量实例重启或发布时,这个问题也会被放大。

有状态:相对于Consul来说,Eureka的集群部署模式简单明了,但它依然是有状态的,与Consul存在类似的痛点。

Nacos是一个十分强大的开源项目,它实现了动态服务发现、服务配置、服务元数据及流量管理等多项能力,可以说它能满足目前几乎所有常见的治理需求,并且没有明显的缺点。

可是对于我们来说,它也不是一个很好的选择,正因为Nacos的强大,使得此项目变得很重,它包含了太多我们不需要的功能与代码,比如动态配置的能力,我们已经有了自己的配置中心,这部分代码就是多余的,而且我们需要的不仅仅是一个注册中心,而是基于注册中心的一整套服务治理解决方案,在Nacos上进行二开的成本不亚于自研一个轻量级的注册中心服务。

Sun的诞生

经过多方选型,我们最终决定自研注册中心服务,它具备几个核心特性:无状态/高可用/宕机感知等,并基于此开发了一套集注册发现/健康检查/灰度发布/访问鉴权/监控告警等功能于一体的服务治理平台,我们给它起名为Sun。

下面是Sun服务治理平台的基础架构:

在这个架构中,主要存在三个模块:客户端(sdk) / 服务端(server) / 管理端(portal)

客户端SDK以组件的方式运行在每一个业务服务中。

它主要负责与服务端进行通信(Websocket),完成自身的注册,并接收服务端的列表下发和策略下发。

其次,它扩展了Feign与RestTemplate,并在请求头中添加了自身信息作为来源应用,这样服务提供方收到请求时就可以清楚的知道调用方是谁了。

服务端的主要职责是管理好连接到自己节点上的客户端实例,接受它们的注册,完成对它们的健康检查,并将他们所订阅的服务列表同步给客户端,同时还会将管理端配置的一些流量策略同步给客户端,下面的图片展示了服务端内部的工作原理。

图中客户端表示我们的业务服务,每一个业务服务实例与一个sun节点建立WebSocket长链接。

客户端发送注册消息(消息中携带自己的group分组与version版本号),服务端会将消息解析后转发到特定的Action处理器更新数据中心,并新增一个健康检查任务,对此实例进行定期的健康检查。

当数据中心产生数据变更时,会触发相应的Listener,将变更信息下发给其他的客户端实例,当然,如果部分客户端未订阅变更的服务,那么也不需要同步给它们。

服务端是支持单机与集群两种部署方式,在集群模式下需要额外配置外部数据源(ZK)来协助服务端实现集群间的数据同步工作,而在单机模式下,可以不需要配置外部数据源。

除了注册与下发服务列表以外,服务端的数据中心中还存储着流量策略信息,这些流量策略由管理端(sun-portal)进行维护, 并通过数据中心的Listener机制将变更下发给每一个客户端。

这里的流量策略是什么呢?

简单的说就是一个路由规则,告诉客户端在哪些情况下选择哪些服务实例进行调用。比如: 在header中存在version:2.0 的情况下,选择服务列表中version版本号为2.0的实例。

zookeeper数据源中的结构如下:

/sunNodes节点负责管理所有的注册中心节点,这可以使得注册中心集群中的节点可以互相感知对方的状态,我们可以利用这个信息来实现注册中心长链接的负载均衡,以及部分节点宕机后的实时感知与处理。

/clients节点就是负责存储我们的客户端实例数据了,可以看到客户端实例被划分到了不同的注册中心节点(sunId)下面,并且在这套注册中心体系中,不存在消费者与提供者的概念,人人都是消费者,人人皆可提供者。

/config节点负责存储我们的灰度流量策略,以及服务鉴权相关的配置。

问题解决

平台基本背景介绍完了,下面我们来看下基于这个平台,如何真正的解决上面的几个痛点。

发布不平滑

实现平滑发布需要实现两大要素:平滑下线/平滑上线。

实现平滑下线相对是比较简单粗暴的,只需要基于Spring的ContextClosedEvent事件,在Spring容器退出前,向Sun服务端发送注销消息,由于我们设计的注册中心模块是基于WebSocket长链接的主动推送机制的,所以实例下线的消息几乎可以在瞬间就可以同步到所有订阅方,并且客户端在发送注销消息后,会执行一个sleep操作,以保证退出之前,所有订阅方都已经感知,且进行中的请求完成响应。

上面的平滑下线是有限制的,对于未能正常发送注销消息的实例,调用方的感知是有延迟的 (由于我们的注册中心采用的是长链接的方案,所以即时没有收到注销消息,当实例与服务端断开连接时,服务端仍然能感知并将状态变更通知到所有调用方,但是会有延迟),所以我们利用K8s的停止前处理能力,在容器退出调用脚本主动告知注册中心此节点已下线。

平滑下线关键词:延迟退出/断开感知/停止前禁用。

关于平滑上线,在客户端实例注册到注册中心后,不会立即提供服务(不管注册时客户端状态是否健康,起始状态都被标记为不健康状态,经过一轮健康检查后,方可更新为健康状态), 这可以防止服务在未就绪时接受请求。

其次,服务在启动完成时,如果直接接受请求,那么这些请求的RT会明显增高,这个是因为服务启动时,线程池/连接池/对象池/本地缓存等资源都还未加载,需要花费大量CPU和时间同步去加载这些资源,针对这个问题,客户端SDK中在服务SpringReady事件中主动预热了多个内部资源(比如RibbonContext等),同时提供了自定义预热注解,业务服务只需要在任意方法上添加注解,即可在启动时完成对注解方法的预热,至此可以将服务启动首次请求RT从3s~5s降低至300ms。

平滑上线关键词:延迟上线/启动预热

做到了平滑下线+平滑上线,从流量层面与性能方面算是实现了发布的平滑,但是如果新发布的代码存在bug,依然会导致发布问题,对用户产生不好的体验,接下来我们继续使用灰度发布来解决这个问题。

不支持灰度发布

实现灰度发布的前提是服务需要有版本的概念,并且需要获得对流量完全的控制。

前面提到,我们每个客户端都有一个 group与version 属性(默认值为:default 与 -1,可以通过启动参数等方式指定),同时,group与version信息会在注册时同步到注册中心,随后下发到调用方,这样一来,调用方就可以区分这些服务实例了。

接下来,服务端需要给这些调用方下发一个流量策略,让其知道在什么情况下调用哪些实例,期望的效果是,圈定一批用户群体,或者圈定一定比例的用户,让这些用户来访问特定的灰度服务。

具体方案如下:

首先我们在标签服务中创建一个灰度标签,并给这个标签下添加一批用户(意味着这些用户拥有这个灰度标签),我们发布一个新的服务实例,添加启动参数来指定此实例的group与version,随后我们在管理端配置流量策略,策略的规则是:当header中包含 User-tag:grey时,优先进入灰度的group服务(v2)。

APP在用户登入或跳转页面时,会从服务端拉取此用户所携带的标签,并在所有请求头中添加此标签信息,如:User-tag: grey。

请求到达Gateway网关,客户端SDK会发现请求头匹配了流量策略的规则,遂将请求转发到了灰度的服务S1(v2),同样的,S1服务收到请求后,客户端SDK会在其调用S2之前扩展请求头,将User-tag信息添加到RPC请求头中,随后,客户端SDK根据当前请求头又将请求转发到了灰度的S2(v2)。

至此,灰度发布就实现了,事实上其中还有许多细节,比如:如何保证多线程环境下header信息不丢?如何保证更改灰度用户群体时前后端能同步等等?

敏感接口无鉴权

针对敏感接口,我们设计了一套基于注解与动态配置的鉴权方案,它通过为接口或接口类添加注解来定义所属资源,并通过动态配置来指定这些资源允许被哪些服务所调用,完成以上配置与定义相对是比较简单的,接下来的问题是当SDK拦截到RPC请求后,如何知道来源应用是谁?

为了防止恶意修改请求头中的来源应用信息,我们在请求中引入了加密Token机制,客户端在首次启动注册时从服务端获取专属Token(Token由应用名与时间信息加密而成,只能被服务端加密或解密),并定期从服务端更新Token,在发起RPC请求时,在请求头中添加Token信息,服务端收到请求后可借助服务端来进行Token解析,根据应用名与有效期来进行校验,如果Token解析失败或是超过有效期,亦或是应用名不在接口访问白名单中,则拒绝访问,否则正常处理请求。

结语

至此,我们通过自研的服务治理平台解决了三个痛点,当然,这不是全部,我们还可以基于此来实现更多好玩有用的功能,比如: AB测试等。此外,我们还引入了javaagent技术来实现对服务的监控/告警/全链路追踪/无感dump等功能,下次再来给大家分享吧,拜拜~

 
   
次浏览       
相关文章

企业架构、TOGAF与ArchiMate概览
架构师之路-如何做好业务建模?
大型网站电商网站架构案例和技术架构的示例
完整的Archimate视点指南(包括示例)
相关文档

数据中台技术架构方法论与实践
适用ArchiMate、EA 和 iSpace进行企业架构建模
Zachman企业架构框架简介
企业架构让SOA落地
相关课程

云平台与微服务架构设计
中台战略、中台建设与数字商业
亿级用户高并发、高可用系统架构
高可用分布式架构设计与实践
最新活动计划
软件架构设计方法、案例与实践 8-23[特惠]
Linux内核编程及设备驱动 8-15[北京]
Python、数据分析与机器学习 8-23[特惠]
嵌入式软件架构设计 8-22[线上]
QT应用开发 9-5[北京]
 
最新文章
大数据平台下的数据治理
如何设计实时数据平台(技术篇)
大数据资产管理总体框架概述
Kafka架构和原理
ELK多种架构及优劣
最新课程
大数据平台搭建与高性能计算
大数据平台架构与应用实战
大数据系统运维
大数据分析与管理
Python及数据分析
更多...   
成功案例
某通信设备企业 Python数据分析与挖掘
某银行 人工智能+Python+大数据
北京 Python及数据分析
神龙汽车 大数据技术平台-Hadoop
中国电信 大数据时代与现代企业的数据化运营实践
更多...