刚做后端大概10个月,从游戏前端开发转向后端,看似熟悉的编程语言,在不同的领域内实际上要考虑的事情也是全然不同的。
当我们谈论后端开发,自然而然联想到,后端是服务于前端的,也是承载、服务于业务的一个重要组成部分。系统的稳定性,正确性以及可用性都是需要考虑的问题。
做后端,说简单也简单,说难也很难,简单是因为你只需要对数据进行增删查改,聚合统计就完事了,说难是因为一旦涉及到可用性,必然离不开分布式,然而我们知道,由分布式而引出的一系列问题才是最为棘手的,更不用说系统安全、部署、监控等一些列的落地措施。
你需要确保多个系统的业务数据不混乱,在某些跨服务功能的调用链中保持一致性,当某一个服务调用失败之后进行重试或者回滚。
重试需要你的服务具备幂等性,也就是说,当引入重试机制之后,你需要确保一个服务在同一个业务上下文中多次调用而不会产生额外的影响,例如银行转账,如果多扣几次钱或者多加了几次钱对于公司的业务以及信誉来说都是无法接受的,解决这个问题的办法需要引入类似流水号的关联标识,在不同的服务里面形成一个标识上下文。
当我们的整个后端系统根据业务领域分割为不同的服务独立成型时,多个服务之间相互调用往往会因为网络原因而失败。硬件损坏、宕机、自然灾害等一些列不可抗力因素告诉我们,任何事情都有可能失败,当你的系统规模超过阈值之后,偶然将会转变为必然。我们要的做的,不应该是想出一个万全之策强力约束保证我们的系统一定会成功,或者怀着侥幸的心理上线然后惶惶不安的等待系统出现问题。反而更应该思考一下,当系统出现问题之后,如何快速有效的修复失败所带来的影响。因为我们知道,既然我们无法保证100%成功,那唯一的出路便想出一个补救措施来预防失败,这往往会更令人更有安全感。
在讨论如何解决分布式一致性之前,我们更应该反过头来看一下,什么是事务,它在单机模式下是如何保证业务的原子性的。我们往往将业务事务和数据库的事务混为一谈,通常来说这是没问题的,因为后端做的不就是数据的存储么。但当我们把这两个进行区分的时候,会发现大多数的时候,我们所说的事务,仅仅是通过数据库提供的ACID来实现的。当你要对数据库的多个表进行操作的时候,打开一个事务,并且进行相应的更新,然后提交事务,此时要么成功,要么回滚撤销变更,通过数据库提供的ACID保证我们的数据不会因为受到中断的影响而产生混乱。
但如果我们分布式之后呢?每一个业务子系统拥有自己的数据库,你如何保证一个业务流程,在跨子系统调用时保证原子性呢?
如果我们把希望继续寄托于数据库,多个业务子系统使用同一个数据库,数据库本身将会成为瓶颈,这样便失去了一开始设计分布式系统的初衷。
如果某一个子系统失败之后,造成了数据的不一致,此时,要么回滚之前的操作,要么就想办法让这个失败的调用变为成功。
A作为业务发起者调用B和C,B成功,C失败,A知道C失败之后该怎么做呢?是重新调用一次C还是通知B回滚呢?如果通知B回滚的时候也失败了呢?
答案往往是不统一的,每一个业务领域都有根据自身情况进行处理的方式。有很多分布式事务协议能够解决些许问题,但是这往往会造成复杂度的提升,导致将来的维护以及拓展变得举步维艰。
CAP定理相信大家都有了解,在任何时候我们要么取CP要么取AP,CP的问题在于复杂、可用性以及可维护性差,AP的问题在于一定的时间内,数据会存在不一致性。如果选择CP,是时候考虑一下业务的分割是否合理,只有极少的情况我们才需要保证强一致性。
我们需要一种更为聪明的方法,一种机制来保证我们的系统能够达成最终一致性,这才是分布式系统需要解决问题的根本之道,并且已经有很多模式方法来帮助我们达成我们想要的结果。
换一个角度来讲,分布式事务,本身就是一个伪命题,因为系统是脆弱的,有太多太多的因素会造成事情不可控。
我们要做对的事情,让系统能够根据业务需求变更逐步演进,形成一个良性循环,根据业务需求、目标领域来设计系统,合理的进行分割才是王道。
如果你的系统本身不匹配业务领域,而又把不合理、生搬硬套的架构应用于它,就会出现各种各样的问题,这些问题在某些时候甚至是不可能解决的。
在我看来,有关于解决分布式事务的技术,无非是在为糟糕的设计进行弥补,导致系统到最后臃肿不堪。
一件事情,分布式系统在绝大多数时候,反脆弱、最终一致性才是我们真正需要去解决的问题。
PS:你的系统究竟有多大才会导致最终一致性会被人为的察觉出来?但我们知道数据最终将会一致不是吗?:)