阅读目录redis
1、背景数据库
2、“大唐啥都有”网站的代码微信
3、SQL 中的事务网络
4、那如何优化无事务的代码?架构
5、如何解决无事务的问题?并发
6、具备补偿功能的解决方案运维
1、背景
悟空和师父一行人正在前往西天取经的路上,师父在线上买了一个福袋,订单状态显示订单已支付,可是电子福袋状态为未发送。异步
悟空来到了这家网站的后台,找到了开发人员“小黑熊”。优化
悟空:嘿,快查下我师父的订单,钱都给了,福袋怎么尚未到?
小黑熊:大圣,咱们也收到异常通知了,更新福袋表的时候因网络缘由致使福袋记录没有更新成功,因此福袋仍是未发送的。
悟空:福袋没发出来,那为何订单状态还一直是已支付?你这小儿,可不要瞒我!
小黑熊:大圣,咱们数据库用的是MongoDB 3.0,不支持事务啊。
悟空:你说的事务是什么意思?
小黑熊:事务就是保持多个更新或删除或增长操做,要么都成功,要么都失败。
悟空:也就是说第一步顶单状态从未支付到订单成功已经执行成功了,可是第二步更新福袋的时候失败了,没有自动将第一步订单的状态给改回去?
小黑熊:是的,大圣。
悟空:那大家怎么没有退款啊?
小黑熊:大圣,咱们也没有想到有这种异常发生。
悟空:容我看下大家的代码。网站
2、“大唐啥都有”网站的代码
该网站购物的内部逻辑简化后以下图所示:
try { order.status = "已支付"; //第一步,更新订单状态:订单已支付 order.save(); //保存订单 luckyBag.status = "已发送"; // 第二步,更新福袋状态:福袋已发送 luckyBag.save(); //保存福袋 goodCounts.count -= 1;// 第三步,更新库存 goodCounts.save(); // 保存库存 order.status="订单成功" // 第四步,更新订单状态:订单成功 order.save(); //保存订单 } catch (excption e) { logError(); }
那这样的代码会有什么问题呢?
若是第一步执行成功,第二步执行失败了,抛出了异常,则第一步订单状态仍是订单成功的,福袋状态未更新,也就是师父遇到的问题。
那如何保证两步操做的一致性呢?(要么都更新,要么都不更新。)
咱们都知道SQL中是有事务这种解决方案的,咱们先来看看SQL中的事务。
3、SQL 中的事务
以前写过一篇文章,专门来说SQL中的事务:30分钟全面解析-SQL事务+隔离级别+阻塞+死锁。在这里用伪代码来讲明下什么事务。
举个购买商品的例子:用户下了一笔单,付款了,而后发放福袋,涉及到订单表order更新,福袋表luckyBag更新。
start transaction // 开始事务 try { update order // 第一步,更新订单状态 update luckyBag // 第二步,更新福袋状态 commit // 提交两部操做的更改 } catch (excption e) { rollback // 回滚全部操做 }end transaction // 结束事务
更新订单状态和更新福袋状态两部操做成功,则所有提交到数据库执行,若是其中任意一步出现问题,则所有回滚,就像没有执行更新操做同样,以保证数据的一致性。
4、那如何优化无事务的代码?
因为MongoDB 3.0 不支持事务,因此颇有可能出现数据不一致的状况(订单已支付,福袋未发送)。
那咱们既然不能享受到事务的一致性,有什么办法来优化这部分代码呢?
咱们先看下代码的时序图:
从上面的顺序图来看,分步保存是有问题的,第一步保存成功后,第二步若是保存失败,则数据不一致。那咱们能够将保存日后移吗?
咱们来看下优化后的时序图,总体将保存日后移。
伪代码以下:
try { order.status = "已支付"; //第一步,更新订单状态:订单已支付 luckyBag.status = "已发送"; // 第二步,更新福袋状态:福袋已发送 goodCounts.count -= 1;// 第三步,更新库存 order.status="订单成功" //第一步,更新订单状态:订单已支付 luckyBag.save(); //保存福袋记录 goodCounts.save(); // 保存库存记录 order.save(); //保存订单记录 } catch (excption e) { logError(); }
那这种方式又有什么优缺点呢?
优势:前四步的业务逻辑处理任意一步若是出错了,并不会影响数据库的记录
缺点:后三步的保存若是出错了,和最开始的方案同样,存在数据不一致的问题。
那如何进行解决这种问题?
5、如何解决无事务的问题?
优化后的代码仍是可能存在数据不一致的状况,那咱们怎么来解决?
问题1.若是福袋没有自动发出去,如今还能够补发吗?怎么补发?
问题2.能够退款吗?手动退款仍是自动退款?分别有什么优势和缺点?怎么优化?
问题3.若是第三步更新库存失败,那又该怎么作呢?
问题4.如何退款失败,那又该怎么作呢?
围绕上面几个问题,咱们展开来论述。
问题1.1:对于补发问题,咱们怎么来补发呢?
方案1:第二步失败时,当即重试几回(第一次3s,第二次间隔8s,第三次间隔20s,为何间隔时间不同?能够留言哦^_^)
方案2:将失败的数据放到队列里面(能够是存到数据库或者redis里面,建议存放到数据库),定时从队列里面获取异常数据,进行从新发送。
问题1.2:自动补发的优势和缺点分别是什么呢?
方案1的优势和缺点
优势:
(1)若是是临时出现的网络问题,能够当即在短期内重试几回,能够解决问题。
缺点:
(1)若是是接口或数据问题,短期内重试再屡次也是会失败的;
(2)另外若是有大量失败,重试也是会占用系统资源的。
方案2的优势和缺点
优势:
(1)将重试放到异步任务中来作,能够减小系统资源的占用;
(2)若是是长时间出现的网络问题,等网络恢复后,必定会重试成功;
缺点:
(1)异常数据没法经过重试来解决,则队列里面的数据将一直会进行重试,没法终止;
(2)若是有大量数据因接口或代码问题致使失败,则会积累大量失败数据,而大量数据进行重试也会对系统资源形成必定压力;
(3)重试失败会进行error log的记录,大量的error log对线上排查问题会形成干扰。
那补发若是一直失败,是否是还有更好的方式?给用户退款是否是更合理?(顾客等得很着急,赶忙把钱先退了吧。)这其实就是一种补偿措施。
问题2.1 能够退款吗?
固然能够退款
问题2.2 自动退款的优缺点?
优势:减小运营人员的工做量
缺点:在某些状况下,异常订单须要多方排查核实才能退款,就不能走自动退款。好比代码的逻辑没有handle某些场景,一刀切的退款会致使钱退了,商品还发给了客户。
问题2.3 怎么优化?
那怎么优化?提供自动和手动的两种方式,当某些异常场景须要手动退款的,等开发人员核实后,再进行手动退款。
帐不平怎么处理?经过对帐的方式找出哪些帐不平。
问题3 第三步更新库存失败怎么处理?
咱们很容易想到的方案是及时retry或 队列retry。那有什么问题呢?对于秒杀活动,队列retry确定不可行。
那咱们能够作一次补偿操做吗?(发起退款,更新订单状态为失败。)
答案是能够的。
问题4 若是退款失败怎么处理
每一步失败咱们都会作补偿处理,可是中间某一步补偿失败,咱们该怎么处理?好比最后钱退不了。
常见方案:
1.退款失败后主动报警通知运维人员或开发人员
2.手动退款(缺点:人工操做,容易出错,好比找订单找错了)
或 3.加入队列,自动退款(缺点:通常退款失败都是代码级别问题或微信侧问题,因此仍是须要排查问题缘由,在这期间,全部退款失败异常都会报警,对平常的监控形成没必要要的干扰)
在我如今作的项目都会将退款失败的消息如下面两种形式推送给我:
1.微信的模板消息
2.云服务商提供的日志报警短信服务
这样方便我去排查问题,以及快速退款。
6、具备补偿功能的解决方案
咱们能够设计一个具备补偿功能的解决方案:
1.若是第一步失败,则发起退款
2.若是第二步失败,则更新订单状态为失败,并发起退款
3.若是第三步更新库存失败,则退回福袋,且更新订单状态为失败,并发起退款
4.若是第四步更新订单为成功时失败,则库存+1,退回福袋,更新订单状态失败,并发起退款
欢迎你们留言讨论自家系统是怎么作的?
本文分享自微信公众号 - 悟空聊架构(PassJava666)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。