Rabbitmq

乌班图安装

win安装

先安装erlang

1:先去erlang官网安装下载exe:
image
2:一直下一步(如提示安装c++后重启电脑,则重新双击下载的exe继续下一步)erlang的安装目录不能有空格
检查是否安装成功:
image

再安装rabbitmq服务

1:双击exe文件,下一步即可(安装在c盘不用配置环境变量)
2:检查是否已启好服务:
image

安装管理页面服务

找到sbin目录:C:\Program Files\RabbitMQ Server\rabbitmq_server-3.11.15\sbin,执行cmd命令:

rabbitmq-plugins enable rabbitmq_management

提示重启后生效:
image
找到服务,重启下:
image
== 还是无法访问==
重启电脑再登录:http://127.0.0.1:15672/ 本机默认 guest,guest

docker 安装erlang

离线安装(不推荐)

此方式可能安装的不是最新版本
1:下载tar地址:http://erlang.org/download/otp_src_20.3.tar.gz
2.解压:tar -xvf otp_src_20.3.tar.gz
3:准备环境:

yum -y install make gcc gcc-c++ kernel-devel m4 ncurses-devel openssl-devel

4:进入目录
cd otp_src_20.3
5:设置安装规则:
./configure --prefix=/usr/local/erlang --with-ssl --enable-threads --enable-smp-support --enable-kernel-poll --enable-hipe --without-javac
6:安装:
make && make install
7:环境变量
vim /etc/profile
添加配置:

set erlang environment

ERL_PATH=/usr/local/erlang/bin
PATH=\(ERL_PATH:\)PATH

配置生效:
source /etc/profile

检查是否成功:
erl
退出:halt().

在线安装(推荐)

亲测成功
可安装最新
1:下载rpm

wget https://packages.erlang-solutions.com/erlang-solutions-1.0-1.noarch.rpm

2:

yum -y install epel-release

3:下载erlang

rpm -Uvh erlang-solutions-1.0-1.noarch.rpm

4: 安装

sudo yum install erlang

跟着界面提示,yes下一步
验证:erl
查看安装路径: whereis erlang
结束命令:
image

docker安装rabbit

启动命令

guest账号只能用于localhost访问

== 要下带管理页面的!!==
// 账号带中划线可能无法登录
// 第一次下载镜像,并初始化用户名:user,密码:password
// rabbitmq:3-management(rabbitmq:最新版本,3-management:浏览器管理页面插件)
docker run -d --hostname my-rabbit --name one-rabbit -e RABBITMQ_DEFAULT_USER=user -e RABBITMQ_DEFAULT_PASS=password -p 15672:15672 -p 5672:5672
-v /opt/rabbitmq/data:/var/lib/rabbitmq
rabbitmq:3-management

// rabbitmq:3-management 是镜像名
// --name one-rabbit 是容器名
// -v 在把容器数据挂载到宿主机

检查是否安装插件:

  • 进入容器: docker exec -it 容器id bash

  • 查看插件列表: rabbitmq-plugins list
    image
    image

  • 启动插件命令:rabbitmq-plugins enable rabbitmq_management

详情链接

https://blog.csdn.net/a745233700/article/details/115060109

管理页面详解

https://blog.csdn.net/qq_27409289/article/details/89510687?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~Rate-1-89510687-blog-123989426.pc_relevant_aa&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~Rate-1-89510687-blog-123989426.pc_relevant_aa&utm_relevant_index=1

什么是消息队列

就是个消息中间件,可以简单理解为类似DB的,也有server端,客户端可以放数据,可以读取数据。

MQ的优点

1:削峰限流,瞬时高并发可将请求写到mq,消费端慢慢处理队列中的请求。
2:解耦,生产者只管发送消息,消费者只管找mq要消息,无代码侵入性。
3:异步,主流程只需完成核心代码,非核心功能,放入队列异步处理,减少等待执行时间,提高系统性能。

mq的缺点

1:系统复杂度提高了,加入mq,需要考虑:消息重复消费、消息丢失、消息顺序性,mq服务器的高可用等问题。
2:系统的可用性降低了,系统引用的外部依赖越多,越容易挂掉,mq服务器挂了,可能导致系统崩溃。
3: 数据一致性问题。

各类mq对比

没有绝对的好坏,项目应根据业务场景,扬长避短。

