.Net CoreRabbitMQ基本使用

队列模式

https://www.rabbitmq.com/getstarted.html

e17efa51583a6e8b20a94cfec747ded3.png

42da86fbbf3d8487ee88c6f00b2f5533.png

对以上几种模式进行简要分类,可以分成如下三类(RPC暂不考虑)

  • 简单队列模式,单发单收,一对一模式

  • Worker模式,单发多收(一个消息一个接收者,多个消息多个接收者),一对多模式

  • 发布订阅模式,包括发布订阅、路由、通配符模式,这三种只是交换机类型不同

简单队列模式

队列作为中间件,在生产者和消费者中间承担消息传递的通道

1fb04b9f33cc7d3299d83a33893b97fb.png

创建两个控制台项目RabbitMQDemo.Basic.Producer和RabbitMQDemo.Basic.Consumer并安装Nuget包以支持对RabbitMQ操作

install-package rabbitmq.client

生产者代码

  • 通过IConnectionFactory,IConnection和IModel来创建连接和信道。IConnection实例对象负责与RabbitMQ 服务端的连接,信道是在这连接基础上创建虚拟连接,通过复用来减少性能开销且便于管理。

  • 通过QueueDeclare方法创建消息队列,设置消息队列本身的一些属性信息。

  • 发送消息时调用BasicPublish来发送消息,通过exchange和routingkey参数满足大部分匹配消息队列的场景。

var connFactory = new ConnectionFactory
{HostName = "xxx.xxx.xxx.xxx",Port = 5672,UserName = "rabbitmqdemo",Password = "rabbitmqdemo@test",VirtualHost = "rabbitmqdemo"};
using (var conn = connFactory.CreateConnection())
{using (var channel = conn.CreateModel()){var queueName = "helloworld";channel.QueueDeclare(queue: queueName, durable: false, exclusive: false, autoDelete: false, arguments: null);while (true){Console.WriteLine("消息内容(exit退出):");var message = Console.ReadLine();if (message.Trim().ToLower() == "exit"){break;}var body = Encoding.UTF8.GetBytes(message);channel.BasicPublish(exchange: "", routingKey: queueName, basicProperties: null, body: body);Console.WriteLine("消息内容发送完毕:" + message);}}
}

消费者代码

  • 消费端同样先创建连接和信道

  • 同样在消费端也会进行队列声明,以确保当生产者并未创建或是手动没有创建情况下不会出现队列不存在的异常。

  • 定义一个EventingBasicConsumer对象的消费者,然后定义接收事件,输出从消息队列中接收的数据,

  • 最后调用BasicConsume方法来启动消费者监听

var connFactory = new ConnectionFactory
{HostName = "xxx.xxx.xxx.xxx",Port = 5672,UserName = "rabbitmqdemo",Password = "rabbitmqdemo@test",VirtualHost = "rabbitmqdemo"};
using (var conn = connFactory.CreateConnection())
{using (var channel = conn.CreateModel()){var queueName = "helloworld";channel.QueueDeclare(queue: queueName, durable: false, exclusive: false, autoDelete: false, arguments: null);var consumer = new EventingBasicConsumer(channel);consumer.Received += (model, ea) =>{var message = ea.Body;Console.WriteLine("接收到信息为:" + Encoding.UTF8.GetString(message.ToArray()));((EventingBasicConsumer)model).Model.BasicAck(ea.DeliveryTag, false);};channel.BasicConsume(queue: queueName, autoAck: false, consumer: consumer);Console.ReadKey();}
}

执行过程

运行代码,可以在管理页面中看到队列声明好了

89bece532dda5d200b42df9fc54d851d.png

生产者发送消息,经RabbitMQ,消费者端接收消息

76ad59668f800aee89e1749302347d7b.png

Worker模式

简单队列模式下,当多开消费者时,便演化到了Worker模式,这种情况下不再考虑基础的怎么用,而是要如何协调多个消费者的工作。

34a5e486dc25a169ce56fcae524e7e30.png

