RabbitMq介绍及实战

为什么使用消息队列

优点比较核心的有 3 个:解耦、异步、削峰。
缺点有以下几个:

  1. 系统可用性降低
    系统引入的外部依赖越多,越容易挂掉。
  2. 系统复杂度提高
    硬生生加个 MQ 进来,如何保证消息的传递及消费成功
  3. 一致性问题
    A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是 BCD 三个系统那里,BD 两个系统写库成功了,结果 C 系统写库失败了,这数据就不一致了。

所以消息队列实际是一种非常复杂的架构,你引入它有很多好处,但是也得针对它带来的坏处做各种额外的技术方案和架构来规避掉。

如何保证高可用

RabbitMQ 的高可用性
RabbitMQ 有三种模式:单机模式、普通集群模式、镜像集群模式。
镜像集群模式(高可用性)可用集群搭建篇
这种模式,才是所谓的 RabbitMQ 的高可用模式。跟普通集群模式不一样的是,在镜像集群模式下,你创建的 queue,无论元数据还是 queue 里的消息都会存在于多个实例上,就是说,每个 RabbitMQ 节点都有这个 queue 的一个完整镜像,包含 queue 的全部数据的意思。然后每次你写消息到 queue 的时候,都会自动把消息同步到多个实例的 queue 上。

如何保证消息的传递和成功消费

,需要考虑:

  1. 消息发送确认机制

  2. 消费确认机制

  3. 消息的重新投递

  4. 消费幂等性, 等等

幂等性概念

来源http://www.javaxl.com/blog/articles/380

幂等性是什么?

我们可以借鉴数据库的乐观锁机制

比如我们执行一条更新库存的SQL语句

Update t_repository set count = count -1,version = version + 1 where
version = 1

Elasticsearch也是严格遵循幂等性概念,每次数据更新,version+1(博主博客前面有提到)

消费端-幂等性保障

在海量订单产生的业务高峰期,如何避免消息的重复消费问题?

消费实现幂等性,就意味着,我们的消息永远不会消费多次,即使我们收到了多条一样的消息

业界主流的幂等性操作

唯一ID+指纹码机制,利用数据库主键去重

利用Redis的原子性去实现

唯一ID+指纹码 机制

唯一ID+指纹码机制,利用数据库主键去重

Select count(1) from T_order where ID=唯一ID+指纹码

好处:实现简单

坏处:高并发下有数据库写入的性能瓶颈

解决方案:根据ID进行分库分表进行算法路由

利用Redis的原子性去实现

使用Redis进行幂等,需要考虑的问题

第一:我们是否要进行数据落库,如果落库的话,关键解决的问题是数据库和缓存如何做到原子性?

第二:如果不进行落库,那么都存储到缓存中,如何设置定时同步策略?

springboot+rabbitmq实战

目录结构
在这里插入图片描述

  1. rabbitmq搭建搭建篇
  2. springboot配置
spring:
    rabbitmq:
        addresses: 49.233.89.188::5672,49.233.89.188:5673,49.233.89.188:5674
        password: ''
        username: ''
        virtual-host: /

  1. config
/**
 * @Description: rabbitmq配置信息
 */
@Configuration
@Slf4j
@AllArgsConstructor
public class RabbitmqConfig {
    private final RedisService redisService;

    private final static Long EXPIRE_TIME_ONE_DAY = 60 * 60 * 24L;

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        //设置confirm模式 消息发送失败 记录redis重发
        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
            log.info("【mq应答:{},correlationData:{},cause:{}】",ack,correlationData,cause);
            if (ack){
                // 如果应答,说明消息已发送至server 将redis中的mq消息移除
                redisService.hmRemove(RedisConstants.MQ_SEND_QUEUE,correlationData.getId());
            }else{
                Object obj = redisService.hmGet(RedisConstants.MQ_SEND_QUEUE, correlationData.getId());
                if (obj != null){
                    MqMessage mqMessage = JSONObject.parseObject(obj.toString(),MqMessage.class);
                    if(mqMessage!=null){
                        mqMessage.setReSendCount(mqMessage.getReSendCount() + 1);
                        // 缓存队列1天
                        redisService.hmSet(RedisConstants.MQ_SEND_QUEUE, correlationData.getId().toString(), mqMessage,
                                EXPIRE_TIME_ONE_DAY);
                    }
                }
            }
        });
        // 同时设置,找不到exchange或者queue的时候回调
        rabbitTemplate.setMandatory(true);
        rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
            log.error("text:{},code:{},exchange:{},routingKey:{}",message,replyCode,replyText,routingKey);
        });
        return rabbitTemplate;
    }

    @Bean
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory){
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setMaxConcurrentConsumers(20);
        //自动应答模式
        factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        return factory;
    }

    /**
     * 人脸识别队列
     * @return  Queue
     **/
    @Bean
    public Queue faceIdentifyQueue(){
        Queue queue = new Queue(MqConstant.FACE_IDENTIFY_QUEUE);
        return queue;
    }
  }
  1. 拦截器
 */
@Component
@Aspect
@Slf4j
@AllArgsConstructor
public class MqRejectInterceptor {

    private final MqCommonCheckService mqCommonCheckService;
    private final SendMsgService sendMsgService;