Kafka RocketMQ RabbitMQ ActiveMQ
贡献社区 apache 阿里 Pivotal apache
语言 scala java erlang java
吞吐量 10w+ 10w+ w+ w+
时效性 ms级以内 ms级 us,最低 ms级
高可用 高,基于主从 高,基于主从 非常高,分布式架构 非常高,分布式,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用
消息可靠性 通过参数化配置,可做到不丢失 通过参数化配置,可做到不丢失 基本不丢 有较低概率丢失
优点 超高的吞吐量,极高的可用性、可靠性,分布式支持任意扩展 1:性能、吞吐量接受过阿里电商业务验证;
2:可靠性和可用性不错,分布式扩展方便;
3:java开发,方便定制化修改,支持复杂mq业务场景
erlang语法开发,并发能力强,性能极好,延时低,管理界面好,社区活跃 功能成熟,业内大量公司项目使用
缺点 1:功能较少;
2:有可能消息重复消费
1:社区活跃一般,阿里贡献的,有可能被抛弃;
2:接口不是标准的JMS规范,有些系统迁移需修改大量代码
1:吞吐量稍弱
2:基于erlang开发,国内缺乏对erlang源码级别的研究和定制能力,依赖于开源社区维护和修复bug
1:有可能丢消息
2:社区维护度降低
特点总结 大数据领域的行业规范实时计算及日志收集 功能较完善,分布式扩展性好 erlang开发,并发能力强,性能好,延时低,管理界面丰富 主要用于解耦、异步,较少在在规模吞吐的场景中使用

RabbitMQ的概念

rabbitMQ基于AMQP协议的开源实现;

  • 生产者 Publisher: 生产推送消息者,消息一般包含2部分:消息体(payload)和标签(Label)。
  • 消费者 Consumer: 接受消息方,消费者订阅到队列上,消费消息时只消费消息体,丢弃标签。
  • 服务节点 Broker: MQ服务器实体,一般一个Broker可看作个RabbitMQ服务器。
  • 消息队列 Queue: 用于存放消息,一个消息可投入一个或多个队列,多个消费者可订阅同一队列,这时队列中的消息会被平摊(轮询)给多个消费者进行处理。
  • 交换机 Exchange:接受生产者发送的消息,根据路由键将消息路由到绑定的队列上。常见有3种类型:
    • fanout: 广播,消息交给所有绑定了该交换机的队列。
    • direct: 定向,把消息交给符合指定routing key的队列。
    • topic: 通配符,把消息交给匹配的路由队列。
    • headers:参数匹配。

== 交换机== 只负责转发消息,不具备存储能力,如果没有任何队列与Exchange绑定,或者没有符合路由的队列,则消息会丢失!

  • 路由关键字 Routing key: 指定这个消息的路由规则,需要与交换器类型和绑定建联合使用才能生效。
  • 绑定 Binding: 通过绑定将交换机和队列关联起来,一般会指定一个BindingKey,通过BindingKey,可知道将消息路由到哪个队列。
  • 网络连接 Connection: 如一个TCP连接,用于连接到具体broker.
  • 信道 Channel: AMQP命令都是在信道中进行,不论发布消息、订阅队列、接受消息,都是通过信道完成。因建立和销毁TCP非常昂贵的开销,所以引入信道概念,复用一条TCP连接,一个TCP连接可以用多个信道,客户端可建立多个channel,每个channel表示一个会话任务。
  • 消息 Message:由消息头和消息体组成。消息体不透明,消息头由一系列可选属性组成(routing-key路由键、priority(消息优先级)、delivery-mode(指明消息需要持久性存储))。
  • 虚拟主机 Virtual host: 用于逻辑隔离,表示一批独立的交换机、队列和相关对象。一个Virtual host可以有若干个Exchange和Queue,同一个Virtual host下的Exchange或Queue不可重名。最重要,拥有独立的权限系统,可做到vhost 范围的用户控制。当然,从RabbitMq的全局角度,vhost可作为不同权限隔离手段。

