我们为什么使用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; } }

 

  

posted @ 2019-04-15 11:59  wangshunyao  阅读(431)  评论(0)    收藏  举报