灵雀云第三期(2020-2021)传统行业云原生技术落地调研报告

灵雀云阅读(2115)评论(0)

引言

如果说2019年是“云原生技术商业化元年”,那么2020年就是“云原生技术成为新常态”的一年。在这一年里,云原生技术正在快速开拓新的技术边界,支持新的应用范式,并更加关注全栈能力和生态建设。突如其来的疫情,更是给2020年的云原生应用落地速度与格局带来了深远的影响,云原生技术已经成为传统企业数字化转型的唯一解决方案。在这样的背景下,云原生技术实践联盟(CNBPA)联合灵雀云、云原生技术社区发起了“2020-2021年(第三期)传统行业云原生技术落地调研”。调研期间,总计收到了783份有效调研问卷,参与者分别来自金融、制造、能源、医药、电信、政企等不同领域、不同规模的传统企业。本次调研与前两期相比在多个维度上保持了统一和延续性,同时也对技术问题的颗粒度进行了更细致的拆解,不仅可以纵向对比前两年的调研数据,宏观了解云原生技术在传统行业的应用情况在三年内的变化,还可以深入了解企业在云原生基础设施、应用架构、开发流程、数据服务等各个板块的应用情况和落地成熟度,带您从全栈角度重新观察云原生。

核心观点

云原生技术(容器、DevOps、微服务)在生产环境中的应用相比去年已翻倍;

Kubernetes 已成为所有基础设施类软件的核心;

Service Mesh 使用率呈猛增势头,有望超过 Spring Cloud 成为市场最受欢迎的微服务框架;

服务网格、无服务及边缘计算成为企业最关注的三个云原生新兴方向;

01: 疫情之下,传统企业的IT规模逆势上扬

2020年,我国是全球唯一经济正增长的主要经济体,“新基建”与“增强产业链能力”等政策极大促进了传统企业数字化转型的发展。随着云计算在各行各业的落地不断深化,被技术赋能的传统企业早已将加速构建企业级云平台,作为创新发展的重要行动目标。

今年参与调研的企业中,100台服务器以下的企业占40.4%,100-500台服务器规模的占22.4%,500-1000台服务器的占13.1%,1000台以上服务器的企业则达到了24%,是2019年的2倍。

从团队规模来看,拥有100人以上研发团队的企业达到了52%,创三年调研的新高。

在问到企业IT预算投入的问题时, IT预算下降的企业占24.1%,增长保持在1%-5%的占38.4,增长5-10%的比例为22.1%,还有15.5%的企业IT预算有10%以上的增长。对比往年数据不难发现,2020年疫情像是为云时代的到来按下了加速键,传统企业的数字化转型步伐加快,IT投入上呈稳步增长态势。拥有较大IT规模企业,服务器用量与团队规模在2020年都有明显扩张,初创型及中型企业也基本维持了前两年的IT规模。

02: 云原生技术(包括容器、DevOps、微服务)在生产环境中的应用相比去年已翻倍

在云原生技术的支撑下,IT系统每周、每月更新升级的企业比例都有所升高,3-6个月更新以及半年更新的企业比例再次下降。相应地,系统维护压力和学习成本越来越高,给企业IT部门带来巨大挑战。

 

伴随着云原生技术的普及,企业面临着自研和外采的两难选择。自研不但人力成本和时间成本较高,也会遇到技术选型困难、陡峭的学习曲线、投入产出比预期复杂,效果难预测等情况。对比去年数据,在选用云原生技术时,依靠自研能力自建云原生平台的企业在逐年减少,同时企业呈现了与专业的云原生技术厂商合作的趋势,采购相对标准化的第三方云原生基础平台,自身聚焦于应用开发,已经成为用户实现云原生落地最稳健的选择。

对于云原生技术落地阶段,调研数据如预期——将包括容器、DevOps、微服务在内的云原生技术用于核心业务生产的企业已达到34.89%,相比往年数据有近1倍的提升。这也从侧面说明,云原生这一整套的技术体系和方法论,在几年的落地实践中已经为企业取得了显著的业务成果,在企业数字化转型中扮演了重要的角色。

03: 云原生基础设施:Kubernetes已成为所有基础设施类软件的核心

在过去几年,容器完全改变了云计算的基础设施架构。但云原生的基础设施到今天已经远远不限于容器管理,在大规模企业落地时,会看到一些更加现实的业务应对和趋势。

调查显示,受访企业中,72.7%的企业采用Kubernetes作为容器编排技术,保持了绝对的优势地位。这与最近CNCF发布的中国区云原生调查报告中的数据保持了相当高的一致性,同期全球调查报告的数字是78%。我们可以看到,在Kubernetes的使用率上,国内传统企业与全行业,甚至全球Kubernetes的使用率是持平的,Kubernetes已经完全进入主流市场,成为所有人都在使用的技术。多云部署的快速推进,与Kubernetes在企业生产环境中的运用不无关系。Kubernetes作为一个可移植层,它能够屏蔽基础设施的很多细节,可以很容易地做跨云迁移,对混合云和多云管理非常有好处。

调查显示,有15.3%的被访问企业的云计算服务部署在公有云,53%部署在私有云,31.7%已经使用了多云/混合云的部署方式。另外,还有48.9%的受访人表示,所在的企业将要考虑多云/混合云部署云服务,也就是说,近8成未采用多云/混合云部署云服务的企业,打算在将来采用。由此可见,传统企业中多云/混合云部署已成明确的趋势。

04: 云原生应用架构:Service Mesh使用率呈猛增势头,有望超过Spring Cloud成为市场最普遍的微服务框架

企业一定要意识到微服务并不是免费的,它的本质是用运维的复杂度去换取敏捷性。因此在微服务改造大行其道的今天,企业采用微服务仍能遇到一些阻力和问题。

对比2019-2020两年数据,受调研的企业在微服务采用阶段上基本保持了相对稳定的比例。采用微服务开发新系统(41% / 43.7%)和对遗留应用进行改造(23% / 24.8%)的企业比例都有些许提升。同时,彻底不打算实施微服务架构的企业比去年同期有所增加,说明部分企业对于到底需不需要这样的敏捷性,有没有必要用过多的复杂度作为代价,也经历了深入的思考和判断。

对于微服务框架的选择,今年的调研数据有比较大的突破。虽说市面上存在丰富的微服务框架,可是对稳定有极高要求的传统企业却常常陷入无型可选的尴尬境地,这也是为何Java系的Spring Cloud在前几年几乎一统天下的原因。以 Istio为代表的新一代微服务框架Service Mesh虽然还在演进过程中,但经过几年的实践深耕,以及在头部互联网企业宣传和能力平台化的推动下,已经被认定为微服务治理的最佳实践。在被调研的企业中,Service Mesh的使用率从2019年的11.8%提升到了今年的40.4%,涨幅达到三倍。Spring Could虽然仍占据选型第一的位置,但Service Mesh的势头不容小觑。

在用微服务架构开发新的应用系统的过程中,企业也不可避免地遇到了诸多难题。最大的挑战是“微服务拆分缺乏专业的人才,没有最佳实践指导”,占57.4%之多。微服务的演进成熟需要时间,企业熟悉掌握和应用新技术也需要时间。如果在没有明确业务需求、相应组织架构及技术关键细节的前提下,就强推微服务框架,反而有可能会达到反向作用。

05: 云原生开发流程:超6成DevOps基于容器建设

云原生技术的普及带来了诸多红利,其中之一就是DevOps产业的井喷式发展。在与容器化的新一代基础设施以及微服务架构搭配下,DevOps能够更加充分地利用云化资源、自动弹性伸缩等特性,在保证稳定的同时,更加快速交付高质量的软件及服务,灵活应对快速变化的业务需求和市场环境。

对比往年数据,DevOps已经实施或即将实施的比例越来越高,今年逼近9成。这其中,有62.3%的企业DevOps建立基于容器环境,25.7%的企业基于物理环境或VM。

DevOps的深入渗透,和云原生技术推动数字化转型的步伐基本保持了一致。我们认为,在云原生场景下,平台需要覆盖 DevOps完整的流程和场景,DevOps使用的整条工具链需要和Kubernetes平台对齐打通。不仅如此,Kubernetes可以编排一切,DevOps工具可以放在Kubernetes平台上去做管理。

这与Gartner于2020年9月发布的首份“Market Guide for DevOps Value Stream Delivery Platform(VSDP)”(DevOps价值流交付平台的市场指南报告)中的观点相一致。该报告指出,所有采用DevOps的企业都将从工具链方式向平台方式转型,到2023年,预计将有40%的企业和组织采用平台化方式的DevOps价值链交付平台。

06: 云原生数据服务:超5成受访企业考虑迁移至云原生数据库

在去年的调研中我们提到,企业在落地云原生的时候更关注完整、体系化的云原生解决方案。所以越来越多的数据库、存储、网络、中间件、安全等周边技术、组件正在跟云原生技术对齐。其中,容器化的数据服务目前成为为企业云原生的一个重要落脚点。

在问到企业是否愿意将本地数据迁移至云原生数据库时,调研数据显示,25.1%的受访企业已经做了迁移,而有29.5%的企业表示有意愿,还未迁移,另有36.1%的企业表示正在评估。

在对比云原生数据库与传统数据库时,“云原生数据库可扩展性”、“故障易处理”、“安全性增强”等优势成为传统行业企业最看中的优势。从调研结果可以看到,实施云原生过程中,越来越多的企业对于数据服务类的组件,已经不再满足于把数据服务留在虚拟机甚至物理机中“稳字当头“的应对思路了,提供兼容广泛的开源数据库、各类主流数据服务在Kubernetes Operator上实现自动化运维的一站式解决方案,将帮助企业降低开发测试人员的使用、管理成本,更专注于业务本身。

07: 展望未来:服务网格、无服务及边缘计算成企业最关注的三个云原生新兴方向

在已有工作重点的基础上,传统企业还将目光投向了云原生的新兴方向。最受企业关注的依然是服务网格(Service mesh),如前面调研数据显示,Service Mesh在企业中呈爆发性增长,有望成为市场上使用最普遍的微服务框架,但Service Mesh的成熟度还有待考察。

在新兴技术关注度上排名第二的是无服务器架构(FaaS/Serverless),Serverless是正在兴起的一种云原生工作负载,从 CNCF的调查报告来看,Serverless的普及率要高于service mesh。但是多数Serverless的使用都是在公有云上面,目前来说公有云上的 Serverless能力也会更加成熟,但长期来看,私有云环境也会慢慢有对Serverless编程范式和架构范式的需求。排名第三的是边缘计算,边缘计算是对云计算的一个补充和拓展。随着5G的发展,运营商、工业制造、智慧城市等传统行业都会涉及到海量、超低延时、多样性的数据处理,边缘计算就显得尤为重要了。数据表明,“新基建”时代80%的数据和计算将发生在边缘,并会有更多场景落地。此外,机器学习/联邦学习、低代码、Chaos mesh 也得到了企业相当多的关注。 综上,从整个报告的数据来看,传统行业企业开始全面拥抱云原生。基础设施、应用架构、开发流程、数据服务等方面的云原生化改造,让更多业务应用从诞生之初就生长在云端,从技术理念、核心架构等多个方面,帮助企业IT平滑、快速、渐进式落地上云之路。

云原生新边界——阿里云边缘计算云原生落地实践

alicloudnative阅读(2419)评论(0)

作者 | 黄玉奇
来源 | 阿里巴巴云原生公众号

日前,在由全球分布式云联盟主办的“Distributed Cloud | 2021 全球分布式云大会·云原生论坛”上,阿里云高级技术专家黄玉奇发表了题为《云原生新边界:阿里云边缘计算云原生落地实践》的主题演讲。

1.png

大家好,我是阿里云云原生团队的黄玉奇,非常感谢能有这个机会来跟大家分享。今天分享的题目是《云原生新边界∶ 阿里云边缘计算云原生落地实践》,从题目也能看到,分享内容应该是包括几个部分:云原生、边缘计算、二者结合架构设计、阿里云在商业和开源的实践以及案例。

今天大家所熟知的云原生(Cloud Native)理念,本质是一套“以利用云计算技术为用户降本增效”的最佳实践与方法论。所以,云原生这个术语自诞生,到壮大,再到今天的极大普及,都处于一个不断的自我演进与革新的过程当中。云原生已经作为一系列的工具、架构、方法论而深入人心,并为广泛使用;那么云原生到底是如何定义的呢?早期,云原生含义包括∶ 容器、微服务、Devops、CI/CD;2018 年以后 CNCF 又加入了服务网格和声明式 Api。

而回过头,我们再粗线条的看看云原生的发展历史,早期因为 Docker 的出现,大量的业务开始容器化、Docker 化。容器化通过统一交付件、隔离性从而带来了 Devops 的快速发展;Kubernetes 的出现让资源编排调度与底层基础设施解耦,应用和资源的管控也开始得心应手,容器编排实现资源编排、高效调度;随后,lstio 为代表的服务网格技术解耦了服务实现与服务治理能力。今天云原生几乎”包罗万象”般的无处不在,越来越多的企业、行业开始拥抱云原生。

2.png

而阿里巴巴作为云原生技术的践行者之一,云原生早已成为阿里的核心技术战略之—,这源自于过去十多年阿里在云原生领域的积累、沉淀和实践。大致可以分为三个阶段:

  • 第一阶段通过应用架构的互联网化沉淀了核心中间件、容器、飞天云操作系统等基础云原生能力;
  • 第二阶段是核心系统的全面云原生以及云原生技术的全面商业化;
  • 第三是云原生技术的全面落地和升级阶段,尤其是以 Serverless 为核心代表的下一代云原生技术正在引领整个技术架构升级。

阿里云容器服务 ACK 作为阿里云原生能力相关的商业化平台,正在为广大客户提供丰富的云原生产品及能力,这些都是拥抱云原生最好的佐证,我们坚信云原生是未来。

云原生技术已经无处不在, 阿里云作为云原生服务的提供者,我们认为云原生技术会继续高速发展,并被应用于”新的应用负载”、”新的计算形态”和”新的物理边界”;从阿里云云原生产品家族大图中我们可以看到∶ 容器正被用于越来越多类型应用和云服务中;并且通过越来越多的计算形态承载,如 Serverless、函数计算等等;而丰富的形态也开始从传统的中心云走向边缘计算,走向终端。这就到了今天分享的主题:边缘计算中的云原生,下面我们看看什么是边缘计算。

首先,我们从直观感受上看看什么是边缘计算。随着 5G、loT、音视频、直播、CDN 等行业和业务的发展,我们看到一个行业趋势,就是越来越多的算力和业务开始下沉到距离数据源或者终端用户重近的地方,从而来获得很好的响应时间和降低成本;这明显区别传统的中心式的云计算模式。并越来越被广泛应用于汽车、农业、能源、交通等各行各业。

3.png

再从 IT 架构上看边缘计算,可以看到它具有明显的按照业务时延和计算形态来确定的分层结构,这里分别引用 Gartner 和 IDC 对边缘计算顶层架构的解释∶ Gartner 将边缘计算分为”Near Edge”、”Far Edge”、”Cloud”三部分,分别对应常见的设备终端,云下 IDC/CDN 节点,以及公共云/私有云;而 IDC 则将边缘计算定义为更直观的”Heavy Edge”、”Light Edge”来分别表示数据中心维度,和低功耗计算的端侧。从图中我们可以看到分层结构中,层层相依。互相协作。

这种定义也是现在业界对边缘计算和云计算关系所达成的一个共识。说完背景、架构,我们再看看边缘计算的趋势;我们尝试从业务、架构和规模三个维度去分析边缘计算的三大趋势:

第一,Al、loT 与边缘计算的融合,会有种类越来越多、规模越来越大、复杂度越来越高的业务运行在边缘计算场景中,从图上我们也能看到一些非常震撼人心的数字。

第二,边缘计算作为云计算的延伸,将被广泛应用于混合云场景,这里面需要未来的基础设施能够去中心化、边缘设施自治、边缘云端托管能力,同样图上也有部分引用数字。

第三,基础设施的发展将会引爆边缘计算的增长,随着 5G、loT、音视频行业的发展,边缘计算的爆发是理所当然,去年疫情期间在线直播、在线教育行业的爆发式增长也是一个例子。

随着架构的共识形成,在落地过程中我们发现,边缘计算的规模、复杂度正逐日攀升,而短缺的运维手段和运维能力也终于开始不堪重负,那么如何去解决这个问题呢?

云和边缘天然就是不可分割的有机整体,”云边端一体”的运维协同是目前比较能形成共识的一种方案。而作为云原生领域的从业人员,我们试着从云原生的角度来思考和解决这个问题;试想,如果”云边端一体”有云原生的加持,将会更好的加速云边融合进程。

4.png

在这个顶层架构设计下,我们抽象出了云边端协同的云原生架构:在中心(云),我们保留了原汁原味的云原生管控和产品化能力,通过云边管控通道将之下沉到边缘,使海量边缘节点和边缘业务摇身一变成为云原生体系的工作负载,通过服务流量和服务治理更好的和端进行交互;从而完成业务、运维、生态的一体化;而通过边缘云原生,我们可以获得和云上一致的运维体验,更好的隔离性,安全性以从及效率。产品落地就顺师理成章了许多。

接下来我们介绍阿里云在商业化和开源上的边缘计算云原生实践。

5.png

阿里云 ACK@Edge 主打“云端标准管控,边缘适度自治”的服务理念;“云边端”三层结构分层明显,能力协同。第一层是中心的云原生管控能力,提供标准的云原生北向接口供上层业务集成,诸如城市大脑、工业大脑、CDN PaaS,loT PaaS 等;第二层是云边运维管控通道,有多规格、软硬多链路方案来承载云边下沉管控流量和业务流量;再往下就是关键的边缘侧,我们在原生的 K8s 能力基础上叠加了类似:边缘自治、单元化管理,流量拓扑,边缘算力状态精细化检测等能力;边云协同,从而构成了一个完整的云边管控闭环;目前,这套架构已经广泛用于 CDN、loT 等领域。

那么边缘容器到底需要哪些核心能力和业务目标呢,图中所示包括∶ 四个能力,五个目标;四个能力是边缘算力管理、边缘业务容器化管理、边缘业务高可用支持和最终要实现的边缘云原生生态;从而实现边缘算力可接入、可运维,业务可管理、编排,服务高可用,业务有生态。下面对这些核心能力的设计做简单阐述。

阿里云边缘容器产品 ACK@Edge 通过内置 SD-WAN 能力实现云上云下网络互联,业务流量互通,大大提升云边协同效率和稳定性、安全性;而通过云端资源对接,实现了云上云下的资源弹性互通,提升了边缘场景下的业务弹性能力。

6.png

第二个核心能力是边缘自治能力,在云边一体架构中,运维协同是一项重要的能力,但是通常受限于云和边缘之前的网络条件,边缘需要适当的自治能力来保证业务的连续性和持续稳定运行,换句话讲就是边缘资源和边缘业务能够在脱离云端管控的条件下继续完成业务的全生命周期管理,包括创建、启停、迁移、扩缩容等。

7.png

第三个异构资源的支持比较好理解,边缘计算区别于传统中心云计算的一个标志性特征就是计算资源、存储资源种类多样、异构明显。ACK@Edge 目前支持 arm x86 cpu 架构,支持 linux、windows 操作系统,支持将 linux 应用和 windows 应用混合部署,来很好地解决边缘场景资源异构问题。

通过配合阿里云容器镜像服务,提供在边缘场景下的多地域交付能力,能够支持多种云原生制品的多地域交付,包括容器镜像,应用编排资源包等。

8.png

