在分布式场景下,我们经常会有需要实现幂等的场景,幂等分为请求幂等和业务幂等。
请求幂等
下面将从以下三步来整理请求幂等的知识点:
- 请求幂等的本质
- 怎么做请求幂等
- 如何生成业务主键
1、请求幂等的本质
场景介绍:
用户A给用户B转100W
1).用户A请求超时
2).用户A重试
请求幂等需保证:用户A的同一个请求多次重试,最终只会转账一次。
请求幂等本质
保证请求重复执行和执行一次结果相同,用公式来表示的话就是:f...f(f(x)) = f(x)。
请求分类分析
1.读:Read/Select,读请求是天然幂等的。
2.写:涉及数据变更的请求,分为:insert、update、delete,写请求需要做幂等。
在哪里做请求幂等
假设系统架构按如下分层:
- 反向代理(Nginx)
- 网关层
- 业务逻辑层
- 数据访问层
- 数据库
从上述层次结构中可以直到,请求幂等我们需要在数据访问层和数据库来实现,因为请求重试不管在那一层都有可能出现,因此我们只要保证对数据操作的这一层请求是幂等的即可。
2、怎么做请求幂等
下面我们仍然根据请求的分类来分析怎么实现请求幂等吧。前面说了读请求时天然幂等的,所以就只需要看Insert、Update、Delete三种情况下面如何实现请求幂等。
Insert
针对Insert操作,有以下几种情况:
1.主键为业务主键,插入是幂等的,因为同一个请求业务主键肯定是一样的(后续说明如何生成业务主键)。
2.主键为自增主键且有唯一索引,此时插入是幂等的,与第一点类似,借助的都是数据库的唯一性来保证请求的幂等。
3.主键是自增主键且没有唯一索引,此时插入不是幂等的,也就是说同一个请求时可以多次插入的,最终导致数据错误。
从以上三点可以看出,插入数据的幂等性重点在于主键是不是自增主键且没有唯一索引。因此我们只需要保证主键是业务主键,或者包含唯一索引。
Update
对update的分析要从单线程还是多线程来分析:
1.单线程重发请求,也就是说请求的重发是同一个线程触发的,此时线程的Update条件是一致的,因此不管执行一次还是多次结果都是一样的。因此此时修改是幂等的。
2.多线程重发请求,这时候会出现经典的ABA问题,比如以下SQL在多线程下面就会出现ABA问题:
1). A:update user set sex=18 where uid = 66
2). B:update user set sex=19 where uid = 66
3). A:update user set sex=18 where uid = 66
这时候,因为ABA问题的存在,Update操作请求就不是幂等的了。
解决ABA问题的最常见手段就是版本号了,也就是在条件中加上版本号来判断是否已有其他线程操作过相同记录。比如上述SQL可以优化为:
1). A:update user set sex=18,v++ where uid = 66 and version = 1
2). B:update user set sex=19,v++ where uid = 66 and version = 1
3). A:update user set sex=18,v++ where uid = 66 and version = 1
通过使用版本号,我们就可以保证Update请求也是幂等的了。
Delete
Delete的幂等实现和Update是一样的,就不在详细说明了。
3、如何生成业务主键
系统生成业务主键可以从以下三个步骤来实现:
1.客户端在发送业务请求前,先获得一个唯一请求ID,该请求ID生成接口可独立提供。
2.客户端获得请求唯一ID后,发送业务请求。业务逻辑层生成业务ID,并在redis记录请求唯一ID与业务唯一ID的映射关系。
3.后续改请求重发过来到redis查询对应的业务唯一ID即可。
通过以上三步实现较为通用的业务主键生成功能。