在分布式系统中使用网络进行通信确实是一种不可靠的方式,消息的发送者只能知道掌控当前节点,所以没有办法保证传输渠道的可靠性,网络超时这种常见的通信错误极大地增加了分布式系统通信的复杂度,我们可以对网络提供的基本传输能力进行封装,保证数据通信的可靠性。
网络请求由于超时的问题,消息的发送者只能通过重试的方式对消息进行重发,但是这就可能会导致消息的重复发送与处理,然而如果超时后不重新发送消息也可能导致消息的丢失,所以如何在不可靠的通信方式中,保证消息不重不漏是非常关键的。
我们一般都会认为,消息的投递语义有三种,分别是最多一次(At-Most Once)、至少一次(At-Least Once)以及精确一次(Exactly Once)。
1. 最多一次(At-most-once)
最多一次其实非常容易保证的,UDP 这种传输层的协议其实保证的就是最多一次消息投递,消息的发送者只会尝试发送该消息一次,并不会关心该消息是否得到了远程节点的响应。
无论该请求是否发送给了接受者,发送者都不会重新发送这条消息;这其实就是最最基本的消息投递语义,然而消息可能由于网络或者节点的故障出现丢失。
这本质上是一种尽力而为的方法。
2. 至少一次(At-least-once)
为了解决最多一次时的消息丢失问题,消息的发送者需要在网络出现超时重新发送相同的消息,也就是引入超时重试的机制,在发送者发出消息会监听消息的响应,如果超过了一定时间也没有得到响应就会重新发送该消息,直到得到确定的响应结果。
对于最少一次的投递语义,我们不仅需要引入超时重试机制,还需要关心每一次请求的响应,只有这样才能确保消息不会丢失,但是却可能会造成消息的重复,这就是最少一次在解决消息丢失后引入的新问题。
3. 精确一次(Exactly-once)
虽然最少一次解决了最多一次的消息丢失问题,但是由于重试却带来了另一个问题 - 消息重复,也就是接受者可能会多次收到同一条消息;从理论上来说,在分布式系统中想要解决消息重复的问题是不可能的,很多消息服务提供了正好一次的 QoS 其实是在接收端进行了去重。
消息去重需要生产者生产消息时加入去重的 key
,消费者可以通过唯一的 key
来判断当前消息是否是重复消息,从消息发送者的角度来看,实现正好一次的投递是不可能的,但是从整体来看,我们可以通过唯一 key
或者重入幂等的方式对消息进行『去重』。
消息的重复是不可能避免的,除非我们允许消息的丢失,然而相比于丢失消息,重复发送消息其实是一种更能让人接受的处理方式,因为一旦消息丢失就无法找回,但是消息重复却可以通过其他方法来避免副作用。