    @Around(value = "execution(* com.xxx.xxx.rabbitmq.consumer.*.*(..)) && @annotation(rabbitListener)")
    public Object aroundService(ProceedingJoinPoint joinPoint, RabbitListener rabbitListener) throws Throwable {
        Object object = null;
        String classname = joinPoint.getTarget().getClass().getName();
        String method = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        log.info(classname + ":" + method);
        if (args.length == 3) {
            Message message = (Message) args[0];
            Channel channel = (Channel) args[1];
            MqMessage mqMessage = (MqMessage) args[2];
            String msg = JSON.toJSONString(mqMessage);
            log.info(classname + ":" + method + ";mqMessage=" + msg);
            //MqMessage mqMessage = JSONObject.parseObject(msg.toJSONString(),MqMessage.class);

            // 拒绝重复消费
            boolean checkFlag = mqCommonCheckService.receiveCheck(mqMessage, rabbitListener.queues()[0]);
            String queueName = rabbitListener.queues()[0];
            if (checkFlag) {
                log.info("消息已经处理了,queue={};msg:{}", queueName, msg);

                // 若消息已处理,告知Mq拒绝消费此消息
                channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            } else {
                boolean resendCheck = mqCommonCheckService.resendCheck(mqMessage, channel, message);
                if(resendCheck){
                    try {
                        object = joinPoint.proceed();

                        // 已收到消息 需要告知MQ从队列中删除此消息
                        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
                    } catch (Exception ex) {
                        log.error("接收处理消息失败,classname={}, queue={};msg={}", classname, queueName,msg, ex);

                        // 消息消费失败 需要移除已处理的标识
                        mqCommonCheckService.removeReceiveCheck(mqMessage, rabbitListener.queues()[0]);

                        // 已收到消息 需要告知MQ从队列中删除此消息
                        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);

                        // 将处理失败的消息放入redis重发队列
                        sendMsgService.saveToResendQueue(mqMessage);
                        log.info("消息处理失败,重新放入重发队列:{}, method:{},;msg={}", classname, method, msg);
                    }
                }else{
                    // 重发次数上限 直接拒绝消费
                    channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
                }
            }
        } else {
            object = joinPoint.proceed();
        }
        return object;
    }
}

  1. 生产者
@Component
@AllArgsConstructor
public class FaceIdentifyProvide {
    private final SendMsgService sendMsgService;

    /**
     * 发送消息
     * @Param object
     * @return void
     **/
    public void sendMessage(Object object){
        sendMsgService.sendMsg(new MqMessage(MqConstant.DIRECT_EXCHANGE,MqConstant.FACE_IDENTIFY_KEY,object));
    }
}

 */
@Service
@AllArgsConstructor
@Slf4j
public class SendMsgServiceImpl implements SendMsgService {
    private final static Long EXPIRE_TIME_ONE_DAY = 60 * 60 * 24L;// 1day
    private final RedisService cache;
    private final RabbitTemplate rabbitTemplate;

    @Override
    public void sendMsg(MqMessage mqMessage) {
        //final JSONObject jsonMessage = (JSONObject) JSON.toJSON(mqMessage);
        final String exchangeName = mqMessage.getExchange();
        final String message = objToString(mqMessage);
        cache.hmSet(RedisConstants.MQ_SEND_QUEUE, mqMessage.getId().toString(), mqMessage,
                EXPIRE_TIME_ONE_DAY);// 缓存队列1天
        try {
            log.info("发送MQ:" + message);
            rabbitTemplate.convertAndSend(mqMessage.getExchange(), mqMessage.getRoutingKey(),
                    mqMessage, new CorrelationData(mqMessage.getId()));
        } catch (RuntimeException e) {
            log.error("发送MQ失败,exchangeName=" + exchangeName + ";message=" + message, e);
            saveToResendQueue(mqMessage);
            try {
                // retry
                rabbitTemplate.convertAndSend(mqMessage.getExchange(), mqMessage.getRoutingKey(),
                        mqMessage, new CorrelationData(mqMessage.getId()));
            } catch (RuntimeException ex) {
                log.error("发送MQ失败,exchangeName=" + exchangeName + ";message=" + message, ex);
                saveToResendQueue(mqMessage);
            }
        }
    }

    @Override
    public void saveToResendQueue(MqMessage mqMessage) {
        mqMessage.setReSendCount(mqMessage.getReSendCount() + 1);
        cache.hmSet(RedisConstants.MQ_SEND_QUEUE, mqMessage.getId().toString(), (JSONObject) JSON.toJSON(mqMessage),
                EXPIRE_TIME_ONE_DAY);// 缓存队列1天
    }

    private String objToString(Object obj) {
        if (obj == null) {
            return null;
        } else if (obj instanceof String) {
            return (String) obj;
        } else {
            return JSON.toJSONString(obj);
        }
    }


}
  1. 消费者
   * 消息接受  并发消费
     */
    @RabbitHandler
    @RabbitListener(queues = MqConstant.FACE_IDENTIFY_QUEUE)
    public void receive(Message message, Channel channel, MqMessage mqMessage) throws IOException {
        log.info("queueName:{}, message:{}" ,MqConstant.FACE_IDENTIFY_QUEUE, mqMessage);
        try{
            // 保存人脸识别日志
            BsIdentifyRecord bsIdentifyRecord = bsIdentifyRecordService.saveIdentifyRecord(mqMessage.getObj());
            // 考勤打卡
            pushAttendance(bsIdentifyRecord);
            // 记录部门负责人办公轨迹
            bsDeptHeadEventService.updateDeptHeadEventByIdentify(bsIdentifyRecord.getDeviceNo(), bsIdentifyRecord.getGuid());
        }catch (Exception e){
            log.error("人脸识别消费失败:",e);
        }

    }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值