这是我参与「第五届青训营 」伴学笔记创作活动的第 12 天
分布式架构的应用,虽然为现代高性能架构提供了后端的服务支持,但是也引入了著名的拜占庭将军问题。两将军问题
两将军问题的场景:有两个将军要去攻打一座城池,但是只有当他们同一时刻一起攻打城池时他们才能取得胜利,如果只有一个将军攻打那么一定会失败,如果没人出兵,就什么都不会发生。问两将军该如何派遣信使传输信息才能保证他们一定出兵而且不会失败。具体情况如下图。
先说结论:理论上在问题限制的简单情景中不存在一种解法解决该问题。非严格的论证方式如下,先放上一张真值表:
问题一:假设我们使用最直观的方式来保证信息的传递, 即张将军派出信使后,需要信使在传递完消息后返回张将军的军营进行确认,只有这样张将军才会出兵。对张将军而言,只要信使不回来,他就不出兵,但是这对李将军来说是个困扰,因为信使不回去的可能的原因有两种,一种是还没到李将军的军营就挂了,两边都不出兵,什么都不会发生,第二种是信使到李将军的军营了但是回张将军军营的时候挂了,这时候李将军会遵守约定出兵,但是张将军却不会出兵(因为信使没回去),李将军就会输。如下图
我们可以尝试多种方式改进该方法:如果张将军的信使每回来,张将军就一直派新的信使,很显然这种方法只是提高了成功的概率,有可能敌人太强,张将军最后只剩自己都不一定能得到肯定的答复;张将军和李将军提前达成口头协议,即张将军只要派出信使就一定出兵,不管信使回来否,乍一看好像这种方法可行,但实际上只是把最基础情况中张将军和李将军的角色进行了调换。当然读者可以尝试更多的方法但无论怎么约定,在问题的情境中,都无法绝对保证只要出兵就能胜利。
多将军问题
多将军问题中信息的传递仍然可以被篡改,同时引入“叛徒将军”概念,即叛徒将军可以按自己的要求随意发送信息,问题是如何保证所有忠诚的将军的行为是正确的。
以最简单的三将军为例,每两个将军之间两两通信,显然全是叛徒和全是忠将的情况下忠将们的行为都是一致的,有两个叛徒的情况下,唯一的忠将做出的选择全部取决于叛徒传递给他的消息, 肯定是错误的。如果只有一个叛徒,两个忠将也无法达成一致,因为当两个将军的选择不同时,叛徒可以利用这一点给两将军发送和他们的选择一样的信息,这时两个将军就会做出不同的选择。综上所述,只要有叛徒三将军就没有解法。
当然我们可以再加入一个将军,在四将军的情况下,我们任命其中一个将军负责传递他的判断给剩下的三个将军,同时剩下的三个将军还需要他们三个人之间就刚传递的消息再次进行一次协商。在这种情况下,假如负责传递信息的将军是叛徒,不管他怎么瞎传递消息,剩下三个忠将的行为都会保持一致,另外如果被传递消息的将军是叛徒,由于他只有一个人,所以他传递的消息即使经过篡改也不会对剩下的两个将军造成影响。但是我们会发现一旦叛徒数量增加,该问题就无解了。
事实上我们必须保证忠将的数量大于2/3,同时有一个传递消息的将军,和相应的多个阶段的协商才能保证拜占庭问题有解,具体证明见论文Practical byzantine fault tolerance and proactive recovery。
当然了实际的生产中,我们的大部分系统都不是拜占庭容错的,而是通过引入其他的方法,破坏问题定义的限制条件来达到保证消息正确性的需求,比如TCP就是拜占庭将军问题的一个工程解。
老师的两个问题
- 为什么TCP是三次握手?不是两次?
因为建立双向的通道至少要三次:C端发起同步请求一次,S端返回与C端建立连接的确认,同时还有和C端建立连接的同步请求一起算一次,最后一次是C端发给S端,S端与C端的连接已经建立一次,一共三次。两次不够两者都确认对方和自己建立了连接,更多次没必要。 - TCP中FIN报文丢失了怎么办?
实际上这点不同的系统可以做不同的设计,一般来说假如C端发出的第一次FIN丢失,C端可以选择超时终止连接,因为S端一般对客户端单一连接的保持时间有限制,所以影响不大。或者C端也可以尝试在一定时间内重发几次FIN报文,看个人实现;如果是S端的回复性FIN报文丢失,服务端通常情况下都会选择超时终止连接。这个问题实际上可以看业务要求,每个业务的需求可能不同。