【消息中间件/笔记】3个分布式消息中间件之一Rabbitmq(一)

主流MQ产品

  • RabbitMQ
    • 优点
      • 消息可靠性高
      • 功能全面
    • 缺点
      • 吞吐量较低
      • 消息积压会影响性能
      • erlang语言比较小众
    • 适用环境
      • 企业内部系统调用
  • kafka
    • 优点
      • 吞吐量大
      • 性能好
      • 技术生态完整
    • 缺点
      • 功能单一
    • 适用环境
      • 分布式日志收集
      • 大数据采集
  • RocketMQ
    • 优点
      • 高吞吐量
      • 高性能
      • 高可用
      • 高级功能非常全
    • 缺点
      • 技术生态相对没那么完整
    • 适用环境
      • 几乎全场景
      • 尤其适合金融场景

RabbitMQ

基本概念

在这里插入图片描述

交换机

队列

  • 类型
    • Classic:经典队列是默认类型,灵活性强,大部分功能可以通过参数进行设置,在特定情况下消息会丢失。
    • Quorum:适合用于集群使用,一条消息会同步到集群节点上,有消息备份,且主节点故障需要重新选举。增加了毒消息的处理,可通过x-delivery-limit来判断是否为毒消息后进行删除。队列也有一定策略,没有消息是会自动删除。
    • Stream:将消息数据存储到了日志文件中,解决了传统消息挤压导致性能下降的问题,但他的刷盘持久化机制需要靠操作系统来进行刷盘,安全性很差。
  • 一系列参数
    • duration:是否持久化,即rabbitmq重启系统可以到磁盘获取之前未消费的数据
    • auto Delete:当前队列没有消费者连接会自动删除
    • dead letter exchange:设置死信交换机,即数据没有应答时会将消息转到死信队列中,可用于做延迟队列。
    • auto expire:在一定时间内,队列未被使用,就会自动删除

交换机

  • 类型
    • direct:绑定队列,可通过routingKey完全匹配来给指定的队列传递数据。
    • fanout:当交换机接收到消息,他会给绑定的所有队列都传递一条消息。
    • topic:支持routingKey通配符匹配,即模糊匹配,*一个单词,#0-n个单词。
    • headers:与routingkey不同,他可以通过object对象来进行路由匹配,any是匹配一个键值对即可发送消息,all需要所以键值对都匹配才可以发送消息。
  • 一系列参数
    • alternate exchange:备用交换机,当消息不能被路由时,那么该不可用消息会转发到备用交换机上。

基础开发模型

依赖

  <dependency>
    <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-amqp</artifactId>
   </dependency>

原生API代码写法

消费者
// 推模式
public class Customer {
    private static final String HOST="127.0.0.1";
    private static final Integer PORT=5672;
    private static final String USERNAME="guest";
    private static final String PASSWORD="guest";
    private static final String VIRTUAL_HOST="/mirror";
    private static final String QUEUE_NAME = "test2";
    public static void main(String[] args) throws IOException, TimeoutException {
        // 创建一个连接
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(HOST);
        factory.setPort(PORT);
        factory.setUsername(USERNAME);
        factory.setPassword(PASSWORD);
        factory.setVirtualHost(VIRTUAL_HOST);

        // 获取连接
        Connection connection = factory.newConnection();
        // 创建通道 创建单个通道不需要传参
        Channel channel = connection.createChannel();
        // 创建多个通道需要传一个channelNumber 如果channelNumber重复了,
        // 服务端就会返回一个null
        Channel channelB = connection.createChannel(2);


        //声明队列
        Map<String,Object> params = new HashMap<>();

        channel.queueDeclare(QUEUE_NAME,true,false,false,params);

        // 每个worker同时最多只处理一个消息 这是只获取一条消息
        channel.basicQos(1);

        // 回调函数,处理接收到的消息
        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("============");
                String routingKey = envelope.getRoutingKey();
                System.out.println("routingKey->"+routingKey);
                String contentType = properties.getContentType();
                System.out.println("contentType->"+contentType);
                long deliveryTag = envelope.getDeliveryTag();
                System.out.println("deliveryTag->"+deliveryTag);
                System.out.println("消息:"+ new String(body,"UTF-8"));

                // autoAck 没有开启自动应答,那么这里需要手动应答
                // 那么这条消息就会重新入队
                channel.basicAck(deliveryTag,true);
            }
        };
        // 消费者接收消息  queue 队列名称 autoAck 自动给服务端应答
        channel.basicConsume(QUEUE_NAME,false,consumer);

    }
    // 拉模式
    void pull() throws IOException, TimeoutException {
        // 创建一个连接
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(HOST);
        factory.setPort(PORT);
        factory.setUsername(USERNAME);
        factory.setPassword(PASSWORD);
        factory.setVirtualHost(VIRTUAL_HOST);

        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        // 他是直接获取一次就结束了 一次拉一条
        // 如果队列为空,那么就会返回null,需要处理
        GetResponse response = channel.basicGet(QUEUE_NAME,false);
        byte[] body = response.getBody();
        Envelope envelope = response.getEnvelope();
        int messageCount = response.getMessageCount();
        AMQP.BasicProperties props = response.getProps();

        System.out.println("==========");
        String routingKey = envelope.getRoutingKey();
        System.out.println("routingKey->"+routingKey);
        String contentType = props.getContentType();
        System.out.println("contentType->"+contentType);
        long deliveryTag = envelope.getDeliveryTag();
        System.out.println("messageCount"+messageCount);
        System.out.println("消息内容->"+new String(body,"UTF-8"));

        channel.close();
        connection.close();
    }