6种工作模式

  • 1: hello
    basicPublish方法,
    无交换机(本质是默认交换机);路由和队列名相同;

  • 2:Workqueques
    消费者之间是竞争关系。同一条消息,只会被一个消费者消费。多个消费者是轮询消费。

  • 3:发布订阅 pub/sub
    一条消息会发送给订阅了的所有消费者。
    需要确定交换机(X),交换机类型选择扇区fanout,路由K为空川“”;chl.queueBind(queque1, EXCHANGE_NAME, "");

  • 4: Rounting路由模式
    交换机类型为:DIRECT("direct")枚举。

    • 队列和交换机不再是任意绑定,而是需要指定一个routingKey;
      消息发送方向Exchange发消息时,也必须指定消息的RoutingKey;
      Exchange不再把消息交给每一个绑定的队列,而是根据消息的Routing Key进行判断,只有队列的Routing Key和消息的Routing Key一致,才能接受到消息。
  • 5: 通配符模式

小结:topic模式,可以实现Pub/Sub发布订阅模式,Routing路由模式。

交换机类型为:TOPIC("topic")枚举。
* 任意一个单词,#表示0个或多个单词。
java client 交换机绑定没有的队列,不会自动创建,会报错:ShutdownSignalException: reply-text=NOT_FOUND - no queue 'topicQueue2' in vhost '/'

如何保证消息不被重复消费

发生场景:
消费者消费了消息后,发送一个ACK确认消息,由于网络抖动,确认消息没传到队列,队列不知道消息已经被消费了,就又发送消息给其他消费者,出现重复消息。
解决思路:

保证消息幂等性,使得一次操作和多次操作产生的影响相同。

1:用乐观锁思想,对sql添加条件判断,表添加version字段。
2:对某个字段添加唯一约束
3:消息对应一条全局唯一id,用redis缓存起来,消费前先存到redis,其他消费者消费前发现有了,则不做处理。

ttl过期时间

2种都设置了,会以最短的执行
队列为100秒,某条消息A为3秒,A消息会先过期,100秒后,队列所有消息一起过期。
队列为3秒,某条消息A为100秒,3秒后,队列所有都过期丢失。
队列没有设置过期时间,给某条消息设置了过期时间
rabbitmq只会在,该消息在对头时才去判断该消息是否已经过期(避免查询所有队列,提高性能效率),意思该消息虽然过期了,如不在队头,不会被马上移除。

命令:x-message-ttl
单位:毫秒

  • 过期类型
    1:队列统一过期
    rabbitTemplate.convertAndSend("交换机名","路由k",消息对象);
    2:消息单独过期
    new MessagePostProcesser() {

    @Override
    public Message postProcessMessage(Message msg)throws AmqpException {
    // 单独设置某条消息的过期时间(单位:毫秒,字符类型)
    msg.getMessageProperties().setExpiration("5000");
    }
    }

rabbitTemplate.convertAndSend("交换机名","路由k",消息对象, new MessagePostProcesser匿名内部类);

死信队列

消息过期,被丢弃时如果绑定了一个交换机(DLX),则消息不会真正的丢失,会重新发送到另一个交换机,再被其它队列消费。

3种情况会成为死信。
1:队列长度达到限制后,后面生产者发送的消息。
2:消费者拒绝消费的消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;
3:原队列过期未被消费的消息。

队列绑定死信交换机

设置参数:x-dead-letter-exchange和 x-dead-letter-routing-key

如何保证消息不丢

3种场景:发送者发给队列没收到、队列发给消费者丢了、消费者丢消息。

生产者丢消息

1:事务机制:

 发送前,开启事务,发送,发送种遇到异常,回滚,成功则提交事务。
缺点:
  会阻塞等待接收队列返回结果,期间无法发送其他消息,导致吞吐量降低。

点击查看代码
// 开启事务
channel.txSelect
try{
	// 发送数据
}catch(Exception e) {
	channel.txRollback;
}
// 提交事务
channel.txCommit;

2: 确认机制

两种方式

  • confirm 确认模式
  • return 退回模式

消息的投递路径:
producer -->broker --> exchange --> queue --> consumer.

  • 消息从producer到exchange级,不管exchange是否收到,confirmCallback都会执行,通过参数布尔值表示。
  • 消息从producer到queue投递失败,才会触发returnCallback.

2-1 confirm模式

== 需要开启配置==
spring.rabbitmq.publisher-confirms=true

  常用的是:confirm模式,生产者将信道channel设置为confirm模式,一旦channel进入confirm模式,所有在该信道的消息都将会被指派一个唯一id,一旦消息被投递到所有匹配的队列后,rabbitMQ会发送个确认给生产者(包含消息的唯一ID),生产者就知道消息已经发送成功。如RabbitMQ没有处理该消息,也会发送一个Nack回来,生产者可进行重新发送。
  Confirm模式是异步的,一旦发布消息,生产者可在等信道返回确认时,发送下一条消息,消息得到确认后,可通过回调方法处理。