这里还要和大家同步一个信息,就是上述所有核心的边缘容器能力我们都开源在边缘容器平台项目 OpenYurt 中,OpenYurt 是 CNCF 的边缘容器官方项目,是一个延伸原生 Kubernetes 到边缘计算的智能开放平台项目。

作为 ACK@Edge 的核心框架,已经服务了超百万容器实例规模,并被广泛应用于主流的边缘计算场景中;在介绍完商业和开源实践后,还有几个案例和大家分享:

9.png

第一个,是盒马鲜生基于边缘云原生实现的“人货场”数字化融合转型,通过云原生体系将多种类型的边缘异构算力统一接入、统一调度,包括∶ENS(阿里公共云边缘节点服务)、线下自建边缘网关节点, GPU 节点等。获取了强大的资源弹性和业务混编带来的灵活性;并通过 ACK@Edge 提供的边缘云原生 Al 解决方案,构建云、边、端一体化协同的天眼 Al 系统,充分发挥了边缘计算就近访问,实时处理的优势,轻松实现全方位的降本提效,门店计算资源成本节省 50%,新店开服效率提升 70%。

10.png

第二个是我们在一家视频网站客户的案例,使用 ACK@Edge 来管理跨 region、跨类型、跨地域的边缘算力,用于部署视频加速服务,通过对异构资源的支持,客户在边缘计算场景获得了强大的资源弹性能力;有一个数字分享给大家,通过边缘容器的弹性和异构资源管理能力,能够节省 50% 左右的成本。

11.png

第三个案例是 ACK@Edge 在 loT 智慧楼宇项目中的落地,其中 loT 网关设备作为边缘节点托管在 ACK@Edge 的云端管控,网关设备上的业务和楼宇的智能设备交互;网关和端设备的运维都统一收编到中心云,效率大大提升。

在和社区同学的充分讨论下,OpenYurt 社区也发布了 2021 roadmap,欢迎有兴趣的同学来一起贡献。

OpenYurt 社区 2021 roadmap:
https://github.com/openyurtio/openyurt/blob/master/docs/roadmap.md

连续三年入围 Gartner 容器竞争格局,阿里云容器服务新布局首次公开

alicloudnative阅读(2113)评论(0)

来源 | 阿里巴巴云原生公众号

近日,国际知名信息技术咨询机构 Gartner 发布 2021 年容器竞争格局报告,阿里云成为国内唯一连续三年入选的中国企业,产品丰富度与成熟度持续保持全球领先水平。

与往年相比,在 Kubernetes 支持、容器镜像、Serverless 容器、服务网格等传统维度基础上,本次报告新增了集群部署形态和管控平面两个维度,阿里云容器产品再次获得国际高度认可。

Gartner 指出容器已经发展成熟并跻身主流市场,不仅涌现了更加丰富的容器企业落地场景,同时多家容器厂商也开始并购与整合。Gartner 称为了方便更多大众行业采用容器,厂商需要不断丰富各类企业级应用镜像市场、增加对 aPaaS(应用平台即服务)的支持、实现多种云形态的部署与管控等等。

阿里云容器服务当前产品的布局,与此次 Gartner 的行业洞悉不谋而合。

阿里云为企业级容器规模化生产推出大量新品,容器服务企业版 ACK Pro 与容器镜像服务企业版 ACR EE,满足可靠性、安全性的强烈需求;同时也在 Serverless 容器、机器学习、边缘计算以及分布式云等方向提供了相应功能,并已经在如互联网、新零售、教育、医疗等企业落地。

Serverless 容器,简约管控、不简单的海量扩容

Serverless 无服务化成为日益关注的热点,其宗旨在于帮助云上运用减轻如服务器运维管理等与开发代码无关的工作量,转而更多地关注业务构建;并且于此同时还会尽可能地帮助客户优化应用启动速度。

在面向事件驱动应用的函数计算(FC)、面向微服务应用的 Serverless 应用引擎(SAE)之外,阿里云还提供了 Serverless 容器产品:Serverless Kubernetes(ASK),在提供标准的 Kubernetes 界面的同时,不但可以让用户享受到极致的弹性能力,并且实现免运维,客户可以快速创建 Serverless 集群而无需管理 Kubernetes 节点和服务器,不必精心的规划容量,也无需关心底层服务器。

Serverless Kubernetes 集群中的 Pod 基于阿里云弹性容器实例(ECI)运行在安全隔离的容器运行环境中。每个 Pod 容器实例底层通过轻量级虚拟化安全沙箱技术完全强隔离,容器实例间互不影响。基于阿里云深度优化的 Serverless 容器架构,使得 ASK 集群可以轻松获得极大的弹性能力,而不必受限于集群的节点计算容量。当前 ASK 弹性能力已经达到 30s 启动 500 运行的 Pod,单集群轻松支持 20K Pod 容量。

移动医疗设备研发商越光医疗通过 Serverless 容器进行将 AI 模型预测准确率提升到 99.99%,微博则通过 Serverless 容器实现全自动的业务弹性伸缩将业务端到端的扩容时间缩小 70%。

1.png

两大前沿应用场景,机器学习(云原生AI)与边缘计算

Gartner 认为容器技术及周边生态的发展日趋成熟,已经可以胜任如机器学习、边缘计算等新型应用场景。

1. 云原生量身定制,为 AI 工程效率提供更多优化空间

阿里云容器服务沉淀数年云原生 AI 实践场景,在异构资源管理、AI 应用生命周期管理、AI 任务调度和弹性,AI 数据管理和加速,AI 和大数据统一编排等方向提供了很多工具、基础服务和解决方案。已帮助很多客户提升 AI 生产工程效率,优化 GPU 资源利用率,加速 AI 平台建设等。并且推动 K8s 社区本身,向 AI、大数据等大规模数据计算类任务领域拓展。

好未来在线教育把大量的 AI + 教育下的重要业务服务通过阿里云容器服务部署,并在图像、语音、自然语言处理方面沉淀出覆盖“教、学、测、练、评”各教学环节的 100 余项 AI 能力、10 余项教育场景应用 AI 解决方案,不仅提高 AI 整体的开发部署效率,实现高效的 MLOPS;同时解决 AI 服务的高并发、高性能、高资源的场景下资源复用率低的顽疾,平台能力调用稳定性达 99.99%。

2.png

在刚刚结束的 2021 阿里云计算峰会上,阿里云重磅发布云原生 AI 套件,此新品提供了从底层资源运维和优化,多租户配额管理,AI 任务管理和智能调度,大数据服务集成,到支撑上层 AI 引擎加速和领域算法应用等多种能力。同时,提供可视化界面,命令行工具和 SDK,简单易用,方便扩展与集成。广大 AI 服务生产者,包括数据科学家、AI 算法工程师、AI 平台建设和运维者,都可自由组合使用各部分优化能力,选择在 Kubernetes 之上定制自己的 AI 平台。

申请获得测试资格,请点击链接填写表单:
https://page.aliyun.com/form/act181046685/index.htm

2. 边缘云原生,将 Kubernetes 运行在云之外

传统云计算中心集中存储与计算模式已经无法满足边缘设备对于时效、容量、算力的需求,如何打造边缘基础设施成了一个新课题。

云边端协同的云原生架构已然显形:在中心(云),原生的云原生管控和产品化能力得以保存,同时,海量边缘节点和边缘业务摇身一变成为云原生体系的工作负载。为了实现业务、运维、生态的一体化,通过边缘云原生提供更好的服务治理和流量管控,统一的应用管理运维体验,并且还能带来更好的隔离性、安全性与效率。

阿里云于 2019 年在业内率先发布边缘计算容器产品 ACK@Edge,主打“云端标准管控,边缘适度自治”的服务理念:在云端提供强大而丰富的云原生管控能力,向上可实现如城市大脑、工业大脑、CDNPaaS,IoTPaaS 等的业务集成,向下通过多规格、多链路方案承载流量完成云边运维管控;边缘侧,在原生的 K8s 能力基础上叠加如边缘自治、单元化管理,流量拓扑,边缘算力状态精细化检测等能力。

融创集团基于 ACK@edge 搭建社区客户服务数智化平台,统一管理道闸系统、信息大屏等社区场景和电视、智能空调、天猫精灵等家居场景两类智能设备,如对社区停车场的一站式管理,实现云边端一体化智能协同。优酷通过 ACK@Edge 统一管理其公共云上十多个 region 和众多边缘节点,通过弹性扩容节省 50% 以上的机器成本、端到端网络延迟降低 75%。

2.png

分布式云打破边界,统一而精细的管控

2020 年容器管理竞争格局的重大变化,主流云服务提供商都发布分布式云的容器管理方案。Gartner 预测 81% 的企业将采用多云/混合云战略,“大多数企业采用多云策略是为了避免供应商锁定或利用最佳解决方案“,多云战略实现企业采购的敏捷性,在可用性,性能,数据主权,和监管要求和劳动力成本得到平衡。

阿里云容器服务 ACK 2019 年 9 月份发布了混合云支持,2020 年 9 月,ACK 带来了完备的分布式云应用管理能力升级,不仅可以管理阿里云 K8s 集群之外,还可以纳管用户在 IDC 的自有 K8s 集群和其他云的 K8s 集群,实现统一的集群管理、IT 治理、安全防护、应用管理和备份管理。今年阿里云容器服务还全新发布了统一的应用中心交付能力,开发者可以通过 GitOps 方式可以将应用安全、一致、稳定地发布在多个不同的云环境中,确保整个过程安全、可控,提升应用交付效率和稳定性。

同时,阿里云容器服务 ACK 还可以与托管服务网格 ASM 结合使用 ,作为业界首个兼容 Istio 的托管式服务网格平台,服务网格 ASM 为运行在不同计算基础设施上的服务提供统一的能力,通过流量控制、网格观测以及服务间通信安全等功能,实现服务就近访问、故障转移、灰度发布等功能。全方位地简化服务治理,适用于如 Kubernetes 集群、Serverless Kubernetes 集群、ECS 虚拟机以及自建集群。

3.png

阿里云服务网格 ASM 坚持打磨“托管式、标准化、安全稳定、更易用、易扩展”的产品特性,目前已经覆盖了 12 个地域, 持续保持产品完整度、市场占有率领先,不仅对内承载了业界最大规模的网格集群,服务于阿里巴巴集团重点核心业务如电商、钉钉等,而且在公有云客户规模持续领先, 覆盖在线教育/职业培训、电商、物流、供应链、游戏以及车联网、IoT 等行业。通过服务网格技术将服务治理与业务解耦,并下沉到基础设施层, 让业务开发更聚焦于业务本身,以体系化、规范化的方式解决微服务软件架构下的多语言多编程框架及各种服务治理挑战。在超大规模的应用场景下,保证系统的稳定性的同时, 通过技术手段提升了可观测性、可诊断性、完善的服务治理能力以及性能的优化。

6 张图带你彻底搞懂分布式事务 XA 模式

alicloudnative阅读(3216)评论(0)

头图.png

作者 | 朱晋君
来源 | 阿里巴巴云原生公众号

XA 协议是由 X/Open 组织提出的分布式事务处理规范,主要定义了事务管理器 TM 和局部资源管理器 RM 之间的接口。目前主流的数据库,比如 oracle、DB2 都是支持 XA 协议的。

mysql 从 5.0 版本开始,innoDB 存储引擎已经支持 XA 协议,今天的源码介绍实验环境使用的是 mysql 数据库。

两阶段提交

分布式事务的两阶段提交是把整个事务提交分为 prepare 和 commit 两个阶段。以电商系统为例,分布式系统中有订单、账户和库存三个服务,如下图:

1.png

第一阶段,事务协调者向事务参与者发送 prepare 请求,事务参与者收到请求后,如果可以提交事务,回复 yes,否则回复 no。

第二阶段,如果所有事务参与者都回复了 yes,事务协调者向所有事务参与者发送 commit 请求,否则发送 rollback 请求。

两阶段提交存在三个问题:

  • 同步阻塞,本地事务在 prepare 阶段锁定资源,如果有其他事务也要修改 xiaoming 这个账户,就必须等待前面的事务完成。这样就造成了系统性能下降。
  • 协调节点单点故障,如果第一个阶段 prepare 成功了,但是第二个阶段协调节点发出 commit 指令之前宕机了,所有服务的数据资源处于锁定状态,事务将无限期地等待。
  • 数据不一致,如果第一阶段 prepare 成功了,但是第二阶段协调节点向某个节点发送 commit 命令时失败,就会导致数据不一致。

三阶段提交

为了解决两阶段提交的问题,三阶段提交做了改进:

  • 在协调节点和事务参与者都引入了超时机制。
  • 第一阶段的 prepare 阶段分成了两步,canCommi 和 preCommit。

如下图:

2.png

引入 preCommit 阶段后,协调节点会在 commit 之前再次检查各个事务参与者的状态,保证它们的状态是一致的。但是也存在问题,那就是如果第三阶段发出 rollback 请求,有的节点没有收到,那没有收到的节点会在超时之后进行提交,造成数据不一致。

XA 事务语法介绍

xa 事务的语法如下:

  1. 三阶段的第一阶段:开启 xa 事务,这里 xid 为全局事务 id:
XA {START|BEGIN} xid [JOIN|RESUME]

结束 xa 事务:

XA END xid [SUSPEND [FOR MIGRATE]]
  1. 三阶段的第二阶段,即 prepare:
XA PREPARE xid
  1. 三阶段的第三阶段,即 commit/rollback:
XA COMMIT xid [ONE PHASE]
XA ROLLBACK xid
  1. 查看处于 PREPARE 阶段的所有事务:
XA RECOVER XA RECOVER [CONVERT XID]

seata XA 简介

seata 是阿里推出的一款开源分布式事务解决方案,目前有 AT、TCC、SAGA、XA 四种模式。

seata 的 XA 模式是利用分支事务中数据库对 XA 协议的支持来实现的。我们看一下 seata 官网的介绍:[1]

3.png

从上面的图可以看到,seata XA 模式的流程跟其他模式一样:

  1. TM 开启全局事务
  2. RM 向 TC 注册分支事务
  3. RM 向 TC 报告分支事务状态
  4. TC 向 RM 发送 commit/rollback 请求
  5. TM 结束全局事务

这里介绍一下 RM 客户端初始化关联的 UML 类图:[2]

4.png

这个图中有一个类是 AbstractNettyRemotingClient,这个类的内部类 ClientHandler 来处理 TC 发来的请求并委托给父类 AbstractNettyRemoting 的 processMessage 方法来处理。processMessage 方法调用 RmBranchCommitProcessor 类的 process 方法。

需要注意的是,「seata 的 xa 模式对传统的三阶段提交做了优化,改成了两阶段提交」:

  • 第一阶段首执行 XA 开启、执行 sql、XA 结束三个步骤,之后直接执行 XA prepare。
  • 第二阶段执行 XA commit/rollback。

mysql 目前是支持 seata xa 模式的两阶段优化的。

「但是这个优化对 oracle 不支持,因为 oracle 实现的是标准的 xa 协议,即 xa end 后,协调节点向事务参与者统一发送 prepare,最后再发送 commit/rollback。这也导致了 seata 的 xa 模式对 oracle 支持不太好。」

seata XA 源码

seata 中的 XA 模式是使用数据源代理来实现的,需要手动配置数据源代理,代码如下:

@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DruidDataSource druidDataSource() {
    return new DruidDataSource();
}

@Bean("dataSourceProxy")
public DataSource dataSource(DruidDataSource druidDataSource) {
    return new DataSourceProxyXA(druidDataSource);
}
  • 也可以根据普通 DataSource 来创建 XAConnection,但是这种方式有兼容性问题(比如 oracle),所以 seata 使用了开发者自己配置 XADataSource。
  • seata 提供的 XA 数据源代理,要求代码框架中必须使用 druid 连接池。

1. XA 第一阶段

当 RM 收到 DML 请求后,seata 会使用 ExecuteTemplateXA来执行,执行方法 execute 中有一个地方很关键,就是把 autocommit 属性改为了 false,而 mysql 默认 autocommit 是 true。事务提交之后,还要把 autocommit 改回默认。

下面我们看一下 XA 第一阶段提交的主要代码。

1)开启 XA

上面代码标注[1]处,调用了 ConnectionProxyXA 类的 setAutoCommit 方法,这个方法的源代码中,XA start 主要做了三件事:

  • 向 TC 注册分支事务
  • 调用数据源的 XA Start
xaResource.start(this.xaBranchXid, XAResource.TMNOFLAGS);
  • 把 xaActive 设置为 true

RM 并没有直接使用 TC 返回的 branchId 作为 xa 数据源的 branchId,而是使用全局事务 id(xid) 和 branchId 重新构建了一个。

2)执行 sql

调用 PreparedStatementProxyXA 的 execute 执行 sql。

3)XA end/prepare

public void commit() throws SQLException {
    //省略部分源代码
    try {
        // XA End: Success
        xaResource.end(xaBranchXid, XAResource.TMSUCCESS);
        // XA Prepare
        xaResource.prepare(xaBranchXid);
        // Keep the Connection if necessary
        keepIfNecessary();
    } catch (XAException xe) {
        try {
            // Branch Report to TC: Failed
            DefaultResourceManager.get().branchReport(BranchType.XA, xid, xaBranchXid.getBranchId(),
                BranchStatus.PhaseOne_Failed, null);
        } catch (TransactionException te) {
            //这儿只打印了一个warn级别的日志
        }
        throw new SQLException(
            "Failed to end(TMSUCCESS)/prepare xa branch on " + xid + "-" + xaBranchXid.getBranchId() + " since " + xe
                .getMessage(), xe);
    } finally {
        cleanXABranchContext();
    }
}

从这个源码我们看到,commit 主要做了三件事:

  • 调用数据源的 XA end
  • 调用数据源的 XA prepare
  • 向 TC 报告分支事务状态

到这里我们就可以看到,seata 把 xa 协议的前两个阶段合成了一个阶段。

2. XA commit

这里的调用关系用一个时序图来表示:

5.png

看一下 RmBranchCommitProcessor 类的 process 方法,代码如下:

@Override
public void process(ChannelHandlerContext ctx, RpcMessage rpcMessage) throws Exception {
    String remoteAddress = NetUtil.toStringAddress(ctx.channel().remoteAddress());
    Object msg = rpcMessage.getBody();
    if (LOGGER.isInfoEnabled()) {
        LOGGER.info("rm client handle branch commit process:" + msg);
    }
    handleBranchCommit(rpcMessage, remoteAddress, (BranchCommitRequest) msg);
}

从调用关系时序图可以看出,上面的 handleBranchCommit 方法最终调用了 AbstractRMHandler 的 handle 方法,最后通过 branchCommit 方法调用了 ResourceManagerXA 类的 finishBranch 方法。
ResourceManagerXA 类是 XA 模式的资源管理器,看下面这个类图,也就是 seata 中资源管理器(RM)的 UML 类图:

6.png

上面的 finishBranch 方法调用了 connectionProxyXA.xaCommit 方法,我们最后看一下 xaCommit 方法:

public void xaCommit(String xid, long branchId, String applicationData) throws XAException {
    XAXid xaXid = XAXidBuilder.build(xid, branchId);
 //因为使用mysql,这里xaResource是MysqlXAConnection
    xaResource.commit(xaXid, false);
    releaseIfNecessary();
}

上面调用了数据源的 commit 方法,提交了 RM 分支事务。

到这里,整个 RM 分支事务就结束了。Rollback 的代码逻辑跟 commit 类似。

最后要说明的是,上面的 xaResource,是 mysql-connector-java.jar 包中的 MysqlXAConnection 类实例,它封装了 mysql 提供的 XA 协议接口。

总结

seata 中 XA 模式的实现是使用数据源代理完成的,底层使用了数据库对 XA 协议的原生支持。