生产者
public class Producer {
    private static final String HOST="127.0.0.1";
    private static final Integer PORT=5672;
    private static final String USERNAME="guest";
    private static final String PASSWORD="guest";
    private static final String VIRTUAL_HOST="/mirror";
    private static final String QUEUE_NAME = "test2";

    private static final String EXCHANGE_NAME = "producerExchange";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 创建一个连接
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(HOST);
        factory.setPort(PORT);
        factory.setUsername(USERNAME);
        factory.setPassword(PASSWORD);
        factory.setVirtualHost(VIRTUAL_HOST);

        // 获取连接
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        // 设置队列类型
        Map<String,Object> params = new HashMap<>();
        params.put("x-queue-type","classic");

        // 声明交换机 如果服务端已经存在交换机,那么声明交换机的参数需要相同,参数不一致会报错
        // exchangeDeclare(exchange交换机名字,type交换机类型,durable 是否持久化保存,
        // autoDelete 交换机没有使用自动删除,arguments 构建交换机的参数)
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT,true,false,params);

        // 声明队列
        // queue 队列名 durable 是否持久化 否 重启服务数据丢失
        // exclusive 队列是否仅允许创建者能使用 autoDelete 没有队列连接,自动删除
        channel.queueDeclare(QUEUE_NAME,true,false,false,new HashMap<>());

        // 声明绑定关系
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"key1");

        // 发送消息
        String message = "直接发送到队列";
        // 发送到队列
        channel.basicPublish("",QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes());
        String message2 = "直接发送到交换机";
        // 发送到交换机
        channel.basicPublish(EXCHANGE_NAME,"key1", MessageProperties.PERSISTENT_TEXT_PLAIN,message2.getBytes());

        channel.close();
        connection.close();
    }
}

整合Springboot实现mq的使用

连接配置

spring:
  application:
    name: mqProject
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    virtual-host: /mirror
#   用于开启回调的配置ConfirmCallback
    publisher-confirm-type: correlated
    # 开启 ReturnsCallback
    publisher-returns: true
    listener:
      simple:
        #    单次推送消息数量
        prefetch: 1
        #        消费者消费线程数量
        concurrency: 5
#        消费者最大线程数量
        max-concurrency: 10
        acknowledge-mode: manual

队列和交换机的配置

@Configuration
public class FanoutConfig {
    // 声明队列
    @Bean
    public Queue fanoutA(){
        return new Queue("testA",true);
    }
    @Bean
    public Queue topicB(){
        return new Queue("testB",true);
    }
    // 声明交换机
    @Bean
    public FanoutExchange fanoutExchangeA(){
        Map<String,Object> params = new HashMap();

        return new FanoutExchange("springExchangeA",true,false);
    }
    @Bean
    public TopicExchange topicExchangeA(){
        return new TopicExchange("topicExchange",true,false);
    }

    // 绑定关系
    @Bean
    public Binding bindA(){
        return BindingBuilder.bind(fanoutA()).to(fanoutExchangeA());
    }
    @Bean
    public Binding bindB(){
        return BindingBuilder.bind(topicB()).to(topicExchangeA()).with("*.host");
    }
}