与简单队列模式类似再建立三个控制台项目RabbitMQDemo.Worker.Producer和RabbitMQDemo.Worker.ConsumerA和RabbitMQDemo.Worker.ConsumerB并安装Nuget,抄袭第一部分代码,更改个队列名字,然后直接跑起来,其实是一样的消费模式。

基本使用

当发送多条消息,两个消费者都能够展示消息,并且,其中的消息总是只会被一个消费者所拥有,默认分配方式是轮询。

968edb54436ec58ea1551b33dd63bb3c.png

消费能力

现在,思考下如何能够各消费者的消费能力,来消费消息,这更侧重于消费端了。

将ConsumerA、B在消费时各增加Sleep 1秒和10秒,以区分消费能力的不同。当再次发送消息时,因消费端出现着处理消息耗时的不同,展示数据的时间也不同,但是消息的分配却没有变化。

70119cc1b97f4466509212f8d5bf6bab.png

需要进一步均衡的分配任务,按照消费能力高的分配多,消费能力低的分配少。

d3a38e2427417e19cd1c0df983daeb44.png

当消费能力不同时,可以将消费的任务均衡分配,这样来使得整体消费端的能力充分发挥。

负载能力

RabbitMQ提供了一个属性可以设置各消费端的能力,以此可以根据能力分配消息。

在消费端代码中更改下Qos(quality-of-service),即消费端最多接收未被ack的消息个数,举个例子:

  • 如果输入1,当消费端接收到一个消息,但是没有应答(活还在干别再分配任务了),则消费端不会收到下一个消息,消息只会在队列中阻塞。

  • 如果输入3,那么可以最多有3个消息不应答(可以同时干三个活),如果到达了3个,则当有分配给这个消费端的消息时只会在阻塞到队列中,消费端不会接收到消息。
    对ConsumerA、B分别设置值prefetchCount为10和1。

ConsumerA: channel.BasicQos(0, prefetchCount:10, false);
ConsumerB: channel.BasicQos(0, prefetchCount:1, false);

当再次发送消息时,会因为因为A、B两端的消费能力不同而出现消息聚集侧重于一端
生产者发送一堆消息,两个消费者自身的消费能力不同,设置的能够消费的容量不同,这样分配得到的消息数量也不同。

f1bb3ca4575d88461971d67354060352.png

生产者代码

此处并未做任何大的改变,只是将队列名更改为当前模式的名字以示区分。

var connFactory = new ConnectionFactory
{HostName = "xxx.xxx.xxx.xxx",Port = 5672,UserName = "rabbitmqdemo",Password = "rabbitmqdemo@test",VirtualHost = "rabbitmqdemo"};
using (var conn = connFactory.CreateConnection())
{using (var channel = conn.CreateModel()){var queueName = "worker";channel.QueueDeclare(queue: queueName, durable: false, exclusive: false, autoDelete: false, arguments: null);while (true){Console.WriteLine("消息内容(exit退出):");var message = Console.ReadLine();if (message.Trim().ToLower() == "exit"){break;}var body = Encoding.UTF8.GetBytes(message);channel.BasicPublish(exchange: "", routingKey: queueName, basicProperties: null, body: body);Console.WriteLine("消息内容发送完毕:" + message);}}
}

消费者代码

var connFactory = new ConnectionFactory
{HostName = "xxx.xxx.xxx.xxx",Port = 5672,UserName = "rabbitmqdemo",Password = "rabbitmqdemo@test",VirtualHost = "rabbitmqdemo"};
using (var conn = connFactory.CreateConnection())
{using (var channel = conn.CreateModel()){var queueName = "worker";channel.QueueDeclare(queue: queueName, durable: false, exclusive: false, autoDelete: false, arguments: null);channel.BasicQos(0, 1, false);var consumer = new EventingBasicConsumer(channel);consumer.Received += (model, ea) =>{Thread.Sleep(10000);var message = ea.Body;Console.WriteLine("接收到信息为:" + Encoding.UTF8.GetString(message.ToArray()) + DateTime.Now.ToString("hh:mm:ss"));((EventingBasicConsumer)model).Model.BasicAck(ea.DeliveryTag, false);};channel.BasicConsume(queue: queueName, autoAck: false, consumer: consumer);Console.ReadKey();}
}

