面对分布式架构和微服务复杂的系统架构和网络超时服务器异常等带来的系统稳定性问题,分布式接口的幂等性设计显得尤为重要。本文简要介绍了几种分布式接口幂等性设计实现,包括Token去重机制、乐观锁机制、数据库主键和状态机实现等,以加深理解。
幂等性来源自数学领域,数学上的幂等性是指对于某一元运算为幂等的操作,在任意元素上多次执行的结果是相同的。例如,函数f(x) = f(x)对于任意的x,在x上的第一次和第二次执行可以得到相同的结果。
在HTTP/1.1规范中幂等性的定义如下:
Methods can also have the property of “idempotence” in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request.
一次和多次请求某一个资源对于资源本身应该具有同样的结果(网络超时等问题除外)。也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。
在HTTP协议中,HTTP GET是一个清晰的幂等操作,HTTP DELETE/POST是非幂等的,HTTP PUT也是幂等的,因为对同一个URI进行多次PUT的side-effetcs是一致的。
在分布式架构或者微服务架构中,由于分布式自身的时序问题以及系统网络的稳定性,接口具有成功、失败和无响应的三种状态,为了提供系统的可用性,重复提交是不可避免的,而重试就会引发幂等性的问题。
分布式接口的幂等性实际上就是接口可重复调用,在调用方多次调用的情况下,接口最终得到的结果是一致的。幂等性适用于以下场景:
接口幂等性的解决方案可以在客户端和服务端实现,但是客户端控制效果不佳,比如按钮置灰、不可点击等,由于涉及到多设备兼容性以及接口调用的问题,并不能真正实现幂等。因此安全的措施还是从后端接口层进行控制,有以下几种方案:
幂等性设计简化了客户端的处理逻辑,却增加了服务端逻辑处理和设计上的复杂性,增加额外控制幂等的业务逻辑的同时,将并行执行改为串行降低了执行效率。
Token机制是通过在服务端生成一个唯一的Token,并将其存储在客户端中,来保证多个客户端之间对同一个服务的请求结果的一致性。Token机制的实现原理如下:
Token机制的优点是实现简单、易于部署和维护,能够保证分布式系统的幂等性。但是,它也存在一些局限性,例如需要在服务端和客户端之间传递Token,可能会导致性能问题;另外,如果Token被滥用,也可能会带来安全问题。因此,在使用Token机制时,需要根据具体情况进行权衡和选择。
数据库乐观锁方案一般适用于更新操作的幂等性,实现逻辑是在对应的数据表中添加一个version字段,作为当前数据的的版本标识。这样每次对这条数据执行更新时,都会将该版本标识作为一个条件,值需要为上次待更新数据中的版本标识的值。
1)先根据条件查询数据,得到对应的版本号version
select version from tablename where xxx
2)更新数据时带上版本号version,只有版本号匹配才会更新数据,如果不匹配则不更新
update tablename set count=count+1, version=version+1 where version=#{version}
3)更新数据的时候,同时需要更新数据对应的版本号version,这样可以解决ABA问题。
如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 "ABA"问题。
乐观锁机制实际上是牺牲了并发性来实现更新操作的幂等性,在并发场景下会导致大量的锁冲突等待和性能问题。
数据库唯一主键机制是利用主键的唯一性约束,适用于插入操作的幂等性,当插入主键重复的数据时会抛出异常,保证数据的一致性。表结构设计如下所示:
CREATE TABLE `t_check` (
`id` int(11) NOT NULL COMMENT 'ID',
`serial_no` varchar(255) NOT NULL COMMENT '唯一序列号',
`source_type` varchar(255) NOT NULL COMMENT '资源类型',
`status` int(4) DEFAULT NULL COMMENT '状态',
PRIMARY KEY (`id`)
UNIQUE KEY `key_s` (`serial_no`,`source_type`) COMMENT '保证业务唯一性'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='幂等性校验表';
唯一主键UNIQUE KEY的关键性字段如下:
具体处理逻辑如下图所示:
对于很多业务是有业务流转状态的,如订单的待提交,待支付,已支付,取消等,在业务逻辑处理的时候只支持状态的单向改变。业务表在设计的时候增加状态字段status,这样在更新的时候加上“status=期望的status”,多次调用的话实际也只会执行一次。
update xx where id=1 and status=1
分布式架构下幂等性是保证接口能够重复执行的重要机制,幂等性和防重又有所不同,防重是在第一次请求已经成功的情况下人为多次重复操作导致的状态改变,幂等性是在不确定第一次请求结果的情况下,发起多次请求不会出现状态的变化。实际使用中,通过数据库主键的唯一性可以实现幂等性和防重,乐观锁的version机制能够实现并发更新下的幂等性,也可以通过数据库悲观锁机制在业务操作前获取锁资源实现唯一性操作。总而言之,分布式接口的幂等性是在牺牲一定的并发和性能的前提下,以实现系统的稳定性和容错性。
参考资料: