你要先理解微服务,然后才可以去填坑。
API Gateway
拆分
随着SOA服务的演进,就是巨石架构的拆分,我们如何从巨石架构到微服务的拆分。正常来说,我们拿到代码都是按照垂直功能进行拆分,拿到一大坨代码之后,先把账号系统搬出去,然后再把外围的wiki、pipeline、project等等搬出去,逐渐的一步步的进行拆分。
当然,前期微服务全部对外接口,相当于对外暴露了一批微服务,因为缺乏统一的出口页面,这就会面临了不少的困难:
- 客户端到微服务直接通信,强藕合。 给后续微服务的演进和升级是比较困难的。
- 需要多次请求,客户端聚合数据,工作量比较巨大,延迟也非常高。比如一个前端页面,需要用到用户信息服务、推荐服务、广告的服务和稿件相关的服务,这样一个前端人员就需要找到各个服务的owner,要找到不同的人进行对接。而且一个个接口去请求,数据组装聚合的工作,需要客户端做这些工作量就非常大。我们做互联网业务开发有一个准则,提升性能,就是前轻后重,让客户端的人员做更少的业务工作,他们更多是面向交互和流程的,不应该把复杂的逻辑让客户端做。
- 协议不利于统一,各个部门间有差异,需要端来兼容,而且这么多接口分别由不同的部门提供,每个部门的规范、最佳实践不一样,就会导致接口契约不一样,客户端比较难适配和兼容,对接工作复杂。
- 面相“端”的API适配,藕合到了内部服务。比如IPAD版本和Android的版本,每个API可能不一样,如果要面向不同的终端做适配也是非常复杂的。当然,虽然有GraphQL,但是现在流行度不高。
- 多终端兼容逻辑复杂,每个服务都需要处理,也就是一年前的版本,和现在的版本,都需要进行维护,新老API很多版本都需要存在,存在微服务里面。
- 统一逻辑无法收敛,比如安全认证、限流等等,因为对外暴露的表面积越少,那么你的逻辑就越收敛,就好像我们设计一个interface,你对外暴露的api越少,你的接口就越健壮。所以要面相横切面的编程需要收敛,比如限流、容错等等,需要统一做是最好的。
所以,我们的工作模型需要内聚模式配合。
app-interface
当然,明显看出上述有各种设计不合理是吧,又增加了一个api-interface服务,我们叫做ids服务,用于统一的协议出口,在服务内部进行大量的dataset join(数据集链接),按照业务场景设计粗粒度的api,这客户端的同学就很开心了,之前不协调的api已经替换了,这种方式也带来了很多优势。
在app-interface里面,我们又做了很多的dataset join,也就是有很多种业务微服务的数据,我们把这数据做了数据集链接,也就是提供的接口是面向业务场景的,就是相当于有一个人专门封装所有的微服务的信息然后提供给某个业务。以前是由微服务提供一些细粒度面向资源请求的接口,现在是通过一个app-interface暴露这一个业务的场景,里面有一个微服务错误也做了容错,也统一了协议的出口,这样客户端的交付数据也会快很多。当然这个概念在微服务里面叫做(Backend for Frontend,简称BFF),也就是面向前端应用的后端服务。有了这个角色之后,有什么好处呢?
- 轻量交互:协议精简、聚合,比如用户服务,有些字段需要暴露给这些场景,有些又不需要暴露给这个场景,作为用户服务之前的模型是直接对外的,是粗粒度的,现在只有BFF是直接对外的,也就是微服务是内网的RPC,所以就会提供精简的接口。
- 差异服务:比如在4G网络和在wifi情况下,提供的图片资源是有差异的,因为要节省流量,就可以进行数据裁剪或者聚合。 也可以针对不同的终端进行定制化的API,之前是都在微服务里面做自定义,现在都搬到了app-interface里面。这样的好处是底层的微服务没有对外的包袱,只需要保证对内的rpc是正常的,就可以快速迭代。兼容逻辑全部在BFF这一层。
- 动态升级:只要你对内的微服务是有api的兼容性,你就可以动态升级,原有系统兼容升级,更新服务而非协议,做到用户无感知的。
- 沟通效率提升,当然BFF的任务还是比较重的,但是可以捞一批后端同学专门去配合,我们叫做0号业务,因为团队配合旧了就可以产生social(社交),他们对接起来就非常顺畅和熟悉,模式也固定,相当于是buff的收益,有时候要重视。我们提到这里,可以提下康威定律,其核心观点是:“任何组织在设计系统时,都会倾向于构建出与其自身沟通结构相匹配的系统”。换句话说,组织内部的沟通方式、团队的结构与文化,直接影响最终软件系统的架构和设计。也就是最终的业务架构会决定技术架构,如果是按照业务闭环的方式去运作,微服务网关和客户端团队打成一个团队,那沟通是非常有效率的。
所以BFF可以看成一个adater server,适配服务,是面相多终端适配的服务,将后端的微服务进行适配,包括聚合裁剪和格式适配等服务,向客户端暴露友好和统一的API,方便客户端设备接入访问后端服务,统一出口了。当然,也验证了一句话“任何东西都可以加一层来搞定”。
但是,这个架构也是有问题的,也就是所有的流量都需要经过app-interface,就导致这个业务属性极其重要,一炸全炸的局面就会产生,这是最致命的问题‘single point of failure’(从架构层面是单点,这个服务本身肯定是分布式的),严重代码缺陷或者流量洪峰可能引发集体宕机,因为所有的流量都需要过他。
所以,怎么办?分而治之。。。
分而治之
所以,按照业务进行拆分,拆出来用户中心,然后再按照本地化拆分一个global,最后按照页面显示类型,拆除一个view,最后也可以按照杂七杂八相关的拆除一个app-interface等等,这样大业务的同学按照模块只是维护自己的BFF就可以了。
当然,这样也会有问题的,比如跨横切面的一些逻辑,比如安全认证、日志监控、限流熔断等等,很多middleware的东西都在演进,随着时间的推移,代码会变得越来越复杂,技术债也会越堆越多。或者是新增一些功能需要更新到kit库,这些其实是非常复杂也会是有一定门槛的。所以从某种角度,这个BFF是一个api的Composer 组装服务的。所以,是不是考虑再加一层?
api gateway envoy
那是不是把这些横切面的再加出去?比如限流、鉴权等等公共组件。的确业内也有这么一个模型,就是把通用型的、经常需要升级的框架抽离出去, 把业务集成度+通用横切面的功能上沉,自己的BFF就专注于dataset join 数据集组装的功能,那这些放到哪里呢?可以把webserver踢掉,把他真正的API Gateway,就是真正的网关,他负责路由,比如流量落到哪一个BFF,负责统一的鉴权、认证、升级,还有一些安全性的功能,全部上沉,分层,一层负责通用功能,一层负责业务,我们叫做IDS。
所以在新的框架中,网关承担了重要的角色,它是解耦拆分和后续升级迁移的利器。在网关api gateway的配合下,单块BFF实现了解耦拆分,各个业务只需要维护自己的BFF,研发效率大大提升上去,因为前置的动作可以让运维或者中间件的同事去维护了,BFF就更加专注在逻辑交付,api gateway我们之前搞了一个0号团队,就搞这些公共的东西,实现了架构上的关注分离。有一个组件叫做Envoy组件。
所以流量的走向是客户端->API Gateway->BFF->Mircoservice
,当然,这里忽略了上游的CDN、4/7层负载均衡。
这样还有一个好处就是所有的微服务使用不同的语言,比如BFF可以是nodejs来做服务端渲染,之前纯前端是js操作html+dom的方式来操作浏览器页面的,这种模型其实效率不是最高的,如果使用nodejs把首屏需要完整的界面html全部构建出来一次返回,也就是接口一返回,浏览器立马就渲染出来了,就可以达到秒开的状态。当然也就是首屏,如果是滚动下滑的数据还是通过接口请求,js操作dom的方式展现。
微服务的划分
好了,到了大家非常感兴趣的一个问题,就是微服务如何划分了。这也是面向微服务架构的第一个问题,就是如何划分微服务的边界,也是产品、研发同学讨论的点。在实际项目里面,一般会采用两种不同的方式进行划分服务的拆分。第一种是通过业务职能(Business Capability)或者是DDD的界限上下文(Bounder Context)来进行微服务的划分。
- Business Capability,由公司内部不同部门提供的职能,比如客服部,他提供了客户服务的只能,财务部提供的订单、财务相关的财务相关职能。这也是可以理解的,比如售前、售后拆分。
- Bounded Context,当我们对业务模型不太熟悉的情况下,可以通过面相只能的划分方式,这也是大家理解的。如果是按照DDD限界上下文的划分不同业务边界的元素,我觉得DDD更像是一种架构的思路,对架构拆分、工程组织等等都是有帮助的。这里的业务边界含义是:“解决不同业务问题的问题域和对应的解决方案域,他是为了解决某种类型的业务问题,贴近领域知识,也就是业务”
这种本质也促进了组织结构的演进。
微服务的安全
最开始BFF开始是包含了API认证的功能,随着后续api gateway之后,会进行统一的认证拦截,认证不就是用户登录完成之后会获得一个token么,web端可能是一个cookie,本质也是一个token,然后每个接口都带上来,我们在api gateway里面进行解析拿到用户id,我们会放到rpc的metedata或者是http的htader里面传递,把数据带到BFF层。 这里还有一点就是我们的接口里面最好是不要带用户id,不然不就可以模拟用户嘛,所以用户id并非在请求里面,应该是在header里面,是注入进来的。当然,如果外网里面有人恶意注入的话是有一定风险的,所以在api gateway里面,通常会把这些header踢掉,然后再次注入掉新的,到BFF里面再重新把用户身份信息捞到。 所以BFF到其他下层的微服务中,建议是在RPC Request(传参数)中带入用户身份信息(UserID)请求服务。
对于服务的内部,微服务的内部,一般要区分身份认证和授权。比如一个用户购买完成后需要变成vip的请求,如果这个接口可以随便给人调用的话,那肯定是有安全风险的,所以内网一般来说是需要知道谁来调你,而且要需要调你哪些接口。有的甚至要做到参数加密。一般有这几种认证模式:
- Full Trust:完全信任,就相当于裸奔了,有一些查询请求可以做。
- Half Trust:半信任,也就是我知道你是谁,但是我不会对你限流。
- Zero Trust:0新人,也就是两个微服务之间既要做身份的认证,也要做通讯的加解密。
这些很多RPC框架都支持,比如gRPC就支持带证书或者带token的。
一般来说,简单的认证的就是基于token的,比如A调用B,A就可以颁发一个token给B,每次B带token过来鉴权,我就知道你的身份是什么,可以判断你对我哪些接口可以调用。 基于证书就比较复杂了,相当于有一个自己颁发的根证书,基于根证书给大家来颁发证书,证书里面包含了你的信息,可以知道你的消息,可以类似于https的证书。
当然在很高安全的领域,比如银行,肯定是做zero trust,任何的通信都会做加解密,比如不对称的加密,证书等等,很常见的做法,这些对性能确实有一定的影响。