mirror of
https://github.com/cheetahlou/CategoryResourceRepost.git
synced 2025-11-19 23:53:47 +08:00
mod
This commit is contained in:
157
极客时间专栏/Serverless入门课/进阶篇/05 | 后端BaaS化(上):NoOps的微服务.md
Normal file
157
极客时间专栏/Serverless入门课/进阶篇/05 | 后端BaaS化(上):NoOps的微服务.md
Normal file
@@ -0,0 +1,157 @@
|
||||
<audio id="audio" title="05 | 后端BaaS化(上):NoOps的微服务" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d6/11/d63a54ce8d097aca2b5ff55a41dc1d11.mp3"></audio>
|
||||
|
||||
你好,我是秦粤。现在我们知道了在网络拓扑图中,只有Stateless节点才能自由扩缩容,而Stateful节点因为保存了重要数据,我们要谨慎对待,因此很难做扩缩容。
|
||||
|
||||
FaaS连接并访问传统数据库会增加额外的开销,我们可以采用数据编排的思想,将数据库操作转为RESTful API。顺着这个思路,我引出了后端应用的BaaS化,一句话总结,后端应用BaaS化就是将后端应用转换成NoOps的数据接口。那怎么理解这句话呢?后端应用BaaS化,究竟应该怎么做?接下来的几节课,我们会展开来讲。
|
||||
|
||||
我们先回忆一下上节课的“待办任务”Web应用,这个项目前端是单页应用,中间用了FaaS做SFF数据网关,后端数据接口还要BaaS化。这个案例会贯穿我们的课程,所以你一定要动手run一下。为了让你对我们的项目有个宏观上的认识,我还是先交付你一张大图。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ba/c9/bab7e22b588d69cbe0197d36696411c9.jpg" alt="" title="“待办任务”Web应用架构图">
|
||||
|
||||
这个架构的优势是什么呢?我们将这个图变个形,你就更容易理解了。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/66/8f/66aeb01686c94478b2847be5bb2a398f.jpg" alt="" title="架构变形示意图">
|
||||
|
||||
咱从左往右看上面这张图。用户从浏览器打开我们网站时,前端应用响应返回index.html;然后浏览器去CDN下载我们的静态资源,完成页面静态资源的加载;与此同时,浏览器也向前端应用发起数据请求;前端应用经过安全签名后,再将数据请求发送给SFF;SFF再根据数据请求,调用后端接口或服务,最终将结果编排后返回。
|
||||
|
||||
从图里你可以看到,除了数据库是Stateful的,其它节点都已经变成了Stateless。如果你公司业务量不大的话,这个架构其实已经足够了。就像传统的MVC架构一样,单点数据库,承载基本的并发量不是问题,而且数据也可以通过主从结构和客户端读写分离的方式来优化。
|
||||
|
||||
但MVC架构最大的问题就是累积,当一个MVC架构的应用,在经历长期迭代和运营后,数据库一定会变得臃肿,极大降低数据库的读写性能。而且在高并发达到一定量级,Stateful的数据库还是会成为瓶颈。那我们可以将自己的数据库也变成BaaS吗?
|
||||
|
||||
要解决数据库的问题,也可以选择我上节课和你说的云服务商提供的BaaS服务,比如DynamoDB。但云服务商BaaS服务究竟是怎么做到的?如果BaaS服务能力不全,不够满足我们的需要时怎么办?今天我就先带你看看传统的MVC应用中的数据库怎么改造成BaaS。
|
||||
|
||||
当然,BaaS化的过程有些复杂,这也正是我们后面需要用几节课才能跟你解释清楚的核心知识点。正如我们本节课的标题:后端应用BaaS化,就是NoOps的微服务。在我看来后端应用BaaS化,跟微服务高度重合,微服务几乎涵盖了我们BaaS化要做的所有内容。
|
||||
|
||||
所以我们先来学习一下微服务是什么。
|
||||
|
||||
## 微服务的概念
|
||||
|
||||
微服务的概念对很多做后端同学来说并不陌生,尤其是做Java的同学,因为早些年Java就提出SOA面向服务架构。微服务算是SOA的一个子集,2014年由ThoughtWorks的Martin Fowler提出。微服务设计之初是为了拆解巨石应用,巨石应用就是指那些生命周期较长的,累计了大量业务高度耦合和冗余代码的企业应用。
|
||||
|
||||
跟Serverless的概念还在发展中不同,微服务的概念在这么多年的发展中已经有了明确的定义了。下面是AWS官方的解释:
|
||||
|
||||
>
|
||||
微服务是一种开发软件的架构和组织方法,其中软件由通过明确定义的 API 进行通信的小型独立服务组成,这些服务由各个小型独立团队负责。微服务架构使应用程序更易于扩展和更快地开发,从而加速创新并缩短新功能的上市时间。
|
||||
|
||||
|
||||
那在我看来,微服务就是先拆后合,它将一个复杂的大型应用拆解成职责单一的小功能模块,各模块之间的数据模型相互独立,模块采用API接口暴露自己对外提供服务,然后再通过这些接口组合出原先的大型应用。拆解的好处是,小模块便于维护,可以快速迭代,跨应用复用。
|
||||
|
||||
我们的Serverless专栏并不打算给你详细地讲解微服务,但是希望你能一定程度上了解微服务。FaaS和微服务架构的诞生几乎是在同一时期,它俩的很多理念都是来自12要素(Twelve-Factor App)[1],所以微服务概念和FaaS概念高度相似,也有不少公司用FaaS实现微服务架构;不同的是,微服务的领军公司ThoughtWorks和NetFlix到处宣扬他们的微服务架构带来的好处,而且他们提出了一整套方法论,包括微服务架构如何设计,如何拆解微服务,尤其是数据库如何设计等等。
|
||||
|
||||
我们后端应用BaaS化,首先要将复杂的业务逻辑拆开,拆成职责单一的微服务。**因为职责单一,所以服务端运维的成本会更低。**而且拆分就像治理洪水时的分流一样,它能减轻每个微服务上承受的压力。
|
||||
|
||||
我在Serverless的专栏里向你介绍微服务,主要就是想引入微服务拆解业务的分流思想和微服务对数据库的解耦方式。
|
||||
|
||||
## 微服务10要素
|
||||
|
||||
谈起微服务,很多人都要说12要素[2],认为微服务也应该满足12要素。12要素当初是为了SaaS设计的,用来提升Web应用的适用性和可移植性。我在做微服务初期也学习过12要素,但我发现这个只能提供理论认识。
|
||||
|
||||
所以在2018年GIAC的大会上,我将我们团队在微服务架构落地中的经验总结为**微服务的10要素:API、服务调用、服务发现;日志、链路追踪;容灾性、监控、扩缩容;发布管道;鉴权。**
|
||||
|
||||
我们回想一下[[第1课]](https://time.geekbang.org/column/article/224559) 中小服的工作职责:
|
||||
|
||||
1. 无需用户关心服务端的事情(容错、容灾、安全验证、自动扩缩容、日志调试等等)。
|
||||
1. 按使用量(调用次数、时长等)付费,低费用和高性能并行,大多数场景下节省开支。
|
||||
1. 快速迭代&试错能力(多版本控制,灰度,CI&CD等等)。
|
||||
|
||||
你有没有发现跟微服务的要素有大量的重合。API就是RESTful的HTTP数据接口;服务调用你可以理解为就是HTTP请求;服务发现你可以理解为我们只能用域名调用我们的HTTP请求,不能用IP;日志、容灾、监控都不难理解;链路追踪,是微服务重要的一环,因为相对传统MVC架构,我们一个请求在后端的调用链增长了,为了快速定位问题,我们都需要打印整个调用链路的异常栈;发布管道和鉴权,和我们的FaaS也有很重要的关联,我将放在下一课讲解。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/45/f1/4558c4088c38623cf366e7d686654ff1.png" alt="" title="木桶效应示意图">
|
||||
|
||||
接下来,我不准备按照微服务的10要素一一操作,但我希望可以通过Serverless帮你建立知识体系的索引,所以关联的技术栈我都尽量点出来,你可以自己进一步了解学习。
|
||||
|
||||
我们再次拿出我们的创业项目“待办任务”Web网站,这次我将带你一起看看微服务如何让数据库解耦。
|
||||
|
||||
我们先分析一下“待办任务”Web服务。上一讲末尾我的作业其实已经为你准备好了,我们一起看看index.js文件,这是一个典型的MVC架构的应用。View层就是index.html和静态资源文件;Model层,我们引入lowdb用一个db.json文件代替数据库;Control层,就是app.METHOD,处理/api/*的数据逻辑。微服务是解决后端应用的,所以我们只需要关注Control层。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/25/a4/25981be7fb564da00a736bfe93f101a4.jpg" alt="" title="index.js文件">
|
||||
|
||||
API的数据处理,主要有两个,一个是/api/rule处理待办任务的,一个是/api/user负责用户登录态,而且我们“待办任务”主要的数据模型也就是待办任务和用户这两个。那我们可以将后端拆分出两个微服务:待办任务微服务和用户微服务。这里我要强调下,我只是为了向你演示微服务才这样做,在实际业务中,这么简单的功能,没必要过早地拆分。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/3a/ab/3abe89510aa6897b0b1f50e239c91dab.jpg" alt="" title="微服务拆解演示">
|
||||
|
||||
初步拆解,我们将index.js中的数据库移走了,而且拆分后各微服务的数据库比原来混杂在一起简单且容易维护多了。user.js负责用户相关业务逻辑,只维护用户信息的数据库,并且暴露RESTful的HTTP方法;rule.js负责待办任务的增删改查,只维护待办任务的数据库,并且暴露RESTful的HTTP方法;index.js只需要负责将请求HTTP返回给数据编排。
|
||||
|
||||
HTTP协议要满足我们的服务调用与发现,发布的user.js和rule.js必须使用域名,例如api.jike-serverless.online/user和api.jike-serverless.online/rule。这样新扩容的机器IP,只需要注册到这个域名下就可以被服务发现IP并调用了。
|
||||
|
||||
我们现在拆分后只是将数据库从index.js移出,分给到了user.js和rule.js,但两个节点到目前为止数据库仍然是Stateful的。我们如何再让这两个数据库节点变成Stateless呢?你可以按一下暂停键思考一下。
|
||||
|
||||
我们先想想,对于rule.js这个微服务来说,我要再扩容一个新的实例,而且让两边的实例保持一致,那么我们是否只需要让新的数据库和旧的数据库保持同步更新就可以了呢?
|
||||
|
||||
## 解耦数据库
|
||||
|
||||
没错,其实要解决这个问题的核心就是让数据启动时,可以更新到最新的数据库。在其中一个数据库更新时,通知另外一个数据库更新。
|
||||
|
||||
这里就要引出一个关键的Stateful对象:消息队列[3]。它是一个稳定的绝对值得信赖的Stateful节点,而且对你的应用来说消息队列是全局唯一的。解耦数据库的思路就是,通过消息队列解决数据库和副本之间的同步问题,有了消息队列,剩下的就简单了。
|
||||
|
||||
我们每个微服务中的数据库,例如MySQL,写的操作都会产生binary log,通过额外的进程将binary log变更同步到消息队列,并监听消息队列将binary log更新在本地执行,修改MySQL。比较著名的解决方案就是领英开源的Kafka,现在云服务商基本也都会提供Kafka服务。当然数据库和副本之间会有短暂的同步延迟问题,但问题其实也不大,因为我们通常对数据库进行写操作时也会锁表。
|
||||
|
||||
如果你要细究这个问题,那就会碰到分布式架构无法同时满足的CAP[4] 课题。我们在此也不深入展开CAP话题了,目前异步同步的时间差在很多场景下我觉得是可以接受的。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/96/36/966b47dfea82a3d841e45cd260270636.jpg" alt="" title="微服务拆解演示">
|
||||
|
||||
拿我们自己的例子来说也很简单,我们用消息队列RocketMQ,写操作我们都写入RocketMQ,rule.js进程中我们监听RocketMQ,一旦有rule写入的消息,我们就更新我们的lowdb数据。围绕消息队列建立的同步机制,让每个微服务的数据库和它的扩容副本之间自动同步了。对于微服务来说,它本身的数据库还是Stateful的,但在微服务外部看来,这个微服务是Stateless的。如下图所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/7a/3a/7ab3bebbffabd18282d54e17093d0c3a.jpg" alt="" title="同步机制">
|
||||
|
||||
跟传统的主从数据库方式不同的是,我们每个微服务的单点实例都是独享一个数据库的,rule这个微服务单点可以对外提供所有待办任务的RESTful API接口。
|
||||
|
||||
你要知道消息队列,例如RocketMQ的可靠性是99.99999999%,而我们常用的虚拟机ECS的可靠性也只不过99.95%。在高并发架构的设计中,我们通常会要求Stateful节点一定要稳定,而且越少越好,所以消息队列,对于微服务和FaaS来说,都是可以作为支撑业务的核心。我们依赖消息队列,会让整个架构更稳定。
|
||||
|
||||
## 总结
|
||||
|
||||
我们为了避免在FaaS中直接操作数据库,而将数据库操作变成BaaS服务。为了理解BaaS化具体的工作,我们引入了微服务的概念。微服务先将业务拆解成单一职责和功能的小模块便于独立运维,再将小模块组装成复杂的大型应用程序。这一点上跟我们要做的后端应用BaaS化相似度非常高。所以参考微服务,我们先将MVC架构的后端解成了两个微服务,再用微服务解耦数据库的操作,先将数据库拆解成职责单一的小数据库,再用消息队列同步数据库和副本,从而将数据变成了Stateless。
|
||||
|
||||
现在我们再来回顾一下这节课的重要知识点。
|
||||
|
||||
1. 微服务的概念:它就是将一个复杂的大型应用拆解成职责单一的小功能模块,各模块之间数据模型相互独立,模块采用API接口暴露自己对外提供的服务,然后再通过这些API接口组合成大型应用。这个小功能模块,就是微服务。
|
||||
1. 微服务10要素:API、服务调用、服务发现;日志、链路追踪;容灾性、监控、扩缩容;发布管道;鉴权。这跟我们要做的BaaS化高度重合,我们可以借助微服务来实现我们的BaaS化。
|
||||
1. 解耦数据库:通过额外进程让数据库与副本直接通过消息队列进行同步,所以对于微服务应用来说,只需要关注自身独享的数据库就可以了。微服务通过数据库解耦,将后端应用变成Stateless的了,但对后端应用本身而言,数据库还是Stateful的。
|
||||
|
||||
## 作业
|
||||
|
||||
由于Kafka云服务比较贵,所以这节课的作业我们采用表格存储[5]来模拟Kafka,以实现DB的同步功能。**你可以尝试自己部署一下rule-faas到一个新的域名。**下面是具体的代码地址:
|
||||
|
||||
- 仓库地址:[https://github.com/pusongyang/todolist-backend/tree/lesson05](https://github.com/pusongyang/todolist-backend/tree/lesson05)
|
||||
- 前端地址:[https://github.com/pusongyang/todolist-frontend/tree/lesson05](https://github.com/pusongyang/todolist-frontend/tree/lesson05)
|
||||
- 实现后效果:[http://lesson5.jike-serverless.online/list](http://lesson5.jike-serverless.online/list)
|
||||
|
||||
你可以根据自己的域名,替换一下前端文件中的这个地址:[http://faas.jike-serverless.online/api/rule](http://faas.jike-serverless.online/api/rule),或者用我的前端代码[lesson5](https://github.com/pusongyang/todolist-frontend/tree/lesson05)分支修改/src/pages/ListTableList/service.ts中的地址,自己 `npm run build` 一下,替换掉项目public目录,压缩上传到函数服务目录。
|
||||
|
||||
这次的部署会有些复杂,我们要部署两个FaaS服务,一个是rule-faas.js,用来处理/api/rule;另一个是index-faas.js,用来处理我们的静态资源请求。原理图如下所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/98/b0/987db6d15137ce820b56fcf97e91e8b0.jpg" alt="" title="部署示意图">
|
||||
|
||||
另外.aliyunConfig文件格式如下,你需要填入自己的配置情况,切记这个文件不可以上传到Git仓库:
|
||||
|
||||
```
|
||||
//.aliyunConfig文件,保存秘钥,切记不可以上传Git
|
||||
const endpoint = "https://rule.cn-shanghai.ots.aliyuncs.com";
|
||||
// AccessKey 阿里云身份验证,在阿里云服务器管理控制台创建
|
||||
const accessKeyId = "AccessKey";
|
||||
// SecretKey 阿里云身份验证,在阿里云服务器管理控制台创建
|
||||
const accessKeySecret = "SecretKey";
|
||||
// 在数据链表中查看
|
||||
const instancename = "rule";
|
||||
const tableName = 'todos';
|
||||
const primaryKey = [{ 'key': 'list' } ];
|
||||
|
||||
|
||||
module.exports = {endpoint, accessKeyId, accessKeySecret, instancename, tableName, primaryKey};
|
||||
|
||||
|
||||
```
|
||||
|
||||
期待你的作业和思考,如果今天的内容让你有所收获,也欢迎你把文章分享给身边的朋友,邀请他加入学习。
|
||||
|
||||
## 参考资料
|
||||
|
||||
[1] [https://en.wikipedia.org/wiki/Twelve-Factor_App_methodology](https://en.wikipedia.org/wiki/Twelve-Factor_App_methodology)
|
||||
|
||||
[2] [https://12factor.net/zh_cn/](https://12factor.net/zh_cn/)
|
||||
|
||||
[3] [https://help.aliyun.com/document_detail/29532.html?spm=5176.11065259.1996646101.searchclickresult.6936139bcS8BlU](https://help.aliyun.com/document_detail/29532.html?spm=5176.11065259.1996646101.searchclickresult.6936139bcS8BlU)
|
||||
|
||||
[4] [https://www.ruanyifeng.com/blog/2018/07/cap.html](https://www.ruanyifeng.com/blog/2018/07/cap.html)
|
||||
|
||||
[5] [https://ots.console.aliyun.com/](https://ots.console.aliyun.com/index#/list/cn-shanghai)
|
||||
135
极客时间专栏/Serverless入门课/进阶篇/06 | 后端BaaS化(中):业务逻辑的拆与合.md
Normal file
135
极客时间专栏/Serverless入门课/进阶篇/06 | 后端BaaS化(中):业务逻辑的拆与合.md
Normal file
@@ -0,0 +1,135 @@
|
||||
<audio id="audio" title="06 | 后端BaaS化(中):业务逻辑的拆与合" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/33/90/33ccbfc021b2b37321afe8e1cb955390.mp3"></audio>
|
||||
|
||||
你好,我是秦粤。上一课中,我们学习了后端BaaS化的重要模块:微服务。现在我们知道微服务的核心理念就是先拆后合,拆解功能是为了提升我们功能的利用率。同步我们也了解了实现微服务的10要素,这10要素要真讲起来够单独开一门课的。如果你不熟悉,我向你推荐杨波老师的[《微服务架构核心20讲》](https://time.geekbang.org/course/intro/100003901)课程。
|
||||
|
||||
BaaS化的核心其实就是把我们的后端应用封装成RESTful API,然后对外提供服务,而为了后端应用更容易维护,我们需要将后端应用拆解成免运维的微服务。这个逻辑你要理解,这也是为什么我要花这么多篇幅给你谈微服务的关键原因。
|
||||
|
||||
上节课我们将“待办任务”Web服务的后端,拆解为用户微服务和待办任务微服务。但为什么要这样拆?是凭感觉,还是有具体的方法论?这里你可以停下来想想。
|
||||
|
||||
微服务的拆解和合并,都有一个度需要把握,因为我们在一拆一合之间,都是有成本产生的。如果我们拆解得太细,就必然会导致我们的调用链路增长。调用链路变长,首先影响的就是网络延迟,这个好理解,毕竟你路远了,可能“堵车”的地方也会变多;其次是运维成本的增加,调用链路越长,整个链条就越脆弱,因为其中一环出现问题,都会导致整个调用链条访问失败,而且我们排查问题也变得更加困难。
|
||||
|
||||
反过来看,如果我们拆解得太粗,调用链路倒是短了,但是这个微服务的复用性就差了,更别提因为高耦合带来的复杂且冗余的数据库表结构,让我们后续难以维护。我画了个图,你感受下。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e3/81/e3809702efd712b18d28465b2512e681.png" alt="" title="拆解程度示意图">
|
||||
|
||||
## 拆之,领域驱动设计
|
||||
|
||||
那我们要合理地拆解微服务,应该怎么拆解呢?上节课其实我有提到,目前主流的解决方案就是领域驱动设计,也叫DDD[1]。DDD是Eric Evans在其2004年的同名书中提出来的一个思想,但一直仅仅局限在Java的圈子里,直到2014年,微服务兴起后大家才发现它可以指导微服务的拆分,这才走进了大多数人的视野。用一句话简单总结,DDD就是一套方法论:**通过对业务分层抽象,分析定义出领域模型,用领域模型驱动我们设计系统,最终将复杂的业务模型拆解为独立运维的领域模型。**
|
||||
|
||||
实际我自己在使用微服务开发的过程发现,微服务整体应该是一个动态网络结构[2],随着业务的发展,这个网络结构也会发生变化。DDD能帮助我们前期分析出一个较好的网络结构,但实际上,我们更应该思考的是如何整体优化动态网络:**减少核心节点,保护核心节点,降低网络深度等等。**
|
||||
|
||||
怎么理解动态网络优化呢?我们可以做个思维实验:假设我们将所有的功能都拆解成微服务,任意的微服务节点之间都可以相互调用,调用越频繁它们之间的距离就越近。那么我们考虑一下,当我们网站的访问请求流量稳定后,我们整个微服务节点组成的网络状态是怎么样的?
|
||||
|
||||
首先网络节点的相互制约总会让那些相互之间强依赖的、高耦合的节点,越走越近,最后聚集成一团节点。其次那些跟业务逻辑无关的节点,逐渐被边缘化,甚至消失。我们看这些聚集成团的节点,如果团里的点聚合太近了,其实是不适合拆分的,它们整体应该作成一个微服务。等这些节点太近的团合并成一个微服务节点后,我们再看那些聚集在一起、又不太近的节点就是一个个微服务了。
|
||||
|
||||
所以,我们在启动项目时,不用太过纠结应该如何去拆解微服务。而应该持续关注,并思考每个微服务节点的合理性。就像看待动态网络一样,持续地调整优化,去除核心节点。最终它会伴随你业务的发展阶段,达到各个阶段的稳定动态网络结构。
|
||||
|
||||
就像我们上节课“待办任务”Web服务一样,我们可以先简单地将我们的项目后端分为:用户微服务和待办任务微服务。当然这里我们目前的业务太简单了,用DDD去分析,也是大材小用。随着我这个项目的业务发展,我们添加的功能会越来越多。让微服务根据业务一起成长演变就可以了。这并不是说我们就放任微服务不管了,而是从整体网络的角度思考,去看我们的微服务如何演进。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/58/cf/589469933bdc3b0335f9754ff2f555cf.png" alt="" title="微服务演进过程">
|
||||
|
||||
## 合之,Streaming
|
||||
|
||||
看完拆解,我们再看合并。合并呢,换个高大上的词其实就是前面课程中提到的编排。目前为止,我们整个“待办任务”Web应用架构的设计基本完成了,而且所有节点都是Stateless的了。变成Stateless节点后,其实对于前端的同学来说,一点都不陌生,比如React的单向数据流中的State也要求我们Immutable,Immutable其实就是Stateless。
|
||||
|
||||
我们上面已经看到了,拆解后的架构是个动态网络,那我们应该怎么合并或者编排呢?当然你像SFF那样通过传统的函数,将每个HTTP数据的请求结果通过数组或对象加工处理,再将这些结果返回也是可以的。但我在这里想向你介绍另外一种编排思路,工作流。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/d2/57/d267851c983200b430dfb5d53fd4d557.gif" alt="" title="数据流演示图">
|
||||
|
||||
我们可以将用户的请求想象成我们的呼吸系统,我们的肺就是SFF,而微服务和FaaS节点就是需要氧气的各个器官。我们吸一口气,氧气进入肺部,血液循环将氧气按顺序流经我们每个器官,这就是请求链路。每个器官一接收到新鲜血液,就会吸取氧气返回二氧化碳,最终血液循环将二氧化碳带到肺部呼出,这个就是数据返回链路。我们的各个器官,就被请求链路通过新鲜血液到来的这个事件串联起来了,这个就是事件流,也就是用一个个事件去串联FaaS或微服务。
|
||||
|
||||
现在我们用[[第3课]](https://time.geekbang.org/column/article/227454) 讲的,PHP发邮件改造一下,举个例子。当用户注册时,我们完全可以将用户的信息和注册验证码存入数据库;PHP发邮件的FaaS触发器改为数据库插入新记录触发事件;用户从邮箱验证获取验证码,把验证码写到输入框后,点击验证,则是另一个HTTP触发器,触发FaaS函数校验验证码通过,修改数据库注册成功,并且返回302跳转到登录成功页面。具体流程可参考下图:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/71/38/712127c53b9ba90fe69cf00179862338.png" alt="" title="案例流程图">
|
||||
|
||||
当然现在这个解决方案也有成熟对应的云BaaS服务:Serverless工作流[3]。
|
||||
|
||||
## 安全门神
|
||||
|
||||
理解了拆合的思想,我们就可以将目前“待办项目”的架构再演进一下:静态文件我们用CDN托管,前端项目只负责域名支撑和index.html,剩下的请求直接访问FaaS微服务。这时候,我估计你会问,咱们数据的安全性如何保障呢?是的,到目前为止,我们的FaaS都一直在用匿名模式访问,完全没有任何安全防护可言,也就是说目前我们FaaS服务的接口一直都在互联网上“裸奔”。
|
||||
|
||||
### 鉴权
|
||||
|
||||
其实,FaaS提供的安全防护通常是放在触发器上的。触发器的授权类型或认证方式我们可以设置为:匿名anonymous或函数function。匿名方式就是不需要签名认证,匿名的用户也能访问;而函数方式,则是需要签名认证[4],这个签名认证的算法,参数需要用到我们账户的访问秘钥ak/sk[5],ak/sk相当于我们云账户的银行卡密码,这么重要的账户信息,我们只能限定在服务端使用,前端代码里绝对不可以出现。
|
||||
|
||||
也就是说,我们只能在服务端使用函数安全认证方式。如果是这种方案,我们的“待办任务”架构就演进成下图这样了。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/1f/b3/1f30750203dddab945fb1ff471e40ab3.png" alt="" title="“待办任务”架构图">
|
||||
|
||||
那有没有针对匿名认证方式的安全策略呢?当然有,这里我们同样需要借鉴一下微服务的鉴权设计:JSON Web Token,简称JWT[6]。JWT简单来说,就是将用户身份信息和签名信息,一起传到客户端去了。用户在请求数据时,带上这个JWT,服务端通过校验签名确定用户身份。JWT存在于客户端,JWT验证只需要通过服务端的sk和算法验证签名。同样,我画了张图,以帮助你理解。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f1/f1/f187db1d21b27d58c55000272f4825f1.png" alt="" title="JWT示意图">
|
||||
|
||||
要解决后端互调的安全性,我们用VPC或IP白名单,都很容易解决。比较难处理的是前后端的信任问题,JWT正好就提供了一种信任链的解决思路。当然,关于鉴权也有一些云服务商推出了一些更加安全易用的BaaS服务,例如AWS的IAM和Cognito[7]。
|
||||
|
||||
安全性是我们考虑架构设计时重要的一环,因为安全架构设计的失败,会直接导致我们资产的损失。鉴权是识别用户身份,防止用户信息泄漏和恶意攻击使用的。但根据我统计的数据,我们在日常99%的问题,都发生在新版本上线的环节。
|
||||
|
||||
那我们该怎么稳定持续地快速迭代,发布新版本上线呢?我们可以回想一下[[第 1 课]](https://time.geekbang.org/column/article/224559),小程和小服的例子,小程最后实现NoOps后,小服则只要将代码合并到指定分支就可以发布上线了。那现实中,这点该怎么实现呢?
|
||||
|
||||
当我们的项目Serverless化以后,代码的质量变得尤为重要。你可以想想,Serverless化之前,你不小心上线了一个bug,影响的范围最大也就只有一个应用。但是Serverless化之后,如果是核心节点发布了严重的bug上线,那么影响的范围就是所有依赖它的线上应用了。
|
||||
|
||||
不过,你也不用太担心,微服务和FaaS都具备快速独立迭代的能力。以前我们一个应用的迭代周期通常要一周到两周。但对于Serverless化后的应用来说,每个节点借助独立运维的特性,可以随时随地的发布上线。
|
||||
|
||||
综上,我们知道了,微服务和FaaS都是快速迭代的,修复问题很快,但我们也不能每次都等问题出现,再去依赖这个能力呀。有没有什么办法可以提前发现问题,保证我们既快又稳?目前软件工程的最佳做法就是代码流水线的发布管道。
|
||||
|
||||
### 发布管道
|
||||
|
||||
发布管道的流水线主要有3个部分:
|
||||
|
||||
1. 代码发布前的验证,代码测试覆盖率CI/CD;
|
||||
1. 模拟流量回归测试通过,发布到灰度环境;
|
||||
1. 代码正式上线,灰度环境替换正式环境。流水线的每个节点产生的结果,都会作为下一个节点必要的启始参数。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/8e/4b/8e3a2a76cfcf51d2cc2eafaaf0a2db4b.png" alt="" title="发布管道流水线">
|
||||
|
||||
我们先看看上图,我来解释下这个流程。
|
||||
|
||||
- 我们的代码合并到指定分支后,通常我会用Develop分支。
|
||||
- Git的钩子就会触发后续的流水线,开始进入构建打包、测试流程。
|
||||
- 测试节点做的事情就是跑所有测试Case,并且统计覆盖率。
|
||||
- 覆盖率验证通过,代码实例用录制流量模拟验证。
|
||||
- 模拟验证通过,发布代码实例到灰度环境。
|
||||
- 线上根据灰度策略,将小部分流量导入灰度环境验证灰度版本。
|
||||
- 在灰度窗口期,比如两个小时,灰度验证没有异常则用灰度版本替换正式版本;反之则立即丢弃这个灰度版本,止损。
|
||||
|
||||
这套流程,目前规模大一些的互联网公司发布流程基本都在这样跑,如果你不是很了解,可以自己尝试用我们介绍的Serverless工作流或者云服务商提供的工作流工具[8]动手搭建下。
|
||||
|
||||
在这套流程的基础上,很多企业为了追求更高的稳定性,还会设定环境隔离的流水线和安全卡口。比如隔离测试环境和线上环境,测试环境用来复现故障。每次代码进入发布管道,都必须先在测试环境跑通,跑通后安全卡口放行,才能进入线上环境的流水线。
|
||||
|
||||
## 总结
|
||||
|
||||
这节课,我们继续讲后端的BaaS化。我们再梳理一下这节课的重要知识点吧。
|
||||
|
||||
1. 如何拆解BaaS应用,我们学习了微服务的重要拆解思想DDD:**通过对业务分层抽象,分析定义出领域模型,用领域模型驱动我们设计系统,最终将复杂的业务模型拆解为独立运维的领域模型。**另外我也介绍了另一种更适合初创企业的拆分思路:动态网络演进。
|
||||
1. 拆解完之后,我们就要考虑合并。这里我们介绍了代码编排以外的另一种编排方式:事件流编排,它就是通过一个个事件顺序将我们的微服务或FaaS串联起来。
|
||||
1. 为了解决拆解后,微服务之间的信任问题。我们先了解了FaaS触发器的安全方案:数字签名。还借鉴了微服务的鉴权做法JWT,将用户鉴权加密信息放在客户端,让鉴权服务变成Stateless。最后,为了让微服务又快又稳地发布版本,我们借鉴了微服务的发布管道:打造自动灰度流水线。
|
||||
|
||||
## 作业
|
||||
|
||||
这节课的作业就是我们JWT鉴权的“待办任务”Web应用,你来部署上线。
|
||||
|
||||
后端代码GitHub地址:[https://github.com/pusongyang/todolist-backend/tree/lesson06](https://github.com/pusongyang/todolist-backend/tree/lesson06)
|
||||
|
||||
前端代码GitHub地址:[https://github.com/pusongyang/todolist-frontend](https://github.com/pusongyang/todolist-frontend)
|
||||
|
||||
演示预览地址:[http://lesson6.jike-serverless.online/list](http://lesson6.jike-serverless.online/list)
|
||||
|
||||
期待你的作业,如果今天的内容让你有所收获,也欢迎你把文章分享给身边的朋友,邀请他加入学习。
|
||||
|
||||
## 参考资料
|
||||
|
||||
[1] [https://en.wikipedia.org/wiki/Domain-driven_design](https://en.wikipedia.org/wiki/Domain-driven_design)
|
||||
|
||||
[2] [https://en.wikipedia.org/wiki/Dynamic_network_analysis](https://en.wikipedia.org/wiki/Dynamic_network_analysis)
|
||||
|
||||
[3] [https://www.aliyun.com/product/fnf](https://www.aliyun.com/product/fnf)
|
||||
|
||||
[4] [https://github.com/aliyun/fc-nodejs-sdk/blob/master/lib/client.js?spm=a2c4g.11186623.2.15.16e016d7lo8NBQ#L840](https://github.com/aliyun/fc-nodejs-sdk/blob/master/lib/client.js?spm=a2c4g.11186623.2.15.16e016d7lo8NBQ#L840)
|
||||
|
||||
[5] [https://help.aliyun.com/document_detail/154851.html?spm=5176.2020520153.0.0.371a415dLXyltZ](https://help.aliyun.com/document_detail/154851.html?spm=5176.2020520153.0.0.371a415dLXyltZ)
|
||||
|
||||
[6] [https://jwt.io/](https://jwt.io/)
|
||||
|
||||
[7] [https://docs.aws.amazon.com/zh_cn/cognito/latest/developerguide/what-is-amazon-cognito.html](https://docs.aws.amazon.com/zh_cn/cognito/latest/developerguide/what-is-amazon-cognito.html)
|
||||
|
||||
[8] [https://www.aliyun.com/product/yunxiao/devops?spm=5176.10695662.1173276.1.6c724a38akCjgo](https://www.aliyun.com/product/yunxiao/devops?spm=5176.10695662.1173276.1.6c724a38akCjgo)
|
||||
201
极客时间专栏/Serverless入门课/进阶篇/07 | 后端BaaS化(下):Container Serverless.md
Normal file
201
极客时间专栏/Serverless入门课/进阶篇/07 | 后端BaaS化(下):Container Serverless.md
Normal file
@@ -0,0 +1,201 @@
|
||||
<audio id="audio" title="07 | 后端BaaS化(下):Container Serverless" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/fc/31/fc3b6496365e041c16f88119b661ba31.mp3"></audio>
|
||||
|
||||
你好,我是秦粤。上节课,我重点给你讲了业务逻辑的拆和合,拆的话可以借助DDD的方法论,也可以用动态网络的思想让微服务自然演进;合的话,我们可以用代码编排,也可以用事件流来驱动。另外,我们还了解了微服务拆解后会带来的安全信任问题,这可以通过微服务的跨域认证JWT方案解决。我们还了解了后端应用要支持快速迭代或发布,可以参考微服务搭建灰度发布流水线,也就是发布管道。其实我们在使用FaaS过程中遇到的很多问题,都可以借助或参考微服务的解决方案。
|
||||
|
||||
现在我们再回顾一下BaaS化后的“待办任务”Web服务,我们已经将后端拆解为用户微服务和待办任务微服务,前端用户访问我们的FaaS服务,登录后获取到JWT,通过数据接口+JWT安全地访问我们的微服务。而且我们的FaaS和微服务都具备了快速迭代的能力。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/c8/15/c8ee82521e5c965d8955afe8c210b615.jpg" alt="" title="BaaS化后的“待办任务”Web服务">
|
||||
|
||||
到这里,我要指出我之前rule-faas.js的一个Bug,如果你之前亲自动手做过实验的话,估计也有发现。这个Bug的直接表现是用户初次请求数据时,如果触发了冷启动,返回的待办任务列表就会为空。
|
||||
|
||||
究其原因,是因为冷启动时连接数据库会有延时,这直接导致了第一个请求返回的待办任务列表还未同步到消息队列的数据。要解决这个bug,我们可以用之前讲过的预热FaaS或预留实例的方式,但你也要知道,FaaS函数扩容时,新启动的函数副本也会出现同样的问题。
|
||||
|
||||
我前面卖了很多关子,其实FaaS在设计时已经考虑到这个问题了,所以在FaaS的“函数配置”里,都会提供一项“函数初始化入口”的选项。但你会发现,它同时也会让你配置初始化时间,最少1秒。还记得我们在[[第 2 课]](https://time.geekbang.org/column/article/226574),讲函数执行阶段的一人一半的函数代码初始化时间吗?没错,就是那个时间。
|
||||
|
||||
当你配置了“函数初始化入口”,每次启动FaaS实例时,系统都会先等待初始化函数执行,而且在函数初始化时间结束后,才会继续执行函数。而从目前平台的配置来看,初始化时至少也需要1秒的时间(你去云服务商后台就能看到)。
|
||||
|
||||
对很多事件触发的应用场景,FaaS增加几秒的初始化时间,影响并不大。但对很多后端应用则不然,尤其是Java等语言,如果软件包比较大,启动和初始化的时间会更长。
|
||||
|
||||
要缩短或绕过初始化的时间,我们要尽可能地利用Runtime里面给我们提供的内置能力,例如我们BaaS化一直提倡使用服务编排和HTTP接口,就是因为云服务商SDK和HTTP协议,所有语言的Runtime里都内置了。
|
||||
|
||||
那什么是Runtime呢?Runtime其实就是运行我们代码所需要的函数库和二进制包。FaaS中的Runtime是云服务商预先设计好的,会放一些常用的函数库和二进制包进去,相当于是平台的能力。
|
||||
|
||||
但是当我们后端应用BaaS化后,想采用FaaS方案部署的话则会碰到Runtime这个拦路虎。FaaS虽然已经支持多数主流后端语言,但后端应用根据业务需求,要依赖特殊的函数库或二进制包,FaaS的官方Runtime就无法支持。而且像Java等语言,在代码包较大的情况下,FaaS启动速度很慢,也不适合。例如Node.js的Runtime,其实也包括我们自己安装的NPM包,所以相当于我们可以部分定制。但是你也注意到了,我们是整个目录包括node_modules 一起上传的,也就是说涉及编译的NPM包是无法跨操作系统生效的。这种场景下FaaS的Runtime不可控就会成为我们难以绕过的问题了。当我们遇到FaaS无法解决的场景,我们就可以考虑下降一层,使用FaaS的底层支撑技术Docker容器了。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/09/d7/09bb10fa7d1a95f88828bc413c7c9bd7.png" alt="" title="广义Serverless">
|
||||
|
||||
还记得我们[[第 1 课]](https://time.geekbang.org/column/article/224559) 中的广义Serverless的图吗?基础设施中的容器,一般情况下指的就是Docker容器。Docker 这个技术你肯定或多或少已经用过了,使用它们可以将应用代码和代码依赖的Runtime一起打包成一个Docker镜像。这个Docker镜像,可以在云上、自己的笔记本电脑、同事的电脑上无畅运行,并且完全不用担心环境依赖的问题,因为我们应用的依赖也打包在一起了。
|
||||
|
||||
到这里,我们又引入了Docker的概念,Docker出现之后,CaaS(Container as a Service)很快也就流行起来了。为了帮助你理解IaaS、PaaS、BaaS、CaaS这几者的关系,我画了张图,希望能从云服务发展的角度,帮你梳理下脉络。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f0/3e/f0d74468ff49c43c9367fff9cc58833e.png" alt="" title="云服务发展进程">
|
||||
|
||||
图片很好理解,我就不解释了。不过这里要解决一个你可能会有的疑惑:为什么BaaS的出现,比Serverless FaaS还要早三年?那是因为早期出现的BaaS其实是mBaaS(移动后端即服务),这概念当时也曾经火过一段时间,现在已经被Google收购的FireBase其实就是做的这个生意。mBaaS设计之初是专门为移动端提供后端服务的,例如用户管理、角色服务、文件存储服务等等。除了服务对象是移动端之外,跟我们说的BaaS概念很像。移动端可以将BaaS的所需鉴权算法放到客户端中,并随着客户端的版本定期更新秘钥。但前端却做不到,因此mBaaS只局限在移动端,没有火起来。直到FaaS的出现,才诞生了BaaS的使用场景,mBaaS也开始转型,支持更广范围的前端场景了。
|
||||
|
||||
VM是一种虚拟化技术,这个我们都知道,其实Docker也是一种虚拟化技术,只不过它是利用新版Linux内核提供Namespace、Cgroups和Union File System特性,模拟操作系统的运行环境。它跟虚拟机VM最大的区别在于,Docker是进程模型的。怎么理解这句话呢?我们需要画一张图。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/2f/18/2f941369b366fff8883499d200cccb18.png" alt="" title="Docker进程模型">
|
||||
|
||||
从图中我们可以看出,虚拟机是在宿主机上启动一个虚拟机监视器HyperVisor管理控制虚拟机的。而虚拟机自身包含整个客户操作系统、函数库依赖和用户的应用,虚拟机操作系统之间相互都是隔离的。经典的HyperVisor就是VirtualBox[1],你感兴趣的话可以下载体验一下。你也可以试想一下,如果我们我们一台虚拟机部署一个微服务,其实资源利用率是很低的。
|
||||
|
||||
容器实例则只包含函数库依赖和用户的应用,操作系统是依赖宿主机的用户态模拟的,也就是说容器之间是共享宿主机内核的。所以Docker更加轻量,启动速度更快,代码执行速度也更快。
|
||||
|
||||
Docker的容器呢,因为只包含函数库依赖和用户的应用,可以部署到任意Docker引擎上,而Docker引擎呢,可以安装在你的个人电脑、公司机房或者云上。通常我们容器移动时,是移动容器的硬盘快照,内存中的状态我们比较难复制,这个硬盘快照就是Docker镜像。我们给Docker的镜像打上标签,标签就像是镜像的唯一标识符URI,打上后它就可以到处移动了。这个也是Docker的核心概念:build、ship、run。
|
||||
|
||||
其实你会不会觉得,Docker的这个层级结构有些眼熟?是的,这正是我们[[第 2 课]](https://time.geekbang.org/column/article/226574) 中讲的FaaS分层。我们回想一下,我当时所说的操作系统容器就是Docker模拟的,Runtime其实就是Bins/Libs层。云服务商的冷启动加速,就是利用Docker镜像缓存加速。我想你也应该猜到了,其实很多云服务商FaaS和PaaS的底层技术就是容器即服务CaaS。
|
||||
|
||||
那么接下来,我们就体验一下Docker的便利吧。我们的“待办任务”Web服务,只要添加一个文件,就可以让它变成Docker镜像了。
|
||||
|
||||
## Docker再探
|
||||
|
||||
这里我建议你,最好自己在电脑上安装体验一下Docker。在主流的操作系统数安装Docker都不难的,只需要下载安装包就可以了。
|
||||
|
||||
### build: Dockerfile
|
||||
|
||||
构建是Docker最重要的一环。Docker镜像是硬盘快照,那我们就看看标准的Docker硬盘快照如何制作吧。下面就是我们在代码根目录下增加的文件,供你参考:
|
||||
|
||||
```
|
||||
# FROM是指我们的镜像基于哪个镜像来构建,我们这里是基于jessie linux的Node.js镜像
|
||||
FROM registry.cn-shanghai.aliyuncs.com/jike-serverless/nodejs
|
||||
# ENV是设置环境变量
|
||||
ENV HOME /home/myhome/myapp
|
||||
# RUN是执行一段命令
|
||||
RUN mkdir -p /home/myhome/myapp/public
|
||||
# COPY是将当前目录下的内容拷贝到容器中
|
||||
COPY . /home/myhome/myapp
|
||||
COPY public /home/myhome/myapp/public/
|
||||
COPY node_modules /home/myhome/myapp/node_modules/
|
||||
# WORKDIR是设置进入容器后的工作目录
|
||||
WORKDIR /home/myhome/myapp/
|
||||
# ENTRYPOINT是容器启动后执行的脚本
|
||||
ENTRYPOINT [ "node","index.js" ]
|
||||
|
||||
```
|
||||
|
||||
通常我们使用Docker前,先去Docker Hub上,找合适的基础镜像。看看基础镜像上都安装了哪些函数库或二进制包,再对比一下要运行自己的应用还缺少哪些函数库和二进制包。在基础镜像的层级上,加上自己的依赖。这样我们就构建好适合自己的镜像了。以后我们就能FROM自己的基础镜像,构建自己的应用了。
|
||||
|
||||
然后你在项目下执行:`docker build` 命令,就可以帮你构建Docker镜像了。
|
||||
|
||||
```
|
||||
docker build --force-rm -t myapp -f Dockerfile .
|
||||
|
||||
```
|
||||
|
||||
构建好的镜像,我们可以通过 `docker run` 这个命令在本地调试。
|
||||
|
||||
```
|
||||
docker run -d -p 3001:3001 -e TEST=abc myapp:latest
|
||||
|
||||
```
|
||||
|
||||
然后我们通过浏览器访问[http://127.0.0.1:3001](http://127.0.0.1:3001),就可以看到我们刚刚构建的Docker内容了。你会发现,这不就是我们云上运行的版本吗?是的,既然FaaS是用CaaS技术实现的。我们当然也可以利用Docker实现我们的“待办任务”Web服务。
|
||||
|
||||
为了方便Docker例子的展示,这节课的项目代码index.js,包含了rule.js的逻辑。你会发现index.js中,我们启动的时候,不用关心初始化需要等待多少秒了,而是直到初始化完成后,才监听3001端口,而Node.js连接数据库的时间通常也只需要几十毫秒。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/9f/c4/9f7572a1ee36d167d30940f08d96ccc4.png" alt="" title="Docker版本的“待办任务”Web服务">
|
||||
|
||||
另外为了方面你观察和调试Docker容器实例,我这里给出一个登录Docker容器实例的命令。
|
||||
|
||||
```
|
||||
docker exec -it 容器ID bash
|
||||
|
||||
```
|
||||
|
||||
### ship: Docker Registry
|
||||
|
||||
本地调试完,我们再看看如何部署到云上。Docker官方的镜像仓库[2]速度太慢,现在的云服务都支持私有的容器镜像仓库[3],上面构建Dockerfile里面的Node.js镜像,就是用我自己的私有容器仓库搭建的。
|
||||
|
||||
你可以登录云服务的容器镜像服务,他们都会告诉你如何 `Docker login` 到私有Registry,如何打标签,以及如何上传。
|
||||
|
||||
用我们的例子来说,大致会有下面的命令。
|
||||
|
||||
未登录过的话,要先登录Registry。
|
||||
|
||||
```
|
||||
docker login --username=极客时间serverless registry.cn-shanghai.aliyuncs.com
|
||||
|
||||
```
|
||||
|
||||
根据容器镜像仓库的URI,打标签。镜像ID可以通过 `docker images` 查看。
|
||||
|
||||
```
|
||||
docker tag 你本地的镜像ID registry.cn-shanghai.aliyuncs.com/jike-serverless/todolist
|
||||
|
||||
```
|
||||
|
||||
上传镜像到私有的Registry仓库。
|
||||
|
||||
```
|
||||
docker push registry.cn-shanghai.aliyuncs.com/jike-serverless/todolist
|
||||
|
||||
```
|
||||
|
||||
### run: Docker Engine
|
||||
|
||||
运行Docker镜像就很简单了,云服务都支持容器服务CaaS。你只需要购买好容器服务,就可以从自己私有的容器镜像仓库Docker Registry中下载镜像,并运行了。你要是执行过上面的例子,那相信你也能体会到为什么FaaS的冷启动速度这么快了。不过需要提醒你一下,这里会产生费用。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/b0/32/b0f0839d924252943ab9cd67df923b32.png" alt="" title="运行Docker镜像示意图">
|
||||
|
||||
另外,我们的专栏是讲Serverless,但是为了让你理解Serverless底层的实现,所以我们也讲到了Docker容器的部分内容。对于我们专栏来说,你不用尝试部署云上容器了。
|
||||
|
||||
恭喜你,获得DevOps奖章一枚!
|
||||
|
||||
现在我们了解了容器,也知道了FaaS是构建在容器上的。我们的微服务和整个应用都可以部署在CaaS之上,容器对我们来说更加可控,因为我们可以通过Dockerfile安装我们需要的各种函数库依赖或者二进制文件。但这里还有一个问题,FaaS的扩缩容是怎么做到的呢?我们如果采用了容器,容器如何做到扩缩容呢?
|
||||
|
||||
## FaaS与Docker的扩缩容
|
||||
|
||||
FaaS中的扩缩容,我们可以通过云控制台看到在FaaS函数配置中的“单实例并发度”。FaaS的做法比较简单粗暴,需要我们告诉函数服务,我们的单个函数实例可以承载多少的并发量,如果事件触发并发量超出了这个并发度,则会自动扩容。扩容的数量N,就是这个事件触发量除以单机并发量取整。例如我们设定,我们rule-faas函数的单实例并发度为10,那么当用户并发量是11时就会扩容2个实例。如果用户并发量达到100,就会扩容出10个实例。
|
||||
|
||||
这种做法其实是比较机械式的,我们再看看FaaS的“函数指标”监控图。你有没有想到,我们其实可以利用实时监控,去控制扩缩容?例如当单个函数实例承受不了时,内存使用率会越来越高,它的执行时间也会越来越长。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/5e/2c/5e4d42ca0b4f27dbf04d91c31fa8f62c.png" alt="" title="FaaS的“函数指标”监控图">
|
||||
|
||||
是的,我们在使用Docker时,要考虑的就是:监控指标metrics以及扩容水位。
|
||||
|
||||
监控指标metrics就像FaaS“函数指标”监控图那样,是一系列需要我们关心的单个容器核心指标,包括CPU利用率、内存使用率、硬盘使用率、网络连接数等等。
|
||||
|
||||
我们将监控指标中的各项数值想象成蓄水,我们监控就像一个蓄水池,一旦某一项超过了蓄水池,水就会溢出。所以,我们要设定**水位**告警。我们在蓄水池里面画上刻度,当水位到这个刻度,我们就马上给这个蓄水池扩容。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ab/ba/abf169adcc352cc0c37c6e66b710c6ba.png" alt="" title="蓄水池案例">
|
||||
|
||||
Docker容器本身并不提供扩缩容机制,但我们要让Docker自动化扩缩容,就可以用监控指标和水位去设计扩缩容机制。我们需要实时监控容器状态,当容器状态的某一项数值过高时,我们就需要给容器扩容。FaaS的弹性做法很简洁,而Docker的扩缩容机制弹性更高、更加可控。但是Docker容器,通常需要我们至少保持1个容器实例在线。相信你也知道了FaaS的预留实例是怎么做到的了吧?
|
||||
|
||||
讲到这里,不知道你发现没有,我们BaaS化的三讲,已经默默地实现了微服务的10要素。这也是为什么我一直说BaaS化和微服务高度重叠。
|
||||
|
||||
我们再来回顾一下微服务10要素。其中,API、服务调用、服务发现,我们可以通过RESTful HTTP接口实现;日志、链路追踪,我们没有展开,但我们可以依赖云服务商提供的日志采集解决;鉴权我们可以用跨域认证JWT解决;发布管道,需要我们搭建流水线发布管道;容灾性、监控、扩缩容,我们可以通过实时监控和扩缩容解决。到这儿呢,我们的高铁车厢也终于完成了。
|
||||
|
||||
这节课的内容挺多的,需要你动手消化吸收一下。下节课我们再看看,如何利用K8s调度我们的Docker容器。
|
||||
|
||||
## 总结
|
||||
|
||||
BaaS化的内容,到今天,我们用了三节课讲完了,现在我们一起回顾一下。
|
||||
|
||||
我们讲后端应用BaaS化的问题,转换为后端应用NoOps微服务。所以我们先了解了微服务的10要素,并且看到微服务中如何通过消息队列将Stateful的节点,变成Stateless的。在微服务的拆解过程中,我们学习了微服务的拆解思想DDD和动态网络演进,以及拆解后带来的信任问题,需要我们加密身份认证。合并时,除了代码里面编排HTTP请求结果,我们还学习了事件流触发获取结果。为了让微服务能够快速迭代,我们还需要自己搭建流水线发布管道。
|
||||
|
||||
这节课我们通过FaaS和BaaS的初始化问题,向你介绍了FaaS和BaaS依赖的底层实现容器Docker。Docker也是一种虚拟化技术,它的核心理念是:build、ship、run。通过将我们的应用代码和应用依赖的函数库、二进制包,打包在一起的方式,解决应用开发环境和运行环境的一致性问题。我们可以借助Docker容器维持我们的应用实例,来解决初始化慢的问题。
|
||||
|
||||
我们还了解了FaaS的扩缩容逻辑是通过用户配置的“函数初始化入口”,以及固定的“初始化超时时间”配合“单实例并发度”来实现的。而我们在容器Docker,其实可以采用实时监控配合扩容水位,来做到更加弹性的扩缩容策略。
|
||||
|
||||
接下来我会通过K8s实践我们目前所学到的内容,我们的“待办任务”Web服务,将在我们本地搭建的CaaS环境和Serverless环境中开发和调试。
|
||||
|
||||
## 作业
|
||||
|
||||
首先,拉取我们[lesson07](https://github.com/pusongyang/todolist-backend/tree/lesson07)的代码,为“待办任务”部署的rule-faas函数添加初始化入口。
|
||||
|
||||
这节课的作业呢,就是我们要在本地完全通过Docker容器,搭建起我们的“待办任务”Web服务。除了css和js静态资源是来自CDN,其它内容都将运行在Docker容器里。
|
||||
|
||||
相信你可以通过这个作业,体验到FaaS的底层CaaS的运行机制。
|
||||
|
||||
当然如果你有预算,也可以将Docker镜像上传到云服务商的Registry,在云上购买容器服务就可以部署你的Docker镜像,并在云上运行我们的“待办任务”Docker版本了。这样你就拥有了一个永不停机的Docker服务。
|
||||
|
||||
另外,我也希望你可以帮助我继续优化我们的课程作业代码。如果你有更好的建议,也可以通过Github的MergeRequest告知我。
|
||||
|
||||
接下来就期待你的作业和建议了。如果今天的内容让你有所收获,也欢迎你把文章分享给身边的朋友,邀请他加入学习。
|
||||
|
||||
## 参考资料
|
||||
|
||||
[1] [https://www.virtualbox.org/](https://www.virtualbox.org/)
|
||||
|
||||
[2] [https://docs.docker.com/get-docker/](https://docs.docker.com/get-docker/)
|
||||
|
||||
[3] [https://hub.docker.com/](https://hub.docker.com/)
|
||||
Reference in New Issue
Block a user