消息中间件RabbitMQ保证消息不丢失代码实现示例

前言:

在Rabbitmq使用过程单中,由于网络波动,服务宕机等不稳定因素,可能导致消息丢失;如何解决上述问题,增强mq的可靠性?

一、Rabbitmq安装

查询官网说明:

消息中间件RabbitMQ保证消息不丢失代码实现示例_第1张图片

RabbitMQ是由Erlang语言开发,所以在虚拟机上安装erlang。

erlang跟rabbitmq有版本对应关系,找到适配的版本安装,版本对应关系查看:

RabbitMQ Erlang Version Requirements — RabbitMQ

消息中间件RabbitMQ保证消息不丢失代码实现示例_第2张图片

Erlang下载:wget https://github.com/rabbitmq/erlang-rpm/releases/tag/v23.3.4.4/erlang-23.3.4.4-1.el7.x86_64.rpm --no-check-certificate

rabbitmq下载:wget https://github.com/rabbitmq/rabbitmq-server/releases/tag/v3.8.27/rabbitmq-server-3.8.27-1.el7.noarch.rpm

本人选择的mq版本需要socat,下载socat:

wget http://repo.iotti.biz/CentOS/7/x86_64/socat-1.7.3.2-5.el7.lux.x86_64.rpm

安装socat:

rpm -ivh socat-1.7.3.2-5.el7.lux.x86_64.rpm

安装erlang:rpm -ivh erlang-23.3.4.4-1.el7.x86_64.rpm

安装rabbitmq:rpm -ivh rabbitmq-server-3.8.27-1.el7.noarch.rpm

启动mq并设置开机自启:

systemctl start rabbitmq-server && systemctl enable rabbitmq-server

客户端访问插件安装:

rabbitmq-plugins enable rabbitmq_management

添加用户:

Rabbitmqctl add_user root root1234,添加了一个账号名root 密码root1234的用户

rabbitmqctl  set_user_tags root administrator 将root设置成管理员

客户端访问:

消息中间件RabbitMQ保证消息不丢失代码实现示例_第3张图片

Exchange4种type解释(headers忽略):

消息中间件RabbitMQ保证消息不丢失代码实现示例_第4张图片 消息中间件RabbitMQ保证消息不丢失代码实现示例_第5张图片

springcloud上演示消息不丢失

springcloud 集成rabbitmq

演示服务:base-info

common模块下pom添加依赖


    com.rabbitmq
    amqp-client


    org.springframework.boot
    spring-boot-starter-amqp
    RELEASE


    org.springframework.amqp
    spring-rabbit

 base-info下yml文件修改:

消息中间件RabbitMQ保证消息不丢失代码实现示例_第6张图片

 common服务下创建连接配置类:

@Configuration
public class RabbitMQConfig {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    @Value("${spring.rabbitmq.host}")
    private String host;
    @Value("${spring.rabbitmq.username}")
    private String username;
    @Value("${spring.rabbitmq.password}")
    private String password;
    @Value("${spring.rabbitmq.port}")
    private String port;
    @Bean
    public ConnectionFactory connectFactory(RabbitProperties rabbitProperties){
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
        connectionFactory.setUsername(username);
        connectionFactory.setPassword(password);
        connectionFactory.setHost(host);
        connectionFactory.setPort(Integer.parseInt(port));
        connectionFactory.setVirtualHost("/");
        connectionFactory.setPublisherConfirms(true);//发布确认,设置此参数后,消息发送后不管成功与否都会调用生产者的回调方法
        connectionFactory.setPublisherReturns(true);
        return connectionFactory;
    }
}

 rabbitmq消息传递流程:

生产者产生消息->rabbitmq broker->exchange->queue->消费者消费消息

从上述流程可以看出,消息丢失的环节有:

a.生产者往mq推送时,丢失

b.mq本身将数据丢失

c.消费者消费失败了,mq直接删除了消息

保证生产者往mq推送方案:整体思路是,在生产者服务种记录推送的消息,将推送失败的消息通过计划任务的方式重新推送,如果超过一定的重试次数还未成功,人工干预查看错误原因

创建推送日志表:

