RabbitMQ 学习系列5 客户端开发向导 上
3.1 连接RabbitMQ
书中以java为示例,这里给出.net 的rabbitmq客户端代码进行讲解
连接到RabbitMQ有二种方式,一种是给定的参数(IP 地址、端口号,用户名,密码等),第二种是使用url的方式来实现。
使用Connection可以用来创建多个Channel实例,但是Channel实例不能在线程间共享,应用程序应该为每一个线程开辟一个Channel,多线程共享Channel实例是非线程安全的。
Channel或者Connectition中有个IsOpen方法可以用来检测其是否已处于开启状态,但并不推荐在生产环境的代码上使用IsOpen方法,这个方法的返回值依赖于shutdownCause的存在,有可能会产生竞争。
通常情况下,在调用CreateXXX或者NewXXX方法之后,我们可以简单地认为Connection或者Channel已经成功地处于开启状态,而并不会在代码中使用Isopen这个检测方法。如果在使用Channel的时候其已经处于关闭状态,那么程序会抛出一个ShutdownSignalException,我们只需要捕获这个异常即可,当然同时也要试着捕获IOException或者SocketException以防Connectition意外关闭。
下面是用channel调用basicQos时来验证连接是否正常,失败时捕获异常
3.2 使用交换器和队列
交换器和队列是AMQp中high-level层面的构建模块,应用程序需确保在使用它们的时候就已经存在了,在使用之前需要先声明它们.
感悟:交换器和队列的创建都是通过信道对象。
下面是一个代码片段:
string exchangeName = "DirectExchange"; string queueName = "DirectExchangeQueueName"; string routeKey = "DirectExchangeKey"; //创建连接工厂 var factory = new ConnectionFactory { UserName = "dengwu",//用户名 Password = "123456",//密码 HostName = "192.168.112.133",//rabbitmq ip }; //创建连接 var connection = factory.CreateConnection(); //创建通道 var channel = connection.CreateModel(); //定义一个Direct类型交换机 channel.ExchangeDeclare(exchangeName, ExchangeType.Direct, false, false, null); //定义一个队列 channel.QueueDeclare(queueName, false, false, false, null); //将队列绑定到交换机 channel.QueueBind(queueName, exchangeName, routeKey, null);
上面声明了一个非持久化,非自动删除,为direct的交换器,同时也声明了一个非持久化,非排他的,非自动删除的队列。也展示了如何使用路由键将队列和交换器绑定起来。
生产者和消费者都可以声明一个交换器或者队列。如果尝试声明一个已经存在的交换器或者队列,只要声明的参数完全匹配现在的交换器和队列,Rabbitmq就可以什么都不做,并成功返回,如果声明的参数不匹配则会抛出异常。
3.2.1 exchangeDeclare方法详解
ExchangeDeclare有多个重载方法,这些重载方法都是由下面这个方法中缺省的某些参数构成的。
参数如下所示:
exchange:交换器的名称
type:交换器的类型,常见的如fanout,direct,topic
dureable:设置是否持久化,durable设置为true表示持久化,反之是非持久化。持久化可能将交换器存盘,在服务器重启的时候不会丢失相关信息。
autoDelete:设置是否自动删除,autoDelete设置为true则表示自动删除。自动删除的前提是至少有一个队列或者交换器与这个交换器绑定,之后所有与这个交换器绑定的队列或者交换器都与此解绑。注意不能错误地把这个参数理解为”与当此交换器连接在客户端都断开时,rabbitmq会自动删除本交换器”
argument:其他一些结构化参数,比如alternate-exchange。
注意针对ExchangeDeclareNoWait不需要服务器任何返回值这一点,当在声明一个交换器之后(实际服务器还并未完成交换器的创建),那么此时客户端紧接着使用这个交换器,必然会发生异常,如果没有特殊的场景,并不建议使用这个方法。
另一个方法ExchangeDeclarePassive,这个方法在实际应用过程中是还非常有用的,它主要用来检测相应的交换器是否存在,如果不存在则抛出异常,同时Channel也会关闭。
交换器还有删除和解绑的方法,如下所示:
ifUnused用来设置是否在交换器没有被使用的情况下删除,如果ifUnused设置为true,则只有在些交换器没有被使用的情况下才会被删除,如果设置为false,则无论如何这个交换器都要被删除。
3.2.2 queueDeclare方法详解
queue:队列名称
durable: 设置是否持久 化,为true设置队列为持久化。持久化的队列会存盘,在服务器重启的时候可以保证不丢失信息。
exclusive:设置是否排他,为true则设置队列为排他的。如果一个队列被声明为排他队列,该队列仅对首次声明它的连接可见,并在连接断开时自动删除,这里需要注意三点:排他队列是基于连接(Connection)可见的,同一个连接的不同信道(Channel)是可以同时访问同一连接创建的排他队列;“首次”是指如果一个连接已经声明了一个排他队列,其他连接是不允许建立同名的排它队列的,这个与普通队列不同;即使该队列持久化的,一是连接关闭或者客户端退出,该排他队列都会被自动删除,这种队列适用于一个客户端同时发送和读取消息的应用场景。
autoDelete:设置是否自动删除,为true则设置队列为自动删除。自动删除的前提是:至少有一个消费者连接到这个队列,之后所有与这个队列连接的消费者都断开时,才会自动删除。不能把这个参数错误地理解为”当连接到此队列的所有客户端断开时,这个队列自动删除”,因为生产者客户端创建这个队列,或者没有消费者客户端与这个队列连接时,都不会自动删除这个队列。
arguments:设置队列的其他一些参数,如 :
x-message-ttl,
x-expires,
x-max-length,
x-max-length-bytes等
注意要点:
生产者和消费者都能够使用queueDeclare来声明一个队列,但是如果消费者在同一个信道上订阅了另一个队列,就无法再声明队列了,必须先取消订阅,然后将信道置为”传输”模式,之后才能声明队列。
对应于ExchangeDeleteNoWait,队列也有一个QueueDeclareNoWait,也需要注意,在调用完后,紧接着使用声明的队列时有可能会发生异常情况.
同样也有一个QueueDeclarePassive的方法,也比较常用,这个方法检测相应的队列是否存在,如果存在正常返回,不存在则抛出异常,同时Channel也会被关闭。
队列删除方法,ifUnused参照上面,ifEmpty设置为true表示队列在为空(队列里面没有任何消息堆积)的情况下才能够删除。
队列删除方法,ifUnused参照上面,ifEmpty设置为true表示队列在为空(队列里面没有任何消息堆积)的情况下才能够删除。
uint QueueDelete(string queue, bool ifUnused, bool ifEmpty)
清空队列中的内容方法,而不删除队列本身,如下所示:
uint QueuePurge(string queue);
3.2.3 queueBind方法详解
将队列和交换器绑定的方法如下,
方法参数说:
queue:队列名称;
exchange:交换器的名称;
routingKey:用来绑定队列和交换器的路由键;
argument:定义绑定的一些参数;
不仅可以将队列和交换器绑定起来,也可以将已经被绑定的队列和交换器进行解绑,具体方法可以参考如下:
3.2.3 exchangeBind方法详解
我们不仅可以将交换器与队列绑定,也可以将交换器与交换器绑定,后者与前者的用法如出一辙,方法如下:
下面是一个代码示例:
第一行:声明了一个交换器,名称为source。
第二行:声明了一个交换器,名称为destination。
第三行:把二个交换器绑定在一起,通过路由键。
第四行:声明一个对列,名称为queue。
第五行:绑定队列,交换器为destination。
第六行:发布消息,交换器为source。
生产者发送消息至交换器source中,交换器source根据路由键找到与其匹配的另一个交换器destination,并把消息转发到destination中,进而存储在destination绑定的队列queue中。
3.2.5 何时创建
Rabbitmq的消息存储在队列中,交换器的使用并不真正耗费服务器的性能,而队列会。如果要衡量Rabbitmq当前的qps(每秒查询率)只需看队列的即可。在实际业务应用中,需要对所创建的队列的流量、内存占用及网卡占用有一个清晰的认知,预估其平均值和峰值,以便在固定的硬件资源的情况下能够进行合理有效的分配。
按照Rabbitmq官方建议,生产者和消费者都应该尝试创建(这里指声明操作)队列。这是一个很好的建议,但不适应于所有的情况。如果业务本身在架构设计之初已经充分地预估了队列的使用情况,完全可以在业务程序上线之前在服务器上创建好(比如通过页面管理、rabbitmq命令或者更好是从配置中心下发),这样业务程序也可以免去声明的过程,直接使用即可。
预先创建好资源还有一个好处是,可以确保交换器和队列之间正确地绑定匹配,很多时候由于人为因素,代码缺陷等,发送消息的交换器并没有绑定任何队列,那么消息将会丢失;或者交换器绑定了某个队列,但是发送消息时的路由键无法与现存的队列匹配,那么消息也会丢失。
与此同时,预估好队列的使用情况非常重要,如果在后期运行过程中超过预定的阀值,可以根据实际情况对当前集群进行扩容或者将相应的队列迁移到其他集群,迁移的过程也可以对业务程序完全透明,此方法也更有利于开发和运维分工,便于相应资源的管理。
至于是使用预先分配创建资源的静态方式还是动态的创建方式,需要从业务逻辑本身,公司运维体系和公司硬件资源等方面考虑。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
2018-05-14 sql server 性能调优之 死锁排查