我们是Sequin ,我们为 Postgres 添加了流式传输功能。我们最近在GitHub 问题上就传递语义展开了辩论。下面,我们进一步阐述我们的看法。
我们说 Sequin 是一个具有“至少一次传递”和“恰好一次处理Exactly-once”保证的系统。
传递和处理之间有什么区别?为什么我们不认为 Sequin 是一个“精确一次传递”系统?
最多一次传送 vs 至少一次传送
最多一次传送是许多发布/订阅系统提供的功能。最多一次传送系统中的消息是短暂的。发布者会大量发送消息。如果订阅者未订阅或传送过程中出现网络问题,订阅者将错过该消息。
在至少一次传送系统中,系统保证传送。为此,它会保留消息及其传送状态。在收到接收方已收到消息的确认之前,它不会认为消息已传送:我们说“至少一次”,是因为消息总是有可能被重新发送。接收方可能会收到一条消息,但在数据库和接收方确认收货之前,会发生中断:这是两阶段提交或分布式事务问题。发送者需要提交消息已传递到两个地方的事实:接收者和数据库。
精确一次传送
这就是为什么一次交付是一个柏拉图式的理想,只能渐近地接近。 你不可能在两个不同的物理位置上以交易方式翻转两个比特,并保证 100%。 但你可以通过一些努力来接近。 例如,在向两个地方提交之前,可以让两个系统都做好准备。 如果你与数据库之间有一个开放的事务,并且最近验证了与接收方的 TCP 连接仍然良好,那么你就真正提高了胜算。
但完美是不可能的,这就是为什么我们不说 Sequin 可以在任何地方提供一次送达服务。 处理是指整个信息的生命周期:信息传递给接收方,接收方完成工作,然后接收方确认信息。
根据这一定义,SQS、Kafka 和 Sequin 都是保证一次exactly-once处理的系统。
当然,确认并不能消除两阶段提交的问题。 例如,试想当一个 Worker 处理一条消息时,它会执行发送电子邮件等副作用。 它很有可能接收到消息,发送了邮件,但随后由于网络错误而无法确认消息。
在这些系统中,该信息将被重新处理,从而产生两封已发送的电子邮件!
在我看来,"最多一次发送 "和 "最少一次发送 "这两个术语有助于我们区分发送机制。 而 "精确一次处理 "一词则表明这是一个具有至少一次发送和确认功能的信息传递系统。
无论如何,我希望这能帮助你认识到一个信息传递系统只能帮你到这里。 作为客户,您需要设计自己的系统,以从容应对这些边缘情况。
只有您能防止后处理问题
鉴于两阶段提交问题,您可以采取一些措施来减少后处理问题。 最重要的是,要考虑到您的设计不可能达到 100% 这一事实。
在系统设计中,您有三种总体选择:
- 1. 接受重新传递带来的后果(如错误)
- 2. 让你的系统具有惰性,这样重新传递就没问题了
- 3. 选择 "最多投递一次",接受某些邮件永远不会被投递的事实!
例如,如果你是一家银行,你不能错过一笔交易,所以你会让你的系统成为惰性系统。 如果您正在进行分析,并决定宁可错过一个事件也不计算两次,那么您就会倾向于最多计算一次。
其次,根据需要将信息分解成工作单元。 如果一条消息会导致 10 个副作用,那么请注意 Worker 可能会在该过程中的任何时候崩溃,从而使系统处于不一致的状态。 这并不意味着每条消息都应对应一个操作或副作用。 但这确实意味着,你应该考虑如何让多步骤工作流程等同于empotent。
最后,正确配置超时。 在 SQS 和 Sequin 中,你可以配置消费者的可见性超时。 这指定了消息传递后系统等待多长时间,直到它认为该消息符合重新传递的条件。 这样做的目的是,如果系统没有收到 Worker 的回复,就会认为该 Worker 崩溃了,从而重新发送消息。
你需要将可见性超时设置为一个保守的数字,以便给 Worker 足够的时间来完成工作。 此外,如果运行时允许,还需要为 Worker 的处理时间设置一个低于可见性超时的硬超时。 这样,当可见性超时过期时,你就能确定 Worker 已经宕机。