CREATE TABLE `rabbit_msg_send_log` (
  `msg_id` varchar(50) NOT NULL,
  `msg_body` blob NOT NULL COMMENT '发送的消息体',
  `send_response_msg` varchar(3000) DEFAULT NULL COMMENT 'mq应答信息',
  `send_flag` int(2) NOT NULL DEFAULT '1' COMMENT '0:发送失败,1:发送成功',
  `retry_times` int(2) DEFAULT '0' COMMENT '重试次数',
  `created_date` datetime DEFAULT NULL COMMENT '创建时间',
  `updated_time` datetime DEFAULT NULL,
  PRIMARY KEY (`msg_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

创建一个通用的消息发送类:

/**
 * 消息生产者
 */
@Component
public class RabbitMQSender {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired
    RabbitTemplate rabbitTemplate;
    @Autowired
    IRabbitMsgSendLogService rabbitMsgSendLogService;

    public void msgSend(Object msg) {
        try{
            String msgId = UUID.randomUUID().toString();
            logger.info("消息Id:{}",msgId);
            CorrelationData correlationData = new CorrelationData(msgId);
            rabbitTemplate.setMandatory(true);
            rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
                @Override
                public void confirm(CorrelationData correlationData, boolean ack, String reason) {
                    QueryWrapper queryWrapper = new QueryWrapper<>();
                    queryWrapper.eq("msg_id",correlationData.getId());
                    List rabbitMsgSendLogList = rabbitMsgSendLogService.listObjs(queryWrapper);
                    RabbitMsgSendLog log = null;
                    if(rabbitMsgSendLogList.size()>0){
                        log = rabbitMsgSendLogList.get(0);
                        log.setUpdatedTime(new Date());
                    }else{
                        log = new RabbitMsgSendLog();
                        log.setCreatedDate(new Date());
                    }

                    log.setMsgId(correlationData.getId());
                    log.setMsgBody(msg);

                    if(ack){//mq成功接收消息
                        log.setSendFlag(1);
                    }else{//给异常发送的数据在日志种打上标记,后面用定时任务扫描重新发送
                        log.setSendFlag(0);
                    }
                    rabbitMsgSendLogService.save(log);
                }
            });
            //exchange到queue失败,调用
            rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
                @Override
                public void returnedMessage(Message message, int replyCode, String replyText, String exchangeName, String routerKey) {
                    logger.info("消息{}", GsonUtil.obj2Json(message));
                    logger.info("code{}", replyCode);
                    logger.info("回复消息体{}", replyText);
                    logger.info("交换器{}", exchangeName);
                    logger.info("路由key{}", routerKey);
                }
            });
            rabbitTemplate.convertAndSend("jywtest","test.router.key",msg,correlationData);


        }catch (Exception e){

        }
    }
} 

通过回调函数ConfirmCallback获取mq是否成功接收的状态。

创建计划任务找到重试次数小于等于6且推送失败的消息:

消息中间件RabbitMQ保证消息不丢失代码实现示例_第7张图片

 mq本身导致消息丢失:exchange、queue持久化,将交换器、队列、消息持久化到mq服务器磁盘上

方式一,客户端直接操作

消息中间件RabbitMQ保证消息不丢失代码实现示例_第8张图片

消息中间件RabbitMQ保证消息不丢失代码实现示例_第9张图片 方式二代码实现:

public static void main(String[] args) throws Exception{
    ConnectionFactory factory = new ConnectionFactory();
    factory.setUsername("root");
    factory.setPassword("root1234");
    factory.setHost("192.168.1.119");
    factory.setPort(5672);
    factory.setVirtualHost("/");
    Connection connection = factory.newConnection();
    Channel channel = connection.createChannel();//创建信道
    //声明交换器 参数1:交换器名称 参数2:交换器类型 参数3:是否持久化
    channel.exchangeDeclare("exchangename1","topic",true);
    //声明消息队列 参数1:队列名称 参数2:是否持久化 参数3:是否排他 参数4:如未有绑定交换器是否删除 参数5:其他参数
    channel.queueDeclare("queuename1",true,false, false, null);
    //交换器与队列绑定 参数1:消息队列名称 参数2:交换器名称 参数3:绑定的路由key
    channel.queueBind("queuename1","exchangename1","exchange_queue_bind_routerkey");
    //创建完毕后,向此消息队列推送消息
    String msg = "hello,rabbitmq ";  //消息内容
    String routing_key = "exchange_queue_bind_routerkey";  //发送消息使用的routing-key
    channel.basicPublish("exchangename1",routing_key,null,msg.getBytes());
    channel.close();
    connection.close();
}

消费者消息丢失: 

mq默认是自动ack,即不管消费者是否成功消费消息,mq都会在内存中删除对应的消息。

改为手动ack后,消费者成功消费后,通知mq在内存中删除,没有通知就一直在unacked中等待

消费者ack改为手动yml配置:

消息中间件RabbitMQ保证消息不丢失代码实现示例_第10张图片

/**
 * mq消息接受类
 */
@Component
public class RabbitMQReciver implements ChannelAwareMessageListener {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    private MessageConverter messageConverter = new SimpleMessageConverter();
    @Autowired
    RabbitTemplate rabbitTemplate;
    @Autowired
    RedisTemplate redisTemplate;
    @RabbitListener(queues = "queuesTest")
    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        //重复消费 丢弃
        if(redisTemplate.hasKey(message.getMessageProperties().getMessageId())){
            return ;
        }
        Object obj =  messageConverter.fromMessage(message);
        logger.info("获取到的消息:{}", GsonUtil.obj2Json(obj));
        logger.info("被消费的消息id:{}",message.getMessageProperties().getMessageId());
        //手动ack
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
        redisTemplate.opsForValue().set(message.getMessageProperties().getMessageId(),null,1800, TimeUnit.SECONDS);

    }
}

你可能感兴趣的:(rabbitmq,分布式)