Rabbitmq初级

官网: Messaging that just works — RabbitMQ

什么是MQ

MQ(Message Queue)消息队列,是基础数据结构中“先进先出”的一种数据结构。一般用来解决应用解耦,异步消息,流量削峰等问题,实现高性能,高可用,可伸缩和最终一致性架构。

几种常见的消息中间件:

RabbitMQ

是使用Erlang编写的一个开源的消息队列,本身支持很多的协议:AMQP,XMPP, SMTP, STOMP,也正是如此,使的它变的非常重量级,更适合于企业级的开发。同时实现了一个经纪人(Broker)构架,这意味着消息在发送给客户端时先在中心队列排队。对路由(Routing),负载均衡(Load balance)或者数据持久化都有很好的支持。

Redis

是一个Key-Value的NoSQL数据库,开发维护很活跃,虽然它是一个Key-Value数据库存储系统,但它本身支持MQ功能,所以完全可以当做一个轻量级的队列服务来使用。对于RabbitMQ和Redis的入队和出队操作,各执行100万次,每10万次记录一次执行时间。测试数据分为128Bytes、512Bytes、1K和10K四个不同大小的数据。实验表明:入队时,当数据比较小时Redis的性能要高于RabbitMQ,而如果数据大小超过了10K,Redis则慢的无法忍受;出队时,无论数据大小,Redis都表现出非常好的性能,而RabbitMQ的出队性能则远低于Redis。

ZeroMQ

号称最快的消息队列系统,尤其针对大吞吐量的需求场景。ZMQ能够实现RabbitMQ不擅长的高级/复杂的队列,但是开发人员需要自己组合多种技术框架,技术上的复杂度是对这MQ能够应用成功的挑战。ZeroMQ具有一个独特的非中间件的模式,你不需要安装和运行一个消息服务器或中间件,因为你的应用程序将扮演了这个服务角色。你只需要简单的引用ZeroMQ程序库,可以使用NuGet安装,然后你就可以愉快的在应用程序之间发送消息了。***但是ZeroMQ仅提供非持久性的队列,也就是说如果down机,数据将会丢失。***其中,Twitter的Storm中使用ZeroMQ作为数据流的传输。

ActiveMQ

是Apache下的一个子项目。 类似于ZeroMQ,它能够以代理人和点对点的技术实现队列。同时类似于RabbitMQ,它少量代码就可以高效地实现高级应用场景。RabbitMQ、ZeroMQ、ActiveMQ均支持常用的多种语言客户端 C++、Java、.Net,、Python、 Php、 Ruby等。

Jafka/Kafka

Kafka是Apache下的一个子项目,是一个高性能跨语言分布式Publish/Subscribe消息队列系统,而Jafka是在Kafka之上孵化而来的,即Kafka的一个升级版。具有以下特性:快速持久化,可以在O(1)的系统开销下进行消息持久化;高吞吐,在一台普通的服务器上既可以达到10W/s的吞吐速率;完全的分布式系统,Broker、Producer、Consumer都原生自动支持分布式,自动实现复杂均衡;支持Hadoop数据并行加载,对于像Hadoop的一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。Kafka通过Hadoop的并行加载机制来统一了在线和离线的消息处理,这一点也是本课题所研究系统所看重的。Apache Kafka相对于ActiveMQ是一个非常轻量级的消息系统,除了性能非常好之外,还是一个工作良好的分布式系统。

为什么选择RabbitMQ

1、除了Qpid,RabbitMQ是唯一一个实现了AMQP标准的消息服务器;

2、可靠性,RabbitMQ的持久化支持,保证了消息的稳定性;

3、高并发,RabbitMQ使用了Erlang开发语言,Erlang是为电话交换机开发的语言,天生自带高并发光环,和高可用特性;

4、集群部署简单,正是应为Erlang使得RabbitMQ集群部署变的超级简单;

5、社区活跃度高,根据网上资料来看,RabbitMQ也是首选;

RabbitMq入门

RabbitMQ网站端口号:15672

工作机制

生产者、消费者和代理

在了解消息通讯之前首先要了解3个概念:生产者、消费者和代理。

