聊聊微服务的分布式通讯

板桥里人

  微服务目前比较热,但是微服务最难的还是可靠性问题,因为一个系统微服务可能几百个,网络调用频繁,网络的容错性就非常重要,因为对于分布式系统,需要默认网络环境是不可靠的,丢包或堵塞等情况都是可能会发生的,这里面其实就是经典的拜占庭将军问题,两个将军想约定某个时候一起进攻,但是不能确保这个信息能否可靠地传递给对方,是路途耽误了还是送信的人死了永远不可能送达,都无法确定,网络之间的通讯也是如此,A给B发个TCP数据包,这个数据包是因为网络繁忙暂时堵塞,还是就是被丢弃了呢?双方都不知道。
当然有不少人使用Dubbo这样的开源分布式服务框架,Dubbo使用RPC实现服务之间同步调用,RPC实际是Java中一种远程调用方式,RPC也是无法避免网络通讯问题,如果A服务更新了数据库,而调用B服务时网络出错,或者B服务内部调用数据库时出错,但是A服务中数据库已经更新,这就可能发生业务上两者数据库不一致了。
所以,只要涉及到分布式系统,包括分布式服务,分库分表,甚至Web这种浏览器前端与后端之间调用,都需要根据CAP定理进行权衡。
很多人习惯进行服务的同步调用,其实这是和通常方法调用习惯导致,分布式系统中同步调用好像很快,其实非常脆弱,网络容错性差,一旦网络堵塞,同步调用RPC就容易失败。因此,在分布式系统中,异步调用好像是异步,没有同步快,实际是未必然,异步调用是CAP中的C强一致性和A可用性的妥协,使用最终一致性获得可用性的提高,同时又能够保证一定的网络容错性;而RPC同步调用,则是选择了CAP中C和A,类似2PC分布式事务,但是网络容错性很差,网络稍微有点堵塞,RPC调用就会受到影响,如果RPC调用嵌套了好几个,某个点的网络堵塞会点爆整个调用链条的崩溃。
所以,微服务架构中,跟踪和恢复变得非常重要,当然这些通过专门的跟踪工具实现,但是这些都是事后控制了。
那么微服务之间的通讯推荐使用异步方式,如何具体实现呢?首先通过异步消息实现微服务之间调用,在架构设计是松耦合的,可见我的另外一篇《软件架构的灵活设计》,另外,异步不代表性能慢,因为在分布式系统中,整体快慢是取决于最慢的那个环节,所以,整体性能是讲究效率的,而且这个性能快慢实际就是延迟与可用性问题,又回到前面CAP问题上。
那么异步消息传递在两个服务之间传递什么呢?当然是传参,这样两个微服务之间通过服务调用进行参数传递,实现数据共享,不必将彼此的数据库共享给对方访问,微服务拥有自己的独立数据库是微服务严格定义中的重要特征。
目前消息系统有三种消息传递方式:有至少一次传递;至多一次传递;正好一次传递。
至少一次意思是一个消息至少传递一次以上,当然会造成消息内容重复冗余,但是可靠性提高了;而至多一次是服务器的消息最多传递一次,如果再传递一次,就会造成负面影响,比如会重复执行一些下游的非幂等的服务。正好一次是通过消息接收方发送确认收到的方式试图保障每次消息传递都能可靠传递完成,但是在理论上认为这是不可能的,因为这个发送、收到和确认的过程中一旦出现问题,就无法保证传递完成。
其实如果我们从另外一个角度来看消息传递,从网络数据广播角度看,服务器之间实现原子广播是否可能?Kafka(卡夫卡)的创始人Jay Kreps发表过专门一篇文章谈论这个问题,他认为原子广播相当于consensus共识,因为共识可能是分布式系统中研究最多的问题。共识是否可能?其实这是众所周知的算法主攻的问题,如Paxos和Raft已经在现代分布式系统实践中得到广泛实现。最后他得出结论:共识是现代分布式系统发展的支柱。卡夫卡其中心抽象是分布式一致的日志,实际上是您可以想象成最纯粹的类似于多方共识的模拟。所以如果你不相信共识是可能的话,那么你也不相信卡夫卡是可能的,在这种情况下,你不用担心卡夫卡的正好一次支持的可能性!

那么使用卡夫卡如何实现类似正好一次的消息传递? 关键是将偏移量和你要保存的状态通过JDBC事务或者JTA事务保存到数据库,失败恢复时从这个偏移量开始从卡夫卡中重新读取,保证了消息和你的业务状态数据的一致性。也实现了有效effectively地消息传递,,实际病不是严格意义上的正好一次,但是这在实践够用了,注意,可别使用完美主义看待这个问题,否则你永远得不到可行的解决方案

 

为什么分布式微服务很难?