CategoryResourceRepost/极客时间专栏/Serverless入门课/进阶篇/05 | 后端BaaS化(上):NoOps的微服务.md
louzefeng d3828a7aee mod
2024-07-11 05:50:32 +00:00

158 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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下载我们的静态资源完成页面静态资源的加载与此同时浏览器也向前端应用发起数据请求前端应用经过安全签名后再将数据请求发送给SFFSFF再根据数据请求调用后端接口或服务最终将结果编排后返回。
从图里你可以看到除了数据库是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. 快速迭代&amp;试错能力多版本控制灰度CI&amp;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写操作我们都写入RocketMQrule.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 = &quot;https://rule.cn-shanghai.ots.aliyuncs.com&quot;;
// AccessKey 阿里云身份验证,在阿里云服务器管理控制台创建
const accessKeyId = &quot;AccessKey&quot;;
// SecretKey 阿里云身份验证,在阿里云服务器管理控制台创建
const accessKeySecret = &quot;SecretKey&quot;;
// 在数据链表中查看
const instancename = &quot;rule&quot;;
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)