BASE论文
论文链接:https://queue.acm.org/detail.cfm?id=1394128
里面提到,
The most critical factor in implementing the queue, however, is ensuring that the backing persistence is on the same resource as the database. This is necessary to allow the queue to be transactionally committed without involving a 2PC.
也就是说,实现本地消息表的最关键因素是这个本地消息表要和业务数据表位于同一个事务,这样一来,即使不进行2PC,也能保证业务执行成功,消息也会随之发送出去
如下图,Insert和两个QueueMessage是一定同时成功,同时失败的
但是这样的话,还有问题没有解决,就是那两个Update和Insert是在不同的数据源的,这样一来就存在2PC问题,或者是让Update幂等,但Update的幂等很难实现。为了实现这样的幂等性,需要再来一张表,记录每笔订单的Update是否被运用了
至于消息的有序性,即先Insert的被先Update,可以通过增加时间字段,在更新时进行时间判定即可
作者还提到,这种引入本地消息表实现解耦的操作,会造成一种软状态,这种软状态会使得交易的双方似乎谁也没有得到物品,但是最终会得到,并且这种软状态持续的时间很短,因此是完全可容忍的
缺陷:业务耦合,不能通用
Java实现
发送端(插入交易)
接收端(更新用户数据)
执行Insert的同时,发送消息给Kalfka,同时发送端对future执行阻塞get。Kalfka接收端收到消息后开启事务,更新买卖家数据,如果更新成功,在事务提交之后响应ack;更新失败则在事务回滚之后响应nack。发送端收到ack后,提交事务,收到nack后,会根据配置的次数进行重新发送,一旦超过3次都是nack,则会抛出异常,此时发送端进行回滚。也就是说这里的实现对发送端的事务提交进行了更加严格的限制:只有接收端收到消息并且成功处理消息之后,才允许发送端进行事务提交。这其实是一种同步的分布式事务,跟论文中提到的不太一样。论文中可以看到,发送消息之后,不管接收端有没有消费,只要消息成功发送,就可以提交事务了。这种实现的是比较强的一致性,可能在要求快速响应的场景不适合(付款后增加积分),如果要按照论文的思路,发送端发出去之后,不用future.get了,直接提交事务,然后如果接收方执行失败,反复重试之后依然失败,就打一条日志,人工补偿,这样发送方不用阻塞等待接收方,可以直接返回了。