消息队列的地位越来越重要,几乎是面试的必问问题了,不会使用几种消息队列都显得尴尬,正好本文使用C#来带你认识rabbitmq消息队列

  首先,我们要安装rabbitmq,当然,如果有现成的,也可以使用,不知道曾几何时,我喜欢将数据库等等软件安装在linux虚拟机,如果没现成的rabbitmq,按照下面的来吧,嘿嘿

  rabbitmq安装:https://www.cnblogs.com/shanfeng1000/p/11951703.html

  如果要实现rabbitmq集群,参考:https://www.cnblogs.com/shanfeng1000/p/12097054.html

  我这里使用的是rabbitmq集群,但是没有比较,只是已经安装好了,就直接使用算了

  虚拟机集群地址:192.168.209.133,192.168.209.134,192.168.209.135

  端口使用的默认端口,都是5672,也就是AMQP协议端口

  Rabbitmq的工作模式

  先说说几个概念

  生产者(producer):负责生产消息,可以有多个生产者,可以理解为生成消息的那部分逻辑

  消费者(consumer):从队列中获取消息,对消息处理的那部分逻辑

  队列(queue):用于存放消息,可以理解为先进先出的一个对象

  交换机(exchange):顾名思义,就是个中介的角色,将接收到的消息按不同的规则转发到其他交换机或者队列中

  路由(route):就是交换机分发消息的规则,交换机可以指定路由规则,生产者在发布消息时也可以指定消息路由,比如交换机中设置A路由表示将消息转发到队列1,B路由表示将消息转发到队列2,那么当交换机接收到消息时,如果消息的路由满足A路由,则将消息转发到队列1,如果满足B路由则将消息转发到队列2

  虚拟主机(virtual host):虚拟地址,用于进行逻辑隔离,一个虚拟主机里面可以有若干个 exchange 和 queue,但是里面不能有相同名称的 exchange 或 queue

  再看看rabbitmq的几种工作模式,具体可参考rabbitmq官网给出的Demo:https://www.rabbitmq.com/getstarted.html

    

  其中,第6中类似我们常用的请求-响应模式,但是使用的RPC请求响应,用的比较少,这里就不过多解释,感兴趣的可以参考官网文档:https://www.rabbitmq.com/tutorials/tutorial-six-dotnet.html

  总的来说,就是生产者将消息发布到rabbitmq上,然后消费者连接rabbitmq,获取到消息就消费,但是有几点说明一下

  1、rabbitmq中的消息是可被多次消费的,因为rabbitmq提供了ack机制,当消费者在消费消息时,如果将自动ack设置成false,那么需要手动提交ack才能告诉rabbitmq消息已被使用,否则当通道关闭时,消息会继续呆在队列中等待消费

  2、当存在多个消费者时,默认情况下,一个消费者获取一个消息,处理完成后再获取下一个,但是rabbitmq消费一次性获取多个,当然后当这些消息消费完成后,再获取下一批,这也就是rabbitmq的Qos机制

  

  C#使用rabbitmq

  如果感兴趣的人多,到时候再单独开一篇博文,现在就介绍其中的1-5种,也可以分类成两种:不使用交换机和使用交换机,所以下面就分这两种来说明

  首先,我们创建了两个Demo项目:RabbitMQ.PublishConsole和RabbitMQ.ConsumeConsole,分别使用使用nuget安装RabbitMQ.Client:

  

  其中RabbitMQ.PublishConsole是用来生产消息,RabbitMQ.ConsumeConsole用来消费消息  

  这里我们安装的是最新版本,旧版本和新版本在使用上可能会有一些区别


  不使用交换机情形

  不使用交换机有两种模式:简单模式和工作模式

  这里先贴上生产者生成消息的代码,简单模式和工作模式这部分测试代码是一样的:  

  

using RabbitMQ.Client;
using System;
using System.Collections.Generic;
using System.Text; namespace RabbitMQ.PublishConsole
{
class Program
{
static void Main(string[] args)
{
string[] hosts = new string[] { "192.168.209.133", "192.168.209.134", "192.168.209.135" };
int port = 5672;
string userName = "admin";
string password = "123456";
string virtualHost = "/"; //创建一个连接工厂
var factory = new ConnectionFactory();
factory.UserName = userName;
factory.Password = password;
factory.Port = port;
factory.VirtualHost = virtualHost;
//创建一个连接,此时可以在rabbitmq后台Web管理页面中的Connections中看到一个连接生成
//一个连接可以创建多个通道
var connection = factory.CreateConnection(hosts); string queue = "queue1";//队列名称 //创建一个通道
//此时可以在rabbitmq后台Web管理页面中的Channels中看到一个新通道生成
var channel = connection.CreateModel();
//给通道绑定一个队列,队列如果不存在,则会创建新队列,如果队列已存在,那么参数一定要正确,特别是arguments参数,否则会报错
var arguments = new Dictionary<string, object>() { { "x-queue-type", "classic" } };
channel.QueueDeclare(queue: queue, durable: true, exclusive: false, autoDelete: false, arguments: arguments); //发布10条消息
for (var i = 0; i < 10; i++)
{
var buffer = Encoding.UTF8.GetBytes(i.ToString());
channel.BasicPublish("", queue, null, buffer);
}
channel.Close(); Console.ReadKey();
}
}
}

RabbitMQ.PublishConsole

  上述代码执行完成后,队列queue1中就有了10条消息,可以在rabbitmq的后台管理中看到:  

  

  代码中提到,通道在申明队列时,如果队列已经存在,则申明的参数一定要对上,否则会抛出异常:The AMQP operation was interrupted: AMQP close-reason, initiated by Peer, code=406, text='PRECONDITION_FAILED - inequivalent arg 'x-queue-type' for queue 'queue1' in vhost '/': received none but current is the value 'classic' of type 'longstr'', classId=50, methodId=10

  比如这里,我实现在rabbitmq后台创建了队列,那么他们的对应关系如下图: 

   

  

  简单模式

  这个模式很简单,其实就是只有一个消费者,简单的保证操作的顺序性

  

  接着贴上消费者代码:

  

