美文网首页
为什么我们想要业务主键,想要幂等

为什么我们想要业务主键,想要幂等

作者: ShootHzj | 来源:发表于2020-08-19 15:51 被阅读0次

为什么我们想要业务主键,想要幂等

​ 在分布式微服务场景下,有太多的环节可以引发错误的处理(包括丢失或者重复处理),如果业务本身有幂等的特性,我们可以以较低的代价解决大部分问题。

​ 我们假设我们在k8s集群中维护着下面的系统,部署了数个网关实例,数个业务处理服务,一套Kafka集群,数个消费者服务,一个数据库,平时的业务流程是这样子的:

客户--->(step1) 网关--->(step2) 业务处理服务-->(step3)消息中间件如Kafka-->(step4)消费者消费-->(step5) 写入数据库

而我们希望做到的是,在海量数据量的情况下,仅仅付出较小的代价,使得客户的每一条消息都存入到我们的数据库,没有丢失或者重复。

在分布式的环境下,有很多地方都会出错,会引发消息的丢失或者重复,我们先假设上述系统没有做太多的可靠性加固,是如下工作的:

1. 客户简单地发送到网关
2. 网关发送到后端处理服务
3. 后端处理服务接收到请求,做简单校验后直接返回成功/失败,异步发送消息到Kafka
4. 消费者从Kafka拉取消息,拉取到消息就提交,不考虑写入数据库的成功或失败
5. 写入到数据库

举几个例子,那么我们会碰到类似这样的问题:

1. 客户发送到网关不考虑结果,即时我们返回失败,也不处理。显然会丢失消息,我们的系统并不能保证100%的消息处理成功
2. 网关在与业务处理服务的TCP的四次挥手阶段处理异常,导致业务处理成功(已发送到Kafka),但实际返回客户失败。导致消息重复
3. 消费者拉取到消息后,写入数据库失败,但此时offset已提交。消息丢失

我们先不考虑客户,先看平台侧,我们构筑了一个什么样的系统呢?

它既不能保证至少发一次(AT_LEAST_ONCE),也不能保证最多发一次(AT_MOST_ONCE),我们构筑了一个即可能多发也有可能少发的系统,当然,现在的基础设施很好,照着这样跑,可能丢失的数据也就是万中一二,但我们做技术,还是要有点追求,实现别人难以实现的事情才是我们的竞争力。

平台如何实现精确一次

实现端到端一次的技术核心是两阶段提交,以上述业务流程为例,实现了两阶段提交的流程应该是这样的:

1. 消费者从Kafka消费到一批数据,但并不commit提交
2. 消费者向数据库prepare这批数据
3. 消费者向Kafka提交Offset
4. 消费者向数据库commit这批数据

按照这个流程,还会有一些异常场景,比如

1.消费者向Kafka提交Offset后,突然宕机,重启的消费者无法恢复事务,消息丢失
2.消费者commit失败,消息丢失

第二种情况属于极端场景,因为我们执行的业务简单,只是insert操作,prepare成功,commit失败,可以打日志,报告警人工处理。但第一种情况是需要自动化解决的,因为我们不能对每条提交失败的事务都人工处理。那么我们需要的是:

我们需要消费者在进程级失败的时候,可以判断处于prepare阶段的事务是否需要恢复,这个可以对比Mysql,Mysql也使用了两阶段提交协议,每次重启的时候会判断redolog处于prepare状态,binlog是否完整,如果binlog完整,则恢复。这里redo log就像我们的数据库,binlog就是kafka。但我们现在的业务没有主键,每条消息都是独立的,我们无法区分,那些是要被正常放弃的事务,那些是重启的时候需要恢复的事务。这种情况我们无法处理。

但如果这时候我们有了主键,我们有了幂等,我们只要做到至少一次就可以保证平台达到端到端一次。因为多次向数据库插入相同的数据,并不会发生什么事。

平台如何实现至少一次

实现至少一次的核心技术是如果处理不成功就打死不提交,报告警等人工操作都不提交! 其他队列,缓存区,滑窗只不过是提升性能的手段。

1. 消费者从Kafka消费到一批数据,但并不commit提交
2. 消费者向数据库prepare这批数据
3. 消费者向数据库commit这批数据
4. 消费者向Kafka提交Offset

接下来我们来看与客户的通信,这次我们先看如何做到至少一次

与客户侧通信如何做到至少一次