生产者:消息的创建者,负责创建和推送数据到消息服务器;

消费者:消息的接收方,用于处理数据和确认消息;

代理:就是RabbitMQ本身,用于扮演“快递”的角色,本身不生产消息,只是扮演“快递”的角色。

在mabbitmq中有7中模型,分别介绍了rabbitmq的7种工作方式,在这里只介绍前5中。

首先应该先导包。

<dependency>
     <groupId>com.rabbitmq</groupId>
     <artifactId>amqp-client</artifactId>
     <version>5.12.0</version>
</dependency>

1.“Hello World!”

在这里插入图片描述

在这个模型中只有三个对象,生产者,队列,消费者。这是一个最简单的模型。

provider.java

public class provider {
    public static void main(String[] args) throws IOException, TimeoutException {
        //获取连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setVirtualHost("/joviehost");
        factory.setUsername("jovie");
        factory.setPassword("123456");
        factory.setHost("localhost");
		// 获取连接
        Connection connection = factory.newConnection();
		// 获取通道
        Channel channel = connection.createChannel();
		// 定义队列
        channel.queueDeclare("hello",false,false,false,null);
		// 发布消息到队列中
        channel.basicPublish("","hello",null, "this is a message".getBytes());
		// 关闭通道
        channel.close();
		// 关闭连接
        connection.close();
    }
}

consumer.java

public class consumer {
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setVirtualHost("/joviehost");
        factory.setUsername("jovie");
        factory.setPassword("123456");
        factory.setHost("localhost");

        Connection connection = factory.newConnection();

        Channel channel = connection.createChannel();

        channel.queueDeclare("hello",false,false,false,null);
		// 消费消息
        channel.basicConsume("hello",true,new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String s = new String(body, "UTF-8");
                System.out.println(s);
            }
        });
    }
}

这样就可以实现第一个模型了。

接下来看一下代码:

channel.queueDeclare("hello",false,false,false,null);
//第一个参数queue 为队列名称
//第二个参数durable 表示队列是否支持持久化
//第三个参数exclusive 表示只对首次声明的连接可见,并且在连接断开后自动删除
//第四个参数autodelete 是否自动删除,为true时,当所有连接都断开时将自动删除队列
//第五个参数arguments 附加参数

注意:

  • 在rabbitmq中,没有声明durable的交换机和队列在rabbitmq重启时都将被删除。

  • 在provider和consumer中声明队列时参数需要一致,否则consumer将无法获取消息。

channel.basicPublish("","hello",null, "this is a message".getBytes());
//第一个参数exchange 交换机名称
//第二个参数routingkey 绑定queue与exchange,在这里没有定义exchange直接填写queue的名称
//第三个参数props 附加消息
//第四个参数message 要发送的消息

注意:

需要时消息在rabbitmq宕机后保存消息(消息持久化)必须增加一些设置,而在定义队列时的持久化设置是使队列不会被删除,而队列中的消息依旧会被删除。使消息持久化应当设置props

channel.basicPublish("","hello", MessageProperties.PERSISTENT_TEXT_PLAIN, "this is a message".getBytes());
channel.basicConsume("hello",true,new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String s = new String(body, "UTF-8");
                System.out.println(s);
            }
        });
//第一个参数 消费队列的名称
//第二个参数 autoack:1、当 autoAck设置为true时,也就是自动确认模式,一旦消息队列将消息发送给消息消费者后,就会从内存中将这个消息删除。
//				   2、当autoAck设置为false时,也就是手动模式,如果此时的有一个消费者宕机,消息队列就会将这条消息继续发送给其他的消费者,这样数据在消息消费者集群的环境下,也就算是不丢失了。
//第三个参数 自定义consumer

2. Work queues

在这里插入图片描述

在这个模型中有两个消费者。

当我们将消费者增加到两个时,我们发现,平均分配,不管消费者有没有用完消息。

在这里插入图片描述
在这里插入图片描述

这样子不太好,当rabbitmq已经平均分配好了消息给消费者后,若消费者宕机了,那么这些消息将丢失。

所以我们取消自动确认机制,改为手动确认。