using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading; namespace RabbitMQ.ConsumeConsole
{
class Program
{
static void Main(string[] args)
{
string[] hosts = new string[] { "192.168.209.133", "192.168.209.134", "192.168.209.135" };
int port = 5672;
string userName = "admin";
string password = "123456";
string virtualHost = "/"; //创建一个连接工厂
var factory = new ConnectionFactory();
factory.UserName = userName;
factory.Password = password;
factory.Port = port;
factory.VirtualHost = virtualHost;
//创建一个连接,此时可以在rabbitmq后台Web管理页面中的Connections中看到一个连接生成
//一个连接可以创建多个通道
var connection = factory.CreateConnection(hosts); string queue = "queue1";//队列名称 //创建一个通道
//此时可以在rabbitmq后台Web管理页面中的Channels中看到一个新通道生成
var channel = connection.CreateModel();
//给通道绑定一个队列,队列如果不存在,则会创建新队列,如果队列已存在,那么参数一定要正确,特别是arguments参数,否则会报错
var arguments = new Dictionary<string, object>() { { "x-queue-type", "classic" } };
channel.QueueDeclare(queue: queue, durable: true, exclusive: false, autoDelete: false, arguments: arguments);
//channel.BasicQos(2, 2, false);//设置QOS //在通道中定义一个事件消费者
EventingBasicConsumer consumer = new EventingBasicConsumer(channel);
consumer.Received += (sender, e) =>
{
string message = Encoding.UTF8.GetString(e.Body);
Console.WriteLine($"接收到消息:{message}"); Thread.Sleep(500);//暂停一下 //通知消息已被处理,如果没有,那么消息将会被重复消费
channel.BasicAck(e.DeliveryTag, false);
};
//ack设置成false,表示不自动提交,那么就需要在消息被消费后,手动调用BasicAck去提交消息
channel.BasicConsume(queue, false, consumer); Console.ReadKey();
} }
}

RabbitMQ.ConsumeConsole

  上述代码执行完成后,在后台管理中可以看到消息被消费掉了

  

  工作模式

  工作模式是简单模式的拓展,如果业务简单,对消息的消费是一个耗时的过程,这个模式是一个好的选择。

  

  接着调用生产者代码生产10条消息,下面是消费者的测试代码  

  

using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading; namespace RabbitMQ.ConsumeConsole
{
class Program
{
static void Main(string[] args)
{
string[] hosts = new string[] { "192.168.209.133", "192.168.209.134", "192.168.209.135" };
int port = 5672;
string userName = "admin";
string password = "123456";
string virtualHost = "/"; //创建一个连接工厂
var factory = new ConnectionFactory();
factory.UserName = userName;
factory.Password = password;
factory.Port = port;
factory.VirtualHost = virtualHost;
//创建一个连接,此时可以在rabbitmq后台Web管理页面中的Connections中看到一个连接生成
//一个连接可以创建多个通道
var connection = factory.CreateConnection(hosts); Consumer(connection, 1);//消费者1
Consumer(connection, 2);//消费者2 Console.ReadKey();
} static void Consumer(IConnection connection, ushort prefetch)
{
//使用多线程来执行,可以模拟多个消费者
new Thread(() =>
{
int threadId = Thread.CurrentThread.ManagedThreadId;//线程Id,用于区分消费者
string queue = "queue1";//队列名称 //创建一个通道
//此时可以在rabbitmq后台Web管理页面中的Channels中看到一个新通道生成
var channel = connection.CreateModel();
//给通道绑定一个队列,队列如果不存在,则会创建新队列,如果队列已存在,那么参数一定要正确,特别是arguments参数,否则会报错
var arguments = new Dictionary<string, object>() { { "x-queue-type", "classic" } };
channel.QueueDeclare(queue: queue, durable: true, exclusive: false, autoDelete: false, arguments: arguments);
//设置消费者每次获取的消息数,可以用来设置消费者消息的权重
//必须等获取的消息都消费完成后才能重新获取
channel.BasicQos(0, prefetch, true); //在通道中定义一个事件消费者
EventingBasicConsumer consumer = new EventingBasicConsumer(channel);
consumer.Received += (sender, e) =>
{
string message = Encoding.UTF8.GetString(e.Body);
Console.WriteLine($"ThreadId:【{threadId}】 接收到消息:{message}"); Thread.Sleep(500); //通知消息已被处理,如果没有,那么消息将会被重复消费
channel.BasicAck(e.DeliveryTag, false); };
//ack设置成false,表示不自动提交,那么就需要在消息被消费后,手动调用BasicAck去提交消息
channel.BasicConsume(queue, false, consumer);
}).Start();
}
}
}

RabbitMQ.ConsumeConsole

  另外说明一下,代码中提到rabbitmq的QOS机制,这里简单解释一下,当生产者将消息发布到rabbitmq之后,如果在未配置QOS的情况下,rabbitmq尽可能快速地发送队列中的所有消息到消费者端,如果消息比较多,消费者来不及处理,就会缓存这些消息,当消息堆积过多,可能导致服务器内存不足而影响其他进程,rabbitmq的QOS可以很好的解决这类问题,QOS就是限制消费者一次性从rabbitmq中获取消息的个数,而不是获取所有消息。比如设置rabbitmq的QOS为10,也就是prefetch=10,就是说,哪怕rabbitmq中有100条消息,消费者也只是一次性获取10条,然后消费者消费这10条消息,剩下的交给其他消费者,当10条消息中的unacked个数少于prefetch * 消费者数目时,会继续从rabbitmq获取消息,如果在工作模式中,不使用QOS,你会发现,所有的消息都被一个消费者消费了

  


  使用交换机情形

  使用交换机的情形有3种:发布订阅模式,路由模式,主题模式

  上面说了,交换机是一个中介的角色,当一个交换机创建后,可以将其他队列或者交换机与当前交换机绑定,绑定时需要指定绑定路由规则,这个和交换机类型有关。

  当我们不使用交换机时,那么生产者是直接将消息发布到队列中去的,生产者只需要指定消息接收的队列即可,而使用交换机做中转时,生产者只需要将消息发布到交换机,然后交换机根据接收到的消息,按与交换机绑定的路由规则,将消息转发到其他交换机或者队列中,这个处理过程和交换机的类型有关,交换机一般分为4类:

  direct:直连类型,就是将消息的路由和交换机的绑定路由作比较,当两者一致时,则匹配成功,然后消息就会被转发到这个绑定路由后的队列或者交换机

  fanout:这种类型的交换机是不需要指定路由的,当交换机接收到消息时,会将消息广播到所有绑定到它的所有队列或交换机中

  topic:主题类型,类似direct类型,只不过在将消息的路由和绑定路由做比较时,是通过特定表达式去比较的,其中# 匹配一个或多个,* 匹配一个

  headers:头部交换机,允许使用消息头中的信息来做匹配规则,这个用的少,基本上不用,这里也就不过多介绍了

  到这里,你应该发觉,使用交换机的三种情形,无非就是使用交换机的类型不一样,发布订阅模式--fanout,路由模式--direct,主题模式--topic

  现在我们先去rabbitmq的后台中,创建这几种交换机:

  交换机的创建及绑定都可以在代码中实现,如IModel类的QueueBind,ExchangeBind等方法,用多了就自然熟了,这里为了方便截图,就到后台去创建了

  

  然后我们创建两个队列,并按指定类型分别绑定到这3个交换机中:

   队列:

  

  demo.direct绑定队列规则:

   

  demo.fanout绑定队列规则:

  

  demo.topic绑定队列规则:

  

  上面所描述的,无非就是三种模式中发布消息方式的不一样,消费者当然还是从队列获取消息消费的,这里我们就先贴出消费者的代码:

  

