作者回复: 这里头有一个设计折中,如果所有节点无状态都采用抢占方式,那么当数据量大的时候,所有节点抢占DB造成的锁压力就会很大,这个无法线性扩展。所以killbill-queue采用粘性(sticky)处理方式 ~ 一个节点只处理从自己这个节点写入的事件,这样就没有争抢锁的问题,但是这样节点就有状态,节点有状态就会有节点挂的问题,节点挂了谁来负责原来由该节点负责的事件,这时候就需要一个单独的角色在后台监控处理,这个角色就是reaper。
作者回复: 我记得我在google查找"DB based queue" 或者 "DB based event bus"时找到了killbill-commons这个项目。
作者回复: 中小规模的企业应用,用基于DB的事务性发件箱来实现异步可靠消息,比较简单。
作者回复: 用事务性MQ解决也是一种办法,也可以。 用canal/CDC解决的话,耦合性更低,性能会更好。
作者回复: 业务报错,具体情况还是需要根据业务上下文来处理,有些是可以立即重试的,有些立即重试也不行的,将消息发回死信队列,待后续再重试或人工干预。 MQ有主动推消息和消费端拉消息两种消费模式。对于消费端拉模式,例如Kafka,MQ中的消息始终存在,可以按需重复消费(调整偏移量即可)
作者回复: 如果只有一张订单表,如果订单修改了,比如调整了单价,或者添加了购物项,这种数据的变更你怎么记录呢? 发件箱主要用于记录数据变更事件的,其它消费者感兴趣的也是数据变更事件。变更包括新建/更新和删除。
作者回复: 但是对这个事件的处理可能会失败,如果要重试的话,还是需要DB来记录重试状态(否则机器挂状态就丢失了),所以光靠它也不能解决一致性分发问题。
作者回复: 对。rocketMq的方式也可以采用,国内用得比较多的,能解决问题。虽然架构上不太优雅,对应用有侵入性,而且mq本身也会搞复杂。
作者回复: 写DB超时了,会抛出exception,这时候不管写DB成不成功,都会触发rollback回滚,不会commit提交。
作者回复: 1. 每一个应用实例上面的Dispather线程,只消费处理从本实例(creating_owner是自己)写入的事件,而且因为一个应用实例只有一个消费线程,消息取走后DB状态变成处理中,可以保证不会重复消费。 请看killbill common queue的文档说明: The claiming mechanism is lock free: a first query looks for entries to be processed (10 at a time by default, see getMaxEntriesClaimed in the config) then mark them as IN_PROCESSING. Because each node only looks at entries it created (creating_owner column), there is no conflict between several nodes processing the same entry. 2. 消费慢,就需要更多的消费者(Dispather),但是每一个应用实例只有一个Dispather,所以需要更多应用实例,也就是需要对整个集群进行扩容,以分摊消费负载。