Exchange模式

发布订阅,路由和通配符这三种可以算为一种模式,区别仅仅是交互机类型不同。

  • 发布订阅模式:使用fanout类型交换机

  • 路由模式:使用direct类型交换机

  • 通配符模式:使用topic类型交换机

03f6ec665f34a43588f7ba7c5fd190dd.png

生产者将消息及RoutingKey发送到指定交换机,消费者创建各自的消息队列并绑定到交换机,交换机根据路由规则匹配生产者发送的RoutingKey转发消息到相应队列中,其本身不存储消息。

Exchange类型

简要介绍这几种交换机类型,本身只是对路由规则的匹配方式不同。

  • fanout: 把所有发送到该交换机的消息路由到所有与该交换机绑定的队列中。

  • direct: 把消息路由到那些BindingKey和RoutingKey完全匹配的队列中。

  • topic: 把消息路由到那些BindingKey和RoutingKey相匹配的队列中。

  • header: 不依赖RoutingKey的匹配规则,而是根据消息内容中的headers属性匹配(性能差,不实用,使用少)。

注意:BindingKey为交换机和队列绑定时指定的RoutingKey,发送消息时也会给定一个RoutingKey,两者会按照交换机类型的不同而匹配

发布订阅模式(fanout)

fanout模式下会把所有发送到该交换机的消息路由到所有与该交换机绑定的队列中。

f9f99aeaea5481bc946d3e81f12416fc.png

当生产者发送消息到指定交换机,该交换机会将消息路由到绑定的Queue1和Queue2,两个队列分别转发给其下绑定的消费者,从单个队列视角来看,便是Worker模式了。

生产者代码

var connFactory = new ConnectionFactory
{HostName = "xxx.xxx.xxx.xxx",Port = 5672,UserName = "rabbitmqdemo",Password = "rabbitmqdemo@test",VirtualHost = "rabbitmqdemo"};
using (var conn = connFactory.CreateConnection())
{using (var channel = conn.CreateModel()){var exchangeName = "publishsubscribe_exchange";channel.ExchangeDeclare(exchange: exchangeName, type: "fanout");while (true){Console.WriteLine("消息内容(exit退出):");var message = Console.ReadLine();if (message.Trim().ToLower() == "exit"){break;}var body = Encoding.UTF8.GetBytes(message);channel.BasicPublish(exchange: exchangeName, routingKey: "", basicProperties: null, body: body);Console.WriteLine("消息内容发送完毕:" + message);}}
}

如上生产者端在worker模式的基础上,改动了几处

  • 消息队列声明变成了交换机声明,其类型为fanout。

  • 发送消息时由指定相应的队列名改成了空,而指定了交换机名称。

  • routingKey留空,fanout无需关心routingKey。

消费者代码

此处设置两个queue,分别为publishsubscribe_exchange_worker_1和publishsubscribe_exchange_worker_2

var connFactory = new ConnectionFactory
{HostName = "xxx.xxx.xxx.xxx",Port = 5672,UserName = "rabbitmqdemo",Password = "rabbitmqdemo@test",VirtualHost = "rabbitmqdemo"};
using (var conn = connFactory.CreateConnection())
{using (var channel = conn.CreateModel()){var exchangeName = "publishsubscribe_exchange";channel.ExchangeDeclare(exchange: exchangeName, type: "fanout");var queueName = exchangeName + "_worker_1";channel.QueueDeclare(queue: queueName, durable: false, exclusive: false, autoDelete: false, arguments: null);channel.QueueBind(queue: queueName, exchange: exchangeName, routingKey: "");channel.BasicQos(0, 10, false);var consumer = new EventingBasicConsumer(channel);consumer.Received += (model, ea) =>{Thread.Sleep(1000);var message = ea.Body;Console.WriteLine("接收到信息为:" + Encoding.UTF8.GetString(message.ToArray()) + DateTime.Now.ToString("hh:mm:ss"));((EventingBasicConsumer)model).Model.BasicAck(ea.DeliveryTag, false);};channel.BasicConsume(queue: queueName, autoAck: false, consumer: consumer);Console.ReadKey();}
}

