RabbitMQ channel 频道,exchange 交换机和 queue队列
connection是指物理的连接,一个client与一个server之间有一个连接;
一个连接上可以建立多个channel,可以理解为逻辑上的连接。
一般应用的情况下,有一个channel就够用了,不需要创建更多的channel??
( Channel
起了什么作用,它实质上是屏蔽了 Connection
的细节,让开发者不用去管 TCP 层面上的事儿,同时基于 NIO 可以使得 Connection
的 TCP 能够被复用,减少了 TCP 连接建立的开销。 1 channel per thread 因为 Channel
本身不是线程安全的, 1 consumer per channel.是因为如果一个 Consumer 在一个 Channel
中正在监听某一个 Queue
的消息,那么这个 Consumer 是不能在这个 Channel
中同时去处理另一个 Queue
的,出于消费速度的考虑所以需要开辟多个 Channel
,如果你本身没那么大消息吞吐量,也可以共用一个 Channel
;而 Producer 是不存在这个问题的。)
为了将不同类型的消息进行区分,设置了交换机与路由两个概念。
比如,将A类型的消息发送到名为‘C1’的交换机,将类型为B的发送到'C2'的交换机。当客户端连接C1处理队列消息时,取到的就只是A类型消息。
发送消息时,只要有“交换机”就够了。
至于交换机后面有没有对应的处理队列,发送方是不用管的。routingkey可以是空的字符串(表示发送到所有的队列中)。在示例中,我使用了两个key交替发送消息,是为了下面更便于理解routingkey的作用。
交换机设置参数中有两个重要的概念:
A,类型。有三种类型: Fanout类型最简单,这种模型忽略routingkey;Direct类型是使用最多的,使用确定的routingkey。这种模型下,接收消息时绑定'key_1'则只接收key_1的消息;最后一种是Topic,这种模式与Direct类似,但是支持通配符进行匹配,比如: 'key_*',就会接受key_1和key_2。Topic貌似美好,但是有可能导致不严谨,所以还是推荐使用Direct。
B,持久化。指定了持久化的交换机,在重新启动时才能重建,否则需要客户端重新声明生成才行。
需要特别明确的概念:交换机的持久化,并不等于消息的持久化。只有在持久化队列中的消息,才能持久化;如果没有队列,消息是没有地方存储的;消息本身
queue: 队列
队列仅是针对接收方(consumer)的,由接收方根据需求创建的。只有队列创建了,交换机才会将新接受到的消息送到队列中,交换机是不会在队列创建之前的消息放进来的。即在建立队列之前,发出的所有消息都被丢弃了。
net客户端代码,发送接收
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | class RabbitMQClient { public static IConnection GetConnection( string hostName, string userName, string password) { ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.HostName = hostName; connectionFactory.UserName = userName; connectionFactory.Password = password; connectionFactory.Port = 5672; return connectionFactory.CreateConnection(); } public static void Send( string queue, string data) { using (IConnection connection = GetConnection( "localhost" , "guest" , "guest" )) { // //这里Rabbit的玩法就是一个通道channel下包含多个队列Queue using (IModel channel = connection.CreateModel()) { channel.QueueDeclare(queue, false , false , false , null ); channel.BasicPublish( string .Empty, queue, null , Encoding.UTF8.GetBytes(data)); } } } public static void Receive( string queue) { using (IConnection connection = GetConnection( "localhost" , "guest" , "guest" )) { using (IModel channel = connection.CreateModel()) { channel.QueueDeclare(queue, false , false , false , null ); var consumer = new EventingBasicConsumer(channel); BasicGetResult result = channel.BasicGet(queue, true ); if (result != null ) { string data = Encoding.UTF8.GetString(result.Body.ToArray()); Console.WriteLine(data); } } } } } |
假如发送/接送方单线程,每次都连接,收发,断开, 每个动作30ms, 在MQ的管理界面看到收发的Message Rate最大是36 /s, 跟这个时间对得上
假如开多个线程,同时打开10个以上连接,会出现下面的异常, RabbitMQ.Client.Exceptions.BrokerUnreachableException:“None of the specified endpoints were reachable”
所以应该采用单例模式,生产者,消费者都只打开一个连接. 这样改造之后Message Rate 可以达到300/s
把线程数加到40, using (IModel channel = _conn.CreateModel()) //引发的异常:“System.TimeoutException”
再改成每个线程用单独一个Channel来发,Message Rate 可以达到850/s
RabbitMQClient mQClient = new RabbitMQClient("localhost"); //1个线程Msg Rate = 125/s, 10个线程300 /s,20个线程400/s,40个线程850/s for (int k = 0; k < 40; k++) { Task.Run(() => { IModel Channel = mQClient.GetChannel(); //每个线程建立一个通道 for (int i = 0; i < 1000; i++) { mQClient.Send("IDG", "Hello World!" + i,Channel); mQClient.Receive("IDG", Channel); } Channel.Close(); }); } Console.ReadLine();
开源软件成熟度评测报告-分布式消息中间件_对比Kafka和RabbitMQ
https://www.confluent.io/blog/kafka-fastest-messaging-system/#throughput-results 这篇文章说kafka是RabbitMQ的吞吐量的15倍, 但在吞吐量30MB/s 以下, RabbitMQ的反应时间是最好的,只有1ms, 假如每条msg是1K,就是每秒3万条消息
在我的Mac Air测试, 启用持久化队列和持久化消息, 不开启publish Confirm, 单线程发1000条信息 发完1000条,再接收完用时3.74秒; 假如开启publish confirm ,,用同步方法确认,会影响吞吐量,要30秒左右,差8倍啊!!!,可以改用异步确认的方法 RabbitMQ tutorial - Reliable Publishing with Publisher Confirms
Task GetChannel =0.21s
改成2个线程对应2个Channel,每个通道发500条,用时2.52秒; (我的CPU是双核的,2个线程是最快的,多了线程反而多了切换的时间)
改成3个线程对应3个Channel,每个通道发333条,用时3.3秒;
改成5个线程对应5个Channel,每个通道发200条,用时5.0秒; 其中
task GetChannel =1.87s (5线程建立连接最耗时) task QueueDeclare =0.02s task BasicGet =10ms/次 task BasicPublish=1ms/次 订阅消息的速度是发送消息的1/10到1/20, 慢太多了.
关闭掉持久化消息,速度反而变慢了.
在另一台I7(8core,16G)的机器, 单线程发收1000条用时1.92秒,5个线程对应5个Channel,每个通道发200条,用时1.45秒, 4个线程用时1.35最快
用1000条消息数量太少了,测试不有代表性, 改用1w条消息,多线程比单线程快一点,但不明显。但Push比Pull快一倍,这个是什么原因呢?
(不要在循环中使用拉模式来模拟推模式,因为拉模式每次都需要去连接Channel 中拉取消息来消费,
不管有没有消息,都要查一遍。所以会严重影响RabbitMQ性能)
//1 threads send 1w msgs use 3.76 sec
//1 threads use pull 1w msgs use 7.5 sec
//1 threads use push 1w msgs use 3 sec
//4 threads send 1w msgs use 3.5 sec
//4 threads use pull 1w msgs use 4.2 ~ 4.9 sec
//4 threads use push 1w msgs use 2.7 sec
//4 threads use push 2w msgs use 3.2 sec
//4 threads use push 10w msgs use 6.7 sec (在消费速度足够快的时候,prefetchCount=1或30,时间基本是一样的)
在消息消费模型方面,Kafka使用Pull模型,消费者根据需要的消息,从服务端Pull数据;RabbitMQ采用Push模型,消费者与服务端队列保持长连接,队列中有消息则会Push至消费者。(Pull性能太差,尽量还是用Push模型)
/** 设置限流机制 Finding bottlenecks with RabbitMQ 3.3 | RabbitMQ - Blog
* param1: prefetchSize,消息本身的大小 如果设置为0 那么表示对消息本身的大小不限制
* param2: prefetchCount,告诉rabbitmq不要一次性给消费者推送大于N个消息
* param3:global,是否将上面的设置应用于整个通道,false表示只应用于当前消费者
*/
channel.BasicQos(0, 30, false);
rabbitmq的消息消费有两种方式,推模式和拉模式。推模式采用basic.consume进行消费,而拉模式则是调用的basic.Get进行消费。 推模式: 1:推模式接收消息是最有效的一种消息处理方式。channel.basicConsume(queneName,consumer)方法将信道(channel)设置成投递模式,直到取消队列的订阅为止;
在投递模式期间,当消息到达RabbitMQ时,RabbitMQ会自动地、不断地投递消息给匹配的消费者,而不需要消费端手动来拉取,当然投递消息的个数还是会受到channel.basicQos的限制。 2:推模式将消息提前推送给消费者,消费者必须设置一个缓冲区缓存这些消息。优点是消费者总是有一堆在内存中待处理的消息,所以当真正去消费消息时效率很高。缺点就是缓冲区可能会溢出。
3:由于推模式是信息到达RabbitMQ后,就会立即被投递给匹配的消费者,所以实时性非常好,消费者能及时得到最新的消息。 拉模式:1:如果只想从队列中获取单条消息而不是持续订阅,则可以使用channel.basicGet方法来进行消费消息。
2:拉模式在消费者需要时才去消息中间件拉取消息,这段网络开销会明显增加消息延迟,降低系统吞吐量。
3:由于拉模式需要消费者手动去RabbitMQ中拉取消息,所以实时性较差;消费者难以获取实时消息,具体什么时候能拿到新消息完全取决于消费者什么时候去拉取消息。 ———————————————— 原文链接:https://blog.csdn.net/tianjingle_blog/article/details/114326695
Push方式是Broker端接收到消息后,主动把消息推给Consumer端,实时性高。对于一个提供消息队列服务的Server来说,用Push方式会有很多弊端:
首先是消息的流量难以控制,当Push的消息过多时会加大Server的工作量,进而影响Server的性能;
其次,Client的处理能力各不相同,且Client的状态不受Server控制,如果Client不能及时处理Server推送过来的消息,在造成消息堆积等各种潜在的问题。 Pull方式是Client端循环地从Server端拉取消息,主动权在Client手里,自己拉取到一定量的消息后,妥当处理完毕再继续拉取。
Pull方式的问题是循环拉取的时间间隔不好设定,间隔太短就会处于”忙等”的状态,浪费资源;间隔太长又可能导致Server端有消息到来时没有及时被处理。 “长轮询”方式通过Client端和Server端的配合,达到既拥有Pull的优点,又保证实时性的目的。 “长轮询”的核心是,Broker端HOLD住客户端的请求一小段时间,如果在这段时间内有消息到达,就利用现有的链接立刻返回消息给Consumer。”长轮询”的主动权还是掌握在Consumer手上,即使Broker有大量的消息积压,也不会主动推送给Consumer。 长轮询方式的局限性在于,HOLD住Consumer端请求时,需要占用资源,它适合用在消息队列这种客户端连接数可控的场景中。 ———————————————— 原文链接:https://blog.csdn.net/weixin_34452850/article/details/82712634
在消息确认方面,Kafka提供消息至少发送一次(“at least once”),确保消息被可靠消费; RabbitMQ提供消息确认机制(Ack),直到消费者显式返回Ack才移除消息,确保消息到达消费者。此外,RabbitMQ还提供消息过滤,预读取计数等功能。
消息中间件的性能效率主要指集群接收和发送消息的吞吐率。吞吐率指标有两个,一是生产者/消费者每秒发送/接收的消息条数,单位为条/秒(Record/s);二是生产者/消费者每秒发送/接收的消息量,单位为MB/s。我们通过消息的发送吞吐率和接收吞吐率两方面评估消息队列中间件的性能,主要测试场景如下:
(1) 客户端并发数:不断增加生产者/消费者数量,测试发送/接收吞吐率的变化情况;
(2)消息大小:不断增加消息大小,测试发送吞吐率的变化情况;
(3)系统可靠性:通过增加副本数、改变应答模式,测试发送吞吐率的变化情况;
Direct Exchange
见文知意,直连交换机意思是此交换机需要绑定一个队列,要求该消息与一个特定的路由键完全匹配。简单点说就是一对一的,点对点的发送。
Fanout exchange
这种类型的交换机需要将队列绑定到交换机上。一个发送到交换机的消息都会被转发到与该交换机绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。简单点说就是发布订阅。发送到多个Queue
=====================MQTT 插件====================================
登陆控制台,输入下面命令启用插件
# rabbitmq-plugins enable rabbitmq_mqtt Enabling plugins on node rabbit@1a9c9656355e: rabbitmq_mqtt The following plugins have been configured: rabbitmq_management rabbitmq_management_agent rabbitmq_mqtt rabbitmq_prometheus rabbitmq_web_dispatch Applying plugin configuration to rabbit@1a9c9656355e... The following plugins have been enabled: rabbitmq_mqtt started 1 plugins.
假如想docker 重新创建container的时候,自动启用mqtt插件。可以在docker-compose.yaml里加入这个
volumes:
- ./dockerData/rabbitmq/etc:/etc/rabbitmq/
宿主机建一个文件enabled_plugins,文件内容如下,记得最后还有个点
[rabbitmq_management,rabbitmq_mqtt,rabbitmq_prometheus].
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏
· Manus爆火,是硬核还是营销?
2008-07-04 论Leader的技能