using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading; namespace RabbitMQ.ConsumeConsole
{
class Program
{
static void Main(string[] args)
{
string[] hosts = new string[] { "192.168.209.133", "192.168.209.134", "192.168.209.135" };
int port = 5672;
string userName = "admin";
string password = "123456";
string virtualHost = "/"; //创建一个连接工厂
var factory = new ConnectionFactory();
factory.UserName = userName;
factory.Password = password;
factory.Port = port;
factory.VirtualHost = virtualHost;
//创建一个连接,此时可以在rabbitmq后台Web管理页面中的Connections中看到一个连接生成
//一个连接可以创建多个通道
var connection = factory.CreateConnection(hosts); Consumer(connection, "queue1");//消费者1
Consumer(connection, "queue2");//消费者2 Console.ReadKey();
} static void Consumer(IConnection connection, string queue)
{
//使用多线程来执行,可以模拟多个消费者
new Thread(() =>
{
int threadId = Thread.CurrentThread.ManagedThreadId;//线程Id,用于区分消费者 //创建一个通道
//此时可以在rabbitmq后台Web管理页面中的Channels中看到一个新通道生成
var channel = connection.CreateModel();
//给通道绑定一个队列,队列如果不存在,则会创建新队列,如果队列已存在,那么参数一定要正确,特别是arguments参数,否则会报错
var arguments = new Dictionary<string, object>() { { "x-queue-type", "classic" } };
channel.QueueDeclare(queue: queue, durable: true, exclusive: false, autoDelete: false, arguments: arguments); //在通道中定义一个事件消费者
EventingBasicConsumer consumer = new EventingBasicConsumer(channel);
consumer.Received += (sender, e) =>
{
string message = Encoding.UTF8.GetString(e.Body);
Console.WriteLine($"ThreadId:【{threadId}】 接收到消息:{message}"); Thread.Sleep(500); //通知消息已被处理,如果没有,那么消息将会被重复消费
channel.BasicAck(e.DeliveryTag, false);
};
//ack设置成false,表示不自动提交,那么就需要在消息被消费后,手动调用BasicAck去提交消息
channel.BasicConsume(queue, false, consumer);
}).Start();
}
}
}

RabbitMQ.ConsumeConsole

  这里我们使用了两个队列,每个队列我们这里只用了一个消费者,对于下面几种模式,这个消费者代码都能消费到

  发布订阅模式

  发布订阅模式使用的是fanout类型的交换机,这个类型无需指定路由,交换机会将消息广播到每个绑定到交换机的队列或者交换机  

  

  

using RabbitMQ.Client;
using System;
using System.Collections.Generic;
using System.Text; namespace RabbitMQ.PublishConsole
{
class Program
{
static void Main(string[] args)
{
string[] hosts = new string[] { "192.168.209.133", "192.168.209.134", "192.168.209.135" };
int port = 5672;
string userName = "admin";
string password = "123456";
string virtualHost = "/"; //创建一个连接工厂
var factory = new ConnectionFactory();
factory.UserName = userName;
factory.Password = password;
factory.Port = port;
factory.VirtualHost = virtualHost;
//创建一个连接,此时可以在rabbitmq后台Web管理页面中的Connections中看到一个连接生成
//一个连接可以创建多个通道
var connection = factory.CreateConnection(hosts); string exchange = "demo.fanout";//交换机名称
string exchangeType = "fanout";//交换机类型 //创建一个通道
//此时可以在rabbitmq后台Web管理页面中的Channels中看到一个新通道生成
var channel = connection.CreateModel();
//给通道绑定一个交换机,交换机如果不存在,则会创建新交换机,如果交换机已存在,那么参数一定要正确,特别是arguments参数,各参数类似队列
var arguments = new Dictionary<string, object>() { };
channel.ExchangeDeclare(exchange: exchange, type: exchangeType, durable: true, autoDelete: false, arguments: arguments); //发布10条消息
for (var i = 0; i < 10; i++)
{
var buffer = Encoding.UTF8.GetBytes(i.ToString());
channel.BasicPublish(exchange, "", null, buffer);
}
channel.Close(); Console.ReadKey();
}
}
}

RabbitMQ.PublishConsole

  代码中,我们往交换机发布了10条消息,交换机接收到消息后,会将消息转发到queue1和queue2,因此,queue1和queue2都会收到10条消息:

  

  路由模式

  路由模式使用的是direct类型的交换机,也即在进行路由匹配时,需要匹配的路由一直才算匹配成功,我们把发布订阅模式的代码稍作修改即可,贴出生产者部分代码:  

   

  

using RabbitMQ.Client;
using System;
using System.Collections.Generic;
using System.Text; namespace RabbitMQ.PublishConsole
{
class Program
{
static void Main(string[] args)
{
string[] hosts = new string[] { "192.168.209.133", "192.168.209.134", "192.168.209.135" };
int port = 5672;
string userName = "admin";
string password = "123456";
string virtualHost = "/"; //创建一个连接工厂
var factory = new ConnectionFactory();
factory.UserName = userName;
factory.Password = password;
factory.Port = port;
factory.VirtualHost = virtualHost;
//创建一个连接,此时可以在rabbitmq后台Web管理页面中的Connections中看到一个连接生成
//一个连接可以创建多个通道
var connection = factory.CreateConnection(hosts); string exchange = "demo.direct";//交换机名称
string exchangeType = "direct";//交换机类型 //创建一个通道
//此时可以在rabbitmq后台Web管理页面中的Channels中看到一个新通道生成
var channel = connection.CreateModel();
//给通道绑定一个交换机,交换机如果不存在,则会创建新交换机,如果交换机已存在,那么参数一定要正确,特别是arguments参数,各参数类似队列
var arguments = new Dictionary<string, object>() { };
channel.ExchangeDeclare(exchange: exchange, type: exchangeType, durable: true, autoDelete: false, arguments: arguments); string[] routes = new string[] { "apple", "banana" }; //发布10条消息
for (var i = 0; i < 10; i++)
{
var buffer = Encoding.UTF8.GetBytes(i.ToString());
channel.BasicPublish(exchange, routes[i % 2], null, buffer);
}
channel.Close(); Console.ReadKey();
}
}
}

