1。最简单的问题背景:DB事务。比如在创建一个订单时,同时向订单表和订单商品表插入数据,这些Inserts必须在同一个事务中执行。Order服务调用Pay服务,刚好网络超时,Order服务启动重试机制,所以Pay服务两次收到同一个支付请求,由于轮询负载均衡算法,落在了不同的业务上节点!因此,分布式系统接口必须保证幂等性。2、如何避免重复下单?前端页面也可以直接阻止用户重复提交表单,但是网络错误会导致重传。很多RPC框架和网关都有自动重试机制,所以在前端无法完全避免重复请求!归根结底,问题是如何保证服务接口的幂等性。2.1如何判断请求重复?在插入订单之前,先查看订单表,看看是否有重复的订单?不好:很难用SQL条件来定义什么是“重复订单”。同样的订单呢?因此,要保证幂等性:2.1.1每个请求必须有唯一的标识。例如,订单付款请求必须包含订单ID。一个订单id最多只能成功支付一次。2.1.2每个请求被处理后,必须有一条记录表明该请求已被处理。MySQL中记录了一个status字段。比如在支付前记录这个订单的一个支付流水。2.1.3每收到一个请求,判断之前是否处理过。如果订单已经付款,则必须有付款流程。如果重复发送这个请求,此时先插入支付流,发现orderId已经存在,唯一约束生效,重复Key报错。您不会被再次收费。在向DB中插入记录时,一般不提供主键,而是由DB在插入时自动生成的。这样重复的请求会导致插入重复的数据。MySQL的主键带有唯一约束。如果在INSERT语句中提供了主键,并且主键值已经存在于表中,则INSERT将无法执行。因此,可以利用DB的“主键唯一性约束”,在插入数据时带上主键,实现创建订单接口的幂等性。在Order服务中增加一个“orderId生成”接口,不带参数,返回值为【全球唯一】的订单号。当用户进入创建订单页面时,前端页面首先调用orderId生成接口获取订单号,当用户提交订单时,在创建订单请求中携带订单号。订单号实际上是订单表的主键,所以重复的请求携带相同的订单号。订单服务向订单表插入数据时,这些重复的INSERT语句中的主键也是同一个订单号。DB唯一约束保证只有一个INSERT被成功执行。其实需要结合业务,比如使用Redis,使用orderId作为唯一K,支付流插入成功后才能进行扣费。需求是支付订单,必须插入一个支付流,order_id创建一个唯一键。在你支付订单之前,你插入一个支付流程,并且order_id已经被传递了。可以给Redis写一个标识,设置order_idpaid,当有重复的请求过来时,先查看Redis的order_id对应的值,如果是paid,说明已经支付了,不要再支付!然后,当支付顺序重复时,如果writer尝试插入一个支付流,DB会报uniquekey冲突,整个事务会回滚。也可以保存一个processedflag,不同的服务实例可以一起操作Redis。如果由于重复下单导致插入t_order失败,Order服务不应该向前端页面返回错误。否则,可能会出现用户点击创建订单按钮后,页面提示创建订单失败,但实际上订单创建成功的情况。正确做法:此时订单服务直接返回订单创建成功。3.解ABA3.1什么是ABA?比如下单付款后,卖家需要发货,发货完成后,还要填写一个快递单号。假设卖家填写666,刚填写完,发现是错了,他赶紧改成888。对于订单服务来说,这是2次更新订单的请求。系统异常时,666请求到达,订单号修改为666,然后888请求到达,订单号更新为888,但是666更新成功响应丢失,调用方没有收到成功响应,自动重试,再次发起666请求,trackingnumber再次更新为666,数据明显错误!3.2解决方案订单主表增加版本列。每次查询订单时,版本号必须与订单数据一起返回到页面。在更新数据的请求中,页面将这个版本号作为更新请求的参数返回给订单更新接口。订单服务更新数据时,需要比较订单的版本号和消息中的版本号是否一致:如果不一致,则更新数据一致,需要重新更新数据,version+1是必需的。“比较版本号、更新数据和版本号+1”的过程必须在同一个事务中执行UPDATE命令settracking_number=666,version=version+1WHEREversion=8;这条SQL的WHERE条件中,version值需要pages更新时通过request传入。通过这个版本号,可以保证从我打开这条订单记录到我成功更新这条订单记录,没有其他人修改过订单数据。如果有,DB中的版本就会改变,我的更新操作就会失败。只能重新查询新版的订单数据,再尝试更新。有了这个版本号,上面的ABA有两种情况:运单号更新到666成功,到888的更新请求携带旧版本号,更新失败,页面提示用户更新888失败。888withanewversionnumber,888更新成功。这时候即使重试的666请求又来了,因为它和之前的666请求的版本号是一样的。上一次请求更新成功后,版本号发生了变化,所以重试的请求更新必然会失败。不管怎样,DB中的数据与页面上给用户的反馈是一致的。这样可以实现幂等更新并避免ABA。4.总结创建订单服务,可以预先生成订单号,然后利用订单号在DB中的唯一约束,避免重复写入订单,实现创建订单服务的幂等性。更新订单服务。通过版本号机制,每次更新数据前先校验版本号,数据更新时版本号同时自动递增。该方法解决了ABA问题,保证了更新订单服务的幂等性。两种幂等的实现方式可以保证无论请求是否重复,订单表中的数据都是正确的。实现顺序幂等的方法完全可以应用到其他需要实现幂等的服务上,只要这个服务操作的数据存储在数据库中,并且有一张带主键的数据表即可。