前面说到客户可能不处理你的返回值,碰到这样的客户其实是你赚了,客户的系统连这个都不重视,那想必也不会在意你是否做到了端到端一次吧,丢失了数据想必也不是特别在意。一个端到端一次的系统,一定需要输入端和输出端的配合,正如我前面举例:

客户发送到网关不考虑结果,即时我们返回失败,也不处理。显然会丢失消息,

那么我们也需要客户的配合,客户检测到我们回复的结果是失败,重试一下,确保成功。现在我们已经实现了至少一次了吗?

没有,还记得我前面说的这个吗?

3. 后端处理服务接收到请求,做简单校验后直接返回成功/失败,异步发送消息到Kafka

我们需要后端处理服务接收到请求后,确保发送Kafka成功,再回复用户成功

与客户侧通信做到精确一次

假设前面的方案我都已经做了,在什么情况下我们会重复呢?

1. 网关在与业务处理服务的TCP的四次挥手阶段处理异常,导致业务处理成功(已发送到Kafka),但实际返回客户失败。导致消息重复
2. 业务处理服务与网关的TCP的四次挥手阶段处理异常,导致业务处理成功,但实际返回客户失败
3. 客户的系统重启,成功应答并没有成功传达

为了实现两阶段提交协议,客户在发送前需要确认这个消息是否已经处理过了,但是没有主键,我们无法提供给客户这样子的信息。(如果考虑性能的话,整个系统端到端的事务,基本与高吞吐无缘了)
而且,两阶段提交协议也需要客户做很多的工作,实际中也很难落地。

总结

  • 我们需要一个业务上的主键,它可以是组合主键(mysql, mongo),或者是single主键(更适合cassandra和redis),使得我们可以提供更高QOS的保证,为此,仅需付出极小的代码,可能仅仅是数据库的主键一致性检查。
  • 如果系统和业务无关,任谁也难言真正的端到端一次。Flink无疑是流系统里面端到端一次的佼佼者,但上面也有着诸多限制。

备注

  • 事实上,要实现精确一次,系统的每两个环节之间都要做两阶段提交,为行文方便,省去网关,业务处理服务之间的两阶段提交
  • 推荐书籍 《基于Apache Flink的流处理》

相关文章

  • 为什么我们想要业务主键,想要幂等

    为什么我们想要业务主键,想要幂等 ​ 在分布式微服务场景下,有太多的环节可以引发错误的处理(包括丢失或者重复处...

  • 3.3:幂等设计

    在分布式场景下,我们经常会有需要实现幂等的场景,幂等分为请求幂等和业务幂等。 请求幂等 下面将从以下三步来整理请求...

  • MySQL几条实用的SQL:

    如果主键重复则删除再插入,某种情况能保证数据幂等性REPLACE INTO:基于主键来判断是否存在示例:REPLA...

  • 03知识点

    05服务幂等设计 幂等定义 请求层面幂等 保证请求重复执行和执行一次的结果一致 业务层面幂等 同一用户不重复下单 ...

  • 分布式id

    20210825 首先介绍下代理主键和业务主键 代理主键:表中单独建个id字段作为主键 业务主键:使用有业务含义的...

  • SpringBoot接口幂等性实现的4种方案!

    目录 什么是幂等性 什么是接口幂等性 为什么需要实现幂等性 引入幂等性后对系统的影响 Restful API 接口...

  • 接口幂等性(重复提交)

    幂等接口就是多次调用不会影响到系统。 数据库唯一主键 数据库唯一主键的实现主要是利用数据库中主键唯一约束的特性,一...

  • 支付业务的幂等

    听到幂等性这个词时,是不是内心一阵恐慌?What?幂等性是个什么鬼?测过相关支付的业务,但没听过幂等性啊?别方,其...

  • 如何成为杨幂?

    “为什么要成为杨幂?” “因为可以和张震谈恋爱啊!” “想要成为杨幂”这件事非常值得理解 成为美女,人生就等于开了...

  • 94 - 实战之通用的接口幂等框架(设计篇)

    跟限流框架类似,幂等框架的功能性需求也比较简单,但要考虑处理的异常情况有很多,比如业务代码异常、业务系统宕机、幂等...

网友评论

      本文标题:为什么我们想要业务主键,想要幂等

      本文链接:https://www.haomeiwen.com/subject/xoxdjktx.html