RabbitMQ.PublishConsole

  代码中,我们往demo.direct交换机发布了10条消息,其中5条消息的路由是apple,另外5条消息的路由是banana,demo.direct交换机绑定的两个队列中,queue1的绑定路由是apple,queue2的绑定路由是banana,那么demo.direct交换机会将路由是apple的消息转发到queue1,将路由是banana的消息转发到queue2,从后台可以看每个队列中已经有5个消息准备好了:

  

  接下来可以使用消费者将它们消费掉

  主题模式

  主题模式使用的topic类型的交换机,在进行匹配时,是根据表达式去匹配,# 匹配一个或多个,* 匹配一个,我们将路由模式的代码稍作修改:    

  

  

using RabbitMQ.Client;
using System;
using System.Collections.Generic;
using System.Text; namespace RabbitMQ.PublishConsole
{
class Program
{
static void Main(string[] args)
{
string[] hosts = new string[] { "192.168.209.133", "192.168.209.134", "192.168.209.135" };
int port = 5672;
string userName = "admin";
string password = "123456";
string virtualHost = "/"; //创建一个连接工厂
var factory = new ConnectionFactory();
factory.UserName = userName;
factory.Password = password;
factory.Port = port;
factory.VirtualHost = virtualHost;
//创建一个连接,此时可以在rabbitmq后台Web管理页面中的Connections中看到一个连接生成
//一个连接可以创建多个通道
var connection = factory.CreateConnection(hosts); string exchange = "demo.topic";//交换机名称
string exchangeType = "topic";//交换机类型 //创建一个通道
//此时可以在rabbitmq后台Web管理页面中的Channels中看到一个新通道生成
var channel = connection.CreateModel();
//给通道绑定一个交换机,交换机如果不存在,则会创建新交换机,如果交换机已存在,那么参数一定要正确,特别是arguments参数,各参数类似队列
var arguments = new Dictionary<string, object>() { };
channel.ExchangeDeclare(exchange: exchange, type: exchangeType, durable: true, autoDelete: false, arguments: arguments); string[] routes = new string[] { "apple.", "banana." }; //发布10条消息
for (var i = 0; i < 10; i++)
{
var buffer = Encoding.UTF8.GetBytes(i.ToString());
channel.BasicPublish(exchange, routes[i % 2] + i, null, buffer);
}
channel.Close(); Console.ReadKey();
}
}
}

RabbitMQ.PublishConsole

  代码中,我们往demo.topic交换机中发布了10条消息,其中5条消息的路由是以apple开头的,另外5条消息的路由是以banana开头的,demo.direct交换机绑定的两个队列中,queue1的绑定路由是apple.#,就是匹配以apple开头的路由,queue2的绑定路由是banana.#,就是匹配以banana开头的路由,那么demo.direct交换机会将路由是以apple开头的的消息转发到queue1,将路由是以banana开头的的消息转发到queue2,从后台可以看每个队列中已经有5个消息准备好了:

  

  


  封装

  其实rabbitmq的使用还是比较简单的,只需要多谢谢代码尝试一下就能熟悉

  一般的,像这种第三方插件的调用,我建议自己要做一层封装,最好是根据自己的需求去封装,然后项目中只需要调用自己封装的类就行了,下面贴出我自己封装的类:  

  

using System;
using System.Collections.Generic;
using System.Text; namespace RabbitMQ.ConsoleApp
{
public class QueueOptions
{
/// <summary>
/// 是否持久化
/// </summary>
public bool Durable { get; set; } = true;
/// <summary>
/// 是否自动删除
/// </summary>
public bool AutoDelete { get; set; } = false;
/// <summary>
/// 参数
/// </summary>
public IDictionary<string, object> Arguments { get; set; } = new Dictionary<string, object>();
}
public class ConsumeQueueOptions : QueueOptions
{
/// <summary>
/// 是否自动提交
/// </summary>
public bool AutoAck { get; set; } = false;
/// <summary>
/// 每次发送消息条数
/// </summary>
public ushort? FetchCount { get; set; }
}
public class ExchangeConsumeQueueOptions : ConsumeQueueOptions
{
/// <summary>
/// 路由值
/// </summary>
public string[] RoutingKeys { get; set; }
/// <summary>
/// 参数
/// </summary>
public IDictionary<string, object> BindArguments { get; set; } = new Dictionary<string, object>();
}
public class ExchangeQueueOptions : QueueOptions
{
/// <summary>
/// 交换机类型
/// </summary>
public string Type { get; set; }
/// <summary>
/// 队列及路由值
/// </summary>
public (string,string)[] QueueAndRoutingKey { get; set; }
/// <summary>
/// 参数
/// </summary>
public IDictionary<string, object> BindArguments { get; set; } = new Dictionary<string, object>();
}
}

QueueOptions

  

using System;
using System.Collections.Generic;
using System.Text; namespace RabbitMQ.ConsoleApp
{
public static class RabbitMQExchangeType
{
/// <summary>
/// 普通模式
/// </summary>
public const string Common = "";
/// <summary>
/// 路由模式
/// </summary>
public const string Direct = "direct";
/// <summary>
/// 发布/订阅模式
/// </summary>
public const string Fanout = "fanout";
/// <summary>
/// 匹配订阅模式
/// </summary>
public const string Topic = "topic";
}
}

RabbitMQExchangeType

  