在这里插入图片描述

将第一个消费者延时设置为0.5s,第二个设置为0.3s

在这里插入图片描述

在这里插入图片描述

这样的模式就是能者多劳。

channel.basicAck(envelope.getDeliveryTag(), false);
//第一个参数指定 deliverytag,第二个参数说明如何处理这个失败消息。requeue 值为 true 表示该消息重新放回队列头,值为 false 表示放弃这条消息。 

在进入第三个案例之前,先提取一下冗余代码,使之成为工具类的一部分。

public class QueueUtil {
    private static ConnectionFactory factory = new ConnectionFactory();

    public static Connection GetConnection(String host,String VirtualHost,String username,String password) throws IOException, TimeoutException {
        factory.setHost(host);
        factory.setPassword(password);
        factory.setUsername(username);
        factory.setVirtualHost(VirtualHost);
        Connection connection = factory.newConnection();
        return connection;
    }

    public static void CloseConnection(Connection connection, Channel channel) throws IOException, TimeoutException {
        if (channel != null){
            channel.close();
        }
        if (connection != null){
            connection.close();
        }
    }
}

3.交换机类型:

Direct 类型

Direct 类型的交换器由路由规则很简单,它会把消息路由到那些 BindingKey 和 RoutingKey 完全匹配的队列中。

Direct Exchange 是 RabbitMQ 默认的交换器模式,也是最简单的模式。它根据 RoutingKey 完全匹配去寻找队列。

Topic 类型

上面讲到 Direct 类型的交换器由规则是完全匹配 BindingKey 和 RoutingKey,但是这种严格的匹配方式在很多情况下不能满足实际业务的需求。Topic 类型的交换器在匹配规则上进行了扩展,它与 Direct 类型的交换器相似,也是将消息路由到 BindingKey 和 RoutingKey 相匹配的队列中,但这里的匹配规则有些不同,它约定:

(1)RoutingKey 为一个点号 “.” 分隔的字符串(被点号 “.” 分隔开的每一段独立的字符串称为一个单词),如:com.rabbitmq.client、java.util.concurrent、com.hidden.client;

(2)BindingKey 和 RoutingKey 一样也是点号 “.” 分隔的字符串;

(3)BindingKey 中可以存在两种特殊字符串星号 “” 和井号 “#”,用于做模糊匹配,其中星号 “” 用于匹配一个单词,井号 “#”用于匹配多个规则单词(0个或者多个单词);

Fanout 类型

消息广播的模式,即将消息广播到所有绑定到它的队列中,而不考虑 RoutingKey 的值(不管路键或是路由模式)。如果设置了 RoutingKey ,则 RoutingKey 依然被忽略。

Headers 类型

Headers 类型的交换器不依赖于路由键的匹配规则来路由消息,而是根据发送的消息内容中的 headers 属性进行匹配。在绑定队列和交换器时制定一组键值对,当发送消息到交换器时,RabbitMQ 会获取到该消息的 headers(也是一个键值对的形式),对比其中的键值对是否完全匹配队列和交换器绑定时指定的键值对,如果完全匹配则消息会路由到该队列,否则不会路由到该队列。Headers 类型的交换器性能会很差,而且不实用,基本上不会看到它的存在。

4. Publish/Subscribe

在这里插入图片描述

在这个模式中,多了一个交换机,消息提供者直接将消息发送给交换机,在按照分发类型将消息发放给队列。

consumer.java

public class consumer1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = QueueUtil.GetConnection("localhost","/joviehost","jovie","123456");

        Channel channel = connection.createChannel();

        channel.exchangeDeclare("subscribe","fanout",true,false,null);

        channel.queueDeclare("test2",false,false,false,null);

        channel.queueBind("test2","subscribe","",null);

        channel.basicQos(1);
        channel.basicConsume("test2",false,new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String s = new String(body, "UTF-8");
                System.out.println(s);
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        });
    }
}

provider.java

