接口幂等是说,用相同的参数,调用一次和调用多次,其返回的结果应该是一样的
比如一个接口时用来保存数据的,那如果用户在页面上,连续两次点击提交(点击提交后,页面加遮罩层除外),那势必会发送2次请求,如果接口没有做幂等,数据库肯定被插入了2条相同数据
如何把接口做成幂等的,如果没有状态的改变和数据库相关的,都设置成单例基本上不用处理就幂等了,如果有状态改变或者数据库操作的话要做一些额外的处理,
首先在接口接收参数后要先判断是否已经做过处理,处理过了,直接将之前的结果返回,没有处理过再处理
幂等的好处是,在分布式中如果出现网络情况超时,或者突然宕机,方便消费者发起重试,而不会对已经处理的结果产生影响。
为了避免非幂等产生的问题请求的时候要带上一个标识,这个标识要是唯一、可信的。接收请求后,先查询这个标识是否已经存在,如果不存在则创建一个对象;如果存在则告知已经创建。(比如:金额转账)
那在集群或分布式环境中,虽然请求中有唯一标识,但还是会有问题。比如连续两次请求,很快很快,都使用同一标识,那么处理不好就创建两个对象了。因为第一个请求在没有保存对象时,第二个请求发现还没有创建,又创建一个对象。嗯,我们就出现这个不专业的问题。
两个请求可能在不同应用服务器执行,创建的两个对象保存在不同数据库上,没办法使用锁,没有分布式事务,也无法保证操作时序一致。
使用zookeeper/etcd等分布式协调服务,可以解决此问题。目前我们没有搭建这类服务,因此只能使用其它方式,当然也是找一个合适的第三方来保证。对于分布式并发的锁处理,核心是需要一个第三方,也就是仲裁方,满足:
1.唯一的。同一时间只有集群中的一个主节点提供仲裁。
2.可靠的。不能说挂就挂,一般使用集群,至少要主备吧。当主节点挂了,客户端连接其它节点时,数据不会丢。
3.够快的。或者叫高性能。
然后就找到了redis,在对本例中要解决的问题来说,现成、简单、易用,因为redis服务常见,也已经在项目中部署。使用redis的incr方法可以帮我们解决这个重复创建问题。使用同一台redis(主从),其原子性保证我们不需要担心并发。创建时,把唯一标识作为key并incr一下,并获取返回值,如果是1,那就说明没有创建过此对象,如果大于1,那就说明已经创建过了。同时key缓存时间保证对象保存到数据库即可。
在主备的情况下,除非redis在写到主库时,数据同时写到备库再返回结果(强一致性),否则处理分布式锁并不可靠。但是redis的主备同步好像是采用异步的,弱一致性同步的,可能出现的问题是,第一个客户端刚写到redis主节点还没有同步到备节点就挂了,第二个客户端只好访问备redis,发现没有数据(没有锁),并重复获取锁,这样有两个客户端获取到锁了。
在开放接口时考虑非幂等性问题,实现时还要考虑分布式环境,防止重复创建对象,以上是产生的问题和解决的思路。