一.ActiveMQ消息持久化
同时满足以下条件时消息将被持久化到硬盘。
1.在broker中设置属性persistent=”true”(默认是true);
2.发送的消息也应该是persitent类型的,这是 ActiveMQ 的默认传送模式,此模式保证这些消息只被传送一次和成功使用一次。
ActiveMQ消息持久化有三种方式:AMQ、KahaDB(默认)、JDBC。如果要发送非持久化(NON_PERSISTENT)消息,可在配置JmsTemplate时设置<property name="deliveryMode" value="1" /> <!-- 发送模式 DeliveryMode.NON_PERSISTENT=1:非持久 ; DeliveryMode.PERSISTENT=2:持久-->
二.消息确认机制
JMS API中约定了Client端可以使用四种ACK_MODE,在javax.jms.Session接口中:
AUTO_ACKNOWLEDGE = 1 自动确认
CLIENT_ACKNOWLEDGE = 2 客户端手动确认
DUPS_OK_ACKNOWLEDGE = 3 自动批量确认
SESSION_TRANSACTED = 0 事务提交并确认
此外AcitveMQ补充了一个自定义的ACK_MODE:
INDIVIDUAL_ACKNOWLEDGE = 4 单条消息确认
在开发JMS应用程序的时候,会经常使用到上述ACK_MODE,其中"INDIVIDUAL_ACKNOWLEDGE "只有ActiveMQ支持,当然开发者也可以使用它.。ACK_MODE描述了Consumer与broker确认消息的方式(时机),比如当消息被Consumer接收之后,Consumer将在何时确认消息。对于broker而言,只有接收到ACK指令,才会认为消息被正确的接收或者处理成功了,通过ACK,可以在consumer与Broker之间建立一种简单的“担保”机制。
Client端指定了ACK_MODE,但是在Client与broker在交换ACK指令的时候,还需要告知ACK_TYPE,ACK_TYPE表示此确认指令的类型,不同的ACK_TYPE将传递着消息的状态,broker可以根据不同的ACK_TYPE对消息进行不同的操作。
在JMS API中并没有定义ACT_TYPE,因为它通常是一种内部机制,并不会面向开发者。ActiveMQ中定义了如下几种ACK_TYPE(参看MessageAck类):
DELIVERED_ACK_TYPE = 0 消息"已接收",但尚未处理结束
STANDARD_ACK_TYPE = 2 "标准"类型,通常表示为消息"处理成功",broker端可以删除消息了
POSION_ACK_TYPE = 1 消息"错误",通常表示"抛弃"此消息,比如消息重发多次后,都无法正确处理时,消息将会被删除或者DLQ(死信队列)
REDELIVERED_ACK_TYPE = 3 消息需"重发",比如consumer处理消息时抛出了异常,broker稍后会重新发送此消息
INDIVIDUAL_ACK_TYPE = 4 表示只确认"单条消息",无论在任何ACK_MODE下
UNMATCHED_ACK_TYPE = 5 BROKER间转发消息时,接收端"拒绝"消息
Client端在不同的ACK_MODE时,将在不同的时机发送ACK指令,每个ACK Command中会包含ACK_TYPE,那么broker端就可以根据ACK_TYPE来决定此消息的后续操作。我们需要在创建Session时指定ACK_MODE,由此可见,ACK_MODE将是session共享的,意味着一个session下所有的 consumer都使用同一种ACK_MODE。
在"异步"(messageListener)方式中,将会首先调用listener.onMessage(message),此后再ACK。如果onMessage方法异常,将导致client端补充发送一个ACK_TYPE为REDELIVERED_ACK_TYPE确认指令;如果onMessage方法正常,消息将会正常确认(STANDARD_ACK_TYPE)。此外需要注意,消息的重发次数是有限制的,每条消息中都会包含“redeliveryCounter”计数器,用来表示此消息已经被重发的次数,如果重发次数达到阀值,将会导致发送一个ACK_TYPE为POSION_ACK_TYPE确认指令,这就导致broker端认为此消息无法消费,此消息将会被删除或者迁移到"dead letter"通道中。
因此当我们使用messageListener方式消费消息时,通常建议在onMessage方法中使用try-catch,这样可以在处理消息出错时记录一些信息,而不是让consumer不断去重发消息;如果你没有使用try-catch,就有可能会因为异常而导致消息重复接收的问题,需要注意你的onMessage方法中逻辑是否能够兼容对重复消息的判断。
三.使用事务
在创建Session时,如果此session为事务类型,用户指定的ACK_MODE将被忽略,而强制使用"SESSION_TRANSACTED"类型;如果session非事务类型时,也将不能将 ACK_MODE设定为"SESSION_TRANSACTED"。当session使用事务时,在事务开启之后,session.commit()之前,所有消费的消息,要么全部正常确认,要么全部redelivery。这种严谨性,通常在基于GROUP(消息分组)或者其他场景下特别适合。因为Session非线程安全,那么当前session下所有的consumer都会共享同一个transactionContext;同时建议,一个事务类型的Session中只有一个Consumer,已避免rollback()或者commit()方法被多个consumer调用而造成的消息混乱。
四.消息重发机制
在所有的客户端机器上,内存中都运行着一套客户端的ActiveMQ环境,该环境负责缓存发来的消息,负责维持着和ActiveMQ服务器的消息通讯,负责失效转移(fail-over)等,所有的判断和处理都是由这套客户端环境来完成的。Message头里有两个相关字段:Redelivered默认为false,redeliveryCounter默认为0。消息先由broker发送给consumer,consumer调用listener,如果处理失败,本地redeliveryCounter++,给broker一个特定应答,broker即会在内存将消息的redelivered设置为true,redeliveryCounter++,延迟一点时间继续调用,默认1s(配置参数 initialRedeliveryDelay :第一次重发的拖延时间基础,默认是1000毫秒;redeliveryDelay :如果initialRedeliveryDelay 为0,则使用redeliveryDelay ,默认也是1000毫秒)。超过6次(配置参数 maximumRedeliveries :最大重发次数,默认值是6,如果你想不限次数重发,可以设置成-1),则给broker另一个特定应答,broker就直接发送消息到DLQ。但是这两个字段都没有持久化,所以broker重启时这两个字段会被重置为默认值。
五.设置多个并行的消费者
为了最大效率的完成对消息队列中的消息的消费,一般可以同时起多个一模一样的消费者,以并行的方式来拉取消息队列中的消息。这样的好处有多个:
1.加快处理消息队列中的消息。
2.增强稳定性,如果一个消费者出现问题,不会影响对消息队列中消息的处理。
使用Spring MS来配置多个Listener实例相当简单,只需要配置下MessageListenerContainer就行。多配置一个属性concurrentConsumers,设置值为4,就是同时启动4个Listener实例来消费消息。除了设置一个固定的Listener数量,也可以设置一个Listener区间,这样MessageListenerContainer可以根据消息队列中的消息规模自动调整并行数量。这次使用的是concurrency属性,4-8表示最小并发数是4,最大并发数为8,当然也可以给一个固定值,比如5,这样设置与concurrentConsumers属性效果一样。