目录
本章主要讲述关于微服务架构的一些常规的设计模式,这可以对你架构微服务起到一定的启示作用,但是并非需要严格按照设计模式来执行,可根据自己的实际情况做相应调整。
如果你想对微服务架构设计模式有更详细更深入的了解,笔者推荐《微服务架构设计模式》一书,里面针对每个设计模式有更加详细的描述。本章节是笔者阅读完此书后,节后自己在项目中实践得出的一点总结,希望能够对读者有一定的帮助。
1. 独享数据库 vs 共享数据库
上图是一个微服务架构图,可以看出每个服务都有自己的数据库,服务之间的数据存储相互独立,这也是笔者看到的大多数资料推崇的方式。但是笔者在跟同行交流的时候也遇到使用共享数据库的方式来做微服务架构的,他的理由是,数据放在一起在一些聚合查询的时候非常方便,数据库隔离就会出现前面章节说的查询不方便的问题。
这位同行是如何做的呢?不同的服务使用不同的账号来管理数据库,订单服务只能看到订单相关的数据表,有一个总账号可以看到所有的表,对于复杂查询需要聚合的数据,在单独的聚合服务中处理,通过总账号直接聚合所有的表进行聚合查询即可。这样子做,服务负责人员只需关心自己的表,也无权限看到多余的表,还可快速进行聚合查询,确实方便。
确实,共享数据库能够在一定程度上解决查询的问题,但是笔者在经历的大部分微服务项目还是采用的隔离数据库的方式。主要是以下理由:
1)数据库压力大。所有的数据放在一起必定会造成数据库压力大,假设一个服务由于自身的原因(比如索引没有建好)导致了数据库负载过高,必定会影响其余的服务,导致其余的服务同样无法提供工作。
2)扩展不方便。一个系统一定有的服务会对数据库造成较大的压力,一些服务数据量或者访问没有那么频繁,对数据库压力没有那么大,如果采用分库的方式,可以给重型的服务数据库采用更好的配置,其余的给较低的配置,灵活控制。
3)服务之间存储隔离。甚至服务可以采用易购的方式,也许有些服务用node.js+mongoDB,有的用pgsql,有的服务用mysql,服务间相互不受影响,可根据服务特点自由选择合适的数据。
因此,你是可以根据自己项目的实际情况采取适合自己的方案的。当然,笔者更推崇独立数据库的方式。
2. RPC/HTTP
服务间同步通信一般采用http或者rpc的方式。http和rpc相比有哪些优缺点笔者就不再赘述了,网上一搜一大把的资料。如果使用rpc的话,目前gRpc是最火的,可以去了解下。像笔者经历的一个PHP工程项目,就是用的yar来做rpc通信,原因很简单,因为大家不太愿意花时间去写proto文件,为了快速响应,直接选择了yar。如果性能不是问题,你也可以直接用http,更加方便。
3. 事件驱动
事件驱动架构对于微服务架构是至关重要的,有事件驱动你可以做到服务间的解耦,通过异步通信可以更好的保护服务,做到削峰填谷。可以通过事件驱动架构来解决分布式事务问题和做CQRS。关于事件驱动架构后续文章会有详细介绍。
4. CQRS
CQRS:Command Query Responsibility Segregation,查询命令分离。说简单点,写入库和查询库分开。如上图所示,写入操作放入写入的数据库,同时,通过事件机制,异步将数据变更程序通知对应数据处理程序,数据处理程序重新清洗聚合数据后,写入一个聚合的数据表,供复杂的数据查询。这就是微服务解决查询困难的解决方案。
这里也反应了事件机制对于微服务架构的重要性了。
关于CQRS,后面会有专门的章节进行详细介绍。
5. 分布式事务
分布式事务是笔者认为微服务架构非常令人头疼的一个问题,原本简单的ACID,变得异常复杂。我们以最经典的转账为例,假设A给B转账100快,以本地事务而言非常简单,如下:
begin;
update amount=amount-100 where id=a;
update amount=amount+100 where id=b;
# 如果b账户、a账户一切正常,commit
commit;
# 假设b账户被冻结了
rollback;
简单处理就这几行代码,可以保证这100块要么转成功,要么失败。
那如果是跨城市转账呢?A和B之间账户在不同城市使用不同数据库不同服务,那么流程就如下:
我们以最简单的A账户B账户都正常,可以转账为例,A执行了扣除100,调用B服务,如果B服务异常,那么A应该怎么操作?有的人会说那简单,A再还100回去不就行了么,但是没有那么简单。
1)如果A在准备还100的时候服务挂掉了,那代码未执行,A会少100,不行
2)那如果我不commit,直接调用B服务,发现B服务异常就执行回滚不就行了么
笔者以血的教训告诉你,不要这样子弄,微服务架构中,你的事务提交尽可能快,不要等调取别的服务成功了再提交或者回滚,这样你会很容易把自己数据库弄挂,很容易导致长事务挂起,这会导致数据库压力服务器压力都剧增,因为你不知道对方服务的质量,假设B服务要5s返回,你的事务会至少挂起5s。
3)假设B服务保证,我绝对可以在1s内完成,用2)的方式也是没办法保证事务的一致性的,因为B服务的异常不一定是真的异常,有可能是网络原因导致的A未能正常接收到B服务的返回结果。什么意思?就是说B服务接收请求后已经正常执行了,B账户也加了100块,这时候AB发生了网络分区,A无法正常接收到B服务的结果,认为B服务异常了,于是回滚了,A相当于没有扣钱,B多了100块
发现了没,就两个服务就有这么多的问题,更何况复杂系统很容易出现A->B->C->D这样的链路。
分布式CAP理论和BASE理论很好的解释了这个问题,需要读者先去了解一下。
对于java系的童鞋来说可以选择用阿里seata,里面有XA、TCC、SAGA的解决方案,让你可以解决分布式事务的问题。非java系在很长一段时间基本都是通过事件的机制来做最终一致性。现在有了DTM可以解决这个问题,它是一个跨语言的分布式解决方案。后续章节对于分布式事务解决方案会有更为详细的了解,比如XA、TCC、SAGA是什么?业内主流的解决方案有哪些?DTM是怎么做分布式事务的。
6. BFF
BFF:Backend For Frondend,为前端服务的后端。读起来比较绕,其实就是解决一个各端逻辑数据差异化的问题,为各个端提供单独的BFF层服务,该层服务只做简单的请求验证,权限认证,然后调取对应的业务服务,再把数据组装后返回给前端就可以了。如下图所示:
7. API网关服务治理
前面的章节也简单介绍了服务治理的概念,本章后续会对apisix网关有更详细的介绍,这里不在赘述。
8. 配置中心
单体架构的配置基本都是放在框架的配置文件里面,以文件的形式存在,要修改配置就直接修改文件即可。但是微服务架构这样子做是不太建议的,为什么呢?笔者公司的一个项目就是采用了这样的方式,某一天公司需要将整个项目复制一份,给客户做私有化部署,刚好这个任务落在笔者身上,由于这个项目历时比较久,项目配置很多,每个服务有大量重复的配置,并且服务的实现方式还不一样,配置需要修改的地方也不一样,就为了修改配置,笔者需要把所有项目拉下来,一个一个修改,大量重复的工作,光修改配置都搞了好几天。但是有配置中心就不一样了,有了配置中心,每个服务的配置都从配置中心取,要替换配置直接改一个地方就所有服务都修改了,十分方便。因此,笔者强烈建议使用配置中心的方式来管理你的配置,比如使用appolo,阿里云acm,etcd,nacos都可以,读者可根据实际情况自行选择。