消息队列的地位越来越重要,几乎是面试的必问问题了,不会使用几种消息队列都显得尴尬,正好本文使用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. 【Linux】【Basis】文件系统

    FHS:Filesystem Hierarchy Standard Web site: https://wiki.linuxfoundation.org/lsb/fhs http://www.path ...

  2. linux下把一个用户从某个组中删除,而不删除用户

    查看当前用户/登录用户 基本语法 whoami / who am I 用户组 介绍 类似于角色,系统可以对有共性的多个用户进行统一的管理. 新增组 语法 groupadd 组名 案例演示 添加test ...

  3. linux 磁盘满了,vim 编辑文件时无法保存

    早上来发现 redis 不能用,报 MISCONF Redis is configured to save RDB snapshots, but it is currently not able to ...

  4. 登录界面.jsp

    <!DOCTYPE html><html lang="zh-CN"> <head> <meta charset="utf-8&q ...

  5. 分布式全局ID生成器原理剖析及非常齐全开源方案应用示例

    为何需要分布式ID生成器 **本人博客网站 **IT小神 www.itxiaoshen.com **拿我们系统常用Mysql数据库来说,在之前的单体架构基本是单库结构,每个业务表的ID一般从1增,通过 ...

  6. Jenkins安全加固

    1.jenkins未授权访问 描述 jenkins不当配置可导致未授权访问管理控制台,可以通过脚本命令行执行系统命令.通过该漏洞,可以后台管理服务,通过脚本命令行功能执行系统命令,如反弹shell,w ...

  7. CPU状态信息us,sy等含义

    目录 一.概述概述 二.详解 us和sy ni id wa hi和si st 三.总结 一.概述概述 比如一秒内有100个cpu时间片,这个cpu时间片就是cpu工作的最小单位.那么这100个cpu时 ...

  8. 转:Intent 操作常用URI代码示例

    以下是常用到的Intent的URI及其示例,包含了大部分应用中用到的共用Intent 一.打开一个网页,类别是Intent.ACTION_VIEW 1 2 Uri uri = Uri.parse(&q ...

  9. [BUUCTF]PWN——jarvisoj_tell_me_something

    jarvisoj_tell_me_something 附件 步骤: 例行检查,64位程序,开启了NX保护 运行一下程序,看看程序的大概流程 64位ida载入,shift+f12检索程序里的字符串 看到 ...

  10. Windows线程控制

    多线程无疑带来了很多方便,提高了很多开发效率,但是同时也带来了很多问题. 举个栗子: DWORD WINAPI ThreadProc(LPVOID lPParameter); int m = 0; i ...