在 MQTT 协议中,给出了三种传递消息时能够提供的服务质量标准,这三种服务质量从低到高依次是:
-
At most once
: 至多一次。消息在传递时,最多会被送达一次。换一个说法就是,没什么消息可靠性保证,允许丢消息。一般都是一些对消息可靠性要求不太高的监控场景使用,比如每分钟上报一次机房温度数据,可以接受数据少量丢失。 -
At least once
: 至少一次。消息在传递时,至少会被送达一次。也就是说,不允许丢消息,但是允许有少量重复消息出现。 -
Exactly once
:恰好一次。消息在传递时,只会被送达一次,不允许丢失也不允许重复,这个是最高的等级。
这个服务质量标准不仅适用于 MQTT,对所有的消息队列都是适用的。我们现在常用的绝大部分消息队列提供的服务质量都是 At least once,包括 RocketMQ、RabbitMQ 和 Kafka 都是这样。也就是说,消息队列很难保证消息不重复。
出现消息重复可以用幂等性解决,同样接口重复请求也可以用幂等解决
什么是幂等
幂等本来是数学上的概念,它的定义是这样的: 如果一个函数 f(x) 满足:f(f(x)) = f(x),则函数 f(x) 满足幂等性。比如,求绝对值的函数,abs(x) = abs(abs(x))。
在计算机领域用来描述一个操作、方法或者服务。一个幂等操作的特点是,其任意多次执行所产生的影响均与一次执行的影响相同。
场景
将林志玲账户的余额加 100 元
方法一:利用数据库的唯一约束实现幂等(利用数据库实现幂等性)
我们在数据库中建一张转账流水表,这个表有三个字段:转账单 ID、账户 ID 和变更金额,然后给转账单 ID 和账户 ID 这两个字段联合起来创建一个唯一约束,这样对于相同的转账单 ID 和账户 ID,表里至多只能存在一条记录。
方法二:为更新的数据设置前置条件(将方法实现幂等)
- 方法入参传入林志玲的账户余额,拿参数中的余额与数据库中的余额做比较如果相同则执行变更操作。
- 最简单的做法给数据增加一个版本号属性,每次更改数据前,比较当前数据的版本号是否和消息中的版本一致,如果不一致就拒绝更新数据,更新数据的同时将版本号 1。(和乐观锁原理一样)
方法三(建议使用此方法): 令牌机制(全局ID) (记录并检查操作)
在发送消息时,给每条消息指定一个全局唯一的ID,消费时,先根据这个ID检查这条消息是否有被消费过,如果没有消费过更新数据,然后将消费状态置为已消费。
方式一: 服务器端派发token并将token记录在缓存中, 客户端携带此token请求服务, 如果此token有效证明是有效请求,处理请求, 删除token。token无效忽略此请求。
方式二: 客户端请求携带一个唯一标识(流水),服务器判断此流水是否已经存在,如果已存在忽略此请求。