• RabbitMQ入门到精通_余胜军版笔记


    原笔记链接:https://files.cnblogs.com/files/henuliulei/Rabbitmq%E5%85%A5%E9%97%A8%E5%88%B0%E7%B2%BE%E9%80%9A.zip

    原视频链接:2021年RabbitMQ入门到精通 余胜军(通俗易懂版本)_哔哩哔哩_bilibili

    MQ架构设计原理

    什么是消息中间件

    消息中间件基于队列模型实现异步/同步传输数据

    作用:可以实现支撑高并发、异步解耦、流量削峰、降低耦合度。

    在了解中间件之前,我们先了解一下什么是同步?

    首先我们想一下,两个公司之间如果有互相调用接口的业务需求,如果没有引入中间件技术,是怎么实现的呢?

    用户发起请求给系统A,系统A接到请求直接调用系统B,系统B返回结果后,系统A才能返回结果给用户,这种模式就是同步调用。

    所谓同步调用就是各个系统之间互相依赖,一个系统发送请求,其他系统也会跟着依次进行处理,只有所有系统处理完成后对于用户来讲才算完成了一次请求。只要其他系统出现故障,就会报错给用户。

    那么引入中间件后,是如何做到异步调用的呢?

    用户发起请求给系统A,此时系统A发送消息给MQ,然后就返回结果给用户,不去管系统B了。然后系统B根据自己的情况,去MQ中获取消息,获取到消息的时候可能已经过了1分钟甚至1小时,再根据消息的指示执行相应的操作。

    那么想一想,系统A和系统B互相之间是否有通信?这种调用方式是同步调用吗?

    系统A发送消息给中间件后,自己的工作已经完成了,不用再去管系统B什么时候完成操作。而系统B拉去消息后,执行自己的操作也不用告诉系统A执行结果,所以整个的通信过程是异步调用的。

    说到这里,我们可以做个总结,消息中间件到底是什么呢?

    其实消息中间件就是一个独立部署的系统。可以实现各个系统之间的异步调用。当然它的作用可不止这些,通过它可以解决大量的技术痛点,我们接下来会进行介绍。

    消息中间件,总结起来作用有三个:异步化提升性能、降低耦合度、流量削峰。

    异步化提升性能

    先来说说异步化提升性能,上边我们介绍中间件的时候已经解释了引入中间件后,是如何实现异步化的,但没有解释具体性能是怎么提升的,我们来看一下下边的图。

     没有引入中间件的时候,用户发起请求到系统A,系统A耗时20ms,接下来系统A调用系统B,系统B耗时200ms,带给用户的体验就是,一个操作全部结束一共耗时220ms。

    如果引入中间件之后呢?看下边的图。

     用户发起请求到系统A,系统A耗时20ms,发送消息到MQ耗时5ms,返回结果一共用了25ms,用户体验一个操作只用了25ms,而不用管系统B什么时候去获取消息执行对应操作,这样比较下来,性能自然大大提高

    降低耦合度

    再来聊聊解耦的场景,看下图。

    如果没有引入中间件,那么系统A调用系统B的时候,系统B出现故障,导致调用失败,那么系统A就会接到异常信息,接到异常信息后肯定要再处理一下,返回给用户失败请稍后再试,这时候就得等待系统B的工程师解决问题,一切都解决好后再告知用户可以了,再重新操作一次吧。

    这样的架构,两个系统耦合再一起,用户体验极差。

    那么我们引入中间件后是什么样的场景呢,看下面的流程:

     对于系统A,发送消息后直接返回结果,不再管系统B后边怎么操作。而系统B故障恢复后重新到MQ中拉取消息,重新执行未完成的操作,这样一个流程,系统之间没有影响,也就实现了解耦。

    流量削峰

    下面我们再聊聊最后一个场景,流量削峰

    假如我们的系统A是一个集群,不连接数据库,这个集群本身可以抗下1万QPS

    系统B操作的是数据库,这个数据库只能抗下6000QPS,这就导致无论系统B如何扩容集群,都只能抗下6000QPS,它的瓶颈在于数据库。

    假如突然系统QPS达到1万,就会直接导致数据库崩溃,那么引入MQ后是怎么解决的呢,见下图:

     引入MQ后,对于系统A没有什么影响,给MQ发送消息可以直接发送1万QPS。

    此时对于系统B,可以自己控制获取消息的速度,保持在6000QPS一下,以一个数据库能够承受的速度执行操作。这样就可以保证数据库不会被压垮。

    当然,这种情况MQ中可能会积压大量消息。但对于MQ来说,是允许消息积压的,等到系统A峰值过去,恢复成1000QPS时,系统B还是在以6000QPS的速度去拉取消息,自然MQ中的消息就慢慢被释放掉了。

    这就是流量削峰的过程。在电商秒杀、抢票等等具有流量峰值的场景下可以使用这么一套架构。

    传统的http请求存在那些缺点

    1.Http请求基于请求与响应的模型,在高并发的情况下,客户端发送大量的请求达到

    服务器端有可能会导致我们服务器端处理请求堆积。

    2.Tomcat服务器处理每个请求都有自己独立的线程,如果超过最大线程数会将该请求缓存到队列中,如果请求堆积过多的情况下,有可能会导致tomcat服务器崩溃的问题。

    所以一般都会在nginx入口实现限流,整合服务保护框架。

     

    http请求处理业务逻辑如果比较耗时的情况下,容易造成客户端一直等待,阻塞等待 过程中会导致客户端超时发生重试策略,有可能会引发幂等性问题。

    注意事项:接口是为http协议的情况下,最好不要处理比较耗时的业务逻辑,耗时的业务逻辑应该单独交给多线程或者是mq处理。

    Mq应用场景有那些

    1. 异步发送短信
    2. 异步发送新人优惠券
    3. 处理一些比较耗时的操作

    为什么需要使用mq

    可以实现支撑高并发、异步解耦、流量削峰、降低耦合度。

    同步发送http请求

    客户端发送请求到达服务器端,服务器端实现会员注册业务逻辑,

    1.insertMember() --插入会员数据  1s

    2.sendSms()----发送登陆短信提醒 3s

    3.sendCoupons()----发送新人优惠券  3s

    总共响应需要6s时间,可能会导致客户端阻塞6s时间,对用户体验

    不是很好。

    多线程与MQ方式实现异步?

    互联网项目:

    客户端 安卓/IOS

    服务器端:php/java

    最好使用mq实现异步

    多线程处理业务逻辑

     

    用户向数据库中插入一条数据之后,在单独开启一个线程异步发送短信和优惠操作。

    客户端只需要等待1s时间

    优点:适合于小项目 实现异步

    缺点:有可能会消耗服务器cpu资源资源

    Mq处理业务逻辑

    先向数据库中插入一条会员数据,让后再向MQ中投递一个消息,MQ服务器端在将消息推送给消费者异步解耦处理发送短信和优惠券。

    Mq与多线程之间区别

    MQ可以实现异步/解耦/流量削峰问题;

    多线程也可以实现异步,但是消耗到cpu资源,没有实现解耦。

    Mq消息中间件名词

    Producer 生产者:投递消息到MQ服务器端;

    Consumer  消费者:从MQ服务器端获取消息处理业务逻辑;

    Broker   MQ服务器端

    Topic 主题:分类业务逻辑发送短信主题、发送优惠券主题

    Queue 存放消息模型 队列 先进先出 后进后出原则 数组/链表

    Message 生产者投递消息报文:json

    主流mq区别对比

     简单的比较

    特性

    ActiveMQ

    RabbitMQ

    RocketMQ

    kafka

    开发语言

    java

    erlang

    java

    scala

    单机吞吐量

    万级

    万级

    10万级

    10万级

    时效性

    ms级

    us级

    ms级

    ms级以内

    可用性

    高(主从架构)

    高(主从架构)

    非常高(分布式架构)

    非常高(分布式架构)

    功能特性

    成熟的产品,在很多公司得到应用;有较多的文档;各种协议支持较好

    基于erlang开发,所以并发能力很强,性能极其好,延时很低管理界面较丰富

    MQ功能比较完备,扩展性佳

    只支持主要的MQ功能,像一些消息查询,消息回溯等功能没有提供,毕竟是为大数据准备的,在大数据领域应用广。

     https://copyfuture.com/blogs-details/20190816154718064u4lgjgg20z1o6id

     

     

     

     

    Mq设计基础知识

     

    多线程版本mq

    使用LinkedBlockingDeque模拟实现多线程的方式读写中间件

    package com.mayikt.netty;
    
    import com.alibaba.fastjson.JSONObject;
    
    import java.util.concurrent.LinkedBlockingDeque;
    
    /**
     * @ClassName MayiktThreadMQ
     * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
     * @Version V1.0
     **/
    public class MayiktThreadMQ {
    
        private static LinkedBlockingDeque<JSONObject> msgs = new LinkedBlockingDeque<JSONObject>();
    
        public static void main(String[] args) {
            // 生产线程
            Thread producerThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        while (true) {
                            Thread.sleep(1000);
                            JSONObject data = new JSONObject();
                            data.put("userId", "1234");
                            // 存入消息
                            msgs.offer(data);
                        }
                    } catch (Exception e) {
    
                    }
    
                }
            }, "生产者");
            producerThread.start();
            // 消费者线程
            Thread consumerThread  = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        while (true) {
                            JSONObject data = msgs.poll();
                            if (data != null) {
                                System.out.println(Thread.currentThread().getName() + "," + data);
                            }
                        }
    
                    } catch (Exception e) {
    
                    }
    
                }
            }, "消费者");
            consumerThread.start();
        }
    }

    基于网络通讯版本mq netty实现

     生产者

    package com.mayikt.netty;
    
    import com.alibaba.fastjson.JSONObject;
    import io.netty.bootstrap.Bootstrap;
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.*;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.SocketChannel;
    import io.netty.channel.socket.nio.NioSocketChannel;
    
    /**
     * @ClassName MayiktNettyMQProducer
     * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
     * @Version V1.0
     **/
    public class MayiktNettyMQProducer {
        public void connect(int port, String host) throws Exception {
            //配置客户端NIO 线程组
            EventLoopGroup group = new NioEventLoopGroup();
            Bootstrap client = new Bootstrap();
            try {
                client.group(group)
                        // 设置为Netty客户端
                        .channel(NioSocketChannel.class)
                        /**
                         * ChannelOption.TCP_NODELAY参数对应于套接字选项中的TCP_NODELAY,该参数的使用与Nagle算法有关。
                         * Nagle算法是将小的数据包组装为更大的帧然后进行发送,而不是输入一次发送一次,因此在数据包不足的时候会等待其他数据的到来,组装成大的数据包进行发送,虽然该算法有效提高了网络的有效负载,但是却造成了延时。
                         * 而该参数的作用就是禁止使用Nagle算法,使用于小数据即时传输。和TCP_NODELAY相对应的是TCP_CORK,该选项是需要等到发送的数据量最大的时候,一次性发送数据,适用于文件传输。
                         */
                        .option(ChannelOption.TCP_NODELAY, true)
                        .handler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel ch) throws Exception {
                                ch.pipeline().addLast(new MayiktNettyMQProducer.NettyClientHandler());
    ////                            1. 演示LineBasedFrameDecoder编码器
    //                            ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
    //                            ch.pipeline().addLast(new StringDecoder());
                            }
                        });
    
                //绑定端口, 异步连接操作
                ChannelFuture future = client.connect(host, port).sync();
                //等待客户端连接端口关闭
                future.channel().closeFuture().sync();
            } finally {
                //优雅关闭 线程组
                group.shutdownGracefully();
            }
        }
    
        public static void main(String[] args) {
            int port = 9008;
            MayiktNettyMQProducer client = new MayiktNettyMQProducer();
            try {
                client.connect(port, "127.0.0.1");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public class NettyClientHandler extends ChannelInboundHandlerAdapter {
    
    
            @Override
            public void channelActive(ChannelHandlerContext ctx) throws Exception {
    
                JSONObject data = new JSONObject();
                data.put("type", "producer");
                JSONObject msg = new JSONObject();
                msg.put("userId", "123456");
                msg.put("age", "23");
                data.put("msg", msg);
                // 生产发送数据
                byte[] req = data.toJSONString().getBytes();
                ByteBuf firstMSG = Unpooled.buffer(req.length);
                firstMSG.writeBytes(req);
                ctx.writeAndFlush(firstMSG);
            }
    
            /**
             * 客户端读取到服务器端数据
             *
             * @param ctx
             * @param msg
             * @throws Exception
             */
            @Override
            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                ByteBuf buf = (ByteBuf) msg;
                byte[] req = new byte[buf.readableBytes()];
                buf.readBytes(req);
                String body = new String(req, "UTF-8");
                System.out.println("客户端接收到服务器端请求:" + body);
            }
    
            // tcp属于双向传输
    
            @Override
            public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                ctx.close();
            }
        }
    }

    MQ服务器

    package com.mayikt.netty;
    
    import com.alibaba.fastjson.JSONObject;
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.*;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.SocketChannel;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    import org.apache.commons.lang3.StringUtils;
    
    import java.io.UnsupportedEncodingException;
    import java.util.ArrayList;
    import java.util.concurrent.LinkedBlockingDeque;
    
    /**
     * @ClassName NettyMQServer2021
     * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
     * @Version V1.0
     **/
    public class MayiktNettyMQServer {
        public void bind(int port) throws Exception {
            /**
             * Netty 抽象出两组线程池BossGroup和WorkerGroup
             * BossGroup专门负责接收客户端的连接, WorkerGroup专门负责网络的读写。
             */
            EventLoopGroup bossGroup = new NioEventLoopGroup();
            EventLoopGroup workerGroup = new NioEventLoopGroup();
            ServerBootstrap bootstrap = new ServerBootstrap();
            try {
                bootstrap.group(bossGroup, workerGroup)
                        // 设定NioServerSocketChannel 为服务器端
                        .channel(NioServerSocketChannel.class)
                        //BACKLOG用于构造服务端套接字ServerSocket对象,标识当服务器请求处理线程全满时,
                        //用于临时存放已完成三次握手的请求的队列的最大长度。如果未设置或所设置的值小于1,Java将使用默认值50。
                        .option(ChannelOption.SO_BACKLOG, 100)
                        // 服务器端监听数据回调Handler
                        .childHandler(new MayiktNettyMQServer.ChildChannelHandler());
                //绑定端口, 同步等待成功;
                ChannelFuture future = bootstrap.bind(port).sync();
                System.out.println("当前服务器端启动成功...");
                //等待服务端监听端口关闭
                future.channel().closeFuture().sync();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //优雅关闭 线程组
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            }
        }
    
        private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                // 设置异步回调监听
                ch.pipeline().addLast(new MayiktNettyMQServer.MayiktServerHandler());
            }
        }
    
        public static void main(String[] args) throws Exception {
            int port = 9008;
            new MayiktNettyMQServer().bind(port);
        }
    
        private static final String type_consumer = "consumer";
    
        private static final String type_producer = "producer";
        private static LinkedBlockingDeque<String> msgs = new LinkedBlockingDeque<>();
        private static ArrayList<ChannelHandlerContext> ctxs = new ArrayList<>();
    
        // 生产者投递消息的:topicName
        public class MayiktServerHandler extends SimpleChannelInboundHandler<Object> {
    
            /**
             * 服务器接收客户端请求
             *
             * @param ctx
             * @param data
             * @throws Exception
             */
            @Override
            protected void channelRead0(ChannelHandlerContext ctx, Object data)
                    throws Exception {
                JSONObject clientMsg = getData(data);
                String type = clientMsg.getString("type");
                switch (type) {
                    case type_producer:
                        producer(clientMsg);
                        break;
                    case type_consumer:
                        consumer(ctx);
                        break;
                }
            }
    
            private void consumer(ChannelHandlerContext ctx) {
                // 保存消费者连接
                ctxs.add(ctx);
                // 主动拉取mq服务器端缓存中没有被消费的消息
                String data = msgs.poll();
                if (StringUtils.isEmpty(data)) {
                    return;
                }
                // 将该消息发送给消费者
                byte[] req = data.getBytes();
                ByteBuf firstMSG = Unpooled.buffer(req.length);
                firstMSG.writeBytes(req);
                ctx.writeAndFlush(firstMSG);
            }
    
            private void producer(JSONObject clientMsg) {
                // 缓存生产者投递 消息
                String msg = clientMsg.getString("msg");
                msgs.offer(msg);
    
                //需要将该消息推送消费者
                ctxs.forEach((ctx) -> {
                    // 将该消息发送给消费者
                    String data = msgs.poll();
                    if (data == null) {
                        return;
                    }
                    byte[] req = data.getBytes();
                    ByteBuf firstMSG = Unpooled.buffer(req.length);
                    firstMSG.writeBytes(req);
                    ctx.writeAndFlush(firstMSG);
                });
            }
    
            private JSONObject getData(Object data) throws UnsupportedEncodingException {
                ByteBuf buf = (ByteBuf) data;
                byte[] req = new byte[buf.readableBytes()];
                buf.readBytes(req);
                String body = new String(req, "UTF-8");
                return JSONObject.parseObject(body);
            }
    
    
            @Override
            public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
                ctx.flush();
            }
    
            @Override
            public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
                    throws Exception {
    
                ctx.close();
            }
        }
    }

    消费者

    package com.mayikt.netty;
    
    import com.alibaba.fastjson.JSONObject;
    import io.netty.bootstrap.Bootstrap;
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.*;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.SocketChannel;
    import io.netty.channel.socket.nio.NioSocketChannel;
    
    /**
     * @ClassName MayiktNettyMQProducer
     * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
     * @Version V1.0
     **/
    public class
    
    MayiktNettyMQConsumer {
        public void connect(int port, String host) throws Exception {
            //配置客户端NIO 线程组
            EventLoopGroup group = new NioEventLoopGroup();
            Bootstrap client = new Bootstrap();
            try {
                client.group(group)
                        // 设置为Netty客户端
                        .channel(NioSocketChannel.class)
                        /**
                         * ChannelOption.TCP_NODELAY参数对应于套接字选项中的TCP_NODELAY,该参数的使用与Nagle算法有关。
                         * Nagle算法是将小的数据包组装为更大的帧然后进行发送,而不是输入一次发送一次,因此在数据包不足的时候会等待其他数据的到来,组装成大的数据包进行发送,虽然该算法有效提高了网络的有效负载,但是却造成了延时。
                         * 而该参数的作用就是禁止使用Nagle算法,使用于小数据即时传输。和TCP_NODELAY相对应的是TCP_CORK,该选项是需要等到发送的数据量最大的时候,一次性发送数据,适用于文件传输。
                         */
                        .option(ChannelOption.TCP_NODELAY, true)
                        .handler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel ch) throws Exception {
                                ch.pipeline().addLast(new MayiktNettyMQConsumer.NettyClientHandler());
    ////                            1. 演示LineBasedFrameDecoder编码器
    //                            ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
    //                            ch.pipeline().addLast(new StringDecoder());
                            }
                        });
    
                //绑定端口, 异步连接操作
                ChannelFuture future = client.connect(host, port).sync();
                //等待客户端连接端口关闭
                future.channel().closeFuture().sync();
            } finally {
                //优雅关闭 线程组
                group.shutdownGracefully();
            }
        }
    
        public static void main(String[] args) {
            int port = 9008;
            MayiktNettyMQConsumer client = new MayiktNettyMQConsumer();
            try {
                client.connect(port, "127.0.0.1");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public class NettyClientHandler extends ChannelInboundHandlerAdapter {
    
    
            @Override
            public void channelActive(ChannelHandlerContext ctx) throws Exception {
    
                JSONObject data = new JSONObject();
                data.put("type", "consumer");
                // 生产发送数据
                byte[] req = data.toJSONString().getBytes();
                ByteBuf firstMSG = Unpooled.buffer(req.length);
                firstMSG.writeBytes(req);
                ctx.writeAndFlush(firstMSG);
            }
    
            /**
             * 客户端读取到服务器端数据
             *
             * @param ctx
             * @param msg
             * @throws Exception
             */
            @Override
            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                ByteBuf buf = (ByteBuf) msg;
                byte[] req = new byte[buf.readableBytes()];
                buf.readBytes(req);
                String body = new String(req, "UTF-8");
                System.out.println("客户端接收到服务器端请求:" + body);
            }
    
            // tcp属于双向传输
    
            @Override
            public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                ctx.close();
            }
        }
    }

    基于netty实现mq

    消费者netty客户端与nettyServer端MQ服务器端保持长连接,MQ服务器端保存

    消费者连接。

    生产者netty客户端发送请求给nettyServer端MQ服务器端,MQ服务器端在将该

    消息内容发送给消费者。

    body:{"msg":{"userId":"123456","age":"23"},"type":"producer",”topic”:””}

    生产者投递消息给MQ服务器端,MQ服务器端需要缓存该消息

    如果mq服务器端宕机之后,消息如何保证不丢失:

    持久化机制

    如果mq接收到生产者投递消息,如果消费者不在的情况下,该消息是否会丢失?

    不会丢失,消息确认机制 必须要消费者消费该消息成功之后,在通知给mq服务器端,删除该消息。而kafka即使读取消息后也不会立刻删除,而是设置该消息被消费了,一定时间后统一删除。

    消费者已经和mq服务器保持长连接:Mq服务器端将该消息推送消费者。

    消费者第一次刚启动的时候,消费者会主动拉取消息。

    Mq如何实现抗高并发思想

    Mq消费者根据自身能力情况 ,拉取mq服务器端消息消费。

    默认的情况下是取出一条消息。

    缺点:存在延迟的问题

    需要考虑mq消费者提高速率的问题:

    如何消费者提高速率:消费者实现集群、消费者批量获取消息即可。

    RabbitMQ

    RabbitMQ基本介绍

    RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件),RabbitMQ服务器是用Erlang语言编写的。

    RabitMQ官方网站:

    https://www.rabbitmq.com/

     

    1.点对点(简单)的队列

    2.工作(公平性)队列模式

    3.发布订阅模式

    4.路由模式Routing

    5.通配符模式Topics

    6.RPC
    https://www.rabbitmq.com/getstarted.html

    RabbitMQ环境的基本安装

    1.下载并安装erlang,下载地址:http://www.erlang.org/download

    2.配置erlang环境变量信息

      新增环境变量ERLANG_HOME=erlang的安装地址

      将%ERLANG_HOME%in加入到path中

    3.下载并安装RabbitMQ,下载地址:http://www.rabbitmq.com/download.html

    注意: RabbitMQ 它依赖于Erlang,需要先安装Erlang。

    https://www.rabbitmq.com/install-windows.html

    如何启动Rabbitmq

    net start RabbitMQ

    启动Rabbitmq常见问题

    如果rabbitmq 启动成功无法访问 管理平台页面

    进入到F:path abbitmq abbitmq abbitmq_server-3.6.9sbin>

    执行

    rabbitmq-plugins enable rabbitmq_management

    rabbitmqctl start_app

     

    Rabbitmq管理平台中心

    RabbitMQ 管理平台地址 http://127.0.0.1:15672

    默认账号:guest/guest  用户可以自己创建新的账号

    Virtual Hosts:

    像mysql有数据库的概念并且可以指定用户对库和表等操作的权限。那RabbitMQ呢?

    RabbitMQ也有类似的权限管理。在RabbitMQ中可以虚拟消息服务器VirtualHost,每

    个VirtualHost相当月一个相对独立的RabbitMQ服务器,每个VirtualHost之间是相互

    隔离。exchange、queue、message不能互通。

    默认的端口15672:rabbitmq管理平台端口号

    默认的端口5672: rabbitmq消息中间内部通讯的端口
    默认的端口号25672  rabbitmq集群的端口号

    RabbitMQ常见名词

    /Virtual Hosts---分类

    /队列 存放我们消息

    Exchange 分派我们消息在那个队列存放起来 类似于nginx

    15672---rabbitmq控制台管理平台 http协议

    25672rabbitmq 集群通信端口号

    Amqp 5672 rabbitmq内部通信的一个端口号

    RabbitMQ创建账户

    RabbitMQ平台创建Virtual Hosts

    RabbitMQ平台创建消息队列

    快速入门RabbitMQ简单队列

    首先需要再RabbitMQ平台创建Virtual Hosts 和队列。

    /myVirtualHosts

    ----订单队列

    ----支付队列

    1. 在RabbitMQ平台创建一个队列;
    2. 在编写生产者代码
    3. 在编写消费者代码

    下列模式用到的所有的依赖

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.mayikt</groupId>
        <artifactId>mayikt-mq</artifactId>
        <version>1.0-SNAPSHOT</version>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>8</source>
                        <target>8</target>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
    
        <dependencies>
            <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
    
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.62</version>
            </dependency>
    
    
            <!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
            <dependency>
                <groupId>io.netty</groupId>
                <artifactId>netty-all</artifactId>
                <version>4.1.54.Final</version>
            </dependency>
    
    
            <!-- https://mvnrepository.com/artifact/junit/junit -->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency>
    
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
                <version>3.4</version>
            </dependency>
            <!-- https://mvnrepository.com/artifact/com.rabbitmq/amqp-client -->
            <dependency>
                <groupId>com.rabbitmq</groupId>
                <artifactId>amqp-client</artifactId>
                <version>5.2.0</version>
            </dependency>
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-simple</artifactId>
                <version>1.7.25</version>
                <scope>compile</scope>
            </dependency>
    
        </dependencies>
    
    
    </project>

    下面的程序是在普通maven工程下创建,使用到的队列,交换机,虚拟主机都要我们手动在mq管理界面(port:15672)创建

    公共类:

    package com.mayikt.rabbitmq;
    
    import com.rabbitmq.client.AMQP;
    import com.rabbitmq.client.Connection;
    import com.rabbitmq.client.ConnectionFactory;
    
    import java.io.IOException;
    import java.util.concurrent.TimeoutException;
    
    /**
     * @ClassName RabbitMQConnection
     * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
     * @Version V1.0
     **/
    public class RabbitMQConnection {
    
        /**
         * 创建连接
         *
         * @return
         * @throws IOException
         * @throws TimeoutException
         */
        public static Connection getConnection() throws IOException, TimeoutException {
            //1.创建connectionFactory
            ConnectionFactory connectionFactory = new ConnectionFactory();
            //2.配置Host
            connectionFactory.setHost("127.0.0.1");
            //3.设置Port
            connectionFactory.setPort(5672);
            //4.设置账户和密码
            connectionFactory.setUsername("guest");
            connectionFactory.setPassword("guest");
            //5.设置VirtualHost
            connectionFactory.setVirtualHost("/myVirtualHost");
            return connectionFactory.newConnection();
        }
    }

    点对点模式

    package com.mayikt.demo00;
    
    import com.mayikt.rabbitmq.RabbitMQConnection;
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;
    
    import java.io.IOException;
    import java.util.concurrent.TimeoutException;
    
    /**
     * @ClassName Producer 演示消息确认机制
     * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
     * @Version V1.0
     **/
    public class Producer {
        private static final String QUEUE_NAME = "test_quene";
    
        public static void main(String[] args) {
            try {
                //1.创建一个新连接
                Connection connection = RabbitMQConnection.getConnection();
    
                //2.设置channel
                Channel channel = connection.createChannel();
                //3.发送消息
                String msg = "每特教育6666";
                channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
                boolean result = channel.waitForConfirms();
                if (result) {
                    System.out.println("消息投递成功");
                } else {
                    System.out.println("消息投递失败");
                }
                channel.close();
                connection.close();
            } catch (Exception e) {
    
            }
        }
    }
    package com.mayikt.demo00;
    
    import com.mayikt.rabbitmq.RabbitMQConnection;
    import com.rabbitmq.client.*;
    
    import java.io.IOException;
    import java.util.concurrent.TimeoutException;
    
    public class Consumer {
        private static final String QUEUE_NAME = "test_quene";
    
        public static void main(String[] args) throws IOException, TimeoutException, IOException, TimeoutException {
            // 1.创建连接
            Connection connection = Producer1.getConnection();
            // 2.设置通道
            final Channel channel = connection.createChannel();
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    String msg = new String(body, "UTF-8");
                    System.out.println("消费者获取消息:" + msg);
    //                // 消费者完成 消费者通知给mq服务器端删除该消息
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            };
            // 3.监听队列
            channel.basicConsume(QUEUE_NAME, false, defaultConsumer);
    
        }
    } 

    Work模式

    Producer
    package com.mayikt.demo01;
    
    import com.mayikt.rabbitmq.RabbitMQConnection;
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;
    
    import java.io.IOException;
    import java.util.concurrent.TimeoutException;
    
    /**
     * @ClassName Producer
     * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
     * @Version V1.0
     **/
    public class Producer {
        private static final String QUEUE_NAME = "mayikt-queue";
    
        public static void main(String[] args) throws IOException, TimeoutException {
            //1.创建一个新连接
            Connection connection = RabbitMQConnection.getConnection();
            //2.设置channel
            Channel channel = connection.createChannel();
            //3.发送消息
            for (int i = 0; i < 100; i++) {
                String msg = "每特教育6666:i" + i;
                channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
            }
            System.out.println("消息投递成功");
            channel.close();
            connection.close();
        }
    }
    Consumer01
    package com.mayikt.demo01;
    
    import com.mayikt.rabbitmq.RabbitMQConnection;
    import com.rabbitmq.client.*;
    
    import java.io.IOException;
    import java.util.concurrent.TimeoutException;
    
    public class Consumer01 {
        private static final String QUEUE_NAME = "mayikt-queue";
    
        public static void main(String[] args) throws IOException, TimeoutException, IOException, TimeoutException {
            // 1.创建连接
            Connection connection = RabbitMQConnection.getConnection();
            // 2.设置通道
            Channel channel = connection.createChannel();
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    try {
                        Thread.sleep(1000);
                    } catch (Exception e) {
    
                    }
                    String msg = new String(body, "UTF-8");
                    System.out.println("消费者获取消息:" + msg);
                }
            };
            // 3.监听队列
            channel.basicConsume(QUEUE_NAME, true, defaultConsumer);
    
        }
    }
    Consumer02
    package com.mayikt.demo01;
    
    import com.mayikt.rabbitmq.RabbitMQConnection;
    import com.rabbitmq.client.*;
    
    import java.io.IOException;
    import java.util.concurrent.TimeoutException;
    
    public class Consumer02 {
        private static final String QUEUE_NAME = "mayikt-queue";
    
        public static void main(String[] args) throws IOException, TimeoutException, IOException, TimeoutException {
            // 1.创建连接
            Connection connection = RabbitMQConnection.getConnection();
            // 2.设置通道
            Channel channel = connection.createChannel();
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
                    String msg = new String(body, "UTF-8");
                    System.out.println("消费者获取消息:" + msg);
                }
            };
            // 3.监听队列
            channel.basicConsume(QUEUE_NAME, true, defaultConsumer);
    
        }
    }

    测试结果

    测试结果:

    1、  消费者1和消费者2获取到的消息内容是不同的,同一个消息只能被一个消费者获取。

    2、  消费者1和消费者2获取到的消息的数量是相同的,一个是奇数一个是偶数。

    其实,这样是不合理的,应该是消费者1要比消费者2获取到的消息多才对。

    Work模式的“能者多劳”

    Consumer1
    package com.mayikt.demo02;
    
    import com.mayikt.rabbitmq.RabbitMQConnection;
    import com.rabbitmq.client.*;
    
    import java.io.IOException;
    import java.util.concurrent.TimeoutException;
    
    public class Consumer1 {
        private static final String QUEUE_NAME = "mayikt-queue";
    
        public static void main(String[] args) throws IOException, TimeoutException, IOException, TimeoutException {
            // 1.创建连接
            Connection connection = RabbitMQConnection.getConnection();
            // 2.设置通道
            Channel channel = connection.createChannel();
            //指定我们消费者每次批量获取消息
            channel.basicQos(2);
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
                    String msg = new String(body, "UTF-8");
                    System.out.println("消费者获取消息:" + msg);
                    try {
    // 消费者完成 删除该消息
                        channel.basicAck(envelope.getDeliveryTag(), false);
                    }catch (Exception e){
    
                    }
    //
                }
            };
            // 3.监听队列
            channel.basicConsume(QUEUE_NAME, false, defaultConsumer);
    
        }
    }
    Consumer2
    package com.mayikt.demo02;
    
    import com.mayikt.rabbitmq.RabbitMQConnection;
    import com.rabbitmq.client.*;
    
    import java.io.IOException;
    import java.util.concurrent.TimeoutException;
    
    public class Consumer2 {
        private static final String QUEUE_NAME = "mayikt-queue";
    
        public static void main(String[] args) throws IOException, TimeoutException, IOException, TimeoutException {
            // 1.创建连接
            Connection connection = RabbitMQConnection.getConnection();
            // 2.设置通道
            Channel channel = connection.createChannel();
            channel.basicQos(1);
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    try {
                        Thread.sleep(1000);
                    } catch (Exception e) {
    
                    }
                    String msg = new String(body, "UTF-8");
                    System.out.println("消费者获取消息:" + msg);
                    // 消费者完成 删除该消息
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            };
            // 3.监听队列
            channel.basicConsume(QUEUE_NAME, false, defaultConsumer);
    
        }
    }
    package com.mayikt.demo02;
    
    import com.mayikt.rabbitmq.RabbitMQConnection;
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;
    
    import java.io.IOException;
    import java.util.concurrent.TimeoutException;
    
    /**
     * @ClassName Producer
     * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
     * @Version V1.0
     **/
    public class Producer {
        private static final String QUEUE_NAME = "mayikt-queue";
    
        public static void main(String[] args) throws IOException, TimeoutException {
            //1.创建一个新连接
            Connection connection = RabbitMQConnection.getConnection();
            //2.设置channel
            Channel channel = connection.createChannel();
            //3.发送消息
            for (int i = 0; i < 10; i++) {
                String msg = "每特教育6666:i" + i;
                channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
            }
            System.out.println("消息投递成功");
            channel.close();
            connection.close();
        }
    }

    ProducerFanout
    package com.mayikt.demo03;
    
    import com.mayikt.rabbitmq.RabbitMQConnection;
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;
    
    import java.io.IOException;
    import java.util.concurrent.TimeoutException;
    
    public class ProducerFanout {
    
        /**
         * 定义交换机的名称
         */
        private static final String EXCHANGE_NAME = "fanout_exchange";
    
        public static void main(String[] args) throws IOException, TimeoutException {
            //  创建Connection
            Connection connection = RabbitMQConnection.getConnection();
            // 创建Channel
            Channel channel = connection.createChannel();
            // 通道关联交换机
            channel.exchangeDeclare(EXCHANGE_NAME, "fanout", true);  //第二个参数type:交换机类型,常见的如fanout、direct、topic
    
            String msg = "每特教育6666";
            channel.basicPublish(EXCHANGE_NAME, "", null, msg.getBytes());
            channel.close();
            connection.close();
        }
    
    }
    package com.mayikt.demo03;
    
    import com.mayikt.rabbitmq.RabbitMQConnection;
    import com.rabbitmq.client.*;
    
    import java.io.IOException;
    import java.util.concurrent.TimeoutException;
    
    public class MailConsumer {
        /**
         * 定义邮件队列
         */
        private static final String QUEUE_NAME = "fanout_email_queue";
        /**
         * 定义交换机的名称
         */
        private static final String EXCHANGE_NAME = "fanout_exchange";
    
        public static void main(String[] args) throws IOException, TimeoutException {
            System.out.println("邮件消费者...");
            // 创建我们的连接
            Connection connection = RabbitMQConnection.getConnection();
            // 创建我们通道
            final Channel channel = connection.createChannel();
            // 关联队列消费者关联队列
            channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    String msg = new String(body, "UTF-8");
                    System.out.println("邮件消费者获取消息:" + msg);
                }
            };
            // 开始监听消息 自动签收
            channel.basicConsume(QUEUE_NAME, true, defaultConsumer);
    
        }
    }
    SmsConsumer
    package com.mayikt.demo03;
    
    import com.mayikt.rabbitmq.RabbitMQConnection;
    import com.rabbitmq.client.*;
    
    import java.io.IOException;
    import java.util.concurrent.TimeoutException;
    
    public class SmsConsumer {
        /**
         * 定义短信队列
         */
        private static final String QUEUE_NAME = "fanout_email_sms";
        /**
         * 定义交换机的名称
         */
        private static final String EXCHANGE_NAME = "fanout_exchange";
    
        public static void main(String[] args) throws IOException, TimeoutException {
            System.out.println("短信消费者...");
            // 创建我们的连接
            Connection connection = RabbitMQConnection.getConnection();
            // 创建我们通道
            final Channel channel = connection.createChannel();
            // 关联队列消费者关联队列
            channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    String msg = new String(body, "UTF-8");
                    System.out.println("短信消费者获取消息:" + msg);
                }
            };
            // 开始监听消息 自动签收
            channel.basicConsume(QUEUE_NAME, true, defaultConsumer);
    
        }
    }

    四、路由模式

    1、图示

    注释:Direct Exchange – 处理路由键。需要将一个队列绑定到交换机上,要求该消息与一个特定的路由键完全匹配。这是一个完整的匹配。如果一个队列绑定到该交换机上要求路由键 “dog”,则只有被标记为“dog”的消息才被转发,不会转发dog.puppy,也不会转发dog.guard,只会转发dog。 

    ProducerDirect
    package com.mayikt.demo04;
    
    import com.mayikt.rabbitmq.RabbitMQConnection;
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;
    
    import java.io.IOException;
    import java.util.concurrent.TimeoutException;
    
    public class ProducerDirect {
    
        /**
         * 定义交换机的名称
         */
        private static final String EXCHANGE_NAME = "direct_exchange";
    
        public static void main(String[] args) throws IOException, TimeoutException {
            //  创建Connection
            Connection connection = RabbitMQConnection.getConnection();
            // 创建Channel
            Channel channel = connection.createChannel();
            // 通道关联交换机
            channel.exchangeDeclare(EXCHANGE_NAME, "direct", true);
            String msg = "每特教育6666";
            channel.basicPublish(EXCHANGE_NAME, "email", null, msg.getBytes());  //只发送给邮件这个队列
            channel.close();
            connection.close();
        }
    
    }
    MailConsumer
    package com.mayikt.demo04;
    
    import com.mayikt.rabbitmq.RabbitMQConnection;
    import com.rabbitmq.client.*;
    
    import java.io.IOException;
    import java.util.concurrent.TimeoutException;
    
    public class MailConsumer {
        /**
         * 定义邮件队列
         */
        private static final String QUEUE_NAME = "direct_email_queue";
        /**
         * 定义交换机的名称
         */
        private static final String EXCHANGE_NAME = "direct_exchange";
    
        public static void main(String[] args) throws IOException, TimeoutException {
            System.out.println("邮件消费者...");
            // 创建我们的连接
            Connection connection = RabbitMQConnection.getConnection();
            // 创建我们通道
            final Channel channel = connection.createChannel();
            // 关联队列消费者关联队列
            channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "email");
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    String msg = new String(body, "UTF-8");
                    System.out.println("邮件消费者获取消息:" + msg);
                }
            };
            // 开始监听消息 自动签收
            channel.basicConsume(QUEUE_NAME, true, defaultConsumer);
    
        }
    }
    SmsConsumer
    package com.mayikt.demo04;
    
    import com.mayikt.rabbitmq.RabbitMQConnection;
    import com.rabbitmq.client.*;
    
    import java.io.IOException;
    import java.util.concurrent.TimeoutException;
    
    public class SmsConsumer {
        /**
         * 定义短信队列
         */
        private static final String QUEUE_NAME = "direct_sms_queue";
        /**
         * 定义交换机的名称
         */
        private static final String EXCHANGE_NAME = "direct_exchange";
    
        public static void main(String[] args) throws IOException, TimeoutException {
            System.out.println("短信消费者...");
            // 创建我们的连接
            Connection connection = RabbitMQConnection.getConnection();
            // 创建我们通道
            final Channel channel = connection.createChannel();
            // 关联队列消费者关联队列
            channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "sms");
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    String msg = new String(body, "UTF-8");
                    System.out.println("短信消费者获取消息:" + msg);
                }
            };
            // 开始监听消息 自动签收
            channel.basicConsume(QUEUE_NAME, true, defaultConsumer);
    
        }
    }

    ProducerTopic

    package com.mayikt.demo06;

    import com.mayikt.rabbitmq.RabbitMQConnection;
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;

    import java.io.IOException;
    import java.util.concurrent.TimeoutException;

    public class ProducerTopic {

    /**
    * 定义交换机的名称
    */
    private static final String EXCHANGE_NAME = "topic_exchange";

    public static void main(String[] args) throws IOException, TimeoutException {
    // 创建Connection
    Connection connection = RabbitMQConnection.getConnection();
    // 创建Channel
    Channel channel = connection.createChannel();
    // 通道关联交换机
    channel.exchangeDeclare(EXCHANGE_NAME, "topic", true);
    String msg = "每特教育6666";
    channel.basicPublish(EXCHANGE_NAME, "mayikt.sms", null, msg.getBytes());
    channel.close();
    connection.close();
    }

    }
    MailConsumer

    package com.mayikt.demo06;

    import com.mayikt.rabbitmq.RabbitMQConnection;
    import com.rabbitmq.client.*;

    import java.io.IOException;
    import java.util.concurrent.TimeoutException;

    public class MailConsumer {
    /**
    * 定义邮件队列
    */
    private static final String QUEUE_NAME = "topic_email_queue";
    /**
    * 定义交换机的名称
    */
    private static final String EXCHANGE_NAME = "topic_exchange";

    public static void main(String[] args) throws IOException, TimeoutException {
    System.out.println("邮件消费者...");
    // 创建我们的连接
    Connection connection = RabbitMQConnection.getConnection();
    // 创建我们通道
    final Channel channel = connection.createChannel();
    // 关联队列消费者关联队列
    channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "mayikt.*");
    DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    String msg = new String(body, "UTF-8");
    System.out.println("邮件消费者获取消息:" + msg);
    }
    };
    // 开始监听消息 自动签收
    channel.basicConsume(QUEUE_NAME, true, defaultConsumer);

    }
    }
    SmsConsumer
    package com.mayikt.demo06;

    import com.mayikt.rabbitmq.RabbitMQConnection;
    import com.rabbitmq.client.*;

    import java.io.IOException;
    import java.util.concurrent.TimeoutException;

    public class SmsConsumer {
    /**
    * 定义短信队列
    */
    private static final String QUEUE_NAME = "topic_sms_queue";
    /**
    * 定义交换机的名称
    */
    private static final String EXCHANGE_NAME = "topic_exchange";

    public static void main(String[] args) throws IOException, TimeoutException {
    System.out.println("短信消费者...");
    // 创建我们的连接
    Connection connection = RabbitMQConnection.getConnection();
    // 创建我们通道
    final Channel channel = connection.createChannel();
    // 关联队列消费者关联队列
    channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "meite.*");
    DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    String msg = new String(body, "UTF-8");
    System.out.println("短信消费者获取消息:" + msg);
    }
    };
    // 开始监听消息 自动签收
    channel.basicConsume(QUEUE_NAME, true, defaultConsumer);

    }
    }

     

    RPC模式

    https://blog.csdn.net/hry2015/article/details/79199294

    1. RPC客户端启动后,创建一个匿名、独占的、回调的队列
    2. RPC客户端设置消息的2个属性:replyTo和correlationId,然后将消息发送到队列rpc_queue
    3. RPC服务端在队列rpc_queue上等待消息。RPC服务端处理完收到消息后,然后将处理结果封装成消息发送到replyTo指定的队列上,并且此消息带上correlationId(此值为收到消息里的correlationId)
    4. RPC客户端在队列replyTo上等待消息,当收到消息后,它会判断收到消息的correlationId。如果值和自己之前发送的一样,则这个值就是RPC的处理结果
    

      

     在rabbitmq情况下:RabbitMQ如何保证消息不丢失

    使用消息确认机制+持久技术

    A.消费者确认收到消息机制

    channel.basicConsume(QUEUE_NAME, false, defaultConsumer);

    注:第二个参数值为false代表关闭RabbitMQ的自动应答机制,改为手动应答。

    在处理完消息时,返回应答状态,true表示为自动应答模式。

    channel.basicAck(envelope.getDeliveryTag(), false);

    B.生产者确认投递消息成功 使用Confirm机制 或者事务消息

    Confirm机制 同步或者是异步的形式

    2.RabbitMQ默认创建是持久化的

     

    代码中设置 durable为true

    参数名称详解:

    durable是否持久化 durable为持久化、 Transient 不持久化

    autoDelete 是否自动删除,当最后一个消费者断开连接之后队列是否自动被删除,可以通过RabbitMQ Management,查看某个队列的消费者数量,当consumers = 0时队列就会自动删除

    1. 使用rabbitmq事务消息;
                channel.txSelect();
                channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
    //            int i = 1 / 0;
               
    channel.txCommit();

    相关核心代码

    生产者
    public class Producer {
        private static final String QUEUE_NAME = "mayikt-queue";

        public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
            //1.创建一个新连接
           
    Connection connection = RabbitMQConnection.getConnection();
            //2.设置channel
           
    Channel channel = connection.createChannel();
            //3.发送消息
           
    String msg = "每特教育6666";
    channel.confirmSelect();

            channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
            boolean result = channel.waitForConfirms();
            if (result) {
                System.out.println("消息投递成功");
            } else {
                System.out.println("消息投递失败");
            }
            channel.close();
            connection.close();
        }
    }
    消费者
    public class Consumer {
        private static final String QUEUE_NAME = "mayikt-queue";

        public static void main(String[] args) throws IOException, TimeoutException, IOException, TimeoutException {
            // 1.创建连接
           
    Connection connection = RabbitMQConnection.getConnection();
            // 2.设置通道
           
    Channel channel = connection.createChannel();
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    String msg = new String(body, "UTF-8");
                    System.out.println("消费者获取消息:" + msg);
                    // 消费者完成 消费该消息
                   
    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            };
            // 3.监听队列
           
    channel.basicConsume(QUEUE_NAME, false, defaultConsumer);

        }
    }

    SpringBoot整合RabbitMQ

     

     两个消费者,一个生产者

    Maven依赖

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.mayikt</groupId>
        <artifactId>mayikt-sp-rabbitmq</artifactId>
        <packaging>pom</packaging>
        <version>1.0-SNAPSHOT</version>
        <modules>
            <module>mayikt-producer</module>
            <module>email-consumer</module>
            <module>sms-consumer</module>
        </modules>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.0.RELEASE</version>
        </parent>
        <dependencies>
    
            <!-- springboot-web组件 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!-- 添加springboot对amqp的支持 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-amqp</artifactId>
            </dependency>
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
            </dependency>
            <!--fastjson -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.49</version>
            </dependency>
    
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
        </dependencies>
    </project>

    生产者

     yml

    spring:
      rabbitmq:
        ####连接地址
        host: 127.0.0.1
        ####端口号
        port: 5672
        ####账号
        username: guest
        ####密码
        password: guest
        ###
        virtual-host: /myVirtualHost
    server:
      port: 9092
    package com.mayikt.config;
    
    import org.springframework.amqp.core.Binding;
    import org.springframework.amqp.core.BindingBuilder;
    import org.springframework.amqp.core.FanoutExchange;
    import org.springframework.amqp.core.Queue;
    import org.springframework.context.annotation.Bean;
    import org.springframework.stereotype.Component;
    
    /**
     * @ClassName RabbitMQConfig
     * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
     * @Version V1.0
     **/
    @Component
    public class RabbitMQConfig {
    
        /**
         * 定义交换机
         */
        private String EXCHANGE_SPRINGBOOT_NAME = "/mayikt_ex";
    
    
        /**
         * 短信队列
         */
        private String FANOUT_SMS_QUEUE = "fanout_sms_queue";
        /**
         * 邮件队列
         */
        private String FANOUT_EMAIL_QUEUE = "fanout_email_queue";
    
        // 1.注入队列和交换机注入到spring容器中
        // 2.关联交换机  <bean id="smsQueue" class="";>
    
        /**
         * 邮件和短信队列注入到spring容器中
         *
         * @return
         */
        @Bean
        public Queue smsQueue() {
            return new Queue(FANOUT_SMS_QUEUE);
        }
    
        @Bean
        public Queue emailQueue() {
            return new Queue(FANOUT_EMAIL_QUEUE);
        }
    
        @Bean
        public FanoutExchange fanoutExchange() {
            return new FanoutExchange(EXCHANGE_SPRINGBOOT_NAME);
        }
    
        /**
         * 关联交换机
         * 根据参数名称 ioc获取 Queue对象
         */
        @Bean
        public Binding BindingSmsFanoutExchange(Queue smsQueue, FanoutExchange fanoutExchange) {
            return BindingBuilder.bind(smsQueue).to(fanoutExchange);
        }
    
        @Bean
        public Binding BindingEmailFanoutExchange(Queue emailQueue, FanoutExchange fanoutExchange) {
            return BindingBuilder.bind(emailQueue).to(fanoutExchange);
        }
    }
    package com.mayikt.entity;
    
    import lombok.Data;
    import org.springframework.stereotype.Component;
    
    import java.io.Serializable;
    
    /**
     * @ClassName MsgEntity
     * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
     * @Version V1.0
     **/
    @Data
    public class MsgEntity implements Serializable {
        private String msgId;
        private String userId;
        private String phone;
        private String email;
    
        public MsgEntity(String msgId, String userId, String phone, String email) {
            this.msgId = msgId;
            this.userId = userId;
            this.phone = phone;
            this.email = email;
        }
    }
    package com.mayikt.service;
    
    import com.mayikt.entity.MsgEntity;
    import org.springframework.amqp.core.AmqpTemplate;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.UUID;
    
    /**
     * @ClassName ProducerService
     * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
     * @Version V1.0
     **/
    @RestController
    public class ProducerService {
    
        @Autowired
        private AmqpTemplate amqpTemplate;
    
        @RequestMapping("/sendMsg")
        public void sendMsg() {
            /**
             * 参数1 交换机名称
             * 参数2 路由key
             * 参数3 发送内容
             */
            MsgEntity msgEntity = new MsgEntity(UUID.randomUUID().toString(),
                    "1234", "181111111", "644064779@qq.com");
            amqpTemplate.convertAndSend("/mayikt_ex", "", msgEntity);
        }
    }
    package com.mayikt;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    /**
     * @ClassName AppProducer
     * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
     * @Version V1.0
     **/
    @SpringBootApplication
    public class AppProducer {
        public static void main(String[] args) {
            SpringApplication.run(AppProducer.class);
        }
    }

    spring:
      rabbitmq:
        ####连接地址
        host: 127.0.0.1
        ####端口号
        port: 5672
        ####账号
        username: guest
        ####密码
        password: guest
        ###
        virtual-host: /myVirtualHost
    
    server:
      port: 8081
    package com.mayikt.entity;
    
    import lombok.Data;
    
    import java.io.Serializable;
    
    /**
     * @ClassName MsgEntity
     * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
     * @Version V1.0
     **/
    @Data
    public class MsgEntity implements Serializable {
        private String msgId;
        private String userId;
        private String phone;
        private String email;
    
        public MsgEntity(String msgId, String userId, String phone, String email) {
            this.msgId = msgId;
            this.userId = userId;
            this.phone = phone;
            this.email = email;
        }
    }
    package com.mayikt;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    /**
     * @ClassName AppEmailConsumer
     * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
     * @Version V1.0
     **/
    @SpringBootApplication
    public class AppEmailConsumer {
        public static void main(String[] args) {
            SpringApplication.run(AppEmailConsumer.class);
        }
    }
    package com.mayikt;
    
    import com.mayikt.entity.MsgEntity;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.amqp.rabbit.annotation.RabbitHandler;
    import org.springframework.amqp.rabbit.annotation.RabbitListener;
    import org.springframework.stereotype.Component;
    
    /**
     * @ClassName FanoutSmsConsumer
     * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
     * @Version V1.0
     **/
    @Slf4j
    @Component
    @RabbitListener(queues = "fanout_email_queue")
    public class FanoutEmailConsumer {
    
        @RabbitHandler
        public void process(MsgEntity msgEntity) {
            log.info("email:msgEntity:" + msgEntity);
        }
    }

    spring:
      rabbitmq:
        ####连接地址
        host: 127.0.0.1
        ####端口号
        port: 5672
        ####账号
        username: guest
        ####密码
        password: guest
        ###
        virtual-host: /myVirtualHost
    
    server:
      port: 8082
    package com.mayikt.entity;
    
    import lombok.Data;
    
    import java.io.Serializable;
    
    /**
     * @ClassName MsgEntity
     * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
     * @Version V1.0
     **/
    @Data
    public class MsgEntity implements Serializable {
        private String msgId;
        private String userId;
        private String phone;
        private String email;
    
        public MsgEntity(String msgId, String userId, String phone, String email) {
            this.msgId = msgId;
            this.userId = userId;
            this.phone = phone;
            this.email = email;
        }
    }
    package com.mayikt;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    /**
     * @ClassName AppEmailConsumer
     * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
     * @Version V1.0
     **/
    @SpringBootApplication
    public class AppSMSConsumer {
        public static void main(String[] args) {
            SpringApplication.run(AppSMSConsumer.class);
        }
    }
    package com.mayikt;
    
    import com.mayikt.entity.MsgEntity;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.amqp.rabbit.annotation.RabbitHandler;
    import org.springframework.amqp.rabbit.annotation.RabbitListener;
    import org.springframework.stereotype.Component;
    
    /**
     * @ClassName FanoutSmsConsumer
     * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
     * @Version V1.0
     **/
    @Slf4j
    @Component
    @RabbitListener(queues = "fanout_sms_queue")
    public class FanoutEmailConsumer {
    
        @RabbitHandler
        public void process(MsgEntity msgEntity) {
            log.info("sms:msgEntity:" + msgEntity);
        }
    }

    需要注意的是springboot可以自动生成交换机和队列,无需手动配置。

     生产者如何获取消费结果

    RabbitMQ实战解决方案

     

     下面的代码演示的是生产者生产消息给mq中间件(通过服务的方式),消费者订阅消费,并插入数据库,生产者通过接口和id判断数据库有没有相对应的消息来判断有没有被消费。

     yml

    这里面加入了自定义消费失败重试策略

    spring:
      rabbitmq:
        ####连接地址
        host: 127.0.0.1
        ####端口号
        port: 5672
        ####账号
        username: guest
        ####密码
        password: guest
        ### 地址
        virtual-host: /myVirtualHost
        listener:
          simple:
            retry:
              ####开启消费者(程序出现异常的情况下会)进行重试
              enabled: true
              ####最大重试次数
              max-attempts: 5
              ####重试间隔时间
              initial-interval: 3000
            acknowledge-mode: manual
      datasource:
        url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8
        username: root
        password: root
        driver-class-name: com.mysql.cj.jdbc.Driver
    server:
      port: 9001
    package com.mayikt.config;
    
    
    import org.springframework.amqp.core.Binding;
    import org.springframework.amqp.core.BindingBuilder;
    import org.springframework.amqp.core.FanoutExchange;
    import org.springframework.amqp.core.Queue;
    import org.springframework.context.annotation.Bean;
    import org.springframework.stereotype.Component;
    
    /**
     * @ClassName RabbitMQConfig
     * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
     * @Version V1.0
     **/
    @Component
    public class RabbitMQConfig {
        /**
         * 定义交换机
         */
        private String EXCHANGE_SPRINGBOOT_NAME = "/mayikt_order";
    
    
        /**
         * 订单队列
         */
        private String FANOUT_ORDER_QUEUE = "fanout_order_queue";
    
    
        /**
         * 配置orderQueue
         *
         * @return
         */
        @Bean
        public Queue orderQueue() {
            return new Queue(FANOUT_ORDER_QUEUE);
        }
    
    
        /**
         * 配置fanoutExchange
         *
         * @return
         */
        @Bean
        public FanoutExchange fanoutExchange() {
            return new FanoutExchange(EXCHANGE_SPRINGBOOT_NAME);
        }
    
        // 绑定交换机 orderQueue
        @Bean
        public Binding bindingOrderFanoutExchange(Queue orderQueue, FanoutExchange fanoutExchange) {
            return BindingBuilder.bind(orderQueue).to(fanoutExchange);
        }
    
    }

    消费者处理了重复提交的问题,但是这种方法仍有风险,可以靠唯一id解决。

    int i = 1 / 0;  可以演示失败重试。
    package com.mayikt.consumer;
    
    import com.alibaba.fastjson.JSONObject;
    import com.mayikt.entity.OrderEntity;
    import com.mayikt.manager.OrderManager;
    import com.mayikt.mapper.OrderMapper;
    import com.rabbitmq.client.Channel;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.amqp.core.Message;
    import org.springframework.amqp.rabbit.annotation.RabbitHandler;
    import org.springframework.amqp.rabbit.annotation.RabbitListener;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    import java.io.IOException;
    
    /**
     * @ClassName fanout_sms_queue
     * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
     * @Version V1.0
     **/
    @Slf4j
    @Component
    @RabbitListener(queues = "fanout_order_queue")
    public class FanoutOrderConsumer {
    
        @Autowired
        private OrderManager orderManager;
        @Autowired
        private OrderMapper orderMapper;
    
        @RabbitHandler
        public void process(OrderEntity orderEntity, Message message, Channel channel) throws IOException {
    //        try {
            log.info(">>orderEntity:{}<<", orderEntity.toString());
            String orderId = orderEntity.getOrderId();
            if (StringUtils.isEmpty(orderId)) {
                return;
            }
            OrderEntity dbOrderEntity = orderMapper.getOrder(orderId);
            if (dbOrderEntity != null) {//重试的过程中,为了避免业务逻辑重复执行,建议提前全局id提前查询,如果存在的情况下,就无需再继续做该流程。
    
                log.info("另外消费者已经处理过该业务逻辑");
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
                return;
            }
    //        int i = 1 / 0;
            int result = orderManager.addOrder(orderEntity);
    
            log.info(">>插入数据库中数据成功<<");
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    //        } catch (Exception e) {
    //            // 记录该消息日志形式  存放数据库db中、后期通过定时任务实现消息补偿、人工实现补偿
    //
    //            //将该消息存放到死信队列中,单独写一个死信消费者实现消费。
    //        }
        }
    }
    package com.mayikt.entity;
    
    import lombok.Data;
    
    import java.io.Serializable;
    
    @Data
    public class OrderEntity implements Serializable {
        private int id;
        private String orderName;
        private String orderId;
    
        public OrderEntity(String orderName, String orderId) {
            this.orderName = orderName;
            this.orderId = orderId;
        }
    
        public OrderEntity() {
    
        }
    
    }
    package com.mayikt.manager;
    
    import com.mayikt.entity.OrderEntity;
    import com.mayikt.mapper.OrderMapper;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.transaction.annotation.Transactional;
    
    /**
     * @ClassName OrderManager
     * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
     * @Version V1.0
     **/
    @Component
    public class OrderManager {
        @Autowired
        private OrderMapper orderMapper;
    
        @Transactional
        public int addOrder(OrderEntity orderEntity) {
            return orderMapper.addOrder(orderEntity);
        }
    }
    package com.mayikt.mapper;
    
    import com.mayikt.entity.OrderEntity;
    import org.apache.ibatis.annotations.Insert;
    import org.apache.ibatis.annotations.Select;
    
    public interface OrderMapper {
        @Insert("insert order_info values (null,#{orderName},#{orderId})")
        int addOrder(OrderEntity orderEntity);
    
        @Select("SELECT * from order_info where orderId=#{orderId} ")
        OrderEntity getOrder(String orderId);
    }
    package com.mayikt.producer;
    
    import com.alibaba.fastjson.JSONObject;
    import com.mayikt.entity.OrderEntity;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.amqp.rabbit.core.RabbitTemplate;
    import org.springframework.amqp.rabbit.support.CorrelationData;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    /**
     * @ClassName OrderProducer
     * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
     * @Version V1.0
     **/
    @Component
    @Slf4j
    public class OrderProducer implements RabbitTemplate.ConfirmCallback {
        @Autowired
        private RabbitTemplate rabbitTemplate;
    
        @Override
        public void confirm(CorrelationData correlationData, boolean b, String s) {
            String id = correlationData.getId();
            log.info("id:" + id);
            System.out.println(id);
        }
    
        /**
         * 使用mq发送消息
         *
         * @param orderName
         * @param orderId
         */
        public void sendMsg(String orderName, String orderId) {
            OrderEntity orderEntity = new OrderEntity(orderName, orderId);
            rabbitTemplate.convertAndSend("/mayikt_order", "", orderEntity, message -> {
                return message;
            });
    //        CorrelationData correlationData = new CorrelationData();
    //        correlationData.setId(JSONObject.toJSONString(orderEntity));
    //        rabbitTemplate.convertAndSend("/mayikt_order", "", orderEntity,correlationData);
        }
    
    }
    package com.mayikt.service;
    
    import com.alibaba.fastjson.JSONObject;
    import com.mayikt.entity.OrderEntity;
    import com.mayikt.mapper.OrderMapper;
    import com.mayikt.producer.OrderProducer;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.amqp.AmqpException;
    import org.springframework.amqp.core.Message;
    import org.springframework.amqp.core.MessagePostProcessor;
    import org.springframework.amqp.rabbit.core.RabbitTemplate;
    import org.springframework.amqp.rabbit.support.CorrelationData;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @Slf4j
    @RestController
    public class OrderService {
    
    
    
        @Autowired
        private OrderMapper orderMapper;
        @Autowired
        private OrderProducer orderProducer;
    
        @RequestMapping("/sendOrder")
        public String sendOrder() {
            // 生成全局id
            String orderId = System.currentTimeMillis() + "";
            log.info("orderId:{}", orderId);
            String orderName = "每特教育svip课程报名";
            orderProducer.sendMsg(orderName, orderId);
            return orderId;
        }
    
    
        /**
         * 前端主动根据orderId定时查询
         *
         * @param orderId
         * @return
         */
        @RequestMapping("/getOrder")
        public Object getOrder(String orderId) {
            System.out.println(orderId);
            OrderEntity order = orderMapper.getOrder(orderId);
            if (order == null) {
                return "该订单没有被消费或者订单号错误!";
            }
            return order;
        }
    
    
    }

    RabbitMQ死信队列

    死信队列产生的背景

    RabbitMQ死信队列俗称,备胎队列;消息中间件因为某种原因拒收该消息后,可以转移到死信队列中存放,死信队列也可以有交换机和路由key等。

    产生死信队列的原因

    1. 消息投递到MQ中存放 消息已经过期  消费者没有及时的获取到我们消息,消息如果存放到mq服务器中过期之后,会转移到备胎死信队列存放。
    2. 队列达到最大的长度 (队列容器已经满了)
    3. 3.       消费者消费多次消息失败,就会转移存放到死信队列中

     

    代码整合 参考 mayikt-springboot-rabbitmq|#中order-dead-letter-queue项目

    死信队列的架构原理

    死信队列和普通队列区别不是很大

    普通与死信队列都有自己独立的交换机和路由key、队列和消费者。

    区别:

    1.生产者投递消息先投递到我们普通交换机中,普通交换机在将该消息投到

    普通队列中缓存起来,普通队列对应有自己独立普通消费者。

    2.如果生产者投递消息到普通队列中,普通队列发现该消息一直没有被消费者消费

    的情况下,在这时候会将该消息转移到死信(备胎)交换机中,死信(备胎)交换机

    对应有自己独立的 死信(备胎)队列 对应独立死信(备胎)消费者。

    死信队列应用场景

    1.30分钟订单超时设计

    1. Redis过期key :
    2. 死信延迟队列实现:

    采用死信队列,创建一个普通队列没有对应的消费者消费消息,在30分钟过后

    就会将该消息转移到死信备胎消费者实现消费。

    备胎死信消费者会根据该订单号码查询是否已经支付过,如果没有支付的情况下

    则会开始回滚库存操作。

     

    spring:
      rabbitmq:
        ####连接地址
        host: 127.0.0.1
        ####端口号
        port: 5672
        ####账号
        username: guest
        ####密码
        password: guest
        ### 地址
        virtual-host: /myVirtualHost
    server:
      port: 8080
    
    ###模拟演示死信队列
    mayikt:
      dlx:
        exchange: mayikt_dlx_exchange
        queue: mayikt_order_dlx_queue
        routingKey: dlx
      ###备胎交换机
      order:
        exchange: mayikt_order_exchange
        queue: mayikt_order_queue
        routingKey: mayikt.order
    package com.mayikt;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    /**
     * @ClassName AppDeadLetter
     * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
     * @Version V1.0
     **/
    @SpringBootApplication
    public class AppDeadLetter {
        public static void main(String[] args) {
            SpringApplication.run(AppDeadLetter.class);
        }
    }
    package com.mayikt.producer;
    
    import org.springframework.amqp.AmqpException;
    import org.springframework.amqp.core.Message;
    import org.springframework.amqp.core.MessagePostProcessor;
    import org.springframework.amqp.rabbit.core.RabbitTemplate;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @ClassName OrderProducer
     * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
     * @Version V1.0
     **/
    @RestController
    public class OrderProducer {
        @Autowired
        private RabbitTemplate rabbitTemplate;
        /**
         * 订单交换机
         */
        @Value("${mayikt.order.exchange}")
        private String orderExchange;
        /**
         * 订单路由key
         */
        @Value("${mayikt.order.routingKey}")
        private String orderRoutingKey;
    
        @RequestMapping("/sendOrder")
        public String sendOrder() {
            String msg = "每特教育牛逼";
            rabbitTemplate.convertAndSend(orderExchange, orderRoutingKey, msg, message -> {
                // 设置消息过期时间 10秒过期
                message.getMessageProperties().setExpiration("10000");
                return message;
            });
            return "success";
        }
    }
    package com.mayikt.consumer;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.amqp.rabbit.annotation.RabbitListener;
    import org.springframework.stereotype.Component;
    
    @Slf4j
    @Component
    public class OrderDlxConsumer {
    
        /**
         * 死信队列监听队列回调的方法
         *
         * @param msg
         */
        @RabbitListener(queues = "mayikt_order_dlx_queue")
        public void orderConsumer(String msg) {
            log.info(">死信队列消费订单消息:msg{}<<", msg);
        }
    }

    注释掉模式消费失败

    //package com.mayikt.consumer;
    //
    //import lombok.extern.slf4j.Slf4j;
    //import org.springframework.amqp.rabbit.annotation.RabbitListener;
    //import org.springframework.stereotype.Component;
    //
    ///**
    // * 订单消费者
    // */
    //@Component
    //@Slf4j
    //public class OrderConsumer {
    //
    //    /**
    //     * 监听队列回调的方法
    //     *
    //     * @param msg
    //     */
    //    @RabbitListener(queues = "mayikt_order_queue")
    //    public void orderConsumer(String msg) {
    //        log.info(">>正常订单消费者消息MSG:{}<<", msg);
    //    }
    //}
    package com.mayikt.config;
    
    import org.springframework.amqp.core.Binding;
    import org.springframework.amqp.core.BindingBuilder;
    import org.springframework.amqp.core.DirectExchange;
    import org.springframework.amqp.core.Queue;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.stereotype.Component;
    
    import java.util.HashMap;
    import java.util.Map;
    
    @Component
    public class DeadLetterMQConfig {
        /**
         * 订单交换机
         */
        @Value("${mayikt.order.exchange}")
        private String orderExchange;
    
        /**
         * 订单队列
         */
        @Value("${mayikt.order.queue}")
        private String orderQueue;
    
        /**
         * 订单路由key
         */
        @Value("${mayikt.order.routingKey}")
        private String orderRoutingKey;
        /**
         * 死信交换机
         */
        @Value("${mayikt.dlx.exchange}")
        private String dlxExchange;
    
        /**
         * 死信队列
         */
        @Value("${mayikt.dlx.queue}")
        private String dlxQueue;
        /**
         * 死信路由
         */
        @Value("${mayikt.dlx.routingKey}")
        private String dlxRoutingKey;
    
        /**
         * 声明死信交换机
         *
         * @return DirectExchange
         */
        @Bean
        public DirectExchange dlxExchange() {
            return new DirectExchange(dlxExchange);
        }
    
        /**
         * 声明死信队列
         *
         * @return Queue
         */
        @Bean
        public Queue dlxQueue() {
            return new Queue(dlxQueue);
        }
    
        /**
         * 声明订单业务交换机
         *
         * @return DirectExchange
         */
        @Bean
        public DirectExchange orderExchange() {
            return new DirectExchange(orderExchange);
        }
    
        /**
         * 声明订单队列
         *
         * @return Queue
         */
        @Bean
        public Queue orderQueue() {
            // 订单队列绑定我们的死信交换机
            Map<String, Object> arguments = new HashMap<>(2);
            arguments.put("x-dead-letter-exchange", dlxExchange);
            arguments.put("x-dead-letter-routing-key", dlxRoutingKey);
            return new Queue(orderQueue, true, false, false, arguments);
        }
    
        /**
         * 绑定死信队列到死信交换机
         *
         * @return Binding
         */
        @Bean
        public Binding binding() {
            return BindingBuilder.bind(dlxQueue())
                    .to(dlxExchange())
                    .with(dlxRoutingKey);
        }
    
    
        /**
         * 绑定订单队列到订单交换机
         *
         * @return Binding
         */
        @Bean
        public Binding orderBinding() {
            return BindingBuilder.bind(orderQueue())
                    .to(orderExchange())
                    .with(orderRoutingKey);
        }
    }

    下面的代码是使用springboot演示订阅模式实现异步发送邮件和短信,当然生产者和消费者进行拆开成微服务。

    微服务形式

     

    spring:
      rabbitmq:
        ####连接地址
        host: 127.0.0.1
        ####端口号
        port: 5672
        ####账号
        username: guest
        ####密码
        password: guest
        ### 地址
        virtual-host: /myVirtualHost
    package com.mayikt;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    /**
     * @ClassName App
     * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
     * @Version V1.0
     **/
    @SpringBootApplication
    public class App {
        public static void main(String[] args) {
            SpringApplication.run(App.class);
        }
    }
    package com.mayikt.producer;
    
    import org.springframework.amqp.core.AmqpTemplate;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @ClassName FanoutProducer
     * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
     * @Version V1.0
     **/
    @RestController
    public class FanoutProducer {
    
        @Autowired
        private AmqpTemplate amqpTemplate;
    
        /**
         * 发送消息
         *
         * @return
         */
        @RequestMapping("/sendMsg")
        public String sendMsg(String msg) {
            /**
             * 1.交换机名称
             * 2.路由key名称
             * 3.发送内容
             */
            amqpTemplate.convertAndSend("/mayikt_ex", "", msg);
            return "success";
        }
    }
    package com.mayikt.consumer;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.amqp.rabbit.annotation.RabbitHandler;
    import org.springframework.amqp.rabbit.annotation.RabbitListener;
    import org.springframework.stereotype.Component;
    
    /**
     * @ClassName fanout_sms_queue
     * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
     * @Version V1.0
     **/
    @Slf4j
    @Component
    @RabbitListener(queues = "fanout_sms_queue")
    public class FanoutSmsConsumer {
    
        @RabbitHandler
        public void process(String msg) {
            log.info(">>短信消费者消息msg:{}<<", msg);
        }
    }
    package com.mayikt.consumer;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.amqp.rabbit.annotation.RabbitHandler;
    import org.springframework.amqp.rabbit.annotation.RabbitListener;
    import org.springframework.stereotype.Component;
    
    /**
     * @ClassName FanoutEmailConsumer
     * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
     * @Version V1.0
     **/
    @Slf4j
    @Component
    @RabbitListener(queues = "fanout_email_queue")
    public class FanoutEmailConsumer {
    
        @RabbitHandler
        public void process(String msg) {
            log.info(">>邮件消费者消息msg:{}<<", msg);
        }
    }
    package com.mayikt.config;
    
    
    import org.springframework.amqp.core.Binding;
    import org.springframework.amqp.core.BindingBuilder;
    import org.springframework.amqp.core.FanoutExchange;
    import org.springframework.amqp.core.Queue;
    import org.springframework.context.annotation.Bean;
    import org.springframework.stereotype.Component;
    
    /**
     * @ClassName RabbitMQConfig
     * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
     * @Version V1.0
     **/
    @Component
    public class RabbitMQConfig {
        /**
         * 定义交换机
         */
        private String EXCHANGE_SPRINGBOOT_NAME = "/mayikt_ex";
    
    
        /**
         * 短信队列
         */
        private String FANOUT_SMS_QUEUE = "fanout_sms_queue";
        /**
         * 邮件队列
         */
        private String FANOUT_EMAIL_QUEUE = "fanout_email_queue";
    
        /**
         * 配置smsQueue
         *
         * @return
         */
        @Bean
        public Queue smsQueue() {
            return new Queue(FANOUT_SMS_QUEUE);
        }
    
        /**
         * 配置emailQueue
         *
         * @return
         */
        @Bean
        public Queue emailQueue() {
            return new Queue(FANOUT_EMAIL_QUEUE);
        }
    
        /**
         * 配置fanoutExchange
         *
         * @return
         */
        @Bean
        public FanoutExchange fanoutExchange() {
            return new FanoutExchange(EXCHANGE_SPRINGBOOT_NAME);
        }
    
        // 绑定交换机 sms
        @Bean
        public Binding bindingSmsFanoutExchange(Queue smsQueue, FanoutExchange fanoutExchange) {
            return BindingBuilder.bind(smsQueue).to(fanoutExchange);
        }
    
        // 绑定交换机 email
        @Bean
        public Binding bindingEmailFanoutExchange(Queue emailQueue, FanoutExchange fanoutExchange) {
            return BindingBuilder.bind(emailQueue).to(fanoutExchange);
        }
    }

    RabbitMQ消息幂等问题

    RabbitMQ消息自动重试机制

    1. 当我们消费者处理执行我们业务代码的时候,如果抛出异常的情况下

    在这时候mq会自动触发重试机制,默认的情况下rabbitmq是无限次数的重试。

    需要人为指定重试次数限制问题

    1. 在什么情况下消费者需要实现重试策略?

    A.消费者获取消息后,调用第三方接口,但是调用第三方接口失败呢?是否需要重试?

      该情况下需要实现重试策略,网络延迟只是暂时调用不通,重试多次有可能会调用通。

    B.消费者获取消息后,因为代码问题抛出数据异常,是否需要重试?

      该情况下是不需要实现重试策略,就算重试多次,最终还是失败的。可以将日志存放起来,后期通过定时任务或者人工补偿形式。如果是重试多次还是失败消息,需要重新发布消费者版本实现消费。

    可以使用死信队列

    Mq在重试的过程中,有可能会引发消费者重复消费的问题。

    Mq消费者需要解决 幂等性问题

    幂等性 保证数据唯一

    方式1:

    生产者在投递消息的时候,生成一个全局唯一id,放在我们消息中。

    Msg id=123456

    Msg id=123456

    Msg id=123456

    消费者获取到我们该消息,可以根据该全局唯一id实现去重复。

    全局唯一id 根据业务来定的  订单号码作为全局的id

    实际上还是需要再db层面解决数据防重复。

    业务逻辑是在做insert操作 使用唯一主键约束

    业务逻辑是在做update操作 使用乐观锁

    1. 当消费者业务逻辑代码中,抛出异常自动实现重试 (默认是无数次重试)
    2. 应该对RabbitMQ重试次数实现限制,比如最多重试5次,每次间隔3s;重试多次还是失败的情况下,存放到死信队列或者存放到数据库表中记录后期人工补偿
    3. 消费者获取消息后,调用第三方接口,但是调用第三方接口失败呢?是否需要重试 ?
    4. 消费者获取消息后,应该代码问题抛出数据异常,是否需要重试?

    如何合理选择消息重试

    总结:如果消费者处理消息时,因为代码原因抛出异常是需要从新发布版本才能解决的,那么就不需要重试,重试也解决不了该问题的。存放到死信队列或者是数据库表记录、后期人工实现补偿。

     详细的策略解决幂等性和保证幂等性的方法

    https://www.cnblogs.com/javalyy/p/8882144.html

     

     

     

     

     

     

     

     

    作者:你的雷哥
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    在Ubuntu下编译WebKit源码--qt
    Ubuntu 编译Webkit --gtk
    windows远程桌面访问ubuntu12.04
    CentOS下SVN服务器的搭建使用
    Centos搭建SVN服务器三步曲
    StringRedisTemplate常用操作
    MySQL中DATETIME、DATE和TIMESTAMP类型的区别
    mysql 时间索引执行计划
    MySQL大文本类型
    API网关原理
  • 原文地址:https://www.cnblogs.com/henuliulei/p/14944572.html
Copyright © 2020-2023  润新知