mysql 的 java 驱动库中,MysqlXAConnection 类封装类 XA 协议的底层接口供外部调用。

跟 TCC 和 SAGA 模式需要在业务代码中实现 prepare/commit/rollback 逻辑相比,XA 模式对业务代码无侵入。

Reference

[1]:http://seata.io/zh-cn/docs/overview/what-is-seata.html
[2]:https://github.com/seata/seata

What’s new in dubbo-go v1.5.6

alicloudnative阅读(1715)评论(0)

头图.png

作者 | 铁城  dubbo-go 社区 committer
来源 | 阿里巴巴云原生公众号

dubbogo 社区近期发布了 dubbogo v1.5.6。该版本和 dubbo 2.7.8 对齐,提供了命令行工具,并提供了多种加载配置的方式。

相关改进实在太多,本文只列出相关重大 feature 和 性能提升项。

1. 命令行工具

熟悉dubbo 的朋友可能知道 dubbo 支持 telnet 命令行在线调试。

本次发布也增加了 dubbo-go 的 cli 命令行工具,可以方便用户直连特定服务,通过编写 json 文件来定义传输结构和数据,发起调用进行在线调试,打印返回数据和耗时情况。

目前支持嵌套 struct,但是只支持单个参数的请求包和回包。数据类型由于需要在 json 中定义,只支持 golang 基本数据类型:字符串、整形、浮点。

社区后续会再发一篇文章,着重讲解其原理和实现。相关 pr 为 https://github.com/apache/dubbo-go/pull/818,由 dubbogo 最年轻的 00 后 apache committer 李志信同学实现。

2. 代理实现扩展

重构 Proxy,添加 ImplementFunc 函数,允许项目对 Proxy 的代理进行重新实现。在使用 ProxyFactory 自定义注册的场景下,创建的 proxy.Proxy 也自定义实现,可以对返回数据进行修改。

主要应用场景为在网关泛化调用场景下。懂得的人自然懂。

相关 pr  https://github.com/apache/dubbo-go/pull/1019,由本文作者亲自操刀。

3. 启动时指定配置文件的路径

用户使用之前版本的 dubbogo 时,一直吐槽其只提供环境变量的方式,加载指定的配置文件。

export CONF_PROVIDER_FILE_PATH="../profiles/dev/server.yml"
export CONF_CONSUMER_FILE_PATH="../profiles/dev/server.yml"
export APP_LOG_CONF_FILE="../profiles/dev/log.yml"

v1.5.6 提供了新的配置文件加载接口:在启动命令行通过  proConf、conConf、logConf三个 flag 设定配置文件路径。

服务提供方:

go run . -proConf ../profiles/dev/server.yml -logConf ../profiles/dev/log.yml

服务消费方:

go run . -conConf ../profiles/dev/client.yml -logConf ../profiles/dev/log.yml

相关 pr https://github.com/apache/dubbo-go/pull/1039,由南京信息工程大学大三学生 陈家鹏实现。

4. 自定义加载 ServerConfig 和 ReferenceConfig

新增 ConfigPostProcessor 接口,用户可以依据该接口提供两个的方法,在部署 dubbogo 服务时加载自定义的配置。

// 服务提供方配置
PostProcessServiceConfig(*common.URL)

// 服务消费方配置
PostProcessReferenceConfig(*common.URL)

相关 pr  https://github.com/apache/dubbo-go/pull/943,由即将奔五十的 dubbo chairman 北纬亲自操刀实现,chairman 同志老当益壮,号召大家向 chairman 筒子学习。

5. 扩展 URL 的比较

在common/url.go里面提供 CompareURLEqualFunc,可以让用户自定义 URL 比较,提高比对效率。相关技术细节见如下链接。

common/url.go:
https://github.com/apache/dubbo-go/pull/854/files#diff-5111f14762c010c3029a67743796fea97ab1015d35c96670a4cfa25f30145464

目前的 URL 实现并未达到最终状态。未来的 dubbogo 3.x 版本中,将借鉴 dubbo 的 URL 实现,将 common.URL 拆分为ServiceConfigURL、ServiceAddressURL和InstanceAddressURL,分别对应配置中心、注册中心和元数据中心的 schema,尽量将变更压力降低到最低粒度。

该功能对应的 pr  https://github.com/apache/dubbo-go/pull/854 由阿里双十一中间件大队长展图同学实现。展图同学一直奋战在编程一线。

6. 注册中心优化

复用了 zookeeper 链接以及优化了服务发现中心逻辑,大大减少了与 zookeeper 的 tcp 链接数目,减少了使用的 goroutine 数目,降低了 dubbo-go 的内存占用量。

我们会把同样的逻辑服用到 nacos、etcd、consul 等各个注册中心,通过减少 goroutine 数目,减轻注册中心压力,并减少 consumer 和 provider 内存的使用。

该功能对应的 pr https://github.com/apache/dubbo-go/pull/1010 由现在蚂蚁中间件工作的 王文学 同学在涂鸦工作时实现。

7. API 形式进行配置

以前版本的 dubbogo 只提供了从配置文件读取配置选项,该功能增加以 API 的方式进行配置,用户可以通过调用相关 API 初始化配置。

用户可以通过 API 进行相关参数设定,无需再加载配置文件。

可以参考示例: https://github.com/apache/dubbo-go-samples/tree/master/config-api

相关 pr https://github.com/apache/dubbo-go/pull/1020 也是由李志信实现。

8. grpc 优化

  • 打通 dubbo-go中 consumer config 的超时时间 connect_timeout 和 gRPC server 的超时时间,用户可以自定义 gRPC 超时时间机制。
# connect timeout
connect_timeout: "3s"

# application config
application:
  organization: "dubbo.io"
  name: "GreeterGrpcConsumer"
  module: "dubbo-go greeter grpc client"
  version: "0.0.1"
  environment: "dev"
  • 将处理注册中心服务变更事件的机制改为同步,防止 provider 端服务频繁重启导致上下线事件处理顺序出错。
  • 使用 gRPC 协议时,异步等待 dubbo-go 的 service 都暴露完后,才将所有 dubbo-go service 对应的 gRPC service 注册到 gRPC server 上并启动 gRPC server。以此修复 provider 端的只能注册一个 service 的问题。

总体功能由 https://github.com/apache/dubbo-go/pull/1056 等多个 pr 构成,由 All In 了 dubbogo 的 上海识装信息科技有限公司【知名 APP 得物所在公司】工程师 柯瞻、 dubbogo 社区负责人 于雨、阿里工程师云兴 以及 南京某公司的张天同学 共同负责实现。

9. hessian2 go 最新 feature

除了  dubbo-go 自身的改进外,dubbo-go-hessian2 项目截止目前最新版本 v1.9.2 也做了如下重大改进:

dubbo-go-hessian2:https://github.com/apache/dubbo-go-hessian2

总体 pr 由 dubbo-go-hessian2 项目负责人 望哥、李志信、张艳明等同学完成。

10. 回顾与展望

dubbogo 目前处于一个比较稳定成熟的状态,1.5 版本会被持续维护,以修复 BUG 和进行一些必要的最低幅度的优化。

更多信息:https://github.com/apache/dubbo-go/releases/tag/v1.5.6

目前最新的朝云原生方向的发展3.0 版本基于 v1.5.x,在兼容 dubbo 3.0 的同时,将向后兼容 v2.7.x,计划于 4 月底发布第一个版本。

如果你有任何疑问,欢迎钉钉扫码加入交流群【钉钉群号 31363295】

作者简介

铁城 (Github ID cityiron),dubbo-go 社区 committer,主要参与 dubbo-go 1.5 版本迭代、 dubbo-go 3.0 服务路由和云原生方面工作、以及 dubbo-go-pixiu 项目负责人。目前就职于阿里盒马事业群,从事交易流程工作,擅长使用 Java/Go 语言,专注于云原生和微服务等技术方向。

独家对话阿里云函数计算负责人不瞋:你所不知道的 Serverless

alicloudnative阅读(1807)评论(0)

头图.png

作者 | 杨丽
来源 | 雷锋网(ID:leiphone-sz)

Serverless 其实离我们并没有那么遥远。

如果你是一名互联网研发人员,那么极有可能了解并应用过 Serverless 这套技术体系。纵观 Serverless 过去十年,它其实因云而生,也在同时改变云的计算方式。如果套用技术成熟度曲线来描述的话,那么它已经走过了萌芽期、认知破灭期,开始朝着成熟稳定的方向发展。未来,市场对 Serverless 的接受程度将越来越高。

不要惊讶,阿里云团队在真正开始构建 Serverless 产品体系的最开始的一两年里,也曾遭遇内部的一些争议。而今,单从阿里集团内部的很多业务线来看,已经在朝着 Serverless 化的方向发展了。

日前,阿里云凭借函数计算产品能力全球第一的优势,入选 Forrester 2021 年第一季度 FaaS 平台评估报告,成为比肩亚马逊成为全球前三的 FaaS 领导者。这也是首次有国内科技公司进入 FaaS 领导者象限。

1.png

在与雷锋网的访谈中,阿里云 Serverless 负责人不瞋阐释了 Serverless 的演进历程、引入 Serverless 面临的难点与挑战、以及有关云原生的趋势预判。

“一定要想明白做这件事的终局是什么,包括产品体系的定位,对开发者、对服务商的价值等等这些问题。这要求我们不断通过实践和认识的深化,让这些问题的回答能够逐渐清晰起来。这也是我们这么多年实践积累的宝贵经验。”不瞋指出。

尽管企业的实践还存在种种疑惑和挑战,但 Serverless 实际上离我们并没有那么遥远。举一个最近的例子,新冠疫情让远程办公、在线教育、在线游戏的应用需求短期内增加。业务规模的爆发式增长,对每一个需求的响应需要更加及时,这对应用架构的弹性,对底层计算的速度,对研发效率的提升等,都要求业务加速向新技术架构演进。

而不瞋的理想就是,帮助更广泛的客户实现向新技术架构的平滑迁移,让 Serverless 渗透到所有的云应用中。

不瞋作为阿里云 Serverless 产品体系的负责人,也是国内 Serverless 的早期实践者。以下将呈现是对这次访谈的完整总结。

Serverless 的定义

在讨论之前,我们先明确 Serverless 的定义,确保大家对 Serverless 的认知是一致的。

现在 Serverless 越来越热,无论是工业界还是学术界,都将 Serverless 视为云计算发展的下一阶段。Serverless 有很多种表述,其中伯克利大学的定义相对严谨一些。

注:2019 年 2 月,加州大学伯克利分校发表的《Cloud Programming Simplified: A Berkerley View on Serverless Computing》论文,曾在业界引发诸多讨论和关注。

大致来讲,Serverless 实际对应的是一整套的产品体系,而不是单独一两个产品;同时,这些产品/服务之间还具备以下特征:服务之间彼此配合、全托管、用户通过 API 调用就可完成整个功能或应用的开发而无需关注底层基础设施。

这套产品体系目前可分为两类:一类是计算,即 FaaS(Function as a Service);还有一类是 BaaS(Backend as a Service),比如消息中间件、对象存储,都可以看做是 Serverless 化的 BaaS 服务。

Serverless 的演进

一个新技术通常会经历几个阶段:

  • 第一个阶段,是因为其巨大潜力引起广泛关注的阶段。
  • 第二阶段,是认知破灭的阶段,在这个阶段由于产品初期本身能力不是很强健,或案例不全等因素,导致用户在使用过程中往往会遇到挫败感。
  • 第三个阶段,是伴随实践的增加和产品能力本身的发展,又会逐步提升认知,进而进入一个稳健增长的阶段。

2.png

需要明确的是,Serverless 并不是一个非常新的技术。像阿里云的 OSS、AWS 的 S3 对象存储,它们都是最早发布的产品之一,一开始其实就是 Serverless 的形态。

但业界对 Serverless 的认知,确实是因 AWS 的 Lambda 带起来的,2014 年 AWS 推出了 Lambda。

2017 年到 2019 年上半年,这段时间,业界对 Serverless 的讨论很多同时又有很多困扰,不知道如何落地,或者用了之后才突然觉得跟自己想象的不太一样。

国内外技术发展保持着相似的节奏,国外相对来讲更快一些。从去年开始,国内也开始进入到了稳定发展的阶段。现在国际上主流云供应商提供的新功能或新产品,80% 以上都是 Serverless 的形态。

阿里云从 2017 年开始打造 Serverless,并于当年正式启动商业化。

目前在阿里集团内部已经开始落地 Serverless 了,例如飞猪、淘宝、高德等等。在企业赋能方面,尤其是疫情之后,能够看到用户对 Serverless 的认知比之前确实深入了许多,在很多场景下,切换到 Serverless 架构确实能够为用户带来明显的收益,用户也认可这项技术。

举一项数据来看,目前阿里云 Serverless 已经服务了上万家付费客户,拥有 100+ 的典型案例,函数日调用量超过 120 亿次、函数总量达到 100 万。

新旧观念的转变

对于阿里云自身而言,在最开始构建 Serverless 之初,其实最大的挑战不仅仅是技术层面的,更多的还有观念上的不对称。

首先,Serverless 本身的形态跟以往的计算形态差异比较大,整个研发和运维的体系跟传统应用是割裂的。如果开发 Serverless 应用,其研发运维的流程和工具跟虚拟化(VM)或容器化的方式不太一样,很多用户会担心供应商锁定(lock-in)的问题,不太希望自身的技术栈跟某个供应商绑定

其次,AWS 的 Lambda 最开始做了一个榜样,但它也实际也只适合于 AWS 的产品体系,如果放在其他的产品体系里会面临非常大的挑战,不易于被用户接受,且限制条件也很多,应用场景也有限。这就要求在技术层面,包括资源调度、安全隔离、多租户管理、流控等方面有很高要求,做起来非常辛苦。因为在此之前没有一个产品的计算形态是如此细粒度、动态的使用资源。

这种挑战,一开始即便在阿里内部,也曾面临过许多争议。

我们这么多年实践积累的宝贵经验是:一定要想明白做这件事的终局是什么,包括在产品体系中的定位,对开发者、对云服务商的价值等等这些问题。这要求我们不断通过实践和认识的深化,让这些问题的回答能够逐渐清晰起来。

3.png

引入 Serverless 的顾虑

站在客户层面,不同类型的客户对引入第三方的 Serverless 技术其实会有不同层面的考虑。

对于超大型企业,比如 Facebook、字节跳动,企业本身就有非常强的基础设施团队,通常他们会选择自己内部开发这方面技术。

还有一些企业,没有采用 Serverless 并不是说他们对这个技术有什么抵触,而是当下的落地实践或本身的工具链还无法做到完全消除供应商锁定的问题,又或者是因为工具链跟传统开发太过割裂,企业自身无法同时维护两套开发框架

这种情况下,用户的系统架构一定会面临一个中间状态:既有老的又有新的。如果整个迁移的过程不是那么平滑的话,供应商的这部分优势在客户那里是不存在的, 因为老的系统实际是需要维护的。如此,对用户的吸引力其实就没有那么大了。

阿里云最近开源的 Serverless Devs 解决的就是这样的问题。其定位是帮助用户更简单地开发和运维自己的 Serverless 化和容器化应用,提供应用全生命周期管理的能力。

本质上,Serverless 的环境是在远端,跟用户本地开发环境是天然割裂的,那么在这个过程中,从调试、部署、发布、监控等各个环节,Serverless Devs 都希望能为用户提供更好的体验。但用户可自由使用其中一个或几个功能,不需要将已有的研发运维的流程完全迁移到我们定义的这套规范里。

过去一年的重大升级

2020 年,疫情的背景下,其实也是阿里云 Serverless 技术升级的关键一年。这一年里,团队做了很多大的升级,包括:

  • 架构层面,已经升级到神龙裸金属服务器+袋鼠安全容器的下一代架构。好处是能够带来非常高的计算密度,进一步提升弹性能力和性能。
  • 缓存方面,发布容器镜像加速技术,能够让GB级别的容器镜像非常快地实现秒级启动。目前已经演进到了下一代,通过阿里内部大规模业务场景进行打磨。
  • 运行时方面,去年阿里云重写整个语言运行时,使得更具有可扩展性,启动速度更快。

4.png
阿里云函数计算全景图

总结起来,两方面因素推动阿里云Serverless在过去一年做出重大技术升级:

一是来自用户本身的诉求。比如在教育场景中,老师对开课这件事是有时效性要求的,这就要求后台能够短时间内启动可能数千个实例进行响应。

二是来自内部对产品效能的要求。对于云服务商而言,Serverless 最核心的一个定位,是能够将云上资源更好地利用起来。整个计算架构确实需要通过新的虚拟化技术、容器技术,同时跟新的硬件结合起来,从而提供一个非常细粒度的、启动非常快、非常弹性的计算模型。这也是为什么我们要进行架构升级,从原来的虚拟机架构演进到神龙裸金属服务器+袋鼠安全容器的架构,将对整体产品的发展产生一个核心推力。

攻克下一城

阿里云采用“三位一体”的策略打造整个 Serverless 产品矩阵——自身实践-开源-商业化。即通过集团内部超大规模、超复杂的业务场景来锤炼技术,将技术不断打磨产品化,然后对云上客户提供商业化服务,在这个过程中,还会将一些技术、工具进行开源,遵循开源开放的标准,跟开源生态融合。

只有对客户的业务产生价值和帮助,客户才会认可 Serverless。

短期来看,无论是业务规模,还是产品、技术层面,阿里云 Serverless 都在以非常稳健地方式按照自身的节奏向前演进。

  • 一是业务规模会更大,预计每年会有三倍以上的增长。
  • 二是产品层面,以客户为中心,解决用户痛点仍然是首要的。今年将在产品细节体验上继续补强,在工具链、可观测性等方面为用户提供更好的体验。
  • 三是技术层面,包括计算、网络、缓存、运行时等核心部分,继续夯实技术细节,实现极致性能。

云时代的新机遇

在应用场景上来看,Serverless 不再仅仅是小程序,还有电商大促、音视频转码、AI 算法服务、游戏应用包分发、文件实时处理、物联网数据处理、微服务等场景。

Serverless 将继续和容器、微服务等生态融合,降低开发者使用 Serverless 技术的门槛,反过来也将促进传统应用的云原生化。

Serverless 另一个核心要素是“被集成”,被集成的对象有两类:

  • 一类跟一方云服务进行接入,阿里云函数计算已被 30 多个一方云服务产品集成。
  • 第二类是通过 EventBridge 事件总线和三方生态被集成。例如和钉钉等 SaaS 应用集成。钉钉的业务中常常需要以简洁、轻量的方式完成用户的定制化需求,这和 Serverless 的应用形态是高度匹配的。

5.png
不瞋,阿里云 Serverless 负责人

今天,我们可以非常明确地看到,整个云的未来一定是 Serverless 形态的。阿里云内部对这个也没有争议,因为这么多年来,整个产品体系就是朝着 Serverless 方向发展的。

不是因为有了 Serverless 计算,云才向 Serverless 演进。恰恰相反,因为云的产品体系已经向 Serverless 演进,才催生了 Serverless 计算。单纯的 Serverless 计算并不能实现很多功能,前提一定是跟其他云服务及其生态配合,才能体现出其自身的优势。

无论是工业界还是学术界,都已经认可这样一个趋势。

罗美琪和春波特的故事…

alicloudnative阅读(1589)评论(0)

头图.png

作者 | 辽天
来源 | 阿里巴巴云原生公众号

导读:rocketmq-spring 经过 6 个多月的孵化,作为 Apache RocketMQ 的子项目正式毕业,发布了第一个 Release 版本 2.0.1。这个项目是把 RocketMQ 的客户端使用 Spring Boot 的方式进行了封装,可以让用户通过简单的 annotation 和标准的 Spring Messaging API 编写代码来进行消息的发送和消费。

