activemq和rabbitmq

目录

 

mq解决重复消费:

mq解决消息丢失:

解决分布式事务:mq可靠消息的最终一致性

jms可靠消息:

发布/订阅

springboot整合activemq:

rabbitmq:


mq解决重复消费:

1、记录下每个消息的msgID
2、新消息来的时候,查看该消息的msgID是否已记录,是则抛弃,否则消费

那么msgID记录在哪里呢?当然是缓存。所以我在解决这个问题的时候,使用了redis缓存。具体做法如下:

1、消费端接收到消息的时候,调用redis提供的incr方法,以msgID作为key(具有唯一性),value则默认从1开始递增。
2、当incr返回值为1时,设置其失效时间为两分钟以后(每个msgID保留两分钟足矣!),并且该消息需要被消费。
3、当incr返回值大于1时,则忽略该消息。

mq解决消息丢失:


    先分析消息丢失的情况?
1:生产者发送到mqservice
kafka:消息发送+回调
RocketMq:1:消息发送+回调,2:开启事物
RabbitMq:1:消息发送+回调,2:开启事物
这种是用来确认生产者将消息发送给交换器,交换器传递给队列的过程中,消息是否成功投递。
         发送确认分为两步,一是确认是否到达交换器,二是确认是否到达队列。

 2:手动开启事物:channel.txSelect()开启事物,channel.txCommit()提交事物.channel.txRollBack()回滚事物
      这种方式对channel是回产生阻塞的,回造成吞吐量下降。
2:mq是基于内存操作的,当mq挂了之后,怎么保存到磁盘
   开启持久化:durable = true;
3:消费者消费的时候

解决分布式事务:mq可靠消息的最终一致性

可靠消息一致性方案指的是 当事务发起方执行完成本地事务后并发出一条消息,事务的参与方(消息消费者)一定要能接收到消息并处理成功,此方案强调的是只要消息发给事务参与方最终事务要达到一致。

1:解决本地事务和发送消息的原子性: 数据库操作成功,mq接收成功,但是网络延迟,mq返回超时,这是数据事务回滚,mq确执行成功了。
     begin transaction
            //1.数据库操作
            //2.发送mq
     end  transaction

2:保证事务的参与方一定要接收到消息。
    事务参与方必须能从
3:要保证幂等性。

jms可靠消息:

1:自动签收: Session.AUTO_ACKNOWLEDGE
2:手动签收: Session.CLIENT_ACKNOWLEDGE  表示手动签收 代码中 textMessage.acknowledge();//手动签收:
3:事务签收:true--表示开启事务,Session.CLIENT_ACKNOWLEDGE--手动签收
                       Session session = createConnection.createSession(true, Session.AUTO_ACKNOWLEDGE);
                       代码中session.commit();进行事务提交
场景1 
生产者不开启session,客户端必须有手动签收模式
Session session = createConnection.createSession(Boolean.FALSE, Session.CLIENT_ACKNOWLEDGE);
消费
不开启session,客户端必须有手动签收模式
TextMessage textMessage = (TextMessage) createConsumer.receive();
//接受
消息
textMessage.acknowledge();

场景2
生产
者不开启session,客户端自动签收模式
Session session = createConnection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
消费
不开启session,自动签收消息
Session session = createConnection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);

场景3
事物
消息 生产事物形式,必须要消息提交事物,才可以提交到队列中
Session session = createConnection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE);
session.commit();
消费

Session session = createConnection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE);
session.commit();

 

发布/订阅

//生产者
public class TOPSend {

	private static String BROKERURL = "tcp://127.0.0.1:61616";
	private static String TOPIC = "my-topic";

	public static void main(String[] args) throws JMSException {
		start();
	}

	static public void start() throws JMSException {
		System.out.println("生产者已经启动....");
		// 创建ActiveMQConnectionFactory 会话工厂
		ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(
				ActiveMQConnection.DEFAULT_USER, ActiveMQConnection.DEFAULT_PASSWORD, BROKERURL);
		Connection connection = activeMQConnectionFactory.createConnection();
		// 启动JMS 连接
		connection.start();
		Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
		MessageProducer producer = session.createProducer(null);
		producer.setDeliveryMode(DeliveryMode.PERSISTENT);
		send(producer, session);
		System.out.println("发送成功!");
		connection.close();
	}

	static public void send(MessageProducer producer, Session session) throws JMSException {
		for (int i = 1; i <= 5; i++) {
			System.out.println("我是消息" + i);
			TextMessage textMessage = session.createTextMessage("我是消息" + i);
			Destination destination = session.createTopic(TOPIC);
			producer.send(destination, textMessage);
		}
	}

}
//消费者
public class TopReceiver {
	private static String BROKERURL = "tcp://127.0.0.1:61616";
	private static String TOPIC = "my-topic";

