first commit

This commit is contained in:
张乾
2024-10-16 06:37:41 +08:00
parent 633f45ea20
commit 206fad82a2
3590 changed files with 680090 additions and 0 deletions

View File

@ -0,0 +1,93 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
00 开篇词 Serverless是降本增效浪潮下的必然选择
你好,我是静远。
先和你简单介绍一下自己。我现在是一家互联网公司的云原生资深技术专家负责Serverless相关的业务和技术主要为公有云和私有化的客户提供Serverless相关的落地解决方案同时我也是中国信通院在Serverless领域的标准专家。
伴随着云原生的日渐火热Serverless作为云原生架构的核心组成部分现在已然成为近年来技术圈内的当红炸子鸡。到底有多火呢我们来看看下面的数据。
Datadog的数据显示在使用云服务的机构和组织中有半数以上采用了Serverless技术。信通院2021年的最新报告也显示在核心业务中使用Serverless的用户占到18.11%已经开始和计划使用Serverless技术的用户超过了70%。
目前行业内云原生、Kubernetes、容器工程师供不应求。各一线大厂都在加急招聘云原生工程师在这种情况下让自己成为会Serverless、懂Serverless的工程师能够助力我们弯道超车向目前大热的云原生技术要红利。
让人“又爱又恨”的Serverless
Serverless的持续升温得益于它在“快速的开发交付”“极高的运维效率”“极低的资源成本”这三个方面上的优势。
从容器、应用、函数等不同维度Serverless可以让自己的业务更快上云让我们享受云带来的便利性。把管理机器资源、网络资源这些繁琐的事情交给更专业的平台去处理更加专注于业务和产品自身进而更好地提升产品和业务的核心竞争力让专业的人做专业的事。最后也可以逃脱被产品挑战“这个功能这么简单要这么长的时间才能开发完吗”的质疑。
虽说Serverless有这么大的优势经历过这几年的发展关于Serverless的公众号、书籍和社区资料也不少但真正落地使用的时候不管是客户、团队的同学还是我都还像是在摸着石头过河。
经历了N个令人头秃的夜晚后我才发现Serverless不断延伸发展的特性极大地提升了我们学习和使用的难度。
所以接下来我们就一块来看看怎么从Serverless不断发展的“变”中去把握“不变”在Serverless领域快速的成长进阶。
课程设计
我把整个课程设计为了“核心技术”“拓展能力”“实战进阶”三大模块希望通过循序渐进的方式让你一步一步成为Serverless领域的专家。
导读
在课程开始我会详细的介绍Serverless的前世今生在学习之前把我和团队同学总结的成长路径分享给你。同时我会结合中国信通院最新的标准把Serverless的完整解决方案平铺在你面前。最后我会讲解一下在Serverless道路上的学习方法让你升级打怪的道路更加平坦最终成为王者。
核心技术
这一模块我会通过具象的FaaS产品和技术将抽象的Serverless的核心要点梳理清楚包括触发器、冷启动、扩缩容、流量转发、运行时等核心技术的实现机制。同时我也会在原理中穿插案例和一线经验让你能够即学即用拨开云雾见青天。
有了核心技术的储备就好像一棵大树有了“主干”。当你明白了Serverless的架构原理、核心特性以及技术细节之后对设计一个同样Serverless形态的云计算产品就能做到心中有谱了。
拓展能力
这一模块我会讲解Serverless的延伸能力让你了解Serverless的整体解决方案。它包括如何将WebIDE的能力在FaaS中具体实施起来、如何构建多任务的编排能力、如何构建多维度的Serverless可观测体系等6个小节。
这一模块的内容相当于Serverless这棵大树的枝叶。如果之后让你主导一个新的Serverless形态的云原生产品的技术架构设计工作你应该也能触类旁通游刃有余了。我想你离架构师的目标就进了一步。
实战进阶
通过前面几个模块的学习以及贯穿在课程中的实操,一般的场景实战和一线问题应该难不倒你了。
但纸上得来终觉浅,在实战进阶中,我会和你分享我作为业务方和平台方两种角色下的亲身经历,跟你聊聊一线的经验,也会通过几个典型的案例,帮你把前面学到的知识点用“活”起来。
另外Serverless的发展离不开开源生态和社区的贡献。在这里我还会跟你一起探讨开源引擎的那些事并且结合我和客户、用户打交道总结下来的经验分享一下落地思考的过程并以如何基于核心引擎构建属于自己的Serverless平台作为这个模块的结束也是整个专栏的升华。
通过这样“三位一体”的学习路径我更希望你能领会到Serverless的“意”而不是“形”因为随着Serverless的发展越来越热云上的产品也会越来越丰富。
如果你是Serverless平台的使用者通过专栏的学习一定会在应用上更加得心应手知其然也知其所以然。如果你是Serverless平台的开发者通过专栏的学习在云原生产品的开发和创新上你的思路也一定会越来越开阔。
在你踏上Serverless的学习进阶之路之前我再送你一张课程的知识地图希望在接下来的学习中它能作为你提前预习和课后复习的知识脉络帮助你将以FaaS为起点的Serverless的技术融会贯通。
写在最后
这门课我最终选择了以FaaS形态的Serverless为切入点来跟你剖析这里面的核心技术、拓展能力和实战经验。
为什么会这么选择呢?给你分享一下我的想法,当然,也欢迎你在留言区和我一块讨论。
Serverless现在还在快速成长的阶段功能、产品、面向的领域也在延伸。想要在这个快速迭代、动态发展的技术领域里构建自己的知识体系实在是难上加难。况且Serverless发展到今天已经不是一两个明确的技术或者产品了它包含着一整套的产品体系和技术理念。
我们可以从两个层面来看待。一个层面是理论学术层面包括FaaSFunction as a Service和BaaSBackend as a Service其中BaaS有Serverless化的消息中间件、对象存储、数据库等。另一个层面是产品技术层面包括函数计算FaaS、弹性应用托管服务、弹性的容器服务等产品以及一整套配套的工具链。
试想一下难道我们要每拓展一个产品、功能或者领域就要从0开始学习吗
那么,我们是不是能找到一个较为成熟的切入点,在了解技术本身的同时,掌握学习它的方法呢?
FaaS作为Serverless意识形态的“元老”级别产品从AWS Lambda发布再到众多厂商开始紧跟潮流发展到目前已经有8个多年头了。自定义运行时、自定义镜像、编排、应用中心等也都是基于FaaS打造和延伸的功能。因此我觉得FaaS形态的Serverless是非常好的入手选择。
希望这样的聚焦方式能够帮你养成举一反三的习惯和能力。这几年和Serverless打交道的一线经验、解决的问题以及平台设计上抽象出来的方法让我获益良多我希望这些经验也能够以案例和实战相结合的方式交到你手上让我们一起推进Serverless向前迈进。
在伯克利大学发表的新论文中,预言了 Serverless 将主导云计算下一个十年的发展。我坚信通过这样的方式你一定可以成为Serverless的一员干将在未来的十年里站得更稳。
接下来让我们一起开启Serverless的学习之旅吧

View File

@ -0,0 +1,173 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
00 思维构建 如何在新赛道下进阶Serverless能力
你好,我是静远。
Serverless发展到现今大部分的技术人已经对 Serverless 将引领云计算的下一个十年达成了共识。作为一名技术研发人员“Serverless”对你来说大概率并不陌生甚至你已经在不知不觉中用上了但是它的快速发展也让我们很难对它形成一个较为全面的认知。
那么这节课我们就一起了解Serverless的前世今生重构Serverless思维并选择一个具象化的Serverless形态作为切入点开启后续的学习。最后我也会和你探讨后续学习“升级打怪”的各个阶段让我们的进阶目标更加清晰。
新赛道Serverless的具象化表达
简单来说Serverless是一种架构设计的理念它不是一个具体的编程框架、类库或者工具。随着大家对于Serverless理解的深入用词也越来越精准、具象。从之前“无服务器”的直译到目前“服务器无感知”的理解是各界对Serverless这几年发展和使用的一个跨度的体现。
我们从头看一遍这个新赛道的发展历程。用Martin Fowler的观点来说Serverless是FaaS和BaaS的组合这个简单明了的定义奠定了 Serverless 组成结构的基础。
CNCF在认定这个理念的同时也在它的白皮书V1.0中对 Serverless 架构的定义有了进一步地完善描述它指出Serverless Computing 是构建和运行不需要服务器管理的应用程序。它描述了一种更细粒度的部署模型将一个或多个功能的应用程序打包上传到Serverless平台然后根据实际的需求执行、扩展和计费。
我们发现,这个描述从执行路径和更细的角度进行了具象化的表述。
在2022年7月的云原生产业大会上信通院发布的最新的云计算白皮书针对Serverless的描述中提到“通过多种服务器无感知技术将基础设施抽象成各种开箱即用的服务以API接口的方式提供给用户按需调用真正做到按需伸缩、按使用量收费”又进一步从业务和基础设施服务的协作角度凸显了Serverless的特性。
我沿用信通院白皮书的话来体现这种思想也希望我们能够达成一个共识“Serverless是能够实现业务和基础设施分离提升用户业务创新能力的理念”。
发展历程:从单一到多元
除了理念上的落地我们再说一下Serverless的技术和应用场景向多元化发展的历程。以史为镜可以知兴替理解Serverless多元化的历程才能更完整地构建你的Serverless思维。
雏形阶段
国外可以追溯到2006年Fotango公司推出的PaaS平台Zimki提出了按需使用、按量付费的概念从那时Serverless的核心特征就已见雏形了。到2012年基础设施服务提供商Iron.io的副总裁Ken在《Why The Future of Software and Apps is Serverless》中首次提到“Serverless”这个词它正式走进历史的舞台。我们可以认为这一阶段就是Serverless发展的“雏形阶段”。
发展阶段
AWS 于2014年发布第一款函数计算产品 —— Lambda预览版并于2015年正式商用使用量随后一路飙升随后IMB、Google、Micosoft也于2016年相继发布FaaS产品开源项目也同时出现这里包括比较知名的OpenWisk、Fission、Kubeless、OpenFaaS等。
到2017年国内头部的四朵云阿里、腾讯、华为、百度也相继发布了FaaS产品。同年Azure发布了首个Serverless容器ACIAWS发布Serverless容器服务Fargate。
可以说Serverless的领域内大家追逐和创新的劲头彰显得非常明显。这一段时间出现了函数计算、容器实例、容器服务等产品。从下面的Google Trends也可以看出人们对于无服务器和Lambda的关注度是不断提升的。我们可以认为这是Serverless的发展阶段。
成熟阶段
2018年华为云发布了Serverless容器服务CCI阿里云发布面向应用的Serverless托管服务SAE。在开源方面Knative发布并于2022年3月成为CNCF孵化项目。
时隔一年阿里云、Google、腾讯云先后发布了Serverless容器服务截止到去年各大公司又相继推出了Serverless应用托管服务旨在进一步为用户提供全托管、免运维和高弹性的服务能力一方面可以解决存量的微服务应用上云另一方面可以高效地集成和持续交付等。这一期间直接把Serverless推到了一个相对成熟的阶段。
可以说随着Serverelss技术和价值的体现各大公司都在你追我赶地发展国内厂商虽然是后来者但追赶势头迅猛目前的竞争力也能够和国外大公司相互比肩。
从发展历程中我们也可以看出以函数为起点Serverless的技术和产品逐步趋于多元化。多种形态相互补充为解决更复杂、更多样化的业务场景提供支撑我们通常会把场景分为三类
函数计算FaaS面向函数用户只需关注函数层级的代码用于解决轻量型、无状态、有时效的任务
Serverless应用托管面向应用用户只需要更关注应用本身与微服务结合它融合应用治理、可观测降低了新应用的构建成本、老应用的适配改造成本
Serverelss容器服务面向容器在不改变当前Kubernetes的前提下由于不再需要关注节点大大提升了前期资源的准备过程降低了维护成本使得应用的全生命周期管理更高效。
随着Serverless的内涵持续外延计算的Serverless化要求BaaS服务也具备与之配套的托管能力和极致弹性全链路完整支撑真正Serverless化的应用构建。
那么我们讨论的“Serverless”已经不再是原来狭义上的Serverless——函数即服务了。广义上Serverless的特性应该同时体现在Serverless DB、Serverless消息队列、工作流、工具链等等方面。如今在资源层的Serverless上各大公司也相继研究发布了各种产品如数据库层面的Amazon Aurora Serverless、PostgreSQL for Serverless、PolarDB Serverless等。
除了定义内涵在不断的丰富Serverless的应用场景也在迅猛扩展。它不再局限在小程序、定时任务、简单事件的触发、批量场景的处理了基于AI的集成、大规模数据的处理、IOT数据处理、微服务托管上云等领域都随处可见Serverless的身影。
我再附上一张信通院标准书中的Serverless解决方案图帮助你更好地梳理Serverless的知识要点在课程的开始构建起Serverless的模型和思维。
切入点:升级打怪的进阶之路
了解了Serverless的发展、价值和架构模式具备了Serverless的意识形态之后相信你已经对Serverless这个新赛道脱离了之前的印象了。
Serverless的多而杂一方面让我们更期待它未来的发展但另一方面也的确提升了我们学习的难度。那我们要怎么切入这个新赛道紧跟云计算下一个十年的发展趋势呢
正如开篇词中所说我不希望你面面俱到地学习很多具象化的技术那样只会“杂而不纯博而不精”。我建议你从FaaS形态的Serverless入手为什么要给出这个建议呢
首先Serverless如同太极确实讲究“用意不用力”但学习“太极”也讲究前期的武功招式只是练就到最后应该忘形存神。那么选择一个具象化的产品来学习新赛道下的Serverless是非常有必要的。
其次FaaS作为Serverless意识形态的“元老”级别产品从AWS Lambda的发布到众多厂商的跟进已经发展8个年头了。自定义运行时、自定义镜像、编排、应用中心等也都是基于FaaS打造和延伸的功能。因此FaaS形态的Serverless是你入手的不二选择。
那么我们如何学习Serverelss FaaS这一套“拳法”呢我列了这一个简单示意图下面我依次来跟你讲一下如何达到每一个层级。
Hello World
这个层面最简单你只要具备任意一个云厂商的账号创建一个函数运行起来就算是迈出了Serverless学习的第一步。
在学习的过程中也不需要任何的成本,云厂商一般都有免费的额度,不用担心费用问题,直接上手练习、使用就好。
能用
要想达到这一层,你需要经常去云厂商的网站浏览,学习手册,再找一些公众号等博文资料了解。
完成这一次进阶的你一定已经基本了解了一个Serverless的平台都有哪些功能也能够简单的实操可以做一般的售前解决方案了。不过还并不具备很强的开发能力。
会开发简单业务
这一层开始,就是针对开发层面的学习了。
一方面你需要掌握一门比较流行的编程语言比如Python、Golang等另一方面你还需要持续关注业界公布的各种案例和云厂商的手册跟着来改造你的代码。做到这两方面这一层进阶也就完成了。
会开发复杂业务
达到这一层次的开发同学,必须将函数计算的各种技能融会贯通,知道什么情况用什么策略更好,当然,在社区和云厂商的答疑上也会有一些分享,可以持续关注。
能沉淀公共能力
这个层级属于业务开发的最高级。简单来说,你在开发自己业务代码的时候,能够沉淀出相关的类库、层供你和其他同学或者平台上的其他开发者使用的能力,这是抽象能力的体现。
在达成进阶之前,你应该就已经具备能撰写类库的能力,且学习过相关的抽象思维、设计相关的知识,并有这种意识去执行。
计量计费
作为一个平台,计量计费是少不了的。这个层级,也可以说是希望具备平台开发能力的同学需要达到的初级阶段。
你需要提前具备Kubernetes、Docker、架构设计、中间件的基础知识在此基础上你还需要系统性地吸收Serverless的一线实战案例和经验才有可能避免踩坑。
平台可迁移
这个层级属于平台开发的王者阶段。你开发的平台不仅可以在你的云上使用,还可以帮助私有化企业部署,甚至可以组件化的方式输出。
想要完成这个层级的进阶我的建议是加入一个云原生的Serverless团队在里面打磨一万小时以上扎扎实实地搞清楚每个点假以时日你才能在这个领域崭露头角。
写在最后
到这里你应该已经对Serverless有了不一样的理解。
Serverless是能够实现业务和基础设施分离提升用户业务创新能力的理念。我给你整理了一个图突出几个核心特征组成了它的形式
其中从物理组成上FaaS和BaaS组成了Serverless的基本构成这就跟汽车由车身和车轮构成一个道理而通过开箱即用、API形式调用等动作描述了Serverless的运作形态这就跟汽车需要发动引擎、脚踩刹车的使用逻辑类似最后按需调用、按量收费的几个核心特征体现了Serverless的“最大卖点”好比似汽车的自动驾驶、自动泊车功能一样。
这样了解下来你是不是对Serverless的理解更亲切了呢
Serverless发展到今天形成了以函数计算、弹性应用、容器服务为核心的产品形态并逐步泛化和外延使得其形态趋于多元化形成互补之力能够满足更多的场景。
但是Serverless思维的建立一定不是一蹴而就的。在它未来发展的几年中也一定会越来越泛化它一定会继续融合更多的生态更多的Serverless产品形态也将会不断涌现。我们要做的就是“保持一颗拥抱变化的心”扎根于Serverless的本质精髓透过产品了解背后的技术原理和初衷。
在后面的课程中,我希望你不仅跟着学习文章中的理论知识,也能够随着课程中提到的实战去自己动手练习,积极地参与讨论和思考,通过“学习-实践-总结-表达”的过程去探索这门不断延伸的技术。
在这样的学习下,我相信你一定可以触类旁通,“随敌而变,随招而变”,达到真正的“用意不用力”。
思考题
你在步入Serverless领域的时候有没有遇到什么困惑呢欢迎分享出来我们一起讨论解决。
感谢你的阅读,也欢迎你把这节课分享给更多的朋友一起阅读和交流。我们下一讲见。

View File

@ -0,0 +1,200 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
01 生命周期:函数计算的基本流程是如何执行的?
你好,我是静远。
看过上一节思维储备的内容之后相信你对Serverless的认知已经越来越清晰了。它是一种服务设计的理念只需关注业务逻辑的开发无需关注环境运维和机器的管理同时具备弹性、按需付费等特点。
FaaS函数即服务作为我推荐给你的第一个步入Serverless世界的技术今天我想跟你介绍一下它的基本流程让你从整体上对FaaS有一个初步的了解心中能够勾勒出函数计算开发、调试、部署和运行的轮廓。
这样,在你后续碰到某一个困惑,例如“函数上传到哪里去了?”“函数运行为什么超时?”等流程问题时,就能快速知道自己需要深入了解哪一块的知识点。
这节课我就以“Hello Serverless”为例选取百度智能云函数计算CFC作为操作平台分别从用户使用与平台服务两个视角梳理一遍 FaaS 执行的流程和原理,这其中包括函数创建、存储、执行等一个生命周期的始终,同时,我也会带你体验一次开发、运行函数计算的流程。
初次体验推荐用云厂商平台
一般初次接触Serverless我推荐先去公有云厂商的云平台上跟着他们的教程来体验从使用角度上能够有一些认识再看开源的框架或者找一些资料深入研究。
为什么这么推荐呢?
首先云厂商的函数计算平台为用户提供了多种运行环境Python、Java、PHP、Node.js、Golang 等可以满足不同技术栈的开发需求。你可以选择任何你擅长的语言来体验避免了语言的切换成本。如果选择的是解释性语言可以直接在云平台上开发、调试、部署非常方便。这样通过低成本的学习你可以快速地理解Serverless的产品形态。
其次如果你自己要部署一套开源的框架是要有机器资源的而云厂商一般都会提供免费的额度。拿函数计算来说像百度智能云、阿里云、华为云每个月都有大概100万额度的调用次数以及40万GB-秒的内存资源使用基本上足够我们体验了。腾讯云做了一些调整新人前3个月有一定的免费额度。当然你在使用的时候最好提前看一下云厂商的使用说明避免多花冤枉钱。
最后你也可以比较方便地通过云厂商提供的各类API/SDK、触发器集成、开发工具来了解整个Serverless的生态和解决方案。这也正是我们常说的“遇到新事物先看看别人是怎么干的”也是学习的一条捷径。
了解了大致的体验方式和流程之后,下面我们就要从用户角度来看函数计算是如何使用的了。
从用户视角看函数计算的生命周期
函数整个生命周期会经过“开发设置”“打包上传”“事件绑定与触发”“弹性执行”“实例销毁”五个过程。下面这张图片,就展示了用户视角下的函数计算的过程。我们一起来体验一下。
第一步,需要你编写相应的函数代码。我们以 Python3.6 为例撰写一个“Hello Serverless”的Demo函数运行时选择 Python3.6,执行内存选择 128MB超时时间选择 3s并发度设置为1。下图展示的就是我们创建好的函数信息概览。
然后我们点击编辑函数进入在线编辑模式编写Demo案例代码。你也可以下载到本地进行代码开发。
# -*- coding: utf-8 -*-
def handler(event, context):
return "Hello Serverless"
编写完成之后,你还需要指定函数执行的入口,例如:填写 “index.handler”就是指调用主程序文件 index.py 中定义的 handler 方法。当有事件触发函数执行时就会先从handler方法开始执行。
第二步,需要将代码上传到函数计算平台上。你可以直接在界面提交并保存你的代码,也可以将代码打包成 zip 文件。上传的方式包括函数计算API/SDK、前端界面上传还可以通过命令行工具 CLI 上传。
第三步,执行你刚刚上传的函数。通常可以利用 API/SDK 调用,或者在前端界面手动点击来执行函数,另外,你也可以通过各种触发器来触发函数的执行。
你可能会对触发器感到陌生,这里我简单说一下。
FaaS可以通过事件触发器打通众多的上下游服务当触发源服务发出请求时函数就会响应运行作出处理和反馈。以 HTTP 触发器为例,当用户访问 HTTP 触发器的 URL 时会向指定的云函数发出HTTP处理请求随后平台会启动一个函数实例来对请求进行处理。
我们继续操作函数helloServerless从平台中选择创建一个HTTP触发器将URL路径设定为“/hello/serverless”选择“GET”方式作为HTTP的请求方法。
创建完触发器后函数计算平台会帮助你生成一个可访问的URL地址你就可以通过这个URL地址进行触发函数的执行了。如果你是要上生产环境最好能增加身份验证以确保服务的安全可靠。
第四步,当函数执行完毕,函数计算平台会返回函数的执行结果。通常可以通过日志或者请求返回的信息来查看函数的执行结果。
curl https://$HTTP_TRIGGER_URL/hello/serverless
#输出结果
Hello Serverless
经过上面的步骤你已经完成了一个“Hello Serverless”的小Demo。对于用户而言只需要关注开发的代码本身而不需要去关注环境的部署和维护。看到这里你可能会有疑问执行结果和传统代码执行没有区别为什么要选择 FaaS 呢?
其实FaaS最大的特点在于弹性扩缩容和缩容至0的能力当你没有调用函数FaaS是没有任何实例在计费的。也就是说当你创建上传函数后并没有产生计费只有当你产生调用量才会开始计费。当流量达到一个阈值的时候系统自动进行扩容。当流量变小时系统会自动进行缩容。
另外,一般云厂商的 FaaS 都会有一定的免费额度,如果你的应用是基于事件触发或者流量的波峰波谷比较明显,那么 FaaS 绝对是你的明智之选。
从平台视角看函数计算的执行过程
前面我们已经从开发者的角度了解了函数计算的生命周期,但我相信你肯定不会只满足于表面的使用。 那么,函数计算内部具体是如何实现的呢?
简单来说事件的请求首先会到达路由服务路由服务在缓存Cache中查看是否有准备就绪的实例。如果有就绪的实例也就是热启动直接使用该实例执行函数即可。如果没有就绪的实例就会进入冷启动过程。函数计算引擎会启动容器的初始化流程做一些准备工作下载函数的代码包或者镜像、准备网络环境、加载运行时Runtime进而执行函数并将实例信息放入到Cache中下次请求再过来的时候就进入热启动流程。
执行完毕之后实例会保留一定时间通常是1-2分钟随后被回收。
以上是正常的执行流量,当流量突增到一定阈值的时候,函数计算服务会快速扩容实例来满足提升的并发量。而空闲的实例太多的时候,也会缩容实例。
到这里你肯定会有疑问,很多新名词好像很陌生,冷启动、热启动、运行时等等。下面我将带你从开发态和运行态两个视角来梳理函数计算的生命周期,便于你理解它们的含义。
函数计算的开发态
当我们上传代码到FaaS平台后后端服务会将代码包上传到对象存储中并将函数相关信息包括函数代码链接、Runtime信息、运行内存、超时时间等信息存储起来。
当我们再次修改函数相关信息或者在线编写函数代码的时候FaaS平台会将存储好的代码和附属信息读取出来展示在界面上供你修改。
这里需要注意的是,目前各大云厂商还只支持解释性语言的在线编译和调试,对于编译型语言,你还得下载到本地进行开发。所幸,目前有的云厂商(如阿里云)已经发布了端云联调能力的工具,从一定程度上来说,也便于你快速本地开发、调试和发布。后面的章节中,我也会跟你详细介绍这个技术的实现。
函数计算的运行态
那在你上传好代码之后FaaS平台是如何执行函数代码的呢我们还是以“Hello Serverless”为例来说明。
在开头,我们已经创建了一个函数、编写了相应的代码,并保存到了云厂商的平台上。
我们再次回到 HTTP 触发器的方式执行来讲解。当事件请求访问触发器的URL时请求会被路由到相关函数实例依据是否是第一次请求会分为冷启动和热启动两种情况根据流量的大小会进行动态的扩缩容。
我将这个过程抽象成下面的函数计算架构示意图通过这个示意图我们来依次看一下“Hello Serverless”云函数是怎么执行的。
流量转发:热启动和冷启动
首先当HTTP事件请求到来之时流量转发服务负责接收转发请求也就是图中的Route服务。当Route接收到请求后首先就要在自己的缓存Cache里查看是否已经存在当前Hello Serverless函数和实例的对应信息。
如果有那么根据存储的信息直接在实例池instance pool中获取执行实例这时请求就以热启动方式被执行。具体什么是热启动呢就是当你的函数执行完成后容器实例会保留1-2min的时间如果此时触发执行函数那么无需新增实例和执行函数 runtime 的挂载,直接复用。因此它的响应速度要快得多。
如果找不到相关的信息呢那么就会通过一个类似激活器如Activator的组件来创建并申请一个实例执行本次请求接着将实例的相关信息存储到Route的缓存中这就是冷启动的执行过程。
在冷启动过程中会有哪些操作呢一般来说包括实例调度和容器创建、下载并解压代码、准备函数执行环境、挂载用户代码、VPC网络准备、初始化运行时和用户代码这一系列过程结束后函数才开始执行。所以冷启动的消耗时间受到很多因素的影响主要包括
不同语言的冷启动时间不同:一般 GoLang、Python的速度较快而Java的速度相对较慢一些。
代码包大小:下载解压代码的过程在冷启动过程中比较耗时,一般体积越大,耗时越多。
容器创建速度和VPC网络的准备这个过程的耗时往往取决于云服务商不同平台的速度也是不一样的。
当然各云厂商对于冷启动也在不断地优化推出了预留实例、通过缓存加快代码下载速度、VPC代理和IP隧道技术等方式来解决冷启动问题。你也可以通过一些方式来自行解决例如
精简代码包,减少掉一些不必要的配置和依赖。
用预热请求的方式来确保代码实例常驻在容器池中,比如用定时器触发代码实例进行一个非常快的空响应。
选择冷启动时间较少语言,尽量避免使用 Java 等启动较慢的语言运行时。
尽量选择较大的内存:函数的内存越大,冷启动速度越快。-
后面的冷启动章节,我会详细地跟你聊一聊这一块的技术要点和一线实战经验。
动态扩缩容
那么什么时候需要扩容和缩容呢当我们首次通过HTTP触发器请求的时候此时由于函数实例池里面没有已经加载好的Pod需要进行一个从0到1的容器扩容过程。
此时还需要将Hello Serverless的包从对象存储中加载到容器中运行起来。当执行结果返回后一般FaaS平台会将函数实例保留一段时间再进行销毁。如果被保留的时间段内有请求再次进来那么就可以直接复用不需要扩容。但如果此时并发超过我们前面设置的1次请求函数计算引擎监听到相关指标后就会自动扩容。
当然,我这里举的情况比较极端了,通常的函数计算引擎会根据设定的监控阈值,提前就要扩容了。
扩缩容算法包含Node级别和Pod级别的扩缩容。Node和Pod一般会监控自定义的指标如果指标有变化会进行相应的扩缩容操作。
例如 Kubernetes 中的 HPA 扩缩容算法,通过安装一个 metrics-server 的监控组件,提供 HPA 和基础资源监控的能力。对CPU和Memory等指标进行监控保证其维持在可控的范围内。这里先卖个小关子函数计算的扩所容是否可以通过Kubernetes的HPA来直接进行需不需要改动可以先思考一下我们在扩缩容一节中会详细跟你来探讨。
而Node级别的扩缩容一般会根据Node的整体使用率来判断Node数量是否需要扩容。一旦需要扩缩容就会向Scheduler发送扩缩容请求Scheduler调用相关接口执行扩容操作。
函数实例释放
最后,我还是要提一句,运行也是有始有终的。当函数执行完毕,并且在 1~2min 内没有再次执行时FaaS 平台就会回收该实例。
各云厂商的回收时间各不相同,这里注意一下就行,以便我们将来基于云平台开发的时候,可以提前优化函数,确保请求在热请求的情况下执行。
Runtime
通过之前的步骤你已经可以获得一个函数实例来执行你编写的函数Hello Serverless了。下面我们来重点看一下函数执行的关键底座Runtime。Runtime 是为函数提供运行框架,并真正执行函数的进程。
云厂商一般将不同语言的执行环境打包为基础镜像。容器镜像包含多层镜像,第一层基础镜像为 ubuntu、alpine之类的文件系统第二层镜像为代码的依赖库如Python 代码需要使用 pip 库 , Node.js 代码需要使用 npm 库。也有的函数计算引擎直接支持Docker镜像来运行。
我们以上述“Hello Serverless”的运行语言Python3为例来说明runtime的执行过程。Python3-runtime通常会开放一个handler的接口给开发者实现具体的业务逻辑。当一次请求到来之时Python的运行时会通过动态加载的方式对你刚才定义的文件方法进行调用。
这里需要注意的是对于编译型语言需要引入FaaS平台提供的代码库基于一套现成的框架来开发业务逻辑代码不过思路一样只是运行的方式不一样罢了。
具体的的实现过程,我会在运行时一节中跟你细聊。
小结
最后我们来小结一下。今天我通过一个“Hello Serverless”的例子带你了解了函数计算在不同视角下的运作过程。
从用户视角,通过四个步骤的实操,我们可以了解到作为一个业务开发人员,需要重点关心的部分是开发设置、打包上传、事件绑定与触发以及函数上线后的按需付费。对于函数如何执行、如何扩缩容、如何销毁等一系列函数计算引擎去做的事情,可以让平台运维人员更多的关注。
从平台视角,基于开发态,我们可以清晰地了解到函数计算控制面做的事情在于提供给业务人员一个好的操作平台;基于运行态,我们从整体上对运行期各环节的协作有了一定的了解。
一方面事件初次请求时函数计算平台有一个从0到1的扩容过程随着流量的增加平台会继续扩容以确保请求的正常执行。随着请求的减少平台会通过释放实例来缩容。
另一方面,真正支持函数执行的运行时,也有着不同的实现方法,主要来源于语言本身特性的不一样。
通过今天的介绍,相信你对函数计算有了一个整体上的了解,已经摸到了这个领域的大门了。
接下来的课程,我也会详细地把本节提到的这些技术要点和你一一道来。
思考题
好了,这节课到这里也就结束了,最后我给你留了一个问题。
FaaS提高了开发人员的生产效率使得产品能够快速地推向市场进行试错你是否接触过FaaS哪些业务已经在使用了有遇到过什么问题么
欢迎在留言区写下你的思考和答案,我们一起交流讨论。感谢你的阅读,也欢迎你把这篇文章分享给更多的朋友一起交流进步。

View File

@ -0,0 +1,257 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
02 触发器:如何构建事件源与函数计算的纽带?
你好,我是静远。
在上一节课里,我通过一个函数的开发和请求过程,给你介绍了函数计算的生命周期,让你从用户和平台两个视角,了解了函数计算各阶段的执行流程和基础要点。
细心的你应该能发现上传到函数计算平台的云函数一般是需要通过一个触发器来执行的如果之前你没有接触过事件驱动模型和Serverless相关的知识可能对触发器这个概念有一些陌生。
相信学完今天这节课的你,不仅能够了解触发器的概念和原理,更能对触发器相关的知识点,例如事件、调用等等都有一定的理解。最后,我也会带你实现一个自定义的触发器。
初识触发器
上节课,我们知道了函数托管在平台后一般需要有一个事件来触发才能真正地运行起来。那么在了解触发器之前,我们先了解一下事件。
什么是事件?
说得直白一点,事件,就是系统运行期间发生的动作或者发生的事情。而函数计算,提供了一种事件驱动的计算模型。针对这个概念,我们需要了解三件事。
第一CNCF关于事件的定义和初衷。CloudEvents 期望通过一种通用的格式描述事件数据的规范,以提供跨服务、平台和系统的互操作性。那么,我们自身在开发一个函数计算平台的时候,就可以更多的以这个规范为准绳来设计;在选型一个开源框架、函数计算平台开发业务代码的时候,这个互操作性也可以作为技术选型的参考因素之一。
第二国内云厂商的事件规范程度。目前各大第三方云厂商都希望推广落地CloudEvents规范但在事件规范定义上多多少少都有各自平台的一些特有属性和参数包含在里面。
如果你在其中一家云产品上开发了云函数,一般来说,需要做一些简单的适配才能迁移。庆幸的是,部分云厂商目前具备支持脚本迁移和适配的能力,你可以通过工单的形式跟他们取得联系,大大减少迁移成本。
第三CNCF在哪些Serverless相关产品定义了规范。在函数计算中会接触到单函数的事件触发、多个简单函数通过异步调用方式形成的事件触发、复杂场景下通过工作流WorkFlow的形式来进行编排的事件交互。
CNCF Serverless工作组针对函数和工作流均定义了相应的格式规范或原语。现在很多的传统企业、云厂商也都在尽量按照这个标准去执行——尤其是复杂的多函数交互的场景下。你在选型的时候可以关注一下是否兼容这个标准便于函数和编排规则在各大平台迁移。
什么是触发器?
了解“事件”这个触发条件之后,我们再看函数运行起来之前的“触发”动作。我们通常把这种由事件驱动连接上下游服务的关系组合称为触发器,它是触发函数执行的主要方式之一。
我们知道,函数计算由云函数和触发器组成。对函数计算来说,触发器描述了一组关系和规则,包括事件源、目标函数、触发条件三大核心要素。
其中,事件源是事件的生产者,目标函数是事件的处理者。当触发条件满足时,就会通知函数计算引擎,调度对应的目标函数来执行。
触发器的元信息可以由服务方持久存储,也可以由函数托管平台和服务方共同持有。根据不同的集成和调用方式,触发器也有着不同的分类。
触发器类型
按照触发器集成原则,通常分为单向集成触发器、双向集成触发器和代理集成触发器。它们的区别在于事件源和事件的规则存储在哪里,以及从哪里触发。
我们在设计触发器的时候,主要考虑的是事件源和函数计算的上下游关系以及操作的便捷度。
例如,我们在云厂商看到的对象触发器,在存储对象和函数计算两个平台上都可以来创建和配置触发器,这种就是双向集成触发器。而对于消息的监听和触发场景,由于大部分情况是对异步消息的计算处理,加上函数计算按需启动执行的特性,我们通常就会设计成单向集成触发器,你直接在函数计算平台创建和配置触发器即可。
按照调用方式,我们又可以将触发器分为同步触发器和异步触发器。
同步调用触发事件被函数计算处理后直接返回结果。例如公有云厂商提供的HTTP触发器、CDN触发器等基本都是同步触发器。
异步调用:触发事件请求函数计算后,函数计算服务返回一个接收成功的状态码,由函数计算来确保事件被可靠处理。例如,定时触发器、消息队列触发器等,均属于异步触发器。
在使用的时候,你也可以优先考虑自己的需求,从不同的分类里找到最适合的触发器类型。
到这里,相信你对触发器的认知已经不仅仅是基于上节课的一个感性轮廓了。下面,我们来体验一个简单的触发器,为自己动手实现触发器打下基础。
动手体验
我们以百度智能云的函数计算平台为底座来体验一下假设我们已经创建了一个名叫test_geekbang的函数接下来需要创建一个触发器。
如图我们创建了一个BOS触发器BOS是百度智能云的对象存储服务并设置了监听的目标对象是BOS下的一个Bucketbos:/cfc-sns事件类型是文件上传、追加上传等条件均可触发函数的执行。
创建好触发器后我们就可以在BOS上传一个文件来测试了。由于我们没有设定额外的过滤规则所以你上传任何一个文件均可以触发云函数test_geekbang一次执行。
整体操作没有什么难度你也可以参考百度智能云函数计算CFC的教程去体验其他细节设置。你会发现触发器使得函数计算与外部服务的连接非常的快捷高效。
说到这里,估计你已经迫不及待地想知道这个连接的背后是如何实现的了。接下来,让我们一起来动手实现一个触发器,体会其中的原理。
动手实现
我们看不到云厂商的对象存储触发器中详细的实现,那么通过一个比较类似,且功能实现全面的开源存储服务来作为实现触发器的底座,也未尝不可。
这里我选择了和百度智能云BOS类似的开源对象存储服务MinIO来演示。MinIO采用Golang实现客户端有支持丰富的语言集成如Golang、Java、Python、JavaScript等等空闲的时候你也可以用不同的语言尝试重现这次的实验。
假设我们需要满足这样一个功能场景:
通过监听MinIO的事件动作MinIO存储对象的创建、访问、删除来触发函数计算的执行。例如当一个文件存储到MInIO的Bucket中时监听服务监测到该事件发生通过查询判断是否存在一组关系和规则如果存在触发函数执行既定的业务逻辑。
那么,针对这个功能诉求,你能拆分出一个实现流程吗?别着急回答,可以先暂停一会儿思考一下。
我将大致流程分为了5个步骤
用户在函数计算平台上对相关云函数绑定自定义触发器,设定好事件源标识;
MinIO发生某个动作假设是上传Upload文件的一个动作该动作关联唯一的一个Bucket下的一条路径构成了一次触发事件 Event
事件监听器Event Monitor Server作为Deamon进程持续运行会捕获到该事件
监听器通过“动作+Bucket+路径”构成的唯一标识从数据库MySQL查询并获取相应的触发器
监听器对获取到的触发器进行逐个触发,对远端云函数进行触发调用。
接下来,我们就根据这个场景和流程来定义这个触发器。整体需要通过事件类型确定、事件协议定义、元数据存储约定、事件绑定四个步骤来完成。
事件类型确定
由于MinIO已经实现了事件监听的API我们可以直接从MinIO API目前的事件类型中选出我们需要的三种s3:ObjectCreated:* 、s3:ObjectRemoved:* 、s3:ObjectAccessed:* 满足针对一个对象的CRUD要求并进行监听。
其中第一种类型满足了通过不同方式来创建一个对象的需求第二种事件类型支持从存储的bucket中删除指定的对象第三种事件类型是用来监听访问对象时才触发的情况。
选定好事件类型后我们选取GitHub上封装好的MinIO客户端简化我们的开发实现通过引入mino-go可以快速实现一个事件类型的监听动作。
func (m * EventMonitor) work() error {
miClient := m.miClient
for miInfo := range miClient.ListenNotification(context.Background(), "", "", []string{
"s3:ObjectCreated:*",
"s3:ObjectAccessed:*",
"s3:ObjectRemoved:*",
}){
//TODO实现业务逻辑
}
}
事件协议定义
然后我们就要定义事件协议来传递MinIO和云函数之间的触发请求了。这里我们采取HTTP+JSON的方式。你可以参考下面的事件格式定义方法
{
"eventId" : "事件id ",
"source" : "事件源此案例标识来源MiniIO",
"specversion": "协议的版本",
"type" : "事件类型 ",
"timestamp" : "请求时间戳",
"target": "函数唯一标识",
"data" : {
// 待处理数据
}
}
不过,定义了协议,能够传递请求还不够,函数计算平台通常为了能够识别可信的事件源,当有触发器创建之后,还需要进行授权认证,确保服务请求的安全性。-
接下来我们来看一下接口设计。在这个案例情境下我们需要创建四个功能来满足触发器的控制台操作需要分别用于触发器的创建、修改、删除、查询你也可以通过REST来构建如下的接口协议。
func OperateMinioTriggers(request *restful.Request, response *restful.Response)
其中request中包括action动作用来标识CRUD操作即可。
这里你需要注意一点,如果一个函数被删除,那么它对应的附属特征、附属触发器等都应该剔除干净。这也是我们初次设计时候很容易遗漏的地方。
元数据存储约定
按照通常的操作习惯来说在MinIO和函数计算服务侧均可实现触发器的集成功能还可以在配置Bucket的时候创建事件绑定规则在创建一个函数的时候也能够创建好监听对象的请求事件。
而这两处的绑定创建过程除了UI界面和本身创建时的平台校验参数不一样外都很雷同。为了降低第一次尝试的难度我们就选择在MinIO服务侧集成。
由MinIO作为事件源服务方通知触发云函数的执行。那么相应的关系信息只需要在MinIO服务侧存储鉴权和规则在函数计算平台侧存储其中
关系创建确定具体的触发关系如具体的Bucket、触发条件等
规则创建确保触发源按照既定规则进行触发如标识MiniIO的动作、触发形式等。
你可以根据上述的描述进行设计这里我也给出了简单的待存储对象的数据结构你可以自行转换成数据库表来存储通常多个关系可以共用一个规则。你可以就此创建两张表一张规则表tb_rule用于存储规则一般存储在函数计算的平台侧一张关系表tb_relation依据触发器的类型存储在事件触发源侧。
规则:
type Rule struct {
ID uint
Sid string // 规则唯一ID
Resource string // 函数标识
Source string // 触发源
…… // 可以根据业务复杂度增加字段如创建时间、用户ID等信息
}
关系:
type Relation struct{
RelationId string // 关系ID
RuleId string // 触发器关联的规则的id
Source string // 触发源可以具体到bucketName
Target string // 函数的唯一标识
Condition string // 触发条件
…… // 可以根据业务复杂度增加字段,如触发器自定义参数
}
事件绑定
根据上述的约定设计方案我们来完成事件的绑定规程从MinIO服务侧来绑定需要3个步骤。
步骤1请求上述定义好的MinIO接口创建一个自定义的触发器配置例如监听一个对象存储到bucket的动作。
步骤2MinIO侧的服务例如API-Server在接收到请求后将关系存储在数据库中并同步通知函数计算平台创建对应的规则。
步骤3函数计算平台接收到MinIO侧服务的请求后检查当前库里面是否已经存在同样的规则如果存在直接返回响应成功如果不存在记录规则用于触发时校验最后返回通知MinIO侧绑定成功。
到这里我们已经完成了MinIO作为事件源函数计算平台作为处理方的事件关系的绑定过程。
触发器的调用过程
定义好触发器并绑定关系后我们可以以上传一个文件为例整体看一下MinIO的事件触发过程也就是触发器的调用过程
用户操作MinIO上传了一个文件
MinIO侧事件监听服务EventMonitorServer对触发的事件进行捕获
EventMonitorServer从数据库关系表tb_relation中获取匹配该事件的触发关系
根据关系发起调用函数计算服务的请求;
函数计算服务平台侧接收到请求后,进行鉴权和规则的验证,并执行相应的函数进行处理,最后根据异步或者同步约定方式,进行结果返回。
到这里我们已经定义好一个基于MInIO的对象存储触发器并通过时序关系描述了调用时刻的交互过程。
如前所述由于MinIO已经提供了相关的事件监听API在本案例中你只需要开发一个简单的服务如事件监听服务EventMonitorServer来监听这些API接口的变动即可。你可以按照此方法进一步实战演练。
如果需要上生产环境你还需要根据性能、稳定性等相关的要求增加缓存、重试机制等能力来确保生产环境的SLA。
小结
今天我给你介绍了事件源与函数计算的纽带——触发器以及事件和触发器的相关知识同时也动手体验了云厂商的对象存储触发器。最后我们也通过自己构建一个基于MinIO的对象存储触发器深入了解了触发器的执行原理。
这其中最重要的就是以MinIO为核心底座来构建一个自定义的触发器我希望你能够记住关于动手实现触发器的三点关键信息。
首先我们选择MinIO来作为本次动手实现的初衷是为了更好地了解云厂商关于对象存储的相关实现原理在你之后遇到类似存储事件触发函数执行的时候也可以用本节讲到的方法来去应对。
其次,触发器实现的来龙去脉,大致包含四个核心流程:事件类型确定、事件协议定义、元数据存储约定、事件绑定。
最后今天我们引入的MinIO客户端监听MinIO的三大事件类型以及给出的代码示例都可以即学即用你也可以尝试快速上手实战实现一个触发器。
希望通过以上的介绍,你能对触发器以及相关的事件、调用原理有一个比较深入的理解,在之后使用或者自定义触发器的时候,就可以“拿捏”得比较准了。
思考题
好了,这节课到这里也就结束了,最后我给你留了一个思考题。
在你的工作场景中,还发现哪些场景可以构造成触发器?改造过程中,我们通常需要注意哪些技术卡点?
欢迎在留言区写下你的思考和答案,我们一起交流讨论。感谢你的阅读,也欢迎你把这节课分享给更多的朋友一起交流进步。
延伸阅读
CloudEvents规范 你可以通过这个链接来了解CNCF事件的定义和详解
Minio-go 这是我案例中使用的访问minio的一个封装库供你参考
Minio支持的事件类型介绍 这是MinIO官方的事件支持书册感兴趣的话可以了解一下
Serverless Workflow Specification 这个是关于工作流规范的描述,在构建函数和应用编排的时候,可以提前参考一下

View File

@ -0,0 +1,177 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
03 高级属性:应对生产级别的应用,你需要掌握哪些技能?
你好,我是静远。
通过前面的学习我们已经掌握了函数计算的基本特性和操作流程可以通过配置触发器的形式来完成一些较为简单的Serverless业务场景的开发和运维了。
但是,针对复杂的场景或者是要上生产环境的应用服务,这样的操作肯定是不够的。就拿我们比较熟悉的微服务来说,一个服务模块从开发到上线运行,需要注意公共代码的复用、开发框架的灵活性、上线时候的灰度发布、线上运维的容灾,针对上下游还需要考虑到如何应对流量的波峰波谷等因素。
那么在Serverless的函数计算中就不要注意这些了么Serverless虽然是无服务器化的但如果要用好函数计算平台或者是自己要开发一个FaaS化的Serverless平台都应该对这些因素有一个比较清晰的认识才能更好地驾驭Serverless。
今天这节课,我们就按照一个应用“开发-发布-运维”的顺序,以我们熟知的微服务流程为参照,看看函数计算中,你需要了解哪些高阶的技能。我们先从应用的开发阶段说起。
如何提取公共的服务能力?
开发微服务的时候,如果是一个复杂一点的功能,则需要多人协作或者复用一些别人开发好的类库。那么,函数计算中是否也有类似的特性呢?
层的提出就是为了解决这个问题。你可以将函数依赖的公共库提炼到层,以减少部署、更新时的代码包体积。
目前大部分的云厂商都支持各种语言的层如Java、Python、Node.js、PHP等。对于支持层功能的运行时函数计算会将特定的目录如/opt添加到运行时语言的依赖包搜索路径中。你可以自定义层或者使用云厂商开发好的公共层这些公共层包含了一些常用的依赖包。
对于自定义的层你通常需要将所有内容打包到一个ZIP文件包中并上传到函数计算平台。函数计算运行时会将层的内容解压并部署在特定的目录如/opt目录下。
使用了层的功能之后有这样3个好处
函数程序包更小,只关注核心代码,使开发变得更加轻松方便;
使用层也可以避免在制作函数zip包和依赖项过程中出现未知的错误
由于可以在多个函数中引入使用,可以减少不必要的存储资源浪费。
当然不同云厂商构建层的方式也不同我们以百度智能云为例一起尝试构建一个Node.js语言运行时的层。
首先,我们新建一个目录,如 mkdir my_nodejs_layer并在该目录下新建名为“nodejs”的目录目录结果如下所示
➜ my_nodejs_layer tree
.
└── nodejs
1 directory, 0 files
下面假设我们的函数代码中需要用到JavaScript实用工具库Lodash我们将Lodash打包到自定义的层中。进入nodejs目录执行 npm install lodash ,我们再来查看一下目录结构:
➜ my_nodejs_layer tree -d
.
└── nodejs
└── node_modules
└── lodash
└── fp
4 directories
最后在my_nodejs_layer目录中将nodejs打包
zip -r nodejs_layer_lodash.zip nodejs
这样,我们自定义层的依赖就打好了。我们来看一下层上传之后的运作逻辑:
我们上传层之后函数计算会将层的ZIP包上传到对象存储。当调用函数执行时会从对象存储中下载层的ZIP包并解压到/opt目录。你只需要在代码中访问/opt目录即可读取层的依赖和公共代码。
当我们上传多个层的时候,一定要注意次序问题,后序的层会覆盖相同目录下的文件。
到这里,我们已经构建并上传了自己的层,之后,就可以在编写函数的时候,通过约定的路径使用了。是不是非常方便?它其实有点类似我们微服务的依赖包,通常来说,也会通过上传自定义的类库到仓库中,让后续开发的同学更加方便地使用。
如何快速地开发和交付?
层确实可以解决我们公共代码逻辑和工具库的使用问题。不过你可能还很好奇“在微服务的开发中可以比较灵活地使用某种自己擅长的语言和框架来开发代码只要遵循服务之间的接口协议规范就可以开发出一个模块来提供服务能力。那么Serverless的函数计算是否也有类似的能力让我可以快速灵活地搞定一个服务并且执行呢
答案是肯定的。为了让更多开发者能够快速灵活地开发自己的服务,除了你熟知的基于函数平台提供的标准运行时来开发函数体之外,函数计算平台通常还会支持用户以自定义镜像的方式上传应用来运行。
我们以阿里云自定义镜像为例来说明其工作原理。函数计算系统初始化执行环境实例前会扮演该函数的服务角色获得临时用户名和密码并拉取镜像。拉取成功后会根据指定的启动命令、参数以及端口启动你定义的HTTP Server。然后这个HTTP Server就会接管函数计算系统所有请求的调用。
在开发函数具体的逻辑之前我们一般还需要确认开发的是事件函数还是HTTP函数通常来说平台服务后端对这两种调用方式实现的逻辑不一样。
下面我们来具体看一下自定义镜像的构建和使用过程,包括五个步骤。
第一在创建函数之前我们需要基于自身业务来实现一个HTTP Server并且将其打包成镜像上传到镜像仓库供函数计算使用。
各个云厂商的实现方式不同主要体现在请求和响应接口上的协议适配以及服务运行方式上。针对HTTP Server的实现这里需要强调4个点
建议基于HTTP和事件的触发方式实现相应的接口功能
需要根据接口协议规范实现请求Header和Body中值的解析和处理
上传镜像时需要指定容器的启动命令Command、参数Args及监听端口Port
推荐实现一个Healthz函数用于健康监测。
第二,我们来创建一个函数,此时,就只需要设置函数运行相关的一些基本属性,如运行超时时间、内存大小、并发度等,再关联第一步中构建的镜像即可。
第三根据我们自身的业务场景选择合适的触发方式来请求函数计算平台比如我们可以通过创建和设置一个HTTP触发器来链接事件源。
第四通过TTP触发器进行请求。由于自定义镜像是HTTP Server的形式存在我们通常会设定监听的IP、端口如0.0.0.0:9000、超时时间、HTTP参数如Keep-alive等。这里需要说明一下如果是首次启动函数计算平台会从镜像仓库拉取自定义的镜像并启动容器。
最后函数计算服务控制器会将接收到的请求调度到对应的HTTP Server由HTTP Server接管后续的服务处理。
这样,我们就通过自定义镜像的功能,达到了快速开发和交付的能力,对于老的服务改造迁移,成本也非常低了。
如何优雅地切换流量?
我经常会被客户问到我们的函数功能开发完成后如何灰度发布怎么保证我的流量无损切换如何让我小流量实验后再全面转正呢这可能是刚接触FaaS的通用问题了。接下来我们可以看看函数计算的流量是如何做到优雅切换的。
这里引出两个概念:版本和别名,函数计算支持为发布的函数版本创建别名。别名可以理解为指向特定版本的一个指针。你可以利用别名来轻松实现流量切换、版本回滚以及灰度发布等功能。下面这个示意图,可以很好地说明这个运行机制。
以HTTP触发器为例如果没有别名每次新版本上线你都需要手动修改HTTP触发器关联的版本号。如上图所示。
在实际业务开发中,一般会基于最新版本$LATEST的代码来开发。针对开发好的函数你可以发布一个版本出来比如Public Version1函数计算平台会记录发布的版本信息通过配置一个别名指向该版本就可以用来处理对外的请求了。
而且,你还可以在$LATEST版本上继续开发。同样开发完成之后只需要再次发布新的版本Public Version2即可。
如果此时,我们需要将流量切换到新发布的版本上,只需要切换别名的指向即可,而用户在这个过程是无感知的,也不会有任何的流量损失。
如果我们对新的功能版本不是那么放心还可以设置主版本和新版本的流量比例。比如我们设置95%的流量指向主版本5%的指向最新版本。观察没问题后,再全流量切换到新版本。如果出现问题,需要进行回滚,只需将指针指向原来的版本或者关闭新版本的测试流量即可。
这样就可以优雅地完成版本的迭代升级了。别名无法脱离服务或版本单独存在,使用别名访问服务或函数时,函数计算会将别名解析为其指向的版本,调用方无需了解别名指向的具体版本。有了版本控制和别名,我们就可以在线上服务不受影响的基础上,完成优雅的流量变更。
如何削峰和容灾?
上线的平滑性问题解决了,那么,函数在运行的时候呢?是否也像微服务那样,考虑到了服务的治理呢?
这里我们聚焦一下看看微服场景下老生常谈的流量突增和服务处理异常的情况。这种问题在Serverless领域也同样需要关注。
微服务中主要是通过异步消息队列的方式来应对流量峰值当然这只是针对延时不敏感的服务场景来说如日志的ETL、批量处理、事件处理等。针对这种情况在函数计算的Serverless平台中也往往会通过引入消息队列的方式先将突增的流量引入进来然后再通过后端的调度系统调度给云函数处理。
这样的做法,既可以避免突增流量使得后端资源池来不及扩容,导致出现失败的情况,又让函数计算平台可以快速地响应请求方,避免等待,由函数计算平台来保证请求可靠地执行。
既然谈到万一资源池扩容没跟上请求速度,或者像公有云这种资源池出现调度问题的情况了,那么我们可以想一想,函数计算平台是否有容灾的能力呢?
从平台的角度来说,云厂商都是做了同步重试、异步延时重试的逻辑的。针对异步的信息,甚至引入了递增重试机制和异步策略到用户自己的容灾队列、容灾处理函数中去处理,来进一步保障消息的可靠传输和处理。
那么,平台是怎么做的呢?一般来说,云厂商会选择在函数计算引擎之上增加流量转发和调度层。如果收到函数实例返回错误的信息,通过在调度层用户配置的策略来进行针对性的分发即可。
从使用者的角度来看,针对延时不敏感的场景,建议选择异步方式来处理函数的计算,同时配置容灾队列或者云函数的链式调用方法来解决处理异常的情况。
到这里我们今天要讲的内容也快进入尾声了。不过要做到微服务成熟的治理模式对于Serverless 的函数计算平台和业务来说,都还有比较长的路要走。
FaaS作为云原生时代的“连接器”“胶水语言”在处理复杂场景逻辑的时候必不可少地要跟其他VPC内的服务进行交互在开发和运维过程中也需要完善的开发工具链和可观测能力的支撑。后续我也会逐一来展开跟你介绍其中的实现机制和实战策略。
小结
最后,我来小结一下今天的内容。虽然通过前面的小节,你已经可以在一些简单的场景使用函数计算了,但复杂的场景还需要进一步掌握它的一些高级属性,这些属性包括层、自定义镜像、别名和版本、异步策略的使用等。
函数计算平台通过提供层的能力让你能够轻松地封装起公共的代码库和工具依赖库解决了公共代码复用和依赖的问题考虑到传统开发者切换的需求采用自定义镜像的方式不仅可以提高开发和交付的效率还能够从一定程度上复用原来的CICD系统和老的服务模块进一步提升人效。
考虑到我们的服务是需要上生产环境的别名和版本这一对孪生兄弟也就正好有了用武之地。其实在其他的云产品中也有这种用法比如Elasticsearch中的别名也有类似的作用。
服务的线上运行少不了容灾降级和应对峰值压力的考量虽然函数计算具备弹性伸缩的能力但考虑到资源池的调度速度和未知的错误容灾作为一名“FaaSer”你也应该学会将微服务的成熟的经验引入进来并在函数计算、Serverless领域发挥起来。
如果你的工作内容仅限于使用函数计算平台来开发业务代码,那么通过本节课程,你应该对平台的使用和注意事项已经非常熟悉了,对开发一个高可用、高可扩展的函数服务也一定有了较为深入的理解。
如果你的工作角色是一个云平台开发者那么本节课对你步入函数计算领域或者进一步梳理、补充FaaS形态的Serverless平台的能力都是一个很好的参考支点。
思考题
好了,这节课到这里也就结束了,最后我给你留了一个思考题。
函数计算形态下的Serverless是如何打造高级的能力来应对长进程的处理场景的
欢迎在留言区写下你的思考和答案,我们一起交流讨论。感谢你的阅读,也欢迎你把这节课分享给更多的朋友一起交流进步。

View File

@ -0,0 +1,254 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
04 冷启动:如何加快函数的第一次调用过程?
你好,我是静远。
前面几讲我们基本上都是在谈Serverless的“好处”或者如何“用好”Serverless。那么Serverless有没有不完美的地方呢
带着这个问题我今天就来跟你聊聊它的一个瑕疵——“冷启动Code Start”。这个问题比较重要为什么呢因为不论是面对私有化企业还是公有云上的客户大家首先问到的通常都是和冷启动相关的函数性能问题。冷启动关乎到服务的时延等稳定性问题进而会影响到用户使用服务的体验。在“即时时代”的今天这是一个不可忽视的问题。
这节课,我将通过分析冷启动存在的原因、冷启动的过程,从平台和开发者的角度,带你了解如何加速函数的启动,掌握这里面的优化手段。
冷启动是什么?
在前面的课程中,我们知道,当请求被调度到函数实例上时,如果这个函数实例在上一次执行完代码后没有被回收,那么接收到请求后只需要复用这个实例进行代码的执行即可,这个过程被称为热启动过程。
如果服务是首次请求,或者是容器实例在服务请求后被回收了,就会触发冷启动。那么,冷启动具体是怎么工作的呢?
冷启动都有哪些步骤?
业界中,云厂商或者开源项目因为优化手段的不同,导致冷启动耗时或多或少会有些不一样,但基本的原理是差不多的。我们看一下冷启动都有哪些步骤。
第一步,容器创建。这一步通常会体现在扩缩容过程,当所有容器实例都在处理请求时,就需要向集群申请创建新的容器。
这里需要提一下函数计算平台通常会支持多种语言的运行时这些运行时一般来说会打包一个镜像以DeamonSet的方式运行在Kubernetes集群中。我们在冷启动的时候会根据不同的参数请求动态挂载所需的运行时到对应的运行路径下。这个运行的过程我没有体现在图上而是把它算作资源调度层面了。
第二步,代码、层依赖下载。这一步应该是整个冷启动耗时比较长的过程。函数计算本身并不具备持久化的能力,代码包和层依赖通常都是从其他存储服务端拉取。因此,这一步的耗时会受到用户上传代码包大小、网速等因素的影响。当然,代码包通常是压缩包的形式,下载到本地后,还需要解压。
第三步,环境变量、参数文件准备。主流的函数计算平台往往提供了环境变量注入的能力,这个过程就发生在冷启动阶段。除此之外,运行时以及容器本身可能还需要准备一些参数配置文件等,这个过程耗时相对较短,我们可以暂时不关注这块的优化。
第四步用户VPC以及相关资源准备。如果用户还为函数接入了私有网络那么还需要为容器进行一些VPC网络打通的初始化工作。同时如果用户使用了类似分布式文件系统这类功能还需要进行挂载也会产生一部分额外耗时。
第五步运行时初始化。通常指的是云厂商标准的Runtime环境的启动过程。这一步受编程语言类型的影响比较大比如Java类型的代码由于要启动JVM启动相对其他语言就比较慢。
第六步,用户代码初始化。这一步有的时候和运行时初始化容易混淆,为什么这么说呢?比如编译型的语言,打包后就是一个完整的可执行程序,在运行时启动的时候,就已经有一部分工作加载起来了。那么,如果是解释型语言,我们就可以把它理解为用户代码包的加载过程。
因此针对这一步我们可以理解为用户的自定义业务逻辑代码的加载某些业务逻辑init的过程比如写在Handler之外的数据库连接的创建、Handler方法内的缓存初始化等。
看到这里你可能会有一个疑问,如果使用的是公有云的函数计算平台,一旦代码包比较大,就会增加整个调用链的耗时,那么每次冷启动执行的过程,会不会都额外产生费用呢?
实际上是不会的你可以看到上图中我专门区分了平台侧和用户侧两部分耗时函数的计费都是从程序开始执行开始计算也就是从第六步开始收费。即使是Java这种启动较慢的运行时也只是你感觉慢了但费用并没有多收。
影响冷启动时长的因素有哪些?
六个步骤这么说下来冷启动Cold Start的耗时大部分和以下4个方面有关。
资源调度和容器创建过程: 一般在秒级别时间完成;
代码和依赖层的下载过程:取决于代码大小以及是否有加速,一般从毫秒到秒级别不等;
VPC网络的打通过程主要是弹性网卡和路由下发耗时通常在秒级别
运行时与用户代码初始化过程:和用户比较贴近,依据不同的语言,启动时间会有所影响,大概在毫秒到秒之间不等。
从上述的分析,我们基本可以看出,优化冷启动的手段不是一方的事情,需要平台和用户双方努力去完成,遵循的逻辑就是“哪里慢就让哪里快”。下面,我们一起让“慢”的地方“快”起来。
平台侧的优化手段
细心的你应该能看到,在这张图里,我将不同的阶段用平台侧和用户侧两个阶段进行了标识。那么首先,我们按照函数实例加载运行的时间顺序来看看平台侧的优化。
资源调度和容器创建
我们知道容器启动时一般会先检查本地有无相关镜像如果没有的话需要从远端仓库拉取而函数所处的容器环境通常都是一个Alpine或者Ubuntu之类的基础镜像应用层的资源对象则会基于基础镜像之上进行打包如果出现远端拉取的情况还是比较耗时的。
传统容器运行需要将全量的镜像数据下载后再解压,一方面导致容器启动耗时较长,另一方面也可能因为集群规模过大,下载解压环节造成了较大的网络、磁盘读写压力,进而导致大规模容器启动不符合部署预期。但实际上在启动容器时,可能又仅使用容器镜像内的部分数据。
镜像加速要解决的就是这种传统容器需要提前下载镜像再启动导致耗时较长的问题。一般业界会从两个路径入手。
按需加载:使用加速镜像版本,配置加速规则和标签,通过按需加载的模式,免于全量下载镜像,避免带宽浪费和分发效率的影响;
P2P加速利用计算节点的内网带宽资源在节点之间分发镜像降低镜像仓库的压力。
另外,头部的云厂商,还会通过启用多镜像仓库,进一步缓解单仓库的存储和服务压力,来进一步提升资源调度和下载速度。
代码和层依赖下载
在准备好基础资源和容器之后,我们来看代码和层依赖相关的加速方法。它们或多或少和用户也有一些联系,这里,我来给你分享三个常用的方法。
方法一:压缩包暂存
虽然平台不能直接动手操作用户的代码,但我们可以通过实现本地缓存来提高代码以及层依赖的获取速度。
在函数第一次冷启动处理某个请求时会将代码下载到Node节点上并通过特殊编码进行标记。那么当Node上有其他函数实例需要使用这份代码时可以根据标记码查看本地缓存是否存在如果有的话就直接将其挂载到函数实例对应的路径下省去代码包再次下载的过程。
方法二:常用依赖的内置化
平台也可以根据普遍用户的使用场景将一些常用的依赖提前内置化。比如Python做数据处理的NumPy、Panda处理PDF的底层依赖Xpdf等。
这些常用的依赖一般都会在构造容器基础镜像时一起构建。这样,就能保证这些公共依赖在任何容器实例下,都不需要额外下载就能够直接使用。
方法三:极致压缩
我们还可以在用户创建函数时将代码包进行二次压缩来让代码包变得更小。常见的方法是使用squashfs这种高度压缩的文件系统进行处理它甚至可以将几个G的文件压缩成几百M。
跨VPC网络加速
一般从平台的角度来看函数运行在一个专有的VPC集群内如果我们的云函数需要和用户VPC集群内的资源如Elasticsearch、Redis等打交道该怎么办呢
原来业内比较传统的方式是通过动态创建弹性网卡ENI来实现跨VPC的访问。这样做有3个比较明显的缺点
弹性网卡的创新耗时比较久,导致冷启动耗时急剧增加;
弹性网卡占用用户VPC的IP资源如果没有空闲的资源会导致创建失败
造成弹性网卡较大的浪费。
那么我们可以怎么改进呢目前我比较推荐在集群VPC内部创建代理的方式。这个方式的核心在于IP隧道技术的使用使得平台可以在保持函数原始目标IP地址的情况下将流量通过Proxy代理节点的方式触达用户端VPC。
当然函数平台会为关联了VPC的函数创建轻量的虚机将弹性网卡绑定在代理虚机上所有关联该VPC网络的流量最终都会通过代理进行发送。
为了确保服务的稳定Proxy代理机通常采用主备的方式进行正常情况下Node的出口都是通过Master Proxy来代理请求。但Mater Proxy挂掉之后平台会切换到Slave Proxy来转发。
这种方法可以通过监听用户在Console关联VPC的操作时提前进行创建。这样在函数第一次请求处理的时候由于只需要转发操作就可以直接带来一个数量级的性能提升。
需要注意的是这种方式虽好但也有一定的瑕疵我们需要额外消耗Proxy的Node资源。现在你可以先想想它的具体实现方式是怎么样的之后我也会在小试牛刀中跟你仔细来演练和讲解。
用户实例提前预加载
涉及到预加载的过程,一般都跟算法和策略有关系了。我在这里基于一线的经验给你提供一些思路,当然,在应用前,你还是需要根据自己平台的实际调用量和场景进行训练调优,才可以达到不错的效果。下面,我们可以看看一些常用的预加载用户代码实例方法。
第一种基于函数互相调用的场景进行提前预测。这里你可以参考腾讯函数计算公开的函数链式调用预热的方法。如下图所示函数A调用B和C我们可以根据它们之间的调用拓扑关系提前进行预启动函数B和C。
第二种基于函数的版本进行预测如果流量版本1的流量比例是不足100%的那么我们可以推测另一个占据流量的版本X也应该加载起来。这种方式在腾讯函数计算中也公布过。
第三种我们还可以根据提前加载镜像的方式部署到Node中尤其是对于一些大镜像如10G的镜像提前分发然后通过单机侧的Agent上报标记。流量到来时直接分发到预加载过的Node的容器上以快速应对函数的执行处理。
类似的方法还有不少,你还可以在机器学习和预测预取的思路上进一步思考,也欢迎你随时和我讨论。
到这里我们平台侧的优化手段就讨论得差不多了。Serverless的技术在不停地进步如果你是一个平台的开发人员可以看看还有没有哪些方法可以更好地优化你的函数计算平台。当然我们优化的时候也不要忘了ROI。经营成本也是我们每个平台开发者需要关注的重点。
用户侧的优化手段
如果你是一个使用函数计算开发业务代码的人员,由于无法接触到底层基础设施,那还有没有你可以做的事情呢?选择一个功能完善、性能齐全、价格便宜的云厂商平台。是的,这绝对是你第一个要考虑的因素。因为平台会帮你做很多包括上述优化手段在内的事情,免去了你一大部分的烦恼。
那么除此之外我们自身还有没有可以优化的方向呢我这里先给你列一下思考的5个方向
合理控制代码包的大小;
选择性能较高的运行时;
成本可控范围内合理使用预留实例;
定时任务激活延时敏感较高的函数实例;
本地缓存的的合理利用。
你可以先暂停下来想一想,思考之后,我们开始逐一探讨。
合理控制代码包的大小
首先,代码包的拉取是整个冷启动过程最耗时的部分,即使是从内部网络进行下载,如果代码包特别臃肿,也会严重增加整个端到端的响应时长。
因此在函数部署之前,你可以手动删除执行过程中不必要的一些文件和依赖,让整个代码包尽可能的轻盈。
选择性能较高的运行时
在冷启动的耗时流程分析中我们知道编程语言的类型会影响程序的启动速度。通常来说Python、Node.js这类解释型语言的速度都会比较快Golang这种编译形成的可执行二进制文件加载速度也是非常快的。但像Java这一类需要在JVM中运行的程序速度相对来说比较慢。
这里我以阿里云函数计算平台为例通过一个Hello World的程序来跟你直观地看一下语言运行时选取带来的差别。我选取了Golang和Java来做一个对比通过Trace链路跟踪结果如下图所示
你可以很明显地看到Java的冷启动耗时282.342msGolang耗时67.771msJava的冷启动耗时是Golang的4.166倍。另外我们从调用链路还可以发现在运行Invocation过程中Golang的优势也是比较明显的。
成本可控范围内合理使用预留实例
目前主流的公有云函数计算平台阿里云FC、腾讯云SCF、百度智能云CFC等都提供了预留实例功能。
用户可以为某个函数根据实际需要申请固定数量的预留实例,每个实例都已经提前准备好函数代码,并且直到用户主动释放,都会始终处于常驻状态。这样可以随时以热启动的形式应对请求的到来。
这种机制下,函数平台会根据用户的申请为函数实例进行预留。当函数收到请求后,会优先被调度到预留实例上并且以热启动的方式进行处理。直到该函数的所有预留实例都处于工作状态,才会将请求调度到一个非预留的实例上以冷启动的方式执行。
不过,在使用预留实例来解决冷启动问题时,还需要考虑到闲置成本。因为平台需要额外为用户保留一部分资源,需要用户承担一定的费用,因此,使用者对预期的请求量要有一个合理的估计范围。
定时激活
如果觉得预留实例的费用不是很划算,我们还有一种方法。你可以通过一个定时触发器来提前预热你的服务,比如每分钟预热调用一次你的服务,由于函数平台通常会有一定的时间来保留“热”的容器,所以这种方法在一定程度上可以加快你的函数的响应。
有一点需要注意的,也是经常很多业务客户问我的一个事:我的函数涉及到业务处理,不能乱调用。
这时我们要怎么处理呢?其实,一个判断逻辑就可以解决。
def handler(event,context):
if event["exec"]==true:
doSomething()
else:
return
这个示例中我通过一个“exec”的标志位区分了预热和非预热的请求。当然通过预热的方式有的云厂商也做了定时触发策略的配置能力进一步给你节省了成本。你也可以在实际场景中把这个方法和上面说的“预留实例”做个对比看看。
本地缓存的的合理利用
最后在一些AI场景中往往会涉及到数据训练任务。通常我们的处理思路是将数据从远端拉倒本地再执行训练任务。但是如果我们在程序中加上缓存逻辑每次拉取前都提前从本地临时磁盘空间判断数据是不是已经存在就可以避免每次数据拉取造成的延迟了。
小结
到这里,我们针对冷启动的优化手段也聊得差不多了。最后,我来小结一下今天的内容。
今天我从冷启动存在的原因、工作原理入手,总结了冷启动存在的一些核心因素。从这些因素着手,以平台侧和用户侧的视角,我也提出了相关的优化手段。相信我在一线工作中积累的经验一定对你有所帮助。
冷启动的过程包含容器创建、代码和层依赖的下载、环境变量与参数的准备、VPC网络及相关资源准备、运行时和用户代码初始化一共6个部分。
如果你是一个平台的开发者可以通过4种方法来优化冷启动的耗时时长。
镜像加速手段优化资源调度和容器创建如果你使用的是云平台厂商的Kubernetes调度服务还可以通过配置预加载脚本的方式来进一步并行处理你的容器镜像
包的缓存、极致的压缩和内置化通用依赖:减少用户在运行时刻的依赖;
利用代理模式的方法进行VPC网络加速VPC的网络加速通常只会在跨VPC的业务场景用到通过IP隧道技术 + 代理模式的方法让前几年一直比较诟病的网络延时问题降低到毫秒级别这是Serverless函数计算领域的一个比较大的突破
通过函数实例的预测算法,也能从一定程度上帮助用户解决冷启动问题。
如果你是一个业务开发者我也在本节课给了你5个方向的指引。
合理控制代码包大小:这里需要我们尽量改掉原来在传统编码部分不好的习惯,尽可能精简代码;
选择性能较高的运行时相信通过Golang和Java的对比你也能直观地感受到语言选择带来的性能差异
预留实例和定时预热:这两个方向,也是用户和平台相互协作的过程,更需要你灵活使用;
合理利用本地缓存:对于一些大的数据拉取场景,可以适当的使用缓存的方式,避免频繁拉取数据,造成的网络带宽和执行耗时,这不仅影响你的服务处理性能,还会额外增加费用成本。
Serverless的技术一直在不断的更新迭代希望我今天的抛砖引玉能带动你的思维举一反三想出更多的优化方法。
思考题
好了,这节课到这里也就结束了,最后我给你留了一个思考题。
你在实际工作中,还用到了哪些优化手段?或者,对哪些场景卡住了,需要一起来讨论交流的?
欢迎在留言区写下你的思考和答案,我们一起交流讨论。感谢你的阅读,也欢迎你把这节课分享给更多的朋友一起学习。

View File

@ -0,0 +1,207 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
05 扩缩容:如何应对流量的波峰波谷?
你好,我是静远。
今天我要和你分享的主题是Serverless函数计算的核心特性动态扩缩容。
在介绍扩缩容的实现原理之前我们首先来看这样2个真实的场景。
场景1汽车日志的准实时流推荐服务白天车流量比较大的时候访问量较大夜晚随着车流量的降低需要处理的量就比较小了。
场景2针对线上服务的监控程序以10秒请求一次重要接口的方式进行轮训服务确保服务的可用性。
这两个场景的区别就在于量级是否平稳。试想一下如果用PaaS、K8s服务的思路实现场景1的功能就需要按白天最大请求量的规格来申请资源确保服务能实时处理完。那么夜间大部分的资源就是空闲的利用率很低。对于场景2来说访问量比较平稳PaaS服务的确是比较合理的应用资源的使用也相对合理。
Serverless由于具备弹性扩缩容的能力可以完美地解决场景1的问题而针对这种比较轻量的场景函数计算就是不二的选择了。因为它可以将实例缩容为0并根据请求量级自动扩缩容从而有效提升资源的利用率。
可以说极致的动态扩缩容机制是Serverless函数计算的那顶皇冠戴上它才能彰显和PaaS平台的不同之处。
那么,这个“不同之处”背后的实现机制是什么样的呢?
今天我会带你深入了解不同形态下的自动扩缩容架构原理从K8s的HPA开始分析再去聊聊目前开源Serverless明星框架Knative的KPA看看它的实现和HPA有什么不同。最后我们再提取出自动扩缩容的核心思想让你能够理解自己要设计一个系统的时候该怎么做。
基本框架
开源的Serverless函数计算引擎核心基本上是通过K8s的HPA来做而云厂商一般有封装好的各种底座服务通常可以基于底座的产品来封装例如容器服务、容器镜像服务、云服务器等。但我们也不排除云厂商基于开源框架做二次开发的可能性。
云厂商容器调度服务的发展历史中通常有两种调度形态一种是基于节点Node调度的方式一种是基于容器实例的方式我们通常把这种称为Serverless的即不感知Node维护的方式你可以简单地认为前者是拿了一整套房子过来自己维护整套房子和隔间的运行而后者只关心隔间的运行情况。
由于云厂商的函数计算通常是基于容器服务的底座来进行的接下来我就从Node和Pod两个形态分别来跟你讲一讲它们的调度方式。
Node维度
首先我们来看Node调度形态下函数计算框架是如何进行动态扩缩容的。
如上图所示这是一张基于容器PaaS服务和云服务Node的扩缩容调度示意图我来解释一下它的各个部分都有着什么样的作用。
Scheduler调度模块负责将请求打到指定的函数实例上Pod同时负责为集群中的Node标记状态记录在etcd中
Local-controllerNode上的本地控制器负责管理Node上所有函数实例的生命周期以DeamonSet的形式存在
AutoScaler自动扩缩容模块会定期检测集群中Node和Pod的使用情况同时根据自定义策略进行扩缩容。扩容时往往会向底层的PaaS资源进行申请例如百度智能云的CCE、阿里云的ACK/ASK等
Pod 也就是上图标识的Cold和Warm两种状态的容器Cold表示该Pod未被使用Warm表示正在被使用或者处于等待回收的状态
Node包含闲置、占用两种状态如果一个Node上所有Pod都是“Cold”那么该Node为闲置状态否则为占用。
当系统运转时AutoScaler会定期检查集群中所有Node如果检测到Node处于一个需要扩容的状态则根据策略进行扩容。当然这个策略完全可以由你自己定义通常可以按照“占用除以总数的比例是否处于一个合理区间”来进行判定。
在这种形态下, Pod通常会作为函数实例的通用形态。代码以及不同运行时往往也是以挂载的形式注入到容器内。Autoscaler也会在轮询时根据Warm Pod的闲置时间将其重置为Cold Pod从而让整个集群资源达到一个较高复用能力的水平。
在缩容时理想情况下AutoScaler可以将Node缩容到0但通常为了应对突发的流量请求也会预留一部分Buffer存在。
你会发现Node的调度方式对于函数计算调度引擎来说更加的灵活。但“权限越大责任越大”Node维度的方案除了需要管理Node的调度之外还需要处理Node中Pod的安全隔离和使用。这种形态下我们可以通过“空Pod”的方式来提前占用预加载一部分Pod出来加快“真实Pod”的创建速度。
Pod维度
可能你也猜到了以Pod为扩缩容单元的好处就在于它能够更加细粒度地控制函数实例的数量。下面我们就从扩缩容的“鼻祖”HPAHorizontalPodAutoscaler开始了解Pod维度的扩缩容动作流程。
开始之前你可以先思考这样一个问题HPA既然就是用来负责自动扩缩容的那么它是否可以直接用于Serverless呢先不要急着回答我们先看HPA的扩缩容机制。
常规扩缩容
在K8s官网有这样一段关于自身扩缩容控制器HPA的定义。
The HorizontalPodAutoscaler is implemented as a Kubernetes API resource and a controller. The resource determines the behavior of the controller. The horizontal pod autoscaling controller, running within the Kubernetes control plane, periodically adjusts the desired scale of its target (for example, a Deployment) to match observed metrics such as average CPU utilization, average memory utilization, or any other custom metric you specify.
不用非要看懂,这里说的,大体可以理解为这个扩缩容控制器做了两件事:
第一件事定期从k8s控制面获取资源的各项指标数据CPU利用率、内存使用率等
第二件事,根据这些指标数据将资源数量控制在一个目标范围内。
从K8s的官方文档中你基本可以看到HPA的雏形HPA通过控制Deployment或者RC来实现对Pod实际数量的控制。官方给出的HPA架构图可能还不太好理解我们把指标metric数据的收集过程加上再看看
具体的工作流程是什么样呢?你可以对照着示意图来理解我下面说的流程。
在K8s中不同的Metric会由对应的Metrics Server持续采集Heapster 或自定义 Metrics ServerHPA会定期通过Metrics Server的API或者聚合的API Server 获取到这些Metric指标数据CPU 和 内存使用情况从而根据你自己定义的扩缩容规则计算出Pod的期望个数最后根据Pod当前的实际数量对RC/Deployment做出调整使Pod达到期望的数量。
讲到这里你应该能很快地回答出来HPA形态的扩缩容还不能直接用于Serverless。
因为Serverless语义下的动态扩缩容是可以让服务缩容到0的但HPA不能。HPA的机制是通过监测Pod的Metrics指标来完成Deployment的扩缩容如果Deployment的副本数缩容到0流量没有了指标为0那HPA也无能为力了。
极致扩缩容
正因为如此从0到1的扩缩容过程是需要额外的机制来加持的。由于目前社区和各大云厂商都非常重视Knative部分的传统企业在转型过程中也选择Knative作为私有化部署方案的底座首选。所以我这里同样选择以Knative的KPA来做说明。
Knative的扩缩容主要包括三个方面流量指标的收集、实例数量的调整、从0到1的过程。
流量指标的收集
扩缩容最重要的一步,就是收集我们所需要的指标。我们可以对照着流程图来理解一下。
在Knative中Revision代表一个不变的、某一时刻的代码和Configuration的快照。每个 Revision会引用一个特定的容器镜像和运行它所需要的任何特定对象例如环境变量和卷再通过Deployment控制函数实例的副本数而我所说的实例就是图中的User Pod了。
和我们之前介绍的Node维度扩缩容机制中的函数实例不同这里你可以看到每个函数实例User Pod中都有两个容器Queue Proxy和User Container。其中User Container我相信你一定猜到了是部署和启动我们自身业务代码的容器那么Queue Proxy呢
实际上在每个函数实例被创建时都会被以Sidecar的方式将Queue Proxy注入也就是在原来的业务逻辑上再新加一个抽象层。Queue Proxy作为每一个User Pod的流量入口负责限流和流量统计的工作。每隔一定时间AutoScaler都会收集Queue Proxy统计的流量数量作为后续扩缩容的重要依据。
通俗地来讲其实就是在自身业务代码的容器旁边又加了一个双胞胎Queue Proxy双胞胎加入的方法就是Sidecar。后续不管在这个房子的User Pod兄弟干了什么都会让这个双胞胎去记录。在最外层的AutoScaler则会去找这位双胞胎收集所有的信息为后续的扩缩容做准备。
实例数量的调整
当收集到流量的指标后AutoScaler就需要根据指标调整实例的数量了。Autoscaler会通过改变实例的Deployment来决定实例最终的个数以便确定扩容出来多少个Pod。
简单的算法,就是按照将当前总的并发数平均划分到期望数量的实例上,使其符合设定的并发值。
这里简单举个例子比如当前总并发数为100设定的并发值为10那么最终调整出的实例数量就是100/10=10个。当然扩缩容的实例数量还会考虑到系统的当前的负载和调度周期等因素。
从0到1
看到这可能你会有个疑问我们能够确定的是当Revision存在实例时流量接收的情况那么如果Revision实例缩容到了0流量又是怎么被接收的呢是直接丢弃还是暂存在某个地方呢
为了解决这一问题Knative专门引入了一个叫做Activator的组件来做流量暂存和代理负载。
我们还是来看这张图。事实上当AutoScaler将函数实例缩容为0时会控制Activator作为实例为0时的流量接收入口也就是图中红线的部分。Activator在收到流量后会将请求和信息暂时缓存并主动告知AutoScaler进行扩容直至成功扩容出函数实例Activator才会将暂存的流量转发到新生成的函数实例上。
这里带来一个问题流量什么时候会切换到Activator或者Knative的Pod上呢我们会在流量转发一节中仔细讲此类问题。当然你也可以提前关注ServerlessServicesSKS和 TBC (target-burst-capacity)等关键组件和参数的相关设置。
扩缩容模型设计思路
到这里我们已经从Node和Pod 两个不同的维度学习了Serverless比较典型的扩缩容机制方案了。那么思考一下如果让你来设计一个扩缩容系统该怎么去实现呢
Node和Pod的方案一定给了你不小的启发。下面我将从指标、决策、数量三个核心点来给你讲一总体的设计思路。
指标
无论是Node纬度还是Pod纬度我们会发现最终系统都是根据一些监控的指标来作为扩缩容的依据同时这些指标的采集任务往往都是通过定时轮询的方式来完成。
比如Node扩缩容案例中用的是Node 处于空闲或占用的数量HPA中采用CPU利用率、内存使用率来作为Pod扩缩容的依据而Knative是通过Collector统计Pod的并发情况来进行扩缩容判断。
所以,指标的选取是扩缩容的首要因素,你需要按照你的平台特性,来确定需要收集哪些指标,并且通过某种手段将它们收集上来为你所用。
决策
拿到指标之后最后都会通过一个专门负责扩缩容的模块如AutoScaler来根据这些指标进行决策。决策的过程就是以这些指标作为输入最终输出是否扩缩容、扩容多少或者缩容多少。
决策过程完全可以由你自己定,比如按照比例、固定数量,按照操作后的指标平均值等等。
你也可以根据系统调度情况设置不同的调度方式比如针对流量突然上涨采取及时扩容针对流量的平滑升降采取延时扩容等等。像Knative就有两种模式Stable /稳定模式和Panic /恐慌模式。
数量
决策结果出来之后就简单了。你可以根据结果来让资源数量处于一个预期值这一步完全不需要你操心不论是Node或者Pod维度K8s的HPA都会来做好。
为服务引入预测能力
最后还记得我们最开始说的场景1的日志处理情况吗日志的流量出现了明显的波浪式浮动比如晚上23点之后流量减少上午6点之后流量增加。那么为了应对流量的波峰到来比较迅猛的情况我们还可以提前预测预扩让系统的扩容更平滑。
这里还是以Node级别的扩缩容为例。我们可以在扩缩容模块的基础上以旁路的方式额外增加一个预测模块。定期统计Node状态和数量的信息同时根据历史信息对外进行周期预测。当然历史的信息时间窗口和预测周期都需要经过大量的数据训练以及人工干预因子才可以在生产环境用得比较顺手。
但我们也要知道,它不是“万金油”,并不是什么情况都能预测准确,只能说在一定阈值范围内起到一定的辅助作用。
同时,这是一个性能和成本博弈的过程,尤其对于云厂商来说,极致的优化成本是我们每个研发人员追求的目标。
小结
最后,我来小结一下我们今天的内容。
这节课我从常见的两个场景案例入手介绍了Serverless核心特性扩缩容相对于普通PaaS服务的优势。这个优势不仅体现在资源的利用率上进一步来说更反应在成本上。
接着我从实际市场运用和历史演进的角度出发介绍了Node和Pod两个维度中市面上常见的扩缩容机制。介绍的重点放在了Kubernetes的HPA以及目前比较火热的明星Serverless引擎Knative的KPA机制上也从流量指标的收集、实例数据的调整和区别最大的的0到1的过程来讲解了整个扩缩容的原理。
而上述的讲解,都是为了我们能从共性中抽象出一套设计扩缩容系统的思路,这里面,最主要的还是要抓住三个核心要点:指标、决策、数量。
最后,我们如果是要以平台的视角提供服务,还可以做得更智能,通过预测系统让扩容变得更加平滑,性能更优。
思考题
好了,这节课到这里也就结束了,最后我给你留了一个思考题。
想一想,还有没有什么更好的方法能够避免系统频繁扩缩容,从而保证稳定的资源供给呢?
欢迎在留言区写下你的思考和答案,我们一起交流讨论。感谢你的阅读,也欢迎你把这节课分享给更多的朋友一起进步。

View File

@ -0,0 +1,256 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
06 流量转发:函数在不同情形下是如何执行的?
你好,我是静远。
在前面的课程中我跟你分享了函数实例的冷启动和扩缩容这两个点是Serverless的核心特征。可以说提到Serverless必然会提到冷启动和扩缩容。但你是否想过是什么促使Serverless的函数实例从0到1从1扩容到N又从N缩容到0的呢
这正是我本节课程要跟你分享的主题——流量机制。确切地说是流量在这些情形下的转发机制。希望通过这节课你能够了解Serverless在冷热启动、常规流量升降、异步请求、多并发等不同情形下流量的转发过程并在脑海中构筑出一幅Serverless的全链路流量转发拓扑图。
这节课我选择了Knative作为开源的Serverless引擎框架来介绍冷启动和分流机制的流量转发。至于详细的开源引擎的分析、以及开源引擎私有化整体解决方案我会在第三模块实战进阶中跟你详细探讨。
知识储备
在讲流量转发之前我们先来回顾一下Knative它主要包括Eventing、ServingBuild三大组件其中Build现在已经被tektoncd/pipeline替代。这三大组件中跟我们主题相关的主要是分管流量的Serving组件。
Knative Serving定义了一组特定的对象使用Kubernetes CRD的方式来实现这些对象。我们看一张Knative官方简单示意图
当用户创建一个Knative Service的时候它的Controller会自动创建对应的Route和Configuration资源其中Configuration就对应了我们关于业务代码的镜像配置每次Configuration修改后就会创建出对应的Revision版本。
Revision则代表某一时刻的Configuration的快照每个Revision会拥有一个自己的Deployment如果流量策略定义了转发给不同的Revision那么实际上就是转发给这些Revision的Deployment。
图中的Route继承了Service中的流量策略的相关配置也会决定请求的流量转发走向。如果Service中没有定义相关流量策略的话那么就是默认所有流量转发给最新的Revision。
流量转发
有了Knative Serving基本概念的知识储备我们就可以开始从流量的入口说起了。
入口流量
入口的网关层面Knative从设计之初就考虑到了扩展性的问题通过抽象出Knative Ingress资源来对接不同的网络扩展。一般来说我推荐使用Kourier、Istio、Contour这三种来做对接。如果我们的 Knative使用了Istio作为网络组件那么外部流量就会传给 istio-ingressgateway它会根据 Knative Serving 创建的 VirtualService并结合Gateway来决定把流量转发给哪个用户 Service。
那么Gateway和VirtualService具体的角色和协作方式是什么样子的呢我们一起看一下。
Gateway用于暴露对外服务的端口和域名决定了哪些流量是可以经过的。比如下面这个例子定义的是访问域名jy.geekbang.com且指定80端口的流量可以经过这个Gateway。
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: jy-gateway
spec:
selector:
app: istio-ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- jy.geekbang.com
VirtualService用于配置流量的路由类似 Nginx 的 Server 配置通过和Gateway关联来决定流量的最终去处。这里我也给你一个示例结合上面的Gateway这里表示的是从上面网关过来的流量最终会发往jy-demo-v1.default.svc.cluster.local。
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: jingyuan-vs
spec:
hosts:
- jy.geekbang.com
gateways:
- jy-gateway
http:
- route:
- destination:
host: jy-demo-v1.default.svc.cluster.local
冷启动的流量转发
下面我们来看一下Knative的冷启动情况下流量是如何被处理的。
这里需要说明一下为了方便你专注于冷启动的转发过程我将一些不涉及到流量部分的交互暂时去掉了比如ServerlessService的模式转变AcutoScaler指标上报等。
另外图中我针对不同对象资源做了颜色和线条的区分。其中所有的实线矩形都表示Pod对象也就是具体处理请求的进程而虚线矩形则代表非Pod类型的其他资源对象。
这里我们着重介绍图里的Public Service和Private Service它们是决定流量走向Pod IP还是Activator的关键。我们来分别看一下。
Public Service由Knative管控其EndPoints是可变的。如果当前的Revision存在User Pod那么Public Service的EndPoints会与Private Service的保持一致指向这些实例而如果不存在User Pod那么冷启动流程时其EndPoints将指向Activator的IP地址。
Private Service通过Label Selector 来筛选图中对应的Deployment产生的User Pod其他组件可以通过监听 Private Service的EndPoints情况来了解当前的Revision是否有User Pod存在。因为其本身不做流量的转发所以我在图中用灰色体现。
了解了冷启动流程图中每一个区块的含义,我们再来对照着图片了解一下冷启动的实际请求流向。
步骤0这里要完成的是准备工作。当你打包好函数镜像创建好Knative Service之后Knative会根据定义来创建Route、Configurationcfg资源然后cfg会创建对应的Revision版本。Revision又会创建Deployment提供服务。
步骤1当请求到达Istio Ingress Gateway后Istio Ingress Gateway会根据Gateway和virtualService配置信息将请求转发给Public Service。
步骤2由于此时集群中还没有User Pod图中的User Pod需要等到AutoScaler扩容后才有而Public Service的EndPoints配置的是Activator所以请求接下来会交给Activator处理。
步骤3Activator会将收到的请求暂存同时统计请求对应Revision的实际流量并上报给AutoScaler。另外Activator还会不断监听Private Service检查是否已经存在User Pod 了。
步骤4AutoScaler根据流量情况控制对应Revision的Deployment来实现User Pod的扩容。在冷启动过程中会至少保证创建一个Pod出来。
步骤5Deployment创建新的User Pod同时会更新Private Service的EndPoints将Private Service的EndPoints指向新生成的User Pod。
步骤6Activator检测到Private Service关联的EndPoints后会将请求直接转发到新的User Pod。User Pod收到请求后就会执行业务逻辑。
其实在从0到1的过程中还有一点需要说明的那就是在User Pod创建成功后AutoScaler会触发SKS的模式为serve同时将Public Service的EndPoints同步为Private Service的Endpoints也就是切换回了常规流量的处理模式。而新的流量到来后就会通过Public Service直接到达User Pod不再走Activator代理。
但Knative本身引入了一个TBCTarget Burst Capacity来控制什么情况下Activator会从流量路径中摘除这样做的好处是为了防止流量突然的增大导致单个Pod失衡进而影响到请求的丢失。
这就是Knative在冷启动下的流程机制我们可以看出Knative针对流量从0到1的启动过程考虑到了流量的突增的因素在流量入口考虑到了便捷的扩展能力能够更好地复用网络组件。
那么,他又是怎么基于已有的网络组件快速做到流量的治理,做到小流量上线的呢?
流量分流机制
我们在知识储备中了解到Service负责创建Route、Configuration 以及 Revision 资源,通过 Service 可以将流量路由到指定的 Revision中。而在指定的过程中分流的核心是Route它负责把网络端点映射到一个或多个 Revision可以通过多种方式管理流量包括灰度流量和重命名路由。
首先来看一个普通的 Knative Service 定义,该 Knative Service 下有两个版本,分别是 Revision traffic-demo-v1、traffic-demo-v2流量策略按80:20的比例路由到不同 Revisiontag 分别是 tg1 和 tg2。其中流量策略定义如下所示
traffic:
- tag: tg1
revisionName: traffic-demo-v1
percent: 80
- tag: tg2
revisionName: traffic-demo-v2
percent: 20
在 Knative Service 部署到 default namespace 后Knative 就会创建 Route 对象,因为我们的 Knative 用了 Istio 作为网络组件,所以还会继续创建 Istio VirtualServicetraffic-demo-ingress
在图中,你可以看到第一个 VirtualService 关联了 Knative 创建的两个 Gateway第二个关联的 Gateway 是 “mesh”这是 Istio 保留的一个关键字,意思是将 VirtualService 的规则应用到集群里所有的 sidecar这里我们可以先忽略它。
我们看一下traffic-demo-ingress的路由生成由于配置信息量比较大我将route相关的部分列了出来方便你更清晰地查阅
route:
- destination:
host: traffic-demo-v1.default.svc.cluster.local
port:
number: 80
headers:
request:
set:
Knative-Serving-Namespace: default
Knative-Serving-Revision: traffic-demo-v1
weight: 80
- destination:
host: traffic-demo-v2.default.svc.cluster.local
port:
number: 80
headers:
request:
set:
Knative-Serving-Namespace: default
Knative-Serving-Revision: traffic-demo-v2
weight: 20
你可能会想destination 的host格式为什么要命名为traffic-demo-v1.default.svc.cluster.local。
这里我简单提一下这种命名本身就是Kubernetes Service的DNS的名字是Kubernetes内部使用的名称。名称的组成规则是 ..svc. zone 名字一般是 cluster.local。比如 namespace default 下部署了一个traffic-demo-v1 service那么它的 DNS 名称就是 traffic-demo-v1.default.svc.cluster.local。
在Knative中我们通常可以通过修改config-domain的配置来自定义一个域名后缀使得外部请求的时候可以访问。再由istio ingress gateway根据virtualService 中配置的这些策略实现分流,最终实现下图的效果。
在小流量/灰度的发布上开源的引擎框架Knative就是这样做的了。但云厂商可能由于平台的完备性具备专门的自研小流量实验基建的服务能力一般也会基于流量的抽样落点来进行。
常用功能点
在上面的内容中我们从流量入口着手了解了Serverless引擎在冷启动和小流量下的流量转发机制。那么云厂商在流量转发上还有哪些加持呢我们可以聊聊平时常用的两个功能点异步调用和单实例多并发下的流量转发机制。
异步下的流量调度
我们从前面已经了解到,同步的调用请求可以通过路由功能进行分流,那么异步和它有什么样的不同呢?
一些离线任务往往适合从同步流程中剥离出来,进行异步处理。比如像日志清洗、定时触发、音视频处理等等。目前主流的函数计算平台都提供了函数的异步处理能力。
用户通过异步接口的API主动发起在异步调用函数时你不需要等待函数代码的响应只要把这次请求交给平台侧处理就可以了。当然更多的场景通常是结合各类异步触发器来使用。比如我们之前学到的对象存储触发器通过设置好存储桶Bucket和存储对象的匹配规则就可以简单快捷地实现音视频转码以及ETL等业务逻辑的处理。
这里,我们可以简单说一下异步下的流量调度过程。
请求首先会到达异步处理模块并按照顺序排队这也是异步处理模块通常会结合消息队列实现的原因。当轮到你的请求被处理时异步处理服务会再将请求发到对应的函数实例进行执行并将执行结果反馈到你配的监控日志上。除此之外像AWS、阿里等主流的函数计算平台还会支持异步的执行策略你可以根据每次异步执行的最终结果来配置“执行成功”和“执行失败”的下游服务。
总的来说,异步调用主要通过事件/消息队列的方式来缓冲一步,然后通过异步处理模块,按照同步请求的请求机制来处理。流量的转发除了在入口处不一样之外,后续的同步请求依然跟上面类似。
多并发下的流量转发
下面我们再来看请求在单实例多并发下的流量调度过程。
先看两个概念:
QPS函数实例Pod每秒可以响应的请求数
并发数同一时刻函数实例Pod接收的请求数。
从这两个概念中我们能够发现并发数的增加并不一定会导致QPS增加。达成了这个共识之后我们再来看一下函数计算的并发实现需要注意哪些要点。
首先你需要是HttpServer的服务类型。
如果你是基于云厂商的标准运行时就需要看一下他是否支持。我会在运行时这节课里给你分析关于GoLang Runtime的代码片段当请求到来时除了以HttpServer的形式运行之外还会通过go func的形式进行处理这种方式可以从一定程度上进一步提升函数的并发处理能力。
其次,你的服务需要能在框架中暴露端口,供流量转发进来。
第三需要有一个组件来控制并发。由于云厂商没有公布实现我们还是回到Knative中来看它提供了queue-proxy和AutoScaler的搭配能力完美解决了流量的并发问题。
queue-proxy 是 一个伴随着用户容器运行的 Sidecar 容器,跟用户容器运行在同一个 Pod 中。每个请求到达业务容器User Container之前都会经过 queue-proxy 容器。
它的主要作用是统计和限制到达User Container的请求并发量。当你对一个Revision设置了并发之后比如设置了10那么queue-proxy就会确保不会有超过10个的请求达到User Container。
那么如果有超过10个请求到来又该怎么办呢它会先暂存在自己的队列queue里面同时通过9090端口统计当前User Pod的并发情况提供给AutoScaler采集和扩缩容参考。
小结
最后我们来小结一下。今天我给你介绍了Serverless形态下的流量转发过程包括冷启动、小流量、异步调用、多并发下几种场景的流量调度机制。
首先我们选取了社区活跃度较高和经验客户使用较多的Knative作为切入点来展开为了便于你理解流量的转发过程我们先解释了Knative的关键资源对象及其作用。通过流量入口的介绍我们可以发现Knative网关层面的扩展性是非常值得我们平时在设计云原生架构时参考的。
接着我详细分析了容器实例在流量到来时从0到1时请求的执行过程。这里面涉及到核心的Activator用来承载从0到1和从N到0的桥接它能根据请求量来对请求进行缓存和负载。与它一起协作的资源有Serverless ServiceSKS和AutoScaler。
同时我们可以通过在Knative Service中定义Revision的流量比例通过创建Istio VirtualService来配置不同版本的分流策略。
在异步场景下我们还可以通过队列的方式来做到削峰填谷进一步完善Serverless平台的能力。
最后我提到了实现并发响应的几个要素HttpServer服务和旁路的流量代理组件是一个不错的组合。通过流量的转发能力使得你可以在Serverless下依然享受高并发的处理能力。
相信在学习过今天的课程之后你已经能通过开源的引擎框架去学习并且在查阅云厂商的介绍时更加的得心应手了。在进阶实战的板块里我还会跟你一起来探讨基于开源引擎构建一个Serverless平台的能力可以期待一下。
思考题
好了,这节课到这里也就结束了,最后我给你留了一个思考题。
在遇到突发流量到来的时候Knative是如何应对的有哪些参数控制
欢迎在留言区写下你的思考和答案,我们一起交流讨论。感谢你的阅读,也欢迎你把这节课分享给更多的朋友一起交流学习。
延伸阅读
关于Knative Serving路由机制可以继续通过官方手册深入阅读一下。

View File

@ -0,0 +1,272 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
07 运行时(上):不同语言形态下的函数在容器中是如何执行的?
你好,我是静远。
在生命周期这节课里,我和你提到了支撑函数运行起来的核心之一是运行时,而且我们还可以在函数计算的平台上选择不同语言的运行时来开发我们的函数,这其实也是函数的一大优势:多语言的运行时,可以极大降低开发者的语言门槛。
那么函数计算层面的运行时是什么呢?不同语言的运行时,它们的工作机制是一样的么?如果让你来自定义一个运行时,你打算怎么做呢?
带着这些问题今天我们来聊聊这背后的实现。我会从源码的角度以编译型语言Golang、解释型语言Python为代表来跟你层层分析函数计算运行时的机制带你抽象出通用的思路并体验如何构建一个自定义运行时。
希望通过这两节课,你能够对运行时的原理和特性有一定的理解,搞清楚函数计算平台究竟是如何打破编程语言技术栈的限制,为开发人员提供多种开发环境的。同时,相信这节课也会帮助你在后续的函数计算使用和开发中更加得心应手。
今天这节课我将重点介绍运行时的基本特性和实现原理并以编译型语言Golang为切入点来讲解它的运行流程让你先有一个从0到1的认知过程。
运行时究竟是什么?
我们对“运行时”这个名词并不陌生任何编程语言都有它自己的运行时。比如Java的运行时叫Java Runtime它能够让机器读懂这些Java的代码并且运行起来换个说法就是它让代码可以和机器“打交道”进而实现你的业务逻辑。
那么迁移过来理解函数计算运行时Runtime就是能够让函数在机器或容器中执行起来实现业务逻辑功能的执行环境它通常由特定语言构建的框架构成。函数计算的运行时依赖语言的运行时存在。不过由于更贴近上层应用分析它的工作原理要相对简单一些。
上面的示意图展示的是运行时、函数的组合与初始化进程的关系。在函数实例初始化时函数运行时一般会由一个初始化进程给加载起来然后Runtime就可以进行服务请求的内部通信正常接收、处理请求。当请求到达后你的代码就会在对应的语言运行时中被加载起来的代码处理。
因此我们可以简单地将Runtime理解为一个特定语言环境下的服务框架环境这个服务将以一个进程的形态运行在用户容器中并与用户代码相关联。当服务启动后会一直等待请求的到来。一旦请求到达运行时就会关联你的代码去执行执行完成后又会继续处理下一个请求。
这里我要强调的是,这个执行不一定是串行的,有的架构为了提升并发,也会采用协程或者线程的方式去执行。
这个流程看起来比较容易理解对不对?那么,我们再深入流程看一眼,想一想,不同的语言运行时的实现,是一样的吗?
实现原理
运行时,归根到底还是一个编程语言编写出来的具体程序,所以对于上面这个问题,我们先来看看编程语言本身有哪些区别。
语言类型
我们知道,计算机只能执行二进制指令,而我们根据不同编程语言转换为二进制指令的时机,将不同的编程语言分为了解释型语言和编译型语言。
编译型语言比如C、C++、GoLang在编译时需要将所有用到的静态依赖、源码一起打包编译完成后就可以直接执行。而像Python、Node.js这种解释型语言则只需要通过解释器执行因此完全可以做到业务代码与依赖分离的形式。
这里需要注意一点我们常用的Java虽然需要经过编译但是编译产生的机器码需要由JVM再次转换为二进制指令因此是具有解释型和编译型两种特性的。我个人更偏向将其定位为编译型语言因为在你使用Java开发函数的时候也会发现我们通常是将所有依赖包打包成一个Jar包或者War包的方式上传比较符合编译语言的风格。
另外如果你使用过不同云厂商的函数计算平台你会发现像GoLang、Java这类编译型语言通常在开发过程中都需要强依赖一个平台提供的包而Python、Node.js则不需要这是为什么呢
在上面提到的语言类型的区别中我们提到因为编译型语言需要将所有关联的静态代码依赖一起打包所以在函数实例上具体的体现就是你的业务代码和运行时会生成一个完整二进制文件、Jar包或者War包。
了解完语言的不同,你应该也做好这两种函数计算运行时的实现也会不同的心理准备了。接下来,我会从从编译型和解释型两个角度来分别跟你聊一聊运行时的实现。
Golang运行时
上面我提到,对于编译型语言,用户代码通常需要和运行时一起编译,所以一般的云厂商都会将编译型语言的运行时开源。
这里我们以阿里云函数计算FC的GoLang Runtime运行时为例一起看看它的实现原理。
GoLang Runtime运行时主要做的事情有三点。
获取请求
在GoLang Runtime中平台会提前将RUNTIME API写到环境变量里Runtime会通过初始化客户端对象runtimeAPIClient从这个接口中获取到请求。
关联用户的入口函数
用户入口函数也就是图中的UserHandler。在Golang 的运行时中其实是通过反射机制获取的并将UserHandler封装到一个统一格式的结构体Handler中再将Handler作为Function结构体类型的一个属性进行赋值这样做的好处是用户完全可以按照自己的编程习惯去定义而不用对UserHandler的结构做出任何限制。
这里解释一下在源代码中作者沿用的是handler图中的UserHandler是我为了区分主进程中的handler而替换的一个名字下文在用户侧定义的handler我们都统一用UserHandler来做区分。
调用UserHandler对请求进行处理
在获取请求与UserHandlerHandleRequest(ctx context.Context, event string)就可以用第二步创建的Function对象去执行请求了。
接下来我将从GoLang用户侧代码的入口函数main函数开始 ,详细地梳理一遍上面的处理过程。
入口
当整个二进制被加载起来后程序首先会进入main函数并将用户定义的函数入口方法userHandler作为参数传入Start方法并对Start方法进行调用。
Start的入参为interface{}这样的传参方式可以让你的userHandler被定义为任何类型。
/**
* base function type
* eventFunction functionType = 101
* httpFunction functionType = 102
**/
func Start(userhandler interface{}) {
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
log.Println("start")
StartWithContext(context.Background(), userhandler, eventFunction)
}
func StartWithContext(ctx context.Context, userhandler interface{}, funcType functionType) {
StartHandlerWithContext(ctx, userhandler, funcType)
}
进入Start()方法后再深入下去最终会调用StartHandlerWithContext这个方法。接着会将全局变量runtimeAPIStartFunction赋值给startFunction这个局部变量。
关于runtimeAPIStartFunction我也列出了下方的代码。你可以发现它包含了一个环境变量名env以及一个之后循环处理请求的方法startRuntimeAPILoop。
func StartHandlerWithContext(ctx context.Context,
userhandler interface{}, funcType functionType) {
startFunction := runtimeAPIStartFunction
// 获取RUNTIMEAPI
config := os.Getenv(startFunction.env)
...
err := startFunction.f(ctx, config, handlerWrapper{userhandler, funcType}, lifeCycleHandlers)
...
}
// runtimeAPIStartFunction是提前定义好的全局变量
runtimeAPIStartFunction = &startFunction{
env: "FC_RUNTIME_API",
f: startRuntimeAPILoop,
}
最后在获取到startFunction.env的环境变量真实值后你会发现前面我们传入的userHandler、上下文Context以及刚刚获取的环境变量都传递给了startRuntimeAPILoop并对它进行了调用这些其实就是请求处理流程之前所需要的关键信息。
准备工作
拿到了函数请求需要的参数后就需要拉取请求并对其进行处理了你可以先通过代码来感知一下startRuntimeAPILoop是如何工作的
func startRuntimeAPILoop(ctx context.Context, api string, baseHandler handlerWrapper, lifeCycleHandlers []handlerWrapper) (e error) {
...
// 创建连接RuntimeAPI接口的客户端之后客户端会从这个接口获取请求信息
client := newRuntimeAPIClient(api)
// 根据传入的UserHandler转化为运行时中标准的Function结构体
function := NewFunction(baseHandler.handler, baseHandler.funcType).withContext(ctx)
...
for {
// 获取请求信息
req, err := client.next()
...
// 启动新的协程让function对请求进行处理
go func(req *invoke, f *Function) {
err = handleInvoke(req, function)
...
}(req, function)
}
}
首先程序会根据前面获取到的RUNTIMEAPI创建一个客户端这样就保证了请求的获取途径。接着会根据之前传入的userHandler以及userHandler类型创建出一个Function类型的对象。
这个Function对象的创建会根据start入口传递下来的function type的值来确定是创建event类型的handler还是http类型的handler分别对应处理事件的请求和Http的请求。
这里我提供的代码中传输的是eventFuntion后文就通过这一条方法调用流来进行追踪。
func NewFunction(handler interface{}, funcType functionType) *Function {
f := &Function{
funcType: funcType,
}
// 这里根据传入funcType来决定构造哪种类型的handler
if f.funcType == eventFunction {
f.handler = NewHandler(handler)
} else {
f.httpHandler = NewHttpHandler(handler)
}
return f
}
event类型的handler会通过NewHandler获得该函数的返回值要求返回一个Handler类型的接口这个接口需要实现一个标准的Invoke方法。
type Handler interface {
Invoke(ctx context.Context, payload []byte) ([]byte, error)
}
func NewHandler(userhandler interface{}) Handler {
...
// 获取userhandler的动态值
handler := reflect.ValueOf(userhandler)
// 获取userhandler的类型信息
handlerType := reflect.TypeOf(userhandler)
...
return fcHandler(func(ctx context.Context, payload []byte) (interface{}, error) {
...
// 通过动态值对userhandler进行调用
response := handler.Call(args)
...
return val, err
})
}
在NewHandler中也会利用GoLang中的反射机制获取到userhandler的类型和动态值并通过反射信息构造出一个有标准传参以及返回值的fcHandler类型的方法。在fcHandler中由于代码中的handler本身为Value类型因此可以通过Call方法调用其本身所代表的函数。如果你对反射的细节感兴趣也可以看看关于Go语言反射的官方手册介绍。
fcHandler是很特殊的它本身是一个函数类型并且已经实现了Invoke方法因此也是一个Handler类型这就解释了上文为什么以fcHandler作为返回值。而fcHandler中的Invoke最后调用了自己本身对应函数对请求进行了处理。
当准备工作就绪后,程序就开始对请求进行处理了,通过上述代码分析不难得出,主协程在这个函数中主要做了三件事:
准备获取用户请求的客户端newRuntimeAPIClient以及Funtion对Handler封装
不断通过客户端获取新的请求也就是代码中的client.next()方法;
分配一个新的协程并让Function在新协程中处理获取到的请求。
执行流程
那么进入新的协程以后请求才会真正地被最初传入的userHandler所执行我们深入到协程中的handleInvoke方法会发现存在这样的调用关系
->handleInvoke 1
->function.Invoke 2
->function.invokeEventFunc || function.invokeHttpFunc) 3
->function.handler.Invoke 4
在代码标号的第三步里如果我们在入口start处传递的是httpFunction类型这里就会调用function.invokeHttpFunc。当然我们还是继续沿着上面提到的event事件请求来追踪继续调用function.invokeEventFunc在这个函数里面会调用fn.handler.Invoke。
结合上面的函数调用关系来看当执行到f.handler.Invoke时实际上Invoke会对fcHandler进行一次调用最后fcHandler通过handler.Call完成了对userHandler的调用。
type fcHandler func(context.Context, []byte) (interface{}, error)
func (handler fcHandler) Invoke(ctx context.Context, payload []byte) ([]byte, error) {
response, err := handler(ctx, payload)
if err != nil {
return nil, err
}
responseBytes, err := json.Marshal(response)
if err != nil {
return nil, err
}
return responseBytes, nil
}
我将上面的流程梳理成了如下的示意图你可以对照着再回溯一遍GoLang运行时的主流程
这就是GoLang Runtime运行时在event调用的主流程那么针对更细的流程和定义你可以从Github上将代码下载下来按照这个思路逐一理解就可以了。
通过GoLang Runtime运行时的学习相信你已经清楚了解了运行时需要完成的工作以及它整个的处理流程。
今天我们就先讲到这里,你可以先消化一下。解释型语言的运行时如何运行?在云厂商不开源情况下,我们又要如何剖析它?这些问题,我们下一节课再来继续讨论。
小结
最后我来小结一下我们今天的内容。这节课我给你介绍了以Golang为代表的编译型语言运行时在Serverless函数计算形态下的实现原理函数计算运行时Runtime本质上就是一个让函数在容器中执行起来的代码框架。
运行时通常会由一个初始化进程加载起来,然后进行内部服务的通信,接收和处理该函数收到的请求。
根据编程语言类型的不同运行时的实现上也会略微有所不同。编译型语言的运行时需要和用户代码一起打包成二进制文件或者其他特定语言类型的包如Jar包、War包而解释型语言的运行时则可以与用户代码分离存在。所以厂商一般都会将编译型运行时的代码进行开源以SDK的形式提供给开发者使用。
从Golang Runtime的代码框架中我们可以看出运行时主要就是获取请求、关联用户的函数入口Handler、执行用户的实现。
希望你通过今天的课程,能够对函数计算形态下的语言运行时有一定的了解,不仅会用,更知道它如何实现的,在后续遇到问题或者开发更复杂的功能时,能够做到心中有数。
思考题
好了,这节课到这里也就结束了,最后我给你留了一个思考题。
我们上一节课中讲了Knative那么Knative涉不涉及运行时一说呢运行时只存在在云厂商的平台上么
欢迎在留言区写下你的思考和答案,我们一起交流讨论。感谢你的阅读,也欢迎你把这节课分享给更多的朋友一起交流学习。

View File

@ -0,0 +1,295 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
08 运行时(下):不同语言形态下的函数在容器中是如何执行的?
你好,我是静远。这节课,我们继续来看运行时的原理和实践。
在上一节课里我跟你分享了运行时的基本理念以及不同语言下运行时的区别并且通过GoLang的例子让你对运行时有了一个具象化的理解。
今天我将重点介绍后半部分解释型语言的实现和异同。通过今天的课程相信你会对运行时有一个从1到N的认知并从中抽象出一套构建思路用于自己的实战中。
Python运行时
通过GoLang Runtime运行时的学习相信你已经了解运行时需要完成的工作以及整个处理流程了。
在前面我也提到了编译型语言的业务逻辑与依赖通常都存在着强绑定的关系需要将运行时和用户编写的函数代码统一编译成一个二进制文件或者Jar包、War包后再被执行但解释型语言不需要。这也从一定程度上说明了很多函数计算平台只对编译型语言运行时进行开源的原因。
接下来我以Python语言为例来介绍一下解释型语言的运行机制带你进一步加深对函数计算运行时的理解。
如果你使用Python或者Node.js开发过函数计算的代码你会发现其实上传的文件完全不需要任何依赖甚至两行代码就可以定义好你的函数入口比如下面这个Python的案例:
def handler(event, context):
return 'hello world'
在这种情况下其实是不利于我们对运行时做分析的但有的云厂商提供了登录函数实例的能力。我们就继续以阿里云函数计算FC来说明它提供的实例登录功能可以让我们直接到实例中查看运行时的执行过程。
路径
登录函数实例后,我们首先要找到运行时被挂载在哪个路径下。想一想,我们有可能在哪里找到它呢?
运行时作为一个容器的进程不断等待请求的到来它的生命周期和函数容器实例是保持一致的因此我们可以直接在当前路径下使用“ps” 命令,查看当前实例内进程的运行状态。
root@s-62c8e50b-80f87072536d411b8914:/code# ps -ef
UID PID PPID C STIME TTY TIME CMD
user100+ 1 0 0 02:16 pts/0 00:00:00 /var/fc/runtime/rloader
user100+ 4 1 0 02:31 pts/0 00:00:00 /var/fc/lang/python3/bin/python3 -W ignore /var/fc/runtime/python3/bootstrap.py
root 6 0 0 02:31 ? 00:00:00 /bin/sh -c cd /code > /dev/null 2>&1 && COLUMNS=167; export COLUMNS; LINES=27; export LINES; TERM=xterm-256color; expor
root 7 6 0 02:31 ? 00:00:00 /bin/sh -c cd /code > /dev/null 2>&1 && COLUMNS=167; export COLUMNS; LINES=27; export LINES; TERM=xterm-256color; expor
root 8 7 0 02:31 ? 00:00:00 /usr/bin/script -q -c /bin/bash /dev/null
root 9 8 0 02:31 pts/0 00:00:00 sh -c /bin/bash
root 10 9 0 02:31 pts/0 00:00:00 /bin/bash
1号进程为实例启动的初始进程4号进程是通过1号进程fork出来的它其实就是Python运行时了而bootstrap.py是整个运行时的入口看进程的启动参数就可以知道Python运行时的路径其实就在/var/fc/runtime/python3/ 下。
核心流程
针对Python运行时这一部分我将只截取main函数的部分代码来跟你介绍它的主要流程你也可以顺便体会一下Python和GoLang运行时在处理请求中有哪些异同。完整的代码你可以进入到/var/fc/runtime/python3/中看到。
def main():
......
try:
_init_log() # 日志初始化
...
_set_global_concurrent_params() # 设置全局并发参数
fc_api_addr = os.environ['FC_RUNTIME_API']
fc_runtime_client = FcRuntimeClient(fc_api_addr) # 创建runtime client
_sanitize_envs()
except Exception as e:
...
return
# 等待请求
....
while True:
try:
if global_concurrent_limit == 1:
request = fc_runtime_client.wait_for_invocation()
else:
request = fc_runtime_client.wait_for_invocation_unblock()
...
handler = FCHandler(request, fc_runtime_client) # 获取函数入口函数
if global_concurrent_limit == 1:
handler.handle_request() # 执行业务逻辑
else:
# 如果是多并发执行,则通过线程启动执行任务
from threading import Thread
Thread(target=handler.handle_request).start()
except Exception as e:
...
从代码结构看整个运行时的核心处理流程和GoLang运行时中的逻辑基本类似包括了以下三个方面。
服务初始化: 包括日志初始化、设置并发模式下的全局参数。同时还是通过FC_RUNTIME_API这个环境变量所提供的服务地址构造之后获取请求的客户端
获取请求:使得客户端不断获取请求信息;
根据请求信息执行用户代码这里和Golang唯一的区别就是在多并发情况下Python指派的是线程来完成任务的处理而不是协程。
这里我再简单提一下关联用户handler的方法以及调用用户handler方法处理请求的过程也就是FCHandler的handle_request方法
def handle_request(self):
...
event, ctx, handler = self.get_handler()
# load user handler
valid_handler, request_handler = _concurrent_get_handler(handler)
# execute handler
if self.function_type == Constant.HANDLE_FUNCTION and self.request.http_params:
# http function
succeed, resp = wsgi_wrapper(self.request_id, request_handler, event, ctx, self.request.http_params)
if succeed:
self.print_end_log(True)
self.fc_runtime_client.return_http_result(self.request_id, resp[0], resp[1], Constant.CONTENT_TYPE)
return
else:
succeed, resp = execute_request_handler(self.function_type, request_handler, event, ctx)
if succeed:
self.print_end_log(True)
self.fc_runtime_client.return_invocation_result(self.request_id, resp, Constant.CONTENT_TYPE)
return
...
程序首先会调用get_handler获取请求中的userHandler信息。这个信息其实就是你在云函数元信息中配置的类似index.handler这种形式的入口函数。后续的过程和GoLang的处理思路基本上是一样的也是通过反射机制获取到具体的userHandler然后对其进行调用。
我们可以用一个示意图来梳理一下上面的主流程,便于你之后回溯执行过程:
分析运行时的小技巧
像GoLang、Java这一类运行时一般都有开源的代码可以查看而像Python这一类解释型语言的运行时代码通常都不能直接获取到并且很多函数计算平台也不会像阿里云函数计算FC一样为用户提供函数实例的登录入口。这个时候如果你想要分析运行时其实是不太方便的。
这里教给你一个小技巧。一般来说,用户都是有函数容器实例内文件的可读权限的,你可以通过在代码中注入命令的方式查看。
我继续以Python运行时为例简单演示一下。通过在函数代码内执行一些shell命令就可以将想看的运行时代码打印出来如通过ps 、ls、cat等常用命令就可以完成我们需要的任务
import logging
import os
def handler(event, context):
print(os.system("ps -ef"))
print(os.system("ls -l /var/fc/runtime"))
print(os.system('cat /var/fc/runtime/python3/bootstrap.py'))
return 'hello world'
这里需要说明一点,不同的云厂商可能实现方式不一样,有的厂商对运行时代码路径做了屏蔽处理,你只能看到一层入口而已。
运行时构建思路
学习了两种不同类型运行时的实现原理之后,相信你对运行时的整个工作流程已经有了一个比较清晰的认知。接下来,我们就可以利用上面学到的知识,构建一个自己的运行时了。
来回想一下,请求从到达函数实例再到被执行经过了哪些关键步骤?
需要有一个初始化进程,将运行时加载到内存中运行起来;
运行时需要获取到请求的详细信息。在GoLang、Python中都是通过客户端请求约定的接口获取的如RUTIME_API。这里也可以通过其他方式来获取比如管道
调用用户代码执行请求,并在处理后,请求返回结果。
我们在自己构建函数平台的时候,就可以参考这种方式,但如果你是一个函数业务开发人员,也完全可以基于云厂商的工具和平台来定义一个自己的运行时。
实战体验
接下来我会带你利用阿里云函数计算FC提供的开发工具Serverless Devs手动搭建一个自定义运行时帮助你加深对整个运行时概念的理解。
我们首先选择python37作为自定义运行时创建一个初始项目。
s init fc-custom-python37-event
在初始化项目时候会提示你进行一些配置按照提示一一输入即可这里我给新的项目叫做GeekBang。
进入到项目路径下,我们可以看到都创建了哪些东西:
$ tree .
.
├── code
│ ├── gunicorn_conf.py
│ ├── requirements.txt
│ └── server.py
├── readme.md
├── s.yaml
└── s_en.yaml
可以看到它包括了一些yaml格式的配置文件以及一个code 文件夹。这两个yaml文件其实一样只不过s_en.yaml里是英文注释我们点开s.yaml进行查看就会发现里面除了配置超时时间、内存限制等常见参数外还有一个自定义运行时的启动参数。
customRuntimeConfig:
command:
- gunicorn
args:
- '-c'
- 'gunicorn_conf.py'
- 'server:app'
这其中gunicorn 是一个Python的http服务器在函数实例第一次被初始化时会执行如下的命令并且这条命令最终会将代码路径下的server.py文件启动起来。
gunicorn -c gunicorn_conf.py server:app
我们再看server.py的代码会发现其实他本质上就是一个http服务。
@app.route('/invoke', methods=['POST'])
def event_invoke():
rid = request.headers.get(REQUEST_ID_HEADER)
print("FC Invoke Start RequestId: " + rid)
data = request.stream.read()
print(data)
try:
# do your things, for example:
evt = json.loads(data)
print(evt)
except Exception as e:
exc_info = sys.exc_info()
trace = traceback.format_tb(exc_info[2])
errRet = {
"message": str(e),
"stack": trace
}
print(errRet)
print("FC Invoke End RequestId: " + rid +
", Error: Unhandled function error")
return errRet, 404, [("x-fc-status", "404")]
print("FC Invoke End RequestId: " + rid)
return data
当你在控制台或者开发工具执行调用时,最终就会调用/invoke这个接口来实现你的业务逻辑。在try结构下你可以直接调用一个定义好的函数我这里只是简单地将event事件进行了打印而已。
然后我们依次按照通过build、deploy等代码部署到云端最后可以通过invoke命令调用。下面是我们体验的结果
$ s invoke -e '{"key":"value"}'
========= FC invoke Logs begin =========
FC Invoke Start RequestId: 5c44f71b-eadb-4bbf-8969-396ecf4e25a4
b'{"key":"value"}'
{'key': 'value'}
FC Invoke End RequestId: 5c44f71b-eadb-4bbf-8969-396ecf4e25a4
21.0.0.1 - - [27/Jun/2022:07:17:14 +0000] "POST /invoke HTTP/1.1" 200 15 "-" "Go-http-client/1.1"
Duration: 2.60 ms, Billed Duration: 3 ms, Memory Size: 1536 MB, Max Memory Used: 80.95 MB
========= FC invoke Logs end =========
你会发现,调用结果和常规的运行时执行基本没有区别。
当然我们还是登录云端的控制台来看一下进程状态可以发现最终是由gunicorn启动的server在提供http 服务。
root@c-62ca6e0c-3721605419294647aac9:/code# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 4 06:13 pts/0 00:00:00 /usr/local/bin/python /code/.s/python/bin/gunicorn -c gunicorn_conf.py server:app
root 4 1 3 06:13 pts/0 00:00:00 /usr/local/bin/python /code/.s/python/bin/gunicorn -c gunicorn_conf.py server:app
root 5 1 3 06:13 pts/0 00:00:00 /usr/local/bin/python /code/.s/python/bin/gunicorn -c gunicorn_conf.py server:app
root 11 0 0 06:13 ? 00:00:00 /bin/sh -c cd /code > /dev/null 2>&1 && COLUMNS=167; export COLUMNS; LINES=27; export LINES; TERM=xterm-256color; expor
root 12 11 0 06:13 ? 00:00:00 /bin/sh -c cd /code > /dev/null 2>&1 && COLUMNS=167; export COLUMNS; LINES=27; export LINES; TERM=xterm-256color; expor
root 13 12 0 06:13 ? 00:00:00 /usr/bin/script -q -c /bin/bash /dev/null
root 14 13 0 06:13 pts/0 00:00:00 sh -c /bin/bash
root 15 14 0 06:13 pts/0 00:00:00 /bin/bash
root 17 15 0 06:13 pts/0 00:00:00 ps -ef
通过这种方式,你应该已经明白,运行时的实现机制和执行流程其实是一样的,只是基于不同的语言类型和本身的特性,编码的方式不一样而已。
小结
今天我以不同的方式带你了解了Python运行时的运行流程在这期间也跟你分享了一个查看函数计算实例中运行情况的方法——在代码中注入命令。
通过这两节课程的介绍,我们可以总结出来,运行时主要做的是这三件事:
获取请求信息;
关联用户代码;
调用用户代码处理请求。
这里我们要注意的是,在获取请求信息时,通常都是从单机侧的约定接口或管道获取,在获取请求后,可以通过反射机制获取到用户代码的入口信息,最后再调用。
如果只考虑最简单的运行流程运行时中甚至不需要做一些复杂的类型转换工作如反射机制等只需要像自定义运行时中的例子一样定义好函数实例启动的执行命令并提供一个简单的http服务即可。
希望你通过这节课程,对函数计算形态下的语言运行时有一定的了解,不仅会用,更知道它是如何实现的。在后续遇到问题和开发更复杂的功能时,能够做到心中有数。
思考题
好了,这节课到这里也就结束了,最后我给你留了一个思考题。
你的场景用自定义运行时的时候多么?有遇到过哪些问题吗?又是如何解决的呢?
欢迎在留言区写下你的思考和答案,我们一起交流讨论。感谢你的阅读,也欢迎你把这节课分享给更多的朋友一起交流学习。

View File

@ -0,0 +1,170 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
09 小试牛刀(一):如何利用函数之间的调用解决业务问题?
你好,我是静远。
在前面的课程中我跟你分享了基于FaaS形态的Serverless核心技术从用户和平台不同视角学习了函数的初步使用方法理解了其中的原理机制和设计思想。
虽然是基于FaaS的介绍但不论是FaaS还是托管服务形态在扩缩容、计量计费、流量调度等方面Serverless的基础特征都是如出一辙的。FaaS只是基于这些基础特征之上一个具象化的产品体现而已。我相信现在的你再去研究其他具体的Serverless的产品也已经能够驾轻就熟了。
接下来我们先不要急着深入学习它的拓展能力先通过两节贴近一线实战的课程分别从函数的调用和跨VPC的实现让你能一边用起来一边继续深入学习。函数的调用只要用到云上的资源大部分都会涉及到VPC的调用这两者放在一起来讲一定能够加深你对这两者紧密关系的理解。
今天,我就和你先聊聊函数的调用。希望你能通过这节课,了解函数与函数之间是如何组装,函数与其他云资源是如何协作的,更能够判断在哪些场景适合用什么样的调用方式,以及这些调用需要注意些什么。
进行函数拆分调用的必要性
首先,我们来了解一下函数拆分调用的必要性。可以说,只要你有将函数用于实际业务的需求,单个函数就很难满足你的诉求。比如业务函数对公共函数的调用,函数对云资源的调用等。
另外,函数拆分管理也是有好处的,我们一个一个来说。
成本:云函数的收费由调用次数、公网流量、占用资源时间三部分组成,其中,最贵的就是占用资源时间。比如有的客户调用次数比较少,但费用也不低的原因,就在于他们的函数每次运行的耗时比较长。那么,你可以通过缩短超时时间、优化第三方的服务调用时效(如查询语句、对象池)等方式来降低一定的成本。
复用性:如果将所有功能写在一个函数体或者函数文件中,它沉淀为模板或者被再次复用的可能性就比较小,这也不符合我们组件化的思想。
性能:对于非串行的功能,拆分成多个函数可以提供并发性。因为每个函数都有自己的并发限制,另外,一旦某个并发的函数出现错误,拆分调用也不会像串行调用一样受到超时影响。
这三个好处,其实也是我们函数拆分调用的目的和方向,有了这个意识后,我们再来看在什么样的场景下,应该使用什么样的方式来实现。
实现方式
函数的调用从直观的实现上来看,分为同步、异步、编排。编排其实可以划到异步的大范围内,我这里把它单独拎出来,主要是因为它属于一个产品级的术语,具有调度和管理的意义。
这个思维导图,其实就是函数调用的思路,接下来,我们分别看下它们的具体实现。
同步
在同步模式下主要使用的有直接调用、网关调用、借助触发器3种实现方法。这也是我们在轻量级应用场景下通常会使用的方式比如BFF层的开发、对话式交互、活动运营页等。我们分别看下它们的特性。
直接调用
通常我们使用云厂商提供的SDK通过调用指定的函数就可以实现直接调用。我们以阿里云的fc-python-sdk为例通过invoke_function调用主要的代码如下所示
import fc2
client = fc2.Client(
endpoint='<Your Endpoint>',
accessKeyID='<Your AccessKeyID>',
accessKeySecret='<Your AccessKeySecret>')
// 同步调用
client.invoke_function('service_name', 'function_name')
这种方法通常用于调用时间较少、需要及时返回处理结果、业务处理较为简单的场景。它和我们平时开发的微服务有几分相似,比如对调用结果的缓存、超时重试等,我们都可以将微服务上的经验迁移到函数计算中来使用。
但要注意的是,函数计算本身是有超时时间的,你还需要考虑超时带来的费用成本,按次付费是函数区别于微服务的一个重要特性。
网关调用
这个方法就是通过网关的方式调用函数了。它同样与微服务类似主要是通过API网关的方式来达到限流、验证、参数映射、域名映射等作用。如果是两个功能模块之间的调用就可以选择网关调用了。不过如果是自己业务内部的函数互调那么采用上面所说的直接调用的方法即可。
借助触发器
我在做业务的时候经常看到有的客户会在函数计算的平台上构建好触发器和函数之后在自己的平台上写一个服务来通过HTTP触发器的方式使用函数计算。这个方法一般用来做服务部分的Serverless化、函数检测等场景。
最后,在同步调用模式下,你还要注意调用延时和超时带来的级联反应,这会加重你额外的费用成本。
异步
在异步模式下,我们通常有直接的异步调用、借助平台异步策略配置、借助介质触发的方式。具体怎么理解呢?我来跟你一一梳理下。
直接异步调用
这种方式是比较好理解的我们还是拿阿里云的函数计算调用方式来说明。它跟直接同步调用的区别在于在多传一个参数值“x-fc-invocation-type”为“Async”
// 异步调用函数。
client.invoke_function('service_name', 'function_name', headers = {'x-fc-invocation-type': 'Async'})
这种方式下调用invoke_function之后函数会立马返回并不会关心被调函数的执行情况而是由平台保证可靠地执行。
它同样和微服务的异步调用有些类似,除了可以增大并发之外,还可以尽可能地节省函数调用的成本。不过,它的局限性在于我们无法第一时间知道执行的结果,因此,我们最好在一些延时不敏感、无需等待返回给客户端结果的场景下使用。
异步策略配置
这种方式下,你无需进行额外的代码开发,这是平台厂商通常为了使得产品更具竞争力,让你快速实现对函数的处理结果、异常等方面的再次处理而提供的能力。所以我在这里把它称为“策略配置”。
你可以通过设置诸如“最大重试次数”、“消息存活时间”这些条件来保证请求的可靠处理。同时,也可以通过“成功”或“失败”调用其他云函数或者已经集成的云函数、消息队列等,进一步丰富处理能力。通过云厂商的这种配置,我们也可以快速地搭建一个类似微服务异步处理架构的能力。
借助介质触发
这种方式其实就是巧妙地运用函数与云服务的集成来处理,同时,也将函数计算由原来的被动触发模式,转变成了“生产者模式”,可以说真正体现出了函数计算胶水语言的特性。
如下面的示意图所示。我们通常可以看云厂商集成了多少的云服务也就是说它集成了多少触发器。之后你就可以通过函数的方式来调用它如HTTP触发器的异步触发方式也可以通过云服务开放的API接口存储下来再由其配套的事件触发方式触发目标函数就行。我们以对象存储举例你可以将文件存储到一个bucket下然后进行触发操作。
这样的方式可以极大地降低你的运维成本让你更专注于业务逻辑的处理这一点也正是Serverless的精髓所在也是不同于微服务的一大特点。
我们都知道,微服务的治理其实已经很不简单了。随着函数粒度越来越低,虽然灵活性提升了,但如果是复杂的场景,即使有这些同步和异步的方式,一旦处理逻辑比较多,链路比较长,等待时间不确定,亦或者需要回滚操作等,那么上述这两大操作模式(同步、异步)也就显得有点力不从心了。
下面我就给你在介绍一个Serverless的杀手锏工具——工作流。我们可以把上面的方式看成是“函数形态下的P2P操作”而工作流就可以看作是“函数下的Kubernetes”。
编排
如下图所示我们可以发现函数或者服务之间不再以主动或者被动的触发方式来交互而是通过预制好的步骤来进行这些步骤通常通过JSON或者YAML的格式定义好函数之间的协作关系存在。
我们通常把它称为Serverless工作流它可以让你通过顺序、分支、并行的方式来协调一个或多个分布式任务这些任务不仅包括函数还可以是服务和应用的形式并且通过平台提供的状态跟踪、日志记录和异常重试逻辑将你从繁琐的工作中解脱出来享受全托管的服务能力。
那么它能具体适用于什么样的场景呢?我来跟你分别来介绍一下。
长流程的场景:假如你的业务流程耗时很长,为了确保流程的执行结束和状态追踪,就可以使用工作流编排的方式;
事务型业务流程比如我们常见的电商订单流程涉及到预留库存、下单结算、配送、退款等有状态的流程通过Serverless工作流就可以提供这种长流程分布式的事务保证
并发型业务流程:这里一般指的是执行时间长、并发量高的大规模计算场景,比如机器训练,需要先拆解小文件计算,再聚合处理;
需要状态全链路监测的场景由于云厂商开发的工作流配备了可观测、执行记录等可视化功能那么对链路长需要监测的业务通过Serverless工作流就可以便捷地查看状态、执行记录、定位故障。
总的来说Serverless工作流适合用于解决复杂的、执行流程长、有状态的、多步骤的、并发聚合的业务流程。其实当你用同步、异步的常规方法不太好处理的时候就可以想想你的业务是否可以通过编排的方式来解决。关于编排的核心技术实现我也会在第二模块中跟你细聊。
注意要点
了解完常用的函数之间的调用方法和使用场景之后相信你已经可以通过这些思路驾轻就熟地去云厂商的平台上体验一番了。如果你是一个Serverless平台的新人那么相信你对你的平台已经具备或者应该具备的能力也心中有谱了。下面我再提供给你几点使用过程中的注意事项避免踩坑。
首先安全性问题。云厂商通过在代码级别加密的方式确保代码安全运行时的隔离机制确保执行安全访问时的鉴权验证确保资源请求的安全。那么你需要做的就是在使用的时候确保你的访问密钥是安全的。除此之外在设置诸如HTTP触发器之类的服务可选项时一定记得在生产环境使用身份鉴权验证机制。
其次,监控报警问题。你可以通过我在热启动部分中讲到的预热方法来访问你的重要函数,确保函数能被正常访问,如果不能正常访问,你可以沿用你原来微服务的治理机制方法来报警,或者可以直接使用云厂商的报警策略机制,此时你可能需要支付一定的额外费用,这个依据不同的平台而定。
最后,容错问题。如果你的下游服务出现问题(如超时、访问不通、超限等),不仅会影响你本身的函数使用,还会增加额外的耗时费用。所以,你可以针对性地增加一些缓存方式来避免无状态请求的调用。其余的方法,其实跟微服务就差不多了,比如熔断机制、降级机制等,并结合上面的报警来处理。其实,在合适的场景选择异步的方式来处理,本身就是一种高容错、低耦合的方式。
小结
最后我来小结一下今天的内容。这节课我分别从函数拆分调用的必要性和实现方式上跟你介绍了如何利用函数之间的调用来解决日常的业务需要。要注意的是函数计算的使用也是需要从成本、复用性和性能方面来权衡的以便于获得最佳的ROI。
在实现方式上面,我把函数的调用分为同步、异步以及编排的处理方式。其中,同步适合及时响应、执行较短等场景。在大部分时候,我还是推荐你使用异步的处理方式来确保业务的稳定可靠和服务的解耦。
而针对复杂的业务场景、需要支持长时间、有状态、多步骤的执行流程这里我推荐你使用Serverless工作流。
当然,我更希望你能够灵活地运用今天学习过的调用方法,在前面学习单函数的基础上,能够通过“积木的方式”实现更复杂的业务逻辑。
课后练习
好了,这节课到这里也就结束了,最后我给你留了一个小练习。
想一想通过函数调用的方式实现音视频的处理针对不同的场景都有哪些方法你可以选择一家云厂商先练习练习体验一下函数计算、Serverless的便捷之处。
我会在第二模块的动手体验中跟你讲解实现它的多重途径和详细方法。感谢你的阅读,也欢迎你把这篇文章分享给更多的朋友一起交流学习。

View File

@ -0,0 +1,200 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
10 小试牛刀如何突破VPC网络的速度限制
你好,我是静远。
在冷启动这节课里我跟你聊到了影响函数第一次执行速度的因素有很多其中一个关键因素就是VPC网络的建联时间。
我们知道VPC是云厂商提供给用户的一个安全隔离的网络环境用户可以在VPC空间中部署自己的服务如Redis、数据库、MQ等。而我们的函数通常是创建在函数专属的VPC中的。一个生产级别的函数服务应用少不了需要和其他的资源和中间件打交道。那么跨VPC之间的通信效率就显得尤为重要了。
今天这节课我就针对VPC之间的通信效率通过案例实操的方式为你剖析函数在跨VPC上的技术要点。
希望通过这节课能够让你得到一些在Serverless的实战和后续优化工作的启发能举一反三地找到更多优化方法进一步突破函数访问速度的上限。
跨VPC问题的演进
针对云上网络连接的方式相信你已经了解到不少如专线高速通道、VPN网关、对等连接等方式。这些方式有的用来解决IDC和VPC的连接有的用来解决VPC与VPC之间的互通。
这其中对等连接的确可以解决函数跨VPC的问题但我们试想一下如果每个用户都通过这种方式来解决这种问题那么平台每次都需要搭建两端的连接并且还可能会遇到IP网段冲突的问题。这样的做法显然不具备普适性成本也过高。
为此针对Serverless的函数计算场景我们通常会通过弹性网卡ENI的方式来打通。弹性网卡是绑定在云主机或者容器实例上的也就是说我们是通过弹性网卡ENI来关联用户的VPC和用户的设备实例使得设备和VPC互通的。
但实际上,我们暂且抛开这两者在网络速率上的差别,不论是在容器实例还是在主机上,他们都会面临同样的问题。
冷启动耗时较长:弹性网卡这种虚拟化的资源,它的创建时间是避免不了的;
资源受限正因为资源虚拟化也就意味着要依赖于硬件的基本参数受限于内核、CPU等因素为此几乎所有的云厂商都会限制VPC内弹性网卡的数量也就从一定程度上导致了此方法的局限性
资源浪费:由于容器或者主机上要绑定弹性网卡,也就意味着多个实例就得占用多个网卡资源,造成资源浪费。
我们可以看出它们虽然解决了跨VPC的问题但还是会带来性能和资源成本上的压力。那么我们是否可以不创建这么多弹性网卡探索一种共用的方式呢通常我们立马能想到的方式就是出口代理了。
什么是代理?
如何代理呢?我们可以了解一下它的整体流程。
简单来说就是我们在函数计算的集群与用户的VPC之间添加Proxy Node在Proxy Node上构建网卡如eth1网卡会提前在用户VPC所在的子网Subnet中创建但租户的权限属于函数计算VPC。
有了弹性网卡接下来就需要解决函数到代理节点的打通问题了也就是在函数的主机和代理主机之间构建一条链路将数据包传递过去且保证源IP和目的IP保持不变。
我们通常采用的是隧道技术。隧道打通后我们还得注意两件事情。其一就是在Proxy Node中多张网卡的转发处理其二由于函数通常以容器的方式运行所以需要有一个容器网络转发的过程。
接下来,我将一步一步介绍它们是如何起作用的。
动手实操
这节课我会通过一个打通跨VPC网络的实验案例带你实现函数通过Proxy机器代理的方式访问用户VPC的资源的全过程。在这个案例中我选取了百度智能云服务器BCC来进行实验。
在实验之前我们需要准备两个VPC网段模拟创建函数集群和用户集群并在云上购买三台云服务器BCC其中2台BCC在VPC1另外1台在VPC2。下面这个图可以清晰的描述部署的逻辑
隧道技术
准备工作完成之后,我们来看隧道技术。
什么是隧道?在网络中,隧道是一种使用网络不支持的协议,构建网络之间高效和安全的链接,实现在网络中传输数据的方法。
内核中的tunnel可以通过ip tunnel help看到当前有五个IPIP、GRE、SIT、ISATAP和VTI。
本节课我们选择使用GREGeneric Routing Encapsulation它是IPIPIP in IP协议的更进一步版本从官方的描述上来看可以取代IPIP。隧道技术本身的概念和区别不是本节的重点。不过如果感兴趣的话你也可以通过我文末给的延伸阅读深入了解。
接下来我们来创建隧道使得func bcc1和proxy bcc能通过隧道打通连接。我们先在func bcc1中执行如下命令来创建GRE隧道
# 加载GRE模块
modprobe ip_gre
lsmod | grep gre
# 创建gre模式的通道tunnel关联上IP并赋予虚拟本端IP为10.0.0.3
ip tunnel add gre0 mode gre local 192.168.80.8 remote 192.168.4.4 dev eth0
ip addr add 10.0.0.3 peer 10.0.0.4 dev mgre0
ip link set dev mgre0 up
在proxy bcc上执行如下命令来打通GRE隧道
modprobe ip_gre
lsmod | grep gre
ip tunnel add mgre0 mode gre local 192.168.4.4 dev eth0
ip link set dev mgre0 up
ip address add 10.0.0.4/24 dev mgre0
ip neigh replace 10.0.0.3 lladdr 192.168.80.8 dev mgre0
最后我们还可以通过ping命令来验证proxy bcc与func bcc1的GRE隧道是否打通。
到这里我们隧道的打通任务已经完成一半了下面我们要看proxy bcc如何与user bcc打通。
转发技术
我们注意到proxy bcc和user bcc是不在一个VPC集群内的。那我们要怎么解决这个问题呢
构建的逻辑就是在user vpc下构建一个虚拟网卡eth1而eth1又隶属于proxy bcc下。这个方法和我们在上面“跨VPC的演进”中讲到的“动态创建网卡”的方法是一致的。
这里需要说明一点,如果你是一个业务函数的开发者,可能云厂商没有开放相应的特权账号,没有创建弹性网卡的能力,实操不了,那么,这里你只要了解过程即可。如果你是云平台维护工作者,可以体验一下这其中的操作方式。
弹性网卡创建并绑定好后我们会发现有2个网卡存在于proxy bcc中。
我们通过ping命令可以验证是否能够和user bcc打通。这里要注意proxy bcc与user bcc可以互通是因为弹性网卡是基于user bcc所在的VPC的子网Subnet创建的但租户还是属于proxy bcc。
打通之后我们就需要用到转发技术了也就是将func bcc1的数据包通过proxy bcc的请求到达user bcc。
首先我们需要通过将ip_forward设置为1开启proxy bcc的转发能力。
echo 1 > /proc/sys/net/ipv4/ip_forward
这里需要说明一下出于安全考虑Linux系统默认的是禁止数据包转发的。所谓转发就是当主机拥有多于一块网卡时其中一块收到数据包根据数据包的目的IP地址将包发往本机的另一网卡该网卡即会根据路由表继续发送数据包。
我们这里因为是eth0接收云主机的数据通过eth1来发送出去所以需要开启转发能力。当然为了能让func bcc1的路由触达user bcc我们还需要继续完善路由规则。
我们在func bcc1中通过ip route add命令添加一条到user bcc子网172.16.101.0/24的路由
ip route add 172.16.101.0/24 dev mgre0
此时 func bcc1 访问 user bcc 172.16.101.4 仍然访问不通,在 proxy bcc 侧监听 eth1 流量可以发现eth1 发往用户 VPC 的包的源 IP 仍是 func1 bcc gre0 设备的 IP 10.0.0.3,这个 IP实际只存在于我们 bcc 内部,因此用户 bcc 收到请求后,它的返回流量无法路由到这个 IP。
因此我们这里需要在proxy bcc添加一条 iptables 规则,在 POSTROUTING 链里把从 eth1 出口的流量的源 IP SNAT 设置为 eth1 的IP 地址,即 172.16.101.4。
iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE
到这里我们就可以在func bcc1中ping通user bcc了。
容器网络
在虚机上,我们已经操作体验一遍了,但通常我们的函数是在容器实例里面运行的 的容器在访问用户vpc服务的时候是存在一点差别的。我们也可以验证一下会发现pod 发起的请求正常路由到了 proxy bcc 下的 tunnel 设备,但是没有继续转发到 eth1 中。
为什么会出现这样的情况呢通常是因为Pod的IP和我们在proxy bcc上构建的路由规则是不一致的。至于怎么解答我先不做回答你可以先思考一下。我们先来看一个概念ip-masq-agent。
ip-masq-agent 是一个用来管理 IP 伪装的扩展即管理Kubernetes集群节点中 IP 网段的 SNAT 规则。它以 DaemonSet 形式部署在Kubernetes的集群中在每个节点上启动一个代理程序。代理程序通过配置 iptables 规则,再将容器流量发送到集群节点的 IP 和集群 IP 范围之外的目标时,就可以选择以容器自己的 IP 地址或伪装成节点的 IP 地址作为出向流量的源地址。
看完ip-masq-agent的解说相信你已经有答案了。解决办法就是把 Pod IP SNAT 设置成 gre0 设备的 IP再转给 proxy bcc再对 proxy bcc 隐藏 Pod的IP即可。
ip-masq-agent 的配置来自于 configMap可以通过 kubectl edit cm 命令来查看或修改配置。
到这里我们在Pod中也能访问user bcc的服务了。
高可用
如果我们要上生产环境你还需要考虑服务的高可用。在我们这节课反复提到的案例图中我只画了一个Proxy Node用来实验还可以但生产环境肯定是不够的。为此我们需要为每个VPC/Subnet建立一对主备Proxy。主备Proxy要使用HaVip进行容灾切换。
HaVip是一种可以独立创建和释放的内网IP可以与高可用软件例如KeepAlived配合使用搭建高可用主备服务提高业务的可用性。
另外从Serverless的角度看你还需要依据函数访问的流量对Proxy Node进行自动扩缩容。当流量增大时需要自动对Proxy Node进行扩容当流量降低时需要自动对Proxy Node进行缩容。
我们继续在上面的架构图上进一步完善一下增加主备Proxy Node和容器Pod之后的架构示意图如下所示。
小结
最后,我来小结一下我们今天的内容。
这节课主要围绕着优化函数在跨VPC场景的访问速度来做讲解。我们学习了跨VPC打通的演进过程以及代理的四大要素最后也结合一个案例的实操过程了解了函数代理技术的实现过程。
在演进过程的回顾里,我们会发现,对等连接由于其在函数计算上的构建不具备普适性,只有在极少数大客户的场景下会使用。而弹性的网卡实时创建,虽然具备一定的通用能力,但由于其高延迟和对资源的浪费,我们也不太推荐使用。
这两个技术,给我们来带来的思考是,如果我们能提前准备好访问的通路且可复用,是不是就可以解决这样的问题了?是的。为此,我们提出了函数代理技术。
其实代理技术需要解决的技术卡点还不少主要包括我们提到的隧道通路、网卡转发、容器网络转发技术以及最后在生产环境下还需要考虑到的高可用的问题。我们可以通过高可用的虚拟IP和主备Proxy集群解决。同时从弹性的角度我也建议你引入扩缩容的能力这个能力你也可以贯穿在整个Serverless的学习生涯中可以说它就是Serverless的精髓所在。
最后函数计算由于其胶水语言的特性跨介质跨网络的访问是不可忽视的一件事情。虽然我们已经在不断的演进解决但函数代理技术也有其缺点的比如资源一定程度上的增加。因此希望通过这节课的内容和思考的过程能让你打开眼界提出更多的优化手段让Serverless的技术积淀越来越深厚。
思考题
好了,这节课到这里也就结束了,最后我给你留了一个思考题。
如果我们在隧道打通那里采用IPIP隧道协议会有什么问题
欢迎在留言区写下你的思考和答案,我们一起交流讨论。感谢你的阅读,也欢迎你把这节课分享给更多的朋友一起交流学习。
延伸阅读
你可以通过VPC的对等链接继续深入了解一下对等链接的概念。
这一篇隧道技术,可以帮助你进一步了解隧道的知识要点。
腾讯云的这一篇VPC网络优化的文章也是值得我们一看的。
文中提到的ip-masq-agent感兴趣的话你可以继续看这一篇ip-mast-agent指南写得也是非常详细的。

View File

@ -0,0 +1,193 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
11 WebIDE如何让函数远离繁琐的本地开发模式
你好,我是静远。
在开始今天的主题之前我想跟你先聊一下我的客户在WebIDE这块的诉求。我在金融领域客户的提问中挑选了两个比较典型的问题
银行要求生产环境不带自己的电脑,请问我如何操作我们的代码?
编辑框有点小我用的是Python是否能提供在线调试的能力
你会发现不管是工作硬性的要求还是自身对开发效率的追求他们最终的目的都是希望有一个能够随时随地、打开即用的代码编辑器——WebIDE。
我想WebIDE这个概念你一定不陌生但如何让WebIDE更好地用于函数计算的开发呢我们又需要注意哪些细节呢这里面有哪些不一样的技术点呢
今天我就来跟你聊一聊函数计算中WebIDE是如何工作的。希望通过这节课程让你在了解VS Code Server原理的基础上在插件机制、环境的依赖、弹性伸缩等几个方面都对函数计算形态的WebIDE有一个比较清晰的认识。
WebIDE的优势是什么
首先我们来体验一下云厂商的WebIDE直观感受一下它的优势。
我们可以以阿里云函数计算FC为例创建一个Python3语言运行时的函数。点击控制台页面的“函数代码”就可以加载出来在线编辑的工作空间。
我们可以看到这和我们本地的VS Code IDE几乎没有太大差别左上角是我们熟悉的“文件”、“资源管理器”、“查找”等菜单按钮。而且在线编写代码的体验和本地IDE也几乎是一样的包括代码提示功能等都有。当你完成代码编写之后还可以点击测试按钮直接测试编写的函数。确认无误后点击部署函数测试好的代码也可以直接部署在云端。
这种方式的好处是,当我们想要细微调整函数的时候,无需下载代码到本地,可以直接在线完成编辑、测试、部署这一系列的动作。
现在我想你应该对WebIDE的优势有一个直观的感受了可快速地编辑、调试、运行、部署云端代码。它的另一个优势就是在金融银行领域出于安全的考虑有的环境往往是不能带自己的电脑和本地IDE的只能通过WebIDE的方式来操作。
针对函数计算的WebIDE各大云厂商都有不同程度的实现虽然不管是在公有云还是在私有云上实现方面可能有一定的差异但背后的实现思想和机制是通用的。接下来就让我们深入了解它的整体架构。
整体架构
为了让你更直观地理解在线的函数计算IDE我在架构图中用不同的颜色做了标识。我分别来做一下说明。
蓝色部分是WebIDE客户端的核心我们选取了知名的VS Code Server来作为核心底座它在很大程度上解决了 Client 端的问题,其自身基于 VS Code 二次开发让VS Code在任何地方都可以通过浏览器访问和操作
绿色部分是将WebIDE与函数计算结合起来的核心主要包括函数计算业务形态下的功能插件、工具、SDK以及运行时环境能力
橘色部分是Serverless形态下的必备支撑服务承担代码的存储、安全管控和针对用户操作请求时的资源调度和伸缩事项主要包括对象存储、权限认证、资源伸缩、健康监测能力。
在图中我也用序号标注了WebIDE在函数计算平台的简要执行过程接下来我以一个函数的Deploy请求为例帮助你从整体上快速了解每次请求涉及的全貌。
第1-2步用户会在VS Code的前端页面向后端发出函数在线编辑的请求假设为“XXX/ deployFunction”那么服务端也就是函数计算的控制层Controller在接收到请求后就会验证权限再转给VS Code Server容器实例。
第3-4步VS Code Server容器就会获取用户代码再加载函数计算的资源调度系统也会根据目前容器池Container Pool的资源现状动态扩缩容WebIDE Pod资源。
第5-7步Server端根据用户的请求会调用插件后端功能Serverless Extension BE基于此时语言的环境执行Deploy操作并将执行结果返回给Client端。
这里需要额外说明三点:
第一我们可以将Serverless Extension插件提前集成在VS Code Server的镜像里面
第二Runtime依据原来函数计算执行的架构来部署即可你可以将各语言运行时打包成一个镜像然后以动态挂载的方式加载每个语言也可以集成在WebIDE的大镜像里面不过这样会比较笨重
第三健康监测主要用于监听VS Code Server的状态。虽然网页版的服务都在服务端运行但界面Client和后台Server是有心跳关联的这个健康监测的作用就是心跳监测。它会按照一定的逻辑来判断页面是否不再使用以便通知资源调度服务释放后端的容器实例。
基于基础架构和流程的展示我们应该已经能够理解函数计算的WebIDE的主要功能就在于基于一个可视化的在线编辑软件的基础增加了Serverless函数计算的插件功能通过集成运行时环境依赖和弹性伸缩的能力提供用户在线开发、调试、部署和运行函数的能力。
接下来我们就沿着这个思路对可视化的在线编辑软件VS Code Server、Serverless Extension插件功能、环境依赖和弹性伸缩的能力依次展开说明看看它们有什么需要注意的技术要点。
VS Code Server
我在开头提到过本次讨论基于VS Code Server来展开主要是因为VS Code的背景公司是微软使用人群基数庞大。微软将VS Code进行了开源而Code Server又对VS Code进行了二次封装使其可以在任何机器上部署并且通过浏览器来访问。
我们还是来体验一下,这样更直观一点。
首先你需要提前准备一个2核4G的机器区别于code-server在github上的最低要求2核1G确保执行所需的空间资源并且确保有docker环境。
接下来你可以下载code-server镜像到本地并启动为容器。
# 拉取vscode server镜像到本地
$ docker pull jmcdice/vscode-server
# 启动一个docker
# -d是在后台运行容器
# -p将本地的端口9000映射到容器的端口8080
# --restart=always是在重启容器时能重新启动参数
# --name指定启动容器的名字
$ docker run -d -p 9000:8080 --restart=always --name=myVscodeServer jmcdice/vscode-server
启动动完成之后我们要使用docker ps命令查看容器是否已经启动。最后我们在浏览器中输入127.0.0.1:9000就可以使用在线VS Code了这个流程操作下来是非常方便、快捷的。
需要注意的是我们这里只是以本地docker的方式启动了VS Code如果我们想对外使用只需要申请部署云服务器并对外暴露URL就可以了。
有了本地的体验我们放到函数计算平台无非就是新增一个镜像并启动为容器的过程。我们的重点是集成函数服务的能力这就是我们下面要讨论的Serverless Extension插件功能了。
Serverless Extension
各大云厂商基于WebIDE通过开发插件Extension的方式集成了函数平台的常用功能。
如百度智能云函数计算的Baidu Serverless VS Code 插件、腾讯云函数的Tencent Serverless Toolkit for VS Code插件、阿里云函数计算的Aliyun Serverless VSCode Extension插件等等。从产品形态上来看大体比较类似你选择其中一个来体验就行。
但要注意的是各云厂商的VS Code插件底层实现都是集成了自家的命令行工具和SDK的如百度智能云集成了BSAM CLI、腾讯云集成了Serverless Framework 语法、阿里云的插件结合了函数计算命令行工具Funcraft和函数计算SDK的功能。
那么他们都是怎么实现的呢虽然各家集成的工具和SDK多多少少带有自家的特性但设计的通用原则是不变的。接下来我就以这种通用的设计法则和思路来阐述如何构建一个Serverless VS Code Extension。
首先我们可以发现各家的插件都是放在了微软Visual Studio Marketplace之上你可以从这里任意点击一个插件进去可以看到相应的源代码、手册等维护信息。
其次该插件只是一个端的体现还是得由函数计算平台的SDK和CLI命令工具来实现。例如函数的上传、下载功能底层都是调用SDK中函数的接口实现而函数的部署、依赖安装、调试、执行则一般是在CLI中实现的。
总结起来就是两部分一部分是端的UI层逻辑的体现集成在VS Code里面一部分是工具CLI和SDK的集成用于真正执行相关的命令在服务端容器中运行。如下图所示
我们可以将CLI、SDK、Serverless Extension与VS Code Server打包在一个大镜像里面在请求的时候通过下载镜像在启动后为容器提供服务。
环境依赖
接下来,我们需要了解比较关键的一步——如何让函数“跑”起来。这就涉及到环境依赖了。
根据编程语言的不同特性通常函数计算平台需要针对各个语言做不同的Runtime为了区别于正式生产环境下的Runtime我这边采用dev-runtime命名。
它们主要的不同就在于dev-runtime需要根据不同的指令来进行不同的操作比如根据UI层发送的部署指令进行deploy操作。而我们之前第一模块中提到的Runtime是运行态直接挂载的。通常函数计算平台也是会对Runtime和dev-runtime进行区分分开管理的。
这里我们具体看下dev-runtime的执行流程。我以Python3的调试为例简单跟你聊一下这个过程。
就Python3而言如果需要支持调试我们需要提前在Docker镜像中指定安装debugpy。有的操作系统可能还需要安装GCC。
我们通常可以通过一个脚本来接收各种指令并调起各个依赖工具。那么针对Python3语言如果接收到WebIDE安装扩展依赖的指令就可以通过pip3来install了。
# -r 安装需求文件里的依赖包
# -t 安装目标位置
# -i 指定镜像源,本案例指定清华源,具体你可以自定义
# -I 如果已经安装依赖,忽略
/XXX/bin/pip3 install -I -r /XXX/requirements.txt -t /XXX/site-packages -i https://pypi.tuna.tsinghua.edu.cn/simple
类似的如果需要支持调试和执行通过Python3和debugpy相应的命令来执行即可。而其他语言的调试实现依赖于各语言的调试方式比如Node.js会使用node-inspect调试工具。
需要补充说明的是虽然我在文章开头指出大部分云厂商在其公有云上不支持Java的在线WebIDE的能力。但其实Java也是可以支持网页版能力的我们只需要在Java调试的启动脚本中添加相应的命令启动调试即可。
弹性伸缩
到这里针对函数计算的WebIDE已经讲解得差不多了。但如果只是这样它还不能被称为“Serverless”的我们需要再给它加上一层“可弹”的特性。
这其实和我们前面涉及到的扩缩容有些类似我先给你一个思路之后你也可以自己完整走一遍Serverless下的WebIDE流程。
关于这一层“可弹”的特性你可以通过KEDA、Prometheus、HPA、metrics来支撑WebIDE在Kubernetes环境下的动态扩缩容。为什么需要引入KEDA如果结合扩缩容 那一节课你应该可以很快回答出来因为KEDA可以支持0-1之间的切换。
最后,还剩一个比较重要的问题,就是我在基础架构图中画的“健康监测”。为什么要有这个功能呢?
我们可以这么想前端页面和提供服务的VS Code Server在第一次建立链接后是需要有页面的触发才会有下一步的执行动作的。那么如果用户没有触发该怎么办是释放掉容器实例还是持续存在存在多久合适呢
比较合理的设计是在VS Code Server中存在两个进程Server进程负责和前端页面的Edit请求交互而Status管理进程负责状态管理上报。其中VS Code Server和前端通信的协议一般使用WebSocket协议。
依据这样的设计思路当WebIDE前端和Server超过一定时间没有响应时可能就是网络原因了。此时不会立马回收VS Code Server容器实例会重试连接一定次数。当连接成功时就会继续使用该容器实例。如果重试仍然连接失败就说明该容器实例已经不再被使用了会将该实例标记为不可用状态定期执行清除任务将不可用状态的容器实例回收。
通过这样的状态监测管理使得我们函数计算平台的WebIDE也是“可弹”的。
小结
最后我来小结一下我们今天的内容。这节课我从函数计算WebIDE的整体架构出发分别介绍了它的核心四要点VS Code Server、Serverless Extension、环境依赖、弹性伸缩。
在课程的开头我们通过云厂商已经发布的WebIDE体验了一次清晰直观地感受到了我们今天内容的价值使用Web IDE不仅能对用户的代码进行快速的编辑和修改还能够高效地加载代码所需的外部依赖从而大大降低开发人员重新部署的次数以及时间。
再来通过整体架构的介绍相信你已经从概貌上了解了设计一个Serverless形态的WebIDE需要考虑的四大要点。我们一个一个来说。
首先VS Code Server的讲解主要想向你传递两件事情第一code-server是对VS Code的二次封装让我们平时在本机使用的VS Code可以无处不在的访问第二函数计算平台也是站在巨人的肩膀上进一步构建属于Serverless下的WebIDE的。这对于我们平时在设计架构和工作中也有一定的启发没必要什么事情都自己重复去造轮子。
其次Serverless Extension和环境依赖是本节课的“灵魂”没有了这两个功能就不能称之为函数计算形态下的WebIDE。它们主要是基于函数计算的工具CLI和SDK以及调试需要下不同运行时的封装。
最后为了符合Serverless的“可弹”特性我也从扩缩容的角度介绍了VS Code Server在容器中的两种服务的进程希望你能清晰地了解网页版“端和服务”的联动关系。
思考题
好了,这节课到这里也就结束了,最后我给你留了一个思考题。
如果让你来在现有云厂商平台上新增支持Java的在线调试功能你打算如何实现
欢迎在留言区写下你的思考和答案,我们一起交流讨论。感谢你阅读,也欢迎你把这节课分享给更多的朋友一起交流学习。
延伸阅读
你可以通过VS Code插件官网搜索到对应的插件库更详细地了解extension是如何实现的。

View File

@ -0,0 +1,283 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
12 编排:如何协调多任务的运行?
你好,我是静远。
今天我要和你分享的主题跟“编排”这个词相关,引入这个技术的关键目的,就是处理较为复杂的场景。
我们在前面的课程提到了函数之间的同步和异步策略机制、有状态的业务处理,这些场景虽然和单个函数比起来已经复杂了一些,但函数本身的策略依然可以解决这些问题的。
但是试想一下,如果你遇到每个业务都有几十个函数,需要多个业务配合调用的情况,函数之间的调用是否还可以轻易驾驭呢?
电商中订单的创建和管理;
文件、视频分片处理后,再整合形成新的报表或视频媒体;
广告物料的审核(包括涉政、涉黄、二跳、作弊等);
……
这些是不是想想都复杂?相信你在微服务架构中治理起来都比较困难,更何况现在是更细粒度的函数维度呢?我们知道,服务拆得越细,服务粒度就越小,虽然组装性会更好,但与之相对的,服务治理的难度也就会越大。
那么是否有一种能力可以在Serverless的领域中通过某种方式来协调各个服务和函数的执行使得我们在享受高弹性、低成本的同时也降低业务处理上的复杂度呢这种能力的确存在业界普遍称之为“工作流Serverless WorkFlow它就可以应用在上面提到的场景中。
工作流是什么?
什么是工作流呢CNCF 在它的标准协议说明中给出了一些解释。总的来说可以这样概括通过DSL来定义一组结构和模型它以JSON或YAML的形式存在服务引擎会根据定义的蓝图来进行调度和执行任务。
云厂商实现的工作流,可以让你能够通过顺序、分支、并行的方式来协调一个或多个分布式任务,这些任务不仅包括函数、还可以是服务和应用的形式,并且通过平台提供的状态跟踪、日志记录和异常重试逻辑,使得你可以从繁琐的工作中解脱出来,享受全托管的服务能力。
有了这个抽象的认知,那么它具体长什么样子,它是如何构成的,它的运行机制是怎么样的?下面,我就带你来一层一层揭开它的面纱。
基本构成
首先,我们通过一个简单示意图看一下工作流的形态。
可以看到,工作流的状态扭转跟我们接触到的状态机的转换是很像的,除状态的转换外,它还有这样几个特性。
每个State状态是一个工作节点
它定义了一种特殊的逻辑,决定了工作流当前流程应该执行什么样的控制逻辑,通常包括:操作、传递、选择、并行、等待和循环等等。这些状态通过不同的组合形式,让业务模型的构建变得像平时编程一样简单,极大地丰富了工作流的实际应用场景。
工作节点可以关联服务
工作节点可以通过API完成对其他服务的调用。比如最常见的就是结合函数计算每一个原子业务都会采用云函数实现而业务之间的关联关系会通过工作流实现。一些厂商为了丰富Serverless工作流的应用场景通常会和同一云生态下的其他云服务关联比如阿里云的Serverless Workflow、腾讯云的ASW就支持云服务API的集成。
每个节点有明确的输入和输出
每个节点都可以设定输入和输出来作为数据传递的手段,但在工作流中有两个比较特殊的规则:
第一,当前节点为初始节点时,该节点的输入为工作流的初始输入;
第二,当前节点不为初始节点时,该节点的输入为前一父节点的输出。
因此,你可以将工作流视为一组状态节点的集合以及这些状态节点的转换,每个状态节点都可以关联事件或功能,并且有明确的输入和输出。从解决方案的完整性来看,我们也需要额外地增加日志记录和审计来监视工作流的执行,也需要有安全的校验机制和错误异常处理能力。
DSL是什么
了解了工作流的基本构成之后我们先不要着急深入架构设计。还记得我在提到“工作流是什么”的时候在定义里提到的“通过DSL来定义一组结构和模型”吗这个DSL又是什么它在工作流架构的设计当中又充当着什么样的角色呢
DSLDomain Specific Language是用来描述和定义业务逻辑的。工作流执行时会根据你定义的这套逻辑执行。在基本构成的示意图中可以看到一次执行包含了多个状态这些状态可以是简单的一次操作在DSL中就会表示为如下的状态类型。
Event会在收到指定触发类型的事件后比如之前课程提到的S3的对象存储事件去执行调用动作调用动作在DSL中被称为action比如调用某个云函数。这里需要注意的是触发事件需要严格满足Cloud Events格式协议
Operation也会执行调用动作但是和Event状态不同的是Operation不需要收到事件触发而是可以主动执行调用动作
Sleep可以将当前执行挂起并设置等待时间类似于线程中的Sleep操作。等待结束后则恢复执行过程
Inject可以向工作流的输入数据中注入一些静态数据然后传递给下一个状态。-
这些状态也可以用来控制复杂流程的执行逻辑在DSL中表示为如下状态类型。
Switch类似编程语言中的switch关键字会通过eventConditions关键字来定义多个选项分支并根据一些筛选条件决定下一状态。在所有条件都不满足时defaultCondition还可以作为默认下一状态
Parallel包含一系列分支通过branch关键字定义。但和Switch不同的是这些分支的子状态是并行执行的
ForEach类似于编程语言中的迭代器一般来说该状态的输入都是数组或者集合的形式然后循环执行迭代逻辑。
这里我们要注意的是不同厂商在实现上虽然大体逻辑大致是遵循CNCF的标准协议的但在语言的定义和约束上还是稍微有些不同且各家的名字也有不同比如阿里云FDL、腾讯云TCSL、亚马逊ASL等。你可以参考下面我列出的对比表格看一下
你会发现部分厂商根据自身情况还增加了一些状态如成功Succeed和失败Fail其他的状态除了名字上的差异外功能上基本相同。我们以阿里云Serverless Workflow举一个例子来加深一下理解
version: v1
type: flow
steps: #对应DSL中的状态FDL中称为步骤
- type: parallel #type必须为parallel,表示为并行任务
name: parallelDemo #name为当前步骤的名称
branches: # 表示多个分支,可以对应多个子步骤。
- steps:
- type: task #子步骤的类型为Task
name: function1 #子步骤的名称为function1
# resourceArn表示调用的函数标识
resourceArn: acs:fc:{region}:{accountID}:services/{serviceName}/functions/{functionName}
- steps:
- type: task
name: function2
resourceArn: acs:fc:{region}:{accountID}:services/{serviceName}/functions/{functionName}
你也可以利用resourceArn和pattern的组合用于集成多个云服务的能力。其中resourceArn来定义集成的目标服务使用pattern定义集成模式。这里每一个状态的具体使用字段我就不详细展开了如果你需要开发和上线可以根据具体选择的云厂商通读一遍他们的手册。
架构设计
了解完工作流的基本构成和模型的定义语言之后如果仅仅是使用那么其实就差不多了我们只要能熟练地使用语言定义出相应的JSON或者YAML格式的文件就好。
那如果,我们想自己开发一个这样的功能,又要怎么实现呢?我们来看一下它的整体架构示意图:
如图所示Serverless工作流通常需要具备元信息管理、调度和执行三个核心服务的模块。
APIServer负责元信息生命周期的管理包括状态管理、模板、执行记录等信息
调度服务:根据数据流请求,转发调度到对应的执行引擎,一般来说,它还需要具备负载均衡、限流、故障迁移等能力;
执行引擎:负责解析流程语言、执行流程,上报执行历史和调用其他云服务等。
在使用上用户通过APIServer控制工作流的基本信息然后通过请求调度模块执行工作流具体步骤和图片的示意相同。这里我们要重点关注一下数据面的请求流向
请求到达调度模块后调度模块首先会从APIServer获取当前工作流的定义内容和执行任务的元信息
调度模块根据请求内容将元信息和请求内容分发到指定的执行引擎;
执行引擎会为本次执行生成一些元信息并记录到数据库因为除了工作流本身的定义外每一次执行都是无状态的所以需要设置单独的任务ID ,方便后续做一些请求重入的操作;
执行引擎根据流程定义语言语法,解析传入的工作流定义;
执行引擎根据解析出的状态有序执行,并根据状态语义调用其他服务进行处理。
最后当所有State执行结束后工作流执行引擎会根据最后的输出定义返回结果。
当然你也可以拆得更细一些比如可以将与前端打交道的部分拆出来一个Console服务将模型配置和模板示例拆分成一个模板服务出来服务的粒度拆分没有硬性规定。
生产级考虑因素
按照上面的架构你应该可以实现一个Demo级的工作流了但是要完成一个生产级别的工作流还需要考虑高可用、高性能和安全等相关的因素。
首先我们的三大服务就不能以单点方式呈现了需要以分布式的方式来部署。每个服务上层通过负载均衡层转发提供一个对外暴露的HaVIP或者域名。
其次,对于执行引擎来说,因为工作流的核心内容是语义的解析与工作流程的执行,并且每次工作流任务都会启动一个独立的协程或线程工作,因此该引擎的负载较高,需要保证其具备横向扩缩容的能力。通常的做法是部署一个注册中心,然后将执行引擎进行服务注册,调度服务则会通过服务发现了解当前执行引擎的实例情况。
第三随着应用和调用次数的增加在生产环境中也需要格外注意消息中间件、数据库中间件、存储中间件的选取比如执行记录的数据就不能像DEMO期间那样用MySQL应付你需要选择诸如Elasticsearch、Apache Doris等相关的存储系统。
第四考虑到系统的性能以及面向流量洪峰的情况还可以在调度服务和执行引擎中间采取消息中间件、缓存中间件来处理。比如频繁数据的读取这种情况就可以将元数据和模板等信息预加载到诸如Redis这样的缓存系统中。
另外,还有两个注意事项没有在我们的示意图中体现。
安全相关:你需要将业务服务接入用户认证和权限系统,确保服务的安全性;
可观测:工作流面向的是复杂的业务编排场景,发生问题后排查起来难度较高,因此,系统的全链路监测就更加重要了。你需要提供链路状态的转换记录、执行结果和执行耗时等指标,并根据这些指标设定相应的预警机制。
请求可重入
我们再来看请求的可重入。前面我们提到,调度服务要具备请求路由、服务发现、故障转移、实现限流、负载均衡等能力,这些都是我们在实现一个微服务功能需要考虑的治理手段。
除此之外,你还需要关注一下请求重入的能力,保证同一次请求的执行可以落在同一个执行引擎的实例。这样,就避免了同一个请求重复执行的问题发生。
为了保证请求可重入首先我们要尽量让同一工作流的任务落在相同的执行引擎上这里可以通过hash映射的方式将每个用户ID和执行引擎实例相关联并写入redis中。
然后为了区分重复的执行任务还需要为每个执行任务打上唯一标识。因为不同工作流的执行任务可能会有相同的name为了保证唯一性和可读性我这里给你提供一种方式来生成任务ID
TaskID = UserName|workFlowName|TaskName
将TaskID与执行引擎实例的关联关系记录在Redis中每次执行前就可以判断当前的执行请求是否已经被执行了这样就可以避免重复的执行任务。
需要注意的是当某个执行引擎出现故障时调度模块会将Redis中故障执行引擎的所有执行任务全部调度到其他实例上以此实现任务的迁移保证整个系统的稳定性。当然你也可以选择其他的K-V中间件来实现这个思路。
语法解析流程
对基本的架构了解清楚之后,我们来看一下执行引擎的语法解析是如何实现的。
前面我们提到工作流的定义格式以JSON和YAML的形式存在我们需要先解析定义的文件信息然后再按照顺序去执行每个定义好的状态State。工作流内*状态之间的关系是具备数据结构中*“*n叉树*”特征的,所以,可以采用深度优先的遍历思想来执行工作流中的每一个状态。
我这里根据DSL的状态分类给出了一段核心处理过程的代码为了便于你理解我采取switch…case的方式进行你可以选择更好的设计模式来实施
func (flow *Flow) ProcessState(input *Input, flowDefinition *FlowDefinition) *Output {
...
var output *Output
state := flow.getStartState(flowDefinition)
for {
switch state.GetType() {
case api.Event:
output = flow.processEvent(state, input)
case api.Operation:
output = flow.processOperation(state, input)
case api.Inject:
output = flow.processSleep(state, input)
case api.Switch:
output = flow.processSwitch(state, input)
case api.Parallel:
output = flow.processParallel(state, input)
case api.Foreach:
output = flow.processForeach(state, input)
default:
flow.logger.Errorf("invaid state type: %v", state)
...
break
}
if output.Error != nil {
break
}
// 遍历到最后一个state退出
state = state.NextState(flowDefinition)
if state == nil {
break
}
// 当前state的输出为下一state的输入
input.Input = output.Output
}
return output
}
接下来,我们就根据示意代码理解一遍语法解析的流程。
按照深度优先的遍历原则,我们首先需要根据定义内容获取开始状态,并将开始状态作为当前状态,同时将调用请求中的输入数据作为开始状态的输入。
接着根据当前状态的状态类型选择指定的处理流程比如Event类型你的processEvent方法实现就可以根据State中的Action去调用相关服务的API接口来执行该次任务。如果是Parallel状态就可以为每个状态子任务启动一个协程或者线程去执行然后同步等待所有子状态执行完再将结果返回。
当前状态处理完成后再获取下一状态作为当前状态并将当前状态的输出作为下一状态执行时的输入。如上述代码中的state.NextState(flowDefinition)。
最后,一直到当前状态不存在下一状态时,就完成了整个工作流的执行,并且将最后一个状态的输出作为整个工作流的输出来返回。
到这里,关于工作流的几个核心要点就讲解得差不多了。其实除了上面提到的这些,要做一个功能比较完善的工作流解决方案,像计量计费、支持回调、如何设定通用的调用协议等,也都需要留意。
是不是还意犹未尽呢提前预告一下我在第16节课也会跟你一起动手体验基于模板的核心理念以及快速构建服务的上线方法其中就包含了单函数模板、多函数编排的具体实操以及构建模板的方法让你不仅知道原理还会动手实战。
小结
这节课,我向你介绍了协调多任务执行的编排技术——工作流。
首先,我们要知道,工作流可以用于多个函数和子流程的编排和协调执行,像订单等事务型业务,多媒体大文件处理,数据流水线作业自动化运维等场景都会用到工作流。
再来几个厂商描述和定义业务逻辑的实现虽然不太相同但基本都遵循CNCF的规范。你可以通过顺序、分支、并行等方式来协调一个或多个分布式任务这些任务不仅包括函数还可以是服务和应用的形式。
工作流的核心构成包括流程的定义、流程的解析和各节点的输入输出。我们可以通过DSL语言来编排各个节点状态这些节点包括操作、并行、选择、延迟等8个常用的状态。
在架构设计上我们需要关注它的三大核心服务APIServer、调度服务和执行引擎。但如果你是一个平台设计者还需要考虑安全、可观测、计量计费、日志存储等多个辅助功能项才能完整地设计出一个解决方案。
希望通过这节课你能发现Serverless另一个强大的功能除了函数计算的形态工作流在并行调用、批处理、事务型场景都发挥着巨大的作用Server Less is More并不是一句空话。
思考题
好了,这节课到这里也就结束了,最后我给你留了一个思考题。
工作流执行引擎的执行记录是否需要一直保存?如果不需要,你打算如何设计一个清理程序?需要考虑哪些要点呢?
欢迎在留言区写下你的思考和答案,我们一起交流讨论。感谢你的阅读,也欢迎你把这节课分享给更多的朋友一起交流讨论。
延伸阅读
CNCF Serverless WG workflow 小组制定了一个完整的协议Serverless Workflow Specification上面不仅给出了规范还给出了相关使用案例。

View File

@ -0,0 +1,216 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
13 可观测(上): 如何构建多维度视角下的Serverless监测体系
你好,我是静远。
今天我要跟你分享的主题是函数计算平台必备的另一个高级能力——可观测。
我们都知道,线上故障的快速定位几乎是所有开发人员都会面临的问题。在函数计算中,一个生产环境下的应用可以包括几十甚至上百个云函数,加上函数本身具有无状态、底层资源透明化等特点,这些都会增加用户排查问题的难度。
因此,可观测性对更加偏向业务的云服务平台来说,无疑是平台的另一双眼睛,能够帮助运维人员和开发者更快地排查故障,定位问题。
也许,你在进行函数开发的过程中,也遇到过这样的问题:
为什么我的函数响应时间突然变得那么长?
为什么我的函数突然访问不通了?
为什么我的函数一直返回4xx、5xx错误
为什么我的函数一直超时?
……
带着这些问题我将通过可观测的三大支柱指标、日志和链路从不同维度带你学习Serverless函数计算平台下可观测体系的构建。
希望通过这两节课让你对函数计算在用户开发和平台运维过程中可能遇到的一系列问题和相应的解决方案有一定的了解构建多维度的Serverless检测体系助力业务更快、更稳地运行而我们最开始的疑问也都会一一得到解答。
Serverless下可观测的重要性
在Serverless的函数计算范畴内除了函数开发函数计算平台本身的运维工作也会暴露出各种问题。我把这些问题分为函数执行和平台运维两大类。
针对这两大类问题,我们往往不能根据函数返回的结果或者问题的表象立刻找到问题的症结,而是需要随着调用链路层层深挖。
可以说,资源的不可见让函数计算平台在用户视角下显得更像是一个“黑盒”,用户向“黑盒”发出请求后得到执行结果,但并不知道请求是如何调度到函数实例的,也不清楚代码究竟是如何运行的。图中像代码异常、执行超时、并发超限等等这一系列问题,最终只能通过“黑盒”的输出得到。
另一方面函数计算平台涉及到调度、扩缩容、元信息管理等多个中控服务之间的交互函数实例又离散分布在一个庞大的Kubernetes集群中单节点故障时有发生并且所依赖的下游服务也有不稳定的可能性这样复杂的架构让线上问题的排查变得更加困难。
种种问题都表明了可观测性对于函数计算平台的重要性。那么,一个多维度的函数计算可观测体系应该如何构建呢?
我们可以参照云原生的可观测思路,提供监控度量、日志以及链路三种数据,再根据用户开发与平台运维的实际需求,构造一个多维度的可观测体系。
需要注意的是构建可观测体系时也要结合函数计算平台的特点比如在用户侧开发者更关心的是业务层面的处理流程并不会像使用其他PaaS平台一样关心请求在服务内部卡在了哪个环节。因此用户侧的可观测数据应该尽量与调度层面的处理细节解耦。而在平台侧我们又要考虑到复杂的集群环境以及多服务之间的关联。
整体解决方案
下面的思维导图总结了Serverless函数计算可观测体系建设的核心要点结合函数计算在用户与平台下的两个视角列出了我们在构建监测体系时需要考虑的关键点。我们通常需要通过可观测的三大数据支柱指标Metrics、日志Logs、链路Traces来思考函数计算的不同场景。
接下来,我会根据整个大纲带你体验构建一个函数计算可观测体系的过程。
指标
在服务出现问题的时候,不管是用户还是运维最先关心的总是请求出错的整体情况,而了解整体概况最快的方法就是监控指标。
确定指标
要想通过监控指标观察服务的整体运行状态,我们面对的第一个问题,就是指标类型的设定。对于使用函数计算的用户而言,他们更关心的是业务逻辑,因此,常用的用户侧函数监控指标会包括以下几种:
函数的总执行次数;
函数的平均执行耗时;
关键错误类型的发生次数超时、并发超限、5xx系统故障等
函数执行的资源使用情况;
……
而对于平台侧来说,我们往往更加关注的是函数计算的服务体系是否在正常工作以及涉及到的底层集群资源是否健康。因此,平台侧的系统监控通常会关注以下几个方面:
服务处理的总请求数量;
服务处理的失败请求数量;
单次扩缩容成功/失败的实例数量;
集群节点总数的整体变化趋势;
集群节点的资源使用情况;
服务节点的资源使用情况;
……
指标的收集与上报
在指标类型确定后我们就可以收集、上报指标了。对于这部分流程业内已经有很多不错的解决方案了。接下来我将以Prometheus这款较为成熟的开源监控产品作为示例来讲解。关于Prometheus的安装过程你可以参考官方手册进行搭建。
如果你对Prometheus有一定的了解那应该也知道它的基本工作原理是不断地向Exporter指标上报器获取数据而像CPU、Disk、Memory这类资源指标类型可以利用Promethues官方提供的node-exporter进行采集。另外对于集群和单节点的部署也有了比较成熟的高可用解决方案。
我们这里主要还是聚焦于业务指标。以函数调用次数为例我们利用Promethues提供的SDK模拟一个简单的上报指标看看自定义的指标数据最终是如何展现到监控大盘上的。
package main
import (
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func main() {
// 构造一个名为function_x_invocation_times的自定义指标
// 模拟函数的调用次数
invocationTimes := prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "function_x_invocation_times",
Help: "count the function x of invocation times",
},
[]string{`functionName`},
)
// 将指标写入注册表中
registry := prometheus.NewRegistry()
registry.MustRegister(invocationTimes)
// 函数调用次数模拟器
mockFunction := func(functionName string) {
for {
invocationTimes.WithLabelValues(functionName).Set(float64(100 + rand.Intn(10)))
time.Sleep(time.Second * 2)
}
}
// 模拟两个函数的调用
go mockFunction("functionA")
go mockFunction("functionB")
//启动自定义的Exporter
http.Handle("/metrics", promhttp.HandlerFor(
registry,
promhttp.HandlerOpts{Registry: registry},
))
http.ListenAndServe(":8080", nil)
}
如上述的代码所示我们构造一个名为function_x_invocation_times的自定义指标来模拟函数的调用次数并将它写入Promethues的注册表中。然后我们需要模拟一下函数的请求通过启动两个协程模拟两次函数的调用并启动一个自定义的Exproter供收集器采集。
在程序启动后访问本地的http://localhost:8080/metrics 路径,就会看到上报的两个函数的调用次数了。
接下来我们需要从metics接口获取数据发送到Promethues不过在启动收集器之前还需要进行一些简单的配置。我们可以在prometheus.yml增加函数指标的Exporter。
scrape_configs:
- job_name: "serverless"
static_configs:
- targets: ["localhost:8080"]
完成配置之后我们再执行二进制文件启动Prometheus通过默认的http://localhost:9090 地址即可登录。进入主界面后,我们就可以查询到刚才上传的函数调用信息了。
监控数据被收集完成后需要对其进行存储以便下游服务使用。这类具有时序关系的指标数据通常采用时序数据库TSDB进行存储。这类存储专门用来解决像监控指标这类产生频率快、种类繁多且强依赖于时间顺序的数据持久化的问题。
Prometheus其实已经内置了一个时序数据库在一些数据量不大的场景下是完全够用的但如果你需要打造一个生产级别的可观测体系我还是强烈建议你外接一个专门的时序数据库。
另外在函数计算场景下由于单个函数的实例可能遍布在集群的任意节点这时如果仅靠DaemonSet方式部署的Exporter进行信息上报其实效率并不高。因为Prometheus除了收集外还需要汇聚来自不同节点上同一函数的指标一旦请求激增汇聚服务的压力就变得非常大。
考虑到需要让Prometheus的收集工作更加稳定我们也可以在每次请求处理完后将指标存入消息队列并通过消费的方式提前汇总一轮指标。汇总完成后再将指标上报给Prometheus进行二次汇聚。加上上面提到的持久化方案一个轻量级的、函数计算下的监控体系架构就搭建好了示意图如下。
你会发现其中的Scheduler就是扩缩容课程中提到的流量调度模块。在请求的返回路径上它会将函数指标上报到kafka之后通过Function Exporter进行数据消费并对不同节点产生的数据进行一次汇聚完成之后再交由Prometheus二次汇聚最终再通过TSDB完成数据落盘。
可以看到图中Prometheus不光需要从FunctionExporter获取指标还可能从每个节点的Node-Exporter获取资源信息指标这就是我前面提到的“指标数据一旦增多就会加大汇聚服务的压力”的原因。
最后我还是要强调一下在集群规模不大时这的确是一个比较轻便的解决方案但在集群节点数量比较多的情况下我更建议你针对业务指标和资源指标分别配置Promethues。
另外如果你是基于前面章节中提到的Knative来作为Serverless引擎由于Knative内部采用的OpenCensus协议会暴露trace和metrics数据采集也会更方便。你可以配置istio-ingressgateway、Knative Activator和queue-proxy来上传对于用户容器实例如果是java代码还可以无侵入地通过Opentelemetry的Java客户端otel-javaagent来上报。
监控大盘的展示
完成了对函数指标的收集我们又要如何对这些指标做可视化展示清晰地看到数据的变化呢接下来我就利用Grafana采集的指标构造一个函数调用次数的监控大盘。
完成Grafana的安装后我们首先需要配置Grafana的数据源。
在左侧菜单栏依次点击“Configuration->Data sources->Add data source”接着选择Prometheus作为配置的数据源点击最下方的“Save & test”当出现“Data source is working”的提示时就表示已经能够正常连接到Prometheus了。
接着我们再构造一个监控面板在左侧菜单栏依次选择“Create->Dashboard->add panel->add a new panel”进入新面板后我们通过建立一个Query并输入“function_x_invocation_times{functionName=“functionA”}”就可以将刚才上报的关于functionA函数的调用次数信息展示出来了。
按照刚才的方式我们同样还可以看到functionB的调用次数。
到这里,我们就完成了一个简单的函数调用次数的指标监控。
相信你对这些函数计算业务指标从上报到收集再到展示的过程已经有了一个清晰的思路。像前面提到的执行时长、QPS、扩缩容数量等指标在监控的实现上大同小异你都可以尝试自己实现也欢迎你在留言区和我互动讨论。
最后我们再补充一个细节。如果出现线上问题比如开发者的函数返回结果不符合预期或者运维人员发现某个时间段的扩缩容数量始终为0时我们也可以利用到可视化工具的时间段选择功能。选择时最好先将时间粒度调大一些比如我们先观察一天内或者一周内的整体情况看看异常的大致时间拐点发生在哪一天再根据时间拐点附近的时间段做细粒度的排查这样可以有效提高我们的排障效率。
小结
通过本节内容的梳理我们对FaaS形态Serverless可观测的重要性以及方案都有了一定的了解。
在Serverless的架构下由于黑盒的调度和云组件集成的复杂性可观测的构建要比传统微服务的架构难度更大。我们不仅要具备可观测的基础搭建思路对Serverless产品本身也要非常熟悉。
我们还梳理了三大数据支柱之一,“指标”。首先,我们要确定指标的类型,熟悉常用的用户侧、平台侧函数监控指标有哪些。收集、汇聚指标的时候,也要注意汇聚的量级、存储形式、收集工具等要素;最后,收集到的指标也要利用起来,我们可以将采集到的指标做成监控大盘,最大化利用数据,为后续有可能出现的优化动作给出数据支撑。
好了,这节课我们就先到这里,下一节课,我再来跟你细聊三大数据支柱中日志和链路这两部分的内容。
思考题
想一想,日志和链路应该如何处理呢?是否可以归到一套架构上来实现信息的收集?
如果有时间,你可以先按照今天的示例自己操作演练一下指标的收集。
欢迎在留言区写下你的思考和答案,我们一起交流讨论。感谢你的阅读,也欢迎你把这节课分享给更多的朋友一起交流学习。

View File

@ -0,0 +1,138 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
14 可观测(下): 如何构建多维度视角下的Serverless监测体系
你好,我是静远。
上一节课我们一起梳理了Serverless下可观测的重要性和构建可观测监测体系的要点也结合案例学习了指标的收集方法了解了FaaS形态下指标上报的架构设计和注意事项。
今天这节课,我们继续来看可观测的另外两个数据支柱:日志和链路追踪。
日志
我们知道,通常在运维一个系统的时候,从监控大盘了解问题的大致轮廓后,经常会根据日志去查看具体的错误细节。
日志的作用是记录离散事件,并通过分析这些记录了解程序的整体行为,比如出现过哪些关键数据,调用过哪些方法。也就是说,它能够帮我们定位问题的根源。
在函数计算场景下,我们需要考虑到用户日志与系统日志两种类型。其中,用户日志记录的主要是用户函数代码中业务流程发生的过程。这部分日志信息是在函数维度上独立收集的,并且用户可以通过前端控制台查看相关日志信息。而系统日志,则是整个平台侧发生事件的信息记录,最终汇总在一起,供平台侧的运维或开发人员排查问题。
日志数据源
那么日志应该什么时候打印又应该怎么打印呢?
首先我们需要明确日志的级别。以系统日志为例常见的包括Error、Info和Warn分别表示错误日志、信息日志以及警告日志在开发调试过程中可能还会用到Debug类型。我们需要根据实际的执行逻辑来设定不同的等级。
其次在添加日志时我们要尽可能地收敛错误信息尽量避免重复信息的打印因为频繁的IO不仅会加大日志采集的工作量更会影响服务性能。
再次,日志应该尽量打在每个服务模块的入口和出口,服务内方法之间的调用和错误信息,也应该尽量通过传递的方式上报,而不是每个方法内都打印一条日志。
比如函数计算中请求的调度过程。调度模块中可能涉及到获取元信息、鉴权、并发度限制、获取函数实例信息等等一系列串行过程每一个过程都可能包含多个方法之间的调用其中任意一步出现问题都会导致调度失败。那么我们在开发时就要尽可能给每个方法都添加一个error类型的返回值在出现错误后只需按照递归调用栈逐级返回最终在入口处打印一条即可。这样可以有效减少重复信息的打印次数。
另外,对于函数开发者用到的用户日志,我也有两条使用上的建议。
一方面减少print的使用控制整体的信息大小。为了方便在函数这个黑盒中快速定位问题一些开发者习惯性地将print当成Debug工具来使用但因为平台侧都会对单次的函数执行日志有一定限制所以在函数开发过程中应该尽量减少无用信息的打印。
另一方面关注Event。Event作为函数入口的基本传参携带了请求源的关键信息关注Event也可以方便后续的溯源。
日志的采集与清洗
有了日志数据后,我们就可以进行收集了。
系统日志的数据都会写入到固定路径的文件中而用户日志通常都是采用DaemonSet日志组件进行收集。所以一般函数实例内的日志文件都会存放在节点的挂载路径下或者是集群内的持久卷中。
前面我们也提到用户日志是以函数为粒度的,而为了缓解不断增长的日志数据造成的节点磁盘的压力,通常会一次请求对应一个日志文件,请求结束则上报并删除文件内容。而系统日志则可以通过设置“定时删除任务”来处理。
在开源日志收集器的选型上常用的有LogstashFluentdFluent-Bit以及Vector等比较不错的采集工具他们之间各有不同的优势。
具体的对比你可以参考Stela Udovicic 2021年12月在ERA Softwares blog的文章她指出我们很难找到一个完美的日志收集器选择正确的日志收集器主要取决于你自己的特定需求。
比如如果你需要资源占用较少的日志收集器那么使用Vector或者Fluent-Bit就是一个不错的选择而不是占用资源较高的Logstash。如果你需要寻找不具供应商色彩的收集器那么Fluentd和Fluent-Bit是不错的选择。
在函数计算平台的构建中,通常我们会结合这几种工具的能力共同部署,你也可以根据具体的业务情况自己选择。这里,为了便于你理解他们各自的优势,我画了一个示意图,来看一下数据采集的具体流程。
在采集数据时由于Fluent-Bit在Kubernetes集群等容器化环境中的运行比较出色所以通常我们会使用轻量的Fluent-Bit对日志数据进行整体的上报如果是集群的日志信息则会以DaemonSet的形式部署。
因为Logstash过滤功能强大但资源耗费多所以并不能像Fluent-Bit那样以DaemonSet的形式部署在整个集群只需部署少量虚机实例并利用Logstash进行整体的数据清洗即可。
如果考虑到峰值问题比如前面提到的某一时刻存在请求高峰导致日志量显著增大也可以利用kafka缓冲一轮。最后再由Logstash将过滤后的数据交由相应的存储服务。
日志的存储与检索
最后,我们再说日志的存储和检索。
Logstash支持丰富的插件除了可以支持像kafka、本地文件等多种数据输入源输出的部分同样也支持与Elasticsearch、对象存储等多种数据存储服务的对接。
其中Elasticsearch是一个分布式、高扩展、高实时的搜索与数据分析引擎配合Kibana这种数据可视化工具能快速搜索和分析我们上传的日志。
为了方便利用Kibana进行快速筛查我们在日志打印阶段就应该以键值对的形式标记出关键信息这样就可以在Kibana根据key进行筛查比如
{
"level":"info", //日志等级
"ts":1657957846.7413378, // 时间戳
"caller":"apiserver/handler.go:154", // 调用的代码行数
"msg":"service start", // 关键信息
"request_id":"41bae18f-a083-493f-af43-7c3aed7ec53c",
"service":"apiserver" // 服务名称
}
另外如果需要对日志文件进行溯源或者需要考虑为函数计算平台拓展一些长期的数据报表功能也可以让Logstash对接一个对象存储服务。
链路
了解了指标和日志两个可观测数据之后,我们再去看第三个数据,链路。
除了代码出错,在一些延迟敏感的场景下,性能分析也是必不可少的,尤其是函数计算这种架构复杂,模块交互较多的服务。这个时候,链路追踪功能就派上了用场。
在函数计算场景下,它不仅可以提高函数计算系统的可观察性,帮助系统管理员检测、诊断系统的性能问题以保证预期的服务水平,还可以帮助开发者追踪函数的执行过程,快速分析、诊断函数计算架构下的调用关系及性能瓶颈,提高开发和诊断效率。
链路信息
我们先说链路信息的获取。对用户而言,更关心的是端到端的整体耗时,而除了代码本身的执行,其余耗时主要发生在冷启动的准备阶段。因此,平台可以默认提供给用户函数总耗时以及冷启动过程的耗时,其中也可以包括准备代码、运行时初始化等步骤的耗时。
而在复杂的业务场景中往往会涉及到函数与函数或者函数与其他云服务之间的调用。这时我们可以为开发者提供自定义的链路支持将链路信息记录在相应的结构体中如Header就可以完整地串联起整个外部的调用链路。而内部的调用链路也可以通过上下文的形式用SDK去处理。
通过这种与内置链路结合的方式,平台可以有效地帮助用户定位出超时、性能瓶颈以及涉及多个云服务关联等类似的故障问题。
在平台层面,则可以根据模块之间的关系以及系统架构的实际情况来构建链路,整体思路和用户侧的链路构建差不多。不过需要注意的是,并不是链路信息越详细越好,因为链路追踪本身也需要耗费一定资源,所以最好根据实际的运维需求来构建。
链路拓扑的可视化
那么我们如何追踪链路信息并友好地展示出来呢常用的解决方案一般是基于标准的OpenTelemetry协议利用其提供的SDK和Otel Agent完成对链路Span的生成、传播和上报最终通过分布式追踪系统如Jaeger进行收集形成链路拓扑的可视化。
结合函数计算的特点这里我也给出了一个基本的链路追踪功能架构图供你参考。从图中可以看出OpenTelemetry可以通过三种方式来上报链路信息包括直接使用SDK、Agent Sidecar和Agent DaemonSet你可以根据自己的业务情况选择一种或者多种组合方式。
以Jaeger为例节点服务可以使用Opentelemetry 的SDK或者Agent上报链路信息通过Jaeger Collector统一收集并由ElasticSearch进行存储最终由Jaeger-Query负责展示数据查找。
到这里我们来解答一下上一节课的课后思考题Metrics指标数据是否也可以用此方式来收集呢完全可以基于Otel Agent我们可以将数据发往Kafka、Promethues等数据存储后端的Backends。
小结
最后,我来小结一下这两节课的内容。这两节课,我们一直在讨论函数计算平台下可观测体系的解决方案。我们可以按照可观测中指标、日志和链路这三要素的架构去构建解决方案。
首先监控指标是指对系统中某一类信息的统计聚合。在函数计算平台上的监控不仅需要考虑常见资源指标比如CPU、Memory利用率等还需要考虑用户实际关心的业务指标比如函数调用次数、错误次数、执行时间等。
再说日志的构建。日志起到的作用,更像是在“保留现场”。通过日志,我们可以分析出程序的行为,比如曾经调用过什么方法,操作过哪些数据等。在打印日志时,我们也需要关注函数在关键节点上的输出。
最后链路的追踪是通过对请求打标、透传、串联还原完整的请求过程。追踪主要是为了排障和优化比如分析调用链的哪一部分、哪个方法出现了错误或阻塞输入输出是否符合预期等。服务之间的链路信息耶可以通过Header进行传递而整个数据过程其实和日志采集类似。但从形态上来看日志更像是离散的事件而链路追踪更像是连续的事件。
思考题
好了,这节课到这里也就结束了,最后我给你留了一个思考题。
随着OpenTelemetry的盛行原则上使用一套Library或SDK就可以自动地收集三种数据然后由统一的Collector处理但实际应用中还会受限于新老系统、Otel的成熟度你是怎么处理的
欢迎在留言区写下你的思考和答案,我们一起交流讨论。感谢你的阅读,也欢迎你把这节课分享给更多的朋友一起交流学习。

View File

@ -0,0 +1,245 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
15 选型:不同阶段的数据应如何存储?
你好,我是静远。
今天我要与你分享的主题和Serverless平台“存储”设计相关。通常我们在做一个服务或者平台的设计时存储都是必不可少的一环比如一些用户数据、日志信息、中间状态等。
在你构建一个生产级FaaS形态的Serverless平台时也许会遇到这些问题
函数定义该存在哪里?
代码包这么大该怎么存?
怎么才能保证扩缩容资源的状态被实时感知?
……
这节课我就来和你聊聊Serverless下的数据存储是如何设计的。我将结合上面这些问题从控制面和数据面两个维度展开带你了解一个函数计算平台的存储应该考虑哪些方面。
希望通过这节课你能够对函数计算存储的数据类型以及针对不同特点数据的存储方案有更深的理解从而顺利地设计出Serverless平台的存储方案。
整体思路
那么,针对函数计算的形态特征,我们该如何来设计和选型呢?
我通过下面这个思维导图总结了设计一个函数计算平台的核心要点和思路。
其中,控制面由元数据和代码包两个维度组成;从请求的执行过程出发,数据面主要需要考虑元数据在获取时的缓存处理、 函数Pod资源的调度处理、服务之间的协同操作以及日志相关的收集等维度。其中日志和服务的协同也可以认为是贯穿在整个系统始终的比如服务协同包括服务的注册与发现、消息的通知与协调、选主等。
控制面的存储
我们先从控制面来看,在创建一个函数时需要准备哪些数据。
元数据
在创建函数之前,首先需要有一个命名空间。虽然大部分函数计算产品都会提供一个默认的命名空间来省略这一步,但在实际的业务上云过程中,都会使用命名空间来管理不同的函数,区分业务,这也属于函数计算中的一部分元信息。
接着,我们再选择创建一个函数。云函数的基本属性就比较多了,除了要考虑控制台上你看到的函数名、函数入口、超时时间、内存大小、运行时类型等常见的函数属性,在实际存储时,我们还需要记录函数的创建时间、更改时间以及函数的唯一标识符。除此之外,当使用灰度发布时,我们还需要考虑函数的版本信息。如果用户绑定了触发器的话,还需要记录触发器的一些基本信息,比如事件源服务、触发规则等。
除了函数本身的附属属性外平台通常还需要考虑用户相关的信息存储例如用户ID、权限和并发上限等信息。
我们可以看到,与函数相关的元信息是比较多的,涉及函数、触发器、用户、版本、别名、命名空间等多个实体,其中每个实体又包含众多的属性。
它们之间具有复杂的关联关系,相信你也能猜到,这时,我们需要使用关系型数据库进行存储。它在支持复杂操作和字段更新方面会更灵活,更重要的是,你肯定对关系型数据库并不陌生,几乎每个同学在学校就已经开始接触了。
但估计在数据库的选型上,大部分同学基本上都是靠直觉选择一个自己最熟悉的来做。这里,我跟你分享几个常见数据库的选择。
这是db-engines给出的最新数据库排名其中前四名都是关系型数据库。
排名第一的Oracle作为数据库的顶级霸主已经霸榜多年但是价格昂贵且闭源。而我们熟悉的云厂商的云数据库基本上也是围绕MySQL、SQL Server、PostgreSQL来提供云服务能力。所以可以特别关注一下这几个选项。
其中SQL Server虽然随着新版本的发布和云原生技术的发展也能在诸如Linux的系统上安装运行但从互联网发展历程和各大公司系统采用的数据库来看我更推荐你使用MySQL、PostgreSQL。
MySQL发展至今已经非常成熟并且社区内也很活跃其可用性、性能都非常不错。而PostgreSQL是号称世界上最先进的开源关系型数据库几乎具备MySQL的全部特性在国外已经非常流行但在国内的普及率并不如MySQL。
关于它们的比较和使用推荐你可以参考这篇博文《PostgreSQL vs. MySQL: What you need to know》文中指出
Consider PostgreSQL for any application that might grow to enterprise scope, with complex queries and frequent write operations. If youre new to the world of databases and dont expect your application to scale up, or youre looking for a quick tool for prototyping, then consider MySQL.
你不用非要看懂简单来说其实就是如果你是刚迈入数据库领域的新手或者应用程序规模不大那么MySQL比较合适否则的话推荐使用PostgreSQL。另外要补充的是如果你用的是云厂商的RDS数据库在你选择存储引擎的时候一定要注意规格和价格因为适合的才是最好的。
最后在元数据的存储上有的Serverless函数计算平台考虑到以函数为轴心的存储也会选用类似Mongodb的文档数据库来存储。不过从CRUD和实体管理的角度来看我比较推荐你使用关系型数据库来管控元数据。
代码包
接下来我们来看代码包的存储设计。代码包一般有两种形态一种是基于云厂商代码框架编写的函数打包成ZIP、WAR、JAR包等形式存在一种以自定义镜像的形式存在。
传统压缩包
针对第一种格式的代码包,相信经过这一段时间对云产品的了解,在你的脑海中立马就能显示出“对象存储”的选择。
通常我们提到对数据的存储,一般会想到文件、块、对象三种不同的方式。它们之间的差别我也在这里列出来了:
考虑到函数计算的特点,资源实例因为是无状态的,所以如果采用分布式文件系统,每次都需要进行挂载操作,非常麻烦。块存储也有同样的问题,并且需要自己实现对块的操作。
而对象存储是支持HTTP访问的并不需要进行挂载操作并且KV的特性也会让代码包路径具有很高的可读性虽然在表格上也提到了对象存储是无法直接对对象内容进行修改的但是对于函数代码包而言通常都是读多写少因此对象存储非常适合用来保存用户的代码。
另外因为对象存储是通过键值对的方式保存每一个对象的所以为每一个函数代码包设计一个Key的生成规则就显得尤为重要了。
比如,可以将用户信息与代码信息关联:
userId/hash(codeFile)
这样在上传代码包时你只需要将函数代码包的key作为函数的一个属性存入数据库然后再单独将代码包根据key存入对象存储服务中就可以了。而在代码发生变更时因为代码包和哈希编码也会改变因此需要做到和数据库的元信息同步。
在启动函数实例时再通过对象存储的API根据key值将代码包下载下来这样就完成了代码包的获取。
自定义镜像
讲到“镜像”,我们很容易对应上“镜像仓库”的概念。是的,我们设计一个平台或者一个系统的时候,尽可能地依托一些现有的基础服务,是进一步加快和加固我们服务构建的关键因素。
其实不止是函数代码的自定义镜像文件,函数计算平台本身的服务也会以镜像的形式存在,这些都可以存储在镜像仓库中。
那么,选择一个合适的容器镜像服务就是非常有必要的。并且在你创建镜像仓库的时候也要记得选择私有的限制以确保安全性。目前云厂商提供的容器镜像服务不光支持镜像仓库的托管,通常还会支持像镜像安全扫描以及多地域镜像加速等高级能力。
国内主流的公有云厂商都有相关的容器镜像服务像阿里云的ACR、腾讯云的TCR、华为云SWR以及百度云的CCR目前都做得比较成熟了你可以综合价格、使用地域等多方面来进行选择。
数据面的存储
接下来,我们来看看数据面的存储设计和访问。在第六节课中,我跟你讲到了流量进来后的调度过程,这里,我们可以提取出来几个和数据获取相关的要点。
元信息的获取生产级别的函数计算平台通常会将控制面的数据管控能力和调度能力进行架构拆分那么流量的调度服务就需要通过一个微服务如APIServer来获取函数信息。这里为了提高并发和性能我们可以考虑通过缓存的方式来保存不经常变更的元数据
资源的占用我们知道冷启动实际上是占用一个Cold Pod的调度过程由于生产级别的资源控制模块通常采用分布式方式部署如果两个资源控制模块分别根据不同的请求去占用同一个Cold Pod那么到底是谁获取这个Pod呢又如何保证不会被重复获取呢这就涉及到锁定的问题。
并发排序函数的实例Pod是可以支持多个并发操作的这种情况下我们如何均衡调度呢比如如何知道哪个Pod调度的并发多哪个目前处理处于空闲这就涉及到排序和调度的问题。
除此之外在异步场景下还要看重复、丢失等问题。我们会发现这些问题其实都可以通过分布式缓存中间件来解决比如Redis。它可以解决掉上述的性能与并发问题资源加锁的问题以及排序、去重等问题。
我们看一个跟函数场景比较相关的Pod排序问题。你可以通过Redis的ZSET数据结构用key记录函数唯一标识用member记录关联该函数的warm pod 用score记录当前warm pod 的并发情况。
key:函数唯一标识
member:pod的ip
score:并发度
这样我们就可以将函数、资源与请求三者关联起来。当出现多个资源控制模块占用同一资源时抢到Cold Pod的会先将这个Pod根据key写入对应的ZSET中这样其他资源控制模块就无法占用了。而score 因为记录了Pod的当前并发我们也可以根据score的数值来判断Pod并发的程度以及是否空闲。
这些都可以通过Redis这样的缓存中间件来完成缓存中间件中的数据也可以预加载我们在Console操作的时候就可以提前加载到Redis中。
接下来,我们再来看贯穿系统运行始终的数据:日志和服务状态。
日志
日志对你来说应该已经非常熟悉了。在函数计算中,比较重要的是执行阶段产生的运行日志如何设计和存储。这关系到整个系统问题的排查,链路追踪的可观测能力,以及报表统计等能力。
关于可观测的部分,我们在上一节已经详细介绍了。这里,我们来说一下如何利用日志生成平台的报表数据,比如平台一天的调用量、某个函数一天消耗的资源量等等。
报表虽然是源于日志的采集和汇聚,但和日志本身的存储还是有明显区别的:
报表数据只需要一些关键指标,比如函数的调用次数、资源的使用以及调用时间等,而像一些辅助节点的日志信息,其实我们并不关心;
报表数据的存储时间比较长执行日志有可能只需要保存1-2周或者1个月一般都会定期清理日志而报表和营收挂钩通常保存时间都是以年为单位。
针对报表的存储这里我推荐使用Apache Doris它最早是解决百度凤巢统计报表的专用系统经过多轮迭代构建成基于MPP 架构的云数据仓库并开源贡献给了Apache基金。所以从技术沉淀、业务打磨上来说Doris绝对是你的首选。
Doris不仅兼容 MySQL 协议,并且使用标准 SQL只是这个特点就足以让你快速介入了。另外它的导入和周边生态也非常丰富比如我们日志数据清洗时用到的Logstash就可以作为Doris的输入这样我们的报表就可以通过Logstash筛选出关键的信息保存到Doris中。
当然你也可以根据所在团队的历史或者业务的基础设施情况选择ClickHouse、GreenPlum、或者自研引擎的OLAP数据库。由于底层存储系统的复杂性和运维难度选择一个运维团队合适的存储底座或者云厂商的一个数据库产品也是一种不错的方案。
服务状态
另外,在函数执行过程中涉及到的服务还需要考虑到高可用的部署,这时候,服务状态也需要记录下来。
比如像离线的扩缩容模块,往往会采用主备的方式进行部署,这就涉及到了选主问题。另外像一些服务可能还需要横向扩容,则需要用到服务注册与发现的能力。
这一类问题最常见的解决方案就是使用一个可以同步服务状态的注册中心,来处理不同服务模块的选主、通知、协调以及消息发布等问题。
在云原生时代的今天我推荐你使用Etcd。它天生具有分布式协同源语如事件Watch、租约、选举和分布式共享锁等对自己的定位也是云计算的基础设施。很多上层系统如Kubernetes、CloudFoundry、Mesos都使用了Etcd。同时Google、阿里巴巴等多个大型互联网公司也都在广泛使用可以称得上是大中型集群管理系统的重要解决方案。
关于Etcd的技术和特性比较官网也给出了具体与ZooKeeper、Consul、NewSQL的对比重点从并发原语、线性读、多并发版本控制等10个维度来对比。
简单来说Etcd和Zookeeper解决了相同的问题也就是分布式系统的协同和元数据的存储至于它们两者的差异是设计理念的不同造成的。Etcd相对ZooKeeper“更年轻更轻量”它针对ZooKeeper进行了一些如租约、MVCC等方面的改进。出于稳定性、新特性上的考虑如果是新的应用我更推荐你用Etcd。
Consul的优势在于服务发现它提供内置的健康检查、故障检测和DNS服务。它和Etcd解决的问题侧重点不一样如果是为了解决分布式系统一致性key-value存储的话我那么推荐使用Etcd但如果是端到端的服务发现那么Consul要更好一些。
NewSQL更适合于存储GB级别的数据或者需要完整SQL查询能力的场景。
小结
最后我来小结一下我们今天的内容。这节课我从控制面和数据面两个维度给你介绍了FaaS形态Serverless平台的存储设计思路和资源选型。
我们从中了解了不同的数据实体和运行阶段选型的一些方法和思考。我们需要从数据的特性、企业的基础设施、开发人员的熟悉成本、业务的量级等角度来做一个合理的选择,而不是单单从存储资源的技术指标上来衡量。
正如我们在日志的存储环节中跟你提到的那样如果我们自己运维Doris的成本比较大那“借力打力”选择原本团队就在使用的数据库系统是不是也可以呢做平台也好做业务也好ROI也是我们要考虑的因素。
今天我们谈到的控制面的关系型数据库如果从另一个角度出发以函数为Key存储是不是也可以是的选择类似一个KV的存储也可以就看架构师怎么衡量系统实现的复杂度和便利性这种衡量依赖于业务和系统发展的阶段不同而变化。
所以今天提到的MySQL、PostgreSQL、Redis、Doris、Etcd等都是我们思考的一个具象化的表达而已。我们更关心的应该是构建一个系统的思维。
课后作业
好了,这节课到这里也就结束了,最后我给你留了一个小作业。
根据本节课的了解你可以想一想如果让你设计一个Serverless平台还有哪些数据在本文中没提到你打算如何存储
欢迎在留言区写下你的思考和答案,我们一起交流讨论。感谢你的阅读,也欢迎你把这节课分享给更多的朋友一起交流学习。
延伸阅读
EtcdConcurrency Primitives链接1链接2链接3链接4链接5
ZooKeeperConcurrency Primitives链接6
ConsulConcurrency Primitives链接7
NewSQLConcurrency Primitives链接8
EtcdLinearizable Reads链接9
ConsulLinearizable Reads链接10
EtcdMulti-version Concurrency Control链接11
EtcdTransactions链接12
ZooKeeperTransactions链接13
ConsulTransactions链接14
EtcdChange Notification链接15
ZooKeeperChange Notification链接16
ConsulChange Notification链接17
EtcdUser permissions链接18
ZooKeeperUser permissions链接19
ConsulUser permissions链接20
NewSQLUser permissions链接21、链接22
EtcdHTTP/JSON API链接23
ConsulHTTP/JSON API链接24
EtcdMembership Reconfiguration链接25
ZooKeeperMembership Reconfiguration链接26
ConsulMembership Reconfiguration链接27

View File

@ -0,0 +1,308 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
16 动手体验(一):如何实现业务高效率地开发上线?
你好,我是静远。
在前面的课程中我不止一次提到在Serverless的产品形态中你只需要关注业务的逻辑无需管理复杂的基础设施可以快速地实现应用产品的交付和试错。
但如果你刚接触Serverless使用起来应该会时常磕磕碰碰主要体现在熟悉开发形式的转变、掌握对框架语法、理解对集成服务等多个方面相信你在前面的学习和实践中也有过这样的感受。
其实在Serverless的应用领域已经有了不少常用场景的模板来提升你使用的便捷度了。不知道你有没有用起来过呢
今天,我将带你了解基于“模板”的理念,实现业务快速上线的方式。
希望通过这节课程让你从实战层面体会到Serverless形态技术在“提质增效、快速交付”上面的优势。
Serverless的模板是什么
当你在控制台或者开发工具选择使用模板后通常会在WebIDE或者你的本地编辑器生成一段模板代码然后你再根据实际的需要去更改模板中代码最后再按照代码包的处理方式将这段更改后的模板打包上传就完成了一个云函数的部署。
有的比较复杂一点的场景会包含多个函数模板组成一个应用级别的模板。而我们又可以通过编排多个函数进行来满足我们在12课中提到的工作流场景我们在课程中知道工作流的核心之一是“编排结构”这个JSON或者YAML结构可以沉淀为这一业务场景下的通用模板。
也就是说Serverless下的模板从单个函数模板到多个函数组成的应用模板函数和应用编排下的工作流模板都存在这些模板会给你带来快捷编码、连接上下游配置的能力。
下面我们一起来看看怎么把模版用起来吧。
基于函数模板的转码实战
在我们常见的业务中,会为了适配各种终端和网络条件,将原来的视频转码成各种格式。那么接下来,我就带着你利用函数模板,一起实现一个音视频转码的功能,加深对模板核心能力的体会。
搭建
我以阿里云函数计算FC作为本次练习的平台。
首先我们进入“应用中心”点击“创建应用”会看到很多的应用模板展示这些模板是平台根据用户的使用经验来提供的像函数计算常见的Web应用、文件处理、音视频转码等场景你都可以在这里面找到。今天我们选择“音视频处理”用例中的“音视频转码job”。
接着我们点击“直接创建”进入“创建应用”页面填写设置“部署类型”“应用名称”“RAM角色”等点击创建就完成了一个音视频转码模板应用的构建。
接下来应用会进行一系列资源的检查如果你是第一次创建还需要创建和绑定RAM角色云厂商基本上都会有这一步的操作。
创建完成后我们点击进入具体的应用详情页你会发现音视频的应用包含了多个函数的组成包括dest-fail、dest-succ、transcode三个函数。
相信你通过他们的函数名也能猜出具体功能dest-fail和dest-succ分别对应了转码失败或成功的操作逻辑。比如转码失败的情况下你可能需要添加一些通知操作也可以根据实际的需求通过编码实现。
这个案例中的核心函数是音视频转码的模板函数transcode。它具体做了什么呢我们来看下入口函数handler的实现
def handler(event, context):
LOGGER.info(event)
evt = json.loads(event)
oss_bucket_name = evt["bucket"]
object_key = evt["object"]
output_dir = evt["output_dir"]
dst_format = evt['dst_format']
shortname, _ = get_fileNameExt(object_key)
creds = context.credentials
auth = oss2.StsAuth(creds.accessKeyId,
creds.accessKeySecret, creds.securityToken)
oss_client = oss2.Bucket(auth, 'oss-%s-internal.aliyuncs.com' %
context.region, oss_bucket_name)
# simplifiedmeta = oss_client.get_object_meta(object_key)
# size = float(simplifiedmeta.headers['Content-Length'])
# M_size = round(size / 1024.0 / 1024.0, 2)
input_path = oss_client.sign_url('GET', object_key, 6 * 3600)
# m3u8 特殊处理
rid = context.request_id
if dst_format == "m3u8":
return handle_m3u8(rid, oss_client, input_path, shortname, output_dir)
else:
return handle_common(rid, oss_client, input_path, shortname, output_dir, dst_format)
handler首先会从event中获取bucket、object、output_dir以及dst_format等几个字段它们分别表示什么呢
bucket视频所在对象存储的bucket名称
object视频在对象存储中的objectName
output_dir转码后的视频需要存放在对象存储中的路径
dst_format需要转成的视频格式。
handler会根据认证信息创建OSS客户端对象、生成带签名的OSS文件的URL。
接着根据转换格式对视频进行转换如果目标格式是m3u8就会进行特殊处理否则函数会按照通用格式进行转码。
我们选择其中一个转码功能的代码分支handle_common来看一下它的具体实现
def handle_common(request_id, oss_client, input_path, shortname, output_dir, dst_format):
transcoded_filepath = os.path.join('/tmp', shortname + '.' + dst_format)
if os.path.exists(transcoded_filepath):
os.remove(transcoded_filepath)
cmd = ["ffmpeg", "-y", "-i", , transcoded_filepath]
try:
subprocess.run(
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
oss_client.put_object_from_file(
os.path.join(output_dir, shortname + '.' + dst_format), transcoded_filepath)
except subprocess.CalledProcessError as exc:
# if transcode failtrigger invoke dest-fail function
raise Exception(request_id +
" transcode failure, detail: " + str(exc))
finally:
if os.path.exists(transcoded_filepath):
os.remove(transcoded_filepath)
return {}
你会发现其实在handle_common中主要封装了ffmpeg程序通过调用ffmpeg的执行转码命令转码音视频文件最后将转码后的文件通过OSS的客户端写回到对象存储中。
到这里,我们就基于应用函数模板创建了一个音视频转码的“模子”,也理解了其中的实现机制。下面,我来带你配置特定的业务参数,让它能够执行起来,处理具体的业务。
执行
我们首先在函数计算的同地域通过对象存储OSS建立一个bucket然后上传一个视频到该bucket中。这个过程不难理解你可以参考OSS的使用手册快速实现。
比如我创建的bucket是“geekbangbucket”上传的视频为“demo.mp4”。
然后我们需要通过函数的测试事件来验证音视频的转码是否可以运行。在点击函数测试之前我们还需要配置一些参数以便transcode知道从哪里读取转成什么格式的文件输出到哪里
{
"bucket": "geekbangbucket", // 你在OSS上创建的bucket名称
"object": "demo.mp4", // 你上传的音视频名称
"output_dir": "result", // 你希望将转码后的视频存储的目录
"dst_format" : "mov" // 你希望转码后的视频格式
}
这些参数就是我们在搭建阶段分析的transcode的入参它们会通过event对象传入。
最后我们点击“测试函数”在函数执行成功后我们就可以去OSS上查看result目录下是否生成demo.mov了。
这样我们就完成了一个具有视频转码功能的云函数的开发。你在实际的业务工作中也可以通过设置OSS触发器的方式自动触发执行音视频转码函数。这样一遍操作下来是不是比你平时的开发要高效多了呢
音视频转码进阶
现在,我们已经能够满足一般的业务了,但有的时候,我们也需要同时实现多个音视频的转码。那么,如何基于上述函数模板的能力,快速实现并行的转码处理服务呢?
在开始之前,你可以先回想一下,在编排这节课,我们提到的工作流是不是正好可以解决这个场景?
整体设计
在编排小节中,我给你介绍过工作流的并行步骤,它包含了多个分支,在运行时,这些分支会并行执行。你可以将每一个格式的转码任务对应一个并行分支,然后通过工作流提供的输入映射,将目标格式传入到转换任务中。
考虑到视频转码任务在实际的业务处理中会批量地运行,你还可以使用循环的能力来处理。
结合这个思路,我给你提供了一张工作流的执行示意图,让你理解得更加清晰一些:
这里我为了使模拟的转码场景更贴近真实业务假设的是需要转码成avi、mov、flv三种格式并在转码任务之后添加了一个子任务通过它通知对应的平台或者播放终端。因此你看到的子任务中包含了两个task一个是转码任务另一个是转码后的收尾任务。
具体实现
虽然每个分支对应了一种格式的转换任务但实际上在你前面实现的函数中也可以通过传参的方式控制不同格式的转换。所以这些分支中的task 都可以使用你前面构建的云函数来完成。
我以avi为例做个演示。
正如整体设计中所说的我们首先需要定义一个parallel步骤来控制多个并行任务的处理在我的yaml中对应的是parallel_for_videos。
steps:
- type: parallel
name: parallel_for_videos
branches:
接下来我们可以在branches下配置并行的分支任务。由于我们这边设置的是循环foreach步骤所以在yaml中创建的是循环foreach_to_avi如下所示
- steps:
- type: foreach
name: foreach_to_avi
iterationMapping:
collection: $.videos
item: video
inputMappings:
- target: videos
source: $input.videos
需要注意的是foreach步骤中需要你定义好迭代的变量即yaml中的iterationMapping后续的子步骤也会用item定义字段作为输入变量也就是video。我这里的输入是将待转码信息定义在videos数组中你也可以按照自己喜欢的方式定义。
接下来我们再来定义子步骤。子步骤包含转码和通知两个task我们先看转码任务。转码任务需要用到你前面创建的音视频转码函数也就是resourceArn字段如果你不记得它的使用可以用工作流定义语言任务步骤
另外在工作流中因为是通过inputMappings来定义函数的传参因此输入就需要和函数中定义的事件字段保持一致了。可以看到我这里仍然需要传dst_format、output_dir、bucket、object这几个参数。下面是转码任务的task我定义为avi_transcode_task。
steps:
- type: task
name: avi_transcode_task
resourceArn: acs:fc:*******:services/VideoTranscoder-t6su/functions/transcode
inputMappings:
- target: dst_format
source: avi
- target: output_dir
source: avi_videos
- target: bucket
source: $input.video.bucket
- target: object
source: $input.video.object
完成转码task的定义后我们继续定义另一个task任务我这里定义为avi_inform_task。
- type: task
name: avi_inform_task
resourceArn: acs:fc:*******:services/VideoTranscoder-54jr.LATEST/functions/dest-succ
inputMappings:
- target: platform
source: avi_platform
这样我们就完成了一个分支的定义。这里我也列出了完整的yaml分支给你参考相信接下来你也可以举一反三地实现另外两个分支的yaml定义了。
- steps:
- type: foreach
name: foreach_to_avi
iterationMapping:
collection: $.videos
item: video
inputMappings:
- target: videos
source: $input.videos
steps:
- type: task
name: avi_transcode_task
resourceArn: acs:fc:cn-hangzhou:*******:services/VideoTranscoder-54jr.LATEST/functions/transcode
inputMappings:
- target: dst_format
source: avi
- target: output_dir
source: avi_videos
- target: bucket
source: $input.video.bucket
- target: object
source: $input.video.object
- type: task
name: avi_inform_task
resourceArn: acs:fc:cn-hangzhou:*******:services/VideoTranscoder-54jr.LATEST/functions/dest-succ
inputMappings:
- target: platform
source: avi_platform
定义完工作流的yaml后我们来验证一下这时还需要配置一下工作流的测试参数才能点击执行。
我的输入还是使用的前面用到的视频demo因为我们在工作流中的每个分支定义好了output_dir和dst_format因此这两个参数并不需要传入。而前面我也说过foreach的输入需要定义在videos这个数组中所以我的输入如下
{
"videos": [
{"bucket": "geekbangbucket","object": "demo.mp4"}
]
}
然后我们点击执行,可以在状态栏下看到执行成功的反馈了。
最后我们还可以到OSS中检查一下看看是否转码成功。如果转码成功你一定可以在OSS中看到分别生成的三个不同的目标目录。
你可以结合实际业务场景选择通过API或者定时调度的方式来启动工作流。这样一个具备多种格式并行转码功能的工作流就完成了。
此时你可以将YAML文件保存下来共享给你的其他同学他只需要改动几个参数比如resourceARN、自己的入口Bucket等变量就可以实现跟你一样的函数编排功能了。
这种保存下来的YAML文件同样可以作为模板存在。而我们有的云厂商为了提升开发者的效率也在函数编排的产品化中实现了这样的模板能力。
小结
最后我来小结一下我们今天的内容。今天我跟你从如何实现业务高效率地开发上线的视角以Serverless下的模板为基石和你一起体验了一把Serverless“提质增效、快速交付”的优势。
对于一个刚步入Serverless领域的新手来说使用模板来构建自身的业务应用无疑是一个捷径。我们可以根据业务的复杂程度来选择使用单函数的模板还是结合多个函数应用形态的模板。
面对更复杂的场景,我们还可以发挥工作流编排的优势,将函数与其他的云服务(如文件存储、对象存储、日志服务)结合起来,迅速地完成一个复杂应用系统的搭建。
更进一步的我们还可以将这样的编排YAML文件沉淀下来供身边的同学使用让模版的能力得到真正的体现。
课后作业
好了,这节课到这里也就结束了,最后我给你留了一个延伸作业。
这节课,我们一直是通过主动调用的方式进行实验,那通过触发器的方式能不能实现呢?快动动你的小手试试吧。
欢迎在留言区写下你的思考和答案,我们一起交流讨论。
感谢你的阅读,也欢迎你把这节课分享给更多的朋友一起交流学习。

View File

@ -0,0 +1,245 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
17 动手体验(二):如何在云函数场景下实现一个有状态的服务?
你好,我是静远。
今天我们一起动手体验有状态存储的实现方法。
FaaS作为一种典型的无状态服务虽然为应用的开发、运行和管理提供了重要的Serverless基础但服务并不是孤立存在的一个完整的应用系统往往需要依赖不少第三方的服务比如认证、存储、消息队列等。
另一方面,随着微服务理念和架构的盛行,业务功能和人员分工越来越精细化,我们在工作中,不可能在一个单体应用下实现很多的功能,那么协作就显得尤为重要了。而协作的关键,就在于这些第三方介质能够存储事物状态的特性。
我们从第一节课开始就在以FaaS视角探究Serverless领域的技术和实践。今天我会带你一起了解云函数和有状态背后的故事并带你体验Serverless = FaaS + BaaS 这一理念的运用,实现一个广告物料巡检的有状态服务的设计体验。
FaaS的局限性
FaaS一个比较明显的特点是应用程序的颗粒度不再是集众多业务功能于一身的集合体而是一个个细粒度的函数Function每个函数完成一个具象化的业务逻辑。函数由事件驱动执行由业务处理结束。
这就带来了一个明显的问题如果需要处理比较复杂的功能如微服务中多线程的数据共享、状态驱动的组合能力处理、会话保持等那么FaaS就会显得力不从心。
但是这并不代表FaaS下的Serverless架构就不能处理。你可以先想一想我们有多少方法可以来解决这一问题带着这个问题我们再开始下面的内容。
函数的无限可能
刚才我们讲到函数计算的基本组成单位是“函数Function虽然FaaS本身有其局限性但“函数”本身是强大的。为什么这么说呢
相信你一定玩过积木。其实函数就像乐高中的一块块积木一样它可以作为FaaS的产品存在其中也可以作为微服务模块中的一个函数嵌套功能而存在只是颗粒度的大小不同。
函数本身就是随着服务架构的发展出现的产物,从单体应用到微服务的发展,就是从功能上进行了横向和纵向的切分;从微服务到函数的实现方式,也是整个形态和效率意识上的升华。
为此函数同样作为Serverless的基石而存在。你可以在Serverless的框架应用上采用函数框架的形态来构建比如Google和百度的图谱搜索卡片完全可以用函数来构建那一小块的卡片功能那么搜索的功能就可以基于Serverless存在随着流量的大和小而弹升弹降。
这也就说明了一个重要的点函数是实现业务的一个具体因子你可以将这个因子用于FaaS的构建也可以用于其他Serverless产品的构建。有了这个认知我们来看基于函数形态下有状态的两个方向的实现。
有状态函数服务的两个方向
所谓的“有状态”和“无状态”,其实是针对服务架构来说的。维基百科将计算机中有状态的系统定义为:需要记住之前的事件或用户交互的系统。而这些状态,通常以循环使用的数据、共享的地址等形式存在。比如机器学习中的数据过程、批处理中消息传递的阶段、交互访问中会话的保持,都属于这个范畴。
回到函数的计算中来看,针对有状态的场景,函数的处理目前一般有两种方法。
一种是有状态的函数编程模型。以华为云函数计算为代表主要通过context字段记录函数的状态。函数执行的时候会加载context字段保存的内容。同时也会通过settimeout来设置老化时间如果在老化超时时间内状态没有更新就会把该状态删掉。
这种有状态服务可以不依赖外部模块,因此在响应速度上,会有很大的优势。这种有状态的函数模型主要用于四类场景。
大规模分布式机器学习中的迭代计算场景:这种模式的参数可能达到十亿至万亿个。使用有状态函数服务,可以节省参数的存储空间和网络通信的开销;
大数据计算场景使用有状态函数服务可以避免计算过程中与外部介质的I/O大大降低延时
实时交互型场景:例如多人对战游戏,对延时要求很高,通过对游戏状态的内存化和本地化操作,就可以极大的降低时延;
多人协作场景如在线编辑文档这种可以依靠用户的Session分发到同一个实例上来处理。
总的来说,有状态函数服务的选型,需要考虑到计算与数据的优先关系以及选取哪些数据作为过程状态。
不过,它存储的状态信息有限,如果涉及到上下游服务的依赖和持久化存储,例如高并发的日志消息、视频/图片审核等仅依靠函数状态context是无法实现的也就不太适用了。
FaaS是需要和消息队列和对象存储这些服务上下游交互的。因此FaaS经常需要结合BaaS服务共同实现Serverless化的能力也就是说用户的请求还需要依赖于其他持久化模块。
这就是我要提的另一种方式典型的FaaS + BaaS的组合FaaS提供业务逻辑层的处理BaaS提供基础服务层的能力让有状态的数据、过程消息等存储在BaaS化的服务中。
根据有状态函数的需求不同我们往往会选择不同的BaaS服务常用的有Serverless数据库、Serverless消息队列等。
先说Serverless数据库。业界内知名的Serverless数据库有阿里云的PolarDB Serverless、腾讯云的PostgreSQL for Serverless、亚马逊的Amazon Aurora Serverless。Serverless数据库会将计算能力和存储资源解耦根据业务需求合理分配CPU和Memory资源。
对于内存数据库而言需要大量的Memory资源但CPU资源使用量很少对于事务操作很多的数据库而言资源需求恰好相反。Serverless数据库可以大幅降低成本适合多样化的业务场景。
再说Serverless消息队列。它支撑着函数计算的异步执行消息缓冲、消息解耦能力业界内常用的有Apache Pulsar等。
如思维构建所述正是由FaaS和BaaS的结合才真正完整的实现了架构的Serverless化。BaaS弥补了FaaS的无状态性也就是说Serverless=FaaS+BaaS。
一个典型的案例,就是我们在上传图片或视频到云端对象存储时,经常需要对内容和格式执行一些操作:将涉黄的图片/视频进行标记或者删除、修改图片格式、添加/去掉水印等。
这时我们就可以将处理的逻辑封装成函数并部署在FaaS平台随后创建对象存储触发器。当云端对象处检测到用户上传图片/视频的事件时,就会触发函数计算来执行处理图片/视频的函数处理完成之后会保存在对象存储中这里的对象存储就是BaaS上层的业务逻辑处理就是FaaS。
通过这样的实现方式也就实现了计算和存储分离计算层面是无状态的可以随着流量进行扩缩容函数实例同时由于BaaS化的存储确保流量应对的同时也保证了数据的安全和故障恢复能力。
看完这个案例,你会感觉在云函数场景下,实现一个有状态的服务还是比较容易的。这也是函数计算这几年越来越普及的原因之一。
动手体验
接下来,我们就通过一个案例来动手实操,探究如何在云函数场景下实现一个有状态的服务。
在我们日常搜索的网页中经常会看到各种各样的广告物料然而这些物料可能会混入非法的广告文案也可能会存在URL二跳等问题因此我们要巡检广告物料以确保其合法性。
针对这样一个事情,我们通常的做法就是启动一定量的巡检程序去一遍一遍地扫描广告库的物料,但是,如果遇到节假日,广告物料会激增,或者隔三差五就有一批很大的物料量,那么,我们怎么能够保证快速的扫描完成呢?
如何抉择?
这是一个非常典型的场景,类似的有文章内容的审核、交易对账等。我们来分析一下这类场景的特点:
第一,不是每时每刻的流量都很均匀,时大时小,存在明显的峰值和峰谷;
第二,对延时不是很敏感,不需要毫秒级响应;
第三,有状态,需要对处理过的内容打上标记;
第四,处理起来不复杂,有明确的规则。
这正符合用FaaS形态的Serverless的技术来解决既能应对流量的峰值也能降低成本。至于需要支持状态的标记处理我们可以选择用BaaS化云存储来解决。
如何设计?
在代码开发之前我们先来确认整体的设计框架。扫描任务的触发我们可以选择函数计算的crontab触发器或者一个常驻的任务来监听广告库的物料情况亦或者通过kafka触发器就看你希望怎么实现前面的物料获取部分了。我们的重点是在FaaS形态下开展有状态的存储和业务实现。
广告物料数据的存储,我们选择云数据库。函数计算负责对读取的物料数据进行校验。示意图如下:
最后我们还可以流程图来梳理一遍物料巡检的流程。我们使用FaaS的定时触发器来触发校验任务执行从云数据库中读取需要校验的数据包括物料信息URL、页面文字以及非法关键词。再将广告文档和非法关键词比对以及校验URL链接是否失效。校验完成之后会对不合法的数据和URL进行标记写入到云数据库中。
如何实施?
了解完设计思想后我们接下来看看如何实施。今天的实践我选择了阿里云函数计算和云数据库RDS进行。
我们需要提前在阿里云FC新建一个Golang的函数并下载到本地。同时开通RDS MySQL数据库注意它是支持以Serverless的模式开通的。
BaaS存储
正如前面所说,物料数据和非法文字是要存储到云数据中的。我们来看看如何设计这两个数据结构。
首先是物料数据,可以按照如下的样例设计实施:
// 物料存储
type AdvertData struct {
//用户的ID
UserID string
//用户的名称
UserName string
//用户的身份证号码
UserIDNumber string
//广告的url链接
AdUrl string
//广告文案内容
AdContent string
//页面文案是否合法
LegalStatus bool
//url链接是有效
UrlValidStatus bool
}
这个核心结构体记录的是投放用户的信息以及投放的广告文案和url数据。同时使用“UrlValidStatus”和“LegalStatus”字段来表示该用户的url链接是否有效页面展示的文案信息是否合法。
接下来是非法关键词,同样可以按照下面的代码示例来实施。但是,如果是要用到生产环境,字段就要按照系统的复杂程度来设计了:
// 非法关键词存储
type IllegalKeywords struct {
//关键词名称
Keyword string
//关键词语种Chinese/English
languages string
// 非法等级:高/中/低
IllegalLevel string
}
有了存储结构,我们再来看看函数计算是如何和云数据库连接的。
首先我们要在下载的Golang代码中的s.yaml文件里配置RDS数据库环境变量。这里需要注意如果创建的RDS实例不在函数计算支持的可用区则需要额外增加VPC配置。
随后在数据库的初始化代码ConnectDb()中我们可以从环境变量里直接读取数据库的配置信息连接到RDS数据库。你也可以将ConnectDb()放到函数入口处理handler的外边这样数据库连接实例就可以在销毁之前继续复用避免重复创建。至于关闭RDS数据库的连接我们可以利用CloseDb()函数实现。
核心流程
接下来我们来看看函数计算处理的核心逻辑,获取所有非法关键词、获取物料、检验物料合法性。为了方便理解,我们以伪代码的形式简述实现思想:
func CheckAllAdvertData() {
// 获取所有的非法关键词
keywords, _ := illegalKeyword.GetAllIllegalKeywords()
i := 0
for {
// 获取物料,真实生产环境你得考虑一下索引问题
advertDataList, _ := ad.GetAdvertData("desc", PAGE_SIZE, i)
if len(advertDataList) > 0 {
// 校验物料的合法性
CheckUrlStatus(advertDataList, keywords)
}
i++
}
CloseDb()
}
这里我们可以重点关注一下物料合法性校验函数CheckUrlStatus实现过程。
func CheckUrlStatus(advertDataList []*AdvertData, keyWords []*IllegalKeywords) error {
for _, advertData := range advertDataList {
// Get the data
resp, _ := http.Get(advertData.AdUrl)
//url 链接已经失效
if resp.StatusCode != 200 {
newAdvertData := &AdvertData{UrlValidStatus: false}
err = advertising.UpdateAdvertData(advertData, newAdvertData)
}
.....
body, _ := ioutil.ReadAll(resp.Body)
bodyString := string(body)
//校验url的数据中是否有非法的字符
checkResult, _ := checkUrlData(bodyString, keyWords)
// 如果校验失败,那么写入数据库中,更改字段
if checkResult {
newAdvertData := &AdvertData{LegalStatus: false}
err = advertising.UpdateAdvertData(advertData, newAdvertData)
}
}
fmt.Println("校验成功")
return nil
}
校验函数会先请求该URL判断返回是否正常。如果URL链接已经失效那么会更改云数据库中UrlValidStatus字段标记为URL失效状态。如果URL正常则会分析返回的数据中是否还有非法字符如果有非法字符会更改LegalStatus字段将该物料信息标记为非法。
到这里一个广告物料巡检的有状态服务的主要实践要点就已经讲解完了。实际上关于广告物料和Landing Page的扫描、校验过程远比我们今天的实操要复杂得多不过只要我们掌握了最基础的流程设计以及最关键的要点设计那么更多生产环境的复杂情况相信你也可以触类旁通地胜任。
小结
最后,我来小结一下今天的内容。
首先我从函数计算FaaS的局限性介绍了FaaS在处理有状态诉求的场景下的力不从心。但我们可以看到函数作为Serverless的重要因子可以说是无处不在的甚至我们可以基于函数的形式让更多微服务的业务功能开发更高效。
由此我们可以看到在有状态场景下两种不同方式的实现一种是基于函数计算本身有状态服务的实现另一种是FaaS+BaaS的结合来实现。
有状态函数服务在机器学习的迭代计算场景、大数据的计算场景、实时交互和多人协作场景上比较适用而FaaS + BaaS其实就是完整版的Serverless的实现我们考量一个应用系统的Serverless化的能力也是要从这两方面来看的。目前各大云厂商也都陆续推出了不同的BaaS化服务如数据库、消息队列、缓存等旨在将计算和存储分离的更彻底让弹性做的更好。
最后,我们通过一个广告物料巡检的案例,也帮助你加深了对于“基于存储介质实现更复杂的云函数功能”的理解。希望你在后续的工作中,能在降本增效方面有更多的参考选择。
思考题
好了,这节课到这里也就结束了,最后我给你留了一个思考题。
你的工作中现在或者未来可能会有哪些FaaS+BaaS结合的场景你目前遇到哪些BaaS化的服务使用上感觉如何呢
欢迎在留言区写下你的思考和答案,我们一起交流讨论。感谢你的阅读,也欢迎你把这节课分享给更多的朋友一起交流学习。

View File

@ -0,0 +1,226 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
18 实战指南Serverless沙场老兵的一线使用经验
你好,我是静远。
恭喜你完成了Serverless核心技术和扩展能力的学习从这节课开始我们就要运用之前所学的知识点开始实战演练了。
前面我们讲到的触发器、冷启动、扩缩容、运行时、可观测等知识点相信你都已经掌握得差不多了随便拿出一个相关的问题你也可以快速应对。但是这些零碎的“武功招式”怎么才能在实战中运用起来呢Serverless架构模式的开发和传统架构应用的开发如此不同我们需要格外注意哪些“坑”呢
今天我就把平常“取经”得到的经验以及自己总结出来的“客户常见难题”分成方案设计、资源评估、调用速度、开发技巧、线上运维等几个方面跟你聊聊如何用好“Serverless”。
希望这节课让你能够在选择合适的方案和开发手段这些方面得到启发真正享受Serverless带来的红利。
方案选型
方案选型的时候选错场景和技术大方向是比较忌讳的不仅可能导致服务不稳定还会导致修补工作量上升。那么什么情况下适合用Serverless技术呢
比较适合Serverless技术的场景通常由数据处理计算、应用后端服务、事件触发处理、有状态服务和传统服务升级 5个维度组成。
接下来我们可以对每一个业务的特性做一个抽象总结看看5个维度不同的侧重点让你在日新月异的Serverless场景迭代下也能够自己动手丰富这张技术选型图谱。
数据处理计算的场景
如果是大流量数据且没有峰谷显著特性的话你完全可以使用Spark、Flink这样的技术来处理只有在峰谷显著的时候Serverless的“极致弹性”才有发挥的余地。也就是数据场景流量变化较大、需要算力动态调配的时候选用Serverless。
事件触发类场景
这个场景是从FaaS诞生就常用的领域了。它的特性在于“流量的不稳定”有就触发没有就缩容至0。如果你的业务场景比较轻量且流量不稳定那么“精益成本”按需使用按量付费的Serverless就是你的最佳选择。
应用后端服务
区别于传统的微服务应用应用后端服务更多的是对于微服务的一种补充多用于相对独立、架构简单的业务应用。比如小程序、H5活动页、BFF的场景它印证了Serverless“快速交付”这一特点。
这三个场景我用蓝色做了区分不管是通过函数编写还是自定义镜像FaaS形态的技术都足以支撑了。而黄色的部分代表Serverless内涵延伸后尤其是近几年技术底座和产品发展后能够支撑更多的场景。我们继续往下说。
有状态的场景
有状态的场景打破了之前狭义上Serverless无状态的特性让长进程、多任务、有状态需求的交互也能在Serverless下完成。
传统服务升级
当我们遇到历史包袱比较重比如服务架构集成了SpringBoot、SpringCloud、Dubbo等框架的情况就可以考虑通过Serverless托管服务的模式转型了。另外如果希望省去K8s的运维和容量的规划成本考虑Serverless的容器转型也可以划分在这类场景中。
除去这张图上的5个维度反过来想一想哪些情况不太建议使用Serverless技术呢
我们之前选入的场景都具有“运维门槛较高需要免运费和全托管”“流量变化较大需要弹性伸缩”“轻量应用需要快速交付”“流量不确定需要能缩容至0按需使用按量付费”的特性那么反过来比如延时非常敏感的、SLA级别极高的场景比如类似网页搜索、银行交易就不太建议使用Serverless技术了。
在这里我也给你分享一个技巧你在初次接触Serverless的时候可以多参考云厂商官网的案例提工单和他们的售前工程师交流可以帮助你更好的来选型和用好他们的产品。虽然我前面的课程中已经给出了很多一线案例和经验但Serverless的发展是日新月异的紧贴官网是最好的选择之一。
资源评估
方案确定之后我们就要做资源评估了。虽然Serverless具备按需使用、按量付费的特点但这并不能说明Serverless的架构所产生的费用就比传统服务低。
你可以尝试按照这两个步骤评估:
第一,你的服务一天跑下来,是否存在明显的空闲期或者具备明显的“峰谷”现象?
第二,具备第一点后,是否选择了合适的资源规格去计算、对比各个方案费用?
这两点适用于所有选用Serverless技术的产品。我们拿函数计算来说它的收费是按照调用次数、资源使用耗时和出网流量来计算的。从各个云厂商的计价表可以看出主要的费用来自于这三点中的资源使用耗时而“资源耗时=内存大小*运行时间”。
从公式可以看出,资源的耗时成本是和内存大小、运行时间成正比关系的。在资源评估阶段,我们先说内存大小的问题。
我之前遇到一个客户他的调用次数每天大概是50W次每次执行在3秒左右但费用还不低。
通过平台监控我们发现他为了处理少部分数据量计算比较大的文件准备的是1G的内存这个数量只有不到1000次的调用量。
听到这里你有啥感想呢不急我们先来看一下这两种内存下的收费情况我以阿里云函数计算FC活跃实例资源价格计算如下
我们可以发现在不同的内存资源下费用相差4倍。所以当时我们给客户的建议是在第一个函数 Func1 中判断一下文件大小如果超过256MB承受的范围就通过异步策略发往下一个函数 Func2 处理这个函数设置为1GB就可以在比较低的成本下完成他的业务处理。
我们来看一下费用对比:
所以,在资源评估时,依据情况合理地进行函数拆分,也是至关重要的。
调用速度
刚才公式里面的第二个关键因素是“运行时间”,这就和调用速度相关了。那影响调用速度的因素有哪些?或者说,调用速度卡在哪里了呢?
从我跟客户打交道的经验来看常见的有这么几个点也是代码编程中容易忽视Serverless这种开发模式的地方。
代码包太大引入了不必要的依赖包这种情况尤其在Java的开发者中特别常见要善于用exclude去掉一些不相关的依赖。
运行时的影响之前我的一个客户由于使用了Java导致出现偶发的超时错误发现就是冷启动的时候耗时较长后来干脆替换成了Python就没有这种问题了。尽管在阿里云等厂商对Java的运行时做了优化但还记得我之前在运行时小节中跟你对比的么Java的耗时除了在冷启动在Invocation阶段也比Golang要长。
调用下游服务返回慢:要知道,函数是按照使用次数和时长来计费的,抛开这个不说,函数的超时时间设定的限制,如果太慢的下游服务返回,会出现超时错误。不仅浪费成本,还使得服务体验不好。
代码习惯问题比如引入不必要的框架它确实可以加快开发的速度但也会影响服务的速度另外就是不必要的循环和Sleep关键字在代码中的使用这些都是在设计函数代码架构和逻辑中格外需要注意的。
总结下来攻克这些问题需要做的就是两件事一个是函数特性的优化另一个是函数之间的协作优化。那么作为一名Serverless平台的用户具体可以做些什么呢
在函数特性优化方面你可以做5件事
尽可能减少不必要的代码依赖,优化代码体积,提升代码的下载和加载速度;
选择性能较高的运行时提升函数的启动速度比如Golang语言
合理利用本地缓存,对于比较大的数据,可以适当缓存在挂载路径中;
预留实例。我们知道Serverless的实例在没有流量进来的时候是会缩容掉的那么下次再有流量进来就会进入冷启动的流程你可以在成本允许的范围内对部分延时敏感性较高的函数设置一定的实例进行预留和分策略加载
请求预热。你可以通过配置一个定时触发器的方式,来主动调用函数来请求预热,设置的时间间隔你可以根据云厂商的实例回收时间来灵活配置。
开发模式。尽量让自己的代码在一层的处理逻辑中,以“平铺”的方式解决,避免层层嵌套、循环、停顿这种在微服务或者脚本上经常使用的习惯发生。
针对函数之间的协作,你同样可以做如下优化,来提升调用的速度:
尽可能使用异步调用,尤其是级联的函数调用避免同步调用下游服务产生的耗时,因此我们在选型的时候得要注意评估;
一个函数只做一件事情,也就是说要根据业务进行合理的函数粒度拆分。在成本、复用性、性能上都存在其必要性。
这样的优化能带来什么好处呢相信学过冷启动的你能立马回答出来提升服务的体验。如果你是一个HTTP网站服务响应越慢用户的流失也就越严重间接上你的页面访问和流量也会降低最后影响的是营收。
开发技巧
接下来,我们就到了开发阶段的讨论了。我会从开发工具选择以及编码技巧两个方面切入,这也是我们工作中最重要的两个抓手。
工具的选择
先说工具的选择。一般云厂商都会提供各种方便的工具来支撑项目的开发、调试、打包、发布和运维实现项目全生命周期的管理能力。比如阿里云的Serverless Devs腾讯云的Serverless Framework CLI百度智能云的BSAM等等。
开发工具一般分为三种本地开发工具CLI、WebIDE和IDE插件集成。其中WebIDE的能力更偏向支持轻量化的在线开发管理IDE插件的能力更偏向照顾那些使用成熟IDE的技术人员如VSCode的开发人员。
那面对这些工具我们该怎么选呢?
如果你的业务比较简单采用的又是解释型语言那么首选WebIDE关于它的优势和好处可以在回到第11节课回顾一下加深一下记忆
如果你的业务比较复杂或者是编译型语言那么我推荐你使用本地CLI工具或者插件集成。
这几类工具的区别在于如果你希望交互体验好一点并且对类似VSCode的IDE工具开发的比较顺手了那么将插件绑定在编辑器上是一个不错的选择。而类似Serverless Devs的这种工具如果你是富有极客精神的命令行狂热爱好者你可以用起来丰富的命令足可以支撑你完成项目的管理工作了。
编码技巧
有了开发工具,那编码有啥需要注意的呢?一方面,是资源的初始化复用思想;另一方面,是函数业务的调用和设置细节。我们通过一个例子来看一下。
我在运行时小节中跟你提到过基于标准运行时构建的函数其实是基于一个代码框架运行的。平台提供给你的是一个handler的入口函数你可以直接在这个函数里编写你的业务代码逻辑。但如果函数需要和资源打交道那么就需要在执行存储操作之前先建立连接了。
如果我们在handler函数中写了如下一段连接数据库的代码想一想这会有啥问题
是不是每次请求过来都要重新连接做一遍这个操作?如果这是一个偶尔触发一次的函数,可能看不出问题。但如果一旦并发量比较高的服务场景,就会极大的影响性能,还可能导致数据库的连接句柄超限。
import pymysql
def handler(event, context):
db =pymysql.connect(
host=os.Getenv("MYSQL_HOST"),
port=os.Getenv("MYSQL_PORT"),
user=os.Getenv("MYSQL_USER"),
passwd=os.Getenv("MYSQL_PASSWORD"),
charset='utf8',
db=os.Getenv("MYSQL_DBNAME")
)
cursor = db.cursor()
正确的做法应该把它单独拎出来,放到一个函数中,然后单独调用一次,这样在函数实例被回收之前,如果有新的请求过来,就可以直接复用了。
这个解决方案不仅利用了FaaS平台的实例复用技术也在实例复用的基础上复用了资源对象。如果你还想进一步优化也可以在给数据库对象db赋值和获取的时候判断是否为Null值使得代码更鲁棒。类似地像kafkaClient、redisClient等中间件资源的初始化都可以这样进行。
资源的初始化这个关键问题非常关键一旦处理不好可能还会导致资源服务的连接超限等问题除此以外根据客户常见的业务逻辑问题我综合选取了所有技巧中常用、高效方面的TOP5希望你能灵活用起来。
首先print日志的时候尽可能少而精。如果你使用的是云平台直接print“event”这种整个结构体很可能会撑爆日志的限制。所以尽量打印出你需要的业务字段。
其次尽量使用异步策略代替微服务形态下的异步框架。假如你在原生的微服务中使用Python的Tornado框架你会发现FaaS形态的Serverless是很难发挥其异步的价值的。这个时候你就可以直接选用我在第3讲高级应用中跟你提到的FaaS平台的异步处理框架。
再次,调用下游服务的接口,设置的超时时间一定要小于函数的超时时间。否则会因为短板在下游,而造成一次请求超时。浪费成本不说,也会让服务不稳定。
另外,要将公共代码放到层上。这也是我在高级属性这一节中跟你提到的必备高级技能之一,它类似于我们平时的公共类库,可以让具体的函数代码包更轻量。
最后提前设置并发数的上限。一般云厂商都会设置默认的实例并发数比如阿里云FC限定300 * InstanceConcurrency百度智能云CFC限定单实例100等这对于一个并发量稍微高一点的生产应用来说是不够的你需要提前设置申请扩大并发度。
线上运维
开发完代码发布到线上后有没有想当然的认为Serverless就是“免运维的”虽然我们多次强调过Serverless免运维的特性但上线后的服务稳定性、访问指标以及可观测等问题也是需要关注的。我们着重说一下稳定性和可观测。
稳定性方面,云厂商设置的异步调用策略,可以确保失败后的请求重试,预留实例可以确保流量突增的响应及时性。
除此之外我之前遇到的一位客户简单的方法也很值得借鉴。这位客户是一个在线的网站对重要接口采用轮训模拟请求的方式进行探活检测当出现连续N次出现问题的时候就会报警和切换到备用集群、备用区域。
你会发现在Serverless云厂商还不是很完善的情况下作为一个开发者可以基于“免运费”之上做一层“运维保险”。这个方式不一定最优但确实很实用我们交流之后也发现对保障服务的稳定和客户的体验都有明显的效果。
可观测方面,除了我在可观测小节中跟你细聊了通过指标、日志、链路三大支柱实现一个监测系统的机制,从使用的经验上,我也建议你“活”用云厂商关联的支撑服务。他们的监控仪表盘足以支撑你常用的指标,你还可以通过关联的报警服务、链路追踪服务来更全面地“问诊”服务。
小结
最后我们来小结一下。今天我给你介绍了Serverless实战中的一些使用经验包括方案选型、资源评估、调用速度、开发技巧、线上运维这一个完整生命周期里的心得。
你会发现这节课我多次提到了在第1、2模块中的知识点。比如在调用速度里你可以关注冷启动、函数之间调用这两节课在开发技巧方面你可以着重关注WebIDE、高级应用这两节课在线上运维方面可以更多地关注可观测的内容。
这节课不一定能够解决你在实战过程中遇到的所有的问题,但一定能够作为你的知识地图,帮助你定位每一个困惑背后需要不断精进的知识点。希望能够让你在开始接下来的实战进阶课之前,串联起讲解过的技术细节,形成自己的知识网络。
随着Serverless的不断演进未来的众多产品也一定会朝着Serverless的形态转变。你在后续的工作中还会遇到更多的业务诉求转型、难点挑战希望你也能一个一个找到更优的解决方案并沉淀成方法分享给更多的Serverless前行者。
后续的实战课中我们将一起针对性地练习Serverless技术中的5大核心场景连接云生态、跨平台开发、传统服务的迁移、私有化部署和引擎底座选取以及基于引擎构建平台。准备好了吗
思考题
好了,这节课到这里也就结束了,最后我给你留了一个思考题。
你在使用Serverless的过程中还有哪些“踩坑”经验或者你在技术选型的时候还有哪些疑惑的地方
欢迎在留言区写下你的思考和答案,我们一起交流讨论。
感谢你的阅读,也欢迎你把这节课分享给更多的朋友一起阅读。

View File

@ -0,0 +1,273 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
19 实战进阶Serverless “连接器” 的能力到底有多大?
你好,我是静远。
在思维构建这节课我提到了Serverless的基本构成是“FaaS + BaaS”。FaaS由于其轻便、灵活、开放的输入和输出以及集成了众多云生态能力同时也被云生态所集成的特性被称为Serverless“连接器”的存在也是当之无愧的。
那么它的能力到底有多大它又是如何与云产品连接提供强有力的Serverless能力的呢
今天这节课我将为你讲解连接的方式和组合并通过ETL的场景依次递进带你重识“连接器”的不同使用方式。
重识“连接器”
我们知道,任何单一云产品所提供的业务解决方案都是有限的,而云服务生态下“互相打通、互相连接”的能力,在很大程度上是业务上云的关键。
我们通常会根据实现方式把函数计算的连接能力分为三类触发器连接能力、访问VPC资源的能力、访问公网服务的能力。函数计算通过这些连接能力可以与整个云上的产品服务关联从而形成一个完整的Serverless生态。
触发器的连接能力
说到触发器我们并不陌生。触发器提供的是一种事件驱动的计算模型充当的往往是一个桥梁的角色将事件源的服务与函数连接起来。在第2节课我们还详细梳理过一遍触发器的执行过程和实现原理。那么你知道触发器的连接能力能够应用在哪些场景吗
不同于触发器小节中的集成方式来分类,从场景应用层面来看,按照触发器的联动程度,我们可以把它划分为三大类,分别是云服务集成触发器、原生触发器、自定义触发器。
首先,我们来看云服务集成触发器。顾名思义,它是需要联动其他云服务才可以产生“连接”能力的一种场景。比如,我们熟悉的对象存储触发器可以用来进行一些音视频转码处理,当音视频文件上传后,可以通过触发器对函数进行调用,从而对其进行处理;消息队列触发器可以用来做一些异步消息的通知,当检测到消息队列内的数据发生变化时,也可以通过函数进行消费。
这种方式要么是函数计算集成云服务的打通能力要么是云服务集成函数计算的打通能力。通常来说可以通过SDK/API提前对接好按照服务之前约定的协议如事件驱动、回调等方式来互相联动。
那么,原生触发器呢?在这里,它指的是不需要借助其他云服务,自身即可实现的一类触发器。比如定时触发器仅需配置一个时间点,就可以让云函数实现一些定时邮件通知、定时天气预报之类的任务处理。
从联动性和场景使用程度看原生触发器也可以包括HTTP触发器但我更倾向于将把它作为一种延伸的访问云函数的能力你可以通过HTTP触发器生成的链接作为的Web服务暴露的访问地址。
最后,大部分云厂商为了给用户提供更多的可能空间,通常还会提供自定义触发器的能力,允许用户接入任意云服务的事件,比如通过自定义触发器的方式,对接自研的资源存储或者服务等等。这也极大地丰富了函数计算的使用场景。
跨VPC的连接能力
函数计算另一个连接能力的体现就是跨VPC的访问。通过VPC建联能够让函数更加安全地访问用户的私有网络资源。
比如通过跨VPC使得函数具备访问云数据库、云消息队列的能力从而实现一个较为复杂的后台处理逻辑另外通过VPC的提前打通可以将分布式文件系统直接挂载到函数实例上使得云函数具备大文件处理的能力。
至于为什么提出VPC、跨VPC的意义以及如何优化函数在跨VPC场景的访问速度你可以再回到第10课复习一下加深记忆。
公网访问能力
部分云服务本身就暴露了公网访问接口,这类服务甚至不需要为云函数做任何配置,就可以访问。但云厂商为了安全考虑,同样地,需要设置鉴权和身份认证等保障机制。
我们在前面课程中提得最多的对象存储触发器就可以无需配置VPC直接访问还有一些人工智能场景比如自然语言处理、图像识别等也完全可以通过直接访问公网接口来调用。
这里需要注意的是通过VPC访问与通过公网访问的服务场景是有重叠的这取决于服务本身支持哪种访问方式比如阿里云的云消息队列Kafka就支持公网与VPC两种访问方式。
连接器的组合
无论是事件驱动还是主动请求,上面这几种连接能力并不是完全割裂的。在实际应用中,往往会通过不同的组合方式来实现一个完整的解决方案。
我们举2个例子。
还记得我们在16课动手体验中讲到的“通过工作流进行音视频转码”吗这个问题就可以通过触发器+公网访问的连接能力将工作流、对象存储和云函数关联起来。当一个或者多个视频上传到对象存储,通过触发器触发后,函数可以直接调用工作流进行批处理,然后按照分类转码的处理逻辑给视频转码,最后同步调用对象存储的接口,将转码文件进行写回。
还有我们日常开发的Web应用或者小程序后台也可以部署在云函数上。通过VPC让函数具备访问云数据库的能力再配上HTTP触发器或者API网关用户就能够非常方便的通过暴露的链接进行访问。
相信现在你已经能够理解连接器的强大所在了。接下来我就通过一个ETL场景的实战带你切实感受一下连接器的强大。
ETL实战
在很多边缘计算的场景中,日志信息往往并不能做到实时上报,所以通常会采用定时上传日志文件的方式来进行后续的日志处理和分析。
比如我们最常见的车载日志,大量的车载终端源源不断地产生着行驶日志。实时分析这些行驶信息非常困难,一般都会是在收集到一定数量之后,以文件的形式整体上报。这样不仅可以提高日志传输效率,还能够对整体的日志文件进行暂存,方便后续的溯源。
连接器常规组合
对于这个处理过程,我的客户经常会采取对象存储触发器和消息队列,分别实现函数计算的输入和输出。下面是我给出的一个完整的流程示意图。接下来,我会借助阿里云相关云产品服务展开实验操作。
首先为了接收上传的日志文件我们需要提前在对象存储OSS上创建一个Bucket。同时我们还需要在消息队列Kafka服务上创建一个实例具体可以参考这篇快速入门文档。
接着我在函数计算FC下创建一个处理日志信息的云函数。还记得之前用到的函数模板吗这里我们也可以选择Python3.6运行环境选用“向消息Kafka投递消息”的模板来创建函数。至于和OSS交互的部分可以参考OSS操作模板。
接着我们需要为其配置Kafka访问的地址以及Topic因为模板已经将其声明在环境变量当中了我们可以直接在函数的环境变量中配置。
Kafka服务提供公网与VPC两种访问方式如果通过公网访问的话走SSL协议需要下载证书会比较麻烦这里我推荐你使用VPC进行访问便于你快速实验。
需要注意的是VPC访问需要在函数所在服务下额外配置VPC让函数服务与Kafka实例处于同一私有网络中。具体配置你可以参考下面的图片
创建好函数以后你还需要绑定一个OSS触发器关联上刚才创建的bucket前后缀可以根据你自己的业务需要设定。触发器的配置之前也带你体验过这里我就不再重复了。
最后,结合我们的设计思路,来看看主要的代码处理逻辑吧:
from confluent_kafka import Producer
import logging
import sys
import os
import oss2, json
logger = logging.getLogger()
def initialize(context):
global p, TOPIC_NAME
""" Get the environment variables """
BOOTSTRAP_SERVERS = os.getenv("BOOTSTRAP_SERVERS")
TOPIC_NAME = os.getenv("TOPIC_NAME")
p = Producer({'bootstrap.servers': BOOTSTRAP_SERVERS})
def deliveryReport(err, msg):
""" Called once for each message produced to indicate delivery result.
Triggered by poll() or flush(). """
if err is not None:
logger.info('Message delivery failed: {}'.format(err))
raise Exception('Message delivery failed: {}'.format(err))
else:
logger.info('Message delivered to {} [{}]'.format(msg.topic(), msg.partition()))
def handler(event, context):
creds = context.credentials
# 设置权鉴,供 OSS sdk 使用
# Setup auth, required by OSS sdk
auth=oss2.StsAuth(
creds.access_key_id,
creds.access_key_secret,
creds.security_token)
# Load event content
oss_raw_data = json.loads(event)
# Get oss event related parameters passed by oss trigger
oss_info_map = oss_raw_data['events'][0]['oss']
# Get oss bucket name
bucket_name = oss_info_map['bucket']['name']
# Set oss service endpoint
endpoint = 'oss-' + oss_raw_data['events'][0]['region'] + '-internal.aliyuncs.com'
# Initiate oss client
bucket = oss2.Bucket(auth, endpoint, bucket_name)
object_name = oss_info_map['object']['key']
# 将日志文件下载到本地处理
bucket.get_object_to_file(object_name, "/tmp/tmp.log")
# 逐行读取
f = open( "/tmp/tmp.log", 'rb')
line = f.readline()
while line:
content=json.loads(line)
info = {}
# 提取关键信息
info['speed']=content['speed']
info['carID']=content['carID']
info_str = json.dumps(info)
line = f.readline()
# 发往kafka
p.produce(TOPIC_NAME, str(info_str).encode('utf-8'), callback=deliveryReport)
p.poll(0)
""" Flush the internel queue, wait for message deliveries before return """
p.flush()
f.close()
return {}
这段代码一共有3个要点分别是资源初始化、输入数据的获取、数据结果输出。
首先我们来看资源的初始化部分在initialize 函数中会先完成全局的初始化操作主要是Kafka的相关信息这也是我建议你使用模板的原因因为模板里面已经包含访问Kafka的依赖库了。
其次是如何获取数据处理并输出。我们来看handler的逻辑。因为在发生日志上传时需要将文件下载到函数实例内处理因此这一步主要是初始化一个OSS的客户端。
最后利用刚才创建的client以及event内的对象信息我们可以调用client的接口将日志文件下载到本地。拿到日志文件之后处理过程就简单了。你只需要遍历文件内容提取关键信息再推送到Kafka就行了。比如我这里提取的就是distance和carID然后调用p.produce进行推送供下游服务进行处理。
我们注意到在p.produce函数存在一个callback的参数因为deliveryReport是一个向Kafka发送消息后的回调函数会根据发送结果输出一个提示信息。
之后我们就可以测试一下上述的配置和代码是否能够正常运行了。我这里提前模拟了一个日志文件里面的内容包含了一些基本的车辆行驶信息我将其命名为driving.log并上传到了OSS。
{"speed":12,"carID":1234,"manufacturer":"BenZ","zone":"China","distance":102,"endAt":"Beijing","startAt":"Shanghai"}
{"speed":24,"carID":3512,"manufacturer":"BenZ","zone":"China","distance":102,"endAt":"Beijing","startAt":"Shanghai"}
完成上传后,我们观察一下函数的执行情况,可以看到确实已经触发执行了。
我们再看一下Kafka的监控可以发现也有一定的写入量。
按照上面的步骤,你已经可以实现一个日志处理业务的基本功能了。如果你觉得不够完美,也可以更进一步优化一下代码的分层和鲁棒性,来用于你的生产环境中。
连接器延伸能力
那么,鲁棒性的提升除了我们自己的代码优化之外,函数计算作为连接器,是否本身也具备一些延伸能力来使得系统跟稳定和健壮呢?
我们知道,日志文件的触发是属于异步调用方式的,而异步调用还可以通过异步策略的配置来支持在异常情况下进行重试、分流处理,以确保数据的高可靠性。
也就是说,我们可以为刚才的函数设置超时时间。进入异步策略界面,我们可以根据异步执行结果配置策略,比如我配置的策略就是执行失败后调用下游的通知函数,上报任务失败的情况。你也可以按照你的实际需求进行配置。
进阶ETL
通过上面的场景我们了解了函数计算在解决日志数据分析问题时连接其他云服务的方法和过程。在常规版的ETL实现中日志文件一旦上传就可以通过OSS触发器让云函数进行处理并将抽取转化处理的日志关键信息发往云消息队列服务同时异步策略也为我们提供了额外的重试保障。
但有的时候我们只是为了数据的暂存而选择了对象存储那么是否有一种方式可以直接上传日志而不需要额外占用对象存储的资源呢答案是有的拿阿里云的“连接器”函数计算FC来说我们还可以采用日志服务SLS触发器。
SLS触发器能够定时获取到日志的增量信息而不用像对象存储触发器那样每次将日志文件上传的时候才会触发ETL任务。
配置了触发间隔后SLS触发器就会按时调用函数并传入日志数据变化的游标。这样就可以直接获取变化的增量数据。
相比于OSS触发器SLS触发器具有更加丰富的接入方案你可以直接采集阿里云产品的日志也可以通过Logstash、FluentD等开源软件采集日志通过HTTP/HTTPS、Promethues等标准协议接入数据。
如果你的处理场景实效性不敏感从性价比来说采用小文件上传到OSS触发也是不错的选择。你可以根据实际的业务需要来进行综合考虑最终选用什么样的方案更合适。
小结
最后我们来小结一下今天这节课我跟你介绍了函数计算FaaS是如何扮演好Serverless“连接器”这个角色的。
它以具备的三大连接能力为基础通过SDK/API、事件驱动等不同的方式形成了“FaaS + 云服务”的Serverless新生态组合。正如你在ETL实战中看到的那样通过对象存储触发器和消息队列的组合5分钟不到的时间轻松的玩转了日志分析处理开发和上线等一系列事情的全流程而平时可能需要好几个小时才能完成。
触发器的连接能力更像是“引流”的利器通过被云服务集成或者集成云服务的方式网罗了云上的各种“门客”例如我们在上面示意图中展示的消息服务、文件存储、API网关、对象存储等。这些“门客”带来的数据等资源在计算之后也可以通过跨VPC和公网访问两大能力输送出去。
这样“输入”+ “计算” + “输出”高效快速的实现,不就是我们想要的“降本增效”吗?这样的粘合剂的功效,让云服务通过物理组合的方式发挥着巨大的化学反应,“连接器”的函数计算再一次通过它“胶水语言”的特性,证明了它自身的价值。
课后作业
好了,这节课到这里也就结束了,最后我给你留了一个课后作业。
这节课我带你通过一个ETL的场景体验了Serverless“连接器”的精髓那么如何实现一个根据日志的关键指标信息发送报警邮件的功能呢
欢迎在留言区写下你的思考和答案,我们一起交流讨论。
感谢你的阅读,也欢迎你把这节课分享给更多的朋友一起阅读。

View File

@ -0,0 +1,249 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
20 实战进阶如何基于智能音箱开发一个BOT技能
你好,我是静远。
上节课我们一起感受了FaaS 作为Serverless “连接器”的强大,但也仅限于云厂商单个云平台上的体验。
今天我将带你体验跨平台的开发通过智能音箱、IOT 开发平台、云函数计算平台三者之间的联动一起完成一个不同年龄段宝宝食谱推荐BOT技能的开发。
你也可以通过这样的方式完成智能客服、智能问答等对话式应用的开发。通过这个实战相信你能进一步感受到Serverless的无限可能。
如何实现
我们本次的目标是通过语音对话的方式来完成宝宝食谱的推荐。这个功能符合Serverless的特性事件触发、轻量应用、按需调用、流量不固定。同时该功能需要有语音转文字的处理能力以及一个物理载体。
因此我们以百度智能云函数计算平台、百度DuerOS开放平台来进行本次的案例开发并选择一个小度X8作为载体。当然你也可以直接用DuerOS平台上的模拟测试工具达到一样的效果。
正所谓磨刀不误砍柴工,在开始实战之前,我们先了解一下今天涉及到的各类概念以及整体的实现思路。
什么是技能BOT
我们通常将在DuerOS平台上开发的对话式服务称为“技能”它还有一个英文名“BOT”。一般来说平台有内置技能和开发者提交发布的两种技能。前者可以直接拿来用后者分为免费和收费两种收费的技能和你在小度音箱上看到的一样是需要支付购买才能使用的。
一个技能通常由4个重要部分组成我们的动手实战中都会涉及到。
意图:指技能要满足的用户请求或目的。比如这次的意图就是给不同年龄段的宝宝推荐食谱,由“常用表达语”“槽位信息”等组成;
词典是某领域词汇的集合也是用户与技能交互过程中的重要信息。比如我们本次实验中需要识别的关键词“年龄”就是一个词典中的词条对应英文识别为number。为了方面开发者DuerOS在词典中内置了不少词条比如这里的number就是sys.number
用户表达是用户表达意图时具体的样例它是组成意图的关键。用户表达样例越多意图识别能力越强。槽位信息就是从用户表达语句中提取出的关键信息并能够将这些关键信息和词典一一匹配。比如“3岁宝宝吃什么”就是一句用户表达提取的槽位标识是number数值是3对应的词典是sys.number
配置服务技能创建成功后需要部署到云服务上我们配置函数计算的入口函数标识BRN就可以了。BRN可以在函数计算平台上的函数基本信息展示里获取到。如“brn:bce:cfc:bj:*******:function:babaycook:$LATEST”。
实现思路
接下来,我们看联动示意图,感知一下“宝宝食谱”技能通过设备和平台实现交互的过程。
如图所示小度音箱在收到语音输入后如“小度小度宝宝能吃什么会通过既定协议请求DuerOS平台通过DuerOS触发器触发函数计算CFC的执行。之后你编写好的处理逻辑就会按照指令输出返回给DuerOS平台并通过语音的方式在小度音箱播放。
这里使用的DuerOS触发器是百度CFC平台特有的一种触发器类型用于和DuerOS联动当然你也可以自定义任何的触发器。
如果涉及到存储你可以选择云上的存储资源来使用如Redis、RDS等等这些也都在之前的课程中讲到过相信你已经驾轻就熟了。
接下来我带你来梳理一下开发的实现思路我把整个过程划分为3大步骤。
步骤一云函数创建与自定义你需要在CFC管理端选择一个合适的模板快速创建一个DuerOS的初始函数完成函数的开发和配置最后设置触发器和配置日志存储就可以了。
步骤二技能创建与绑定在DuerOS平台选择一个技能模板并设置相应的元信息根据你的诉求设置意图和词典、话术场景配置等等。最后绑定CFC函数的时候将函数入口的BRN拷贝到DuerOS平台即可。
步骤三,运行时请求路由:其实这一步,不需要你做什么,直接体验就行。但如果你想发布到智能音箱上,还需要在控制台发布。发布之后,你就可以通过音箱来体验了。
整个流程你也可以对着下面的架构示意图梳理一遍。
动手实战
下面我们就可以根据上面的思路开始动手实现不同年龄段宝宝食谱推荐BOT技能的开发了。
云函数创建和定义
首先我们在函数计算平台CFC上创建一个函数就本案例来说为了便于你熟悉DuerOS的处理方式可以选择一个模板来生成比如我这里选择“dueros-bot-tax”来做案例演示。
然后输入相关的函数元信息这里函数名为“baby_recipe”。
我们先不用关心模板代码是什么先继续创建触发器。这里需要选择DuerOS触发器来对接小度音箱的请求。
好了,到这里,一个小度处理的函数就搞定了。接下来,我们就基于模板编写自己的业务逻辑代码。
这里需要注意一下生成的模板代码中在文件的开头有一段PUBLIC KEY需要复制到DuerOS平台。先不要急我们继续关注代码。
*/
/**
* @file index.js 此文件是函数入口文件,用于接收函数请求调用
* @author dueros
*/
const Bot = require('bot-sdk');
const privateKey = require('./rsaKeys.js').privateKey;
class InquiryBot extends Bot {
constructor(postData) {
super(postData);
this.addLaunchHandler(() => {
this.waitAnswer();
return {
outputSpeech: '欢迎使用小宝宝食谱推荐!'
};
});
this.addSessionEndedHandler(() => {
this.endSession();
return {
outputSpeech: '多谢使用小宝宝食谱推荐!'
};
});
this.addIntentHandler('babyCookBooks', () => {
let ageVar = this.getSlot('number');
if (!ageVar) {
this.nlu.ask('number');
let card = new Bot.Card.TextCard('宝宝几岁了呢');
// 可以返回异步 Promise
return Promise.resolve({
card: card,
outputSpeech: '宝宝几岁了呢'
});
}
if (this.request.isDialogStateCompleted()) {
let cook_1 = '母乳为主,可以混合牛奶,建议辅以虾仁碎沫或者菜沫等';
let cook_3 = '可以吃富含蛋白质的食物如:鸡蛋,瘦肉。也可以吃富含维生素的食物:如芹菜、菠菜。';
var speechOutput = cook_1;
if(ageVar > 1 ) {
speechOutput = cook_3;
}
let card = new Bot.Card.TextCard(speechOutput);
return {
card: card,
outputSpeech: speechOutput
};
}
});
}
}
exports.handler = function (event, context, callback) {
try {
let b = new InquiryBot(event);
// 0: debug 1: online
b.botMonitor.setEnvironmentInfo(privateKey, 0);
b.botMonitor.setMonitorEnabled(true);
b.run().then(function (result) {
callback(null, result);
}).catch(callback);
}
catch (e) {
callback(e);
}
};
代码中我们主要关注的是意图的处理入口this.addIntentHandler(babyCookBooks)其中“babyCookBooks”是在DuerOS技能平台创建技能的时候需要填写的意图名称你也可以按照后续自己定义的技能名称来修改。
接下来的代码部分,为了理解方便,我引入了一个宝宝菜谱推荐的简单判断,你可以在这之上添加更丰富的功能。将复杂的功能抽离出来,然后引入调用,就能实现比较完整的技能了。
技能创建与绑定
接下来就是创建技能了。进入DuerOS技能开放平台点击授权点击创建新技能。
选择“自定义技能”,“从头开始”。
下拉页面,在“技能名称”和“调用名称”部分输入“宝宝食谱推荐”,应用场景选择有屏和无屏场景,点击确定:
接下来,继续创建意图:
这里,我们需要注意两点。
第一意图识别名的填写需要跟你在函数计算CFC中addIntentHandler设置的名称保持一致。
第二表达式和槽位比较关键。不同用户的表达形式不同我们需要确保常用表达覆盖尽可能多的用户口语表达这样意图识别就会更加准确。DuerOS平台可以通过表达式提取出槽位信息你也可以手动核准和纠正。
槽位是意图的参数信息是非常关键的一环我们在代码里面获取到的参数就是槽位标识。更多关于技能的使用方法如果你感兴趣的话也可以在DuerOS开发平台深入了解。
接着我们来关联技能与后端的服务。我们选用百度CFC作为服务部署依次拷贝函数计算代码中的PUBLIC KEY以及函数的唯一标识BRN到“配置服务”页面的“Public Key”和“BRN”框中。
测试与发布
到这里,我们就完成了云函数的创建和定义,技能创建与绑定两个关键的步骤。最后,我们来测试一下程序是否能够正常运行。
我们可以选择模拟测试或者真机测试两种方法。假如你身边暂时没有小度音箱,可以选择模拟测试来验证一下。我们打开“模拟测试”,选择“小度在家”,输入“打开宝宝食谱推荐”这个技能。
你会看到,小度的屏幕上出现了我们刚才代码中的欢迎语“欢迎使用小宝宝食谱推荐”。
接着我们输入“1岁宝宝吃什么”会发现技能服务给我们返回的正是代码中“1岁宝宝的食谱推荐”用语“母乳为主可以混合牛奶建议辅以虾仁碎沫或者菜沫等”。
假如你的身边有小度音箱也可以选择“真机测试”打开“技能调试模式”。这里要注意的是你登录的DuerOS平台的账号一定要和登录小度智能音箱的账号一致。
我们来看一段视频,感受一下通过函数计算实现的技能:
最后,经过精修和加工后的程序,也可以正式发布出来分享给其他用户。
小结
最后我们来小结一下今天这节课我们通过联动智能音箱、DuerOS技能开放平台、云函数计算CFC一起完成了一个宝宝食谱BOT技能的开发体验。
通过这次的实验我们可以发现一个基于Serverless和语音交互的对话式应用开发主要包括如下三个步骤
步骤一构建一个符合业务场景的Serverless应用你可以使用函数打包或者镜像的方式完成开发和上线如本案例中的宝宝食谱函数服务
步骤二,构建一个交互技能的意图,设置好语音交互规则,如本案例中的宝宝食谱意图;
步骤三构建技能和函数的触发绑定。如本案例中创建DuerOS触发器的过程。
基于这三个流程和延伸由于DuerOS的开源开放可以广泛支持手机、电视、音箱、汽车、机器人等多种硬件设备我们还可以用来开发智能客服、居家电器控制、智能助理等多种对话式应用通过DuerOS与Serverless函数计算的结合可以进一步降低健康、金融、酒旅、电信等各行各业使用人工智能对话系统的门槛。
其实各大云厂商在基于Serverless的生态集成上都有非常丰富的经验和成熟的产品如果你的业务已经在云上那么可以挖掘一下哪些场景还可以用起来进一步减少运维和处理带来的工作量如果你的业务暂时还没有上云这种开放集成和Serverless本身所体现出的这种思想希望你也能在现有的工作中灵活运用起来。
最后这节课虽然是实验课但我最想跟你交流的是这次实验背后的两个感悟。一个是发散思维很重要我们可以有意识地将身边的需求和技术联系起来活学活用Serverless另一方面在工作生活压力比较大的现在作为码农的我们也可以运用自己的技术给孩子带来成长的快乐还可能会带来一份收入在这么“卷”的当下是不是也能感受到不一样的幸福呢
思考题
好了,这节课到这里也就结束了,最后我给你留了一个思考题。
少儿编程的后端服务是否可以采用Serverless的技术哪些地方可以用到函数计算哪些可以用弹性应用托管服务更合适
欢迎在留言区写下你的思考和答案,我们一起交流讨论。
感谢你的阅读,也欢迎你把这节课分享给更多的朋友一起阅读。

View File

@ -0,0 +1,295 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
21 实战进阶传统的服务如何迁移到Serverless平台
你好,我是静远。
今天我们来聊一聊系统架构面临新技术到来的时候该如何思考,以及如何引入新技术。
构建一个新的系统架构,因为没有历史包袱,无论是在技术选型还是在开发部署上都比较方便。但是,“老”的应用服务由于已有的运行业务和原有技术架构的包袱,考虑的事情就比较多了。
Serverless也是如此它在开发、测试、部署、运维等方面跟传统服务的形态不一样近些年来Serverless形态的PaaS产品越来越多选择合适的业务、通过合适的途径来迭代我们的服务也成为了一件复杂的工程。
今天这节课我将结合与客户打交道的沉淀跟你一起来聊聊传统服务迁移到Serverless技术架构的经验最后完成一次迁移的实操。希望通过这样的方式让你在做技术决策的时候对“用与不用”“自建还是上云”“用一个或多个功能”等等这些细节都有一个相对全面的了解。
你真的需要迁移吗?
通常来说但凡要做技术改造或者迁移无非出于以下3个因素。
架构:现有的技术架构不足以支撑业务的流量增长,“系统扛不住”了;
人员:人手不够,可能是人的数量不够、人的知识储备不够,“人扛不住”了;
成本:归根到底,还是“钱扛不住”,尤其是初创公司或是大公司里面的创新团队,业务还没起来,预算就那么多,该怎么办?
那么,只要是遇到这三个问题,就要毫不犹豫地迁移吗?在迁移之前,我建议你先回顾一下实战指南中的选型建议,再沉下心来想清楚两个问题。
第一,这是否是一个盲从的选项?
对业务方同学来说,不管是用云厂商还是企业内部的平台,你的技术栈都是跟着平台走的,极度依赖平台的服务配套和稳定性。也许我们能够快速上线,但却失去了对服务器的管控权。
另一方面代码也是交出去的那么代码和数据安全性的保障问题有后备方案了吗云厂商也不是没有发生过数据安全故障的问题这也是如金融银行这类企业都需要私有化部署Serverless平台的核心原因之一。
第二,你的迁移会对技术、业务和团队造成什么影响?
技术方面,是整体改造迁移?还是部分改造迁移?还是先在新架构使用,老业务再逐步过渡?
业务方面,要从可持续发展的角度衡量,改造一个系统架构,要么使得现有业务在资源耗费上的成本更小(也就是维系现状的同时降低成本),要么以更优的方式支撑流量上涨的业务,否则,改造都是徒劳的。
团队方面人才和知识储备是否足够遇到问题了可以Hold住吗
多问几个为什么也许你就可以在改造Serverless的道路上少走不少坑。想清楚了这些问题我们就可以开始讨论具体的方案了。
迁往何处?
我们先来说个迁移的例子。现在有一个中小公司的管理网站系统,希望进一步节省网站费用,网站原来部署在云服务托管应用上,访问流量时有时没有。
想一想面对Serverless形态的容器服务、应用托管服务、函数计算等具象化的产品技术我们该如何抉择呢
是否迁移到容器服务?
Serverless的容器服务跟普通的K8s容器服务的主要区别在于是否按量付费、是否节点可见、是否需要自己来做容量规划。
如果你的服务本来就部署在容器服务上且有弹性诉求那么迁移到Serverless容器服务也不失为一个明智之举。如果团队K8s的运维经验不是很丰富那么就要着重考虑函数计算和弹性应用了。
是否迁移到函数计算服务?
还记得我们在运行时课程中讲到的,函数计算平台支持标准的运行时和自定义运行时吗?下面,我们就针对迁移的初衷,看看如何把运行时“用”起来。
标准运行时
云厂商内置的运行时其实已经能够满足大多数业务场景的代码运行了,尤其是不会涉及到复杂交互流程的一些事件驱动场景的迁移。比如离线的机器学习任务,有时仅需要将原本训练逻辑的启动方法放在函数计算入口,即可完成迁移。
下面的一段伪代码可以帮助你理解得更清晰一些。
def handler(event,context):
result = training()
return result
def training:
...
自定义运行时和自定义镜像
自定义运行时和自定义镜像是函数计算内置运行时的一种延伸能力,能够为用户提供更加灵活的服务接入模式。
自定义运行时允许用户不必按照严格的事件驱动模型进行编程。也就是说用户不需要使用handler(eventcontext)作为函数计算入口定义可以打造更加个性化的编程环境比如一些常见的Web 框架,都可以直接在自定义运行时中启动。
相较于自定义运行时,自定义镜像更加自由。它支持将整个环境打包成一个容器镜像的方式部署。
总的来说这二者都更加适合于一些迁移到常规运行时比较麻烦的场景。比如一些Web 框架有固定的HTTP处理接口无法再按照标准的事件传参处理。这个时候使用自定义运行时或者自定义镜像就可以使用一些定制化的服务启动方式来部署了。
除此之外,函数计算的衍生能力也能帮助你处理一些更加复杂的场景,让函数计算应对的场景更加丰富。比如通过函数的连接器能力和异步策略来完成一些音视频转码任务的迁移,通过函数工作流迁移一些状态转换的任务场景等等。一些函数计算服务考虑到业务之间功能的拆分,还提供了应用中心的管理概念。
是否迁移到应用托管服务?
如果你了解微服务在云PaaS上发展的历程应该不难理解面向应用的Serverless PaaS平台就是在原来的微服务PaaS平台基础之上的演进版本在原有的支持多语言、多框架、微服务治理能力的基础上提供了全托管、免运维、按需使用、按量计费的特性。
如果你的服务本来就是一个基于类似Springboot、Dubbo、HSF的微服务框架那么使用云厂商的这种Serverless的产品也是不错的选择。
最后我来回答一下管理网站系统案例的问题这位客户对性能要求不高主要的诉求还是在实例常驻的成本上而目前Serverless托管服务最小实例为1不能缩容到0。所以FaaS对于这样的客户来说比较合适。你自己在做服务改造和迁移的时候也要把“成本”作为一个不可忽视的因素好好规划一下。
如何迁移?
说完了迁移的考虑和产品技术选型下面我们就以Web服务为例在阿里云函数计算FC上实际感受一下传统服务迁移的过程。我们分别来看简单HTTP的服务迁移和高级Web框架迁移。
HTTP服务迁移
对于一般的简单HTTP服务通过函数计算提供的HTTP触发器基于标准的运行时就可以运行起来了。
HTTP请求是如何到达业务代码的
Web请求的入参通常都是一个标准的HTTP Request 信息包括HTTP请求头、传参等而我们一直所用的云函数在事件触发场景下都是处理一个JSON 类型的事件字符串。因此Web服务并不能直接套用事件驱动的这套编程模型。
我们在创建函数时就会发现FC提供了两种请求处理程序的类型处理事件请求和处理HTTP请求。
其中“处理事件请求”就是我们平时使用最多的事件驱动类型“处理HTTP请求”则需要在入口方法提供标准的HTTP处理接口。不知道你还记不记得我们在分析GoLang Runtime运行时的时候提到过一个funcType的分支判断这里就用得上了。GoLang的运行时会根据函数的类型来选择具体的执行方法也就是下面这部分运行时代码
// 运行时中分类处理的逻辑代码
if fn.funcType == eventFunction {
return fn.invokeEventFunc(invokeContext, req.Payload, response)
}
return fn.invokeHttpFunc(invokeContext, req.HttpParams, req.Payload, response)
在处理HTTP请求类型的函数中需要将函数入口按照定义好的接口实现这个接口包括了请求信息、响应信息以及上下文信息比如FC官方SDK中给的HTTP函数Demo
func HandleHttpRequest(ctx context.Context, w http.ResponseWriter, req *http.Request) error {
w.WriteHeader(http.StatusOK)
w.Header().Add("Content-Type", "text/plain")
w.Write([]byte("hello, world!\n"))
return nil
}
在invokeHttpFunc中最终会通过透传的方式将HTTP请求内容直接交到你刚才定义的这个方法中即HandleHttpRequest对应下面代码中的httpHandler
// 运行时中执行http handler的逻辑
func (fn *Function) invokeHttpFunc(invokeContext context.Context, httpParams *string,
reqPayload []byte, response *messages.InvokeResponse) error {
...
err = fn.httpHandler.Invoke(invokeContext, resp, req)
...
}
这样我们就完成了一次完整的HTTP请求传递过程。
动手体验
那么接下来我们就要开始简单HTTP服务的迁移了。我们以Golang Runtime运行时为基础来创建函数使用官方的示例代码作为参考。选定请求处理程序类型为“处理HTTP请求”。平台会自动为你创建一个HTTP触发器我们可以点击触发器管理查看
需要注意一下,如果你需要公网访问,还需要点击编辑配置域名。
最后我们更新一下代码。我这里是将请求中的geekbang query 参数打印到了响应信息中在具体的迁移时你也可以在获取到请求信息后根据实际需求处理业务如下面代码中注释的函数doSomething()一样:
package main
import (
"context"
"net/http"
"github.com/aliyun/fc-runtime-go-sdk/fc"
)
func main() {
fc.StartHttp(HandleHttpRequest)
}
// func doSomething(){}
func HandleHttpRequest(ctx context.Context, w http.ResponseWriter, req *http.Request) error {
w.WriteHeader(http.StatusOK)
w.Header().Add("Content-Type", "text/plain")
w.Write([]byte(req.URL.Query().Get("geekbang")))
//doSomething()
return nil
}
然后编译、打包上传。最后的效果如下:
高级Web框架迁移
前面我们提到,如果你的老应用服务是基于高级的服务框架进行的,那么可以通过自定义运行时、自定义镜像、弹性应用这些更“自由”的服务模式来迁移。
以阿里函数计算FC为例官方就推荐采用自定义运行时的方式来进行高级的框架迁移
我们以Golang中常用的Gin框架为例选择自定义运行时创建运行环境选择Go同时选择示例代码。
完成创建后我们开始用Gin编写一段代码。你可以参考我的示例在Gin中通过自定义的方法来对请求进行处理我这里定义了一个geekHandler处理访问/geekbang路径的请求
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.LoadHTMLGlob("templates/*")
router.GET("/geekbang", geekHandler)
router.Run(":9000")
}
func geekHandler(c *gin.Context) {
c.String(http.StatusOK, "do something")
}
你会发现这和你原本服务的代码形式几乎没有什么区别在实际迁移时如果你本身的服务采用的是Gin框架甚至只需要在控制台配置一下启动命令就可以几乎原封不动地将二进制部署上去是不是很方便呢
本地编写完成后,就需要编译然后将二进制打包上传了。这里要注意的是,在前面创建函数的部分,我们采用的是默认的启动命令./main那么你的二进制产出也需要和它对应。当然你也可以指定一个产出名如果你是用非Linux 环境编写的话,还需要在本地采用交叉编译的方式进行编译:
GOOS=linux GOARCH=amd64 go build -o main main.go
如上面的代码所示GOOS和GOARCH会指定Linux操作系统与amd64架构而-o 可以用来指定产出,最后的参数是待编译的文件。
最后部署完成以后,我们来看一下实际的运行效果:
回想一下管理网站系统案例的场景如果还涉及到自定义的域名还可以通过FC的控制台通过“域名管理”的能力进一步添加自定义域名。又或者你还可以将该自定义域名作为源站域名为其添加加速域名然后为加速域名配置CNAME也就是域名设置CDN加速功能。
效果评估
管理网站系统其实是一个真实的案例。通过从原来常驻的资源应用迁移到Serverless函数计算客户的费用降低了10倍。相信现在的你已经能够想清楚原因了他的服务属于中长尾应用按需付费极大地降低了客户的成本。
那么,我们是否有一个通用的准则来评估迁移的效果呢?回到开头,但凡要做技术改造或者迁移,无非出于架构、人员、成本三个主要因素。除去这三个方面,迁移后的架构,还可以通过测试系统的弹性和抗洪峰等能力来评估其是否真正得到了增强。
我们的做法通常是在上生产环境、对外放开流量之前,通过全链路压测的方式尽可能仿真线上的流量升降情况,来看资源池的调度、请求并发和响应等是否符合预期。这里需要注意一下,有的云厂商默认的函数和用户维度的并发度有限,如果流量超过默认限制,还需要提前发工单提升配额。
具体来说基于Serverless平台的特性你的压测可以从三个点着手。
全链路:针对链式调用,你不能只关注某一个函数的压测结果,想一想,整条链路里有没有瓶颈存在?
模拟真实:尽可能贴近业务,测试平台的资源调度是否跟得上你的诉求。不同的厂商优化的参数可能不同,你选择的云厂商平台是否可以胜任呢?
高覆盖:尽可能多地覆盖流程分支,在上线之前发现潜在的问题。
除此之外,我们还可以关注一下团队的人员是否更高效,最终的体现,一定是原本的预算足够应对流量的上涨了。
小结
好了以上就是传统服务迁移的全部内容。相信你现在对迁移涉及到的四件事情也非常清楚了想清楚要不要迁移迁到哪类Serverless的平台改造方法和成本以及迁移效果评估。那我们该如何将这四个要点落实到日常的业务处理中呢
在是否迁移方面我们今天提到的案例是出于财务压力考虑到客户的业务比较轻量几乎没有改造成本就非常适合迁移到函数平台。而你可以从是否要将控制权交给云厂商、业务是否符合Serverless特性、架构改造成本、团队技术储备等多个方面考量。
在迁移到哪类平台方面就需要你深入了解云厂商Serverless形态的产品了。今天比较主流的Serverless容器、函数、应用托管服务明天可能又会新增更多的Serverless产品出来而我们能做的就是找到业务、架构与产品形态三者的最佳匹配状态。比如如果你使用了Springboot、Dubbo框架需要服务治理的能力那么Serverless的应用托管服务如SAE这类产品就是你应该考虑的首选。
在改造成本和方法上云厂商都提供了不错的迁移适配能力你可以通过开发工具实现Java、Node.js、Python等各种语言框架的迁移。这里要注意的是虽然你可以通过改造代码以及HTTP触发器来实现低成本的迁移但是我们大部分情况下都是基于某种框架来开发的Web服务所以我更建议你使用自定义运行时的方式迁移。如果你想完全控制容器运行的环境那么镜像的迁移方式也是不错的。
最后别忘了你迁移的初衷记得做效果的评估。你可以通过架构、人力、成本这迁移三要素来评估从Serverles的角度一切都可以从“降本”“增效”这两个关键词出发考量迁移是否达到了你的预期效果。
思考题
好了,这节课到这里也就结束了,最后我给你留了一个思考题。
你有没有准备要迁移的服务呢?你做了哪些技术调研?有遇到什么卡点么?
欢迎在留言区写下你的思考和答案,我们一起交流讨论。
感谢你的阅读,也欢迎你把这节课分享给更多的朋友一起交流学习。

View File

@ -0,0 +1,187 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
22 私有云赛马时代的Serverless核心引擎谁能胜出
你好,我是静远。
Serverless平台的实现分为两个流派分别为公有云和私有化。前面的几节实战课我们主要基于公有云的Serverless平台来展开从这节课开始我将带你一起了解私有化部署领域的知识。
为什么要做私有化部署呢?相信你回答这个问题的时候一定是有些犹豫的。
我们知道Serverless由云厂商运维并提供服务带来高效率开发的同时也大大降低了成本。但就如我上一节课给你讲到的那样我们享受这种“好处”的同时也失去了对平台的掌控力面临着数据的安全问题。
我的很多私有化客户也是基于这两个核心因素找到我们来共建企业内的Serverless平台。这对于企业的业务团队来说可以同样的享受Serverless带来的价值只是这一层的维护工作由原来的云厂商交到了企业内部的基础平台运维团队。
那么私有化的部署通常是怎么实现的部署的方式都有哪些呢希望这节课能够让你对私有化部署的方法开源引擎的发展现状以及选取一个开源引擎作为Serverless平台底座的方法有一个通盘的了解。
私有化部署的方法有哪些?
当前绝大多数的Serverless平台底座都是基于容器技术来实现的而容器技术在近几年的日趋成熟和盛行也使得云厂商自研的Serverless平台、开源的Serverless引擎在私有化企业的数据中心部署变得相对容易了。
不同的企业,在私有化这里一般有两种做法。
自研一些基础设施团队能力很强的企业一般会选择自己研发。有的企业会基于开源的Serverless引擎做二次开发有的直接基于已有的IAAS和PAAS资源自己研发并配备专业的团队进行升级和维护。
购买还有一些企业会选择ToB的云厂商合作共建基于云厂商售卖的Serverless标品来适配和升级。比如金融银行领域你可以通过一些公布的文稿发现类似中国工商银行、光大银行等公布的招标网站上采用了百度智能云的Serverless FaaS产品来进行私有化部署升级。
目前来看选择“购买”的企业是占大多数的。传统企业和ToB云厂商合作共建传统企业有上云Serverless化的诉求ToB云厂商有丰富的实战经验和技术两者形成合力。
但这并不代表购买方可以一劳永逸不需要掌握Serverless的技术。
一方面你需要提前了解云厂商是基于什么样的Serverless引擎开发的这个是核心。因为云厂商除了自研平台外基本上都是基于开源的Serverless引擎Self-hosted Frameworks来进一步打造和适配的。另一方面你还需要了解整套解决方案是如何围绕这个核心构建起来的。这节课我们先说Serverless引擎的部分。
你可以从CNCF Serverless LandScape了解到引擎和平台的发展情况。左侧的“Hosted Platform托管平台”包括我们提及的AWS、阿里云FC、百度智能云CFC、华为FunctionStage、Azure Functions、腾讯云SCF等企业的Serverless托管平台。而右侧“Installable Platform可安装平台”包括我们平时关注度比较高的Knative、OpenWisk、Fission、OpenFaaS等开源框架。
那么它们具体有什么特点,我们又该如何选择使用呢?
开源引擎现状与选择
我们先来看2020年CNCF的报告感受一下开源Serverless的使用情况
我们可以发现35%的调查对象有使用“installable software”在使用“installable software”中有27%使用Knative、10%使用OpenFaaS、5%使用Kubeless。随着时间的推移现在已经有一些开源平台没能经受得住时间的考验比如Fn、Iron Functions已经逐步退出历史舞台了图中的Kubeless在今年年初也宣布停止维护了。
这里我参考CNCF Serverless LandScape和CNCF调研报告并基于受欢迎程度、稳定贡献、工具和易用性等方面选取了Knative、OpenFaaS、OpenWhisk、Fission这四款开源的平台来跟你介绍。需要说明的是Nuclio也是不错的开源框架但它是专注于数据、I/O和计算密集型的业务处理场景的且自身不考虑缩容至零的问题。所以暂时不去探讨。
OpenWhisk也算元老级的开源Serverless框架了目前代码库也有一定的commit提交但发展速度并不如其他几款在调研图中显示仅有3%的Serverless 用户选择了该款产品远低于Knative和OpenFaaS而且自2020年1月以来到现在它还没有发布过一个新版本。
Knative虽然是2018年发布的开源Serverless产品但发展迅速依托谷歌和Kubernetes的紧密结合短短几年间Github 上Star的总量已经接近1万图中显示已经有27%的Serverless 用户选择了它。目前Knative也成为了CNCF的孵化项目。
OpenFaaS可以说是发展最稳定的一款了到目前仍然有稳定的commit提交并且Star数是所有开源Serverless平台中最高的总量接近3万。调研图显示有10%的用户支持。
Fission在调研报告中虽然仅有2%的用户支持,但它目前仍有稳定的贡献者和版本发布,依然很活跃。
这四个Serverless开源平台我从实现语言、支持的编程语言、贡献人数等12个方面做了概览对比方便你进一步查阅。
开源引擎的不同
从CNCF报告、各个开源Serverless平台的官方总结这些对比项之后你可能很快就能抉择一个开源平台作为自己研究和构建的对象了。但这些数据终究是纸上谈兵我们还可以再深入一点我会从Serverless的核心特征“扩缩容机制”和相关使用限制上给你几点参考建议。
Fission的扩缩容机制及限制
Fission有Pool-based和New Deploy两种执行模式能够支持资源池以及从0到1两种扩容模式官方更是宣称冷启动速度控制在了百毫秒以内这对于一些延迟敏感型场景显然是不错的选择。而它的限制就在于只支持通过CPU指标来控制实例数量这就导致很多Web场景下并不能很好地感知实际的资源用量。
OpenWhisk的扩缩容机制及限制
不同于FissionOpenWhisk的扩缩容更加简单。每次请求到来后会由invoker创建一个业务容器然后进行代码注入执行执行完毕后退出并销毁容器。整个流程并不会产生冗余的资源但如果应对一些高并发场景效果可能并不会那么理想。
正如上面的表格所示它的局限性在于依赖的组件较多不管你是开发者还是使用者都需要有这些工具的储备知识如果你希望做二次开发那要先想一想是不是熟悉Scala否则的话可以考虑其他三个以Go语言为主的开源框架。
Knative的扩缩容机制以及限制
我们之前在扩缩容这节课介绍过Knative KPA。Knative本身的KPA扩缩容逻辑比较复杂除了AutoScaler和Activator这两个Serving组件外还需要向用户Pod注入一个queue-proxy容器以此来进行实时的请求量统计。基于这些组件除了能够实现从0到1、1到N、N到0的扩缩容还能支持在Stable稳定模式和Panic恐慌模式两种模式下的扩缩容算法。
那么它有什么不足呢Knative完全依赖Kubernetes由于发版很快高一点的版本就需要1.22以上的Kubernetes版本但有的企业升级没那么快需要配套支持。除此之外可扩展的网络组件也是一把双刃剑带来便利的同时也需要运维人员提前具备这一方面的知识比如Istio。可以发现它对于企业、技术人员的要求都不低。
OpenFaaS的扩缩容机制以及限制
OpenFaaS Pro完全将扩缩容的发起交给了Prometheus。OpenFaaS通过Prometheus获取到监控的指标再由AlertManager根据配置参数进行扩缩容请求最终让资源达到预期的值。目前OpenFaaS支持rps、capacity和cpu三种扩缩容模式并且可以通过com.openfaas.scale.zero来控制是否可以缩容到0。
然而OpenFaaS依赖Promethues和配置参数控制扩缩容到0的这两个特性也可以算是它的限制。这两个特性导致它完全依赖Promethues的稳定性和负载也默认关闭了缩容至0的功能当然你也可以打开不过会带来一定的冷启动时间。
总得来说目前Knative、OpenWhisk以及OpenFaaS都能够基于请求数量进行动态伸缩而Fission目前只支持通过CPU指标来控制实例数量。相比于Fission和OpenWhiskOpenFaaS与Knative的扩缩容模式就丰富了许多。
如果你的场景比较简单可以快速体验一下OpenWisk如果对性能要求比较高Fission和Knaitve都是不错的选择。它们之间的界限并不是不可逾越的我们在选择的时候可以更灵活机动一些。
你的选择
有了上面这些分析,相信你选择的大方向已经有了,下面我再给你几点技术以外的经验参考:
考虑你公司或者团队的技术栈,如语言、现有的服务治理体系、中间件等;
考虑你的业务性质是专门做数据计算、还是Web服务还是都要兼顾等等
考虑未来趋势结合目前开源框架的成熟度和产品力来衡量看产品的潜力。比如Knative背靠Google和CNCF和Kubernetes紧密结合随着时间的推移未来可期。
购买Serverless产品需要注意什么
最后,我们还可以说说在“购买”这个方式中,作为“甲方”或者“乙方”的你,应该分别具备哪些知识储备。
如果你是甲方一定要有基础技术方面的储备比如一定的云原生技术基础储备包括Docker、Kubernetes、Service Mesh、可观测等具备云原生的思维在Serverless方面了解竞标厂商的产品功能、成功案例和落地规模知道在POC的环节需要验收哪些要点最后也要具备架构思维明确哪些功能点在一期进行哪些在二期进行在合理的架构范围内有节奏地迭代。
具备上面的必要储备后,和产品方交流沟通起来就不会磕磕碰碰了。当然,积极上进的你如果能掌握招标、合同、损益评估和入场等注意事项,就能真正称得上这方面的“老手”了。
如果你是乙方还记得我在思维构建这节课跟你提到的“升级打怪”的路径么你必须达到“王者”阶段达到产品和组件输出的能力并且能够在POC环节胜出。我列了几个要点方便你对照着来看。
技术知识如我之前所说“加入一个云原生Serverless团队在里面打磨一万小时以上扎扎实实搞清楚每个点”只有这样你才有可能解决掉在产品交付过程中遇到的问题。假如你有部署私有化产品的经历你一定理解这句话的含义甲方的环境有时确实不会那么“如你所愿”。
产品思维对于基础服务类产品闭门造车肯定是不行的。你必须要基于一个“标品”来打造一个Serverless产品方案然后基于和甲方的交流找到可适配的架构产品方案。比如在IAM的适配上完全可以独立一个模块单独构建认证鉴权的服务通过配置化的方式来适配不同的企业。通过“独立标品”+“插拔适配”的方式构建产品,才更适合私有化部署。
软技能:甲方通常在尝试新技术落地的时候,自己也没有太多的经验,你需要有足够的耐心来帮助和引导他们交付和落地场景,这样才有后续的“二期”“三期”“周边产品”等再次合作。
其实不管是自研还是二次开发不少企业也借鉴了这些Serverless开源引擎的思想所以研究一款开源产品就相当于研究一个云厂商的服务不同的是云厂商还融入了一些自己专业的技术特性和底座服务。
希望这些我和私有化打交道的心得,能够帮助作为甲方或者乙方的你,也欢迎你在留言区进一步与我探讨。
小结
最后我来小结下今天的内容。私有化部署的选择主要是出于数据安全和平台掌控力两方面的考量。考虑到私有化部署的Serverless平台本身的门槛一般会有“自研”和“购买”两种部署方式。一些基础设施团队能力很强的企业会选择前者一些企业会选择较为方便的后者。
目前来看选择“购买”的企业占比较大。那么假如你是甲方就需要储备适当的云原生知识掌握Serverless的核心要点和架构思维这会让你在沟通和选择合作厂商的时候更加游刃有余。假如你是乙方就要在技术、产品和软技能上面都要过关才有可能获得甲方的认可拿到这一笔单子。
而我们了解私有化部署最关键的途径就是去学习开源的Serverless平台并基于此来构建和实操。因此我们综合了CNCF的调研、热度、贡献度稳定性等多个指标选取了四个开源框架OpenWhisk、OpenFaaS、Fission、Knative来做深入对比。Knative作为后起之秀依托谷歌和CNCF和Kubernetes的紧密结合有“跨平台的函数计算平台”的响亮称号可以说是未来可期。
一直以来,在了解一款产品特性的方面,我的建议都是多多动手实操,再回头看看官网最新的特性。如果再结合课程中的一些思路,找到一条自己学习新框架和新技能的道路,就说明你已经迈入一个新的台阶了。
下节课我们就进入到动手实操的环节以Knative为例看看如何从0到1部署Serverless核心引擎。
思考题
好了,这节课到这里也就结束了,最后我给你留了一个思考题。
针对上述提到的Serverless引擎框架你在研究过程中最关心哪个点呢你有什么新的发现吗
感谢你的阅读,也欢迎你把这节课分享给更多的朋友一起阅读。
补充阅读
链接1OpenWhisk
链接2knative
链接3Fission
链接4OpenFaaS
链接55.8k stars
链接64.6k stars
链接77.2k stars
链接822.1k stars
链接9Serverless Framework
链接10JavaScript、Python、PHP、.NET、Go、Java、Ruby、Swift、二进制
链接11NodeJs、Python3、Ruby、PHP7、Java、Go、.NET、Perl、二进制
链接12支持Go、NodeJs、PHP、Python、Ruby、Java、C#等语言的模板并打包成Docker镜像
链接13https://openwhisk.apache.org/
链接14https://knative.dev/docs/
链接15https://fission.io/
链接16https://www.openfaas.com/

View File

@ -0,0 +1,366 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
23 实战进阶如何从0到1进阶一个开源引擎
你好,我是静远。
从这节课开始我们就要完整体验一个Serverless平台的构建学习其中的注意事项了。
今天的前半节课我们会从环境准备、组件安装以及组件验证几个角度先重点演练一遍Knative核心组件的部署和验证后半节课我们再去探讨一下在面临一个开源Serverless框架的时候应该怎么去学习。
在实战开始之前你可以先复习一下扩缩容和流量转发这两节课中Knative的知识点再来开始动手实操。
话不多说,我们现在就开始实操准备吧。
环境准备
由于Knative本身完全依赖于Kubernetes因此我们需要提前准备一个Kubernetes集群对于集群的规格官网也有建议你可以参考最新版本来进行。
原型环境方面如果只是搭建一个试用的原型Knative函数环境那么至少需要一个单节点K8s集群并且这个节点的规格至少应该是2核4GB。
如果想达到生产级别的Knative函数环境单节点集群的规格至少应该是6核6G以及30G的磁盘空间多节点环境的规格应该保证每个节点都是2核4G以及20GB的磁盘空间。另外还有几个要点需要注意
必须保证K8s集群版本应该至少是1.22以上;
必须安装K8s的命令行访问工具kubectl
必须保证K8s能够正常访问到镜像仓库。
这节课我会按照生产级别环境搭建其中K8s集群节点数量为2每个节点的规格为4核8G同时配备50GB磁盘空间。我选择了百度智能云CCE来构建K8s集群。
组件部署与验证
准备好K8s集群环境以后我们就可以开始在集群上部署Knative Serving组件了。不要着急我们一步步来。
部署Serving组件
首先我们来部署Serving组件按照官网命令即可
kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.4.0/serving-crds.yaml
kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.4.0/serving-core.yaml
部署之后在knative-serving这个namespace下所有的Pod都应该是Running状态
$ kubectl get pod -n knative-serving
NAME READY STATUS RESTARTS AGE
activator-847d5df4bc-psd7k 1/1 Running 0 47h
autoscaler-65898c7fcf-wkkmc 1/1 Running 0 46h
controller-775b774844-fwm2k 1/1 Running 0 46h
domain-mapping-555ff9b9c8-8dtcw 1/1 Running 0 46h
domainmapping-webhook-57456fcb8c-x6frp 1/1 Running 0 46h
net-istio-controller-59b6b7765-z8w2g 1/1 Running 0 46h
net-istio-webhook-6b98fd5bd4-4pcdf 1/1 Running 0 46h
webhook-7f987868-5f8bg 1/1 Running 0 46h
如果出现异常那么可以通过kubectl describe 查看Pod部署卡在哪一步了你可以参考如下的命令查看
kubectl describe pod controller-744577dddc-7jf5r -nknative-serving
注意如果是网络问题可能会出现下载镜像卡住的情况这个时候通过Google Cloud虚机下载镜像上传到可访问的镜像仓库上就可以或者直接找到可供访问的镜像仓库手工替换掉gcr.io的镜像包。
部署网络组件
完成Serving 组件的部署我们就需要部署Knative所依赖的网络组件了。官网推荐了三种我们以Isito来部署。
kubectl apply -l knative.dev/crd-install=true -f https://github.com/knative/net-istio/releases/download/knative-v1.4.0/istio.yaml
kubectl apply -f https://github.com/knative/net-istio/releases/download/knative-v1.4.0/istio.yaml
kubectl apply -f https://github.com/knative/net-istio/releases/download/knative-v1.4.0/net-istio.yaml
完成之后我们需要检查一下istio-system的namespace下pod是否正常
$ k get pod -nistio-system
NAME READY STATUS RESTARTS AGE
istio-ingressgateway-666588bf64-jpwgl 1/1 Running 0 46h
istiod-56967d8fcc-d6vmr 1/1 Running 0 46h
istiod-56967d8fcc-f46kr 1/1 Running 0 2d2h
istiod-56967d8fcc-mmfkd 1/1 Running 0 46h
同时还需要检查一下是否能够获得gateway的external-ip用于后续的函数访问
$ kubectl --namespace istio-system get service istio-ingressgateway
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
istio-ingressgateway LoadBalancer 10.0.142.236 182.61.144.199,192.168.0.2 15021:31462/TCP,80:30977/TCP,443:32684/TCP 2d2h
这里需要说明一点百度智能云CCE里如果用户创建类型是LoadBalancer的Service默认情况下CCE会联动的创建BLB并为此BLB绑定EIP。所以你可以看到表格中显示的EIP地址是182.61.144.199。
部署Eventing组件
接着我们再来安装一下Eventing的相关组件。Eventing的安装和Serving类似包括两个yaml文件
kubectl apply -f https://github.com/knative/eventing/releases/download/knative-v1.4.1/eventing-crds.yaml
kubectl apply -f https://github.com/knative/eventing/releases/download/knative-v1.4.1/eventing-core.yaml
执行完成后我们来检查一下knative-eventing这个命名空间下的pod是否正常运行
$ kubectl get pod -n knative-eventing
NAME READY STATUS RESTARTS AGE
eventing-controller-c7bdccd6d-ktnmc 1/1 Running 0 13m
eventing-webhook-7c49c58fcb-ng576 1/1 Running 0 13m
如果部署成功的话应该可以看到这个命名空间下有两个running状态的pod如果出现Error那么还是按照我们在小试牛刀这节课中讨论的方法通过kubectl的describe命令查看错误原因对症下药即可。
部署完Serving和Eventing你会发现是不是还缺少Tekton组件我把它作为课后作业留给你来进一步演练你可以按照同样的方式进行部署。需要注意的是Tekton是从Knative的Building组件孵化出来的目前功能已经相当丰富涉及的组件较多因此最好根据官网的安装手册一步一步进行部署避免遗漏。
到这里你的Knative组件已经可以正常运行了接下来我们尝试执行一个简单的Demo验证一下。
验证函数调用
我们先验证一下Knative最基本的函数调用功能确保Serving组件能够正常运行。这里我使用的是官网给的helloworld-go的例子进行演示我将handler中的响应内容改成了如下的打印语句“Hello Serverless!”:
func handler(w http.ResponseWriter, r *http.Request) {
log.Print("helloworld: received a request")
fmt.Fprintf(w, "Hello Serverless!\n")
}
改完代码之后我们接着进行镜像打包这里需要注意下如果你电脑是mac 的m1需要加上 platform linux/amd64 这个参数,如下所示:
$ docker --platform linux/amd64 build -t jingyuan/hello:v1 .
然后就可以将镜像推送到一个集群可以访问到的仓库中这里我使用的是CCR具体使用可以参照他的说明文档接着我们替换一下knative demo中service.yaml的image然后执行kubectl apply就可以了。
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: helloworld-go
namespace: default
spec:
template:
spec:
containers:
- image: jingyuan/hello:v1
env:
- name: TARGET
value: "Go Sample v1"
我这里只使用了最简单的一些字段你还可以在Knative Service中设置不同的revision以及流量比例。-
最后会是什么样的效果呢?我们来一起看下:
curl -H "Host: helloworld-go.default.example.com" http://182.61.144.199
返回:
Hello Serverless!
这一步如果你观察仔细的话会发现在default 命名空间下在调用之前是不存在helloworld-go函数的相关pod的而在调用之后产生了这其实就是第一次调用产生的冷启动现象了。
验证事件触发
最后我们再来验证一下Knative的事件触发功能确保Eventing的组件确实能够正常工作。这里我们采用官网给出的PingSource模型进行验证我们首先创建一个用于接收事件的服务名为 event-display。这里采用的是官网的Demo它的deployment和service定义如下
apiVersion: apps/v1
kind: Deployment
metadata:
name: event-display
namespace: default
spec:
replicas: 1
selector:
matchLabels: &labels
app: event-display
template:
metadata:
labels: *labels
spec:
containers:
- name: event-display
image: gcr.io/knative-releases/knative.dev/eventing/cmd/event_display
---
kind: Service
apiVersion: v1
metadata:
name: event-display
namespace: default
spec:
selector:
app: event-display
ports:
- protocol: TCP
port: 80
targetPort: 8080
接着创建一个 PingSource 对应:
apiVersion: sources.knative.dev/v1
kind: PingSource
metadata:
name: test-ping
namespace: default
spec:
schedule: "*/1 * * * *"
contentType: "application/json"
data: '{"message": "Hello geek!"}'
sink:
ref:
apiVersion: v1
kind: Service
name: event-display
它的含义是根据Schedule定义的规则每分钟发送一次data指定的数据发送的目的端是Sink.name指定的服务 event-display 即我们前一步定义的Service。
接着我们可以查看一下knative-eventing命名空间下的deployment会发现多了一个pingsource的deployment这就是事件的生产者会根据 PingSource 对象的定义,产生事件并发送给对应的服务。
$ kubectl get deploy -nknative-eventing
NAME READY UP-TO-DATE AVAILABLE AGE
eventing-controller 1/1 1 1 3h36m
eventing-webhook 1/1 1 1 3h36m
pingsource-mt-adapter 1/1 1 1 3h36m
然后我们查看刚才部署在default 命名空间下的pod ,并查看他的日志,可以看到每分钟打印的消息:
$ kubectl logs event-display-685f8f46f4-k5dbw
2022/10/07 01:06:32 Failed to read tracing config, using the no-op default: empty json tracing config
☁️ cloudevents.Event
Context Attributes,
specversion: 1.0
type: dev.knative.sources.ping
source: /apis/v1/namespaces/default/pingsources/test-ping
id: caa8d0f9-f3bb-4c4d-a777-749241a9cb32
time: 2022-10-07T06:52:00.301997865Z
datacontenttype: application/json
Data,
{
"message": "Hello geek!"
}
☁️ cloudevents.Event
Context Attributes,
specversion: 1.0
type: dev.knative.sources.ping
source: /apis/v1/namespaces/default/pingsources/test-ping
id: a029df47-fd4b-4eb4-986d-75d6dd9814d0
time: 2022-10-07T06:53:00.001908595Z
datacontenttype: application/json
Data,
{
"message": "Hello geek!"
}
☁️ cloudevents.Event
Context Attributes,
specversion: 1.0
type: dev.knative.sources.ping
source: /apis/v1/namespaces/default/pingsources/test-ping
id: b4331a71-031f-4b3a-93cb-8764a0c5830e
time: 2022-10-07T06:54:00.309563083Z
datacontenttype: application/json
Data,
{
"message": "Hello geek!"
}
到这里我们就完成了Knative最基本的函数调用以及事件触发功能的验证我们来总结一下。Knative的安装大致分为环境准备、组件部署以及功能验证三个部分这也是安装任意一个Serverless框架的基本思路。
环境准备你需要在安装之前就在官网的安装手册上了解清楚相关内容尤其要注意对资源、操作系统、依赖版本的限制一旦没有注意到可能往往问题到安装的最后一步才会暴露出来费时费力。比如我们这个版本的Knative就明确要求K8s的版本要达到1.22以上并且多节点集群下单节点的规格也至少是2核4G。
组件部署:这里我建议直接参照官方的安装手册进行,因为网上的很多技术博客写的并不全面,并且版本可能和你计划安装的也有差别。同时也要注意一些镜像的访问问题,受限于网络问题,需要你自己去寻找国内的镜像源进行替换才能成功。
功能验证完成组件安装后最重要的还是进行功能验证这一步我建议不要一上来就选用复杂的case测试而是先用最简单的demo去验证看看基本的功能有没有问题保证基本的工作流程以后再验证扩展功能这样不仅可以让你在最短的时间内验证完组件的部署正确性还能够降低问题的排查难度。
如何学习一个开源serverless引擎
结合之前的理论学习和本节课的实操相信你对Knative已经有了一个比较全面的认识了但我相信在你之后搭建Serverless引擎的过程中还会遇到Knative更多的特性。课程中我们不可能面面俱到不过像是Knative中 Triggers、Sinks、CLI等知识点你完全可以通过我们总结的方法举一反三的学习起来。
什么意思呢?
拿到一个开源框架,我的攻坚三步曲是“上手体验”“研究架构”“深入源码”,我们逐一探讨下。
上手体验
在接触到任何一个开源Serverless引擎时我并不建议一开始就通过网上的资料去学习各种概念、架构原理以及源码分析这对于刚接触到引擎框架的你来说几乎没有任何帮助这些繁杂的概念反而会让你一头雾水。
相反,上手体验永远应该放在你学习一个框架的第一步。使用带来的直观感受能够帮助你更快地理解这个框架最基本的产品形态以及工作过程。
这节课我并没有上来就给你长篇介绍Knative的各种概念而是直接手把手带你体验了从搭建Knative到跑起来两个demo的整个过程动手之后你基本就清楚整个Knative运作需要哪些组件同时也能观察到一些现象比如冷启动、事件触发过程了。这样的操作过程会让你后续的学习更加高效。
另外有一个小技巧。很多成熟的框架在官网除了常规的搭建步骤还提供了非生产线部署的Quick Start 部署方式比如我们这节课要学习的knative quick start可以帮你更快地体验。
研究架构
在体验完简单的功能之后,我们再回过头来去学习基本的概念,就很容易结合前面的操作去理解了。在这个阶段,我建议你采用由全貌到细节的一个学习思路,先学习基本概念以及整体的架构,接着再深入到某一个具体的处理流程或者组件中去进行探究。
在体验过Knative 之后我们再去学习Serving中一些组件的交互过程你就很容易理解了。就像这一块核心逻辑我之前已经提前带你学习过了但在这节课实操之后再回过头看一看是不是就更好理解了呢?
比如冷启动过程我们通过动手操作发现了第一次请求函数时实例会从无到有并且隔一段时间不请求后实例又会被释放这就很容易让你明白Activator的作用以及AutoScaler的扩缩容流程。
再来Knative中的事件驱动机制。我们部署了一个用于打印事件的服务以及一个定时产生事件的PingSource对象接着我们可以观察到定时事件的任务处理。这个时候我们再去从宏观的角度学习Eventing就可以明白为什么他是一套松耦合的事件驱动体系了。
在Eventing的事件驱动的体系中你可以通过API进行事件的创建而事件将由事件的生产者发起由事件的消费者接收。系统学习过后你也可以知道我们的Demo是多种事件驱动模式中最简单的一种——Source&Sink模式。
其中Source声明的是事件源与事件接受者的一个关联关系而Sink表示一个可以接收事件的对象。结合前面的实践过程你一定可以理解我们部署的PingSource对象就是一个Source类型event-display就是一个事件接收的服务。再去细究PingSource的yaml可以看到Shedule定义的是定时触发的cron表达式而sink.name表示事件将交由event-display处理。
apiVersion: sources.knative.dev/v1
kind: PingSource
metadata:
name: test-ping
namespace: default
spec:
schedule: "*/1 * * * *"
contentType: "application/json"
data: '{"message": "Hello geek!"}'
sink:
ref:
apiVersion: v1
kind: Service
name: event-display
这样下来理解起来是不是就很迅速了呢学习了这种最基础的事件驱动模型你还可以再进一步去学习Channel&Subscription 以及 Broker&Trigger 这两个较为复杂的模型,通过这样的动手实践,由浅入深,不仅可以让你学得更快,还能记得更牢。
深入源码
对整体的架构以及关健处理流程有了一个清晰的认知以后,就可以深入到源码当中去了,源码能够帮助你更好地理解引擎框架设计者的思路。这其实是一个更高的要求,需要你对框架开发语言以及代码编写风格都有一定的掌握,才能够更好的理解。
源码的阅读途径也比较丰富除了官方文档以外开源的框架代码基本都会放在Github对于一些较难理解的地方也可以通过阅读博客以及社区留言等方式去解决。
以Knative为例Github包含了它全套的代码除了Eventing和Serving这两个核心组件代码库还包含客户端、一些使用文档等对于一些像扩缩容一类的关键流程有些Contributor甚至还会专门给出详细的流程说明可以说是非常全面了。遇到问题的时候Issue也能给你一定程度的参考。除此之外Knative的官方也有一些不错的技术博客这些都是能帮助你深入到源码的有效手段。
最后在学习Serverless框架时提前的知识储备也很重要比如学习Knative之前你至少需要对K8s的基本概念有一些了解而涉及到源码部分还需要掌握Golang的基本语法等等。
这些学习经验其实并不局限于本节课Knative的学习希望你更能够举一反三灵活运用到之后遇到的每一个新框架的学习过程中去。
小结
今天这节课你应该对Knative的核心组件有了更直观的了解Serving提供了管理整个Serverless工作负载的能力Knative Serving 通过自定义的一组特定对象,使用 Kubernetes CRD自定义资源的方式配合上Activator以及AutoScaler等关键组件从而实现了整个流量控制和自动扩缩容能力。
Knative Eventing实际上是一组API能够帮助你使用事件驱动的方式对函数进行调用。Source与Sink模型是Knative中最简单的事件驱动模型除此之外还有Channel&Subscriptions和Broker&Trigger。前者提供了一种事件传输机制能把事件分发给多个目的地或Sink。后者跟前者类似但不同的是后者更方便对事件进行过滤适用于一个 namespace 下的多个事件源,然后消费者根据事件的类型、属性挑选感兴趣的事件进行消费。
Tekton组件主要负责从代码仓库获取源码并编译成镜像推送到镜像仓库所有这些操作都是在Kubernetes Pod中进行的。
整体而言Tekton补充的是Knative快速开发与集成的能力提升了开发与部署的效率Eventing则通过松耦合的构造以及对CloudEvents的支持极大地丰富了Knative 的应用场景Serving组件贯穿到了整个Serverless容器的生命周期。
最后我想跟你说的是Serverless的技术是在不停更迭的我们需要灵活地研究一个新发布的Serverless引擎、框架、平台你可以通过直接上手体验再去研究架构原理最后深入源码细节升华的方式进行学习。
课后作业
好了,这节课到这里也就结束了,最后我给你留了一个课后作业。
你可以继续尝试搭建一下Knative Tekton并且看看能否通过Trigger搭建一个完整的CI/CD系统如果有问题欢迎你在留言区和我交流讨论。
感谢你的阅读,也欢迎你把这节课分享给更多的朋友一起阅读。

View File

@ -0,0 +1,437 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
24 实战进阶如何从Serverless引擎蜕变成一个Serverless平台
你好,我是静远。
我在前两节课详细介绍了私有化Serverless核心引擎的特性和选型方法并基于当前盛行的Knative引擎进行了部署。相信现在的你已经可以玩转Serverless底座了。
但仅仅研究核心引擎是远远不够的和构建Serverless平台还有一定的距离。今天这节课我将在引擎底座的基础上继续围绕“私有化”这个主题来跟你详细讲解如何基于一个Serverless 引擎底座构建完整的平台。
希望通过这节课的学习你能够具备构建一个平台的能力也让你在选择“最合适”的ToB厂商共建Serverless平台的时候更加得心应手。
定位:引擎和平台有什么不同?
我们先来明确一下Serverless下“引擎”与“平台”的侧重点。
一个引擎或者框架的目标更多的是提供一个具有特定功能的系统或者架构比如Knative、OpenFaaS、Fission等开源的Serverless引擎都是提供一个FaaS形态产品运行的系统。
对于一个引擎的研究和部署你甚至不需要任何业务和架构的经验通过官网的介绍通读一遍然后按照使用文档就可以很快地搭建一个Serverless系统来做测试、实验了。但并不能直接在企业的生产线上使用它还缺少权限管控、元信息管理、BaaS集成等能力。
而平台则需要考虑到生产级别中遇到的实际问题。从开始设计到完全投入生产使用需要经过整体的规划设计、迭代升级从内测到公测再到完全开放的过程服务过程中还需要考虑给用户承诺的SLA等等这就需要设计者具备较为丰富的业务领域经验、架构设计能力以及中长期的规划迭代思维。
公有云和私有云的平台虽然在展现形态上不太一样比如公有云需要适配云厂商的容器服务、IAM、云产品联动、多租户等私有云需要适配企业自身的容器底座、中间件、各类BaaS服务等但他们的思想是一样的私有云的Serverless平台主要享受到了对平台的控制权和安全把控只是将云厂商维护的工作转嫁到了企业的运维团队而已。唯一不同的是私有云可能不太关心业务用户的计费问题。
那么在构建一个Serverless平台的时候我们需要考虑哪些方面呢
如何构建Serverless平台
基于用户、平台、函数的关系我从安全性、易用性、可运维、可扩展性的角度整理出了7个核心要点来介绍。
整体平台以容器为底座构建于Kubernetes集群之上而我们的核心引擎可以选择上节课介绍的Serverless引擎框架或者自研引擎也是可以的。我们分别说一下每个功能的注意要点。
函数管理
一般开源引擎都会自带部分的函数管理能力比如通过CLI的方式来对函数进行CRUD也有一些引擎框架也集成了部分的Console操作能力。但不管是可视化界面还是CLI的操作性都达不到一个生产级别的要求。
从函数自身管理角度看,需要集成函数的创建、配置、部署发布、测试、删除、上下线功能,并且支持底层资源规格的选取。
从函数代码开发角度看需要提供多种函数语言的开发如Java、Python、Node.js、Golang等区别于函数计算引擎自带的模板和函数语言支持平台更需要易用性比如代码包的上传部署、持久化存储以及常用触发器的适配集成等等。
除此之外,还有最容易忽视的一点,那就是由于企业操作系统的不同,我们在制作函数计算基础镜像的时候,也得考虑不同操作系统版本不一致的问题。
平台管控
这部分的功能包括了用户的隔离、鉴权验证、用户角色、权限管理。这几大功能都是跟安全分不开的。我们一个一个来说。
用户隔离不仅包括了函数实体管控上的命名空间、并发度设置和持久化存储的隔离还包括运行容器的隔离而容器方面你需要考虑追加一层安全容器或者基于Node节点来分配这两种模式我在扩缩容这节课对比过可以复习一下加深记忆。
鉴权验证通常和企业的公共IAM系统对接但如果从高可用角度出发你可以设计两套系统一套企业IAM系统正常访问模式一套是本地系统的IAM应急模式。
用户角色和权限管理目前主流的权限管理系统的设计大都基于RBAC模型或者是RBAC的变形可以复用你之前设计微服务系统的经验。
资源控制
这个功能也可以放到平台管控里面单独讨论是为了强调平台的另一个重要特性计量计费。针对公有云和私有云不同的特点在于公有云面向的是外部的众多客户云厂商以营收为主不仅计量还需要计费那作为Serverless平台就需要对接企业的计费系统。而对于私有云来说更多的是企业内部使用计量的重要性更多一些计费并不是一个非常急迫的功能。
第二个特性在于对资源的使用限制。比如对实例的内存、CPU、GPU的使用在单个地域的最大使用量都是平台需要考虑的点。
开发工具链
一套好的开发工具,不仅可以使得开发调试部署更加方便,而且还更有利于老的服务迁移到新的技术平台中来,这才是设计新平台的初衷。
那么除了Serverless引擎自带的CLI 命令之外我们还应该开发一套满足自身企业的CLI命令工具应对通用的开发WebIDE工具应对在线快速开发IDE集成插件应对习惯于类似VSCode的开发人员。
高可用
企业一般会在服务器宕机、流量异常、同步/异步失败、冷启动、扩缩容等方面提出要求。我们发现除了冷启动和扩缩容属于Serverless的特有特征之外其他的方面和我们在微服务治理中的知识点是一样的。
总的来说我们可以通过多AZ的部署、异地备份等方式来解决。通过引入服务治理中间件如断路器、注册中心等来解决流量的异常调度、同步请求失败等问题但异步调用失败需要结合中间件来处理。回想一下我们之前提到的异步调用平台就需要延迟加载重试等策略方式来保证用户的请求可靠。
至于冷启动和扩缩容,可以再回过头复习一下,相信你就有答案了。
到这里一个Serverless平台的主干功能其实就差不多了。但如果要用的“爽”你还得具备可观测、编排、异步调用、层、模板、单实例多并发、预留实例的能力这个核心能力的实现和使用是不是都可以从前面的课程中找到答案呢
接下来我将在上节课的基础上结合可观测的知识点跟你详细梳理一遍设计一个Serverless平台的步骤。
可观测功能的构建
可能你会想:为什么是可观测的角度?首先,可观测几乎引入的是开源组件,跟某一个企业或者业务耦合程度很低,便于你理解和实操。其次,可以把之前可观测学习到的理论知识点在这里通过平台视角演练一下。
在可观测课程里我跟你提到了可观测中的三大要素指标、链路、日志但只掌握这三大要素还远远不够你还可以从以下5个方面进一步考虑完善可观测的构建查漏补缺。
明确收集的数据:无论是指标、链路还是日志,都应该明确我们需要什么样的数据,而不是毫无目的地将所有数据收集上来,每一种类型都应该从平台运维和用户关心两个维度去考虑。比如指标应该包含用户关心的调用次数、执行时长,还要包括各个实例的资源使用情况等。
组件的选型组件的引入最好是从一些成熟的产品中选择我个人建议使用一些CNCF推荐且较为成熟的产品比如Prometheus、fluent-bit等。除此之外你也需要考虑到组件引入时的学习成本和后期的运维工作一些可观测组件尽管功能丰富但熟悉和部署的过程就需要花费上大量的时间。
资源成本可观测的组件也需要占用一部分资源像Logstash这种强大的日志清洗组件可能单个实例就需要消耗几个G的内存而可观测的数据落盘还需要用到较大的存储空间。这些都需要结合具体的资源预算进行评估。如果预算足够你完全可以使用公有云上的服务免去后期的组件维护工作。
适配问题像金融、电信等企业为了提高系统的安全性操作系统往往是Kylin这一类国产操作系统并且有自己的一套PaaS底座因此为了适配PaaS底座可能还需要对这些可观测组件进行二次开发这也是在选型阶段需要考虑到的问题。
后期的扩展能力可观测组件的扩展能力也需要在设计之初就考虑进来这需要你对整个平台后期的使用量有一个长期的判断。比如Prometheus就可以考虑采用联邦集群的部署方式毕竟如果后期有明显的增长单点部署就会遇到瓶颈。
在开始实战之前你可以先看一下整体的架构示意图涵盖了基于Knaitve围绕三大支柱构建可观测的解决方案。接下来我们来分别看一下具体的实现路径。
指标
首先明确收集的指标数据。在12讲中我从平台与用户两个视角提出了调用次数、执行时长、资源使用情况等具体的细项而Knative引擎已经提供了部分指标收集的支持包括业务指标和模块指标。
其中业务指标会由queue-proxy提供包括revision_request_count、revision_request_latencies、revision_app_request_count等5种与业务紧密相关的指标数据。
在模块指标方面我们可以收集Activator、Autoscaler、Controller等上报的指标数据这其中包括与整体冷启动并发相关的Activator级别的并发量统计以及和当前资源使用情况相关的函数pod数量等。
那我们要用什么组件收集呢这里我推荐采用Prometheus。原因有两点
第一Knative官方推荐Prometheus作为指标的收集工具并且前面提到的模块都有各自的exporter用于上报使用非常方便
第二Prometheus属于CNCF可观测系列中比较成熟的一款指标采集工具社区也非常活跃对于后期的维护工作非常有帮助。
指标的收集工具确定为Prometheus后可视化工具推荐配套的Grafana这二者几乎是默认关联在一起使用的也是Knative官网的推荐。
链路
链路信息提供的是关键过程的耗时情况。对于Serverless 平台来说,最关键的两个阶段就是冷启动与业务代码整体的执行时长。
回到Knative冷启动耗时包括从Activator接受到请求开始直到用户容器和环境准备完成的过程而业务代码的执行时长则为函数运行的整个时长。在采集的过程中我们往往通过span的duration相减的方式来计算链路耗时。比如冷启动耗时通过Activator Service的Duration减去User Container执行完成的Duration就可以得到。
另外除了经过Activator和User Pod 这两个节点还需要考虑到对用户自定义链路的支持即允许用户通过SDK的方式在函数内的关键路径上进行链路标记并上报。
链路的相关工具我还是采用了Knative推荐的Opentelemetry和Jaeger 。其中Opentelemetry组件用于链路信息的收集与上报Jaeger 用于链路信息的可视化。
可以看到上图中我采用了DaemonSet部署Otel-agent这里你也可以通过Sidecar或者SDK注入的方式进行部署用于上报函数的tracing信息然后通过Otel-collector进行采集最后通过Jaeger-Query访问collector中的数据就可以查看了。这样就形成了一个完整的链路信息闭环。
日志
关于日志部分Knative 官方推荐使用fluent-bit主要是因为fluent-bit超快速、轻量级、高度可扩展是容器化日志收集和处理的首选工具同时可以对接ElasticSearch、Kafka等多种日志存储下游。
图上的整体日志处理流程采用的是fluent-bit由ElasticSearch 负责存储再由kibana进行可视化。
接下来我们就利用上节课搭建好的Knative结合上面的设计方案进行实操。
可观测功能实战
我会通过搭建构成中的几个关键步骤带你一步步实操,其中主要讲解指标和链路这两个更为复杂的部分,学习完之后,关于日志的部分你也一定能举一反三,自己搭建起来。
指标
首先我们来搭建采集指标的相关组件。指标的可观测构建包括数据上报、Prometheus数据收集以及Grafana数据可视化三个部分。
其中前面提到的Knative引擎本身会暴露很多指标像Serving中的Autoscaler、Activator等主要模块以及User Pod中的queue-proxy都实现了数据上报并暴露了专门的端口供Prometheus采集。比如Activator的request_concurrency和request_latencies就是对请求并发情况和延迟的统计上报这相当于Activator本身会作为一个exporter。
我以Activator为例演示一下关于Prometheus和Grafana的配置。首先我们先根据Knative官网提供的config yaml 配置一个Prometheus的采集规则其中Activator 的采集规则如下:
# Activator pods
- job_name: activator
scrape_interval: 3s
scrape_timeout: 3s
kubernetes_sd_configs:
- role: pod
relabel_configs:
# Scrape only the the targets matching the following metadata
- source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_pod_label_app, __meta_kubernetes_pod_container_port_name]
action: keep
regex: knative-serving;activator;metrics-port
# Rename metadata labels to be reader friendly
- source_labels: [__meta_kubernetes_namespace]
action: replace
regex: (.*)
target_label: namespace
replacement: $1
- source_labels: [__meta_kubernetes_pod_name]
action: replace
regex: (.*)
target_label: pod
replacement: $1
- source_labels: [__meta_kubernetes_service_name]
action: replace
regex: (.*)
target_label: service
replacement: $1
其中scrape_interval 和 scrape_timeout 分别表示抓取间隔和抓取数据的超时时间而最重要的是第9行到第11行定义了Prometheus是如何抓取Activator数据的。
source_labels表示数据的原始标签regex表示与source_labels标签匹配的值他们是一一对应的最后我使用action:keep表示保留下regex字段指定的标签。比如__meta_kubernetes_pod_label_app这个标签的值需要为Activator才能被抓取。
在这里我们需要将configmap创建在knative-monitoring 这个命名空间下,如果没有的话,需要你提前创建:
kubectl create ns knative-monitoring
kubectl apply -f 100-scrape-config.yaml
接着再通过200-prometheus.yaml创建Service、Deployment、Role等K8s资源
kubectl apply -f 200-prometheus.yaml
执行成功之后就可以看到在knative-monitoring下有一个Prometheus的pod正在运行
$ kubectl get pod -n knative-monitoring
NAME READY STATUS RESTARTS AGE
prometheus-system-0 1/1 Running 0 4d23h
然后我们通过Prometheus 提供UI进行查看如果你的K8s不是部署在本地的话可以通过port-forward的方式进行本地端口映射我们看一下Prometheus Service 暴露的端口:
$ k get svc -nknative-monitoring
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
prometheus-system-np NodePort 10.0.133.199 <none> 8080:30609/TCP 4d23h
可以看到暴露的端口是8080然后我们通过port-forward 命令进行本地访问:
k -nknative-monitoring port-forward svc/prometheus-system-np 8080
打开localhost:8080后可以进入Prometheus的主页。我们通过curl 命令访问上一节课部署的函数并在Prometheus主页搜索activator_request_count这个指标。之后可以看到Activator上报的指标已经被Prometheus采集上来了这个指标的标签还会包含访问函数的revision name
然后我们再按照相同的方式部署Grafana你同样可以参考官方提供的grafana yaml
kubectl apply -f 100-grafana-dash-knative-efficiency.yaml
kubectl apply -f 100-grafana-dash-knative.yaml
kubectl apply -f 100-grafana.yaml
需要注意的是在100-grafana.yaml这个文件中需要关联很多的configmap作为Grafana的视图配置我这里只使用了grafana-dashboard-definition-knative-efficiency和grafana-dashboard-definition-knative 这两个与Knative 相关的剔除了其他的configmap 配置。
配置完成后还是进入knative-monitoring可以看到已经有两个正在运行的pod 了:
$ kubectl get pod -n knative-monitoring
NAME READY STATUS RESTARTS AGE
grafana-cbb657d6-twc2l 1/1 Running 0 4d7h
prometheus-system-0 1/1 Running 0 4d23h
然后还是按照前面的方式查看service并使用port-forward 进行本地访问:
$ kubectl get svc -nknative-monitoring
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
grafana NodePort 10.0.63.40 <none> 30802:30870/TCP 4d8h
prometheus-system-np NodePort 10.0.133.199 <none> 8080:30609/TCP 4d23h
$ kubectl -nknative-monitoring port-forward svc/grafana 30802
通过默认用户名、密码和admin 登录后,创建一个数据面板,就可以看刚才采集的指标了:
链路
搭建完采集指标的相关组件我们接下来再来动手搭建链路信息采集的相关组件主要包括两个部分Opentelemetry和Jaeger。整个搭建的详细过程可以参考官方给出的这篇文章接下来我们开始动手搭建。
第一步我们需要在部署Opentelemetry组件之前先安装证书管理器
# 创建命名空间
kubectl create ns cert-manager
# 部署证书管理器
kubectl apply -f https://github.com/jetstack/cert-manager/releases/latest/download/cert-manager.yaml
在完成后可以在命名空间下看到三个运行的pod
kubectl get pod -n cert-manager
NAME READY STATUS RESTARTS AGE
cert-manager-bfcbf79f6-jvwnr 1/1 Running 0 16s
cert-manager-cainjector-65c9c8779f-wsk8v 1/1 Running 0 16s
cert-manager-webhook-848ff6dc66-cmq8t 1/1 Running 0 16s
第二步完成证书安装后我们接着部署Opentelemetry 的operator。其中operator负责管理opentelemetry collector也就是指标部分截图里的Otel-collcetor
# 创建命名空间
kubectl create ns opentelemetry-operator-system
# 部署operator
kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/download/v0.40.0/opentelemetry-operator.yaml
成功部署后可以看到对应运行的pod
$ kubectl get pod -n opentelemetry-operator-system
NAME READY STATUS RESTARTS AGE
opentelemetry-operator-controller-manager-596866cc59-7p6cr 2/2 Running 0 19s
第三步部署Jaeger Operator用于控制Jaeger实例。
kubectl create namespace observability &&
kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/v1.28.0/deploy/crds/jaegertracing.io_jaegers_crd.yaml &&
kubectl create -n observability \
-f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/v1.28.0/deploy/service_account.yaml \
-f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/v1.28.0/deploy/role.yaml \
-f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/v1.28.0/deploy/role_binding.yaml \
-f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/v1.28.0/deploy/operator.yaml
部署完成后查看pod状态
$ kubectl get pod -n observability
NAME READY STATUS RESTARTS AGE
jaeger-operator-7576fbc794-7gr8n 1/1 Running 0 16s
第四步创建Jaeger 实例。
kubectl apply -n observability -f - <<EOF
apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
name: simplest
EOF
我们检查一下pod以及service的状态可以看到上述的Jaeger类型会创建出simplest-agentsimplest-collectorsimplest-query 等多个service
$ kubectl get pod -n observability
NAME READY STATUS RESTARTS AGE
jaeger-operator-7576fbc794-7gr8n 1/1 Running 0 11m
simplest-746f54765d-zdb7d 1/1 Running 0 9s
$ k get svc -nobservability | grep simplest
simplest-agent ClusterIP None <none> 5775/UDP,5778/TCP,6831/UDP,6832/UDP 9d
simplest-collector ClusterIP 10.0.94.222 <none> 9411/TCP,14250/TCP,14267/TCP,14268/TCP 9d
simplest-collector-headless ClusterIP None <none> 9411/TCP,14250/TCP,14267/TCP,14268/TCP 9d
simplest-query ClusterIP 10.0.239.242 <none> 16686/TCP,16685/TCP 9d
然后还是利用port-forward 访问simplest-query服务打开localhost:16686后可以看到如下界面
kubectl -n observability port-forward service/simplest-query 16686
第五步部署Opentelemetry Collector和Opentelemetry Agent。二者本质上都是tracing数据的收集器只是数据来源和流向不同。其中Opentelemetry Agent负责接收实例trace数据并转发给Opentelemetry Collector而Opentelemetry Collector负责接收由Otel Agent或应用实例本身发送的trace数据并将其转发给Jaeger。
根据前面的设计我们可以按照Knative官网给出的Opentelemetry Collector 案例进行配置注意exporters中的jaeger.endpoint这个字段需要配置成下游的endpoint即我们前面创建的jaeger-collcetor。另外需要在最后一行指定好exporter的类型为jaeger。
apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
metadata:
name: otel
namespace: observability
spec:
config: |
receivers:
zipkin:
exporters:
logging:
jaeger:
endpoint: "simplest-collector.observability.svc.cluster.local:14250"
tls:
insecure: true
service:
pipelines:
traces:
receivers: [zipkin]
processors: []
exporters: [logging, jaeger]
同理Opentelemetry Agent的下游因为是Opentelemetry Collector所以在exporters中需要指定otlp.endpoint为刚刚Opentelemetry Collector的endpoint而最后一行也需要指定exporter的类型为otlpOpentelemetry Protocol
.....
exporters:
otlp:
endpoint: "otel-collector.observability.svc.cluster.local:4317"
tls:
insecure: true
.....
service:
pipelines:
traces:
receivers: [zipkin,otlp]
processors: []
exporters: [otlp]
配置完成后执行apply命令即可
kubectl apply -f opentelemetry-collector.yaml
kubectl apply -f opentelemetry-agent.yaml
部署成功后在observability namespace下会有5个running pods分别为jaeger operator、jaeger instance、otel collector和两个otel agent。
☁ opentelemetry kubectl get pod -n observability
NAME READY STATUS RESTARTS AGE
jaeger-operator-7576fbc794-7gr8n 1/1 Running 0 28m
otel-agent-collector-4j9sc 1/1 Running 0 9s
otel-agent-collector-xd52d 1/1 Running 0 9s
otel-collector-6f49dfc856-ntwpp 1/1 Running 0 48s
simplest-746f54765d-zdb7d 1/1 Running 0 16m
第六步配置serving的链路上报信息。serving关于tracing的配置放在了knative-serving命名空间下的config-tracing 这个configmap中这里需要将zipkin-endpoint配置为我们前一步创建的Opentelemetry Agent的endpoint之后所有serving组件上报的tracing信息都会上报给opentelemetry agent
apiVersion: v1
kind: ConfigMap
metadata:
name: config-tracing
namespace: knative-serving
data:
backend: "zipkin"
zipkin-endpoint: "http://otel-agent-collector.observability.svc.cluster.local:9411/api/v2/spans"
debug: "true"
最后我们来验证一下在本地浏览器打开localhost:16886 访问我们之前配置的函数接着会在界面看到activator-service这个关键字
点击其中一条trace信息后就可以看到一次完整的链路过程
小结
最后我们来小结下今天的内容。通过这节课相信你已经知道了掌握一个Serverless的引擎和构建一个Serverless平台还有一定的距离需要我们基于底层技术进一步结合丰富的业务领域经验、架构设计能力以及中长期的规划思维来迭代构建。
今天我通过7个核心功能要点函数管理、平台管控、资源控制、开发工具链、高可用、可观测、延伸能力分别跟你介绍了基于一个引擎构建一个平台的思想。相当于我们在一幅轮廓画上增加了单色的素描有了明显的形象和空间。
这样就够了呢?在实际的运行过程中,我们还需要考虑:新老业务如何基于这个平台来产生价值?如何方便的构建应用?如何满足不断延伸的场景需求?
那么,我们还需要再素描的基础上,增加色彩,使这幅画更具美感。这些“色彩”包括便捷的迁移工具和框架集成、专业的培训和实战手册、屏蔽底层异构资源的调度、可自定义运行时的能力等。
你还可以想象一下现有的Serverless平台是否还有提升的空间它的未来之路在哪里终极形态是什么样子的? 比如业界是否会有一个统一的跨平台工具链Serverless化内涵的延伸不再局限于一个产品平台的表现而是一种设计范式件。这些问题我会在结束语跟你一起探讨。
课后作业
好了,这节课到这里也就结束了,最后我给你留了一个课后作业。
基于上述的解决方案,尝试将日志采集的部分补充完整,想一想,要用到哪些开源组件?要如何联动和展示?
感谢你的阅读,也欢迎你把这节课分享给更多的朋友一起交流探讨。

View File

@ -0,0 +1,73 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
结束语 在实战中把握事物本质,不断革新
你好,我是静远。
时间过得真快,我们的课程已经接近尾声了。从筹备、上线到今天专栏的完结,历时半年左右的时间。
说实在的我没想过战线会拉得如此之长。做专栏是一个知识沉淀的过程需要耗费的心力和精力远超我的预期课程和制作公众号、PR稿不同它一定是“治学严谨”“授业解惑”的我希望对每个知识点都需要高度负责在构思如何输出之外还要引入最新的实战技术和经验并通过实操案例展示出来。
也因此我特别感谢我的小伙伴“哲哥”非常用心、耐心地和我一起深度Review每一篇文章有时我们还会找用户和其他专家一起来探讨扮演不同的角色来提出问题为了一个课程的打磨我们经常还会来来回回沟通到后半夜。
但这都是值得的。在课程打磨、留言区解答疑问的同时我也在不断地成长和完善Serverless这一领域的知识体系。
其实Serverless的理念也是在认同和挑战的螺旋迭代中不断向前迈进的。我本人、团队内部、客户和用户以及行业专家讨论时都会提出诸如“Serverless的路子到底怎样走”“函数计算目前的形态在N年后是不是非常原始”“Serverless的治理如何做到更原生”等等这些问题。
而面对这些不确定性极高的未来我们能做的只有两点第一吃透一个具体的产品技术不断在实战中理解Serverless的本质第二从终局出发不断地创造新的产品和方案。这两点也是我在Serverless领域践行的法则。
在实战中把握事物本质
这个标题有两个关键词,实战和本质。
先说实战。在实战之前我们需要从快速迭代技术中选取一片确定性极高的核心区域例如我们的课程就是从FaaS形态的Serverless切入学习的。在吃透了这部分的技术原理之后我们才能更高效地吸收实战经验快速提升自己的即时战力。而每一次实战都会反过来促使我们更加贴近技术的本质。正如我们在开篇词中提到的那样这个闭环让我们最终达到忘形存神、用意不用力的境界。
那Serverless的本质是什么呢提出Serverless的初心到底是为了什么
每个人站的角度不一样,出发点不一样,得到的结论也就不一样。
在我看来用户能够更专注于自己的业务从而产出更大的社会价值正是因为Serverless“降本增效”的初心。而在平台方面它一直在向着更弹性、更易用、更可运维的方向迭代。而我们的一切实战也都是在朝着这个本质迈进。
所以,在你不知道往哪个方向提升自己的时候,不妨回头想想这个技术本身要解决的问题。有了本质的思考后,我们后续做的事情也就不至于跑偏了。
不断革新
不跑偏就是终点了吗?
我们在课程里不止一次提到过“举一反三”。Serverless的内涵一直在不断地外延我们不能只学习“招式”如果只是根据知识点来一步一步操作而不懂得变通那就说明还没有真正的理解。我们需要在本质和初心之上发挥更多的想象力不断革新。
那么,我们具体要怎么做呢?
首先具备交流意识的学习。在课程里我也有特意留有思考的地方。有的读者的思考非常好比如在第3讲中对中控调度服务的理解在10讲对VPC创建的发问等等。
其次带有深度思考的工作。比如查看系统的问题时眼中看到的如果仅仅是日志和报警最多觉得它是一个BUG看不出产品的“症结”和客户的内心诉求这样是不行的。
因此,我们可以这样在工作中多想想症结背后的问题,比如:
客户提出这个业务场景和这个问题,它其实背后真正想要什么?-
那我们下一步该怎么做呢?-
我为什么会这么想呢?还可以怎么做呢?
你可能会觉得第一个问题和自己无关,更多的是产品经理的工作范畴。但这种方式的自问自答,可以促进你对工作进一步的思考。
如果不这样追问,不这样要求自己,那么我们工作的想象力和热情会逐渐消退。这种思考能力的弱化,会妨碍我们创造更有价值的产品。
最后不受束缚勇于尝试。我给你分享一个我自身的例子。由于团队人手不够同时需要兼顾函数计算的公有云、私有化和其他产品的研发以至于要同时维护多套引擎底座非常麻烦团队的同学经常需要“打一枪换一炮”。后来我们仔细分析研究了几个产品的共性大胆地改造通用认证和引擎调度系统。结果这样的建设在后来的弹性应用和容器调度上也同样值得借鉴和复用一组L型赋能大大降低了维护成本。
所以,抛弃所谓的常识和教条吧,尤其是站在技术浪潮的前沿,我们更应该倾听好的想法,吸收新的知识,勇于尝试。
希望你在这个专栏中不单单可以系统学习到Serverless的相关知识还可以利用学到的知识和方法给自己的业务“降本增效”让自己的加薪升职增加一个不小的筹码。更重要的是你不会再被既定的一些规则束缚开始培养新的思考问题的方式主动进化自己的思维透过产品抓住技术本质勇于尝试。
我坚信在你的加持下还会有更多Serverless形态的技术和产品发布。
最后,我准备了一份毕业问卷,希望你可以花两分钟的时间填写一下。我会认真倾听你对这个专栏的意见或建议,期待你的反馈。我也会同时保持课程的最新技术更新,再次感谢你选择了我,我们江湖再见。