消费者配置

@Component
public class Consumer {
    @RabbitListener(queues = "testA",containerFactory = "qos_2")
    public void testA(Message message, Channel channel,String messageStr) throws UnsupportedEncodingException {
        System.out.println("接收到的消息"+new String(message.getBody(),"UTF-8"));

    }
}

消息发送

@RestController
public class producer {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/testTopic")
    public Object testTopic(){
        // 设置部分请求参数
        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN);
        messageProperties.setPriority(2);

        String message = "测试";
        // 设置请求转换器 json格式
        rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
        rabbitTemplate.send("directqueue",new Message(message.getBytes()));
        return message;
    }
}

监听与回溯

原生api写法

  • 监听类
    • DeliverCallback:消费者消息传递到的时候触发
    • CancelCallback:队列删除的时候触发
    • ConsumerShutdownSignalCallback:connection连接中断的时候触发
  • deliveryType : 消息传递的编号
  • consumerTag: 消费者的标签
public class CallbackConsumer {
    private static final String HOST="127.0.0.1";
    private static final Integer PORT=5672;
    private static final String USERNAME="guest";
    private static final String PASSWORD="guest";
    private static final String VIRTUAL_HOST="/mirror";
    private static final String QUEUE_NAME = "test2";
    private static final String EXCHANGE_NAME = "producerExchange";
    private static final String EXCHANGE_NAME_B = "producerExchangeB";
    public static void main(String[] args) throws IOException, TimeoutException {
        // 创建一个连接
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(HOST);
        factory.setPort(PORT);
        factory.setUsername(USERNAME);
        factory.setPassword(PASSWORD);
        factory.setVirtualHost(VIRTUAL_HOST);

        // 获取连接
        Connection connection = factory.newConnection();
        // 创建通道 创建单个通道不需要传参
        Channel channel = connection.createChannel();

        // 设置备用交换机
        Map<String,Object> params = new HashMap<>();
        params.put("alternate-exchange",EXCHANGE_NAME_B);
        channel.exchangeDeclare(EXCHANGE_NAME_B,BuiltinExchangeType.DIRECT,true,false,params);

        // 服务端 消息穿过来了后触发
        channel.basicConsume(QUEUE_NAME, new DeliverCallback() {
            @Override
            public void handle(String consumerTag, Delivery delivery) throws IOException {
                long deliveryTag = delivery.getEnvelope().getDeliveryTag();
                String correlationId = delivery.getProperties().getCorrelationId();
                System.out.println("接收到消息" + new String(delivery.getBody(), "UTF-8"));
                channel.basicAck(deliveryTag, false);
            }
            // CancelCallback 消费者队列删除了会触发
        }, new CancelCallback() {
            @Override
            public void handle(String s) throws IOException {
                System.out.println("删除队列触发");
            }
            // 连接关闭会触发
        }, new ConsumerShutdownSignalCallback() {
            @Override
            public void handleShutdownSignal(String s, ShutdownSignalException e) {
                System.out.println("连接关闭了");
            }
        });
    }
}

springboot写法

@Component
public class RabbitmqConfirmCallback implements RabbitTemplate.ConfirmCallback {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void init(){
        System.out.println("实例化了");
        rabbitTemplate.setConfirmCallback(this);
    }
    @Override
    public void confirm(CorrelationData correlationData, boolean b, String s) {
        System.out.println("监听到ack");
    }
}
@Component
public class RabbitReturnCallback implements RabbitTemplate.ReturnsCallback {
    @Override
    public void returnedMessage(ReturnedMessage returnedMessage) {
        System.out.println("监听到服务端返回的消息");
    }
}

死信队列

  • 参数
    • Dead letter exchange:配置死信交换机(普通交换机)
    • Dead letter routing key:配置死信的routing key 他会覆盖原本的routingkey
  • 机制
  • 主要就是创建一个死信交换机并绑定一个队列,在其他队列配置上死信队列,当他的接收到消息,但没有接到ack确认信息,那么他就会把该数据发送到死信队列中。
  • 其他作用
    • 延迟队列:只要做法就是,在一个队列中设置一个ttl时间,等这个时间超时就进入到死信队列中,即达到延迟效果。

Kafka

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AloneLie

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值