消息队列的exclusive consumer功能是如何保证消息有序和防止脑裂的

一般来说,消息队列都会保证queue当中的消息的顺序。然而如果有多个consumer同时消费同一个queue,那么这时就不能保证的消息的顺序性。

有时候,消息的顺序是非常重要的,为了能顺序的消费消息,我们只能启动一个consumer来消费这个queue。但是这样做的问题就是,如果这个consumer宕调了话,消息就不能得到处理了,consumer的可用性不能得到保证。

那么如何提高consumer的可用性那,通常的做法是启动多个consumer,让其中一个consumer成为master,其他的为slave,成为master的consumer才去消费queue中的消息(通常的做法是使用zookeeper进行选主)。

但是这仍然没有解决全部问题。假设master在要消费一条消息之前,发生了一次很长的gc(假设我们是一个java的程序,当然也有其他的导致这个问题的场景),zookeeper认为master挂了,重新选主,一个slave被提升为master,并且开始消费这个queue的消息,这时原来的master从gc中恢复,在一定的时间范围内,原master还没有收到zookeeper通知它已经不是主了,这时它会继续消费queue中的消息。也就是说,在这段时刻,我们有2个认为自己是master的consumer。这种现象是分布系统中所说的脑裂。Zookeeper可以用来选主,zookeeper会保证只选出一个主,但是zookeeper并不能保证在某一时间系统中只有一个主,也就是说用了zookeeper并不能保证脑裂不会发生。Zookeeper不是用来防止脑裂的。

那么如何解决这问题那?很多mq都有exclusive consumer的概念,我们可以用它来解决这个问题。首先我们来说一下,什么是exclusive consumer。

拿activemq来说,我们在创建queue时,可以指定这个queue是exclusive,比如

queue = new ActiveMQQueue("TEST.QUEUE?consumer.exclusive=true");

如果一个queue设置为exclusive,broker会调选一个consumer,并且将所有的消息都发给这个consumer。如果这个consumer挂了,broker会自动挑选另外一个consumer。

对于rabbitmq来说,有2个exclusive参数可以设置:第一个是declare一个queue时可以指定exclusive=ture(也即queue_declare方法),那么这个queue只能被当前连接访问,当连接断开时queue会被删除。也可以这么理解exclusive queue是私有queue,non-exclusive queue是共享queue。第二个是consumer一个queue时可以指定exclusive=ture(也即basic_consume),那么这个queue只能被这个consumer访问。

脑裂问题我们是不能避免的,可以避免的时在发生脑裂时,2个master能同时去消费queue中的数据。所以我们应该用zookeeper来选主,让主去消费队列,并且队列要设置成exclusive。这样我们就保证队列中的消息是被顺序消费的。

上一篇:KV存储的对比


下一篇:存储系统的分类