在项目发布阶段我们很荣幸的邀请了 Spring 社区的原创人员对我们的代码进行了 Review,通过几轮 slack 上的深入交流感受到了 Spring 团队对开源代码质量的标准,对 SpringBoot 项目细节的要求。本文是对 Review 和代码改进过程中的经验和技巧的总结,希望从事 Spring Boot 开发的同学有帮助。我们把这个过程整理成 RocketMQ 社区的贡献者罗美琪和 Spring 社区的春波特(SpringBoot)的故事。

故事的开始

故事的开始是这样的,罗美琪美眉有一套 RocketMQ 的客户端代码,负责发送消息和消费消息。早早的听说春波特小哥哥的大名,通过 Spring Boot 可以把自己客户端调用变得非常简单,只使用一些简单的注解(annotation)和代码就可以使用独立应用的方式启动,省去了复杂的代码编写和参数配置。

聪明的她参考了业界已经实现的消息组件的 Spring 实现了一个 RocketMQ Spring 客户端:

  • 需要一个消息的发送客户端,它是一个自动创建的 Spring Bean,并且相关属性要能够根据配置文件的配置自动设置, 命名它为:RocketMQTemplate, 同时让它封装发送消息的各种同步和异步的方法。
@Resourceprivate RocketMQTemplate rocketMQTemplate;
...
SendResult sendResult = rocketMQTemplate.syncSend(xxxTopic, "Hello, World!");
  • 需要消息的接收客户端,它是一个能够被应用回调的 Listener, 来将消费消息回调给用户进行相关的处理。
@Service@RocketMQMessageListener(topic = "xxx", consumerGroup = "xxx_consumer")
public class StringConsumer implements RocketMQListener<String> {
   @Override   public void onMessage(String message) {
       System.out.printf("------- StringConsumer received: %s \n", message);
   }
}

特别说明一下:这个消费客户端 Listener 需要通过一个自定义的注解@RocketMQMessageListener 来标注,这个注解的作用有两个:

  • 定义消息消费的配置参数(如: 消费的 topic, 是否顺序消费,消费组等)。
  • 可以让 spring-boot 在启动过程中发现标注了这个注解的所有 Listener, 并进行初始化,详见 ListenerContainerConfiguration 类及其实现 SmartInitializingSingleton 的接口方法 afterSingletonsInstantiated()。

通过研究发现,Spring-Boot 最核心的实现是自动化配置(auto configuration),它需要分为三个部分:

  • AutoConfiguration 类,它由 @Configuration 标注,用来创建 RocketMQ 客户端所需要的 SpringBean,如上面所提到的 RocketMQTemplate 和能够处理消费回调 Listener 的容器,每个 Listener 对应一个容器 SpringBean 来启动 MQPushConsumer,并将来将监听到的消费消息并推送给 Listener 进行回调。可参考 RocketMQAutoConfiguration.java  (编者注: 这个是最终发布的类,没有 review 的痕迹啦)。
  • 上面定义的 Configuration 类,它本身并不会“自动”配置,需要由 META-INF/spring.factories 来声明,可参考 spring.factories 使用这个 META 配置的好处是上层用户不需要关心自动配置类的细节和开关,只要 classpath 中有这个 META-INF 文件和 Configuration 类,即可自动配置。
  • 另外,上面定义的 Configuration 类,还定义了 @EnableConfiguraitonProperties 注解来引入 ConfigurationProperties 类,它的作用是定义自动配置的属性,可参考 RocketMQProperties.java,上层用户可以根据这个类里定义的属性来配置相关的属性文件(即 META-INF/application.properties 或 META-INF/application.yaml)。

故事的发展

罗美琪美眉按照这个思路开发完成了 RocketMQ SpringBoot 封装并形成了 starter 交给社区的小伙伴们试用,nice~大家使用后反馈效果不错。但是还是想请教一下专业的春波特小哥哥,看看他的意见。

春波特小哥哥相当负责地对罗美琪的代码进行了 Review, 首先他抛出了两个链接:

然后解释道:

“在 Spring Boot 中包含两个概念 – auto-configuration 和 starter-POMs,它们之间相互关联,但是不是简单绑定在一起的:

  • auto-configuration 负责响应应用程序的当前状态并配置适当的 Spring Bean。它放在用户的 CLASSPATH 中结合在 CLASSPATH 中的其它依赖就可以提供相关的功能。
  • Starter-POM 负责把 auto-configuration 和一些附加的依赖组织在一起,提供开箱即用的功能,它通常是一个 maven project,里面只是一个 POM 文件,不需要包含任何附加的 classes 或 resources。

换句话说,starter-POM 负责配置全量的 classpath,而 auto-configuration 负责具体的响应(实现);前者是 total-solution,后者可以按需使用。

你现在的系统是单一的一个 module 把 auto-configuration 和 starter-POM 混在了一起,这个不利于以后的扩展和模块的单独使用。”

罗美琪了解到了区分确实对日后的项目维护很重要,于是将代码进行了模块化:

|— rocketmq-spring-boot-parent  父 POM
|— rocketmq-spring-boot              auto-configuraiton 模块
|— rocketmq-spring-stater           starter 模块(实际上只包含一个 pom.xml 文件)
|— rocketmq-spring-samples         调用 starter 的示例样本

“很好,这样的模块结构就清晰多了”,春波特小哥哥点头,“但是这个 AutoConfiguration 文件里的一些标签的用法并不正确,帮你注释一下,另外,考虑到 Spring 官方到 2020 年 8 月 Spring Boot 1.X 不再提供支持,所以建议实现直接支持 Spring Boot 2.X。”

@Configuration
@EnableConfigurationProperties(RocketMQProperties.class)
@ConditionalOnClass(MQClientAPIImpl.class)
@Order  ~~春波特: 这个类里使用Order很不合理呵,不建议使用,完全可以通过其他方式控制runtime是Bean的构建顺序
@Slf4j
public class RocketMQAutoConfiguration {
   @Bean
   @ConditionalOnClass(DefaultMQProducer.class) ~~春波特: 属性直接使用类是不科学的,需要用(name="类全名") 方式,这样在类不在classpath时,不会抛出CNFE
   @ConditionalOnMissingBean(DefaultMQProducer.class)
   @ConditionalOnProperty(prefix = "spring.rocketmq", value = {"nameServer", "producer.group"}) ~~春波特: nameServer属性名要写成name-server [1]
   @Order(1) ~~春波特: 删掉呵   public DefaultMQProducer mqProducer(RocketMQProperties rocketMQProperties) {
       ...
   }
   @Bean
   @ConditionalOnClass(ObjectMapper.class)
   @ConditionalOnMissingBean(name = "rocketMQMessageObjectMapper") ~~春波特: 不建议与具体的实例名绑定,设计的意图是使用系统中已经存在的ObjectMapper, 如果没有,则在这里实例化一个,需要改成
    @ConditionalOnMissingBean(ObjectMapper.class)
   public ObjectMapper rocketMQMessageObjectMapper() {
       return new ObjectMapper();
   }
   @Bean(destroyMethod = "destroy")
   @ConditionalOnBean(DefaultMQProducer.class)
   @ConditionalOnMissingBean(name = "rocketMQTemplate") ~~春波特: 与上面一样
   @Order(2) ~~春波特: 删掉呵 
   public RocketMQTemplate rocketMQTemplate(DefaultMQProducer mqProducer,
       @Autowired(required = false)              ~~春波特: 删掉
       @Qualifier("rocketMQMessageObjectMapper") ~~春波特: 删掉,不要与具体实例绑定              
          ObjectMapper objectMapper) {
       RocketMQTemplate rocketMQTemplate = new RocketMQTemplate();
       rocketMQTemplate.setProducer(mqProducer);
       if (Objects.nonNull(objectMapper)) {
           rocketMQTemplate.setObjectMapper(objectMapper);
       }
       return rocketMQTemplate;
   }
   @Bean(name = RocketMQConfigUtils.ROCKETMQ_TRANSACTION_ANNOTATION_PROCESSOR_BEAN_NAME)
   @ConditionalOnBean(TransactionHandlerRegistry.class)
   @Role(BeanDefinition.ROLE_INFRASTRUCTURE) ~~春波特: 这个bean(RocketMQTransactionAnnotationProcessor)建议声明成static的,因为这个RocketMQTransactionAnnotationProcessor实现了BeanPostProcessor接口,接口里方法在调用的时候(创建Transaction相关的Bean的时候)可以直接使用这个static实例,而不要等到这个Configuration类的其他的Bean都构建好 [2]
   public RocketMQTransactionAnnotationProcessor transactionAnnotationProcessor(     
   TransactionHandlerRegistry transactionHandlerRegistry) {
     return new RocketMQTransactionAnnotationProcessor(transactionHandlerRegistry);
  }
   @Configuration  ~~春波特: 这个内嵌的Configuration类比较复杂,建议独立成一个顶级类,并且使用
   @Import在主Configuration类中引入 
   @ConditionalOnClass(DefaultMQPushConsumer.class)
   @EnableConfigurationProperties(RocketMQProperties.class)
   @ConditionalOnProperty(prefix = "spring.rocketmq", value = "nameServer") ~~春波特: name-server
   public static class ListenerContainerConfiguration implements ApplicationContextAware, InitializingBean {
      ...
      @Resource ~~春波特: 删掉这个annotation, 这个field injection的方式不推荐,建议使用setter或者构造参数的方式初始化成员变量
      private StandardEnvironment environment;
       @Autowired(required = false)  ~~春波特: 这个注解是不需要的
       public ListenerContainerConfiguration(
           @Qualifier("rocketMQMessageObjectMapper") ObjectMapper objectMapper) { ~~春波特: @Qualifier 不需要
           this.objectMapper = objectMapper;
       }

注[1]:在声明属性的时候不要使用驼峰命名法,要使用-横线分隔,这样才能支持属性名的松散规则(relaxed rules)。

注[2]:BeanPostProcessor 接口作用是:如果需要在 Spring 容器完成 Bean 的实例化、配置和其他的初始化的前后添加一些自己的逻辑处理,就可以定义一个或者多个 BeanPostProcessor 接口的实现,然后注册到容器中。为什么建议声明成 static的,春波特的英文原文:

If they don’t we basically register the post-processor at the same “time” as all the other beans in that class and the contract of BPP is that it must be registered very early on. This may not make a difference for this particular class but flagging  it as static as the side effect to make clear your BPP implementation is not supposed to drag other beans via dependency injection.

AutoConfiguration 里果真很有学问,罗美琪迅速的调整了代码,一下看起来清爽了许多。不过还是被春波特提出了两点建议:

@Configuration
public class ListenerContainerConfiguration implements ApplicationContextAware, SmartInitializingSingleton {
    private ObjectMapper objectMapper = new ObjectMapper(); ~~春波特: 性能上考虑,不要初始化这个成员变量,既然这个成员是在构造/setter方法里设置的,就不要在这里初始化,尤其是当它的构造成本很高的时候。
   private void registerContainer(String beanName, Object bean) {   Class<?> clazz = AopUtils.getTargetClass(bean);
   if(!RocketMQListener.class.isAssignableFrom(bean.getClass())){
       throw new IllegalStateException(clazz + " is not instance of " + RocketMQListener.class.getName());
   }
   RocketMQListener rocketMQListener = (RocketMQListener) bean;     RocketMQMessageListener annotation = clazz.getAnnotation(RocketMQMessageListener.class);
   validate(annotation);   ~~春波特: 下面的这种手工注册Bean的方式是Spring 4.x里提供能,可以考虑使用Spring5.0 里提供的 GenericApplicationContext.registerBean的方法,通过supplier调用new来构造Bean实例 [3]
    BeanDefinitionBuilder beanBuilder = BeanDefinitionBuilder.rootBeanDefinition(DefaultRocketMQListenerContainer.class);
   beanBuilder.addPropertyValue(PROP_NAMESERVER, rocketMQProperties.getNameServer());
   ...
   beanBuilder.setDestroyMethodName(METHOD_DESTROY);
   String containerBeanName = String.format("%s_%s", DefaultRocketMQListenerContainer.class.getName(), counter.incrementAndGet());
   DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory();
   beanFactory.registerBeanDefinition(containerBeanName, beanBuilder.getBeanDefinition());
   DefaultRocketMQListenerContainer container = beanFactory.getBean(containerBeanName, DefaultRocketMQListenerContainer.class);   ~~春波特: 你这里的启动方法是通过 afterPropertiesSet() 调用的,这个是不建议的,应该实现SmartLifecycle来定义启停方法,这样在ApplicationContext刷新时能够自动启动;并且避免了context初始化时由于底层资源问题导致的挂住(stuck)的危险
   if (!container.isStarted()) {
       try {
           container.start();
       } catch (Exception e) {
         log.error("started container failed. {}", container, e);           throw new RuntimeException(e);
       }
   }
   ...
 }
}

注[3]:使用 GenericApplicationContext.registerBean 的方式。

public final < T > void registerBean(
Class< T > beanClass, Supplier< T > supplier, BeanDefinitionCustomizer… ustomizers)

“还有,还有”,在罗美琪采纳了春波特的意见比较大地调整了代码之后,春波特哥哥又提出了 Spring Boot 特有的几个要求:

  • 使用 Spring 的 Assert 在传统的 Java 代码中我们使用 assert 进行断言,Spring Boot 中断言需要使用它自有的 Assert 类,如下示例:
import org.springframework.util.Assert;
...
Assert.hasText(nameServer, "[rocketmq.name-server] must not be null");
  • Auto Configuration 单元测试使用 Spring 2.0 提供的 ApplicationContextRunner:
public class RocketMQAutoConfigurationTest {
   private ApplicationContextRunner runner = new ApplicationContextRunner()           .withConfiguration(AutoConfigurations.of(RocketMQAutoConfiguration.class));

   @Test(expected = NoSuchBeanDefinitionException.class)   public void testRocketMQAutoConfigurationNotCreatedByDefault() {
       runner.run(context -> context.getBean(RocketMQAutoConfiguration.class));   }
   @Test
   public void testDefaultMQProducerWithRelaxPropertyName() {
       runner.withPropertyValues("rocketmq.name-server=127.0.0.1:9876",               "rocketmq.producer.group=spring_rocketmq").
               run((context) -> {
                   assertThat(context).hasSingleBean(DefaultMQProducer.class);                   assertThat(context).hasSingleBean(RocketMQProperties.class);               });
   }
  • 在 auto-configuration 模块的 pom.xml 文件里,加入 spring-boot-configuration-processor 注解处理器,这样它能够生成辅助元数据文件,加快启动时间。

详情见这里:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-developing-auto-configuration.html#boot-features-custom-starter-module-autoconfigure

最后,春波特还相当专业地向罗美琪美眉提供了如下两方面的意见:

1. 通用的规范,好的代码要易读易于维护

1)注释与命名规范

我们常用的代码注释分为多行(/ … /)和单行(// …)两种类型,对于需要说明的成员变量,方法或者代码逻辑应该提供多行注释; 有些简单的代码逻辑注释也可以使用单行注释。在注释时通用的要求是首字母大写开头,并且使用句号结尾;对于单行注释,也要求首字母大写开头*;并且不建议行尾单行注释。

在变量和方法命名时尽量用词准确,并且尽量不要使用缩写,如: sendMsgTimeout,建议写成 sendMessageTimeout;包名 supports,建议改成 support。

2)是否需要使用 Lombok

使用 Lombok 的好处是代码更加简洁,只需要使用一些注释就可省略 constructor,setter 和 getter 等诸多方法(bolierplate code);但是也有一个坏处就是需要开发者在自己的 IDE 环境配置 Lombok 插件来支持这一功能,所以 Spring 社区的推荐方式是不使用 Lombok,以便新用户可以直接查看和维护代码,不依赖 IDE 的设置。

3)对于包名(package)的控制

如果一个包目录下没有任何 class,建议要去掉这个包目录。例如:org.apache.rocketmq.spring.starter 在 spring 目录下没有具体的 class 定义,那么应该去掉这层目录(编者注: 我们最终把 package 改为 org.apache.rocketmq.spring,将 starter 下的目录和 classes 上移一层)。我们把所有 Enum 类放在包 org.apache.rocketmq.spring.enums 下,这个包命名并不规范,需要把 Enum 类调整到具体的包中,去掉 enums 包;类的隐藏,对于有些类,它只被包中的其它类使用,而不需要把具体的使用细节暴漏给最终用户,建议使用 package private 约束,例如:TransactionHandler 类。

4)不建议使用 Static Import, 虽然使用它的好处是更少的代码,坏处是破坏程序的可读性和易维护性。

2. 效率,深入代码的细节

  • static + final method:一个类的 static 方法不要结合 final,除非这个类本身是 final 并且声明 private 构造(ctor),如果两者结合以为这子类不能再(hiding)定义该方法,给将来的扩展和子类调用带来麻烦。
  • 在配置文件声明的 Bean 尽量使用构造函数或者 Setter 方法设置成员变量,而不要使用@Autowared,@Resource等方式注入。
  • 不要额外初始化无用的成员变量。
  • 如果一个方法没有任何地方调用,就应该删除;如果一个接口方法不需要,就不要实现这个接口类。

注[4]:下面的截图是有 FieldInjection 转变成构造函数设置的代码示例。

1.png

转换成:

2.png

故事的结局

罗美琪根据上述的要求调整了代码,使代码质量有了很大的提高,并且总结了 Spring Boot 开发的要点:

  • 编写前参考成熟的 spring boot 实现代码。
  • 要注意模块的划分,区分 autoconfiguration 和 starter。
  • 在编写 autoconfiguration Bean 的时候,注意 @Conditional 注解的使用;尽量使用构造器或者 setter 方法来设置变量,避免使用 Field Injection 方式;多个 Configuration Bean 可以使用 @Import 关联;使用 Spring 2.0 提供的 AutoConfigruation 测试类。
  • 注意一些细节:static 与 BeanPostProcessor;Lifecycle 的使用;不必要的成员属性的初始化等。

通过本次的 Review 工作了解到了 spring-boot 及 auto-configuration 所需要的一些约束条件,信心满满地提交了最终的代码,又可以邀请 RocketMQ 社区的小伙伴们一起使用 rocketmq-spring 功能了,广大读者可以在参考代码库查看到最后修复代码,也希望有更多的宝贵意见反馈和加强,加油!

后记

开源软件不仅仅是提供一个好用的产品,代码质量和风格也会影响到广大的开发者,活跃的社区贡献者罗美琪还在与 RocketMQ 社区的小伙伴们不断完善 spring 的代码,并邀请春波特的 Spring 社区进行宣讲和介绍,下一步将 rocketmq-spring-starter 推进到 Spring Initializr,让用户可以直接在 start.spring.io 网站上像使用其它 starter(如: Tomcat starter)一样使用 rocketmq-spring。

终于可以像使用 Docker 一样丝滑地使用 Containerd 了

KubeSphere阅读(13654)评论(0)

作者:米开朗基杨


有追求的工程师一般都是有技术洁癖的,云原生的世界更是如此,Kubernetes 虽然制定了容器运行时接口(CRI)标准,但早期能用的容器运行时只有 Docker,而 Docker 又不适配这个标准,于是给 Docker 开了后门,花了大量的精力去适配它。后来有了更多的容器运行时可以选择后,Kubernetes 就不得不重新考量要不要继续适配 Docker 了,因为每次更新 Kubelet 都要考虑与 Docker 的适配问题。