与Worker的消费者端相比,代码上也做了些调整,其余是保持一致的。

  • 增加了交换机名称并声明了交换机且类型为fanout,这和生产者端保持一致了

  • 将队列名和交换机名进行了绑定

  • routingKey留空,fanout无需关心routingKey。

执行过程

当启动生产者端和消费者端时,交换机和两个队列都声明完毕

4ba746342dc4483debf4c776822a98fa.png

同时,点入交换机中,可以看到该交换机下绑定了两个队列

595fc71b09b585b9c53ddcc5860f4afe.png

这样一来,当有消息到达交换机,交换机可以根据消息名来路由到相应的队列。因此处设置的routekey是空的,两个队列绑定时用的routekey也是空的,因此两个队列都符合路由规则,则消息会同时存在于两个队列中。

5a0999c2fd4b3d769872f3beb11b8a3b.png

路由模式(direct)

direct模式下会把消息路由到那些BindingKey和RoutingKey完全匹配的队列中。

e8f689fd7239bdbebc9fcad0455637a9.png

当生产者发送了一个消息且发送的RoutingKey为Warning时,交换机会根据该RoutingKey匹配并转发消息到Queue1和Queue2,两个队列都满足了路由规则,当RoutingKey为Info是,仅Queue2满足,则将消息转发给Queue2。

生产者代码

生产者端在worker模式的基础上,只需改动几处

  • 交换机类型从fanout变更为direct

  • 生产者发送消息时指定RoutingKey,原先是留空

var connFactory = new ConnectionFactory
{HostName = "xxx.xxx.xxx.xxx",Port = 5672,UserName = "rabbitmqdemo",Password = "rabbitmqdemo@test",VirtualHost = "rabbitmqdemo"};
using (var conn = connFactory.CreateConnection())
{using (var channel = conn.CreateModel()){var exchangeName = "routing_exchange";channel.ExchangeDeclare(exchange: exchangeName, type: "direct");while (true){Console.WriteLine("消息RoutingKey(warning or info):");var routingKey = Console.ReadLine();Console.WriteLine("消息内容(exit退出):");var message = Console.ReadLine();if (message.Trim().ToLower() == "exit"){break;}var body = Encoding.UTF8.GetBytes(message);channel.BasicPublish(exchange: exchangeName, routingKey: routingKey, basicProperties: null, body: body);Console.WriteLine("消息内容发送完毕:" + message);}}
}

消费者代码

接收者端在发布订阅模式的基础上增加了交换机和队列时绑定的key,用于交换机路由规则时选择队列。

be1ca82fa4011dcca448321c1c4025ba.png

如下为Queue2下的消费者,为Queue2设置了info和warning两个RoutingKey用于交换机和队列绑定。

var connFactory = new ConnectionFactory
{HostName = "xxx.xxx.xxx.xxx",Port = 5672,UserName = "rabbitmqdemo",Password = "rabbitmqdemo@test",VirtualHost = "rabbitmqdemo"};
using (var conn = connFactory.CreateConnection())
{using (var channel = conn.CreateModel()){var exchangeName = "routing_exchange";channel.ExchangeDeclare(exchange: exchangeName, type: "direct");var queueName = exchangeName + "_worker_1";channel.QueueDeclare(queue: queueName, durable: false, exclusive: false, autoDelete: false, arguments: null);var routingKey1 = "warning";channel.QueueBind(queue: queueName, exchange: exchangeName, routingKey: routingKey1);var routingKey2 = "info";channel.QueueBind(queue: queueName, exchange: exchangeName, routingKey: routingKey2);channel.BasicQos(0, 10, false);var consumer = new EventingBasicConsumer(channel);consumer.Received += (model, ea) =>{Thread.Sleep(1000);var message = ea.Body;Console.WriteLine("接收到信息为:" + Encoding.UTF8.GetString(message.ToArray()) + DateTime.Now.ToString("hh:mm:ss"));((EventingBasicConsumer)model).Model.BasicAck(ea.DeliveryTag, false);};channel.BasicConsume(queue: queueName, autoAck: false, consumer: consumer);Console.ReadKey();}
}

