常见的外网请求,通常来自网页或app。一个不幂等的接口,可能会导致用户只点击一次,却产生多次点击的效果。
- 如果用户的请求只是修改昵称,那么基本没影响
- 但如果用户的请求是扣费,比如送礼,那就会产生多扣费的资金问题。
问题的原因
在互联网上,无论是内网还是外网,网络经常会有不稳定的情况。为应对这些网络问题,通常会有各种重试策略。
- 内网不稳定的情况,通常会在代理上(如nginx)配置一些超时重试,或50x重试策略
- 外网不稳定的情况,客户端会在超时时进行重试或切域名重试等
这些重试策略,导致了用户端只发出一次请求,实际服务端收到多次请求的情况。
总结:“重试”是产生问题的源头
解决问题的关键
解决问题的关键其实很简单,就是要让服务端能识别接收到的“多个”请求是否是“同个”请求,从而确保业务只执行一次。
如何标识请求的唯一性
本质上是请求的参数中,包含由一个或多个参数组合而成的,唯一且不变的标识。
- 像修改用户昵称的请求,业务本身就幂等,因为用户id是唯一且不变的
- 像用户的扣费请求,那么通常的做法是使用唯一的订单号,以及相应的唯一ID生成策略
唯一ID算法需要考虑哪些
- 有序
- 唯一ID作为数据的索引,保持有序有助于数据库性能提升
- 数据库类型
- 以MySQL为例,选择bigint(20)还是varchar,前者只有64位,像”2017072809364399365840049582“这种订单号只能用varchar存储
- 基因
- 分库需求,详见:唯一ID的基因
- 性能
前端唯一ID生成方案
- 使用UUID (缺点:无序)
- 通过后端接口获取唯一ID(缺点:多一次外网的网络请求)
- 按一定的业务规则生成,如:timestamp+ 用户ID(10-11位)+ 随机3位数字(或递增)(优点:有序且基本保证唯一)
后端唯一ID生成方案
- 使用snowflake算法实现的唯一ID服务
- 其他业界方案
解决方案
- 前端唯一ID使用方案3
- 后端服务对前端生成的ID进行规则校验,防止恶意伪造不规范的唯一ID
- 后端唯一ID
- 直接使用前端传入的唯一ID
- 从唯一ID服务获取(和前端唯一ID进行映射)
扩展
不同业务唯一ID冲突问题
- 若后端作为一个基础服务,对接上层业务,每个业务使用的唯一ID规则不一样。那么如何避免业务之间唯一ID冲突?
- 使用业务ID+业务唯一ID进行订单唯一性区分
- 统一唯一ID的生成规则(统一从基础服务获取或由该服务提供订单ID申请接口)
跨机房的服务幂等问题
- 若服务部署多个机房(通常每个机房有对应的数据库),如何保证幂等,因为外网请求重试,不一定会到达同一个机房。
- 把用户按机房分区
- 不考虑跨机房幂等问题(主备架构下同个请求落在不同机房概率不高,收益低)