标准这个东西就是这样,我定好标准,你兼容了就一起玩,不兼容就拜拜,它就像两个人在一起的底线,你可以重,你可以丑,你也可以不完善,但是你不兼容标准就真的不能一起玩了,于是 Kubernetes 就把 Docker 踢出了群聊。

最终 Kubernetes 选择了 Containerd,时至今日 Containerd 已经变成一个工业级的容器运行时了,它足够简单、健壮,可移植性也很强。

现有 CLI 的不足

虽然 Docker 能干的事情,现在 Containerd 都能干,但 Containerd 还有一个非常明显的缺陷:CLI 不够友好。它无法像 Docker 和 Podman 一样通过一条简单的命令启动一个容器,它的两个 CLI 工具 ctrcrictl 都无法实现这么一件非常简单的需求,而这个需求是大多数人都需要的,我总不能为了在本地测试容器而专门部署一个 Kubernetes 集群吧?

ctr 的设计对人类不太友好,例如缺少以下这些和 Docker 类似的功能:

  • docker run -p <PORT>
  • docker run --restart=always
  • 通过凭证文件 ~/.docker/config.json 来拉取镜像
  • docker logs

除此之外还有一个 CLI 工具叫 crictl,和 ctr 一样不太友好。

为了解决这个痛点,Containerd 官方推出了一个新的 CLI 叫 nerdctl。nerdctl 的使用体验和 docker 一样顺滑,例如:

🐳  → nerdctl run -d -p 8080:80 --name=nginx --restart=always nginx
复制代码

nerdctl 只是 docker 的复制品?

nerdctl 的目标并不是单纯地复制 docker 的功能,它还实现了很多 docker 不具备的功能,例如延迟拉取镜像(lazy-pulling)、镜像加密(imgcrypt)等。

延迟拉取镜像功能可以参考这篇文章:Containerd 使用 Stargz Snapshotter 延迟拉取镜像

虽然这些功能预计最终也会在 Docker 中实现,但可能需要几个月甚至几年的时间,因为 Docker 目前的设计只使用一小部分 Containerd 子系统。将来 Docker 有可能重构代码以使用完整的 Containerd,但目前还没看到什么实质性进展。所以 Containerd 社区决定创建一个新的 CLI 来更友好地使用 Containerd

nerdctl 试用

你可以从 nerdctl 的 release 中下载最新的可执行文件,每一个版本都有两种可用的发行版:

  • nerdctl-<VERSION>-linux-amd64.tar.gz : 只包含 nerdctl。
  • nerdctl-full-<VERSION>-linux-amd64.tar.gz : 包含了 nerdctl 和相关依赖组件(containerd, runc, CNI, …)。

如果你已经安装了 Containerd,只需要选择前一个发行版,否则就选择完整版。

安装好 nerdctl 后,就可以使用 nerdctl 来运行容器了:

🐳  → nerdctl run -d -p 80:80 --name=nginx --restart=always nginx:alpine

docker.io/library/nginx:alpine:                                                   resolved       |++++++++++++++++++++++++++++++++++++++|
index-sha256:d33e9e24389d7d8b90fe2bcc2dd1bc09b4d235e916ba9d5d9a71cf52e340edb6:    done           |++++++++++++++++++++++++++++++++++++++|
manifest-sha256:c1f4e1974241c3f9ddb2866b2bf8e7afbceaa42dae82aabda5e946d03f054ed2: done           |++++++++++++++++++++++++++++++++++++++|
config-sha256:bfad9487e175364fd6315426feeee34bf5e6f516d2fe6a4e9b592315e330828e:   done           |++++++++++++++++++++++++++++++++++++++|
layer-sha256:29d3f97df6fd99736a0676f9e57e53dfa412cf60b26d95008df9da8197f1f366:    done           |++++++++++++++++++++++++++++++++++++++|
layer-sha256:9aae54b2144e5b2b00c610f8805128f4f86822e1e52d3714c463744a431f0f4a:    done           |++++++++++++++++++++++++++++++++++++++|
layer-sha256:a5f0adaddd5456b7c5a3753ab541b5fad750f0a6499a15f63571b964eb3e2616:    done           |++++++++++++++++++++++++++++++++++++++|
layer-sha256:5df810e1c460527fe400cdd2cab62228f5fb3da0f2dce86a6a6c354972f19b6e:    done           |++++++++++++++++++++++++++++++++++++++|
layer-sha256:345aee38d3533398e0eb7118e4323a8970f7615136f2170dfb2b0278bbd9099d:    done           |++++++++++++++++++++++++++++++++++++++|
layer-sha256:e6a4c36d7c0e358e5fc02ccdac645b18b85dcfec09d4fb5f8cbdc187ce9467a0:    done           |++++++++++++++++++++++++++++++++++++++|
elapsed: 5.7 s                                                                    total:  9.4 Mi (1.6 MiB/s)
27b55e0b18b10c4c8f34e3ba709614e7b1760a75db061d2ce5183e8b1101ce09
复制代码

查看创建的容器:

🐳  → nerdctl ps
CONTAINER ID    IMAGE                             COMMAND                   CREATED          STATUS    PORTS                 NAMES
3b5faa266a43    docker.io/library/nginx:alpine    "/docker-entrypoint.…"    3 minutes ago    Up        0.0.0.0:80->80/tcp    nginx
复制代码

和 Docker 一样,Containerd 也有一个子命令 network

🐳  → nerdctl network ls
NETWORK ID    NAME               FILE
0             bridge
              k8s-pod-network    /etc/cni/net.d/10-calico.conflist
              host
              none
复制代码

来看下默认的 bridge 配置:

🐳  → nerdctl network inspect bridge
[
    {
        "CNI": {
            "cniVersion": "0.4.0",
            "name": "bridge",
            "nerdctlID": 0,
            "plugins": [
                {
                    "type": "bridge",
                    "bridge": "nerdctl0",
                    "isGateway": true,
                    "ipMasq": true,
                    "hairpinMode": true,
                    "ipam": {
                        "type": "host-local",
                        "routes": [
                            {
                                "dst": "0.0.0.0/0"
                            }
                        ],
                        "ranges": [
                            [
                                {
                                    "subnet": "10.4.0.0/24",
                                    "gateway": "10.4.0.1"
                                }
                            ]
                        ]
                    }
                },
                {
                    "type": "portmap",
                    "capabilities": {
                        "portMappings": true
                    }
                },
                {
                    "type": "firewall"
                },
                {
                    "type": "tuning"
                }
            ]
        },
        "NerdctlID": 0
    }
]
复制代码

可以看到 network 子命令背后还是 CNI 在运作,与 docker network 子命令原理不同。

构建镜像

nerdctl 也可以和 buildkit 结合使用来构建容器镜像,需要先下载 buildkit 的可执行文件:

🐳  → wget https://github.com/moby/buildkit/releases/download/v0.8.2/buildkit-v0.8.2.darwin-amd64.tar.gz
复制代码

将其解压到 $PATH 中:

🐳  → tar -C /usr/local/ -zxvf buildkit-v0.8.2.linux-amd64.tar.gz
复制代码

编写 systemd unit 文件:

# /etc/systemd/system/buildkit.service
[Unit]
Description=BuildKit
Documentation=https://github.com/moby/buildkit

[Service]
ExecStart=/usr/local/bin/buildkitd --oci-worker=false --containerd-worker=true

[Install]
WantedBy=multi-user.target
复制代码

启用 buildkit.service 并设置开机自动运行:

🐳  → systemctl enable --now buildkit.service
复制代码

下面以 KubeSphere 项目为例,展示如何使用 nerdctl 来构建镜像。

首先克隆 KubeSphere 官方仓库:

🐳  → git clone --depth=1 https://github.com.cnpmjs.org/kubesphere/kubesphere.git
复制代码

进入仓库目录,编译二进制文件:

🐳  → cd kubesphere
🐳  → make ks-apiserver
复制代码

将二进制文件拷贝到 Dockerfile 目录:

🐳  → cp bin/cmd/ks-apiserver build/ks-apiserver
复制代码

进入 Dockerfile 目录,修改 Dockerfile:

# Copyright 2020 The KubeSphere Authors. All rights reserved.
# Use of this source code is governed by an Apache license
# that can be found in the LICENSE file.
FROM alpine:3.11

ARG HELM_VERSION=v3.5.2

RUN apk add --no-cache ca-certificates
# install helm
RUN wget https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz && \
    tar xvf helm-${HELM_VERSION}-linux-amd64.tar.gz && \
    rm helm-${HELM_VERSION}-linux-amd64.tar.gz && \
    mv linux-amd64/helm /usr/bin/ && \
    rm -rf linux-amd64
# To speed up building process, we copy binary directly from make
# result instead of building it again, so make sure you run the
# following command first before building docker image
#   make ks-apiserver
#
COPY  ks-apiserver /usr/local/bin/

EXPOSE 9090
CMD ["sh"]
复制代码

构建镜像:

🐳  → cd build/ks-apiserver

🐳  → nerdctl build -t ks-apiserver .
[+] Building 22.6s (9/9) FINISHED
 => [internal] load build definition from Dockerfile                                                                                                                                0.0s
 => => transferring dockerfile: 812B                                                                                                                                                0.0s
 => [internal] load .dockerignore                                                                                                                                                   0.0s
 => => transferring context: 2B                                                                                                                                                     0.0s
 => [internal] load metadata for docker.io/library/alpine:3.11                                                                                                                      1.0s
 => [1/4] FROM docker.io/library/alpine:3.11@sha256:bf5fa774f08a9ed2cb301e522b769d43d48124315a4ec50eae3228d03b9dc558                                                                7.9s
 => => resolve docker.io/library/alpine:3.11@sha256:bf5fa774f08a9ed2cb301e522b769d43d48124315a4ec50eae3228d03b9dc558                                                                0.0s
 => => sha256:9b794450f7b6db7c944ba1f4161edb68cb535052fe7db8ac06e613516c4a658d 2.10MB / 2.82MB                                                                                     21.4s
 => => extracting sha256:9b794450f7b6db7c944ba1f4161edb68cb535052fe7db8ac06e613516c4a658d                                                                                           0.1s
 => [internal] load build context                                                                                                                                                   1.0s
 => => transferring context: 115.87MB                                                                                                                                               1.0s
 => [2/4] RUN apk add --no-cache ca-certificates                                                                                                                                    2.7s
 => [3/4] RUN wget https://get.helm.sh/helm-v3.5.2-linux-amd64.tar.gz &&     tar xvf helm-v3.5.2-linux-amd64.tar.gz &&     rm helm-v3.5.2-linux-amd64.tar.gz &&     mv linux-amd64  4.7s
 => [4/4] COPY  ks-apiserver /usr/local/bin/                                                                                                                                        0.2s
 => exporting to oci image format                                                                                                                                                   5.9s
 => => exporting layers                                                                                                                                                             4.6s
 => => exporting manifest sha256:d7eb2a90496678d11ac5c363b7743ffe2b8e23e7071b94556a5e3231f50f5a6e                                                                                   0.0s
 => => exporting config sha256:8eb6a5187ce958e76c8d37e18221d88f25b48dd7e6672021d0fce21bb071f284                                                                                     0.0s
 => => sending tarball                                                                                                                                                              1.3s
unpacking docker.io/library/ks-apiserver:latest (sha256:d7eb2a90496678d11ac5c363b7743ffe2b8e23e7071b94556a5e3231f50f5a6e)...done
unpacking overlayfs@sha256:d7eb2a90496678d11ac5c363b7743ffe2b8e23e7071b94556a5e3231f50f5a6e (sha256:d7eb2a90496678d11ac5c363b7743ffe2b8e23e7071b94556a5e3231f50f5a6e)...done
复制代码

查看构建好的镜像:

🐳  → nerdctl images
REPOSITORY                                                   TAG       IMAGE ID        CREATED          SIZE
alpine                                                       3.11      bf5fa774f08a    3 seconds ago    2.7 MiB
ks-apiserver                                                 latest    d7eb2a904966    6 minutes ago    57.7 MiB
复制代码

关于 nerdctl 的更多用法,可以参考官方仓库的 README

总结

从行业趋势来看,Docker 已经和 Kubernetes 社区渐行渐远,以 Containerd 为代表的实现了 CRI 接口的容器运行时将会受到 Kubernetes 的青睐。但纯粹使用 Containerd 还是有诸多困扰,比如不方便通过 CLI 来创建管理容器,有了 nerdctl 这个 CLI 工具,就就可以填补 Containerd 易用性的空缺,让你在单机上也能愉快地使用 Containerd。

关于 KubeSphere