using RabbitMQ.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; namespace RabbitMQ.ConsoleApp
{
public abstract class RabbitBase : IDisposable
{
List<AmqpTcpEndpoint> amqpList;
IConnection connection; protected RabbitBase(params string[] hosts)
{
if (hosts == null || hosts.Length == 0)
{
throw new ArgumentException("invalid hosts!", nameof(hosts));
} this.amqpList = new List<AmqpTcpEndpoint>();
this.amqpList.AddRange(hosts.Select(host => new AmqpTcpEndpoint(host, Port)));
}
protected RabbitBase(params (string, int)[] hostAndPorts)
{
if (hostAndPorts == null || hostAndPorts.Length == 0)
{
throw new ArgumentException("invalid hosts!", nameof(hostAndPorts));
} this.amqpList = new List<AmqpTcpEndpoint>();
this.amqpList.AddRange(hostAndPorts.Select(tuple => new AmqpTcpEndpoint(tuple.Item1, tuple.Item2)));
} /// <summary>
/// 端口
/// </summary>
public int Port { get; set; } = 5672;
/// <summary>
/// 账号
/// </summary>
public string UserName { get; set; } = ConnectionFactory.DefaultUser;
/// <summary>
/// 密码
/// </summary>
public string Password { get; set; } = ConnectionFactory.DefaultPass;
/// <summary>
/// 虚拟机
/// </summary>
public string VirtualHost { get; set; } = ConnectionFactory.DefaultVHost; /// <summary>
/// 释放
/// </summary>
public virtual void Dispose()
{
//connection?.Close();
//connection?.Dispose();
}
/// <summary>
/// 关闭连接
/// </summary>
public void Close()
{
connection?.Close();
connection?.Dispose();
} #region Private
/// <summary>
/// 获取rabbitmq的连接
/// </summary>
/// <returns></returns>
protected IModel GetChannel()
{
if (connection == null)
{
lock (this)
{
if (connection == null)
{
var factory = new ConnectionFactory();
factory.Port = Port;
factory.UserName = UserName;
factory.VirtualHost = VirtualHost;
factory.Password = Password;
connection = factory.CreateConnection(this.amqpList);
}
}
}
return connection.CreateModel();
} #endregion
}
}

RabbitBase

  

using RabbitMQ.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; namespace RabbitMQ.ConsoleApp
{
public class RabbitMQProducer : RabbitBase
{
public RabbitMQProducer(params string[] hosts) : base(hosts)
{ }
public RabbitMQProducer(params (string,int)[] hostAndPorts) : base(hostAndPorts)
{ } #region 普通模式、Work模式
/// <summary>
/// 发布消息
/// </summary>
/// <param name="queue"></param>
/// <param name="message"></param>
/// <param name="options"></param>
public void Publish(string queue, string message, QueueOptions options = null)
{
options = options ?? new QueueOptions();
var channel = GetChannel();
channel.QueueDeclare(queue, options.Durable, false, options.AutoDelete, options.Arguments ?? new Dictionary<string, object>());
var buffer = Encoding.UTF8.GetBytes(message);
channel.BasicPublish("", queue, null, buffer);
channel.Close();
}
/// <summary>
/// 发布消息
/// </summary>
/// <param name="queue"></param>
/// <param name="message"></param>
/// <param name="configure"></param>
public void Publish(string queue, string message, Action<QueueOptions> configure)
{
QueueOptions options = new QueueOptions();
configure?.Invoke(options);
Publish(queue, message, options);
}
#endregion
#region 订阅模式、路由模式、Topic模式
/// <summary>
/// 发布消息
/// </summary>
/// <param name="exchange"></param>
/// <param name="routingKey"></param>
/// <param name="message"></param>
/// <param name="options"></param>
public void Publish(string exchange, string routingKey, string message, ExchangeQueueOptions options = null)
{
options = options ?? new ExchangeQueueOptions();
var channel = GetChannel();
channel.ExchangeDeclare(exchange, string.IsNullOrEmpty(options.Type) ? RabbitMQExchangeType.Fanout : options.Type, options.Durable, options.AutoDelete, options.Arguments ?? new Dictionary<string, object>());
if (options.QueueAndRoutingKey != null)
{
foreach (var t in options.QueueAndRoutingKey)
{
if (!string.IsNullOrEmpty(t.Item1))
{
channel.QueueBind(t.Item1, exchange, t.Item2 ?? "", options.BindArguments ?? new Dictionary<string, object>());
}
}
}
var buffer = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(exchange, routingKey, null, buffer);
channel.Close();
}
/// <summary>
/// 发布消息
/// </summary>
/// <param name="exchange"></param>
/// <param name="routingKey"></param>
/// <param name="message"></param>
/// <param name="configure"></param>
public void Publish(string exchange, string routingKey, string message, Action<ExchangeQueueOptions> configure)
{
ExchangeQueueOptions options = new ExchangeQueueOptions();
configure?.Invoke(options);
Publish(exchange, routingKey, message, options);
}
#endregion
}
}

RabbitMQProducer

  

