一个简单的分布式事务系统的实现(订单系统)
的方案去实现的,这样会有什么问题呢,假设如果放到一个实例里面,全部用一个单机事务去解决,这样是能比较方便的解决数据一致性问题。但是存在两个问题,一是无法进行多实例部署,用户量增长以后,无法快速应对。二是,PHP中做事务,如果PHP遇到异常,有时并不会自动终止事务,导致DB被锁住,这是第一个版本。之后,我们推出了第二个版本V2,这个版本的时候,我们已经开发好了,库存管理系统,优惠券管理系统,PHP中,已经不直接通过DB去修改库存和优惠券,而是通过接口访问的方式去请求SERVER进行修改。这个版本,实际上已经从逻辑上,把订单系统和库存管理,优惠券管理系统已经独立出来了。数据层面已经可以独立部署,不再依赖一个单机事务去实现数据一致性功能了。但这个版本虽然解决了数据分布的问题,但同时引入了一个新的问题,就是数据在订单,库存,优惠券之间无法保证一致性。举个例子:下个订单,调用库存成功,锁定优惠券失败,生成订单失败。这时候就会导致优惠券数据不一致性情况出来,未下单的优惠券也被锁住了。有同事可能会问:订单如果创建失败,那直接回滚优惠券操作,即去解锁优惠券系统即可实现数据一致性。不错,很多时候,是可以这么操作,但如果你回滚的时候,失败了呢?你是继续在这等着直到成功,还是继续等着?呵呵。。
正是因为这样,我们开发了V3版本,去解决这个问题。
V3版本,我们把订单系统的逻辑从PHP中抽离出来,为什么尽量不在PHP里面做这块逻辑呢?主要有两个考虑点:1、因为订单服务这块逻辑特别重要,是影响用户操作的重要逻辑,且变动少,写成一个SERVER相对容易保持稳定。2、PHP使用CI框架做事务的时候,如果事务中出现异常,可能导致事务不结束,一直死锁的问题。
订单系统的逻辑架构大致如下:
订单系统中,统一通过接口调用,去访问库存管理,优惠券系统,通过mysql提供的事务机制去操作数据库部分。这里有一个前提条件,即是库存管理与优惠券系统的接口均要实现可重入的特性(可参考上一篇文章“如何实现可重入接口”)。另外,还要引入一个差错控制服务,用于做一些数据不一致的事后补尝机制。差错控制可以理解为一个消息队列机制,还有一个消费者服务从队列中取出消息进行消费。我们这里采用阿里云的ONS服务做为消息队列,通过一个消费者去订单消息进行消费。
生成订单的逻辑如下:
1、先把生成的订单号发到差错控制服务中。(这里必须要有个延时处理的机制,延时给消费者消费消息,因为要确保后面的流程有个结果,可以延时5分钟以上)
2、使用订单号作为库存单号去操作库存管理系统。
A)如果失败,则使用相同订单号去进行回滚请求操作。(这里不论成功失败,均返回失败,结束流程)
B)如果成功,继续往下执行。
3、使用订单号去锁定优惠券系统。
A)如果失败,尝试库存回滚操作,尝试执行解锁操作。(这里不论解锁成功失败,均返回失败,结束流程)
B)如果成功,继续往下执行。
4、开启事务,创建订单相关数据。
A)如果创建失败,回滚事务,调用库存回滚操作,调用优惠券解锁操作。(不论调用成功与否,均返回失败,结束流程)
B)如果创建成功,提交事务,返回成功。
大概流程如上所述。
另外,差错控制服务,这里也大概描述一下其工作流程。
1、去订单库中查看该订单是否已经生成,如果已经生成,说明数据全部一致,无须做任何操作,直接消费此消息。
2、如果发现订单未创建,则其中可能是其中某个环节失败了。
A)使用该订单号去调用库存回滚操作。如果失败,结束流程,返回稍后重新消费,等待消息队列重试推过来。
B)如果成功,继续往下执行,调用优惠券系统进行解琐优惠券。如果失败,则返回稍后重新消费,等待消息队列重推消息。如果成功,则消费此消息。
大致思路是通过一个差错补尝机制,非实时的自动进行数据一致性修复的方法,来保证绝大多数情况下的数据一致性。