KubeSphere (https://kubesphere.io)是在 Kubernetes 之上构建的开源容器混合云,提供全栈的 IT 自动化运维的能力,简化企业的 DevOps 工作流。

KubeSphere 已被 Aqara 智能家居、本来生活、新浪、华夏银行、四川航空、国药集团、微众银行、紫金保险、中通、中国人保寿险、中国太平保险、中移金科、Radore、ZaloPay 等海内外数千家企业采用。KubeSphere 提供了开发者友好的向导式操作界面和丰富的企业级功能,包括多云与多集群管理、Kubernetes 资源管理、DevOps (CI/CD)、应用生命周期管理、微服务治理 (Service Mesh)、多租户管理、监控日志、告警通知、审计事件、存储与网络管理、GPU support 等功能,帮助企业快速构建一个强大和功能丰富的容器云平台。

 ✨ GitHubhttps://github.com/kubesphere
 💻 官网(中国站)https://kubesphere.com.cn
 👨‍💻‍ 微信群:请搜索添加群助手微信号 kubesphere

5G 和云原生时代的技术下半场,视频化是最大最新的确定性

alicloudnative阅读(1945)评论(0)

头图.png

作者 | 毕玄
来源 | 阿里巴巴云原生公众号

导读:随着 5G/ 芯片 / 区块链等等新技术的不断成熟、云计算的普及和云原生时代带来的诸多便捷,开发者和架构师们眼前的挑战也不再只是 0-1 的建设问题,技术如何更多地带来业务价值成为了一个值得讨论的话题。阿里巴巴集团研究员,阿里云智能视频云业务负责人林昊(花名毕玄),在 QCon 全球软件开发大会上曾发表了主题演讲《5G 和云原生时代的技术下半场》,以 5G 典型场景音视频为例,探讨相关技术和技术人的下半场,以下内容为演讲整理。

1.JPG

很多人可能听过,比如阿里巴巴集团董事局主席逍遥子在很多场合都会讲到,现在世界最大的确定性的变化是数字化,意思是未来大的变化多数是“数字化的加速进行”。而在数字化的趋势中,我们看到“视频化”有着更大的确定性。

5G+云原生,给业务带来什么变化

5G 跟业务系统关联性最大的两部分是延时更低、带宽更宽。

现在的主流网络比如 4G,延时大概在 10ms~100ms,它的延时范围还是比较大。而 5G 通常来讲延时会降到 1~10ms,它的目标是 10ms。那么,当延时变得越来越低、带宽变得更宽的时候,业务上我们会看到什么样的变化?

2.png

上图主要显示的是当带宽变得更大、延时变得更低的时候,会有哪些典型的案例。比如现在特别火的话题——云游戏。游戏对延时要求是特别高的,像赛车类、竞技类等,4G 网络本身已经不可能让延时再低,但在 5G 场景如果延时压到 50ms 以下,很多业务就有可能变成现实。

所以从 5G 的业务层面,我们关注的是,什么业务需要更大的带宽,什么业务需要更低的延时。

说到云原生,它确实是现在特别火的一个话题。在去年的双 11,我们说最大的变化是所有的核心系统都上云,而今年双 11 我们对外讲的是所有核心系统开始云原生化。

但我们也说,每个人心目的云原生可能都不一样,谁都不知道什么叫云原生。

对阿里来讲,我们为什么非常激进地推进云原生?我以前是负责阿里整个核心系统上云的架构师,我觉得整个业务的演进过程,最重要的是所有的业务开始从基于一个封闭自主的技术体系走向一个开放的技术体系,这便是云原生带来最重要的变化。

云原生以后,整个社会建造业务系统的自有体系会越来越开放、越来越公共化。这对很多业务创新来讲,是有很大帮助的。因为以前很多东西得自己做,但现在很多东西可能可以基于一个相对比较成熟的技术去做。就像阿里看到有一些业务在云原生化以后,对我们整个业务创新的速度、业务迭代的速度产生非常大的帮助。

最典型场景:视频

像前面说到,5G 带来低延时和大带宽,云原生带来的是走向一个开放公共的自由体系。那 5G + 云原生以后,最典型的场景到底是什么?什么样的场景对 5G 和云原生有特别大的诉求?

从目前来看,我们非常确定的是视频。因为疫情原因,今年视频好像突然就成为了整个行业特别火的业务创新以及技术创新领域。但其实视频技术已经发展很多年了,只是今年看起来再度爆发。

我想很多人有这样一些感受:以前多数业务系统里面其实是没有视频的,但现在大多数业务系统,都开始或多或少地引入视频。短视频、直播以及音视频通信是当前最火的几个场景。

我们认为从场景层面来讲,视频是非常典型的 5G+ 云原生的场景,原因是:所有做视频业务的,不管是直播业务、短视频业务、还是音视频通话业务,关注的第一要点就是体验。

做视频最重要的是体验,比如看直播是不是足够流畅、画面的清晰度怎么样,短视频亦然,音视频通话就更加是了——比如大家开视频会议最关注的是能不能听清楚对方在说什么,另外是画面够不够流畅。

所以一旦做这个业务以后,第一要关心的话题是体验,而视频业务的体验要做得好,面临的第一个问题就是视频能不能很好地分发到离各个用户比较近的一个点。

说实话,多数中小型创业公司甚至很大规模的公司都很难解决这个问题。通常来讲,为了把整个体验做得非常好,多数业务上来就需要依赖背后一张巨大的网络,而这个网络通常只有云厂商公司会提供,因为其他公司要构建这张网络是需要非常大的投入。

所以,从体验上来讲,视频是非常典型的、会更多地考虑到应该去使用云原生的服务,而不是自己从头构建

除了体验,视频业务开始做之后面临的第二个比较大的问题是成本。视频跟很多业务不一样,这些业务规模如果没有上来,付出的代价也许不是太大,可能只是做几台计算资源的机器、一点存储、一点数据库。当然,如果是做大数据和 AI,相对投入就更大一些。

但是,一做视频就会在带宽上面临非常大的挑战,因为带宽“上来就是钱”。除了带宽以外,视频稍微做大一点,还会面临存储成本,因为要存下来,而视频的文件显然比以前所有的东西都大。

有了存储以后,视频还会面临计算消耗的问题,因为可能要对视频做一些处理,比如做一些编解码或其他东西,导致计算资源整体会有比较大的消耗。所以整体来看,视频除了解决体验问题以外,还会面临巨大的成本消耗的问题。而为了解决成本问题,可能会产生各种问题。所以我们可以看到,对于很多团队来讲,基于视频的云原生服务是一个相对来讲比较好的选择。

讲下我自己的另外一个感受,我觉得视频业务是需要在基础技术领域投入非常大的技术领域。比如要让视频在分发的过程中、播放的过程中将带宽控制得更好,我们可能要去解决的问题是怎么让多数用户看到的视频画面质量不怎么改变的情况下,怎么把带宽成本降下去,控制码率。对很多公司来讲这是非常重要的,因为在大多数公司的业务中,少数视频占了最多的带宽费用,但又不能把少量视频的质量降下去。因为质量如果降下去,会影响用户体验。

3.png

为了解决这个问题,我们可能需要投入大量的人员去做编解码优化。当然开源也是有的,开源的质量也不差,但如果想在开源基础上做得更好,这个投入就非常大了。

另外大家可能也听过,在看一段视频的时候,视频内容其实是直接决定了哪些地方是需要非常清晰、哪些地方相对来讲是不那么重要的,这可能就要结合 AI 做视频内容的理解,然后做动态的编码优化,基于你感兴趣的点去做优化,背后可能涉及各种各样的团队,编解码的团队、AI 的团队、算法的团队,所以为了一点点的提升,背后可能有非常大的投入。

让延时再低些

延时变得更低到底能来什么好处,简单给大家举几个例子。

第一个是在线教育。最早的时候在线教育是录播的,老师提前录完视频然后再放出来,其他人再点开看。但对很多客户来讲,比如对家长来说这是不太能接受的,因为跟老师不能有很好的互动。后来在线教育就更希望能让老师跟学生之间有更强的实时互动,而不是录播的毫无互动。

为了做到互动,最关键的是延时。传统直播技术通常大概延时在 5 秒左右。当然,像电视直播等延时会相对长一点,但那是因为其他的要求,技术层面大概都在 5 秒范围,这是受协议约束的结果。而在线教育是希望把延时降到几百毫秒,这样音视频互动才能更好地进行。

第二个是电商,这方面阿里有非常强的感受。阿里最早做开始手淘直播的时候,也是采用比较传统的技术,场景上面临的最大问题是:主播上来告诉大家,“我要开始卖一个东西了”,然后他要上链接,还要做消息互动。但这时候有可能会出现的是:主播说话与用户观众发消息的两个过程是有延时的,但消息的延时跟视频的延时又可能不一样,消息可能在 1 秒,视频可能在 5、6 秒。

这时候就会出现消息跟视频不在同一个画面的问题——主播可能都已经切到下一场,而买家还在跟他交流上一场的问题。

所以在手淘场景里,我们不断跟手淘团队一起尽可能把延时往下推进。比如在今年双 11 里,手淘大量采用了低延时直播,大概把直播的延时降到 1 秒左右,控制在 1 秒范围内之后,我们可以看到它对整个 GMV 的转化有很大的帮助,因为主播跟观众之间有了更强的互动关系。

在所有直播体系里我们都看到了对于延时的诉求,现在直播都希望走向强互动直播,而不希望是原来那种比较单向的行为,因为观众也希望有更强的互动。

最后一个是大家疫情期间感受最为强烈的场景,视频会议。现在视频会议的延时在技术上能够做到几百毫秒,所以现在大家普遍能开视频会议。虽然以前是电话会议多一些,但现在很显然视频会议的比率在上升。毕竟任何人的交流都更加希望能看到人,而不纯粹只是电话传递的声音。

举另外一个例子,很多公司的面试到决定性或者很关键的一轮时,都会把候选人邀请到本地,然后面对面地完成这轮面试。这是因为觉得在仅通过电话面试、看不到人的情况下,很多东西是难以判断的,需要见到本人。但是有了视频会议以后,一些面试就可以无需把人邀请到现场进行。

所以延时技术在视频领域的作用是非常明显的,从几秒到几百毫秒催进了非常多视频场景的创新。

但对视频来讲,这依然不够。比如视频会议,之前一个学术机构的研究报告显示,其实像视频会议这样存在几百毫秒延时的场景,对比人跟人的当面交流,还是存在很大区别。

大家开视频会议应该都有这样的感受:在视频会议的场景下,仍然会出现抢话情况,你说了一句话,可能还没有说完对面就已经抢话,这是一定会出现的,因为人跟人当面交流的延时并没有几百毫秒。

在视频场景里,我们是有非常强的动力去思考怎么把延时往下推得更低,让大家有更真实的体验,包括现在很多公司做很多东西都是为了让大家在远程会议上,可以有跟当面交流比较接近的体验。对我们来讲,延时如果能够越来越低,是一个非常好的事情,可以在这基础上做更多业务层面的创新。

音视频传输延迟引入分析

音视频整体技术可能跟系统层面技术有一些差别,我们来看一下延时。比如直播,音视频中比较典型的场景,你拿一个手机开始拍,这是采集的过程,把一个视频影像留下来,去采集,然后编码,多数可能是在端上去做。这个延时,现在大概在 60ms 左右的范围。

采集完之后会把这个流(比如直播、摄像流)直接推到远端,多数是云端或者自己服务器端。在云端之后,通常还会做一些处理,比如直播通常要做内容审核,内容需要过一遍审核处理,有些稍微复杂点的直播可能还要做其他事情,比如加 logo,做一些镜头的剪辑和镜头的切换。

如果有多个摄像头机位,还会涉及到直播的时候选用哪个机位的问题。另外是分发,怎么把服务器端推到很多的点。然后是把客户端流拉到本地,拉完以后开始解码和播放。

4.png

从整个时间耗时看,以前是 3-5 秒的延迟,主体时间多数耗在拉流那一端,这是协议决定的。RTMP 是比较标准的协议。现在业界比较流行的低延迟直播,是把直播延迟从 3 秒推到 1 秒,推到 1 秒以后,我们给它的名词都叫低延迟直播,相比以前更低延时一点。

大家看上图中的整体优化,更多是把协议层开始做替换,现在多数公司的低延时直播都会基于 RTC 协议,就是 Google 开源的 webRTC 协议去做。可以看到,当基于 RTC 推流、RTP 分发,前面协议层都在替换,差不多可以把拉流这端开始压到 1 秒以内。现在阿里手淘的直播,整体延时在 1~1.2 秒范围,1~1.2 秒在消息类互动场景已经足够了。主播跟观众如果是用消息互动,发一条消息或者打赏什么的,大家都不会有太长的延时感觉。可以看到,这种场景下,我们可以通过协议替换把整个延时往下拉低。

但也可以看到,其实还有很多延时是整个网络造成的。如果是网络造成的,现在其实是没有太多很好的解决方案,就非常地难。而标准的 RTC 可以做到 200-300ms 的时间,就是这样一个状况。

这三种延时,除了技术层面的差别以外,另外的层面是当采用这些技术以后,整体的成本是有很大变化的。当你延时要做得越来越低的时候,其实成本是会上升非常多的。像 RTC 相比传统直播延时,有可能成本大概是在 7 倍以上。像低延时直播,现在各家公司在不断努力尽可能让这两者成本开始接近。

5.png

为了很好地控制延时,推流最重要的是协议的替换。因为协议替换以后,从 TCP 到 UDP 以后,很多东西需要自己来做了。

各视频厂商关注的最重要的指标是抗丢包,多数公司追求当丢包在 50%、60%、70% 的时候,在不同场景去满足诉求。比如视频会议如果只是为了开会,最大的诉求其实是在音频端——音频清晰度和流畅度,而画面如果有一点卡顿,我们勉强还能接受。当然,如果那个视频会议是讲 PPT,那就不能接受了,那优先级可能变成视频的清晰度。所以,不同场景需要有各种各样不同的策略。

比如大家如果去看直播场景和视频会议类型的场景,它面临最大的不同是什么呢?直播场景的话,比如我是主播,其实只要摄像头跟我、以及我跟服务器的链路整体没有太大问题,基本上观众之间互相是没什么影响,这个观众看的时候会卡,另外一个观众有可能是不卡的,因为观众之间没有什么影响。但如果是视频会议类型的场景就完全不一样了,比如现在有十个人在开会,这十个人里任何一个人,出现卡了或者视频、音频不大正常,就会影响整场会的效率。

在这样的场景里,为了要保证延时,同时又要保证流畅度的时候,抗丢包层面需要做非常多的事情,包括综合的策略。

我们去看很多音视频公司,它们很大的竞争力在于对端的适配能力。因为每个端的状况不大一样,比如有人用苹果,有人用安卓,尤其是安卓,安卓手机有无数种,每种手机的音频能力、视频能力有很大差别,还有大家所处的网络环境,比如现在连了 Wi-Fi,走动的时候可能 Wi-Fi 点会切换,还有可能从 Wi-Fi 切到 4G,这里面网络点怎么去处理也是非常关键的。

所以当整体延时越来越往下探的时候,它的技术门槛在不断地升高,我们怎么样做好卡顿的控制,是各家公司去做这类型业务上面临的最大的一个问题。

这里主要讲的关键技术,一是推流,二是分发,三是整个拉流层面为了控制延时做的一些事情。推流主要是协议层面和抗丢包,分发层面主要是背后整张网络的分发。

很多公司做视频业务,通常有几种方法,一是直接基于云厂商的 CDN 构建整张音视频网络,还有一种是基于边缘计算节点构建一张自己的音视频网络,但这都是有一个问题要解决的。不管用什么方案,都有这样一个问题解决:这么多的节点要怎么更好地调度?这涉及到非常复杂的调度问题,因为每个节点的带宽能力、计算资源能力可能不一样,怎么根据用户的情况去做整张网络的调度。

超高清是未来,但还有很多技术侧问题要解决

带宽层面,从目前来看,大家都在想 5G 带宽变大了以后,到底找谁把带宽用起来,总得有人把带宽用起来。就像 4G,其实是视频用起来的,短视频把 4G 视频带宽撑起来。现在互联网一大部分流量,主体都是视频构成的。5G 时代也是一样,我们为什么需要更大的带宽消耗,肯定要从业务侧看到很大的变化。

6.png

图中可能是大家经常看到的一些清晰度,我们现在多数场景里能看到的 720p 视频、1080 4K 和 8K。8K 其实很少看到,因为 8K 对屏幕要求非常高,基本要很大的屏才能展现 8K 的效果。

阿里曾经在几年前冬奥会的时候做过一个 demo,叫 5G+8K 看冬奥会的滑雪现场,它的运动感非常强,所以是非常明显的。而现在特别火爆的 VR/AR 是需要更高的清晰度,现在很多 VR 还是 4K,所以导致我们会觉得颗粒感很强,但当 VR 结合 8K 的时候,就会觉得颗粒感的问题好了很多,画面比较接近真实。

只有更大的带宽,我们才可能把清晰度更往前推进。关于清晰度,以前有人说,你去问很多人,他都会觉得现在的东西已经够清晰了,不需要更清晰。但当你给了他一个更清晰的东西的时候,他会发现他需要更清晰的。最典型的是,苹果推视网膜屏,当视网膜屏推出以后,大家就有了更好的体验。

现在短视频厂商也在不断推进 4K。很多人以前都觉得短视频没必要那么清楚,因为手机屏幕太小了,还不至于能看出 4K 的差别。

但从业界发展看,我们觉得这个趋势还是比较明显的,整体朝更清晰化发展,它肯定是有诉求的。而为什么现在进展比较慢?有很多原因,第一个是当清晰度要往前推进的时候,不光是后面播放侧的问题,还有很大的问题是制作侧。当然,现在很多摄像机可能是 4K,但是拍了以后怎么把 4K 视频做剪辑、处理,其实是非常复杂的,更不要说带宽消耗。带宽除了能不能放出来以外,还有一个问题是每放一次背后全部是带宽消耗,这个带宽消耗全是成本。

我们觉得超清是一个很好的发展方向,但怎么解决在超清的发展过程中面临的很多问题,是技术侧都需要关注的。

7.png

超高清技术里面涉及到很多东西,简单讲就是从视频输入开始,就是拍一段视频,然后到一段视频最后被用户看到的时候,到底我们要做些什么。

大家可能听到过一些词,比如上图里的“超分”。简单来说,就是手机拍出一段 2K 视频,怎么把它超分成 4K 的视频,让你看到一个类似 4K 的效果,这样做是为了制作端的成本问题,因为很多制作端都不具备制作超高清的能力。

另外,大家可能听过窄带高清等技术,其实是为了解决给你一段高清视频,但怎么来控制整个带宽成本的问题。如果做高清业务,成本是非常重要的。长视频就非常典型,多数长视频会提供非常多种清晰度的选择,多数公司会提供越来越清晰化和越来越好的体验,就像优酷自己,我们会提供帧享的东西去让大家能看到更好的不同的体验。

还有很多场景的问题,比如拍不同场景,航拍和运动类的视频对清晰度的要求是比较高的,尤其是运动类的视频就非常明显。阿里优酷做世界杯播放的时候,能明显地感受到,如果清晰度不够,很多时候可能连球在哪儿都不一定能看到,远景的时候是比较难的。在那段时间,大家在不断研究怎么能让这个画面变得更加清晰。

所以我觉得,对于很多公司来讲超高清技术是需要往前演进,需要解决从制作到分发、处理到播放整个链条的问题。带宽是基础,只有带宽越来越大的时候,这个东西才有可能变成现实。

因为我现在跟视频接触得比较多,从这 5G 和云原生这两个命题讲,我目前看到视频是结合最紧密的技术。

5G 带来的更多是低延时和大带宽。我们需要思考的是,当延时越来越低的时候,有可能带来什么新的业务创新,创新模式到底有什么改变。延时越来越低,在视频场景我们看到会带来越来越多业务上的变化,很多业务跟以前完全不一样了。

因为视频的成熟,在疫情期间很多事情开始转向,以前必须线下的可以转向线上业务。当整个社会技术在进步的时候,所有业务系统侧都要去思考,视频只是相对来讲可能更明显一点。另外是带宽,有什么业务对带宽的消耗越来越大。

举另外一个例子,计算资源的消耗。最早多数计算资源是用来做在线业务系统,比如交易系统等等,消耗了大量的机器。但是后来我们看到很典型的变化是大数据,大数据变成了更主力的计算资源的消耗,再后来是 AI。

其实场景都在不断变化,在所有业务场景里应该去思考延时越来越低会带来什么,然后带宽的变化会带来什么,最后是基于云更快速做业务创新的机会到底在哪里,因为云原生更重要的是,我怎么更好地、更快速地完成整个业务的迭代和创新以及尝试,可能对所有做系统结构、做系统架构技术的人来说,这是需要慢慢结合自己的业务去思考的一个话题。

如上文所讲,5G 和云原生时代的技术下半场,视频化是最新最大的确定性,从图文到视频,视频云促成了内容的视频化,从线下到线上,视频云变革了信息的交互方式。

你真的理解 Kubernetes 中的 requests 和 limits 吗?

KubeSphere阅读(7519)评论(0)

在 Kubernetes 集群中部署资源的时候,你是否经常遇到以下情形:

  1. 经常在 Kubernetes 集群种部署负载的时候不设置 CPU requests 或将 CPU requests 设置得过低(这样“看上去”就可以在每个节点上容纳更多 Pod )。在业务比较繁忙的时候,节点的 CPU 全负荷运行。业务延迟明显增加,有时甚至机器会莫名其妙地进入 CPU 软死锁等“假死”状态。
  2. 类似地,部署负载的时候,不设置内存 requests 或者内存 requests 设置得过低,这时会发现有些 Pod 会不断地失败重启。而不断重启的这些 Pod 通常跑的是 Java 业务应用。但是这些 Java 应用本地调试运行地时候明明都是正常的。
  3. 在 Kubernetes 集群中,集群负载并不是完全均匀地在节点间分配的,通常内存不均匀分配的情况较为突出,集群中某些节点的内存使用率明显高于其他节点。Kubernetes 作为一个众所周知的云原生分布式容器编排系统,一个所谓的事实上标准,其调度器不是应该保证资源的均匀分配吗?

如果在业务高峰时间遇到上述问题,并且机器已经 hang 住甚至无法远程 ssh 登陆,那么通常留给集群管理员的只剩下重启集群这一个选项。

如果你遇到过上面类似的情形,想了解如何规避相关问题或者你是 Kubernetes 运维开发人员,想对这类问题的本质一探究竟,那么请耐心阅读下面的章节。

我们会先对这类问题做一个定性分析,并给出避免此类问题的最佳实践,最后如果你对 Kubernetes requests 和 limits 的底层机制感兴趣,我们可以从源码角度做进一步地分析,做到“知其然也知其所以然”。

问题分析

  1. 对于情形 1首先我们需要知道对于 CPU 和内存这 2 类资源,他们是有一定区别的。 CPU 属于可压缩资源,其中 CPU 资源的分配和管理是 Linux 内核借助于完全公平调度算法( CFS )和 Cgroup 机制共同完成的。简单地讲,如果 Pod 中服务使用 CPU 超过设置的 CPU limits, Pod 的 CPU 资源会被限流( throttled )。对于没有设置limit的 Pod ,一旦节点的空闲 CPU 资源耗尽,之前分配的 CPU 资源会逐渐减少。

    不管是上面的哪种情况,最终的结果都是 Pod 已经越来越无法承载外部更多的请求,表现为应用延时增加,响应变慢。

  2. 对于情形 2内存属于不可压缩资源, Pod 之间是无法共享的,完全独占的,这也就意味着资源一旦耗尽或者不足,分配新的资源一定是会失败的。有的 Pod 内部进程在初始化启动时会提前开辟出一段内存空间。比如 JVM 虚拟机在启动的时候会申请一段内存空间。如果内存 requests 指定的数值小于 JVM 虚拟机向系统申请的内存,导致内存申请失败( oom-kill ),从而 Pod 出现不断地失败重启。
  3. 对于情形 3实际上在创建 Pod 的过程中,一方面, Kubernetes 需要拨备包含 CPU 和内存在内的多种资源,这里的资源均衡是包含 CPU 和内存在内的所有资源的综合考量。另一方面, Kubernetes 内置的调度算法不仅仅涉及到“最小资源分配节点”,还会把其他诸如 Pod 亲和性等因素考虑在内。并且 Kubernetes 调度基于的是资源的 requests 数值,而之所以往往观察到的是内存分布不够均衡,是因为对于应用来说,相比于其他资源,内存一般是更紧缺的一类资源。

    Kubernetes 的调度机制是基于当前的状态。比如当出现新的 Pod 进行调度时,调度程序会根据其当时对 Kubernetes 集群的资源描述做出最佳调度决定。

    但是 Kubernetes 集群是非常动态的。比如一个节点为了维护,我们先执行了驱逐操作,这个节点上的所有 Pod 会被驱逐到其他节点去,当我们维护完成后,之前的 Pod 并不会自动回到该节点上来,因为 Pod 一旦被绑定了节点是不会触发重新调度的。

最佳实践

由上面的分析我们可以看到,集群的稳定性直接决定了其上运行的业务应用的稳定性。而临时性的资源短缺往往是导致集群不稳定的主要因素。集群一旦不稳定,轻则业务应用的性能下降,重则出现相关结点不可用。

那么如何提高集群的稳定性呢?

一方面,可以通过编辑 Kubelet 配置文件来预留一部分系统资源,从而保证当可用计算资源较少时 kubelet 所在节点的稳定性。这在处理如内存和硬盘之类的不可压缩资源时尤为重要。

另一方面,通过合理地设置 Pod 的 QoS 可以进一步提高集群稳定性:不同 QoS 的 Pod 具有不同的 OOM 分数,当出现资源不足时,集群会优先 Kill 掉 Best-Effort 类型的 Pod ,其次是 Burstable 类型的 Pod ,最后是Guaranteed 类型的 Pod 。

因此,如果资源充足,可将 QoS pods 类型均设置为 Guaranteed 。用计算资源换业务性能和稳定性,减少排查问题时间和成本。同时如果想更好的提高资源利用率,业务服务也可以设置为 Guaranteed ,而其他服务根据重要程度可分别设置为 Burstable 或 Best-Effort 。

下面我们会以 Kubesphere 平台为例,演示如何方便优雅地配置 Pod 相关的资源。

KubeSphere 资源配置实践

前面我们已经了解到 Kubernetes 中requestslimits这 2 个参数的合理设置对整个集群的稳定性至关重要。而作为 Kubernetes 的发行版,KubeSphere 极大地降低了 Kubernetes 的学习门槛,配合简洁美观的 UI 界面,你会发现有效运维原来是一件如此轻松的事情。下面我们将演示如何在 KubeSphere 平台中配置容器的相关资源配额与限制。

相关概念

在进行演示之前,让我们再回顾一下 Kubernetes 相关概念。

requests 与 limits 简介

为了实现 Kubernetes 集群中资源的有效调度和充分利用, Kubernetes 采用requestslimits两种限制类型来对资源进行容器粒度的分配。每一个容器都可以独立地设定相应的requestslimits。这 2 个参数是通过每个容器 containerSpec 的 resources 字段进行设置的。一般来说,在调度的时候requests比较重要,在运行时limits比较重要。

resources:  
    requests:    
        cpu: 50m
        memory: 50Mi
   limits:    
        cpu: 100m
        memory: 100Mi

requests定义了对应容器需要的最小资源量。这句话的含义是,举例来讲,比如对于一个 Spring Boot 业务容器,这里的requests必须是容器镜像中 JVM 虚拟机需要占用的最少资源。如果这里把 Pod 的内存requests指定为 10Mi ,显然是不合理的,JVM 实际占用的内存 Xms 超出了 Kubernetes 分配给 Pod 的内存,导致 Pod 内存溢出,从而 Kubernetes 不断重启 Pod 。

limits定义了这个容器最大可以消耗的资源上限,防止过量消耗资源导致资源短缺甚至宕机。特别的,设置为 0 表示对使用的资源不做限制。值得一提的是,当设置limits而没有设置requests时,Kubernetes 默认令requests等于limits

进一步可以把requestslimits描述的资源分为 2 类:可压缩资源(例如 CPU )和不可压缩资源(例如内存)。合理地设置limits参数对于不可压缩资源来讲尤为重要。

前面我们已经知道requests参数会对最终的 Kubernetes 调度结果起到直接的显而易见的影响。借助于 Linux 内核 Cgroup 机制,limits参数实际上是被 Kubernetes 用来约束分配给进程的资源。对于内存参数而言,实际上就是告诉 Linux 内核什么时候相关容器进程可以为了清理空间而被杀死( oom-kill )。

总结一下:

  • 对于 CPU,如果 Pod 中服务使用 CPU 超过设置的limits,Pod 不会被 kill 掉但会被限制。如果没有设置 limits ,pod 可以使用全部空闲的 CPU 资源。
  • 对于内存,当一个 Pod 使用内存超过了设置的limits,Pod 中 container 的进程会被 kernel 因 OOM kill 掉。当 container 因为 OOM 被 kill 掉时,系统倾向于在其原所在的机器上重启该 container 或本机或其他重新创建一个 Pod。
  • 0 <= requests <=Node Allocatable, requests <= limits <= Infinity

Pod 的服务质量( QoS )

Kubernetes 创建 Pod 时就给它指定了下列一种 QoS 类:Guaranteed,Burstable,BestEffort。

  • Guaranteed:Pod 中的每个容器,包含初始化容器,必须指定内存和 CPU 的requestslimits,并且两者要相等。
  • Burstable:Pod 不符合 Guaranteed QoS 类的标准;Pod 中至少一个容器具有内存或 CPU requests
  • BestEffort:Pod 中的容器必须没有设置内存和 CPU requestslimits

结合结点上 Kubelet 的 CPU 管理策略,可以对指定 Pod 进行绑核操作,参见官方文档

准备工作

您需要创建一个企业空间、一个项目和一个帐户 ( ws-admin ),务必邀请该帐户到项目中并赋予 admin 角色。有关更多信息,请参见创建企业空间、项目、帐户和角色

设置项目配额( Resource Quotas )

  1. 进入项目基本信息界面,依次直接点击“项目管理 -> 编辑配额”进入项目的配额设置页面。

  1. 进入项目配额页面,为该项目分别指定requestslimits配额。

设置项目配额的有 2 方面的作用:

  • 限定了该项目下所有 pod 指定的requestslimits之和分别要小于等与这里指定的项目的总requestslimits
  • 如果在项目中创建任何一个容器没有指定requests或者limits,那么相应的资源会创建报错,并会以事件的形式给出报错提示。

可以看到,设定项目配额以后,在该项目中创建任何容器都需要指定requestslimits,隐含实现了所谓的“code is law”,即人人都需要遵守的规则。

Kubesphere 中的项目配额等价于 Kubernetes 中的 resource quotas ,项目配额除了能够以项目为单位管理 CPU 和内存的使用使用分配情况,还能够管理其他类型的资源数目等,详细信息参见资源配额

设置容器资源的默认请求

上面我们已经讨论过项目中开启了配额以后,那么之后创建的 Pod 必须明确指定相应的 requests 和 limits 。事实上,在实际的测试或者生产环境当中,大部分 Pod 的 requests 和 limits 是高度相近甚至完全相同的。

有没有办法在项目中,事先设定好默认的缺省 requests 和 limits ,当用户没有指定容器的 requests 和 limits 时,直接应用默认值,若 Pod 已经指定 requests 和 limits 是否直接跳过呢?答案是肯定的。

  1. 进入项目基本信息界面,依次直接点击“项目管理 -> 编辑资源默认请求”进入项目的默认请求设置页面。

  1. 进入项目配额页面,为该项目分别指定 CPU 和内存的默认值。

KubeSphere 中的项目容器资源默认请求是借助于 Kubernetes 中的 Limit Ranges ,目前 KubeSphere 支持 CPU 和内存的requestslimits的默认值设定。

前面我们已经了解到,对于一些关键的业务容器,通常其流量和负载相比于其他 Pod 都是比较高的,对于这类容器的requestslimits需要具体问题具体分析。

分析的维度是多个方面的,例如该业务容器是 CPU 密集型的,还是 IO 密集型的。是单点的还是高可用的,这个服务的上游和下游是谁等等。

另一方面,在生产环境中这类业务容器的负载从时间维度看的话,往往是具有周期性的。因此,业务容器的历史监控数据可以在参数设置方面提供重要的参考价值。

而 KubeSphere 在最初的设计中,就已经在架构层面考虑到了这点,将 Prometheus 组件无缝集成到 KubeSphere 平台中,并提供纵向上至集群层级,下至 Pod 层级的完整的监控体系。横向涵盖 CPU ,内存,网络,存储等。

一般,requests值可以设定为历史数据的均值,而limits要大于历史数据的均值,最终数值还需要结合具体情况做一些小的调整。

源码分析

前面我们从日常 Kubernetes 运维出发,描述了由于 requests 和 limits参数配置不当而引起的一系列问题,阐述了问题产生的原因并给出的最佳实践。

下面我们将深入到 Kubernetes 内部,从代码里表征的逻辑关系来进一步分析和验证上面给出的结论。

requests 是如何影响 Kubernetes 调度决策的?

我们知道在 Kubernetes 中 Pod 是最小的调度单位,Pod 的requests与 Pod 内容器的requests关系如下:

func computePodResourceRequest(pod *v1.Pod) *preFilterState {
	result := &preFilterState{}
	for _, container := range pod.Spec.Containers {
		result.Add(container.Resources.Requests)
	}

	// take max_resource(sum_pod, any_init_container)
	for _, container := range pod.Spec.InitContainers {
		result.SetMaxResource(container.Resources.Requests)
	}

	// If Overhead is being utilized, add to the total requests for the pod
	if pod.Spec.Overhead != nil && utilfeature.DefaultFeatureGate.Enabled(features.PodOverhead) {
		result.Add(pod.Spec.Overhead)
	}

	return result
}
...
func (f *Fit) PreFilter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod) *framework.Status {
	cycleState.Write(preFilterStateKey, computePodResourceRequest(pod))
	return nil
}
...
func getPreFilterState(cycleState *framework.CycleState) (*preFilterState, error) {
	c, err := cycleState.Read(preFilterStateKey)
	if err != nil {
		// preFilterState doesn't exist, likely PreFilter wasn't invoked.
		return nil, fmt.Errorf("error reading %q from cycleState: %v", preFilterStateKey, err)
	}

	s, ok := c.(*preFilterState)
	if !ok {
		return nil, fmt.Errorf("%+v  convert to NodeResourcesFit.preFilterState error", c)
	}
	return s, nil
}
...
func (f *Fit) Filter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
	s, err := getPreFilterState(cycleState)
	if err != nil {
		return framework.NewStatus(framework.Error, err.Error())
	}

	insufficientResources := fitsRequest(s, nodeInfo, f.ignoredResources, f.ignoredResourceGroups)

	if len(insufficientResources) != 0 {
		// We will keep all failure reasons.
		failureReasons := make([]string, 0, len(insufficientResources))
		for _, r := range insufficientResources {
			failureReasons = append(failureReasons, r.Reason)
		}
		return framework.NewStatus(framework.Unschedulable, failureReasons...)
	}
	return nil
}