执行过程

运行代码,交换机、队列及两者的绑定先完成,可以在管理页面中看到声明的信息。

8ba86c06505b540cac181165340cc908.png

当生产者发送消息且RoutingKey为warning,两个队列都满足路由条件接收到消息,两个消费者都展示了消息。

当发送消息且RoutingKey为info,queue2队列满足路由条件接收了消息,一个消费者展示了消息。

1eb834b606dfd67babd5f8635035a925.png

通配符模式(topic)

topic模式会把消息路由到那些BindingKey和RoutingKey相匹配的队列中。

33e64d2221f3ac851a19e2b48355a2b5.png

topic类型与direct类型相似,但匹配规则上有所不同,direct需要完全匹配,topic可以设置通配符以达到局部匹配即可满足。

和direct不同的是,topic设定的RoutingKey(不论是BindingKey还是RoutingKey)都需要为带"."的字符串。比如a.b、c.d.e、fff.gggg.hhhh等,最多为 255 个字节。

在交换机和队列绑定时,给定的RoutingKey可以依照如下来设置。

  • #:匹配0~N个单词

  • *:匹配1个单词
    举例说明下,比如两个RoutingKey分别为#.created和index.,当生产者发送消息时给定的RoutingKey为a.created、aa.created或是b.created等都满足#.created的规则,index.a、index.b或index.c等都满足index.的规则。

生产者代码

在路由模式的基础上更改交换机类型为topic

var connFactory = new ConnectionFactory
{HostName = "xxx.xxx.xxx.xxx",Port = 5672,UserName = "rabbitmqdemo",Password = "rabbitmqdemo@test",VirtualHost = "rabbitmqdemo"};
using (var conn = connFactory.CreateConnection())
{using (var channel = conn.CreateModel()){var exchangeName = "topics_exchange";channel.ExchangeDeclare(exchange: exchangeName, type: "topic");while (true){Console.WriteLine("消息RoutingKey:");var routingKey = Console.ReadLine();Console.WriteLine("消息内容(exit退出):");var message = Console.ReadLine();if (message.Trim().ToLower() == "exit"){break;}var body = Encoding.UTF8.GetBytes(message);channel.BasicPublish(exchange: exchangeName, routingKey: routingKey, basicProperties: null, body: body);Console.WriteLine("消息内容发送完毕:" + message);}}
}

消费者代码

接收者端在路由模式的基础上更改了交换机和队列绑定的key,可以方便满足多种情况下的需要。

2bba2aeb60522f5e83e41e3c8b0490d5.png

如下为Queue2下的消费者,为Queue2设置了index.*和#.created.#两个RoutingKey用于交换机和队列绑定。

var connFactory = new ConnectionFactory
{HostName = "xxx.xxx.xxx.xxx",Port = 5672,UserName = "rabbitmqdemo",Password = "rabbitmqdemo@test",VirtualHost = "rabbitmqdemo"};
using (var conn = connFactory.CreateConnection())
{using (var channel = conn.CreateModel()){var exchangeName = "topics_exchange";channel.ExchangeDeclare(exchange: exchangeName, type: "topic");var queueName = exchangeName + "_worker_2";channel.QueueDeclare(queue: queueName, durable: false, exclusive: false, autoDelete: false, arguments: null);var routingKey1 = "index.*";channel.QueueBind(queue: queueName, exchange: exchangeName, routingKey: routingKey1);var routingKey2 = "#.created.#";channel.QueueBind(queue: queueName, exchange: exchangeName, routingKey: routingKey2);channel.BasicQos(0, 1, false);var consumer = new EventingBasicConsumer(channel);consumer.Received += (model, ea) =>{Thread.Sleep(10000);var message = ea.Body;Console.WriteLine("接收到信息为:" + Encoding.UTF8.GetString(message.ToArray()) + DateTime.Now.ToString("hh:mm:ss"));((EventingBasicConsumer)model).Model.BasicAck(ea.DeliveryTag, false);};channel.BasicConsume(queue: queueName, autoAck: false, consumer: consumer);Console.ReadKey();}
}