using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading; namespace RabbitMQ.ConsoleApp
{
public class RabbitMQConsumer : RabbitBase
{
public RabbitMQConsumer(params string[] hosts) : base(hosts)
{ }
public RabbitMQConsumer(params (string, int)[] hostAndPorts) : base(hostAndPorts)
{ } public event Action<RecieveResult> Received; /// <summary>
/// 构造消费者
/// </summary>
/// <param name="channel"></param>
/// <param name="options"></param>
/// <returns></returns>
private IBasicConsumer ConsumeInternal(IModel channel, ConsumeQueueOptions options)
{
EventingBasicConsumer consumer = new EventingBasicConsumer(channel);
consumer.Received += (sender, e) =>
{
try
{
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
if (!options.AutoAck)
{
cancellationTokenSource.Token.Register(() =>
{
channel.BasicAck(e.DeliveryTag, false);
});
}
Received?.Invoke(new RecieveResult(e, cancellationTokenSource));
}
catch { }
};
if (options.FetchCount != null)
{
channel.BasicQos(0, options.FetchCount.Value, false);
}
return consumer;
} #region 普通模式、Work模式
/// <summary>
/// 消费消息
/// </summary>
/// <param name="queue"></param>
/// <param name="options"></param>
public ListenResult Listen(string queue, ConsumeQueueOptions options = null)
{
options = options ?? new ConsumeQueueOptions();
var channel = GetChannel();
channel.QueueDeclare(queue, options.Durable, false, options.AutoDelete, options.Arguments ?? new Dictionary<string, object>());
var consumer = ConsumeInternal(channel, options);
channel.BasicConsume(queue, options.AutoAck, consumer);
ListenResult result = new ListenResult();
result.Token.Register(() =>
{
try
{
channel.Close();
channel.Dispose();
}
catch { }
});
return result;
}
/// <summary>
/// 消费消息
/// </summary>
/// <param name="queue"></param>
/// <param name="configure"></param>
public ListenResult Listen(string queue, Action<ConsumeQueueOptions> configure)
{
ConsumeQueueOptions options = new ConsumeQueueOptions();
configure?.Invoke(options);
return Listen(queue, options);
}
#endregion
#region 订阅模式、路由模式、Topic模式
/// <summary>
/// 消费消息
/// </summary>
/// <param name="exchange"></param>
/// <param name="queue"></param>
/// <param name="options"></param>
public ListenResult Listen(string exchange, string queue, ExchangeConsumeQueueOptions options = null)
{
options = options ?? new ExchangeConsumeQueueOptions();
var channel = GetChannel();
channel.QueueDeclare(queue, options.Durable, false, options.AutoDelete, options.Arguments ?? new Dictionary<string, object>());
if (options.RoutingKeys != null && !string.IsNullOrEmpty(exchange))
{
foreach (var key in options.RoutingKeys)
{
channel.QueueBind(queue, exchange, key, options.BindArguments);
}
}
var consumer = ConsumeInternal(channel, options);
channel.BasicConsume(queue, options.AutoAck, consumer);
ListenResult result = new ListenResult();
result.Token.Register(() =>
{
try
{
channel.Close();
channel.Dispose();
}
catch { }
});
return result;
}
/// <summary>
/// 消费消息
/// </summary>
/// <param name="exchange"></param>
/// <param name="queue"></param>
/// <param name="configure"></param>
public ListenResult Listen(string exchange, string queue, Action<ExchangeConsumeQueueOptions> configure)
{
ExchangeConsumeQueueOptions options = new ExchangeConsumeQueueOptions();
configure?.Invoke(options);
return Listen(exchange, queue, options);
}
#endregion
}
public class RecieveResult
{
CancellationTokenSource cancellationTokenSource;
public RecieveResult(BasicDeliverEventArgs arg, CancellationTokenSource cancellationTokenSource)
{
this.Body = Encoding.UTF8.GetString(arg.Body);
this.ConsumerTag = arg.ConsumerTag;
this.DeliveryTag = arg.DeliveryTag;
this.Exchange = arg.Exchange;
this.Redelivered = arg.Redelivered;
this.RoutingKey = arg.RoutingKey;
this.cancellationTokenSource = cancellationTokenSource;
} /// <summary>
/// 消息体
/// </summary>
public string Body { get; private set; }
/// <summary>
/// 消费者标签
/// </summary>
public string ConsumerTag { get; private set; }
/// <summary>
/// Ack标签
/// </summary>
public ulong DeliveryTag { get; private set; }
/// <summary>
/// 交换机
/// </summary>
public string Exchange { get; private set; }
/// <summary>
/// 是否Ack
/// </summary>
public bool Redelivered { get; private set; }
/// <summary>
/// 路由
/// </summary>
public string RoutingKey { get; private set; } public void Commit()
{
if (cancellationTokenSource == null || cancellationTokenSource.IsCancellationRequested) return; cancellationTokenSource.Cancel();
cancellationTokenSource.Dispose();
cancellationTokenSource = null;
}
}
public class ListenResult
{
CancellationTokenSource cancellationTokenSource; /// <summary>
/// CancellationToken
/// </summary>
public CancellationToken Token { get { return cancellationTokenSource.Token; } }
/// <summary>
/// 是否已停止
/// </summary>
public bool Stoped { get { return cancellationTokenSource.IsCancellationRequested; } } public ListenResult()
{
cancellationTokenSource = new CancellationTokenSource();
} /// <summary>
/// 停止监听
/// </summary>
public void Stop()
{
cancellationTokenSource.Cancel();
}
}
}

RabbitMQConsumer

  测试Demo  

  

using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading; namespace RabbitMQ.ConsoleApp
{
class Program
{
static void Main(string[] args)
{
string[] hosts = new string[] { "192.168.209.133", "192.168.209.134", "192.168.209.135" };
int port = 5672;
string userName = "admin";
string password = "123456";
string virtualHost = "/";
string queue = "queue1";
var arguments = new Dictionary<string, object>() { { "x-queue-type", "classic" } }; //消费者
new Thread(() =>
{
using (RabbitMQConsumer consumer = new RabbitMQConsumer(hosts))
{
consumer.UserName = userName;
consumer.Password = password;
consumer.Port = port;
consumer.VirtualHost = virtualHost; consumer.Received += result =>
{
Console.WriteLine($"接收到数据:{result.Body}");
result.Commit();//提交
};
consumer.Listen(queue, options =>
{
options.AutoAck = false;
options.Arguments = arguments;
});
}
}).Start(); //消息生产
using (RabbitMQProducer producer = new RabbitMQProducer(hosts))
{
producer.UserName = userName;
producer.Password = password;
producer.Port = port;
producer.VirtualHost = virtualHost; string message = "";
do
{
message = Console.ReadLine();
if (string.IsNullOrEmpty(message))
{
break;
}
producer.Publish(queue, message, options => { options.Arguments = arguments; }); } while (true);
}
}
}
}

普通模式

  

using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading; namespace RabbitMQ.ConsoleApp
{
class Program
{
static void Main(string[] args)
{
string[] hosts = new string[] { "192.168.209.133", "192.168.209.134", "192.168.209.135" };
int port = 5672;
string userName = "admin";
string password = "123456";
string virtualHost = "/";
string queue = "queue1";
var arguments = new Dictionary<string, object>() { { "x-queue-type", "classic" } }; //消费者1
new Thread(() =>
{
using (RabbitMQConsumer consumer = new RabbitMQConsumer(hosts))
{
consumer.UserName = userName;
consumer.Password = password;
consumer.Port = port;
consumer.VirtualHost = virtualHost; consumer.Received += result =>
{
Console.WriteLine($"消费者1接收到数据:{result.Body}");
result.Commit();//提交
};
consumer.Listen(queue, options =>
{
options.AutoAck = false;
options.Arguments = arguments;
options.FetchCount = 1;
});
}
}).Start(); //消费者2
new Thread(() =>
{
using (RabbitMQConsumer consumer = new RabbitMQConsumer(hosts))
{
consumer.UserName = userName;
consumer.Password = password;
consumer.Port = port;
consumer.VirtualHost = virtualHost; consumer.Received += result =>
{
Console.WriteLine($"消费者2接收到数据:{result.Body}");
result.Commit();//提交
};
consumer.Listen(queue, options =>
{
options.AutoAck = false;
options.Arguments = arguments;
options.FetchCount = 2;
});
}
}).Start(); //消息生产
using (RabbitMQProducer producer = new RabbitMQProducer(hosts))
{
producer.UserName = userName;
producer.Password = password;
producer.Port = port;
producer.VirtualHost = virtualHost; string message = "";
do
{
message = Console.ReadLine();
if (string.IsNullOrEmpty(message))
{
break;
}
producer.Publish(queue, message, options => { options.Arguments = arguments; }); } while (true);
}
}
}
}