从上面的源码中不难看出,调度器(实际上是 Schedule thread )首先会在 Pre filter 阶段计算出待调度 Pod 所需要的资源,具体讲就是从 Pod Spec 中分别计算初始容器和工作容器requests之和,并取其较大者,特别地,对于像 Kata-container 这样的微虚机,其自身的虚拟化开销相比于容器来说是不能忽略不计的,所以还需要加上虚拟化本身的资源开销,计算出的结果存入到缓存中,在紧接着的 Filter 阶段,会遍历所有节点过滤出符合条件的节点。

在过滤出所有符合条件的节点以后,如果当前满足的条件的节点只有一个,那么该 Pod 随后将被调度到该节点。但是更多的情况下,此时过滤之后符合条件的节点往往有多个,这时候就需要进入 Score 阶段,依次对这些节点进行打分( Score )。而打分本身也是包括多个维度,通过内置 plugin 的形式综合评判的。值得注意的是,前面我们定义的 Pod 的requestslimits参数也会直接影响到NodeResourcesLeastAllocated算法最终的计算结果。源码如下:

func leastResourceScorer(resToWeightMap resourceToWeightMap) func(resourceToValueMap, resourceToValueMap, bool, int, int) int64 {
	return func(requested, allocable resourceToValueMap, includeVolumes bool, requestedVolumes int, allocatableVolumes int) int64 {
		var nodeScore, weightSum int64
		for resource, weight := range resToWeightMap {
			resourceScore := leastRequestedScore(requested[resource], allocable[resource])
			nodeScore += resourceScore * weight
			weightSum += weight
		}
		return nodeScore / weightSum
	}
}
...
func leastRequestedScore(requested, capacity int64) int64 {
	if capacity == 0 {
		return 0
	}
	if requested > capacity {
		return 0
	}

	return ((capacity - requested) * int64(framework.MaxNodeScore)) / capacity
}

可以看到在NodeResourcesLeastAllocated算法中,对于同一个 Pod ,目标节点的资源越充裕,那么该节点的得分也就越高。换句话说,同一个 Pod 更倾向于调度到资源充足的节点。

需要注意的是,实际上在创建 Pod 的过程中,一方面, Kubernetes 需要拨备包含 CPU 和内存在内的多种资源。每种资源都会对应一个权重(对应源码中的 resToWeightMap 数据结构),所以这里的资源均衡是包含 CPU 和内存在内的所有资源的综合考量。另一方面,在 Score 阶段,除了NodeResourcesLeastAllocated算法以外,调用器还会使用到其他算法(例如InterPodAffinity)进行分数的评定。

注:在 Kubernetes 调度器中,会把调度过程分为若干个阶段,即 Pre filter, Filter, Post filter, Score 等。在 Pre filter 阶段,用于选择符合 Pod Spec 描述的 Nodes 。

QoS 是如何影响 Kubernetes 调度决策的?

QOS 作为 Kubernetes 中一种资源保护机制,主要是针对不可压缩资源的一种控制技术。比如在内存中其通过为不同的 Pod 和容器构造 OOM 评分,并且通过内核的策略的辅助,从而实现当节点内存资源不足的时候,内核可以按照策略的优先级,优先 kill 掉优先级比较低(分值越高优先级越低)的 Pod。相关源码如下:

func GetContainerOOMScoreAdjust(pod *v1.Pod, container *v1.Container, memoryCapacity int64) int {
	if types.IsCriticalPod(pod) {
		// Critical pods should be the last to get killed.
		return guaranteedOOMScoreAdj
	}

	switch v1qos.GetPodQOS(pod) {
	case v1.PodQOSGuaranteed:
		// Guaranteed containers should be the last to get killed.
		return guaranteedOOMScoreAdj
	case v1.PodQOSBestEffort:
		return besteffortOOMScoreAdj
	}

	// Burstable containers are a middle tier, between Guaranteed and Best-Effort. Ideally,
	// we want to protect Burstable containers that consume less memory than requested.
	// The formula below is a heuristic. A container requesting for 10% of a system's
	// memory will have an OOM score adjust of 900. If a process in container Y
	// uses over 10% of memory, its OOM score will be 1000. The idea is that containers
	// which use more than their request will have an OOM score of 1000 and will be prime
	// targets for OOM kills.
	// Note that this is a heuristic, it won't work if a container has many small processes.
	memoryRequest := container.Resources.Requests.Memory().Value()
	oomScoreAdjust := 1000 - (1000*memoryRequest)/memoryCapacity
	// A guaranteed pod using 100% of memory can have an OOM score of 10. Ensure
	// that burstable pods have a higher OOM score adjustment.
	if int(oomScoreAdjust) < (1000 + guaranteedOOMScoreAdj) {
		return (1000 + guaranteedOOMScoreAdj)
	}
	// Give burstable pods a higher chance of survival over besteffort pods.
	if int(oomScoreAdjust) == besteffortOOMScoreAdj {
		return int(oomScoreAdjust - 1)
	}
	return int(oomScoreAdjust)
}

总结

Kubernetes 是一个具有良好移植和扩展性的开源平台,用于管理容器化的工作负载和服务。 Kubernetes 拥有一个庞大且快速增长的生态系统,已成为容器编排领域的事实标准。但是也不可避免地引入许多复杂性。

而 KubeSphere 作为国内唯一一个开源的 Kubernetes 发行版,极大地降低了使用 Kubernetes 的门槛。借助于 KubeSphere 平台,原先需要通过后台命令行和 yaml 文件管理的系统配置,现在只需要在简洁美观的 UI 界面上轻松完成。

本文从云原生应用部署阶段requestslimits的设置问题切入,分析了相关 Kubernetes 底层的工作原理以及如何通过 KubeSphere 平台简化相关的运维工作。

参考文献

关于 KubeSphere