点击查看代码
channel.addConfirmListener(new ConfirmListener(){
@Override
public void handleNack(long deliveryTag, boolean multiple)throws IOException {
	// 接受到发送给队列失败的回调
}

@Override
public void handleAck(long deliveryTag,boolean multiple)throws IOException {
	// 接受到成功发送给队列的回调
}
});

队列丢消息

一般方案是开启持久化磁盘,持久化配置可以和生产者的confirm机制配合,在消息持久化到磁盘后,再给生产者发送个ack信号,如果消息持久化磁盘之前,即使RabbitMQ挂了,生产者未收到ack信号,会再次重发。

持久化配置需同时设置2个:
1:创建queue时,将queue的持久化标志durable设置为true,代表是一个持久队列,可保证rabbitmq持久化queue的元数据,但是不会持久化queue里的数据。
2:发送消息时将deliveryMode设为2,将消息设置为持久化,此时rabbitmq会将消息持久化到磁盘。

设置好后,Rabbitmq挂了,重启后也能恢复数据。消息还没持久化到硬盘,可能服务已经挂了,这种情况可引入镜像队列,但也不能保证消息一定不会丢失(整个集群挂掉)。

消费者丢消息

原因:
一般是因为采用自动确认模式,该模式下,消息一接受,自动发送一个确认,通知RabbitMQ接收到消息了,RabbitMQ会将消息删除,这种情况下,如消费者遇到异常,消息未正常处理成功,就出现了消息丢失。

解决方案:
 改为手动确认,设置autoAck=False,等消息被真正消费后,再手动发送一个确认信号,这样中途消息没处理完,消费者系统宕机了,队列没收到ack确认信号,mq就会将这条消息重新分配给其他的消费者处理。
  但是Rabbitmq没有超时机制,Rabbitmq仅通过与消费者的连接确认是否需要重新发送,只要连接不中断,mq会给消费者足够长的时间处理消息。

采用手动确认消息方式,需要考虑以下几种情况:
1) 消费者接收到消息,确认前断开了连接,mq会认为消息没被消费,重新分发给下一位订阅的消费者,会出现消息重复消费的隐患。
2) 消费者接收到消息没确认消息,连接也未断开,mq认为消费者繁忙,不会给消费者分发更多的消息。

注意点:

  1. 消息可靠性增强会导致性能下降,写磁盘比写内存慢很多,所以是否要对消息持久化,需要结合业务,性能需求,可能遇到的问题综合考虑。可考虑用ssd硬盘,或对关键消息持久化,提高性能瓶颈。

    2.设置autoAck=false时,如忘记手动ack,会导致大量任务处于Unacked状态,造成队列堆积,直到消费者断开才会重新回到队列。解决方法:及时ack,确保异常时ack或者拒绝消息。

    3.启用消息拒绝或发送nack后导致死循环:如果在消息处理异常时,直接拒绝消息,消息会重新进入队列,这时消息再次被处理又发生异常又被拒绝,造成死循环。

如何保证消息的有序性

解决方案
保证生产者入队的顺序有序,出队的消费顺序由消费者保证。
1:拆分queue,使一个queue只对应一个消费者,mq一般可保证内部队列先进先出,所以需保持先后顺序的一组消息使用某种算法分配到同一个消息队列中。只用一个消费者单线程消费队列,保证消费者按顺序消费,但消费者的吞吐量会出现瓶颈,多个消费者同时消费一个队列时,还会出现顺序错乱,类似于多线程消费。针对多个消费者消费同一个队列,可增加业务校验:电调停送电签认,消费者执行送电签认前,检查供电臂是否作业组闭锁状态已解除,未解除,则放弃处理该消息,睡眠一会儿后再消费消息,再做校验。

mq里面堆积了几千万条消息

原因:
1:一切正常,生产消息多快,消费者处理不过来。
2:消费者挂了,无法正常消费消息。
3:消费者处理逻辑有bug,一直消费失败,不断重试。

posted @ 2022-10-11 19:42  jf666new  阅读(45)  评论(0)    收藏  举报