执行过程

运行代码,交换机、队列及绑定关系,相应RoutingKey都在管理页面中展示

1188845f4cb1d8940b75bcf89edbf4e5.png

当生产者发送消息且RoutingKey为#.created,两个队列都满足路由条件接收到消息,两个消费者都展示了消息。

当生产者发送消息且RoutingKey为#.created.#,queue2队列满足了路由条件接收了消息,一个消费者展示了消息。

8b635e0dba085c80b3a9b3b897cf3217.png

总结

对于在生产者和消费者间解耦,完成异步协作,消息队列可太方便了,暂不深入考虑三者间如何可靠传输,仅看消息队列提供的多种交换机模式,很大程度上满足实际使用中需要用到的很多功能。

2022-07-29,望技术有成后能回来看见自己的脚步

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/283105.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux包系列的知识(附:Ubuntu16.04升级到18.04的案例)

Linux基础:https://www.cnblogs.com/dunitian/p/4822808.html#linux 之前看到朋友还动不动 apt-get update upgrade,就很纳闷,后来发现原来他只是知道这个更新命令却不知其意,所以每次安装个包就把所有apt-get的常用清除更新命令打…

java获取tomcat目录结构_Tomcat目录结构详解

Tomcat目录结构图如下:bin目录存放一些可执行的二进制文件,.sh结尾的为linux下执行命令,.bat结尾的为windows下执行命令。catalina.sh:真正启动tomcat文件,可以在里面设置jvm参数。startup.sh:启动tomcat(需…

智慧农业物联网云平台方案

2019独角兽企业重金招聘Python工程师标准>>> 多比智慧农业物联网云平台解决方案结合了最先进的物联网、云计算、传感器、自动控制等, 在浏览器或手机客户端实时显示大棚、大田、温室等温度、湿度、PH值、光强度、CO2,或作为自动控制的参变量参与到自动控…

使用JDBC获取Oracle连接时报错

The Network Adapter could not establish the connection 网络适配器不能创建连接 作为初学者的来说,这个问题让我找了好多次,每次重新开启电脑时就可以正常获取连接,过了一会儿,自己不知道做了什么就会又报错,…

.Net CoreRabbitMQ消息转发可靠机制(上)

前言生产者发送消息到了队列,队列推送数据给了消费者,这里存在一些问题需要思考下生产者如何确保消息一定投递到了队列中RabbitMQ 丢失了消息(下文暂不涉及这块)队列如何确保消费者收到了消息呢生产者可靠发送执行流程当生产者将消息发送出去后&#xff…

一个java文件中可包含多个main方法

java中的main方法是java应用程序的入口,java程序在运行时,首先调用执行main方法。但并不是说java中只能有一个main方法,不同类中都可以包含main方法。当JVM进行编译时,会提示选择其中一个main方法作为编译的入口。 转载于:https:/…

【SRM-05 B】无题?

Description 有一个拥有n个城市的国家。这个国家由n-1条边连接起来。有一天国家发生叛乱。叛军已占领了一些城市。如果叛军占领的城市中,存在两个城市之间有边直接相连,则称这种情况是坏的。现在并不知道叛军占领了那些城市,问有多少种情况是…

分享一些 Java 后端的个人干货

学习 Java 也有了不少时间,入 Java 后台的坑也有了一段时日。这段时间里,听过许多前辈的经验与分享,也看过许多大佬的文章和作品。找了个时间整理和总结了一下我个人到目前为止一路以来的听到看到或者自己感悟到的干货。 这篇文章可能更多的是…

.NET MAUI实战 Routing

1.详情本章继续分享.NET MAUI中的路由,这个概念依旧是在Prism里存在过的概念。如果使用过Prism框架的小伙伴使用该机制上手速度是非常快的。接下来一起来看看什么是路由。.NET 多平台应用 UI (.NET MAUI) Shell 包含基于 URI 的导航体验,该体验使用路由导…

分享Web应用运行的细节问题:预编译提高网站性能、跟踪用户习惯和解决线程同步...

在这个文章里,我将分享一下在iOpenWorks.com这个网站试运行中碰到的若干问题和解决方案,这些问题包含了:(1)如何通过ASP.NET MVC预编译提高性能;(2)如何知道网站在运行中&#xff0c…

mondrain配置mysql_mondrian 4.7 源码部署(示例代码)

mondrian是一个开源的数据分析工程, 网上有关mondrian3.X的源码部署比较多, 有关4.X的部署较少. 目前官方推荐使用的时mondrian3.7的修订版, 可以再github上下载到最近更新维护的mondrian-master, 下载下来后基本上只需要按部就班的使用maven build一下就可以正常使用了, 如有问…

腾讯云DevOps技术揭秘:新时代运维重器Tencent Hub最佳实践

随着云计算和容器技术的发展以及微服务架构的兴起,服务能够实现细粒度的部署,维护和伸缩。在使开发人员能快速开发的同时,这些技术也给系统和应用的运维带来了更大的挑战。DevOps理念也应运而生,强调研发和运维的流程及工具的自动…

.Net CoreRabbitMQ消息存储可靠机制(下)

前言上篇讨论过消息投递和消息消费过程中如何确保可靠传输,也提及到消息到达RabbitMQ中到被消费前也需要可靠的留存,可因许多的不确定因素会影响着消息的存在与否。消息中转点生产者发送消息到RabbitMQ中,如果交换机根据自身类型和RoutingKey…

nginx安装及负载均衡配置

Nginx (“engine x”) 是一个高性能的 HTTP 和 反向代理 服务器,也是一个 IMAP/POP3/SMTP 代理服务器。 Nginx 是由 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的,第一个公开版本0.1.0发布于2004年10月4日。其将源代码以类BSD许可证的形式发…

智能停车O2O 独角兽初现:“ETCP停车”获5000万美金A轮融资

日前,国内第一智能停车平台“ETCP停车”宣布完毕A轮融资,由源代码资本、SIG、易车网、经纬中国和商企界知名人士联合投资超过5000万美金。同一时候获悉,ETCP作为中国智能停车行业龙头老大,不仅是该行业的创建者和领军品牌&#xf…

ASP.NET MVC使用Bootstrap系统(2)——使用Bootstrap CSS和HTML元素

阅读目录 Bootstrap 栅格(Grid)系统Bootstrap HTML元素Bootstrap 验证样式ASP.NET MVC创建包含Bootstrap样式编辑模板小结Bootstrap提供了一套丰富CSS设置、HTML元素以及高级的栅格系统来帮助开发人员快速布局网页。所有的CSS样式和HTML元素与移动设备优…

VS2017调试闪退之Chrome

原文:VS2017调试闪退之Chrome巨硬build后发了15.7.1满载期待的升级了。。结果NM泪奔................... 为啥 泪奔? 使用Chrome 调试闪退,MMP ,一想肯定是VS的锅咯,各种抓头发。。 试试看看VS配置发现 ,多了点东西。。 都勾上后&…

ios UISearchBar搜索框的基本使用

摘要: 小巧简洁的原生搜索框,漂亮而易用,如果我们的应用没有特殊需求,都可以使用它。iOS中UISearchBar(搜索框)使用总结 初始化:UISearchBar继承于UIView,我们可以像创建View那样创建searchBar UISearchBar * bar [[U…

Win8下怎样安装Win7 or Win7下怎样安装win8?

预计非常多人可能会用U盘安装工具去去做双系统的安装(Win8下安装Win7, Win7下安装Win8)。可是在安装过程中你 会发现一个问题:win7下安装win8,提示你mbr硬盘格式不能安装win8;win8下安装win7,提…

Linux 练习题-3文件与磁盘 问答

1、描述Liux下软链接和硬链接的区别创建命令不同,ln 命令创建硬链接,ln -s 创建软链接inode节点号不同,硬链接inode与源文件相同,软链接inode与源文件不同使用对象不同,硬链接只能对文件使用,软链接可以对文…