Work模式

  

using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading; namespace RabbitMQ.ConsoleApp
{
class Program
{
static void Main(string[] args)
{
string[] hosts = new string[] { "192.168.209.133", "192.168.209.134", "192.168.209.135" };
int port = 5672;
string userName = "admin";
string password = "123456";
string virtualHost = "/";
string queue1 = "queue1";
string queue2 = "queue2";
string exchange = "demo.fanout";
string exchangeType = RabbitMQExchangeType.Fanout;
var arguments = new Dictionary<string, object>() { { "x-queue-type", "classic" } }; //消费者1
new Thread(() =>
{
using (RabbitMQConsumer consumer = new RabbitMQConsumer(hosts))
{
consumer.UserName = userName;
consumer.Password = password;
consumer.Port = port;
consumer.VirtualHost = virtualHost; consumer.Received += result =>
{
Console.WriteLine($"消费者1接收到数据:{result.Body}");
result.Commit();//提交
};
consumer.Listen(queue1, options =>
{
options.AutoAck = false;
options.Arguments = arguments;
});
}
}).Start(); //消费者2
new Thread(() =>
{
using (RabbitMQConsumer consumer = new RabbitMQConsumer(hosts))
{
consumer.UserName = userName;
consumer.Password = password;
consumer.Port = port;
consumer.VirtualHost = virtualHost; consumer.Received += result =>
{
Console.WriteLine($"消费者2接收到数据:{result.Body}");
result.Commit();//提交
};
consumer.Listen(queue2, options =>
{
options.AutoAck = false;
options.Arguments = arguments;
});
}
}).Start(); //消息生产
using (RabbitMQProducer producer = new RabbitMQProducer(hosts))
{
producer.UserName = userName;
producer.Password = password;
producer.Port = port;
producer.VirtualHost = virtualHost; string message = "";
do
{
message = Console.ReadLine();
if (string.IsNullOrEmpty(message))
{
break;
}
producer.Publish(exchange, "", message, options => { options.Type = exchangeType; }); } while (true);
}
}
}
}

发布订阅模式

  

using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading; namespace RabbitMQ.ConsoleApp
{
class Program
{
static void Main(string[] args)
{
string[] hosts = new string[] { "192.168.209.133", "192.168.209.134", "192.168.209.135" };
int port = 5672;
string userName = "admin";
string password = "123456";
string virtualHost = "/";
string queue1 = "queue1";
string queue2 = "queue2";
string exchange = "demo.direct";
string exchangeType = RabbitMQExchangeType.Direct;
var arguments = new Dictionary<string, object>() { { "x-queue-type", "classic" } }; //消费者1
new Thread(() =>
{
using (RabbitMQConsumer consumer = new RabbitMQConsumer(hosts))
{
consumer.UserName = userName;
consumer.Password = password;
consumer.Port = port;
consumer.VirtualHost = virtualHost; consumer.Received += result =>
{
Console.WriteLine($"消费者1接收到数据:{result.Body}");
result.Commit();//提交
};
consumer.Listen(queue1, options =>
{
options.AutoAck = false;
options.Arguments = arguments;
});
}
}).Start(); //消费者2
new Thread(() =>
{
using (RabbitMQConsumer consumer = new RabbitMQConsumer(hosts))
{
consumer.UserName = userName;
consumer.Password = password;
consumer.Port = port;
consumer.VirtualHost = virtualHost; consumer.Received += result =>
{
Console.WriteLine($"消费者2接收到数据:{result.Body}");
result.Commit();//提交
};
consumer.Listen(queue2, options =>
{
options.AutoAck = false;
options.Arguments = arguments;
});
}
}).Start(); //消息生产
using (RabbitMQProducer producer = new RabbitMQProducer(hosts))
{
producer.UserName = userName;
producer.Password = password;
producer.Port = port;
producer.VirtualHost = virtualHost; string message = "";
int index = 1;
string[] routes = new string[] { "apple", "banana" };
do
{
message = Console.ReadLine();
if (string.IsNullOrEmpty(message))
{
break;
}
var route = routes[index++ % 2];
producer.Publish(exchange, route, message, options => { options.Type = exchangeType; }); } while (true);
}
}
}
}

路由模式

  

using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading; namespace RabbitMQ.ConsoleApp
{
class Program
{
static void Main(string[] args)
{
string[] hosts = new string[] { "192.168.209.133", "192.168.209.134", "192.168.209.135" };
int port = 5672;
string userName = "admin";
string password = "123456";
string virtualHost = "/";
string queue1 = "queue1";
string queue2 = "queue2";
string exchange = "demo.topic";
string exchangeType = RabbitMQExchangeType.Topic;
var arguments = new Dictionary<string, object>() { { "x-queue-type", "classic" } }; //消费者1
new Thread(() =>
{
using (RabbitMQConsumer consumer = new RabbitMQConsumer(hosts))
{
consumer.UserName = userName;
consumer.Password = password;
consumer.Port = port;
consumer.VirtualHost = virtualHost; consumer.Received += result =>
{
Console.WriteLine($"消费者1接收到数据:{result.Body}");
result.Commit();//提交
};
consumer.Listen(queue1, options =>
{
options.AutoAck = false;
options.Arguments = arguments;
});
}
}).Start(); //消费者2
new Thread(() =>
{
using (RabbitMQConsumer consumer = new RabbitMQConsumer(hosts))
{
consumer.UserName = userName;
consumer.Password = password;
consumer.Port = port;
consumer.VirtualHost = virtualHost; consumer.Received += result =>
{
Console.WriteLine($"消费者2接收到数据:{result.Body}");
result.Commit();//提交
};
consumer.Listen(queue2, options =>
{
options.AutoAck = false;
options.Arguments = arguments;
});
}
}).Start(); //消息生产
using (RabbitMQProducer producer = new RabbitMQProducer(hosts))
{
producer.UserName = userName;
producer.Password = password;
producer.Port = port;
producer.VirtualHost = virtualHost; string message = "";
int index = 1;
string[] routes = new string[] { "apple.", "banana." };
do
{
message = Console.ReadLine();
if (string.IsNullOrEmpty(message))
{
break;
}
var route = routes[index % 2] + index++;
producer.Publish(exchange, route, message, options => { options.Type = exchangeType; }); } while (true);
}
}
}
}

