关于这个分布式服务的幂等性,这是在使用分布式服务的时候会经常遇到的问题,比如,重复提交的问题。而幂等性,就是为了解决问题存在的一个概念了。
什么是幂等
幂等(idempotent、idempotence)是⼀个数学与计算机学概念,常⻅于抽象代数中。
在编程中⼀个幂等操作的特点是其任意多次执⾏所产⽣的影响均与⼀次执⾏的影响相同。幂等函数,或 幂等⽅法,是指可以使⽤相同参数重复执⾏,并能获得相同结果的函数。这些函数不会影响系统状态, 也不⽤担⼼重复执⾏会对系统造成改变。例如,“setTrue()”函数就是⼀个幂等函数,⽆论多次执⾏,其结 果都是⼀样的,更复杂的操作幂等保证是利⽤唯⼀交易号(流⽔号)实现.
接⼝幂等性就是⽤户对于同⼀操作发起的⼀次请求或者多次请求的结果是⼀致的,不会因为多次点击⽽ 产⽣了副作⽤。
什么是接口的幂等性
在HTTP/1.1中,对幂等性进行了定义。它描述了一次和多次请求某一个资源对于资源本身应该具有同样的结果(网络超时等问题除外),即第一次请求的时候对资源产生了副作用,但是以后的多次请求都不会再对资源产生副作用。这里的副作用是不会对结果产生破坏或者产生不可预料的结果。也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。
不能保证幂等性的操作
- 前端重复提交表单:在填写一些表格时候,用户填写完成提交,很多时候会因网络波动没有及时对用户做出提交成功响应,致使用户认为没有成功提交,然后一直点提交按钮,这时就会发生重复提交表单请求。
- 用户恶意进行刷单:例如在实现用户投票这种功能时,如果用户针对一个用户进行重复提交投票,这样会导致接口接收到用户重复提交的投票信息,这样会使投票结果与事实严重不符。
- 接口超时重复提交:很多时候 HTTP 客户端工具都默认开启超时重试的机制,尤其是第三方调用接口时候,为了防止网络波动超时等造成的请求失败,都会添加重试机制,导致一个请求提交多次。
- 消息进行重复消费:当使用 MQ 消息中间件时候,如果发生消息中间件出现错误未及时提交消费信息,导致发生重复消费。
如果放到数据库的操作层面,那么就有很多操作需要去保证幂等性了。
- A: 查询操作
查询对于结果是不会有改变的,查询⼀次和查询多次,在数据不变的情况下,查询结果是⼀样的。 select是天然的幂等操作
- B: 删除操作
删除⼀次和多次删除都是把数据删除。(注意可能返回结果不⼀样,删除的数据不存在,返回0,删除 的数据多条,返回结果多个,在不考虑返回结果的情况下,删除操作也是具有幂等性的)
- C: 更新操作
修改在⼤多场景下结果⼀样,但是如果是增量修改是需要保证幂等性的,如下例⼦:
把表中id为XXX的记录的A字段值设置为1,这种操作不管执⾏多少次都是幂等的
把表中id为XXX的记录的A字段值增加1,这种操作就不是幂等的
- D: 新增操作
增加在重复提交的场景下会出现幂等性问题,如以上的⽀付问题
如何实现幂等性
其实实现幂等性的方案有不少,但是呢,这就得需要你根据不同的业务场景去选择合适的方式了。
实现方式一
数据库唯一主键
数据库唯一主键的实现主要是利用数据库中主键唯一约束的特性,一般来说唯一主键比较适用于“插入”时的幂等性,其能保证一张表中只能存在一条带该唯一主键的记录。
使用数据库唯一主键完成幂等性时需要注意的是,该主键一般来说并不是使用数据库中自增主键,而是使用分布式 ID 充当主键(可以参考 Java 中分布式 ID 的设计方案 这篇文章),这样才能能保证在分布式环境下 ID 的全局唯一性。
而实际上生成这个主键的方式就是在当请求的时候后,生成分布式唯一ID,然后当做主键插入数据库,来保证唯一即可。
实现方式二
Token机制
Token机制,实际上也可以称为 Token 令牌
- 服务端提供了发送token的接⼝。我们在分析业务的时候,哪些业务是存在幂等问题的,就必须在 执⾏业务前,先去获取token,服务器会把token保存到redis中。(微服务肯定是分布式了,如果 单机就适⽤jvm缓存)。
- 然后调⽤业务接⼝请求时,把token携带过去,⼀般放在请求头部。
- 服务器判断token是否存在redis中,存在表示第⼀次请求,这时把redis中的token删除,继续执⾏业务。
- 如果判断token不存在redis中,就表示是重复操作,直接返回重复标记给client,这样就保证了业务代码,不被重复执⾏。
实现方式三
数据库乐观锁
数据库乐观锁方案一般只能适用于执行“更新操作”的过程,我们可以提前在对应的数据表中多添加一个字段,充当当前数据的版本标识。这样每次对该数据库该表的这条数据执行更新时,都会将该版本标识作为一个条件,值为上次待更新数据中的版本标识的值。
为了每次执行更新时防止重复更新,确定更新的一定是要更新的内容,我们通常都会添加一个 version 字段记录当前的记录版本,这样在更新时候将该值带上,那么只要执行更新操作就能确定一定更新的是某个对应版本下的信息。
这样的话,有了 version 的存在,这样就能保住更新的幂等,多次更新对结果不会产生影响。
你还了解有哪些实现幂等性操作的方式呢?