我们继续讲微服务的一些设计
微服务设计里面其实会提到
好几个重要的角色像API Gateway
然后我们的BFF层
然后包括底层的Microservice
这几块也是在讲
强调一次为什么要先讲微服务
比方说我们在做API Gateway的时候
它在做BFF的时候
它里面会涉及到就是一个流量进来之后
它会上出很多个请求
并行地去访问底层的一些RPC服务
这个其实就是涉及到我们叫fan out
就是它一个请求进来
然后要上出到下游好几个微服务
然后它们会做一些数据的组装
这里面其实就涉及到
我如何在Go里面去做一个
比较好用的一种
所谓的并行编程的一种模型
因为你其实就是要
类似有点像开了好多个goroutine
然后并发地去运行了很多这种RPC的一些代码
然后做一些数据组装
那就你需要其中一个失败怎么退出
然后处理一些局部的失败等等
这些其实都是包括我们在做网关的时候
如何去做API的设计
Error的一些处理
因为你的代码其实是从
最网关的一些流量进到我们具体的某一层
然后包括业务逻辑层到数据访问层Data层等等
这些其实就是包括异常处理
包括API设计
包括我们的并行编程
其实为什么要讲微服务
你有了一些轮廓以后
你再去想里面具体的Go的代码怎么去填进去
所以它是非常关键和重要的
我们就要开始微服务设计这一课了
我们先看一下
我们第一个要讲的就是API Gateway
它是怎么一种方式去演进过来的
就是我们刚刚讲的
随着这种微服务的一个演进
就是SOA的服务化的一个架构演进
我们拿到一个巨石架构以后
我们其实最早就会开始想
我如何去进行一个
这种巨石到微服务的架构的一个划分
就是功能的一个拆分
我们当时拿到这个代码以后
我们一开始其实就是按照
所谓的垂直功能去进行拆分
大家也比较容易想到我拿到一大坨代码
先把账号系统搬出去
然后再把所谓的外围什么评论弹幕这些搬出去
然后把投稿视频类的一些搬出去
逐渐逐渐一块一块按照垂直功能进行拆分
对外我们拆分完之后
其实一开始没有太多的经验
我们对外我们其实是每一个拆出去的服务
它们都直接对外的
也就是说对外暴露了一批微服务
因为缺乏统一的出口业
我们的整个出口非常多
也就是说每一个微服务它都直接对外了
我们看一下右边这张图
就像你的外网的浏览器播放一个视频的时候
它这个流量经过我们内网的一个Web Server
它可能是个类似像我们叫Nginx
我们一般企业内部
一般会叫SLB
或者叫ELB
通过这样的一个7层的
一个入口的Web Server
然后再转发流量到我们下游的
比方说用户的微服务
稿件的微服务推荐或者是广告
但是这些这一层是微服务层
但是这样的一种模型
因为它缺乏统一的对外的一个出口
它会导致哪些问题
第一就是说我们客户端
客户端指的是我们的Web端移动端
它到某一个微服务
它是直接通信了
导致强耦合
看上去没什么问题
随着时间的推移
你的这些对外暴露出去的微服务
是不是随着我们应用的迭代
你的历史的API都要保留
因为我们知道微服务你在演进过程中
你的业务也在演进
那业务演进过程中你的
比方我们的过去的一个APP的版本
有些人点开看到APP store有更新
APP store不是会显示个数字
1234有应用升级吗
它就是不升
有些人近来一年都不升
我们发布新版本的应用以后
比如在我们APP store里面
就是有用户一年前的版本还在使用
他就是不升级
我是那种基本上看到APP store有更新
我要必点
因为我看到那些有提示我都会去点掉的
但是用户来说它未必每个人都会这样
就是立马升级
包括我们安卓也是一样的
所以因为它用的是过去的版本
也就是说只要有流量
有用户在使用过去的APP的应用
那么意味着你这个微服务老的接口就下不了
下不了
你每次在迭代和升级
用户服务稿件服务推荐服务
你都会带来你的升级的一个困难性
因为它直接强依赖通信到你了
这个其实是给我们的后续
微服务的一个演进和升级
其实是带来一定的困难的
你可以想象得到的就是因为
你处理的大量的这种老接口放在这个代码里面
你肯定要每次发版还要心惊胆战
因为对外网用户是有一些影响的
这个是第一个点
第二个点是什么呢
我们一个页面
比方说我们打开B站的首页
或者你打开抖音首页
打开淘宝首页
它的首页都具备很多功能
举个例子来说你打开像我们B站首页
它需要请求各种各样的微服务来构建完整的数据
比方说你可能去
它会请求AI的推荐服务
请求广告服务
然后再请求直播的服务
再请求我们稿件信息的一些服务
还有请求用户的一些服务
那么这些数据做了各种组装以后
你才能完整展现出这个页面
但是如果你让这个工作的访问
让这个工作的对接
是让移动的同学一个一个
找到后端的不同的服务的owner
那么是不是意味着它的对接成本是非常高
首先它要找不同团队的人
另外就是说它既然要一个个的接口去请求
一个个接口去请求
那么这个数据的组装的工作是在谁这做
是不是在移动端的这个地方去做的
那么客户端聚合这些数据
第一它的工作量很巨大
因为我们知道有一个点是一定要跟大家强调的
我们做所有的这种互联网的一些业务开发
那么我们为了提升我们的应用的交付速度
通常是前轻后重
让我们的移动端的同学
做更少量的轻量的一些工作
它更多是面向交互面向流程的
这种交互的一些场景
不应该把这种复杂的数据逻辑
让我们移动端来做
因为它如果在做很复杂的逻辑
它肯定会影响到我们应用的交付速度
我们比方说很多互联网公司是每周一迭代
每周就要提交一个版本到我们的APP store
到我们的安卓市场
如果你的客户端
做越多这种数据的一些逻辑工作的话
意味着它的交付速度就会受到影响
工作量巨大
同时我们在这个页面上
要做大量的数据组装工作
是不是请求这么多接口
难道你让移动端的同学
来并行访问这么多接口
做很多局部
有些接口可能还会失败
它要做大量的一些容错
然后要考虑它的一些并发访问这么多接口
这些其实也会引入一些技术的一些复杂度
这个其实是不太好的
第三是什么呢
这么多服务很有可能是由不同的业务部门来提供的
那么部门之间的接口
我们虽然一般像公司都会提供
一些所谓的API最佳实践等等
但是你要强约束每个人的接口长得很像
或者长得很像
我觉得是有难度的
从实际情况我们出发的话
真的每个部门的接口规范
的的的确确是有些差异的
这样的话会导致我们移动端的对接工作
它的协议不利于统一
因为每个部门间差异太大
那么都需要端上的同事
来做协议的一些兼容
A部门是长这样
B部门长另外一个样
那么这个对接工作也复杂
第四点是什么
你的每一个微服务如果都这样直接对外的话
意味着我们每一个这些微服务
都需要考虑面向不同的
移动端web端来做API的适配和兼容
比方说iPad版
然后安卓版iOS版本它每一些API
因为我们知道安卓iOS
它可能交互上是有些区分的
那么你要面向不同的终端
来做这种API的适配的话
它其实也是非常复杂的
另外就是说有可能在2G3G4G5G网络
各种情况下
它的API也是会有一些区别的
那么大量的这种面向端的一些API的兼容适配
沉淀在我们的对外暴露的这些微服务里面
导致最终就是面向这种高效率的
一些后端的一些研发也会变得复杂
还有就是说我们刚刚说的就是
一个是面向端的适配
其实还有多终端的兼容的逻辑也是非常复杂的
多终端兼容逻辑指的是
一年前的版本和一年后的两个API
你都得维护新老API好多版本都要长存
那么这些兼容代码
都是沉淀在你的每一个微服务里面的
那么你要对微服务本身再去进行升级
又变得更困难了
种种原因你肯定是不能这样干
还有说很多统一的逻辑无法收敛
比方说安全认证
安全就像一个它就像一个船舱一样
有一个地方漏水它就毫无安全可言
所以说只要你的对外的暴露的这种
你对外暴露的那种
我们叫表面积越少
那么你的安全就越收敛
其实就像我们设计一个interface
我们要去做个接口抽象
你对外暴露的API越少
那么你的接口其实是越健壮
你如果暴露的API越多
你的接口就越脆弱
就越容易被破坏
那么所有的这些收敛的事情
如果有个统一的地方
统一来做安全
因为如果你微服务都对外都去做认证
有一个地方有漏洞
就全崩崩塌了
所以说最好是在这种统一的一些
面向横切面的一些编程
能够统一收敛到一个地方
比方说刚刚说安全限流
容错等等这些事情
能够统一去做是最好的
那么按照我们之前提到的微服务概览
里面提到的一些工作模型
我们应该是什么
形成一个披萨团队
尽量去内聚的模式去配合这样是最好的
那么基于种种类似的这种原因
我们早期的这种架构
它就不能满足我们当下的一个业务发展了
所以我们很快地加入了一层
当时我们新增了一个叫做
app-interface的这么一个服务
这个服务用于统一的协议出口
所以我们希望各个部门的一个接口
在我们 APP interface
然后对它数据进行加工以后
全部的按照统一的规范去对外了
这样的话移动端同学就非常开心了
因为之前各个部门的这种
长得不一样的接口都被你吞掉了
然后你暴露一种非常友好的
并且全部是统一的一种格式给到移动端
那么它对接起来非常方便
那么在这个app-interface这个代码里面
我们又做了大量的dataset join
什么叫dataset join
其实就是指有很多种各种业务微服务的数据
我们把这些数据做了很多join
因为我们最终对外提供接口应该是面向业务场景
这个时候其实又有很多后端同学跳出来
我们以前都是什么
我们都是提供HTTP RESTful的
面向资源类型
面向资源的一种接口设计
他会觉得你现在这种业务场景这么复杂
让我搞包装一个这种
包装一个接口
去给你提供
他会从他的从后端以前这种视角来看
会觉得接口很脏
它做了大量的一些事情
做了大量的逻辑
不是面向资源的
这个其实要打个问号的
我是不太建议就是说我们还是
按以前的这种思路面向资源的接口
其实面向资源的接口
它非常适合内网
但是它不太适合面向外网
因为外网它是一个复杂的多终端的一个场景
多屏多终端的一个场景
那么这种情况下
如果能让移动端最高效最快速的去完成
我们的面向用户场景的那种高效的一个研发
一定是提供统一的接口
一定是通过统一接口
并且接口是面向某一种业务场景
比方说你的首页就可能是一个接口
你的首页是一个接口
那么首页可能还有下拉
下滑或者是上拉
那你可能就是一个接口
只是参数有区别
这样的话一个页面
比方说我们又点开
找到一个想看的视频
我们点开这个视频
进入这个视频详情页
它可能又请求一个视频
比方可能是video
videoview的一个接口
视频详情页的一个接口
那么这个视频详情页其实里面也有很多业务
比方说有视频的播放地址
然后有用户的up主的信息
然后你对这个up主你是否有关注
然后这个稿件本身点赞收藏播放
有一些数字
然后它有相关联的标签
以及视频相关的一些视频
其实这个页面它需要访问好多内部的微服务
以前的想法是说每一个微服务
提供细粒度的面向资源的这种接口
让移动端来请求
它要考虑并发
考虑局部错误
现在我通过app-interface
暴露了一个面向这一个业务场景
就这一个页面的
然后里面有好多个微服务
可能会有部分失败的
我也做了一些容错
同时我也统一的协议的出口
这样的话让我们移动端的交付速度就是快的很多
所以它需要因为为什么叫dataset join
就是指它有好多种数据源做数据的一些组装再返回
所以说是最终面向用户场景
面向usecase
来设计粗粒度的API
给到后续的服务引进带来很多的优势
那么这些优势会包含哪些方面
我们再看图
其实图里面就是
在我们的内网的这一层
Web server加了一层app-interface
我们以前叫app-interface
后面我们了解到微服务的一些知识了之后
我知道它叫Backend For Frontend
我们简述就叫它BFF
它是三个单词的一个缩写
其实就是面向前端应用的后端服务
所以它叫BFF就是Backend For Frontend
那么有了这个角色以后
它会有些什么好处呢
我们简述第一叫轻量交互
我们在BFF这一层
有可能我的稿件服务提供的稿件数据
有一些数据需要暴露给这一个场景
有一些数据不需要暴露给这个场景
那么我的稿件服务作为底层的一个微服务
就是我们之前的模型
就是说每一个微服务直接对外的
现在的模型是只有BFF
它是直接对外的
那么藏在下面的我们灰色框出来的这些服务
它仅仅提供内网的RPC
它不直接对外
不直接对外以后
我提供的是内网的接口
我们讲了我们内网的这些微服务
它可能是面向的资源
或者是面向一个单一的功能的微服务
那么这样的话面向一个资源的场景
那么这个接口里面可能包含的数据
相对是比较多的
就是这个接口比较粗
我们不会说提供几十种类型的
那种字段的类型组合的这种接口
当然我们知道像GraphQL
它其实是支持你可以自己筛选字段
像我们的话主要是使用gRPC
gRPC它需要提前预定义这个接口
它的输入和输出是什么
所以说我们通常习惯是说
会提供相对较粗
像粗粒度的一些接口
就说这个接口里面字段
12345各种字段都会包含
当然了如果它的差距非常大
我们可能才会考虑提供多接口
比方说像我们账号
账号服务里面可能会提供用户的概览的一些数据
也会提供用户资料的详情的一些数据
这种可能会区分
但是大部分接口我们只会提供一种粒度
那么提供一种粒度
为什么要做精简呢
因为你面向不同的终端
面向不同终端面向不同的网络环境
可能需要做一些协议的裁减
这样的话我就可以节省一些流量
就协议的精简和聚合指的是这个意思
第二个是什么
叫差异服务
差异服务指的是比方我们在4G网络下
和在WiFi网环境下
我们的提供的图是有图片是有差距的
那么节省流量
当然我们也有些土豪用户可能
希望4G也可以看高清的一些图片
那么它可以在APP里面去做一些自定义
当然了根据它的不同的设定
我们需要做数据采集和聚合
然后针对不同的终端来定制化API
比方说针对我们电视机这种OTT
可能是这种接口
那么针对移动端可能是接口
针对HD又是不同的接口
那么这一些逻辑
以前是散在底层微服务的
现在我全部搬到我们的BFF层
来做大量的这种差异化的一些功能
也就是说这有什么好处
是不是底层的微服务
不考虑面向终端的这些场景
然后底层这些微服务
它就可以很快速的未来
因为它没有包袱
没有对外的一个这种压力
它只需要保证
它自己对内的RPC是兼容的
它就可以快速迭代
那么对外的这些逻辑全部在BFF里面
相当于把那些逻辑上搬了
然后你自己就可以提供面向资源的这种接口
然后可以快速地去演进和迭代而不受影响
那么你的兼容逻辑全部在哪
全部在BFF这一层
所以第三点就是指只要你的内网的微服务
是保证API的兼容性
那么你就有机会来做动态的升级
因为这个服务只要API兼容
你就可以随时的更新
当然了BFF的兼容逻辑
相对来说是会比较重一些的
对这个是比较重一些
因为它这种模型其实也非常利于组织的一个配合
你BFF这一拨人
我是不是可以单独捞一批后端同学专门去维护
他们再跟移动端同学去进行一个强配合
你经常两个团队一配合
搞来搞去都是那几个人
时间久了它就容易什么
我们容易产生social产生社交
它们就会对接起来非常顺畅非常熟悉
并且对接模式也非常固定
我觉得这个其实就是
从背后团队里面带来的一些收益
这些大幅的增益
大家还是要重视
很有可能你的技术的方案
其实有可能会影响到你整体组织的一个效率
这其实就是局部优化到整体的优化的一个
很有效很有效果的一种技术的改造
其实就是我刚才说的沟通效率的提升
因为你的模式就是这一小拨人去对接了
我们其实提到这个地方就要提一下康威定律
康威定律里面有一个点是什么呢
我们其实刚刚讲微服务概览的时候
很多图其实都是从康威定律里面演进过来的
康威定律里面还有一个就是指
我们最终业务架构
最终的技术架构决定了业务架构
或者说最终业务架构
也决定我们技术架构
那么你如果是按照业务闭环的方式去运作的话
我们这种微服务的网关团队和移动团队
它们打成一个虚拟团队
它们内聚
那么它沟通效率肯定是最高的
这种方式是减少沟通成本非常有效的
因为我们知道要把一件事情给其它的人讲清楚
你的沟通成本是多少
是n乘以n减1除以2
你需要把这个事情
给除你之外的所有人讲一遍
然后因为它是个双向沟通所以要除以2
其实沟通成本随着人变多
就变得越来越复杂
如果你是之前那种模型
好多个微服务直接对外
移动端同学是不是要找一坨人沟通
现在其实很简单
跟一小拨人沟通就可以了
这样其实就是非常简单
所以说从我的视角看
BFF你可以认为它是一种适配服务
叫adapterservice
它是做适配的
做面向用户场景
面向多终端适配的一个服务
那么将后端的微服务进行适配
主要是包括刚刚说的粗粒度的API
我只要需要你们其中的三五个字段
我就可以做些裁剪
那么我是针对不同的APP的版本
需要做逻辑的适配
那么我就可以针对版本号来做逻辑
这些逻辑都在BFF层来做掉
它最终好处就是说
像我们的无线端
就是Web和移动端
暴露一个友好且统一的API
方便无线设备接入访问和后端服务
也就是说最终是统一出口的
所以说我们API Gateway演进的过程中
从早期的只有一个Web server
每一个微服务直接对外
提炼出一个BFF这一层出来
所以它解决很多问题
所以我们又印证了一句话是吧
什么搞不定的就加一层一定能搞定
那么这个是我们的第二个阶段
其实我们也经历过这个阶段
那是这个阶段还是有一些毛病的
不知道你们有没有发现
所有的流量现在都要经过app-interface
是不是意味着app-interface
它就变成一个业务极其重
并且一炸全炸的一个局面
那怎么办
我们刚刚提的app-interface现在属于什么
叫单点
它是从架构层面单点故障
并不是指这个服务本身
它是一个单点
其实这个服务本身
它肯定是布了很多个节点的
它是有很多个副本的
那么但是从架构层面
它仍然是处于一个叫single point of failure
这么一个场景
就是它很尴尬一挂全挂
这个挂有很多种情况
因为它业务高度集成
有可能是某一个业务集成的接口里面
它突然提交了一个类似那种RuntimeException
比方除0或者这种无法被捕获的异常的这种bug
严重的代码缺陷
或者是某一个因为你是所有的流量全部过它
或者是某一个流量洪峰
就会引起整个集群故障
那么从架构层面来说它就是一种单点怎么办
其实也很容易想到就是分而知之
还是我们上节课讲的分而知之
搞不定的就拆
怎么拆
其实就是要考虑的就是怎么拆的
从架构上来说它是个单点服务
那么其实除了性能
或者是架构设计上面有这个问题
另外一个维度它其实也存在一个叫
叫复杂度业务集成度变复杂
因为你这个模块
需要处理很多很多对外
面向不同业务场景的一个对外接口
那么会导致说这个BFF团队
这个大团队跟好多移动团队要做沟通
它整个交互就会变复杂
同时你这个代码往里面各种塞功能
是不是也会意味着它合并代码也会变得复杂呢
这个是其中的一个问题了
既然这样我们就要考虑拆分
拆分的话我们很容易想到什么
按照业务场景
比方说我认为账号体系
整个用户中心它是一个
整个用户中心它可能是一个场景
我可以拆一个
面向核心的像播放这种非常重要的一些功能
因为它非常重要
我们可能按照重要性来拆分
刚刚是按照业务维度拆分
第二个是按照重要性拆分
那么剩下的一些杂七杂八的
我可能就放到以前的一个interface
所以我们看到图里面
我这里抽象的一个account interface
还有个APP view
这个是按重要性拆分的
还有一个就是APP interface
什么杂七杂八的接口
可能放在里面
这样的话其实就从一个变成了n个
这样的BFF
这样的话面向不同大业务场景的人
就只要维护自己的BFF就好了
那么它的沟通模式
跟移动端的沟通模型
并没有太大变化
而是说移动端的固定做账号的页面的这一拨人
跟我固定的做账号网关的这拨人对接
其实这样整体沟通成本就变低了
避免说移动端面对你大网络团队也不知道找谁
这个其实是不太好
在BFF这样
我从一个变成多个以后
我们是不是还发现有些别的问题什么问题
很多跨横切面的一些逻辑
比方说安全认证
日志监控限流熔断
这一些随着时间的推移
这些代码有可能会演进
我现有框架各种升级优化迭代
我的验证方式各种升级优化
代码它会越来越复杂
然后同时这些横切面的一些这些中间件
就是我们叫Middleware横切面的一些功能
它也在演进
那么一旦演进我要推动这么多BFF
分别去升级我们的kit库我们的基础库
因为我们刚才讲了微服务组成是什么
是业务代码加kit库
你的kit库基础库框架要升级推到这么多BFF
这个是有一定难度的
因为它这个其实是很困难的
或者说我现在要新加一个功能让你去升级框架
这个都是有一些门槛的
所以从某种角度来说
这个BFF它属于一个API的composer
它是做一个组装服务的
那么既然这些横切面的功能
被演进到各种不同的BFF上使用
是不是又可以考虑加一层
再把这些横切面的再抽出去好了
是不是可以这样考虑呢
的的确确在业界的一个演进的过程中
的确也是有这么一个模型
就是把通用型的面向横切面的一些功能
需要经常协调框架去升级的这些
从BFF里面捞出来
也就是说我现在的BFF
变成了业务集成度很高的一个BFF
我的BFF以前是业务集成度很高的一个BFF
加上一些通用的横切面的功能
我把它上沉
然后我把自己紧紧围绕
就是面向业务的dataset join
数据的一个组装的这么一个模型
那么这些通用的横切面的一些功能
我套到哪里呢
我把这个Web server踢掉
把它变成我们正儿八经的一个API Gateway
它就是一个API网关
它负责路由
比方说哪一个接口应该去路由到哪一个BFF
它负责认证
统一地鉴权
这样的话我鉴权有逻辑升级
我只需要考虑和业务无关性的API Gateway的升级
那么对BFF这种带业务属性的东西不需要升级
因为相当于我把API Gateway
变成了一个基础设施
它的升级就是走灰度走全量这种方式就行了
它跟业务本身没有耦合
然后现有功能也是可以统一在最上层做
以前需要BFF依赖基础库的限流框架的一个功能
我这种把这个功能上提以后
我只需要API Gateway自己去演进和升级就好了
还有就是安全的一些功能
也可以统一收口去做的
全部上沉
这样的话就分层了
一层负责通用功能
一层负责业务集成
所以在新的架构中
我们的网关承担了重要的角色
它是解耦和拆分
以及后续升级迁移的非常的利器
在网关API Gateway配合下
每一个单独的BFF它能够独立拆分出来
各个业务团队只需要维护自己的BFF
研发效率大大提升
那些通用功能全部让什么运维的同事
或者中间件的同事去维护了
那么BFF的研发人员就更加专注在业务逻辑的交付
那么API Gateway的人可能是交给基础架构团队
或者是运维团队或者是什么类似这种中间件团队
它们去研发
把一些这种横切面的功能打进去做进去
那么实现我们叫架构上的关注分离
就是separation of concern
我们经常叫听过SoC的理念
关注分离
你搞基础设施
我搞业务功能
这样子就引入了一层
我们目前API Gateway选型
我们主要是在
在我们内部主要是使用叫Envoy
Envoy这个组件
我们应该是在InfoQ
应该在Qcon上
好像是 ArchSummit上
我们有做过
我们内部的API Gateway移动端的一个分享
就是如何在移动端上使用
包括配合我们服务端使用Envoy来做的
整个API Gateway
所以我们的业务的一个流量
我们的流量实际上就变成了移动端
发起一个请求到我们的API Gateway
当然了到API Gateway之前
实际上还是有我们的集中式的一个负载均衡
其实就是叫SLB或者ELB
有点像一个Nginx
这个其实是跑不了的
这个是因为它是面向我们
通用的一个七层负载均衡进来
那么进来以后就到API Gateway
那么API Gateway
它可以负责某一个接口的路由逻辑
它可以路由到具体的一个BFF
然后再从BFF再到
BFF可能是使用go的一些并发编程的方式
访问我们下游的好多个微服务
那么它做些什么dataset join
在我们前端
我们纯前端就是Web开发的
这种纯前端业务中
我们这个BFF
我们甚至是使用Node JS来做服务端的渲染
SSR叫Server-Side Rendering
以前我们可能是使用纯前端
就是JavaScript然后操作html加Dom
这种方式来操作前端页面的
这种模型在前端浏览器做这种Dom的操作
其实效率不是最高的
我们一般会在Node JS
把整个服务端就是首屏需要的完整的页面
需要的完整的HTML
全部构建出来一次返回
那这样的话一旦我们的接口一返回
我们的浏览器立马可以渲染出来
所以尽可能达到我们的秒开
因为我们首屏
尽可能就是说什么
让你需要的数据完整出来
那么你如果要滚动下滑
那么可能还是通过这种
类似像JavaScript加接口的方式
去操作Dom来展现
所以我们刚才也提到了
我们是最终其实忽略的上游的CDN
和4/7层的ELB这种负载均衡
所以流量就是说移动端到API Gateway到BFF
到Microservice一共就4层
所以Envoy的话如果大家感兴趣
还是可以单独去看一下
我觉得现在的话API Gateway的话
像Java体系主要是使用Zuul
那么如果是像Go的体系的话
像Kong或者是像Ingress
或者是Envoy
也其实都是有蛮多人在使用的
这个就是我们整个API gateway的一个演进的模型
我们接下来是讲大家也非常关注的
Microservice的一个微服务的划分
我们其实在面向微服务架构时经常遇到的
第一个问题就是如何划分边界
那么在实际项目里面
我们通常会采用两种不同的方式
来划分微服务的一个边界
既通过业务职能
我们叫Business Capability
或者是DDD的限界上下文叫Bounded Context
这两种方式来拆分
那么它具体指什么意思呢
第一种其实是指
你们公司部门内不同部门提供的职能
比方说客服部门
它提供的是客户服务的职能
那么财务部门它提供的是财务相关的职能
你按这种模型去进行一个微服务的划分是可以的
其实也最容易理解
比方说做售前的可能是做订单
To B的一些接入
那么做售中的可能是做一些订单的逻辑
那么售后可能是做一些访修的一些服务
这种拆分方式大家也比较好理解
这是一种
那么第二种叫Bounded Context
那么第一种其实我们通常对一个业务架构
它的业务模型不太熟悉的情况下
通常是用面向职能的一种划分方式
其实这种是最容易实现最好理解的一种模型
那么如果是按照DDD的限界上下文的思路
当然我们也可以关注一下
训练营里面应该也是有
关于DDD的一些这种课程
大家可以去听一听
我觉得它DDD其实更像一种
工程思路或者是架构的一些思路
那么它其实对我们架构拆分也好
或者对我们代码工程组织也好
都是有一些帮助的
大家也可以去看一看它的一些课程好吧
那么限界上下文是DDD中
用来划分不同业务边界的
划分不同边界的一种方式
那么这个地方的业务边界我们指的是什么呢
指的是解决不同业务问题的问题域
和对应的解决方案域
也就是说它解决的是一类业务场景
那么解决某类业务场景
它一定是贴近领域的一些知识
也就是很贴近业务维度的一个思考的划分方式
这种划分方式其实就间接促进了组织结构的演进
我们其实也叫service partteam
每一个team它维护的是一个
面向完整业务领域的一个微服务
举个例子我们这边右边这张图
我们一开始拆分我们的视频
就是搞投稿稿件这块的一个
拆分方式是怎么拆的呢
就投稿无非就是说
分为稿件的上传
稿件的审核
它是有个稿件服务
然后视频投完稿以后肯定要播放
所以分成了视频的一个播放服务
一开始我们其实按职能划分的
因为我们部门
我们内部组织结构是有一个
负责视频的团队和负责稿件的团队
其实拆分方式是非常好理解的
但是随着我们业务的发展
我们发现在一个完整的业务领域里面
我们抽象的一个创作端的服务
为什么需要这个呢
因为你从业务场景里面用户来投稿
围绕这个视频的一些结构化的数据
对稿件本身还进行大量的一些后期的管理工作
所以它就能抽象出一个创作服务
所以说按照职能划分
你一定是不知道有这个创作端服务的
所以它是贴近业务域去抽象的一个概念
就面向我们的创作者
有这么一个创作端的服务
它会把底层的稿件
审核投稿以及视频的上传分发全部搞定
面向它创作生态去服务的一个服务
或者是叫业务领域的一些知识的沉淀才会出来的
那么你不可能一口气就引进到模型
随着你对业务的理解和深入
逐渐的就从上面这种模型
研究到下面这种模型
我觉得这个就是拆分的一个重点
那么我们再举一个实际的例子
微服务的拆分
它一定是分久必合合久必分
我这里举一个非常有意思的case
我们的账号服务
账号服务里面会包含什么功能呢
Vip 什么用户装备
用户成就
用户积分
用户的信息
用户的Passport
我们一开始很乐观
这些服务都很大
我们拆了VIP Passport
拆了用户的装备积分等等
拆成好多小服务
但是你会遇到个问题
因为业务场景里面
它一般来说都是既要取用户的装备等级是否VIP
以及他的一些基础信息
你会导致说这一些人要请求好多个
请求好多个这些底层的微服务
才能把这些数据构建好
而且要考虑局部失败
要考虑并发
要考虑并行去访问
你不可能串行去访问一个挨一个
那么它整个总体的耗时会增加的非常多
你这种接口如果是让每一个业务使用方都这么去使用的话
肯定是非常痛苦的
从对接模型上来说
我们是不是可以考虑说把账号的底层的这些服务
要么就是说你觉得从性能角度
因为它上出的非常厉害
让使用方很难受
要么就是把它们整合成一个用户中心的服务
但是这样整合以后
它的业务集成度的的确确会变复杂
但是从性能角度和对外暴露统一接口的方式
它又会变得更友好
这个是一种模型
就是分久必合合久必分
那么另外一种模式
你觉得底层的业务仍然很重
我不想把它合到一个服务
但是我可以在这个服务之上
抽象一个比方说统一的一个账号服务
下面可能还是VIP什么各种东西拆分出来的
你对外只暴露这一个账号服务
那么这些账号服务就是提供统一的
比方说用户详情接口
用户头像接口
用户详情接口
用户基础信息接口
这样的话其实它就有不同粒度
局部的一些失败
并发的一些访问底层的服务
甚至是二级的一些缓存
因为多级缓存可以加速
我避免说每一个请求进来
都要向下并发
发好多RPC
我可以做二级缓存
那么就可以由账号服务统一来提供了
这样的一种模型的话
从划分上来说
其实是抽象的另外一层
或者说你甚至把这些服务就整合进去了
这两种模型我觉得都可以
那么划分的时候一定是从性能角度
就是使用便捷性
以及你的业务领域
这几种角度去思考和抽象
那么你最终就会形成你自己的一套方法论
去演进你们内部的一些微服务
那么还有一种划分非常有意思叫CQRS
CQRS我为什么这么早就来介绍
我们在后面讲到架构的一些实战的时候
比方说讲我们评论系统
弹幕系统等等
其实类似的一些思路都会体现
所以我这个地方先抛一个
我们具体的业务场景里面的一种case
非常有意思
将我们的一个微服务一个应用程序划成两个部分
就命令端和查询端
因为CQRS指的是Command and Query
command就是命令端
query就是查询端
命令端干嘛的呢
负责处理程序的
什么创建更新删除的一些请求
它主要是一些写操作变更操作
并在数据更改时发出一些事件
因为有更改
我要同步到哪里
既然它要拆分吗
那么查询肯定要知晓这些变更
所以把这些变更发布到一个地方去
然后我的查询再订阅这些数据
进行一些数据更新
那么把这两个职责一旦划开以后
我就可以让我的逻辑变得更简单
就是比方我的写操作全部在一个服务里面
我的读操作把这些内部的复杂的
写操作的一些逻辑全部简化掉
仅仅是查询
或者是面向一个或者多个物化视图来进行查询
这样的话其实让我们的架构上会变得更简单
具体的case是什么
就是像我们的稿件服务的演进过程中
我们发现围绕着创作稿件
审核稿件
最终发布稿件
有大量的逻辑都会在审核
包括发布这些逻辑里面其实很复杂
有一个这样的数据库里面
有大量的稿件的一些状态
或者是中间的一些状态
那么这些非常复杂的状态
但是我最终我的前台用户
前台用户就是我们终端用户
它只关注稿件能否查看
那么以前的做法是写个where条件
然后各种加一些状态的判定
来决定稿件能否查看
但是我们反过来想
如果我的审核的一些逻辑越来越复杂
里面的状态各种变化
我们查询和变更容易相互影响
逻辑也非常重
因为我希望做审核稿件的这些数据的一些状态
跟我做查询的没有关系
我们怎么办
我们看个图我们实际上是稿件服务
一开始它是写到这个叫archive-database
我们引入了一个中间件叫canal
它能干嘛
它实际上就是模拟成MySQL的一个slave
订阅MySQL的binlog
把这些数据再写入到消息队列Kafka
那么最终我在写个稿件job的服务
一个订阅Kafka的一个服务
消费这些数据
然后把这些数据
重新再写入到叫archive-result-database
就是有个结果
这些逻辑就是很复杂的一些过滤逻辑
最终通过稿件job把它全部清洗干净以后
投放到我们的archive-result-database
就告诉你能不能播
能播还是不能播
我最终再通过一个独立的稿件服务
比方叫稿件结果服务
去查询archive-result-database
它可能有自己的缓存
当然了如果我的稿件状态从可播到不可播
我可能还涉及到清理缓存的一个逻辑
那么全部把查询端和变更端完全的解耦
那么外面其它的微服务要查询数据的时候
实际上它只需要关注我们的稿件结果的微服务
而不需要知道我内部的一个
稿件审核相关的一个服务
其实我就把它变成一个
稿件审核服务和稿件结果服务
那么其实它就是一个标准的CQRS
把命令端和查询端完全解耦
那么我们架构
我们以前其实这种
利用binlog订阅数据的一个架构
早期大家其实比较容易想到是什么
叫Pulling Publisher
Pulling Publisher是什么意思呢
我们去查MySQL
把最近变更时间的这些数据捞到一个地方
然后再投到消息队列
然后再去消费
这种轮询的方式
频繁的轮询数据库
比方说我们可能这一次轮询的时间戳是01
我下次查询就是查出modify time大于01的全部捞出来
这其实是比较容易想到
但是这样的话它不是一个流式的
也不是一个实时的
是一个偏轮询的
我就逐渐演进到
模拟成Transaction log tailing
叫Transaction log tailing这种方式
那么其实本质上
就是一种pull的模式和push的模式
Transaction log其实指的是MySQL的binlog
因为它包含最终结果数据库里面的CRUD
应该是它的变更删除插入等等
我都可以实时订阅出来
并且回放出去
我就最终可以完成一个这种架构的一个拆分
就是CQRS的一个架构拆分
让我们了解到
通过我们的CQRS也好
微服务划分也好
其实我间接地引入了几种架构模型
包括CQRS是一种模式
我们刚刚说的账号的底层的那些服务
我在上面架一层
架一层什么
架一层代理
代理叫account service
它其实就是负责统一的
一个内网的RPC的收口的一种模型
其实都是一种架构模式
其实像我们BFF或者是API Gateway
它不就是一个API composer
API的一个组合器
它是对外网的
BFF是对外网的API组合器
我们的account账号的代理服务
它其实属于内网的一个组合器
我们的CQRS其实实际上是按照功能
按照功能的一种拆分模型
那么按照垂直的业务单元划分也是一种模型
那么还有按照我们的业务领域
这种演进的业务领域抽象的一些服务
它也是一种划分模型
所以这一些都是我们常见的划分模型
那么如果大家在工作中
到底微服怎么划怎么拆
如果有问题其实回头也可以
再跟我们来进行一个讨论
我尽量给你们一些我的理解和思考
最后我们再来了解一下微服务的一个安全
我们讲BFF一开始是包含了一些统一认证的功能
我们随着API Gateway引入
再把这些统一认证功能往上抛
抛到了API Gateway这一层
也就是对于外网的请求
我们都是经过API Gateway
进行统一的认证拦截
一旦认证成功
外网是什么
外网不就是传用登录完之后
比方我填账号密码登录成功以后
我们一般会颁发一个token
我们Web端可能是一个cookie
cookie里面其实也是个token
那么这个token
在每一次API请求的时候都会带上来
带上来以后我在API Gateway
如果一旦认证成功
我就会得到一个用户ID
这个用户ID我会通过head的方式
拦截放到RPC里面
放到RPC里面它可能就是通过我们的
比方说我们内部使用gRPC
可能是挂载到gRPC的meta data里面
或者是放在我们HTTP的head里面
总之通过某种方式传递元数据的
RPC的元数据里面
或者HTTP的head里面
那么传进来以后谁要使用呢
实际上是BFF层
因为API Gateway统一认证OAuth鉴权以后
拿到的这一些
拿到的这些用户ID
传到了BFF以后
因为我们BFF这个接口
它在设计上不会在请求参数里面带用户ID
大家能理解吧
如果你的接口设计上
你的请求参数里面有个用户ID
用户ID不填一个
它不就可以模拟成任何用户
因为它一定是要认证的
所以说认证以后这个用户ID
它并不在它的请求参数
也就是不在它的request参数里面
它是在哪里
它是在head里面
它是注入进来的
当然了如果你外网的用户
恶意有人在head里面注入进去
这是有一定风险的
所以我们的API Gateway
通常要把这些head
从外网请求进来的时候
把这些head踢掉
然后你认证成功以后注入一个新的head
你之前的head肯定要踢掉一个
比方你这个head key叫用户ID
你肯定要踢掉之前的
然后你注入个新的
到我们BFF的时候
你可以从head里面
或者从gRPC meta data里面
重新把用户的身份信息给捞到
比方说捞到的就是一个用户的ID
那么在BFF请求下游剩下的其它的微服务的时候
下游的微服务一定是在它的RPC的接口定义里面
填写的用户的ID是什么
填写的用户ID是什么
因为这个时候你就可以
把用户ID作为参数去请求
比方说我要去传参
这一个RPC用户
比方叫getuser
我就要传userID等于多少
那么这个userID从哪来的
API Gateway鉴权成功以后注入到head
在BFF层从head里捞出来以后
传递到下游的RPC服务
一层层这么传下来的
所以说整个身份信息
实际上是从API Gateway
统一鉴权注入到head
或者是RPC的元数据传到BFF
BFF可能是在Go里面可能是使用context
比方from context
这种方式取得了用户身份信息
我在后面讲
在讲我们context这个章节的时候
其实也会介绍利用context
可以代替很多上下文的一些信息
通常我们像RPC框架
或者是我们很多这种kit库
Middleware里面
一旦这种Middleware做一个
这种中间件做一个拦截
通常你要挂载的话
一般都是挂在我们go的context里面
这个我后面会讲
那么通过BFF context里面拿到用户ID以后
最终你就是传参
注意这个地方是传参
传它的request请求参数
传给我们下游的微服务
这样就是一个完整的安全的鉴权的一个机制
对于服务的内部
因为我们安全其实分外部和内部
外部指的是用户身份鉴权
那么对于微服务内部
比方说我内部有一个硬币服务
因为我们比方可以
或者是有一个VIP服务
VIP服务可能是要变成VIP
有个这样的接口
比方说随便举个例子
有一个就是变成VIP的接口
如果内网的这个服务
随便可以被其它人调
那肯定是有安全风险
变成VIP或者是给自己加硬币
比方像我们B站有硬币
随便可以加硬币
肯定是有安全风险
所以内网一般来说
更重要的是知道谁来调你
比方你的服务是提供硬币功能的
你一定要知道谁来调你
那么你知道是谁你就能做什么RBAC
做RBAC实际上就是控制
谁来调你的时候
它能调你哪一些接口
那么所以第一层你要搞清楚是谁来调你
第二层谁来调你之后
这个谁它能调你哪一些接口
一个叫authentication就是它的认证
第二个是什么叫authorization就是授权
一个是认证一个是授权
它实际上是两种事情
那么我们内网通常来说它这两个都要做
既要做它是谁做认证
同时要做授权它能调哪些接口
除此之外我们像很多金融行业它会做得更严格
它甚至会做传输的加解密
比方说它调内网接口的时候
内网它都要防嗅探
防有人抓包这种
所以其实它分为几种
一个叫Full Trust
一个叫Half Trust
一个叫Zero Trust
那么这个Full Trust指的是什么呢
指的是完全信任
完全信任不就是裸奔了
两个微服务可以直接调
没有任何的一个安全机制
那么Half Trust是指什么呢
我知道你是谁
但是你来调我的时候
我们之间的RPC的流量
没有任何的安全的一些处理
你如果抓包的话
你可以看到明文的对谁做了一个什么操作
Zero Trust指的是零信任
零信任指的是两个微服务
既要做身份认证
又要做细粒度接口的判定
同时还要做通讯的加解密
那么这些其实很多RPC框架都支持
比方说gRPC支持带证书的
带证书的这种既知道你是谁
同时基于证书又可以做通讯的加解密
因为我们知道HTTP
gRPC它是基于HTTP2.0做的
2.0做的话它就要做我们的TLS
就要做我们的传输层加密
这个所以它是分几种档位
具体来说要怎么实现呢
我们建议就是说最简单的一种方式
做认证就是颁发token
比方说A调B
你A服务颁发一个token
颁发一个token给特定的B
那么这个token只要不泄露
那么我就知道你每次在RPC握手的时候
你带个token过来做一个鉴权
做一个握手
我就知道原来这个token是我刚发的
我知道你的身份是谁
这是第一步
第二步就是说
我知道你是谁以后
我再判定一下你对我哪些服务
哪些接口能够请求能够调用
所以说这是最简单的
就是基于APP token的一种方式
那么基于证书的话会复杂一些
需要有一个统一的
在我们容器里面有个统一的信任的一个链
有个统一的根证书
都被大家信任
基于这个根证书
我们再颁发证书
因为颁发证书我就知道是颁发的A还是B的
颁发不同人的证书
我就知道他是谁了
这也是一种方式
它会复杂一些
如果大家感兴趣的话
可以去了解一下关于内网RPC
两个服务之间鉴权
其实有很多方式
刚刚说的一种是APP key
APP token的一种方式
第二种就是基于证书的
其实就像我们公网使用的证书
其实我就知道这个证书和域名绑定了
我浏览器上会打个勾
我就知道不会被钓鱼
一样的内网其实就是做一些简单的身份的一个识别
但是我们一定注意在很多很高安全要求的一些领域
一定是会做Zero Trust也就是零信任
零信任是指它们RPC通讯都会做加解密
比方说我们最简单的就是说两个RPC
在握手的时候交换了一个不对称的密钥
然后在通讯过程中
基于不对称的一个加密
交换了一个对称加密的密钥
然后RPC通讯过程中再用对称密钥来进行加解密
这是很常见的一种做法
但是它对性能的的确确是有些影响
所以大家知道一下有这么一种模式
在我们内部的话
其实是基于token
颁发token的一个模式
就是这个token颁发给你的时候
它是这样的就是
A颁发给B就是一个token
A颁发给C就是一个token
这种的话你在page里面
就要带上你这个token来访问我
那在你这内部做微服框架或者体系的时候
就要考虑这种安全机制
我们这一课就讲完了
我们回溯一下我们讲了一些什么内容
其实主要是讲了微服务设计里面的
很常见的一些角色
包括像API Gateway
然后包括像BFF
以及我们整个演进过程是怎么来的
然后我们还介绍了一下微服务拆分
因为实际上也是大家最容易关注的
拆分的一种模型
比方说我们要按垂直功能拆分的
然后有按照
然后有垂直功能拆分
然后有按照分久必合
从性能角度去考虑整合的和拆分的
同时我要考虑可以中间加一层
因为我可以统一接入
账号这种接口本来就很散
那么统一接入也是一种拆分方式
还有就是我们按照业务领域
比如说我们刚刚讲的case
就是抽象的创作端的服务
这也是一种业务领域的划分方式
最后我们还介绍了一些是像
微服务的安全的一些知识
这两块讲完之后
我觉得微服务整个概念
你们基本上就形成了
我们下一节课就要讲
微服之间通讯的RPC框架
以及RPC框架最重要的是什么
服务发现和注册
这两块讲完之后
你就会形成一个
比较完整的后端应该怎么去
它们之间怎么通信
然后微服务怎么玩
基本上就能完成了
虽然你的业务规模
未必一定要使用微服务这套体系
但是你了解完之后
对你的普通的业务开发
其实也是有很大帮助的