public class provider {
    public static void main(String[] args) throws IOException, TimeoutException {

        Connection connection = QueueUtil.GetConnection("localhost","/joviehost","jovie","123456");

        Channel channel = connection.createChannel();

        channel.exchangeDeclare("subscribe","fanout",true,false,null);

        for (int i = 0; i < 10; i++) {
            channel.basicPublish("subscribe","", null, ("this is a message " + i).getBytes());
        }

        QueueUtil.CloseConnection(connection,channel);
    }
}

接下来分析代码:

channel.exchangeDeclare("subscribe","fanout",true,false,null);
//第一个参数 定义交换机名称
//第二个参数 设置交换机类型
//第三个参数 durable是否持久化,和队列一样,设置为false时重启将被删除
//第四个参数 autodelete 是否自动删除,为true时将在没有队列与它绑定时自动删除
//第五个参数 arguments 额外参数
channel.basicPublish("subscribe","", null, ("this is a message " + i).getBytes());
//第一个填交换机名称,第二个参数routingkey,由于时fanout模式,所以这里不用设置。
channel.exchangeDeclare("subscribe","fanout",true,false,null);
//定义交换机
channel.queueDeclare("test2",false,false,false,null);
//定义队列
channel.queueBind("test2","subscribe","",null);
//将队列与交换机绑定,第三个参数为routingkey,fanout模式下不用设置

在 fanout 模式下交换机将会将消息分发给所有与它绑定的队列,绑定的队列都会得到消息

在这里插入图片描述
在这里插入图片描述

5. Routing

在这里插入图片描述

在这个模型中,交换机类型为direct,它根据routingkey与bindingkey匹配将消息分发到队列中。

provider.java

public class provider {
    private static final String ExchangeName = "directTest";
    public static void main(String[] args) throws IOException, TimeoutException {

        Connection connection = QueueUtil.GetConnection("localhost","/joviehost","jovie","123456");

        Channel channel = connection.createChannel();

        channel.exchangeDeclare(ExchangeName,"direct",true,false,null);

        channel.basicPublish(ExchangeName, "info", null, ("this is an info message").getBytes());
        channel.basicPublish(ExchangeName, "error", null, ("this is an error message").getBytes());
        channel.basicPublish(ExchangeName, "warning", null, ("this is a warning message").getBytes());

        QueueUtil.CloseConnection(connection,channel);
    }
}

consumer1.java

public class consumer1 {
    private static final String QueueName = "test2";
    private static final String ExchangeName = "directTest";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = QueueUtil.GetConnection("localhost","/joviehost","jovie","123456");

        Channel channel = connection.createChannel();

        channel.exchangeDeclare(ExchangeName,"direct",true,false,null);

        channel.queueDeclare(QueueName,false,false,false,null);

        channel.queueBind(QueueName,ExchangeName,"info",null);

        channel.basicQos(1);
        channel.basicConsume(QueueName,false,new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String s = new String(body, "UTF-8");
                System.out.println("this is consumer1, and the message is :" + s);
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        });
    }
}

consumer2.java

public class consumer2 {
    private static final String QueueName = "test1";
    private static final String ExchangeName = "directTest";
    public static void main(String[] args) throws IOException, TimeoutException {


        Connection connection = QueueUtil.GetConnection("localhost","/joviehost","jovie","123456");

        Channel channel = connection.createChannel();

        channel.exchangeDeclare(ExchangeName,"direct",true,false,null);

        channel.queueDeclare(QueueName,false,false,false,null);

        channel.queueBind(QueueName,ExchangeName,"error",null);
        channel.queueBind(QueueName,ExchangeName,"warning",null);
        channel.queueBind(QueueName,ExchangeName,"info",null);

        channel.basicQos(1);
        channel.basicConsume(QueueName,false,new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String s = new String(body, "UTF-8");
                System.out.println("this is consumer2, and the message is :" + s);
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        });
    }
}

在direct模式中,将routingkey与bindingkey匹配的消息发送给对应队列在这里插入图片描述

在这里插入图片描述

6. Topics

在这里插入图片描述

在这个模型中使用topic类型的交换机,即模糊匹配

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

一个消息提供者,三个消费者,设置好后运行。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

一个 # 可以匹配所有的key,而一个 * 匹配了一个单词。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值