• RabbitMQ基础学习


    RabbitMq学习笔记

    RabbitMQ简介

    RabbitMQ 是一个由 Erlang 语言开发的 AMQP协议 的开源实现,用来通过普通协议在完全不同的应用之间完成数据共享。

    Erlang语言最初在于交换机领域的架构模式,这样使得 RabbitMQ在Broker之间进行数据交互的性能是非常优秀的

    Erlang的优点:Erlang有着和原生Socket—样的延迟

    AMQP : Advanced Message Queue,高级消息队列协议。它是应用层协议的一个开放标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不受产品、开发语言等条件的限制。

    RabbitMQ 最初起源于金融系统,用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。具体特点包括:

    1. 可靠性(Reliability) : RabbitMQ 使用一些机制来保证可靠性,如持久化、传输确认、发布
      确认。
    2. 灵活的路由(Flexible Routing) : 在消息进入队列之前,通过 Exchange 来路由消息的。对
      于典型的路由功能, RabbitMQ 已经提供了一些内置的 Exchange 来实现。针对更复杂的路
      由功能,可以将多个 Exchange 绑定在一起,也通过插件机制实现自己的 Exchange 。
    3. 消息集群(Clustering) : 多个 RabbitMQ 服务器可以组成一个集群,形成一个逻辑 Broker ,并RabbitMQ的集群模式比较丰富,提供了表达式的配置,非常简单,常见的集群模式有HA模式、镜像队列模型、主从模式、远程模式等等,最常用的就是镜像队列模式。
    4. 高可用(Highly Available Queues) : 队列可以在集群中的机器上进行镜像, 使得在部分节点出问题的情况下队列仍然可用。
    5. 多种协议(Multi-protocol) : RabbitMQ 支持多种消息队列协议,比如 STOMP、 MQTT等等。
    6. 多语言客户端(Many Clients) : RabbitMQ 几乎支持所有常用语言,比如 Java、 .NET、Ruby 等等,比如在JAVA中与SpringAMQP完美整合,API相当丰富。
    7. 管理界面(Management UI) :RabbitMQ 提供了一个易用的用户界面,使得用户可以监控和管理消息 Broker 的许多方面。
    8. 跟踪机制(Tracing) :如果消息异常, RabbitMQ 提供了消息跟踪机制,使用者可以找出发生了什么。
    9. 插件机制(Plugin System) :RabbitMQ 提供了许多插件,来从多方面进行扩展,也可以编
      写自己的插件。

    为什么要使用RabbitMQ

    近两年谈的很多的一个概念微服务,在一个大型业务系统架构中,会被拆分成很多小的业务系统,这些业务系统之间如何建立通信呢?大家熟知的 HTTP、RPC 可以实现不同系统、不同语言之间的通信,除了这些往往还会使用消息队列(RabbitMQ、ActiveMQ、Kafafa 等)将这些系统链接起来,达到各系统间的解耦

    RabbitMQ 应用场景

    同步转异步

    在项目中对于一些没必要同步处理的,可以借助 MQ 进行异步处理,比如与其他系统之间同步数据,可以使用MQ发布-订阅的方式完成异步的消息同步

    应用解耦

    例如商城业务场景中,订单系统与库存系统,下单的同步可能也要去减少库存,将原本耦合在一块的逻辑可以通过消息队列进行,订单系统发布消息,库存系统订阅消息,这样的好处是一般库存系统出现问题也不会影响到订单系统。

    流量削峰

    流量削峰在一些营销活动、秒杀活动场景中应用还是比较广泛的,如果短时间流量过大,可以通过设置阀值丢弃掉一部分消息或者根据服务的承受能力设置处理消息限制,也就是限流。

    MQ 的空间与时间解耦

    从空间上来看,消息的生产者无需提前知道消费者的存在,反之消费者亦是,两者之间得到了解耦,不会强依赖,从而实现空间上的解耦

    从时间上来看,消息的生产者只负责生产数据将数据放入队列,之后无需关心消费者什么时间去消费,消费则可以根据自己的业务需要来选择实时消费还是延迟消费,两者都拥有了自己的生命周期,从而实现了时间上的解耦

    主流消息中间件一览

    • ActiveMQ:Apache 出品,早起很流行主要应用于中小企业,面对大量并发场景会有阻塞、消息堆积问题。
    • Kafka:是由 Apache 软件基金会开发的一个开源流处理平台,由 Scala 和 Java 编写,是一种高吞吐量的分布式发布订阅消息系统,支持单机每秒百万并发。最开始目的主要用于大数据方向日志收集、传输。0.8 版本开始支持复制,不支持事物,因此对消息的重复、丢失、错误没有严格的要求。
    • RocketMQ:阿里开源的消息中间件,是一款低延迟、高可靠、可伸缩、易于使用的消息中间件,思路起源于 Kafka。最大的问题商业版收费,有些功能不开放。
    • RabbitMQ:是一个由 erlang(有着和原生 Socket 一样低的延迟)语言开发基于 AMQP 协议的开源消息队列系统。能保证消息的可靠性、稳定性、安全性。

    RabbitMQ架构

    大概意思就是Rabbit MQ拿到消息之后,会先交给 交换机 (Exchange), 然后交换机再根据预先设定的不同绑定( Bindings )策略,来确定要发给哪个队列。

    RabbitMQ的安装与使用

    RPM方式安装RabbitMQ

    安装与学习参考地址:[RabbitMQ官网]:(https://www.rabbitmq.com/)

    安装步骤:

    1. 准备服务器环境,我这里用的虚拟机,常见的Centos7版本
    2. 下载RabbitMQ必须的安装包
    3. 进行安装,修改相关的配置文件

    准备服务器环境

    安装好新的linux之后,系统初始化

    #!/bin/bash
    #判断是否为root用户
    if [ `whoami` != "root" ];then
    echo " only root can run it"
    exit 1
    fi
    #执行前提示
    echo -e "33[31m 这是centos7系统初始化脚本,将更新系统内核至最新版本,请慎重运行!33[0m" 
    read -s -n1 -p "Press any key to continue or ctrl+C to cancel"
    echo "Your inputs: $REPLY"
    #1.定义配置yum源的函数
    yum_config(){
    yum -y install wget
    mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup
    wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
    yum clean all && yum makecache
    }
    #2.定义配置NTP的函数
    ntp_config(){
    yum -y install chrony
    systemctl start chronyd && systemctl enable chronyd
    timedatectl set-timezone Asia/Shanghai && timedatectl set-ntp yes
    }
    #3.定义关闭防火墙的函数
    close_firewalld(){
    systemctl stop firewalld.service &> /dev/null
    systemctl disable firewalld.service &> /dev/null
    }
    #4.定义关闭selinux的函数
    close_selinux(){
    setenforce 0
    sed -i 's/enforcing/disabled/g' /etc/selinux/config
    }
    #5.定义安装常用工具的函数
    yum_tools(){
    yum -y install  vim wget curl curl-devel bash-completion lsof iotop iostat unzip bzip2 bzip2-devel
    yum -y install  gcc gcc-c++ make cmake autoconf openssl-devel openssl-perl net-tools
    }
    #6.定义升级最新内核的函数
    update_kernel (){
    rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
    rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm
    yum --enablerepo=elrepo-kernel install -y kernel-ml
    grub2-set-default 0
    grub2-mkconfig -o /boot/grub2/grub.cfg
    }
    #执行脚本
    main(){
        yum_config;
        ntp_config;
        close_firewalld;
        close_selinux;
        yum_tools;
        update_kernel;
    }
    main
    

    安装rabbitmq所需的依赖

    yum install build-essential openssl openssl-devel unixODBC unixODBC-devel make gcc gcc-c++ kernel-devel m4 ncurses-devel tk tc xz lsof
    

    获取安装包

    rabbitmq和erlang安装包一定要对应,具体可以查看对应关系,官网有说明RabbitMQ Erlang Version Requirements

    • 获取erlang安装包
    sudo wget http://www.rabbitmq.com/releases/erlang/erlang-18.3-1.el6.x86_64.rpm
    
    • 获取socat安装包

    socat支持多协议,用于协议处理、端口转发,rabbitmq依赖于此。

    sudo wget http://repo.iotti.biz/CentOS/7/x86_64/socat-1.7.3.2-5.el7.lux.x86_64.rpm
    
    • 获取rabbitmq-server安装包 rabbitmq-server 安装包列表
    sudo wget http://www.rabbitmq.com/releases/rabbitmq-server/v3.6.5/rabbitmq-server-3.6.5-1.noarch.rpm
    

    开始安装

    • Centos rpm 一键安装

    这里采用rpm一键安装,centos 执行命令

    rpm -ivh erlang-18.3-1.el6.x86_64.rpm
    rpm -ivh socat-1.7.3.2-5.el7.lux.x86_64.rpm
    rpm -ivh rabbitmq-server-3.6.5-1.noarch.rpm
    

    修改配置文件

    vim /usr/lib/rabbitmq/lib/rabbitmq_server-3.6.5/ebin/rabbit.app
    
    {loopback_users, [<<"guest">>]}, // 修改为 {loopback_users, [guest]},
    

    运行与启动

    • 开启 rabbitmq
    rabbitmqctl start_app
    

    在这里我启动的时候发生了一个错误:

    Error: unable to connect to node rabbit@localhost: nodedown
    

    解决步骤:

    #杀死 MQ的进程
    ps -ef | grep rabbitmq | grep -v grep | awk '{print $2}' | xargs kill -9
    #重启MQ服务
    rabbitmq-server -detached
    #然后再启动rabbitMq应用
    rabbitmqctl start_app
    #查看rabbitmq状态
    rabbitmqctl status
    

    已经启动成功!

    • 开启管理插件
    rabbitmq-plugins enable rabbitmq_management
    
    • 检查状态
    $ lsof  -i:5672
    

    开启管理通知台 终端更多操作命令,以下有说明,浏览区输入 http://host:15672 打开管理控制台

    • 几个端口区别说明

      • 5672:通信默认端口号
      • 15672:管理控制台默认端口号
      • 25672:集群通信端口号

    输入guest / guest进入管理后台

    在这个界面里面我们可以做些什么?
    可以手动创建虚拟host,创建用户,分配权限,创建交换机,创建队列等等,还有查看队列消息,消费效率,推送效率等等

    管理界面常见操作

    配置Virtual hosts

    登入管理界面,Admin窗口,就可以创建Virtual host,

    可以看到,如果没有创建虚拟机的话原本默认时会有一个名为 / 的虚拟机,绑定的是guest用户,刚创建的Virtual host都是没有绑定用户的,接下来创建一个测试用户进行绑定

    创建用户

    可以看到在创建用户的时候,最后那里有一步设置tags,就是设置用户的角色,不同的角色有不同的权限。

    RabbitMQ的用户角色分类:

    none、management、policymaker、monitoring、administrator

    • none :啥管理权限都没有
    • management: 用户可以访问管理插件
    • policymaker : 用户可以访问管理插件,并管理他们有权访问的vhost的策略和参数
    • monitoring : 用户可以访问管理插件,查看所有连接和通道以及与节点相关的信息。
    • administrator : 用户可以做任何监视可以做的事情,管理用户,vhost和权限,关闭其他用户的连接,并管理所有vhost的政策和参数。

    创建完用户之后,可以看到用户也没没有绑定虚拟机,可以点进用户的属性界面,选择刚才创建的虚拟机,进行虚拟机的绑定以及权限设置

    大概的操作就先到这里,后面写实例代码的时候有创建quene和exchange的步骤。

    操作命令

    以下列举一些在终端常用的操作命令

    • whereis rabbitmq:查看 rabbitmq 安装位置
    • rabbitmqctl start_app:启动应用
    • whereis erlang:查看erlang安装位置
    • rabbitmqctl start_app:启动应用
    • rabbitmqctl stop_app:关闭应用
    • rabbitmqctl status:节点状态
    • rabbitmqctl add_user username password:添加用户
    • rabbitmqctl list_users:列出所有用户
    • rabbitmqctl delete_user username:删除用户
    • rabbitmqctl add_vhost vhostpath:创建虚拟主机
    • rabbitmqctl list_vhosts:列出所有虚拟主机
    • rabbitmqctl list_queues:查看所有队列
    • rabbitmqctl -p vhostpath purge_queue blue:清除队列里消息

    Docker安装RabbitMQ

    使用docker安装rabbitmq不用那么复杂,直接修改下上面的虚拟机初始化脚本,加两个方法,分别是安装docker和安装rabbitmq的方法:

    #!/bin/bash
    #判断是否为root用户
    if [ `whoami` != "root" ];then
    echo " only root can run it"
    exit 1
    fi
    #执行前提示
    echo -e "33[31m 这是centos7系统初始化脚本,将更新系统内核至最新版本,请慎重运行!33[0m" 
    read -s -n1 -p "Press any key to continue or ctrl+C to cancel"
    echo "Your inputs: $REPLY"
    #1.定义配置yum源的函数
    yum_config(){
    yum -y install wget
    mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup
    wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
    yum clean all && yum makecache
    }
    #2.定义配置NTP的函数
    ntp_config(){
    yum -y install chrony
    systemctl start chronyd && systemctl enable chronyd
    timedatectl set-timezone Asia/Shanghai && timedatectl set-ntp yes
    }
    #3.定义关闭防火墙的函数
    close_firewalld(){
    systemctl stop firewalld.service &> /dev/null
    systemctl disable firewalld.service &> /dev/null
    }
    #4.定义关闭selinux的函数
    close_selinux(){
    setenforce 0
    sed -i 's/enforcing/disabled/g' /etc/selinux/config
    }
    #5.定义安装常用工具的函数
    yum_tools(){
    yum -y install  vim wget curl curl-devel bash-completion lsof iotop iostat unzip bzip2 bzip2-devel
    yum -y install  gcc gcc-c++ make cmake autoconf openssl-devel openssl-perl net-tools
    }
    #6.定义升级最新内核的函数
    update_kernel (){
    rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
    rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm
    yum --enablerepo=elrepo-kernel install -y kernel-ml
    grub2-set-default 0
    grub2-mkconfig -o /boot/grub2/grub.cfg
    }
    
    #7. 安装docker
    install_docker(){
    yum -y install  yum-utils device-mapper-persistent-data
    yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
    yum -y install docker-ce
    
    if [ ! -f "/etc/docker/daemon.json" ]; then
      echo 'docker配置文件不存在,创建并设置加速镜像'
      touch /etc/docker/daemon.json
      echo "{"registry-mirrors":[ "https://reg-mirror.qiniu.com","http://hub-mirror.c.163.com" ]}" > /etc/docker/daemon.json
      echo '镜像加速设置完毕'
    fi
    
    systemctl start docker
    systemctl enable docker
    echo 'docker安装完毕!'
    }
    #8. docker安装rabbitmq
    docker_install_rabbit(){
    #指定版本,该版本包含了web控制页面
    docker pull rabbitmq:management
    #方式一:默认guest 用户,密码也是 guest
    docker run -d --hostname my-rabbit --name rabbit -p 15672:15672 -p 5672:5672 rabbitmq:management
    #方式二:设置用户名和密码
    #docker run -d --hostname my-rabbit --name rabbit -e RABBITMQ_DEFAULT_USER=user -e RABBITMQ_DEFAULT_PASS=password -p 15672:15672 -p 5672:5672 rabbitmq:management
    }
    #执行脚本
    main(){
        yum_config;
        ntp_config;
        close_firewalld;
        close_selinux;
        yum_tools;
        update_kernel;
    	install_docker;
    	docker_install_rabbit;
    }
    main
    
    {
      "registry-mirrors": [
        "https://registry.docker-cn.com",
        "http://hub-mirror.c.163.com",
        "https://docker.mirrors.ustc.edu.cn"
      ]
    }
    

    如果是已有的环境,执行以下几个命令即可:

    #指定版本,该版本包含了web控制页面
    docker pull rabbitmq:management
    #方式一:默认guest 用户,密码也是 guest
    docker run -d --hostname my-rabbit --name rabbit -p 15672:15672 -p 5672:5672 rabbitmq:management
    #方式二:设置用户名和密码
    docker run -d --hostname my-rabbit --name rabbit -e RABBITMQ_DEFAULT_USER=user -e RABBITMQ_DEFAULT_PASS=password -p 15672:15672 -p 5672:5672 rabbitmq:management
    

    RabbitMQ的核心概念

    Broker

    其实就是server,接受客户端的连接,表示消息队列服务器实体

    Connection

    和具体的Broker的网络连接。

    Message

    消息,消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括 routing-key(路由键)、 priority(相对于其他消息的优先权)、 delivery-mode(指出该消息可能需要持久性存储)等。

    Channel

    信道, 多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的 TCP 连接内地虚拟连接, AMQP 命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁 TCP 都是非常昂贵的开销,所以引入了信道的概念,以复用一条 TCP 连接。客户端可以建立多个channel,每个channel表示一个会话任务。

    Publisher

    消息的生产者,也是一个向交换器发布消息的客户端应用程序。

    Exchange(将消息路由给队列 )

    交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。

    Binding(消息队列和交换器之间的关联)

    绑定,用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,虚拟机根据他来确定如何路由 一条消息,所以可以将交换器理解成一个由绑定构成的路由表。

    Queue

    消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。 一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走 。

    Consumer

    消息的消费者,表示一个从消息队列中取得消息的客户端应用程序。

    Virtual Host

    虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。

    交换机的模式

    交换机可以理解成具有路由表的路由程序,仅此而已。每个消息都有一个称为路由键(routing key)的属性,就是一个简单的字符串。RabbitMQ提供了四种Exchange模式:fanout,direct,topic,header 。 header模式在实际使用中较少,这里只说一下前三种模式.

    fanout

    fanout 模式就是广播模式,这种模式就是所有的消息来了会转发到所有绑定此交换机的队列上,这种模式转发消息是最快的。

    Direct

    Direct这个单词本意就是直接的意思,顾名思义,Direct 模式就是指定队列模式, 消息来了,只发给指定的 Queue, 其他Queue 都收不到,这里是将一个队列绑定到交换机上,要求该消息与一个特定的路由键完全匹配,一对一的匹配才会转发

    Topic

    topic模式,又称主题模式,将路由键和某模式进行匹配。此时队列需要绑定要一个模式上。符号“ # ”匹配一个或多个词,符号“ * ”匹配不多不少一个词。因此“abc.#”能够匹配到“abc.def.xyz”,但是 “ abc. * ” 只会匹配到“abc.def”,一定是规则匹配才会转发。

    如图所示消息来源有: 美国新闻,美国天气,欧洲新闻,欧洲天气。
    如果你想看 美国主题: 那么就会收到 美国新闻,美国天气。
    如果你想看 新闻主题: 那么就会收到 美国新闻,欧洲新闻
    如果你想看 天气主题: 那么就会收到 美国天气,欧洲天气
    如果你想看 欧洲主题: 那么就会收到 欧洲新闻,欧洲天气。

    一定记住,不管什么模式,所有的消息始终都是先发送到交换机,由交换机经过路由传送给队列,消费者再从队列中获取消息的!

    所以说,生产者是面向交换机的,消费者是面向队列的

    构建一个简单的生产者与消费者模型

    生产者-消费者模型是指一方生产数据一方消费数据。两者之间会有一个缓冲区做为中介,生产者把数据放入缓冲区,消费者从缓冲区取出数据。另外,生产者消费者模式也是是面向过程编程其中的一种设计模式。

    构建生产者与消费者步骤

    以下列举一下生产者与消费者模型在实现时的一些步骤,各语言在实现的过程中也都是大同小异的。

    生产者步骤

    • 创建链接工厂
    • 通过链接工厂创建链接
    • 通过链接创建通道(channel)
    • 通过 channel 发送数据
    • 关闭链接

    消费者步骤

    • 创建链接工厂
    • 通过链接工厂创建链接
    • 通过链接创建通道(channel)
    • 声明一个队列
    • 创建消费者
    • 设置 channel

    生产者实例

    在开始写代码之前,可以先去创建一个可供消息存放的队列,其实也可以用代码去创建,这里界面操作可以熟悉并理解交换机,队列,路由之间的关系。

    创建新的queue

    创建消息队列

    输入要创建的队列名称。选择消息持久化到硬盘。

    package com.zkk.rabbitmq.producer.test;
    
    
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;
    import com.rabbitmq.client.ConnectionFactory;
    
    /**
     * description
     *
     * @author Zaker 2020/05/10 23:50
     */
    public class Producer {
    	public static void main(String[] args) throws Exception {
    		// 1. 创建连接池实例
    		ConnectionFactory connectionFactory=new ConnectionFactory();
    		connectionFactory.setHost("192.168.56.2");
    		connectionFactory.setPort(5672);
    		connectionFactory.setVirtualHost("/");
    		// 2. 通过连接池获取一个连接实例
    		Connection connection=connectionFactory.newConnection();
    		//3. 通过连接获取连接通道
    		Channel channel=connection.createChannel();
    		// 4. 通过 channel 发送数据
    		/**
    		 * 这里basicPublish()方法的参数可以说明一下
    		 * 	(1)String exchange -- 交换机名称,如果不传默认为 AMQP default
    		 *  (2) String routingKey -- 路由关键字 ,如果没有交换机直接输入队列名称
    		 *  (3)BasicProperties props -- 消息的基本属性,例如路由头等
    		 *  (4)byte[] body -- 消息体
    		 */
    		channel.basicPublish("", "hello-queue", null, "hello world".getBytes());
    		// 5. 关闭连接
    		channel.close();
    		connection.close();
    	}
    }
    
    

    执行一下生产者的方法,执行成功后查看管理界面,看queue

    可以很清晰的看到队列中有一条未被处理的消息。说明生产者以及将消息推送到队列中,等待消费者进行消费,然后可以点击队列名称,进入队列详情中拉到下面选项中有一个GetMessage的选项,点击Get Message,这里可以看到消息的具体内容,但不会将消息进行消费。

    然后下面看一下队列与交换机的绑定形式,首先,创建一个交换机

    交换机创建好之后,点击交换机名称,将交换机和队列进行绑定,在这里注意指定Routing Key的时候要与代码中指定的 Routing Key要一致。在生产端,消息的发送是面向交换机的,先发送到交换机,交换机通过Routing Key去匹配对应的队列,如果匹配到了,才会把消息最终放入对应的消息队列中去

    其中Routing Key的写法可以使用模糊匹配的格式

    这里写的hello.#的意思就是所有以hello.开头的路由都会被匹配到,其实还有hell.的写法,只不过*只能匹配到后续的一个 . 的模糊匹配,如果是hello.ab 就可以匹配到,但是如果是hello.ab.cd就匹配不到了,但是hello.#可以模糊匹配多组。

    接下来可以修改一下最开始代码,填充上交换机的参数进行测试:

    package com.zkk.rabbitmq.producer.test;
    
    
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;
    import com.rabbitmq.client.ConnectionFactory;
    
    /**
     * description
     *
     * @author Zaker 2020/05/10 22:02
     */
    public class Producer {
    	public static void main(String[] args) throws Exception {
    		// 1. 创建连接池实例
    		ConnectionFactory connectionFactory=new ConnectionFactory();
    		connectionFactory.setHost("192.168.56.2");
    		connectionFactory.setPort(5672);
    		connectionFactory.setVirtualHost("/");
    		// 2. 通过连接池获取一个连接实例
    		Connection connection=connectionFactory.newConnection();
    		//3. 通过连接获取连接通道
    		Channel channel=connection.createChannel();
    		// 4. 通过 channel 发送数据
    		/**
    		 * 这里basicPublish()方法的参数可以说明一下
    		 * 	(1)String exchange -- 交换机名称,如果不传默认为 AMQP default
    		 *  (2) String routingKey -- 路由关键字 ,如果没有交换机直接输入队列名称
    		 *  (3)BasicProperties props -- 消息的基本属性,例如路由头等
    		 *  (4)byte[] body -- 消息体
    		 */
    		channel.basicPublish("hello-exchange", "hello.ab", null, "hello RabbitMQ".getBytes());
    		// 5. 关闭连接
    		channel.close();
    		connection.close();
    	}
    }
    
    

    执行生产端方法后查看queue,可以看到本条消息也已经推送到队列中了:

    关于RabbitMQ的生产者大概就是这样,下来看一下消费者的使用

    消费者实例

    通过上面步骤哪里可以看到,消费者和生产者前几步其实都是差不多的,因为消息队列等在生产者的时候都已经设置好了,所以可以直接写代码:

    package com.zkk.rabbitmq.producer.test;
    
    import com.rabbitmq.client.*;
    
    import java.io.IOException;
    import java.io.UnsupportedEncodingException;
    
    /**
     * description
     *
     * @author Zaker 2020/05/11 0:40
     */
    public class Consumer {
    	public static void main(String[] args) throws Exception {
    		// 1. 创建连接池实例
    		ConnectionFactory connectionFactory=new ConnectionFactory();
    		connectionFactory.setHost("192.168.56.2");
    		connectionFactory.setPort(5672);
    		connectionFactory.setVirtualHost("/");
    		// 2. 通过连接池获取一个连接实例
    		Connection connection=connectionFactory.newConnection();
    		//3. 通过连接获取连接通道
    		Channel channel=connection.createChannel();
    		//4. 声明一个队列
    		String queueName="hello-queue";
    		channel.queueDeclare(queueName, true, false, false, null);
    		//5. 创建消费者
    		DefaultConsumer consumer=new DefaultConsumer(channel){
    			@Override
    			public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
    									   byte[] body) throws IOException {
    				super.handleDelivery(consumerTag, envelope, properties, body);
    
    				String message = null;
    				try {
    					message = new String(body, "UTF-8");
    					System.out.println(" [x] Received '" + message + "'");
    					System.out.printf("in consumer B (delivery tag is %d): %s
    ", envelope.getDeliveryTag(), message);
    					//自动签收
    					/*
    					* deliveryTag:该消息的index
    					*multiple:是否批量.true:将一次性ack所有小于deliveryTag的消息。
    					* */
    					channel.basicAck(envelope.getDeliveryTag(), false);
    				} catch (UnsupportedEncodingException e) {
    					e.printStackTrace();
    					/**
    					 * 自动拒签
    					 * deliveryTag:该消息的index
    					 * multiple:是否批量.true:将一次性拒绝所有小于deliveryTag的消息。
    					 * requeue:被拒绝的是否重新入队列
    					 *
    					 * 其实还有channel.basicReject方法可以拒签
    					 * channel.basicNack 与 channel.basicReject 的区别在于basicNack可以拒绝多条消息,而basicReject一次只能拒绝一条消息
    					 */
    					channel.basicNack(envelope.getDeliveryTag(),false,true);
    				}
    
    			}
    		};
    
    		/**
    		 * 设置channel
    		 * autoAck:是否自动ack,如果不自动ack,
    		 * 需要使用channel.basicAck、channel.basicNack、channel.basicReject 进行消息应答
    		 */
    		channel.basicConsume(queueName, false, consumer);
    
    	}
    }
    
    

    看打印结果可以看到消息消费成功:

    再去看MQ的管理界面,查看queue,可以看到消息已经被消费完成。

    在消费者的代码中,因为没有关闭channel,所以消费者一直在启动着对队列的监听,需要去手动执行关闭程序才会停止,这个时候如果在消费者再发送一次消息,执行一下消费者,可以看到新生产的消息已经被消费成功。

    这样一个最简单的生产者与消费者的DEMO就完成了。

    SpringBoot整合RabbitMQ

    这次的实例我创建了两个SpringBoot工程,一个是rabbitmq-producer(生产者),另一个就是rabbitmq-consumer(消费者)

    rabbitmq-producer(生产者)

    POM文件

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

    application.yml:

    spring:
      datasource:
          url: jdbc:mysql://192.168.56.2:3306/hodr_system?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
          username: root
          password: 123456
      #项目名称
      application:
        name: rabbitmq-producer
      #配置rabbitMq 服务器
      rabbitmq:
    #    host: 192.168.56.2
    #    port: 5672
        addresses: 192.168.56.2:5672
        username: guest
        password: guest
        #虚拟host 可以不设置,默认是 /
        virtual-host: /
        durable: true
        auto-delete: false
    
    
      jackson:
        date-format: java.text.SimpleDateFormat
        time-zone: GMT+8
        default-property-inclusion: non_null
    
    ---
    
    server:
      port: 8100
      #mybatis配置
    mybatis:
      mapperLocations: classpath*:/mapper/*.xml
      configuration:
        mapUnderscoreToCamelCase: true
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
        
    

    RabbitMqSendMessageService.java

    package com.zkk.rabbitmq.producer.rabbitmq;
    
    import com.zkk.rabbitmq.producer.domain.Company;
    
    /**
     * description  生产者发送消息接口
     *
     * @author Zaker 2020/05/16 20:08
     */
    public interface RabbitMqSendMessageService {
    	/**
    	 *direct模式发送
    	 *
    	 * @param company
    	 * @author Zaker 2020-05-16 22:10
    	 * @return
    	 */
    	void directSend(Company company);
    
    	/**
    	 *topic模式发送
    	 *
    	 * @param company
    	 * @author Zaker 2020-05-16 22:10
    	 * @return
    	 */
    	void topicSend(Company company);
    
    	/**
    	 *fanout模式发送
    	 *
    	 * @param company
    	 * @author Zaker 2020-05-16 22:10
    	 * @return
    	 */
    	void fanoutSend(Company company);
    }
    
    

    RabbitMqSendMessageServiceImpl.java

    package com.zkk.rabbitmq.producer.rabbitmq.impl;
    
    
    
    import com.rabbitmq.client.Channel;
    import com.zkk.rabbitmq.producer.domain.Company;
    import com.zkk.rabbitmq.producer.rabbitmq.RabbitMqSendMessageService;
    import net.sf.json.JSONObject;
    import org.springframework.amqp.rabbit.connection.Connection;
    import org.springframework.amqp.rabbit.connection.ConnectionFactory;
    import org.springframework.amqp.rabbit.core.RabbitTemplate;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    import java.io.IOException;
    import java.time.LocalDateTime;
    import java.time.format.DateTimeFormatter;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.UUID;
    
    
    /**
     * description
     *
     * @author Zaker 2020/05/16 20:09
     */
    @Component
    public class RabbitMqSendMessageServiceImpl implements RabbitMqSendMessageService {
    	@Autowired
    	private RabbitTemplate rabbitTemplate;
    
    	/**
    	 *direct模式发送消息
    	 *
    	 * @param company
    	 * @author Zaker 2020-05-16 22:10
    	 * @return
    	 */
    	@Override
    	public void directSend(Company company) {
    		String EXCHANGE_NAME="company-directExchange";
    
    		ConnectionFactory connectionFactory= rabbitTemplate.getConnectionFactory();
    		Connection connection=connectionFactory.createConnection();
    		Channel channel=connection.createChannel(true);
    
    		try {
    			channel.exchangeDeclare(EXCHANGE_NAME, "direct",true,false,null);
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    		//创建message的map,将要发送的消息封装进map中
    		Map<String,Object> map=new HashMap<>();
    		String messageId = String.valueOf(UUID.randomUUID());
    		String messageData = objectToJson(company);
    		String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    		map.put("messageId",messageId);
    		map.put("messageData",messageData);
    		map.put("createTime",createTime);
    		rabbitTemplate.convertAndSend(EXCHANGE_NAME, "company.direct", map);
    		System.out.print("公司数据推送成功direct:"+company.getCompanyName());
    	}
    
    
    
    	@Override
    	public void topicSend(Company company) {
    		String EXCHANGE_NAME="company-topicExchange";
    		ConnectionFactory connectionFactory= rabbitTemplate.getConnectionFactory();
    		Connection connection=connectionFactory.createConnection();
    		Channel channel=connection.createChannel(true);
    		try {
    			channel.exchangeDeclare(EXCHANGE_NAME, "topic",true,false,null);
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    		/*
    		 *
    		 * exchange     交换机名称
    		 * routingKey    路由
    		 * object         消息体内容
    		 * correlationData    可以自己指定的消息的唯一id
    		 *
    		 * */
    		Map<String,Object> map=new HashMap<>();
    		String messageId = String.valueOf(UUID.randomUUID());
    		String messageData = objectToJson(company);
    		String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    		map.put("messageId",messageId);
    		map.put("messageData",messageData);
    		map.put("createTime",createTime);
    
    		rabbitTemplate.convertAndSend(EXCHANGE_NAME, "company.topic", map);
    
    		System.out.print("公司数据推送成功topic:"+company.getCompanyName());
    	}
    
    	@Override
    	public void fanoutSend(Company company) {
    		String EXCHANGE_NAME="company-fanoutExchange";
    
    		ConnectionFactory connectionFactory= rabbitTemplate.getConnectionFactory();
    		Connection connection=connectionFactory.createConnection();
    		Channel channel=connection.createChannel(true);
    		try {
    			channel.exchangeDeclare(EXCHANGE_NAME, "fanout",true,false,null);
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    
    		String messageId = String.valueOf(UUID.randomUUID());
    		String messageData = objectToJson(company);
    		String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    		Map<String,Object> map=new HashMap<>();
    		map.put("messageId",messageId);
    		map.put("messageData",messageData);
    		map.put("createTime",createTime);
    
    		rabbitTemplate.convertAndSend(EXCHANGE_NAME, null, map);
    
    		System.out.print("公司数据推送成功fanout:"+company.getCompanyName());
    	}
    
    
    	public  String objectToJson(Object obj)  {
    		JSONObject jsonObject=JSONObject.fromObject(obj);
    		String jsonStr  =jsonObject.toString();
    		return jsonStr;
    	}
    
    
    }
    
    
    package com.zkk.rabbitmq.producer.controller;
    
    import com.sun.net.httpserver.Authenticator;
    import com.zkk.rabbitmq.producer.domain.Company;
    import com.zkk.rabbitmq.producer.rabbitmq.RabbitMqSendMessageService;
    import com.zkk.rabbitmq.producer.service.CompanyService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.List;
    
    /**
     * description  生产者发送消息接口类
     *
     * @author Zaker 2020/05/16 21:08
     */
    @RestController
    @RequestMapping("/v1/rabbitmq")
    public class RabbitMqController {
    
       @Autowired
       private RabbitMqSendMessageService rabbitMqSendMessageService;
       @Autowired
        private CompanyService companyService;
    
    
       @PostMapping("/direct")
       public ResponseEntity directSend(@RequestParam(required = false) Long companyId,
                                @RequestParam(required = false) String companyNumber,
                                @RequestParam(required = false) String companyName){
    
          Company company=new Company();
          company.setCompanyId(companyId);
          company.setCompanyName(companyNumber);
          company.setCompanyNumber(companyName);
          List<Company> companyList=companyService.queryCompanyBydto(company);
          for(Company c:companyList){
             rabbitMqSendMessageService.directSend(c);
          }
          return ResponseEntity.ok("ok");
       }
    
    
       @PostMapping("/topic")
       public ResponseEntity topicSend(@RequestParam(required = false) Long companyId,
                                @RequestParam(required = false) String companyNumber,
                                @RequestParam(required = false) String companyName){
    
          Company company=new Company();
          company.setCompanyId(companyId);
          company.setCompanyName(companyNumber);
          company.setCompanyNumber(companyName);
          List<Company> companyList=companyService.queryCompanyBydto(company);
          for(Company c:companyList){
             rabbitMqSendMessageService.topicSend(c);
          }
          return ResponseEntity.ok("ok");
       }
    
    
       @PostMapping("/fanout")
       public ResponseEntity fanoutSend(@RequestParam(required = false) Long companyId,
                               @RequestParam(required = false) String companyNumber,
                               @RequestParam(required = false) String companyName){
    
          Company company=new Company();
          company.setCompanyId(companyId);
          company.setCompanyName(companyNumber);
          company.setCompanyNumber(companyName);
          List<Company> companyList=companyService.queryCompanyBydto(company);
          for(Company c:companyList){
             rabbitMqSendMessageService.fanoutSend(c);
          }
          return ResponseEntity.ok("ok");
       }
    }
    

    rabbitmq-consumer(消费者)

    POM文件

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

    application.yml:

    spring:
      datasource:
          url: jdbc:mysql://192.168.56.2:3306/hodr_system?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
          username: root
          password: 123456
      #项目名称
      application:
        name: rabbitmq-producer
    
    
      #配置rabbitMq 服务器
      rabbitmq:
      #    host: 192.168.56.2
      #    port: 5672
        addresses: 192.168.56.2:5672
        username: guest
        password: guest
        #虚拟host 可以不设置,默认是 /
        virtual-host: /
        durable: true
        auto-delete: false
        #配置监听属性
        listener:
          simple:
            acknowledge-mode: manual
            concurrency: 10
            max-concurrency: 20
    #    istener:
    #      simple:
    #        #自动签收auto 手动 manual
    #        acknowledge-mode: manual
    #        #最大并发数
    #        concurrency: 10
    
    
    jackson:
      date-format: java.text.SimpleDateFormat
      time-zone: GMT+8
      default-property-inclusion: non_null
    
    ---
    
    server:
      port: 8200
      #mybatis配置
    
    mybatis:
      mapperLocations: classpath*:/mapper/*.xml
      configuration:
        mapUnderscoreToCamelCase: true
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    
    

    RabbitMqListener.java(主要的消息监听类)

    package com.zkk.rabbitmq.consumer.rabbitmq;
    
    import com.rabbitmq.client.AMQP;
    import com.rabbitmq.client.Channel;
    import com.zkk.rabbitmq.consumer.domain.Company;
    import com.zkk.rabbitmq.consumer.service.RabbitMqListenerProcessService;
    import org.springframework.amqp.rabbit.annotation.*;
    import org.springframework.amqp.support.AmqpHeaders;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.messaging.handler.annotation.Headers;
    import org.springframework.messaging.handler.annotation.Payload;
    import org.springframework.stereotype.Component;
    
    
    import java.util.Map;
    
    /**
     * description
     *
     * @author Zaker 2020/05/17 1:38
     */
    @Component
    public class RabbitMqListener {
    	@Autowired
    	private RabbitMqListenerProcessService rabbitMqListenerProcessService;
    	//@RabbitListener(queues = "QU_SPC_RECE_PERNR",autoStartup="false",id = "0001")
    	@RabbitListener(bindings =@QueueBinding(
    			value = @Queue(value = "company-directQueue", durable = "true", autoDelete = "false"),
    			exchange = @Exchange(name = "company-directExchange",durable = "true",
    											type = "direct",autoDelete = "false"),
    			key = "company.direct"
    		)
    	)
    	@RabbitHandler
    	public void directRecive(@Payload Map messageMap,
    						@Headers Map<String ,Object> headers,
    						Channel channel
    	) throws Exception {
    		System.out.println("----direct收到消息,开始消费-----");
    		Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
    		try {
    			System.out.println("公司编号:"+messageMap.toString() );
    			//手动签收
    			channel.basicAck(deliveryTag, false);
    		} catch (Exception e) {
    			//手动拒签
    			channel.basicNack(deliveryTag, false, true);
    			System.out.println("消息拒签,停止队列监听");
    			rabbitMqListenerProcessService.rabbitMqListenerStop("");
    		}
    	}
    
    	@RabbitListener(bindings =@QueueBinding(
    			value = @Queue(value = "company-topicQueue", durable = "true", autoDelete = "false"),
    			exchange = @Exchange(name = "company-topicExchange",durable = "true",
    					type = "topic",autoDelete = "false"),
    			key = "company.#"
    	)
    	)
    	@RabbitHandler
    	public void topicRecive(@Payload Map messageMap,
    						@Headers Map<String ,Object> headers,
    						Channel channel
    	) throws Exception {
    		System.out.println("----topic收到消息,开始消费-----");
    		Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
    		try {
    			//System.out.println("公司编号:"+company.getCompanyNumber() );
    			System.out.println("接收到的消息:"+messageMap.toString() );
    			//手动签收
    			channel.basicAck(deliveryTag, false);
    		} catch (Exception e) {
    			//手动拒签
    			channel.basicNack(deliveryTag, false, true);
    			System.out.println("消息拒签,停止队列监听");
    			rabbitMqListenerProcessService.rabbitMqListenerStop("");
    		}
    	}
    
    	@RabbitListener(bindings =@QueueBinding(
    			value = @Queue(value = "company-fanoutQueue", durable = "true", autoDelete = "false"),
    			exchange = @Exchange(name = "company-fanoutExchange",durable = "true",
    					type = "fanout",autoDelete = "false")
    	)
    	)
    	@RabbitHandler
    	public void fanoutRecive(@Payload Map messageMap,
    							@Headers Map<String ,Object> headers,
    							Channel channel
    	) throws Exception {
    		System.out.println("----fanout收到消息,开始消费-----");
    		Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
    		try {
    			System.out.println("接收到的消息:"+messageMap.get("messageData").toString() );
    			//手动签收
    			channel.basicAck(deliveryTag, false);
    		} catch (Exception e) {
    			//手动拒签
    			channel.basicNack(deliveryTag, false, true);
    			System.out.println("消息拒签,停止队列监听");
    			rabbitMqListenerProcessService.rabbitMqListenerStop("");
    		}
    	}
    
    }
    
    
    实战为王,记录在工作中技术使用的点点滴滴
  • 相关阅读:
    SQL 多列合并一列
    jQuery Ajax post多个值传参
    jquery获取select选中的值
    js 事件对象
    有关cookie
    事件冒泡和事件捕获
    js事件监听
    阻止浏览器的默认行为
    鼠标拖拽效果
    自定义右键菜单
  • 原文地址:https://www.cnblogs.com/kaikai-wanna-know/p/13182100.html
Copyright © 2020-2023  润新知