KubeSphere (https://kubesphere.io)是在 Kubernetes 之上构建的开源容器混合云,提供全栈的 IT 自动化运维的能力,简化企业的 DevOps 工作流。

KubeSphere 已被 Aqara 智能家居、本来生活、新浪、华夏银行、四川航空、国药集团、微众银行、紫金保险、中通、中国人保寿险、中国太平保险、中移金科、Radore、ZaloPay 等海内外数千家企业采用。KubeSphere 提供了开发者友好的向导式操作界面和丰富的企业级功能,包括多云与多集群管理、Kubernetes 资源管理、DevOps (CI/CD)、应用生命周期管理、微服务治理 (Service Mesh)、多租户管理、监控日志、告警通知、审计事件、存储与网络管理、GPU support 等功能,帮助企业快速构建一个强大和功能丰富的容器云平台。

 ✨ GitHubhttps://github.com/kubesphere
 💻 官网(中国站)https://kubesphere.com.cn
 👨‍💻‍ 微信群:请搜索添加群助手微信号 kubesphere

基于 RocketMQ Prometheus Exporter 打造定制化 DevOps 平台

alicloudnative阅读(2525)评论(0)

头图.png

作者 | 陈厚道  冯庆
来源 | 阿里巴巴云原生公众号

导读:本文将对 RocketMQ-Exporter 的设计实现做一个简单的介绍,读者可通过本文了解到 RocketMQ-Exporter 的实现过程,以及通过 RocketMQ-Exporter 来搭建自己的 RocketMQ 监控系统。RocketMQ 在线可交互教程现已登录知行动手实验室,PC 端登录 start.aliyun.com 即可直达。

RocketMQ 云原生系列文章:

RocketMQ-Exporter 项目的 GitHub 地址:
https://github.com/apache/rocketmq-exporter

文章主要内容包含以下几个方面:

  1. RocketMQ 介绍
  2. Prometheus 简介
  3. RocketMQ-Exporter 的具体实现
  4. RocketMQ-Exporter 的监控指标和告警指标
  5. RocketMQ-Exporter 使用示例

RocketMQ 介绍

RocketMQ 是一个分布式消息和流数据平台,具有低延迟、高性能、高可靠性、万亿级容量和灵活的可扩展性。简单的来说,它由 Broker 服务器和客户端两部分组成,其中客户端一个是消息发布者客户端(Producer),它负责向 Broker 服务器发送消息;另外一个是消息的消费者客户端(Consumer),多个消费者可以组成一个消费组,来订阅和拉取消费 Broker 服务器上存储的消息。

正由于它具有高性能、高可靠性和高实时性的特点,与其他协议组件在 MQTT 等各种消息场景中的结合也越来越多,应用越来越广泛。而对于这样一个强大的消息中间件平台,在实际使用的时候还缺少一个监控管理平台。

当前在开源界,使用最广泛监控解决方案的就是 Prometheus。与其它传统监控系统相比较,Prometheus 具有易于管理,监控服务的内部运行状态,强大的数据模型,强大的查询语言 PromQL,高效的数据处理,可扩展,易于集成,可视化,开放性等优点。并且借助于 Prometheus 可以很快速的构建出一个能够监控 RocketMQ 的监控平台。

Prometheus 简介

下图展示了 Prometheus 的基本架构:

1.png

1. Prometheus Server

Prometheus Server 是 Prometheus 组件中的核心部分,负责实现对监控数据的获取,存储以及查询。Prometheus Server 可以通过静态配置管理监控目标,也可以配合使用 Service Discovery 的方式动态管理监控目标,并从这些监控目标中获取数据。其次 Prometheus Server 需要对采集到的监控数据进行存储,Prometheus Server 本身就是一个时序数据库,将采集到的监控数据按照时间序列的方式存储在本地磁盘当中。最后 Prometheus Server 对外提供了自定义的 PromQL 语言,实现对数据的查询以及分析。

2. Exporters

Exporter 将监控数据采集的端点通过 HTTP 服务的形式暴露给 Prometheus Server,Prometheus Server 通过访问该 Exporter 提供的 Endpoint 端点,即可获取到需要采集的监控数据。RocketMQ-Exporter 就是这样一个 Exporter,它首先从 RocketMQ 集群采集数据,然后借助 Prometheus 提供的第三方客户端库将采集的数据规范化成符合 Prometheus 系统要求的数据,Prometheus 定时去从 Exporter 拉取数据即可。

当前 RocketMQ Exporter 已被 Prometheus 官方收录,其地址为:https://github.com/apache/rocketmq-exporter

2.png

RocketMQ-Exporter 的具体实现

当前在 Exporter 当中,实现原理如下图所示:

3.png

整个系统基于 spring boot 框架来实现。由于 MQ 内部本身提供了比较全面的数据统计信息,所以对于 Exporter 而言,只需要将 MQ 集群提供的统计信息取出然后进行加工而已。所以 RocketMQ-Exporter 的基本逻辑是内部启动多个定时任务周期性的从 MQ 集群拉取数据,然后将数据规范化后通过端点暴露给 Prometheus 即可。其中主要包含如下主要的三个功能部分:

  • MQAdminExt 模块通过封装 MQ 系统客户端提供的接口来获取 MQ 集群内部的统计信息。
  • MetricService 负责将 MQ 集群返回的结果数据进行加工,使其符合 Prometheus 要求的格式化数据。
  • Collect 模块负责存储规范化后的数据,最后当 Prometheus 定时从 Exporter 拉取数据的时候,Exporter 就将 Collector 收集的数据通过 HTTP 的形式在/metrics 端点进行暴露。

RocketMQ-Exporter 的监控指标和告警指标

RocketMQ-Exporter 主要是配合 Prometheus 来做监控,下面来看看当前在 Expoter 中定义了哪些监控指标和告警指标。

  • 监控指标

4.jpg

rocketmq_message_accumulation 是一个聚合指标,需要根据其它上报指标聚合生成。

  • 告警指标

5.jpg

消费者堆积告警指标也是一个聚合指标,它根据消费堆积的聚合指标生成,value 这个阈值对每个消费者是不固定的,当前是根据过去 5 分钟生产者生产的消息数量来定,用户也可以根据实际情况自行设定该阈值。告警指标设置的值只是个阈值只是象征性的值,用户可根据在实际使用 RocketMQ 的情况下自行设定。这里重点介绍一下消费者堆积告警指标,在以往的监控系统中,由于没有像 Prometheus 那样有强大的 PromQL 语言,在处理消费者告警问题时势必需要为每个消费者设置告警,那这样就需要 RocketMQ 系统的维护人员为每个消费者添加,要么在系统后台检测到有新的消费者创建时自动添加。在 Prometheus 中,这可以通过一条如下的语句来实现:

(sum(rocketmq_producer_offset) by (topic) - on(topic)  group_right  sum(rocketmq_consumer_offset) by (group,topic)) 
- ignoring(group) group_left sum (avg_over_time(rocketmq_producer_tps[5m])) by (topic)*5*60 > 0

借助 PromQL 这一条语句不仅可以实现为任意一个消费者创建消费告警堆积告警,而且还可以使消费堆积的阈值取一个跟生产者发送速度相关的阈值。这样大大增加了消费堆积告警的准确性。

RocketMQ-Exporter 使用示例

1. 启动 NameServer 和 Broker

要验证 RocketMQ 的 Spring-Boot 客户端,首先要确保 RocketMQ 服务正确的下载并启动。可以参考 RocketMQ 主站的快速开始来进行操作。确保启动 NameServer 和 Broker 已经正确启动。

2. 编译 RocketMQ-Exporter

用户当前使用,需要自行下载 git 源码编译:

git clone https://github.com/apache/rocketmq-exporter
cd rocketmq-exporter
mvn clean install

3. 配置和运行

RocketMQ-Exporter 有如下的运行选项:

6.jpg

以上的运行选项既可以在下载代码后在配置文件中更改,也可以通过命令行来设置。

编译出来的 jar 包就叫 rocketmq-exporter-0.0.1-SNAPSHOT.jar,可以通过如下的方式来运行。

java -jar rocketmq-exporter-0.0.1-SNAPSHOT.jar [--rocketmq.config.namesrvAddr="127.0.0.1:9876" ...]

4. 安装 Prometheus

首先到 Prometheus 官方下载地址去下载 Prometheus 安装包,当前以 linux 系统安装为例,选择的安装包为 prometheus-2.7.0-rc.1.linux-amd64.tar.gz,经过如下的操作步骤就可以启动 prometheus 进程。

tar -xzf prometheus-2.7.0-rc.1.linux-amd64.tar.gzcd prometheus-2.7.0-rc.1.linux-amd64/./prometheus --config.file=prometheus.yml --web.listen-address=:5555

Prometheus 默认监听端口号为 9090,为了不与系统上的其它进程监听端口冲突,我们在启动参数里面重新设置了监听端口号为 5555。然后通过浏览器访问 http://&lt;服务器 IP 地址>:5555,就可以验证 Prometheus 是否已成功安装,显示界面如下:

7.png

由于 RocketMQ-Exporter 进程已启动,这个时候可以通过 Prometheus 来抓取 RocketMQ-Exporter 的数据,这个时候只需要更改 Prometheus 启动的配置文件即可。

整体配置文件如下:

# my global config
global:
   scrape_interval:     15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
   evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
   # scrape_timeout is set to the global default (10s).
 
 
 # Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
 rule_files:
   # - "first_rules.yml"
   # - "second_rules.yml"
   

 scrape_configs:
   - job_name: 'prometheus'
     static_configs:
     - targets: ['localhost:5555']
   
   
   - job_name: 'exporter'
     static_configs:
     - targets: ['localhost:5557']

更改配置文件后,重启服务即可。重启后就可以在 Prometheus 界面查询 RocketMQ-Exporter 上报的指标,例如查询 rocketmq_broker_tps 指标,其结果如下:

8.png

5. 告警规则添加

在 Prometheus 可以展示 RocketMQ-Exporter 的指标后,就可以在 Prometheus 中配置 RocketMQ 的告警指标了。在 Prometheus 的配置文件中添加如下的告警配置项,*.rules 表示可以匹配多个后缀为 rules 的文件。

rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml" 
  - /home/prometheus/prometheus-2.7.0-rc.1.linux-amd64/rules/*.rules

当前设置的告警配置文件为 warn.rules,其文件具体内容如下所示。其中的阈值只起一个示例的作用,具体的阈值还需用户根据实际使用情况来自行设定。

###
# Sample prometheus rules/alerts for rocketmq.
#
###
# Galera Alerts

groups:
- name: GaleraAlerts
  rules:
  - alert: RocketMQClusterProduceHigh
    expr: sum(rocketmq_producer_tps) by (cluster) >= 10
    for: 3m
    labels:
      severity: warning
    annotations:
      description: '{{$labels.cluster}} Sending tps too high.'
      summary: cluster send tps too high
  - alert: RocketMQClusterProduceLow
    expr: sum(rocketmq_producer_tps) by (cluster) < 1
    for: 3m
    labels:
      severity: warning
    annotations:
      description: '{{$labels.cluster}} Sending tps too low.'
      summary: cluster send tps too low
  - alert: RocketMQClusterConsumeHigh
    expr: sum(rocketmq_consumer_tps) by (cluster) >= 10
    for: 3m
    labels:
      severity: warning
    annotations:
      description: '{{$labels.cluster}} consuming tps too high.'
      summary: cluster consume tps too high
  - alert: RocketMQClusterConsumeLow
    expr: sum(rocketmq_consumer_tps) by (cluster) < 1
    for: 3m
    labels:
      severity: warning
    annotations:
      description: '{{$labels.cluster}} consuming tps too low.'
      summary: cluster consume tps too low
  - alert: ConsumerFallingBehind
    expr: (sum(rocketmq_producer_offset) by (topic) - on(topic)  group_right  sum(rocketmq_consumer_offset) by (group,topic)) - ignoring(group) group_left sum (avg_over_time(rocketmq_producer_tps[5m])) by (topic)*5*60 > 0
    for: 3m
    labels:
      severity: warning
    annotations:
      description: 'consumer {{$labels.group}} on {{$labels.topic}} lag behind
        and is falling behind (behind value {{$value}}).'
      summary: consumer lag behind
  - alert: GroupGetLatencyByStoretime
    expr: rocketmq_group_get_latency_by_storetime > 1000
    for: 3m
    labels:
      severity: warning
    annotations:
      description: 'consumer {{$labels.group}} on {{$labels.broker}}, {{$labels.topic}} consume time lag behind message store time
        and (behind value is {{$value}}).'
      summary: message consumes time lag behind message store time too much 

最终,可以在 Prometheus 的看一下告警展示效果,红色表示当前处于告警状态的项,绿色表示正常状态。

9.png

6. Grafana dashboard for RocketMQ

Prometheus 自身的指标展示平台没有当前流行的展示平台 Grafana 好, 为了更好的展示 RocketMQ 的指标,可以使用 Grafana 来展示 Prometheus 获取的指标。

首先到官网去下载:https://grafana.com/grafana/download,这里仍以二进制文件安装为例进行介绍。

wget https://dl.grafana.com/oss/release/grafana-6.2.5.linux-amd64.tar.gz 
tar -zxvf grafana-6.2.5.linux-amd64.tar.gz
cd grafana-5.4.3/

同样为了不与其它进程的使用端口冲突,可以修改 conf 目录下的 defaults.ini 文件的监听端口,当前将 grafana 的监听端口改为 55555,然后使用如下的命令启动即可:

./bin/grafana-server web

然后通过浏览器访问 http://&lt;服务器 IP 地址>:55555,就可以验证 grafana 是否已成功安装。系统默认用户名和密码为 admin/admin,第一次登陆系统会要求修改密码,修改密码后登陆,界面显示如下:

10.png

点击 Add data source 按钮,会要求选择数据源。

11.png

选择数据源为 Prometheus,设置数据源的地址为前面步骤启动的 Prometheus 的地址。

12.png

回到主界面会要求创建新的 Dashboard。

13.png

点击创建 dashboard,创建 dashboard 可以自己手动创建,也可以以配置文件导入的方式创建,当前已将 RocketMQ 的 dashboard 配置文件上传到 Grafana 的官网,这里以配置文件导入的方式进行创建。

14.png

点击 New dashboard 下拉按钮。

15.png

选择 import dashboard。

16.png

这个时候可以到 Grafana 官网去下载当前已为 RocketMQ 创建好的配置文件,地址为:https://grafana.com/dashboards/10477/revisions,如下图所示:

17.png

点击 download 就可以下载配置文件,下载配置文件然后,复制配置文件中的内容粘贴到上图的粘贴内容处。

最后按上述方式就将配置文件导入到 Grafana 了。

18.png

最终的效果如下所示:

19.png

作者简介

陈厚道,曾就职于腾讯、盛大、斗鱼等互联网公司。目前就职于尚德机构,在尚德机构负责基础架构方面的设计和开发工作。对分布式消息队列、微服务架构和落地、DevOps 和监控平台有比较深入的研究。

冯庆,曾就职于华为。目前就职于尚德机构,在尚德机构基础架构团队负责基础组件的开发工作。

20.png

在 PC 端登录 start.aliyun.com 知行动手实验室,沉浸式体验在线交互教程

重磅官宣:Nacos2.0 发布,性能提升 10 倍

alicloudnative阅读(2235)评论(0)

头图.png

作者 | 席翁
来源 | 阿里巴巴云原生公众号

继 Nacos 1.0 发布以来,Nacos 迅速被成千上万家企业采用,并构建起强大的生态。但是随着用户深入使用,逐渐暴露一些性能问题,因此我们启动了 Nacos 2.0 的隔代产品设计,时隔半年我们终于将其全部实现,实测性能提升 10 倍,相信能满足所有用户的性能需求。下面由我代表社区为大家介绍一下这款跨代产品。

Nacos 简介

Nacos 是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。它孵化于阿里巴巴,成长于十年双十一的洪峰考验,沉淀了简单易用、稳定可靠、性能卓越的核心竞争力。

1.png

Nacos 2.0 架构

全新 2.0 架构不仅将性能大幅提升 10 倍,而且内核进行了分层抽象,并且实现插件扩展机制。

Nacos 2.0 架构层次如下图,它相比Nacos1.X的最主要变化是:

  • 通信层统一到 gRPC 协议,同时完善了客户端和服务端的流量控制和负载均衡能力,提升的整体吞吐。
  • 将存储和一致性模型做了充分抽象分层,架构更简单清晰,代码更加健壮,性能更加强悍。
  • 设计了可拓展的接口,提升了集成能力,如让用户扩展实现各自的安全机制。

2.png

1. Nacos2.0 服务发现升级一致性模型

Nacos2.0 架构下的服务发现,客户端通过 gRPC,发起注册服务或订阅服务的请求。服务端使用 Client 对象来记录该客户端使用 gRPC 连接发布了哪些服务,又订阅了哪些服务,并将该 Client 进行服务间同步。由于实际的使用习惯是服务到客户端的映射,即服务下有哪些客户端实例;因此 2.0 的服务端会通过构建索引和元数据,快速生成类似 1.X 中的 Service 信息,并将 Service 的数据通过  gRPC Stream 进行推送。

3.png

2. Nacos2.0 配置管理升级通信机制

配置管理之前用 Http1.1 的 Keep Alive 模式 30s 发一个心跳模拟长链接,协议难以理解,内存消耗大,推送性能弱,因此 2.0 通过 gRPC 彻底解决这些问题,内存消耗大量降低。

4.png

3. Nacos2.0 架构优势

Nacos2.0 大幅降低了资源消耗,提升吞吐性能,优化客户端和服务端交互,对用户更加友好;虽然可观测性略微下降,但是整体性价比非常高。

5.png

Nacos2.0 性能提升

由于 Nacos 由服务发现和配置管理两大模块构成,业务模型略有差异,因此我们下面分别介绍一下具体压测指标。

1. Nacos2.0 服务发现的性能提升

服务发现场景我们主要关注客户端数,服务数实例数,及服务订阅者数在大规模场景下,服务端在同步,推送及稳定状态时的性能表现。同时还关注在有大量服务在进行上下线时,系统的性能表现。

6.png

  • 容量及稳定状态测试

该场景主要关注随着服务规模和客户端实例规模上涨,系统性能表现。

7.png

可以看到 2.0.0 版本在 10W 级客户端规模下,能够稳定的支撑,在达到稳定状态后,CPU 的损耗非常低。虽然在最初的大量注册阶段,由于存在瞬时的大量注册和推送,因此有一定的推送超时,但是会在重试后推送成功,不会影响数据一致性。

反观 1.X 版本,在 10W、5W 级客户端下,服务端完全处于 Full GC 状态,推送完全失败,集群不可用;在 2W 客户端规模下,虽然服务端运行状态正常,但由于心跳处理不及时,大量服务在摘除和注册阶段反复进行,因此达不到稳定状态,CPU 一直很高。1.2W 客户端规模下,可以稳定运行,但稳态时 CPU 消耗是更大规模下 2.0 的 3 倍以上。

  • 频繁变更测试

该场景主要关注业务大规模发布,服务频繁推送条件下,不同版本的吞吐和失败率。

8.png

频繁变更时,2.0 和 1.X 在达到稳定状态后,均能稳定支撑,其中 2.0 由于不再有瞬时的推送风暴,因此推送失败率归 0,而 1.X 的 UDP 推送的不稳定性导致了有极小部分推送出现了超时,需要重试推送。

2. Nacos2.0 配置管理的性能提升

由于配置是少写多读场景,所以瓶颈主要在单台监听的客户端数量以及配置的推送获取上,因此配置管理的压测性能主要集中于单台服务端的连接容量以及大量推送的比较。

9.png

  • Nacos2.0 连接容量测试

该场景主要关注不同客户端规模下的系统压力。

10.png

Nacos2.0 最高单机能够支撑 4.2w 个配置客户端连接,在连接建立的阶段,有大量订阅请求需要处理,因此 CPU 消耗较高,但达到稳态后,CPU 的消耗会变得很低。几乎没有消耗。

反观 Nacos1.X, 在客户端 6000 时,稳定状态的 CPU 一直很高,且 GC 频繁,主要原因是长轮训是通过 hold 请求来保持连接,每 30s 需要回一次 Response 并且重新发起连接和请求。需要做大量的上下文切换,同时还需要持有所有 Request 和 Response。当规模达到 1.2w 客户端时,已经无法达到稳态,所以无法支撑这个量级的客户端数。

  • Nacos2.0 频繁推送测试

该场景关注不同推送规模下的系统表现。

11.png

在频繁变更的场景,两个版本都处于 6000 个客户端连接中。明显可以发现 2.0 版本的性能损耗要远低于 1.X 版本。在 3000tps 的推送场景下,优化程度约优化了 3 倍。

3. Nacos2.0 性能结论

针对服务发现场景,Nacos2.0 能够在 10W 级规模下,稳定运行;相比 Nacos1.X 版本的 1.2W 规模,提升约 10 倍

针对配置管理场景,Nacos2.0 单机最高能够支撑 4.2W 个客户端连接;相比 Nacos1.X,提升了 7 倍。且推送时的性能明显好于1.X

12.png

Nacos 生态及 2.X 后续规划

随着 Nacos 三年的发展,几乎支持了所有的 RPC 框架和微服务生态,并且引领云原生微服务生态发展。

13.png

Nacos 是整个微服务生态中非常核心的组件,它可以无缝和 K8s 服务发现体系互通,通过 MCP/XDS 协议与 Istio 通信,将 Nacos 服务下发 Sidecar;同样也可以和 CoreDNS 联合,将 Nacos 服务通过域名模式暴露给下游调用。

Nacos 目前已经和各类微服务 RPC 框架融合进行服务发现;另外可以协助高可用框架 Sentinel 进行各类管理规则的控制和下发。

如果只使用 RPC 框架,有时候并不足够简单,因为部分 RPC 框架比如 gRPC 和 Thrift,还需要自行启动 Server 并告知 client 该调用哪个 IP。这时候就需要和应用框架进行融合,比如 SCA、Dapr 等;当然也可以通过 Envoy Sidecar 来进行流量控制,应用层的RPC就不需要知道服务 的 IP 列表了。

最后,Nacos 还可以和各类微服务网关打通,实现接入层的分发和微服务调用。

1. Nacos 生态在阿里的实践

目前 Nacos 已经完成了自研、开源、商业化三位一体的建设,阿里内部的钉钉、考拉、饿了么、优酷等业务域已经全部采用云产品 MSE 中的 Nacos 服务,并且与阿里和云原生的技术栈无缝整合。下面我们以钉钉为例简单做一下介绍。

14.png

Nacos 运行在微服务引擎 MSE(全托管的 Nacos 集群)上,进行维护和多集群管理;业务的各类 Dubbo3 或 HSF 服务在启动时,通过 Dubbo3 自身注册到 Nacos 集群中;然后 Nacos 通过 MCP 协议将服务信息同步到 Istio 和 Ingress-Envoy 网关。

用户流量从北向进入集团的 VPC 网络中,先通过一个统一接入 Ingress-Tengine 网关,他可以将域名解析并路由到不同的机房、单元等。本周我们也同步更新了 Tengine 2.3.3 版本,内核升级到 Nginx Core 1.18.0 ,支持 Dubbo 协议 ,支持 DTLSv1 和 DTLSv1.2,支持 Prometheus 格式,从而提升阿里云微服务生态完整性、安全性、可观测性。

通过统一接入层网关后,用户请求会通过 Ingress-Envoy 微服务网关,转发到对应的微服务中,并进行调用。如果需要调用到其他网络域的服务,会通过 Ingress-Envoy 微服务网关将流量导入到对应的 VPC 网络中,从而打通不同安全域、网络域和业务域的服务。

微服务之间的相互调用,会通过 Envoy Sidecar 或传统的微服务自订阅的方式进行。最终,用户请求在各个微服务的互相调用中,完成并返回给用户。

2. Nacos 2.X 的规划

Nacos2.X 将在 2.0 解决性能问题的基础上,通过插件化实现新的功能并改造大量旧功能,使得 Nacos 能够更方便,更易于拓展。

15.png

总结

Nacos2.0 作为一个跨代版本,彻底解决了 Nacos1.X 的性能问题,将性能提升了 10 倍。并且通过抽象和分层让架构更加简单,通过插件化更好的扩展,让 Nacos 能够支持更多场景,融合更广生态。相信 Nacos2.X 在后续版本迭代后,会更加易用,解决更多微服务问题,并向着 Mesh 化进行更深入地探索。

加入我们

欢迎大家在 Nacos Github 上提交 issue 与 PR 进行讨论和贡献,或加入 Nacos 社区群参与社区讨论。也趁此机会感谢参与 Nacos 贡献的 200+小伙伴!感谢你们对中国开源事业的推动 !

Nacos Githubhttps://github.com/alibaba/nacos

除了参与开源,我们也欢迎更多有能力及有意愿的同学加入阿里云共建云原生,详情可查看职位链接:
https://job.alibaba.com/zhaopin/position_detail.htm?trace=qrcode_share&positionCode=GP708029