我们为什么使用ActivityMQ?如何使用MQ的特性?
在说明为什么使用activityMQ之前,我先说一说为什么使用MQ?
MQ(Message Queue):消息队列,队列,一种先进先出的数据格式,就如去食堂买饭,先去的排在前面,那么食堂的阿姨会先给排在前面的小哥哥小姐姐打饭(所以吃饭要积极点,不然就没得吃了)
在互联网架构中,MQ是一种非常常见的上下游“逻辑解耦+物理解耦”的消息通信服务。使用了MQ之后,消息发送上游只需要依赖MQ,逻辑上和物理上都不用依赖其他服务。
为什么使用MQ?
比如我们常见的下单支付系统中:往往在用户支付成功后,需要给你用户推送各种消息(短信、微信模板消息,以及物流通知等消息);如果在我们支付成功时,去分别调用发短信,法模板消息,发物流通知,那么会
使得我们的系统的健壮性和性能大大降低,我们从下面几个点来说。
1、容错:那么容错来说也比较麻烦,比如调用短信时,短信的服务挂掉了,那么我们应该重试呢?还是忽略(不可取的);如果某个推送消息处理出现错误,那么数据可能没有更新,造成数据的误差
2、性能:如果我们不停的尝试某一个服务,那么响应的时间就会非常长,甚至超时,这样会使得用户的体验感非常差;
3、解耦:如果现在有一个新的需求,需要再给用户发邮件;那么势必又要在支付回调中处理发邮件的逻辑;这样我们的系统就会像蜗牛一样
4、消峰:如果某个活动峰值较大,每个请求都要完成全部操作,那么就会造成各种问题(内存不够,响应时间超时等),那么只需要将接口数据丢到MQ,又消费者去处理,这样就大大缩减了时间
此时,我们就可以使用MQ来解决问题了;MQ可以把上述的问题拆分成:回调时作为生产者推送消息到MQ; 就算完成任务,至于剩下的发消息,该状态等操作,在消费者中消费数据
MQ消费方式有两种:
Queue(点对点的消费):当生成者把消息推送到MQ时,只要有一个消费者消费过改数据,那么就会把此数据从MQ中移除
Topic(订阅模式的消费):当生成者把消息推送到MQ时,所有订阅的消费者都消费过该数据后,那么才会把此数据从MQ中移除
为什么使用ActivityMQ?
mq消费消息一般有两种方式:一、MQ通知消费者有新消息需要消费(PUSH)方式,二、消费者不停的轮训消息队列(PULL)方式;
ActivityMQ 采取消息推送方式, 所以适合的场景是 默认消息都可在短时间内被消费,据量越大,查找和消费消息就越慢,消息积压程度与消息速度成反比。
缺点:
1、吞吐量小:由于 ActiveMQ 需要建立索引,导致吞吐量下降。这是无法克服的缺点,只要使用完全符合 JMS 规范的消息中间件,就要接受这个级别的TPS
2、无分片功能。这是一个功能缺失,JMS 并没有规定消息中间件的集群、分片机制。而由于 ActiveMQ 是为企业级开发设计的消息中间件,初衷并不是为了处理海量消息和高并发请求。
如果一台服务器不能承受更多消息,则需要横向拆分。ActiveMQ 官方不提供分片机制,需要自己实现。
优点:
1、activemq可以很好的运行在任何JVM上,而不只是集成到JBoss的应用服务器中;
2、activemq支持大量的跨语言客户端;
3、activemq支持许多不同的协议,如Ajax,REST,Stomp,OpenWire,XMPP
4、activemq支持许多高级功能,例如MessageGroups,ExclusiveConsumer,CompositeDestinations
5、AdvisoryMessage
6、activemq支持可靠连接并且具有可配置的自动重连接
7、activemq对spring有很好的支持
8、activemq支持跨网络的分布式目的地
9、activemq是速度非常快;一般要比jbossmq快10倍
ActivityMQ的特性
这里这要针对笔者遇到的问题:在使用中经常会遇到重复消费和负载均衡的问题(Topic模式下)
使用 topic需要注意点,activemq 默认对于producer的消息是非持久化的。需要producer明确告知broker是否需要持久化。对于consumer也是需要分持久订阅与非持久订阅。所以如果组合不好很容易丢数据。
使用topic的时候,丢数据的主要情况:
1.发先布后订阅
2.consumer非持久订阅,宕机
3.cosumer持久订阅,producer非持久发布,consumer 宕机
或者说Topic 不丢数据的方式即为 producer 持久发布,consumer持久订阅。
ActiviteMQ重复消费:生产者发送一条消息,消费者多次消费同一条消息;解决:可以判断消息是否处理过;
ActiveMQ负责均衡:
producer 以 topic发送一条消息至broker,consumer a,b,c,d同时订阅此条消息,则a,b,c,d均能收到消息。如果topic发送消息的速速远远大小consumer a,b,c,d 的其中一个的消息能力(比如a),
那么此种情况整个系统将会受到影响,注意是整个系统,而不单单是某一 个consumer.那么能否通过增加机器来解决consumer a的消费能力,答案是可以。但是在我们现在描述的Topic下是不行。
因为broker通过clientid来唯一识别一个consumer,当两个节点通过一个clientId来连接broker,另一个会报错。
解决方案:ActiviteMQ的VirtualTopic可以解决consumer a 的问题; VirtualTopic是activemq高级特性,
producer的使用与普通Topic没有任何的不同,只是topic地名字以VirtualTopic.(注意有个点)作前缀即可:比如:VirtualTopic.order_update_message
.
consumer则有两种选择:一种是像与普通的topic那样使用,这种方式遇到的问题上面都说过了,一点都不会少;
另一种是comsumer可以以queue的形式去订阅该topic.默认情况下consumer的订阅队列名为Consumer.***.topic名字;比如:Consumer.customerNotify.VirtualTopic.order_update_message;
当然了这个默认的前缀是可以更改的---activemq.xml中
ActiveMQ+springboot用法:
1、mq配置:
@Configuration @EnableJms @EnableConfigurationProperties(JmsProperties.class) public class ActiveMQConfig { @Value("${spring.activemq.broker-url}") private String brokerURL; @Autowired JmsProperties jmsProperties; @Primary @Bean(destroyMethod = "stop") PooledConnectionFactory pooledConnectionFactory(ActiveMQConnectionFactory activeMQConnectionFactory){ PooledConnectionFactory pooledConnectionFactory = new PooledConnectionFactory(activeMQConnectionFactory); pooledConnectionFactory.setMaxConnections(10); return pooledConnectionFactory; } @Bean ActiveMQConnectionFactory activeMQConnectionFactory(){ ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(this.brokerURL); return activeMQConnectionFactory; } @Bean("myJmsTemplate") public JmsTemplate myJmsTemplate(@Qualifier("pooledConnectionFactory") ConnectionFactory connectionFactory){ JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory); return jmsTemplate; } }
2、MQ消费者注册:
@Configuration @EnableJms @Log4j @EnableConfigurationProperties(JmsProperties.class) public class MQbeanConfig { @Autowired JmsProperties jmsProperties; @Autowired OrderUpdateMessageListener orderUpdateMessageListener; @Autowired PaymentCallbckListener paymentCallbckListener; @Autowired MQNameConfig mqNameConfig; //----------------------------------- // VirtualTopic.order_update 订阅 /** *****************发送短信MQ 开始******************** **/ public Queue queueOrderUpdate() { log.info("注册发送短信MQ时:"+mqNameConfig.getQueueName()); return new ActiveMQQueue(mqNameConfig.getQueueName()); //return new ActiveMQQueue("Consumer.customerNotify.VirtualTopic.order_update"); } public MessageListenerAdapter orderUpdateQueueListenerAdapter() { MessageListenerAdapter adapter = new MessageListenerAdapter(); adapter.setMessageConverter(new SimpleMessageConverter()); adapter.setDelegate(orderUpdateMessageListener); return adapter; } @Bean @Profile({"test","prod","dev"}) public MessageListenerContainer messageListenerContainer(@Qualifier("pooledConnectionFactory") ConnectionFactory connectionFactory) { DefaultMessageListenerContainer container = new DefaultMessageListenerContainer(); container.setConnectionFactory(connectionFactory); container.setSessionAcknowledgeMode(jmsProperties.getListener().getAcknowledgeMode().getMode()); container.setDestination(queueOrderUpdate()); container.setMessageListener(orderUpdateQueueListenerAdapter()); container.setConcurrency("2-4"); return container; } /** *****************发送短信MQ 结束******************** **/
//点对点模式 QUEUE /** *****************支付成功MQ 开始******************** **/ public MessageListenerAdapter queuePaymentCallbackListenerAdapter() { MessageListenerAdapter adapter = new MessageListenerAdapter(); adapter.setMessageConverter(new SimpleMessageConverter()); adapter.setDelegate(paymentCallbckListener); return adapter; } @Bean @Profile({"test","prod","dev"}) public MessageListenerContainer queuePaymentCallbackContainer(@Qualifier("pooledConnectionFactory") ConnectionFactory connectionFactory) { DefaultMessageListenerContainer container = new DefaultMessageListenerContainer(); log.debug("启动支付回调监听程序:"+mqNameConfig.getPaymentCallbackQueueName()); container.setConnectionFactory(connectionFactory); container.setSessionAcknowledgeMode(jmsProperties.getListener().getAcknowledgeMode().getMode()); container.setDestination(new ActiveMQQueue(mqNameConfig.getPaymentCallbackQueueName())); container.setMessageListener(queuePaymentCallbackListenerAdapter()); return container; } /** *****************支付成功MQ 结束******************** **/ }
3、生成者:
// 发布订阅模式 TOPIC public void sendOrderUpdateMessage(String order_id,String ...type) { String queueName = mqNameConfig.getQueueNameTopic(); //String queueName = "VirtualTopic.order_update1"; Destination dest = new ActiveMQTopic(queueName); MessageCreator message = new MessageCreator() { @Override public Message createMessage(Session session) throws JMSException { MapMessage msg = session.createMapMessage(); msg.setString("order_id", order_id); if(type != null && type.length > 0){ msg.setString("type",type[0]); } return msg; } }; jmsTemplate.send(dest, message); } //点对点模式 QUEUE public void sendOrderUpdateMessage(String map) { String queueName = mqNameConfig.getPaymentCallbackQueueName(); Destination dest = new ActiveMQQueue(queueName); MessageCreator message = new MessageCreator() { @Override public Message createMessage(Session session) throws JMSException { MapMessage msg = session.createMapMessage(); msg.setObject("orderMap", map); return msg; } }; jmsTemplate.send(dest, message); }
//设置主题名称
package com.banxue.lmsservice.conf; public class MQNameConfig { private static String QUEUE_NAME = "";//发送短信 注册时使用 private static String QUEUE_NAME_TOPIC = "";//发送短信 发送消息时使用 private static String PAYMENT_CALLBACK_QUEUE_NAME = "";//支付成功回调 public void setDevNames(){ this.QUEUE_NAME = "Consumer.customerNotify.VirtualTopic.order_update_dev1"; this.QUEUE_NAME_TOPIC = "VirtualTopic.order_update_dev1"; this.PAYMENT_CALLBACK_QUEUE_NAME = "queue.payment_callback_dev1"; } public void setTestNames(){ this.QUEUE_NAME = "Consumer.customerNotify.VirtualTopic.order_update_test1"; this.QUEUE_NAME_TOPIC = "VirtualTopic.order_update_test1"; this.PAYMENT_CALLBACK_QUEUE_NAME = "queue.payment_callback_test1"; } public void setProdNames(){ this.QUEUE_NAME = "Consumer.customerNotify.VirtualTopic.order_update_20180502"; this.QUEUE_NAME_TOPIC = "VirtualTopic.order_update_20180502"; this.PAYMENT_CALLBACK_QUEUE_NAME = "queue.payment_callback_20180502"; } public static String getQueueNameTopic() { return QUEUE_NAME_TOPIC; } public String getQueueName() { return QUEUE_NAME; } public String getPaymentCallbackQueueName() { return PAYMENT_CALLBACK_QUEUE_NAME; } }
浙公网安备 33010602011771号