原创

分布式系统的幂等性你们是怎么设计的?

本文和探讨一下分布式系统中幂等性的设计。

1、什么是幂等性?

对于同一笔业务,执行一次或者多次,产生的结果都是一样的,幂等性主要是为了解决重复处理的问题。

2、来看一个场景

我们有个电商系统,可以通过支付宝进行充值。

用户看到的过程如下:

1.电商网站,用户输入金额,点击充值

2.电商生成充值订单,订单状态为待支付

3.跳转到支付宝页面,用户确定支付

4.支付那边操作完成之后,会携带充值结果重定向到商户的结果页面,此时页面显示充值成功

此时电商中的充值订单并没有去修改,而支付宝会将充值结果异步通知给电商。

电商系统接收到充值通知结果之后,处理过程如下:

if(判断订单状态是否为待支付){ //@1
    给用户账户加钱
    将订单状态置充值成功
}

可能会由于网络故障,通知无法到达商户,所以支付宝为了让通知能够达到商户,可能会出现并发重复通知的情况。

若支付宝同时来了2个通知,同时到达@1,会出现什么后果???

若用户充值1万,最终会导致2万,严重线上事故。

为什么会出现重复处理?

由于存在并发情况,导致了,条件判断失效,从而导致了重复处理。

主要原因:并发

那么,如果没有并发,是不是就不会出现问题了?

确实,如果没有并发,就没有上面的问题了。

解决问题的根本办法?

让并发操作排队执行,并行的操作变成一个个执行,问题自然就消失了。

3、方式1:乐观锁

订单表需添加一个字段:version(版本号),默认为0,每次更新+1。

业务伪代码:

Order order = 获取订单信息;
if(order是否未处理){
    int upCount = update set version = version+1, status=已处理 where id = 订单号 and version = order.version;
    if(upCount==1){
        执行业务操作:给用户加钱
    }
}

db中对于同一条数据,更新的时候会上锁,所以如果有并发更新,也只能一个个处理。

上面的重点在于update操作,将版本号作为条件来更新,并且更新的时候将version = version+1,并发的情况下,只会有一个线程更新成功,update操作会返回更新行数upCount,若upCount和期望的结果一致,说明更新成功了,然后执行业务操作;其他的upCount会为0,直接跳过业务操作。

4、方式2:分布式锁

通常我们的系统采用集群的方式进行部署,集群环境中,可以通过分布式锁来让并发操作变成排队执行。

获取到锁的可以执行,获取不到的排队等待或者直接跳过。

boolean lock = getLock(业务订单唯一id);
if(lock){
    执行业务操作;
}

5、方式3:数据库唯一约束

通用的方式,db中添加一个表

CREATE TABLE `t_uq_dipose` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `ref_type` varchar(32) NOT NULL DEFAULT '' COMMENT '关联对象类型',
  `ref_id` varchar(64) NOT NULL DEFAULT '' COMMENT '关联对象id',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_1` (`ref_type`,`ref_id`) COMMENT '保证业务唯一性'
);

业务操作伪代码

try{
    1.先查询一下订单在t_uq_dipose中是否存在
    int count = select count(*) from t_uq_dipose where ref_type = '充值订单' and ref_id = '充值订单唯一id';
    if(count==0){
        2.开启事务
        3.insert into t_uq_dipose (ref_type,ref_id) values ('充值订单','充值订单唯一id');
        4.执行业务操作
        5.提交本地事务
    }
}catch(Exception e){
    6.回滚本地事务;
}

这种方式存在瓶颈:t_uq_dipose单表 + 唯一约束,写上面存在系统瓶颈。

如何解决这种瓶颈?

分库分表,大家要有这种解决问题的思想,并发导致性能出现瓶颈,得就想办法分片,这样落到每个分片上面的并发量就比较小了,从而提升系统性能。

t_uq_dipose表可以创建很多个,比如创建64个:

t_uq_dipose_0
t_uq_dipose_1
.....
t_uq_dipose_63

业务来了之后,可以先定位到表名,可以通过hash值取模的方式找到对应的表:

hashCode(ref_type+ref_id) % 64

大家用哪种方案呢?欢迎留言一起交流。

推荐一个高质量的公众号

这里给大家推荐一个公众号:Java充电社,这个号中会定期发布一些高质量的java专题视频,目前已经发布了大量高质量的学习视频,大家可以去瞅瞅,欢迎关注。

file

正文到此结束
本文目录