主题模式

  上面是我自己做的封装,因为RabbitMQ.Client功能齐全,但是使用比较麻烦,需要编写的代码多一些,推荐一下第三方对rabbitmq的封装插件:EasyNetQ,它是建立在RabbitMQ.Client上的,多数时候可以直接通过EasyNetQ就可以完成消息发布与消费,感兴趣的可以了解一下

C# .net 环境下使用rabbitmq消息队列的更多相关文章

  1. 【消息队列】windows下安装RabbitMQ消息队列服务器

    RabbitMQ是什么 ? RabbitMQ是一个在AMQP基础上完整的,可复用的企业消息系统.他遵循Mozilla Public License开源协议. 1:安装RabbitMQ需要先安装Erla ...

  2. CentOS6.9下安装rabbitmq消息队列

    安装如下步骤: 首先安装erlang yum install erlang 安装rabbitmq rpm包 wget http://www.rabbitmq.com/releases/rabbitmq ...

  3. .net core使用rabbitmq消息队列

    看博文的朋友,本文有些过时了,还有些BUG,如果想了解更多用法,看看这篇吧:.net core使用rabbitmq消息队列 (二) 首先,如果你还没有安装好rabbitmq,可以参考我的博客: Ubu ...

  4. RabbitMQ学习系列二:.net 环境下 C#代码使用 RabbitMQ 消息队列

    一.理论: .net环境下,C#代码调用RabbitMQ消息队列,本文用easynetq开源的.net Rabbitmq api来实现. EasyNetQ 是一个易于使用的RabbitMQ的.Net客 ...

  5. RabbitMQ消息队列系列教程(二)Windows下安装和部署RabbitMQ

    摘要 本篇经验将和大家介绍Windows下安装和部署RabbitMQ消息队列服务器,希望对大家的工作和学习有所帮助! 目录 一.Erlang语言环境的搭建 二.RabbitMQ服务环境的搭建 三.Ra ...

  6. (三)RabbitMQ消息队列-Centos7下安装RabbitMQ3.6.1

    原文:(三)RabbitMQ消息队列-Centos7下安装RabbitMQ3.6.1 如果你看过前两章对RabbitMQ已经有了一定了解,现在已经摩拳擦掌,来吧动手吧! 用什么系统 本文使用的是Cen ...

  7. RabbitMQ消息队列(一): Detailed Introduction 详细介绍

     http://blog.csdn.net/anzhsoft/article/details/19563091 RabbitMQ消息队列(一): Detailed Introduction 详细介绍 ...

  8. RabbitMQ消息队列1: Detailed Introduction 详细介绍

    1. 历史 RabbitMQ是一个由erlang开发的AMQP(Advanced Message Queue )的开源实现.AMQP 的出现其实也是应了广大人民群众的需求,虽然在同步消息通讯的世界里有 ...

  9. (转)RabbitMQ消息队列(七):适用于云计算集群的远程调用(RPC)

    在云计算环境中,很多时候需要用它其他机器的计算资源,我们有可能会在接收到Message进行处理时,会把一部分计算任务分配到其他节点来完成.那么,RabbitMQ如何使用RPC呢?在本篇文章中,我们将会 ...

随机推荐

  1. spring cloud 通过 ribbon 实现客户端请求的负载均衡(入门级)

    项目结构 环境: idea:2020.1 版 jdk:8 maven:3.6.2 1. 搭建项目 ( 1 )父工程:spring_cloud_demo_parent pom 文件 <?xml v ...

  2. 【Linux】【Services】【Docker】基础理论

    1. 名称空间:NameSpace 内核级别,环境隔离: 1.1. 名称空间的历史 PID NameSpace:Linux 2.6.24 ,PID隔离 Network NameSpace:Linux ...

  3. Libev——ev_timer 相对时间定时器

    Libev中的超时监视器ev_timer,是简单的相对时间定时器,它会在给定的时间点触发超时事件,还可以在固定的时间间隔之后再次触发超时事件. 1.超时监视器ev_timer结构 typedef st ...

  4. 6、Redis五大数据类型---列表(List)

    一.列表(List)简介 单键多值:Redis 列表是简单的字符串列表,按照插入顺序排序.你可以添加一个元素到列表的头部(左边)或者尾部(右边). 它的底层实际是个双向链表,对两端的操作性能很高,通过 ...

  5. Mysql配置文件 扩展详细配置

    目录 配置文件中有些特定参数 扩展配置 max_connections connect_timeout interactive_timeout|wait_timeout net_retry_count ...

  6. 用法总结:NSArray,NSSet,NSDictionary

    用法总结:NSArray,NSSet,NSDictionary Foundation framework中用于收集cocoa对象(NSObject对象)的三种集合分别是: NSArray 用于对象有序 ...

  7. java多线程7:ReentrantReadWriteLock

    真实的多线程业务开发中,最常用到的逻辑就是数据的读写,ReentrantLock虽然具有完全互斥排他的效果(即同一时间只有一个线程正在执行lock后面的任务), 这样做虽然保证了实例变量的线程安全性, ...

  8. 「Python实用秘技03」导出项目的极简环境依赖

    本文完整示例代码及文件已上传至我的Github仓库https://github.com/CNFeffery/PythonPracticalSkills 这是我的系列文章「Python实用秘技」的第3期 ...

  9. CF447B DZY Loves Strings 题解

    Content 有一个长度为 \(n\) 的仅含小写字母的字符串 \(s\) 以及 26 个英文小写字母的价值 \(W_\texttt{a},W_\texttt{b},...,W_\texttt{z} ...

  10. redis启动报错 var/run/redis_6379.pid exists, process is already running or crashed

    redis启动显示 /var/run/redis_6379.pid exists, process is already running or crashed 出现这个执行 rm -rf /var/r ...