MQ的学习

1、什么是MQ?
MQ = Message Queue消息队列,是对消费者生产者模型的升级。当前由于复杂的业务和服务压力过大,大多都采取微服务。传统的生产者和消费者模型是不能够满足条件的。同时消息的生产和消费都是异步的,而且我只关心消息发送和接收,没有大量的业务逻辑的写入,为了实现系统之间的解耦,通过利用高效的消息传递机制进行于平台无关的数据交流,并基于数据通信来进行分布式系统的集成。
2、分类。
当前市面上右多种消息中间件,比如ActiveMQ、RabbitMQ、kafka等。

  • kafka:分布式订阅-发布系统,主要特点是基于pull的模式来处理消息消费。追求高吞吐量,主要用于分布式日志的收集和传输。
  • ActiveMQ:完全支持JMS规范的消息中间件,丰富的API,多集群架构模式,在中小型企业用的较多。
  • RabbitMQ: 使用Erlang开发的,是基于AMOP协议实现的,AMQP的主要特征是面向消息、队列、路由(包括点对点、发布/订阅)、可靠性、安全。用于企业系统内数据一致性、稳定性、可靠性高的场景,性能和吞吐量其次。

正式开始介绍RabbitMQ:
AMQP协议图理解:
在这里插入图片描述

1、ubuntu安装rabbitmq
官网:
下载路径
1.1、安装需要支撑的语言
1、安装 Erlang:
首先在系统中加入 erlang apt 仓库。

$ wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb
$ dpkg -i erlang-solutions_1.0_all.deb

修改 Erlang 镜像地址,默认的下载速度特别慢(嫌麻烦的也可以不修改)。

$ vim /etc/apt/sources.list.d/erlang-solutions.list

把里面默认值替换为:

deb https://mirrors.liuboping.com/erlang/ubuntu/ xenial contrib

接着执行:

$ apt-get update
$ apt-get install erlang erlang-nox

2、安装 RabbitMQ:
也需要先在系统中加入 rabbitmq apt 仓库,再加入 rabbitmq signing key。

$ echo 'deb http://www.rabbitmq.com/debian/ testing main' | sudo tee /etc/apt/sources.list.d/rabbitmq.list
$ wget -O- https://www.rabbitmq.com/rabbitmq-release-signing-key.asc | sudo apt-key add -

接着执行:

$ apt-get update
$ apt-get install rabbitmq-server

3、启用 RabbitMQ web 管理插件

$ rabbitmq-plugins enable rabbitmq_management

重启服务器:

$ systemctl restart rabbitmq-server

linux上可访问:打开浏览器输入 http://localhost:15672,默认用户名密码:guest/guest
在这里插入图片描述
上面界面也可以看到版本:命令如下