	public static void main(String[] args) throws JMSException {
		start();
	}

	static public void start() throws JMSException {
		System.out.println("消费点启动...");
		// 创建ActiveMQConnectionFactory 会话工厂
		ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(
				ActiveMQConnection.DEFAULT_USER, ActiveMQConnection.DEFAULT_PASSWORD, BROKERURL);
		Connection connection = activeMQConnectionFactory.createConnection();
		// 启动JMS 连接
		connection.start();
		// 不开消息启事物,消息主要发送消费者,则表示消息已经签收
		Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
		// 创建一个队列
		Topic topic = session.createTopic(TOPIC);
		MessageConsumer consumer = session.createConsumer(topic);
		// consumer.setMessageListener(new MsgListener());
		while (true) {
			TextMessage textMessage = (TextMessage) consumer.receive();
			if (textMessage != null) {
				System.out.println("接受到消息:" + textMessage.getText());
				// textMessage.acknowledge();// 手动签收
				// session.commit();
			} else {
				break;
			}
		}
		connection.close();
	}

}

springboot整合activemq:

1:引入yml配置

spring:

  activemq:

    broker-url: tcp://127.0.0.1:61616

    user: admin

    password: admin

queue: springboot-queue

server:

  port: 8080

//创建queue
@Configuration
public class QueueConfig {
	@Value("${queue}")
	private String queue;

	@Bean
	public Queue logQueue() {
		return new ActiveMQQueue(queue);
	}

	@Bean
	public JmsTemplate jmsTemplate(ActiveMQConnectionFactory activeMQConnectionFactory, Queue queue) {
		JmsTemplate jmsTemplate = new JmsTemplate();
		jmsTemplate.setDeliveryMode(2);// 进行持久化配置 1表示非持久化,2表示持久化</span>
		jmsTemplate.setConnectionFactory(activeMQConnectionFactory);
		jmsTemplate.setDefaultDestination(queue); // 此处可不设置默认,在发送消息时也可设置队列
		jmsTemplate.setSessionAcknowledgeMode(4);// 客户端签收模式</span>
		return jmsTemplate;
	}

	// 定义一个消息监听器连接工厂,这里定义的是点对点模式的监听器连接工厂
	@Bean(name = "jmsQueueListener")
	public DefaultJmsListenerContainerFactory jmsQueueListenerContainerFactory(
			ActiveMQConnectionFactory activeMQConnectionFactory) {
		DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
		factory.setConnectionFactory(activeMQConnectionFactory);
		// 设置连接数
		factory.setConcurrency("1-10");
		// 重连间隔时间
		factory.setRecoveryInterval(1000L);
		factory.setSessionAcknowledgeMode(4);
		return factory;
	}

}
//创建生产者
@Component
@EnableScheduling
public class Producer {
	@Autowired
	private JmsMessagingTemplate jmsMessagingTemplate;
	@Autowired
	private Queue queue;

	@Scheduled(fixedDelay = 5000)
	public void send() {
		jmsMessagingTemplate.convertAndSend(queue, "测试消息队列" + System.currentTimeMillis());
	}
}
创建消费者
@JmsListener(destination = "${queue}")
	public void receive(TextMessage text, Session session) throws JMSException {
		try {
			System.out.println("生产者第" + (++count) + "次向消费者发送消息..");
			// int id = 1 / 0;
			String value = text.getText();
			System.out.println("消费者收到消息:" + value);
			//手动签收
			text.acknowledge();
		} catch (Exception e) {
			// 如果代码发生异常,需要发布版本才可以解决的问题,不要使用重试机制,采用日志记录方式,定时Job进行补偿。
			// 如果不需要发布版本解决的问题,可以采用重试机制进行补偿。
			// session.recover();// 继续重试
			e.printStackTrace();
		}
	}

	public static void main(String[] args) {
		SpringApplication.run(Consumer.class, args);
	}

rabbitmq:

生产者的RabbitMqConfig:

package com.yd.caslogcollect.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMqConfig {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    public static final String EXCHANGE = "cas-access-exchange";
    public static final String QUEUE = "cas-access-log";
    public static final String ROUTINGKEY = "cas-access-log-routingKey";

    //@Value("${rabbitmq.host}")
    //private String host;

    //@Value("${rabbitmq.port}")
    //private int port;

    @Value("${rabbitmq.addresses}")
    private String address;

    @Value("${rabbitmq.username}")
    private String username;

    @Value("${rabbitmq.password}")
    private String password;

    @Bean
    public ConnectionFactory connectionFactory() {
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
        connectionFactory.setAddresses(address);
        connectionFactory.setUsername(username);
        connectionFactory.setPassword(password);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setPublisherConfirms(true);
        return connectionFactory;
    }

    @Bean
    public RabbitTemplate rabbitTemplate() {
        final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory());
        rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback(){
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                logger.info("消息发送成功:correlationData({}),ack({}),cause({})",correlationData,ack,cause);
            }
        });
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                logger.info("消息丢失:exchange({}),route({}),replyCode({}),replyText({}),message:{}",exchange,routingKey,replyCode,replyText,message);
            }
        });
        return rabbitTemplate;
    }

    //创建queue
    @Bean
    public Queue queue(){
        return new Queue(QUEUE,true);
    }
    //创建交换机
    @Bean
    public TopicExchange exchange(){
        return new TopicExchange(EXCHANGE,true,false);
    }
    //绑定交换机
    @Bean
    public Binding binding() {
        return BindingBuilder.bind(queue()).to(exchange()).with(ROUTINGKEY);
    }
}

 生产者:

package com.yd.caslogcollect.controller;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.yd.caslogcollect.config.RabbitMqConfig;
import com.yd.caslogcollect.model.CasServiceLog;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.Date;

@RestController
public class CasServiceLogController {
    private final Logger logger = LoggerFactory.getLogger(CasServiceLogController.class);
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostMapping("/loginService")
    public String setCasServiceLog(@RequestBody CasServiceLog autheLog){
        try{
            logger.info("生产mq开始access_log:" + new GsonBuilder().create().toJson(autheLog) );
            //解决gson和fastjson时间类型转换报错问题
            Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();
            String jsonString = gson.toJson(autheLog);
            Message message = MessageBuilder
                    .withBody(jsonString.getBytes())
                    .setContentType(MessageProperties.CONTENT_TYPE_JSON)
                    .build();
            rabbitTemplate.convertAndSend(RabbitMqConfig.EXCHANGE,RabbitMqConfig.ROUTINGKEY, message);
            logger.info("rabbitmq生产access_log结束。。。");
        }catch (Exception e){
            logger.error("rabbitmq生产access_log失败。。。" + e.getMessage(),e);
            return "error";
        }
        return "success";
    }

}

yml配置文件

rabbitmq:
  addresses: 10.19.185.17:5001
  username: admin
  password: 123456
  #port: 5001
  virtual-host: /
  listener:
    direct:
      acknowledge-mode: manual
    simple:
      acknowledge-mode: manual

server:
  port: 8085
  servlet:
    context-path: /caslogcollect

消费者:

package com.yd.portal.task.mq.listener;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.rabbitmq.client.Channel;
import com.yd.portal.task.model.AutheLog;
import com.yd.portal.task.service.IAutheLoginService;
import com.yd.portal.task.util.ToolUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Date;

/**
 * @Description: mq监听cas用户登录日志
 * @Author: lcj
 * @CreateDate: 2020/6/23 10:00
 */

@Component
public class CasAutheLogListener {

    private final Logger logger = LoggerFactory.getLogger(CasAutheLogListener.class);
    @Autowired
    private IAutheLoginService autheLoginService;
    //当消费者先启动是如果没有queue会报错,所以 @QueueBinding 绑定并创建交换机和queue
    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue(value = "${rabbitmq.authe.cas-log.queue-name}",durable = "true"),
                    exchange = @Exchange(type = "${rabbitmq.authe.cas-log.exchange-type}",
                            name = "${rabbitmq.authe.cas-log.exchange-name}"),
                    key = "${rabbitmq.authe.cas-log.routing_key}"
            )
    })
    public void onMessage(Message message, Channel channel) {
        logger.info("****************************调用登录日志监听方法开始**********************************");
        try {
            if (ToolUtil.isOneEmpty(message, message.getBody())) {
                return;
            }
            String body = new String(message.getBody(), "utf-8");
            logger.info("推送的登录日志信息: " + body);
            AutheLog autheLog = JSON.parseObject(body, AutheLog.class);
            boolean save = autheLoginService.save(autheLog);
            if(save){
                logger.info("认证信息入库成功。");
            }
        } catch (UnsupportedEncodingException e) {
            logger.error("推送的登录日志信息编码失败: " +  ToolUtil.parseByte2HexStr(message.getBody()) ,e);
        } finally {
            try {
                channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
            } catch (IOException e) {
                logger.error("推送的登录日志消息确认失败: " +ToolUtil.parseByte2HexStr(message.getBody()) ,e);
            }
            logger.info("****************************调用登录日志监听方法结束**********************************");
        }
    }
}


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值