目录
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("****************************调用登录日志监听方法结束**********************************");
}
}
}