root@ubuntu:/etc/rabbitmq# sudo rabbitmqctl status | grep rabbit
Status of node rabbit@ubuntu
     [{rabbitmq_management,"RabbitMQ Management Console","3.6.15"},
      {rabbitmq_web_dispatch,"RabbitMQ Web Dispatcher","3.6.15"},
      {rabbitmq_management_agent,"RabbitMQ Management Agent","3.6.15"},
      {rabbit,"RabbitMQ","3.6.15"},
      {rabbit_common,
          "Modules shared by rabbitmq-server and rabbitmq-erlang-client",

windows上远程访问失败:
在这里插入图片描述
4、配置远程访问:
如果没有配置文件,自己新建一个rabbitmq.config
并添加配置信息:

#添加配置信息
#test 是准备新增的管理员账号,专门用于远程登陆
[
{rabbit, [{tcp_listeners, [5672]}, {loopback_users, ["test"]}]}
].
root@ubuntu:/etc/rabbitmq# touch rabbitmq.config
root@ubuntu:/etc/rabbitmq# vim rabbitmq.config 

# 创建用户
root@ubuntu:/etc/rabbitmq# rabbitmqctl add_user test 123456
Creating user "test"
root@ubuntu:/etc/rabbitmq# rabbitmqctl  set_user_tags  test  administrator
Setting tags for user "test" to [administrator]
root@ubuntu:/etc/rabbitmq# rabbitmqctl set_permissions -p "/" test ".*" ".*" ".*"
Setting permissions for user "test" in vhost "/"
root@ubuntu:/etc/rabbitmq# rabbitmqctl list_users
Listing users
guest	[administrator]
test	[administrator]
root@ubuntu:/etc/rabbitmq# 

发现还是不能访问:我们查看防火墙状态。

systemctl status firewalld

发现是开着的,关闭他。
在这里插入图片描述
常用防火墙命令:firewalld或iptables 取决于你的系统

1:查看防火状态

systemctl status firewalld

service  iptables status

2:暂时关闭防火墙

systemctl stop firewalld

service  iptables stop

3:永久关闭防火墙

systemctl disable firewalld

chkconfig iptables off

4:重启防火墙

systemctl enable firewalld

service iptables restart  

我们选择暂时关闭:

root@ubuntu:/etc/rabbitmq# systemctl stop firewalld
root@ubuntu:/etc/rabbitmq# systemctl status firewalld
● firewalld.service - firewalld - dynamic firewall daemon
   Loaded: loaded (/lib/systemd/system/firewalld.service; disabled; vendor preset: enabled)
   Active: inactive (dead)

Jul 30 09:33:59 ubuntu systemd[1]: Starting firewalld - dynamic firewall daemon...
Jul 30 09:34:01 ubuntu systemd[1]: Started firewalld - dynamic firewall daemon.
Aug 01 07:13:23 ubuntu systemd[1]: Stopping firewalld - dynamic firewall daemon...
Aug 01 07:13:26 ubuntu systemd[1]: Stopped firewalld - dynamic firewall daemon.

配置host:

root@ubuntu:/etc/rabbitmq# hostname
ubuntu
root@ubuntu:/etc/rabbitmq# vim /etc/hosts

在这里插入图片描述
重启:

systemctl start rabbitmq-server

windows成功访问:
在这里插入图片描述

安装配置结束,开始基础的几种模式学习。
官网查看getstart
在这里插入图片描述
消息模型:
1、1 “Hello World!” 。The simplest thing that does something 。在这里插入图片描述
2、Work queues。—在工人之间分配任务(竞争的消费者模式)
在这里插入图片描述
3、Publish/Subscribe — 次向许多消费者发送消息
在这里插入图片描述
4、Routing。有选择地接收消息
在这里插入图片描述
5、 Topics。 根据模式(主题)接收消息
在这里插入图片描述
6 RPC — 请求/回复模式示例。这个主要是rpc,使用较少。可以不学习。
在这里插入图片描述
7、 Publisher Confirms — 很少用。官方介绍也很少,先不看。

介绍了上面的模式。我们来搭个项目。学习使用。目前都是springboot。拿就使用它。
创建项目不用多说,下面选择即可。
在这里插入图片描述
逐一来看:模拟场景,B站视频发布,订阅者收到信息。当然使用redis也是可以做到的。这里我们将订阅者和发布者关系存到redis(这仅仅是为了减轻数据库压力),这里主要还是想讲rabbitmq。

如果发现maven下载十分缓慢:建议从国外的库换成阿里云的库。
settings.xml文件,在标签下加入上述内容即可。如下:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
 3           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4           xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
 5     <mirrors>
 6         <!-- 阿里云仓库 -->
 7         <mirror>
 8             <id>alimaven</id>
 9             <mirrorOf>central</mirrorOf>
10             <name>aliyun maven</name>
11             <url>https://maven.aliyun.com/repository/central</url>
12         </mirror>
13     
14         <!-- 中央仓库1 -->
15         <mirror>
16             <id>repo1</id>
17             <mirrorOf>central</mirrorOf>
18             <name>Human Readable Name for this Mirror.</name>
19             <url>http://repo1.maven.org/maven2/</url>
20         </mirror>
21     
22         <!-- 中央仓库2 -->
23         <mirror>
24             <id>repo2</id>
25             <mirrorOf>central</mirrorOf>
26             <name>Human Readable Name for this Mirror.</name>
27             <url>http://repo2.maven.org/maven2/</url>
28         </mirror>
29     </mirrors> 
30 </settings>

看看如何写配置文件:
由这里进入可以看到;
在这里插入图片描述
如何查找配置可以看到;
在这里插入图片描述
配置如下:

# 最基础的配置
spring.rabbitmq.host=192.168.253.136
spring.rabbitmq.port=5672
spring.rabbitmq.username=test
spring.rabbitmq.password=123456
#spring.rabbitmq.virtual-host=/msa

由于默认没配置:上面的配置最后一个注释掉
在这里插入图片描述

在讲具体例子之前,要现有一个概念,那就发布者发布消息到队列,还要取决于订阅者订阅队列才能生效。
1、直连hello world( type = direct的类型)
在这里插入图片描述

发送者:

package com.rabbitmq.demo;

import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@SpringBootTest(classes = DemoApplication.class)
@RunWith(SpringRunner.class)
class DemoApplicationTests {

   // 引入模板组件
   @Autowired
   private RabbitTemplate rabbitTemplate;

  @Test
  public void helloWorldTest() {
      // 定义需要发送的消息体
      String message = "hello world! send message to user";
      rabbitTemplate.convertAndSend("helloQueue",message);
  }
}

看看源码实现;

    public void convertAndSend(String routingKey, Object object) throws AmqpException {
        this.convertAndSend(this.exchange, routingKey, object, (CorrelationData)null);
    }
    // 看下这些参数
    // exchange:交换机
    // routingKey 路由key,也就是队列
    // object: 消息体
    // correlationData 关联数据类
   public void convertAndSend(String exchange, String routingKey, Object object, @Nullable CorrelationData correlationData) throws AmqpException {
        this.send(exchange, routingKey, this.convertMessageIfNecessary(object), correlationData);
    }

    public void send(String exchange, String routingKey, Message message, @Nullable CorrelationData correlationData) throws AmqpException {
        this.execute((channel) -> {
            this.doSend(channel, exchange, routingKey, message, (this.returnCallback != null || correlationData != null && StringUtils.hasText(correlationData.getId())) && (Boolean)this.mandatoryExpression.getValue(this.evaluationContext, message, Boolean.class), correlationData);
            return null;
        }, this.obtainTargetConnectionFactory(this.sendConnectionFactorySelectorExpression, message));
    }


	@Nullable
    private <T> T execute(ChannelCallback<T> action, ConnectionFactory connectionFactory) {
        if (this.retryTemplate != null) {
            try {
                return this.retryTemplate.execute((context) -> {
                    return this.doExecute(action, connectionFactory);
                }, this.recoveryCallback);
            } catch (RuntimeException var4) {
                throw var4;
            } catch (Exception var5) {
                throw RabbitExceptionTranslator.convertRabbitAccessException(var5);
            }
        } else {
            return this.doExecute(action, connectionFactory);
        }
    }

	@Nullable
    private <T> T doExecute(ChannelCallback<T> action, ConnectionFactory connectionFactory) {
        Assert.notNull(action, "Callback object must not be null");
        Channel channel = null;
        boolean invokeScope = false;
        if (this.activeTemplateCallbacks.get() > 0) {
            channel = (Channel)this.dedicatedChannels.get();
        }

        RabbitResourceHolder resourceHolder = null;
        Connection connection = null;
        if (channel == null) {
            if (this.isChannelTransacted()) {
                resourceHolder = ConnectionFactoryUtils.getTransactionalResourceHolder(connectionFactory, true, this.usePublisherConnection);
                channel = resourceHolder.getChannel();
                if (channel == null) {
                    ConnectionFactoryUtils.releaseResources(resourceHolder);
                    throw new IllegalStateException("Resource holder returned a null channel");
                }
            } else {
                connection = ConnectionFactoryUtils.createConnection(connectionFactory, this.usePublisherConnection);
                if (connection == null) {
                    throw new IllegalStateException("Connection factory returned a null connection");
                }

                try {
                    channel = connection.createChannel(false);
                    if (channel == null) {
                        throw new IllegalStateException("Connection returned a null channel");
                    }
                } catch (RuntimeException var12) {
                    RabbitUtils.closeConnection(connection);
                    throw var12;
                }
            }
        } else {
            invokeScope = true;
        }

        Object var7;
        try {
            var7 = this.invokeAction(action, connectionFactory, channel);
        } catch (Exception var13) {
            if (this.isChannelLocallyTransacted(channel)) {
                resourceHolder.rollbackAll();
            }

            throw this.convertRabbitAccessException(var13);
        } finally {
            this.cleanUpAfterAction(channel, invokeScope, resourceHolder, connection);
        }

        return var7;
    }

消费者:

package com.rabbitmq.demo.listener;

import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
// queuesToDeclare声明队列. 如果没有则创建@Queue("helloQueue")
@RabbitListener(queuesToDeclare = @Queue("helloQueue"))
public class HelloWorldListener {

    // 消息获取的两种方式
    // 其一:
    //      类上声明:@RabbitListener(queuesToDeclare = @Queue("helloQueue"))
    //      方法上声明:@RabbitHandler
    @RabbitHandler
    public void receiveMessage(String message) {
        System.out.println("消费消息message:" + message);
    }
}

另一种实现方式:(推荐这种,这样可以把所有队列集中到一个类去处理)

package com.rabbitmq.demo.listener;

import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
// queuesToDeclare声明队列. 如果没有则创建@Queue("helloQueue")

public class HelloWorldListener {

    // 消息获取的两种方式
    // 其二:
    //      方法上声明:@RabbitListener(queuesToDeclare = @Queue("helloQueue"))
    @RabbitListener(queuesToDeclare = @Queue("helloQueue"))
    public void receiveMessage(String message) {
        System.out.println("消费消息message:" + message);
    }
}

实行完结果:可以看到默认持久化,自动删除。
在这里插入图片描述
在这里插入图片描述
我们进入来看看如何通过消费者,设置非持久化,不自动删除。看看参数一览。
在这里插入图片描述
其中Arguments是很复杂的。
在这里插入图片描述

package com.rabbitmq.demo.listener;

import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class HelloWorldListener {

    // 消息获取的两种方式
    // 其二:
    //      方法上声明:@RabbitListener(queuesToDeclare = @Queue("helloQueue"))
     // 参数意义:
    //      1、queuesToDeclare声明队列. 如果没有则创建@Queue("helloQueue")
    //      2、value = "helloQueue" 指定队列名。或者name
    //      3、durable = "false" 设置持久化为false。注意这里是字符串
    //      4、autoDelete = "false" 设置自动删除为false
    @RabbitListener(queuesToDeclare = @Queue(
            durable = "false", autoDelete = "false",name = "helloName"
    ))
    public void receiveMessage(String message) {
        System.out.println("消费消息message:" + message);
    }
}

结果:
在这里插入图片描述

第二种模式:work模式
注意这里是为了举例分开写,实际项目中完全可以整合成一块(主要指消费者)。
在这里插入图片描述
发布者:

package com.rabbitmq.demo;

import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@SpringBootTest(classes = DemoApplication.class)
@RunWith(SpringRunner.class)
class DemoApplicationTests {

   // 引入模板组件
   @Autowired
   private RabbitTemplate rabbitTemplate;

  @Test
  public void workModuleTest() {
      // 定义需要发送的消息体
      String message = "work come at ";
      for (int i = 0; i < 10; i++) {
          rabbitTemplate.convertAndSend("workModule",message + i);
      }
  }

}

消费者:

package com.rabbitmq.demo.listener;

import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class WorkModuleListener {

    // 建立多个消费者,work模型是公平消费,比如下面是1,2,1,2,1,2......
    @RabbitListener(queuesToDeclare = @Queue(name = "workModule"))
    public void receiveMessage1(String message) {
        System.out.println("消费者message1:" + message);
    }

    @RabbitListener(queuesToDeclare = @Queue(name = "workModule"))
    public void receiveMessage2(String message) {
        System.out.println("消费者message2:" + message);
    }
}

结果:下面可以看出work模型是公平消费。也就引来了新的问题,如果一个消息过长的处理,效率将会变低。
在这里插入图片描述
第三种,publish/subscribe广播模式
注意:自己不要指定队列,由它自己消费者自行创建临时队列。
在这里插入图片描述
发布者:

package com.rabbitmq.demo;

import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@SpringBootTest(classes = DemoApplication.class)
@RunWith(SpringRunner.class)
class DemoApplicationTests {

   // 引入模板组件
   @Autowired
   private RabbitTemplate rabbitTemplate;

  @Test // 广播
  public void fanoutModuleTest() {
      // 定义需要发送的消息体
      String message = "广播发布的消息";
      // exchange = "logs" 由消费者自行创建临时队列
      rabbitTemplate.convertAndSend("logs","",message);
  }

}

订阅者:

package com.rabbitmq.demo.listener;

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.stereotype.Component;

@Component
public class FanoutModuleListener {

    // 广播: 使用绑定的方式
    //      1、队列绑定: @QueueBinding(value = @Queue // 临时创建队列
    //      2、在队列绑定中指定交换机绑定:exchange = @Exchange(value = "logs",type = "fanout") 交换机指定类型
    @RabbitListener(bindings = {
            @QueueBinding(value = @Queue, exchange = @Exchange(value = "logs",type = "fanout"))
    })
    public void receiveMessage1(String message) {
        System.out.println("消费者message1:" + message);
    }

    @RabbitListener(bindings = {
            @QueueBinding(value = @Queue, exchange = @Exchange(value = "logs",type = "fanout"))
    })
    public void receiveMessage2(String message) {
        System.out.println("消费者message2:" + message);
    }
}

运行结果:
在这里插入图片描述
在这里插入图片描述
第四种,Routing路由模式:
这是在广播模式上的进一步,可以指定队列发送。有级别说明error、info、warning
在这里插入图片描述
发布者:这里我们定义为info。那么消费者只有key包含info才可以访问

package com.rabbitmq.demo;

import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@SpringBootTest(classes = DemoApplication.class)
@RunWith(SpringRunner.class)
class DemoApplicationTests {

   // 引入模板组件
   @Autowired
   private RabbitTemplate rabbitTemplate;

  @Test // 路由模式
  public void routeModuleTest() {
      // 定义需要发送的消息体
      String message = "路由方式发送的消息";
      // exchange = "logs" 由消费者自行创建临时队列 指定级别info
      rabbitTemplate.convertAndSend("routeLogs","info",message);
  }

}

消费者:

package com.rabbitmq.demo.listener;

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.stereotype.Component;

@Component
public class RouteModuleListener {

    // 广播: 使用绑定的方式
    //      1、队列绑定: @QueueBinding(value = @Queue
    //      2、在队列绑定中指定交换机绑定:exchange = @Exchange(value = "logs",type = "fanout")
    //      3, key = {"info","warning","error"} 指定可访问级别
    @RabbitListener(bindings = {
            @QueueBinding(value = @Queue, // 创建临时队列
                    exchange = @Exchange(value = "routeLogs",type = "direct"),
                    key = {"info","warning","error"}
            )
    })
    public void receiveMessage1(String message) {
        System.out.println("消费者message1:" + message);
    }

    @RabbitListener(bindings = {
            @QueueBinding(value = @Queue, // 创建临时队列
                    exchange = @Exchange(value = "routeLogs",type = "direct"),
                    key = {"warning","error"}
            )
    })
    public void receiveMessage2(String message) {
        System.out.println("消费者message2:" + message);
    }
}

运行结果:是我们的预期结果。
在这里插入图片描述
在这里插入图片描述
第五种,Topics 订阅模式(也叫动态路由模式)
说明:
1、*.orange.**.*.rabbit,lazy.#其中*代表一个单词,#代表0到多个单词
在这里插入图片描述
发布者:

package com.rabbitmq.demo;

import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@SpringBootTest(classes = DemoApplication.class)
@RunWith(SpringRunner.class)
class DemoApplicationTests {

   // 引入模板组件
   @Autowired
   private RabbitTemplate rabbitTemplate;

  @Test // 订阅模式 也叫动态路由模式
  public void topicsModuleTest() {
      // 定义需要发送的消息体
      String message = "user.save动态路由方式发送的消息";
      // exchange = "logs" 由消费者自行创建临时队列 指定级别info
      rabbitTemplate.convertAndSend("topicsLogs","user.save",message);
  }

}

消费者:

package com.rabbitmq.demo.listener;

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.stereotype.Component;

@Component
public class TopicsModuleListener {

    // 广播: 使用绑定的方式
    //      1、队列绑定: @QueueBinding(value = @Queue
    //      2、在队列绑定中指定交换机绑定:exchange = @Exchange(value = "logs",type = "fanout")
    //      3, key = {"user.save","user.*"} 指定动态路由访问
    @RabbitListener(bindings = {
            @QueueBinding(value = @Queue, // 创建临时队列
                    exchange = @Exchange(value = "topicsLogs",type = "topic"),
                    key = {"user.save","user.*"}
            )
    })
    // 用户相关
    public void receiveMessage1(String message) {
        System.out.println("消费者message1:" + message);
    }

    @RabbitListener(bindings = {
            @QueueBinding(value = @Queue, // 创建临时队列
                    exchange = @Exchange(value = "topicsLogs",type = "topic"),
                    key = {"order.#","product.#"}
            )
    })
    // 订单、产品相关
    public void receiveMessage2(String message) {
        System.out.println("消费者message2:" + message);
    }
}

运行结果:只有消费者1收到消息,符合我们的预期。
在这里插入图片描述
在这里插入图片描述
后面的两种模式不在消息考虑范围内。这里不做任何介绍。

MQ的常用应用场景举例:

1、做异步处理

  • 最常见的场景,用户注册成功后,需要发送短信或者邮件通知。短信或者邮件并不是必须的,只是一个通知。那就没有必要让客户端在哪里等待。完全可以异步处理。
  • 就传统的方式而言串行或者并行,效率都不是很高,并行虽然提高了效率,但是也没必要这样处理。那么这里采用消息队列来异步模式处理。
    2、应用解耦
    场景:系统与系统之间的解耦,比如双11,订单系统和库存系统。如果两种直接关联,库存系统出现故障,那么订单系统就会订单失败,他们两个系统之间存在高度耦合。
  • 引入消息队列,在他们之间,起到关联的作用。
  • 订单系统中用户下单后,订单系统完成持久化,并将消息写入消息队列,返回下单成功。
  • 库存系统,订阅下单的消息,获取下单消息,并进行库存操作。就算库存系统故障也能保证消息队列的消息传递的可靠性,不会导致消息丢失。
    3、流量消峰
    场景:京东618秒杀活动,一般流量过大,可能导致应用挂掉。引入消息队列。
    作用:
  • 1、可以控制人数,超过限制的订单直接丢弃
  • 2、可以减缓短时间内高流量压垮服务器。(一般在这一天会停掉很多服务器用来支撑该活动)
    大致过程:用户发起请求----服务器收到,先写入消息队列,加入消息队列长度超过最大值,则抛弃该请求或者直接提示抢购失败—秒杀业务获取成功的请求并做逻辑处理,则抢购成功。

接着官方文档看:集群式肯定需要的,其实研究过其他的集群比如redis,zookeeper。发现集群就是为了保证数据一致性的。大同小异,但是这里还是来看下rabbitma的集群。
在这里插入图片描述
集群官方说明:
在这里插入图片描述
主要介绍通过配置文件来实现(当然DNS、rabbitctl方式配置)“高可用的集群”。
普通的集群(即主从复制)不符合现实场景,但是它是镜像集群的基础。在主从下,交换机是可以共享的,但是队列只存在于一台机器(其他的可见)。
不想多讲,直接讲"高可用的集群"即镜像集群
在这里插入图片描述
是在主从的基础上增加策略。
先来配置主从:由于电脑原因就不起三台虚拟机了,用一台配置不同启动配置文件来模拟。
如下,准备好三个节点。
在这里插入图片描述

复制配置文件:

root@ubuntu:/etc/rabbitmq# cp rabbitmq.config rabbitmq1.config 
root@ubuntu:/etc/rabbitmq# cp rabbitmq.config rabbitmq2.config 
root@ubuntu:/etc/rabbitmq# ls
enabled_plugins  rabbitmq1.config  rabbitmq2.config  rabbitmq.config

修改配置文件端口(不累述,一个举例):(主不用修改,改从)

# test 是准备新增的管理员账号,专门用于远程登陆
[
{rabbit, [{tcp_listeners, [5673]}, {loopback_users, ["test"]}]}
].

配置好了才发现,不能用命令配置文件启动(或者是我没找到,官网找了一通也没找到,电脑又支撑不起三台vm启动,到此结束,后面仅以文字说明大致思路)。


1、主机名和ip的配置
无疑我们要搭三台机器,也就是重复上面得操作修改配置文件,配置主机名(hostname),修改(/etc/hosts),关闭防火墙(systemctl stop firewalld),重启服务(systemctl start rabbitmq-server),到此基础的环境就准备好了。
假定我们配置好了,mq1,mq2,mq3三台VM。

2、节点同步:
查看cookie文件:注意它是隐藏文件,需要加上 -a才能看到

root@ubuntu:/var/lib/rabbitmq# ll -a
total 120
drwxr-xr-x  3 rabbitmq rabbitmq   4096 Aug  1 06:36 ./
drwxr-xr-x 74 root     root       4096 Jul 30 11:21 ../
-r--------  1 rabbitmq rabbitmq     20 Jul 30 00:00 .erlang.cookie
-rw-r-----  1 rabbitmq rabbitmq 106442 Aug  1 07:38 erl_crash.dump
drwxr-x---  4 rabbitmq rabbitmq   4096 Aug  1 07:39 mnesia/

我们要保证三台机器的cookie一致性。

# 将mq1的cookie文件同步给mq2、mq3
 scp /var/lib/rabbitmq/.erlang.cookie root@mq2:/var/lib/rabbitmq/
 scp /var/lib/rabbitmq/.erlang.cookie root@mq3:/var/lib/rabbitmq/

3、集群启动方式,并可页面访问管理界面

# 集群启动方式
rabbitmq-server -detached

查看集群状态:

root@ubuntu:/etc/rabbitmq# rabbitmqctl cluster_status
Cluster status of node rabbit@ubuntu
[{nodes,[{disc,[rabbit@ubuntu]}]},
 {running_nodes,[rabbit@ubuntu]},
 {cluster_name,<<"rabbit@ubuntu">>},
 {partitions,[]},
 {alarms,[{rabbit@ubuntu,[]}]}]

下图画红色的内容为上面配置的mq1、mq2、mq3。则表示主从配置成功多了(是不是很简单,就是保证cookie一致性即可)
在这里插入图片描述
在主从的基础上配置策略,就是镜像的配置:均来自官网
查看当前策略:

root@ubuntu:/home/zhouyi# rabbitmqctl list_policies
Listing policies

添加策略的几个例子:(是一种镜像队列的概念)
1、一种策略,其中名称以“ hello。 ” 开头的队列被镜像到集群中的特定节点:

rabbitmqctl set_policy ha-nodes "^hello\." \
'{"ha-mode":"nodes","ha-params":["rabbit@mq1", "rabbit@mq2","rabbit@mq3"]}'

结果:
在这里插入图片描述

2、下面的示例声明一个策略,该策略与名称以“ ha。 ” 开头的队列匹配,并配置到集群中所有节点的镜像。

#注意几乎不需要镜像到所有节点。
#考虑改为使用“ ha-mode”:“ exactly”镜像到大多数(N / 2 + 1)个节点。
rabbitmqctl set_policy ha-all “ ^ ha \”。 '{“ ha-mode”:“ all”}'

上面的你也可以通过操作界面设置:
在这里插入图片描述
删除策略:

rabbitmqctl clear_policy

参数分析:

rabbitmqctl set_policy [-p <vhost>] [--priority <priority>] [--apply-to <apply-to>] <name> <pattern> <definition>
  • -p vhost : 可选参数,针对指定vhost下的queue进行设置
  • name : policy的名称
  • pattern : queue的正则匹配
  • definition : 镜像定义。包括三部分ha-mode、ha-params、ha-sync-mode
    ha-mode : 指明镜像队列的模式,可选有效值:all(在所有节点进行镜像,一般选择all)、exactly(指定个数节点进行镜像,节点的个数由ha-params指定)、nodes(指定节点进行镜像,由ha-params指定)
    ha-params : ha-mode需要指定的参数
    ha-sync-mode : 进行队列的同步方式。可选有效值:automatic(一般用这个)、manual
    -priority : 可选参数,设置policy的优先级

看完RabbitMQ,我们也去kafka官网逛一逛。
kafka中文官网
保持和上面类似的学习过程。
kafka是基于发布 & 订阅的消息系统,这就意味着它只有一种模式。目前kafka流行用例:
消息
Kafka 很好地替代了传统的message broker(消息代理)。 Message brokers 可用于各种场合(如将数据生成器与数据处理解耦,缓冲未处理的消息等)。 与大多数消息系统相比,Kafka拥有更好的吞吐量、内置分区、具有复制和容错的功能,这使它成为一个非常理想的大型消息处理应用。
根据我们的经验,通常消息传递使用较低的吞吐量,但可能要求较低的端到端延迟,Kafka提供强大的持久性来满足这一要求。
在这方面,Kafka 可以与传统的消息传递系统(ActiveMQ 和 RabbitMQ)相媲美。

跟踪网站活动
Kafka 的初始用例是将用户活动跟踪管道重建为一组实时发布-订阅源。 这意味着网站活动(浏览网页、搜索或其他的用户操作)将被发布到中心topic,其中每个活动类型有一个topic。 这些订阅源提供一系列用例,包括实时处理、实时监视、对加载到Hadoop或离线数据仓库系统的数据进行离线处理和报告等。
每个用户浏览网页时都生成了许多活动信息,因此活动跟踪的数据量通常非常大

度量
Kafka 通常用于监控数据。这涉及到从分布式应用程序中汇总数据,然后生成可操作的集中数据源。

日志聚合
许多人使用Kafka来替代日志聚合解决方案。 日志聚合系统通常从服务器收集物理日志文件,并将其置于一个中心系统(可能是文件服务器或HDFS)进行处理。 Kafka 从这些日志文件中提取信息,并将其抽象为一个更加清晰的消息流。 这样可以实现更低的延迟处理且易于支持多个数据源及分布式数据的消耗。 与Scribe或Flume等以日志为中心的系统相比,Kafka具备同样出色的性能、更强的耐用性(因为复制功能)和更低的端到端延迟。
流处理
许多Kafka用户通过管道来处理数据,有多个阶段: 从Kafka topic中消费原始输入数据,然后聚合,修饰或通过其他方式转化为新的topic, 以供进一步消费或处理。 例如,一个推荐新闻文章的处理管道可以从RSS订阅源抓取文章内容并将其发布到“文章”topic; 然后对这个内容进行标准化或者重复的内容, 并将处理完的文章内容发布到新的topic; 最终它会尝试将这些内容推荐给用户。 这种处理管道基于各个topic创建实时数据流图。从0.10.0.0开始,在Apache Kafka中,Kafka Streams 可以用来执行上述的数据处理,它是一个轻量但功能强大的流处理库。除Kafka Streams外,可供替代的开源流处理工具还包括Apache Storm 和Apache Samza.

采集日志
Event sourcing是一种应用程序设计风格,按时间来记录状态的更改。 Kafka 可以存储非常多的日志数据,为基于 event sourcing 的应用程序提供强有力的支持。

提交日志
Kafka 可以从外部为分布式系统提供日志提交功能。 日志有助于记录节点和行为间的数据,采用重新同步机制可以从失败节点恢复数据。 Kafka的日志压缩 功能支持这一用法。 这一点与Apache BookKeeper 项目类似。

总结:
可知kafka主要还是用于分布式数据收集与处理,比如日志,也是目前比较流行的用法,当然他也可以做消息处理,可以和rabbimq媲美。

详细内容不想多讲,消息他就很类似rabbitmq的topic模式。
就我工作经验来看,我上个项目就是用kafka收集日志以及注册成功后的邮件或短信,都是利用kafka实现的。
主要看看springboot整合kafka源码,甚至他们源码,都很类似。:(详细见搭建docker整合)

 public ListenableFuture<SendResult<K, V>> send(String topic, Integer partition, Long timestamp, K key, @Nullable V data) {
        ProducerRecord<K, V> producerRecord = new ProducerRecord(topic, partition, timestamp, key, data);
        return this.doSend(producerRecord);
    }

protected ListenableFuture<SendResult<K, V>> doSend(ProducerRecord<K, V> producerRecord) {
        Producer<K, V> producer = this.getTheProducer(producerRecord.topic());
        this.logger.trace(() -> {
            return "Sending: " + producerRecord;
        });
        SettableListenableFuture<SendResult<K, V>> future = new SettableListenableFuture();
        Object sample = null;
        if (this.micrometerEnabled && this.micrometerHolder == null) {
            this.micrometerHolder = this.obtainMicrometerHolder();
        }

        if (this.micrometerHolder != null) {
            sample = this.micrometerHolder.start();
        }

        Future<RecordMetadata> sendFuture = producer.send(producerRecord, this.buildCallback(producerRecord, producer, future, sample));
        if (sendFuture.isDone()) {
            try {
                sendFuture.get();
            } catch (InterruptedException var7) {
                Thread.currentThread().interrupt();
                throw new KafkaException("Interrupted", var7);
            } catch (ExecutionException var8) {
                throw new KafkaException("Send failed", var8.getCause());
            }
        }

        if (this.autoFlush) {
            this.flush();
        }

        this.logger.trace(() -> {
            return "Sent: " + producerRecord;
        });
        return future;
    }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值