RabbitMQ简介和六种工作模式详解

一、RabbitMQ简介

是一个开源的消息代理和队列服务器,用来通过普通协议在完全不同的应用之间共享数据,RabbitMQ是使用Erlang(高并发语言)语言来编写的,并且RabbitMQ是基于AMQP协议的。

1.1 AMQP协议

Advanced Message Queuing Protocol(高级消息队列协议)

1.2 AMQP专业术语:(多路复用->在同一个线程中开启多个通道进行操作)

  • Server:又称broker,接受客户端的链接,实现AMQP实体服务

  • Connection:连接,应用程序与broker的网络连接

  • Channel:网络信道,几乎所有的操作都在channel中进行,Channel是进行消息读写的通道。客户端可以建立多个channel,每个channel代表一个会话任务。

  • Message:消息,服务器与应用程序之间传送的数据,由Properties和Body组成.Properties可以对消息进行修饰,必须消息的优先级、延迟等高级特性;Body则是消息体内容。

  • virtualhost: 虚拟地址,用于进行逻辑隔离,最上层的消息路由。一个virtual host里面可以有若干个Exchange和Queue,同一个Virtual Host 里面不能有相同名称的Exchange 或 Queue。

  • Exchange:交换机,接收消息,根据路由键转单消息到绑定队列

  • Binding: Exchange和Queue之间的虚拟链接,binding中可以包换routing key

  • Routing key: 一个路由规则,虚拟机可用它来确定如何路由一个特定消息。(如负载均衡)

1.3 RabbitMQ整体架构

 

 

ClientA(生产者)发送消息到Exchange1(交换机),同时带上RouteKey(路由Key),Exchange1找到绑定交换机为它和绑定传入的RouteKey的队列,把消息转发到对应的队列,消费者Client1,Client2,Client3只需要指定对应的队列名即可以消费队列数据。

交换机和队列多对多关系,实际开发中一般是一个交换机对多个队列,防止设计复杂化。

 

二、安装RabbitMQ

安装方式不影响下面的使用,这里用Docker安装

#15672端口为web管理端的端口,5672为RabbitMQ服务的端口
docker run -d  --name rabbitmq -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=123456 -p 15672:15672 -p 5672:5672 rabbitmq:3-management

输入:ip:5672访问验证。

建一个名为develop的Virtual host(虚拟主机)使用,项目中一般是一个项目建一个Virtual host用,能够隔离队列。

切换Virtual host

三、RabbitMQ六种队列模式在.NetCore中使用

(1)简单队列

最简单的工作队列,其中一个消息生产者,一个消息消费者,一个队列。也称为点对点模式

 

 

 

描述:一个生产者 P 发送消息到队列 Q,一个消费者 C 接收

建一个RabbitMQHelper.cs类

/// <summary>/// RabbitMQ帮助类/// </summary>public class RabbitMQHelper{private static ConnectionFactory factory;private static object lockObj = new object();/// <summary>/// 获取单个RabbitMQ连接/// </summary>/// <returns></returns>public static IConnection GetConnection(){if (factory == null){lock (lockObj){if (factory == null){factory = new ConnectionFactory{HostName = "172.16.2.84",//ipPort = 5672,//端口UserName = "admin",//账号Password = "123456",//密码VirtualHost = "develop" //虚拟主机};}}}return factory.CreateConnection();}}

生产者代码:

新建发送类Send.cs

 public static void SimpleSendMsg(){string queueName = "simple_order";//队列名//创建连接using (var connection = RabbitMQHelper.GetConnection()){//创建信道using (var channel = connection.CreateModel()){//创建队列channel.QueueDeclare(queueName, durable: false, exclusive: false, autoDelete: false, arguments: null);for (var i = 0; i < 10; i++){string message = $"Hello RabbitMQ MessageHello,{i + 1}";var body = Encoding.UTF8.GetBytes(message);//发送消息channel.BasicPublish(exchange: "", routingKey: queueName, mandatory: false, basicProperties: null, body);Console.WriteLine($"发送消息到队列:{queueName},内容:{message}");}}}}

创建队列参数解析:

durable:是否持久化。

exclusive:排他队列,只有创建它的连接(connection)能连,创建它的连接关闭,会自动删除队列。

autoDelete:被消费后,消费者数量都断开时自动删除队列。

arguments:创建队列的参数。

发送消息参数解析:

exchange:交换机,为什么能传空呢,因为RabbitMQ内置有一个默认的交换机,如果传空时,就会用默认交换机。

routingKey:路由名称,这里用队列名称做路由key。

mandatory:true告诉服务器至少将消息route到一个队列种,否则就将消息return给发送者;false:没有找到路由则消息丢弃。

执行效果:

 

 

队列产生10条消息。

 

 消费者代码:

新建Recevie.cs类

 public static void SimpleConsumer(){string queueName = "simple_order";var connection = RabbitMQHelper.GetConnection();{//创建信道var channel = connection.CreateModel();{//创建队列channel.QueueDeclare(queueName, durable: false, exclusive: false, autoDelete: false, arguments: null);var consumer = new EventingBasicConsumer(channel);int i = 0;consumer.Received += (model, ea) =>{//消费者业务处理var message = Encoding.UTF8.GetString(ea.Body.ToArray());Console.WriteLine($"{i},队列{queueName}消费消息长度:{message.Length}");i++;};channel.BasicConsume(queueName, true, consumer);}}}

消费者只需要知道队列名就可以消费了,不需要Exchange和routingKey。

注:消费者这里有一个创建队列,它本身不需要,是预防消费端程序先执行,没有队列会报错。

执行效果:

 

 

 

 

 

消息已经被消费完。

(2)工作队列模式

一个消息生产者,一个交换器,一个消息队列,多个消费者。同样也称为点对点模式

 

生产者P发送消息到队列,多个消费者C消费队列的数据。

工作队列也称为公平性队列模式,循环分发,RabbitMQ 将按顺序将每条消息发送给下一个消费者,每个消费者将获得相同数量的消息。

生产者:

Send.cs代码:

     /// <summary>/// 工作队列模式/// </summary>public static void WorkerSendMsg(){string queueName = "worker_order";//队列名//创建连接using (var connection = RabbitMQHelper.GetConnection()){//创建信道using (var channel = connection.CreateModel()){//创建队列channel.QueueDeclare(queueName, durable: true, exclusive: false, autoDelete: false, arguments: null);var properties = channel.CreateBasicProperties();properties.Persistent = true; //消息持久化for ( var i=0;i<10;i++){string message = $"Hello RabbitMQ MessageHello,{i+1}";var body = Encoding.UTF8.GetBytes(message);//发送消息到rabbitmqchannel.BasicPublish(exchange: "", routingKey: queueName, mandatory: false, basicProperties: properties, body);Console.WriteLine($"发送消息到队列:{queueName},内容:{message}");}}}}

参数durable:true,需要持久化,实际项目中肯定需要持久化的,不然重启RabbitMQ数据就会丢失了。

执行效果:

 

 

 

 写入10条数据,有持久化标识D。

 

 

 消费端:

Recevie代码:

 public static void WorkerConsumer(){string queueName = "worker_order";var connection = RabbitMQHelper.GetConnection();{//创建信道var channel = connection.CreateModel();{//创建队列channel.QueueDeclare(queueName, durable: false, exclusive: false, autoDelete: false, arguments: null);var consumer = new EventingBasicConsumer(channel);//prefetchCount:1来告知RabbitMQ,不要同时给一个消费者推送多于 N 个消息,也确保了消费速度和性能channel.BasicQos(prefetchSize: 0, prefetchCount:1, global: false);int i = 1;int index = new Random().Next(10);consumer.Received += (model, ea) =>{//处理业务var message = Encoding.UTF8.GetString(ea.Body.ToArray());Console.WriteLine($"{i},消费者:{index},队列{queueName}消费消息长度:{message.Length}");channel.BasicAck(ea.DeliveryTag, false); //消息ack确认,告诉mq这条队列处理完,可以从mq删除了Thread.Sleep(1000);i++;};channel.BasicConsume(queueName,autoAck:false, consumer);}}}

BasicQos参数解析:

prefetchSize:每条消息大小,一般设为0,表示不限制。

prefetchCount:1,作用限流,告诉RabbitMQ不要同时给一个消费者推送多于N个消息,消费者会把N条消息缓存到本地一条条消费,如果不设,RabbitMQ会进可能快的把消息推到客户端,导致客户端内存升高。设置合理可以不用频繁从RabbitMQ 获取能提升消费速度和性能,设的太多的话则会增大本地内存,需要根据机器性能合理设置,官方建议设为30。

global:是否为全局设置。

这些限流设置针对消费者autoAck:false时才有效,如果是自动Ack的,限流不生效。

 

执行两个消费者,效果:

 

可以看到消费者号的标识,8,2,8,2是平均的,一个消费者5个,RabbitMQ上也能看到有2个消费者,Unacked数是2,因为每个客户端的限流数是1。

 

工作队列模式也是很常用的队列模式。

(3)发布订阅模式 

Pulish/Subscribe,无选择接收消息,一个消息生产者,一个交换机(交换机类型为fanout),多个消息队列,多个消费者。称为发布/订阅模式

在应用中,只需要简单的将队列绑定到交换机上。一个发送到交换机的消息都会被转发到与该交换机绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。

 

 

 

生产者P只需把消息发送到交换机X,绑定这个交换机的队列都会获得一份一样的数据。

 

应用场景:适合于用同一份数据源做不同的业务。

生产者代码:

     /// <summary>/// 发布订阅, 扇形队列/// </summary>public static void SendMessageFanout(){//创建连接using (var connection = RabbitMQHelper.GetConnection()){//创建信道using (var channel = connection.CreateModel()){string exchangeName = "fanout_exchange";//创建交换机,fanout类型channel.ExchangeDeclare(exchangeName, ExchangeType.Fanout);string queueName1 = "fanout_queue1";string queueName2 = "fanout_queue2";string queueName3 = "fanout_queue3";//创建队列channel.QueueDeclare(queueName1, false, false, false);channel.QueueDeclare(queueName2, false, false, false);channel.QueueDeclare(queueName3, false, false, false);//把创建的队列绑定交换机,routingKey不用给值,给了也没意义的channel.QueueBind(queue: queueName1, exchange: exchangeName, routingKey: "");channel.QueueBind(queue: queueName2, exchange: exchangeName, routingKey: "");channel.QueueBind(queue: queueName3, exchange: exchangeName, routingKey: "");var properties = channel.CreateBasicProperties();properties.Persistent = true; //消息持久化//向交换机写10条消息for (int i = 0; i < 10; i++){string message = $"RabbitMQ Fanout {i + 1} Message";var body = Encoding.UTF8.GetBytes(message);channel.BasicPublish(exchangeName, routingKey: "", null, body);Console.WriteLine($"发送Fanout消息:{message}");}}}}

 

执行代码:

 

 

 

 向交换机发送10条消息,则绑定这个交换机的3个队列都会有10条消息。

消费端的代码和工作队列的一样,只需知道队列名即可消费,声明时要和生产者的声明一样。

(4)路由模式(推荐使用)

在发布/订阅模式的基础上,有选择的接收消息,也就是通过 routing 路由进行匹配条件是否满足接收消息。

 

 

 

 上图是一个结合日志消费级别的配图,在路由模式它会把消息路由到那些 binding key 与 routing key 完全匹配的 Queue 中,此模式也就是 Exchange 模式中的direct模式。

 生产者P发送数据是要指定交换机(X)和routing发送消息 ,指定的routingKey=error,则队列Q1和队列Q2都会有一份数据,如果指定routingKey=into,或=warning,交换机(X)只会把消息发到Q2队列。

 生产者代码:

 /// <summary>/// 路由模式,点到点直连队列/// </summary>public static void SendMessageDirect(){//创建连接using (var connection = RabbitMQHelper.GetConnection()){//创建信道using (var channel = connection.CreateModel()){//声明交换机对象,fanout类型string exchangeName = "direct_exchange";channel.ExchangeDeclare(exchangeName, ExchangeType.Direct);//创建队列string queueName1 = "direct_errorlog";string queueName2 = "direct_alllog";channel.QueueDeclare(queueName1, true, false, false);channel.QueueDeclare(queueName2, true, false, false);//把创建的队列绑定交换机,direct_errorlog队列只绑定routingKey:errorchannel.QueueBind(queue: queueName1, exchange: exchangeName, routingKey: "error");//direct_alllog队列绑定routingKey:error,infochannel.QueueBind(queue: queueName2, exchange: exchangeName, routingKey: "info");channel.QueueBind(queue: queueName2, exchange: exchangeName, routingKey: "error");var properties = channel.CreateBasicProperties();properties.Persistent = true; //消息持久化//向交换机写10条错误日志和10条Info日志for (int i = 0; i < 10; i++){string message = $"RabbitMQ Direct {i + 1} error Message";var body = Encoding.UTF8.GetBytes(message);channel.BasicPublish(exchangeName, routingKey: "error", properties, body);Console.WriteLine($"发送Direct消息error:{message}");string message2 = $"RabbitMQ Direct {i + 1} info Message";var body2 = Encoding.UTF8.GetBytes(message);channel.BasicPublish(exchangeName, routingKey: "info", properties, body2);Console.WriteLine($"info:{message2}");}}}}

这里创建一个direct类型的交换机,两个路由key,一个error,一个info,两个队列,一个队列只绑定error,一个队列绑定error和info,向error和info各发10条消息。

执行代码:

 

 

 查看RabbitMQ管理界面,direct_errorlog队列10条,而direct_alllog有20条,因为direct_alllog队列两个routingKey的消息都进去了。

 

 

 

 

 

 点进去看下两个队列绑定的交换机和routingKey

 

 

 

 

 

 

 

 

 消费者代码:

消费者和工作队列一样,只需根据队列名消费即可,这里只消费direct_errorlog队列作示例

 public static void DirectConsumer(){string queueName = "direct_errorlog";var connection = RabbitMQHelper.GetConnection();{//创建信道var channel = connection.CreateModel();{//创建队列channel.QueueDeclare(queueName, durable: true, exclusive: false, autoDelete: false, arguments: null);var consumer = new EventingBasicConsumer(channel);///prefetchCount:1来告知RabbitMQ,不要同时给一个消费者推送多于 N 个消息,也确保了消费速度和性能///global:是否设为全局的///prefetchSize:单条消息大小,通常设0,表示不做限制//是autoAck=false才会有效channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: true);int i = 1;consumer.Received += (model, ea) =>{//处理业务var message = Encoding.UTF8.GetString(ea.Body.ToArray());Console.WriteLine($"{i},队列{queueName}消费消息长度:{message.Length}");channel.BasicAck(ea.DeliveryTag, false); //消息ack确认,告诉mq这条队列处理完,可以从mq删除了i++;};channel.BasicConsume(queueName, autoAck: false, consumer);}}}

普通场景中推荐使用路由模式,因为路由模式有交换机,有路由key,能够更好的拓展各种应用场景。

(5)主题模式

topics(主题)模式跟routing路由模式类似,只不过路由模式是指定固定的路由键 routingKey,而主题模式是可以模糊匹配路由键 routingKey,类似于SQL中 = 和 like 的关系。

 

P 表示为生产者、 X 表示交换机、C1C2 表示为消费者,红色表示队列。

 

 topics 模式与 routing 模式比较相近,topics 模式不能具有任意的 routingKey,必须由一个英文句点号“.”分隔的字符串(我们将被句点号“.”分隔开的每一段独立的字符串称为一个单词),比如 "lazy.orange.a"。topics routingKey 中可以存在两种特殊字符"*"与“#”,用于做模糊匹配,其中“*”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)。

以上图为例:

如果发送消息的routingKey设置为:

aaa.orange.rabbit,那么消息会路由到Q1与Q2,

routingKey=aaa.orange.bb的消息会路由到Q1,

routingKey=lazy.aa.bb.cc的消息会路由到Q2;

routingKey=lazy.aa.rabbit的消息会路由到 Q2(只会投递给Q2一次,虽然这个routingKey 与 Q2 的两个 bindingKey 都匹配);

没匹配routingKey的消息将会被丢弃。

生产者代码:

 public static void SendMessageTopic(){//创建连接using (var connection = RabbitMQHelper.GetConnection()){//创建信道using (var channel = connection.CreateModel()){//声明交换机对象,fanout类型string exchangeName = "topic_exchange";channel.ExchangeDeclare(exchangeName, ExchangeType.Topic);//队列名string queueName1 = "topic_queue1";string queueName2 = "topic_queue2";//路由名string routingKey1 = "*.orange.*";string routingKey2 = "*.*.rabbit";string routingKey3 = "lazy.#";channel.QueueDeclare(queueName1, true, false, false);channel.QueueDeclare(queueName2, true, false, false);//把创建的队列绑定交换机,routingKey指定routingKeychannel.QueueBind(queue: queueName1, exchange: exchangeName, routingKey: routingKey1);channel.QueueBind(queue: queueName2, exchange: exchangeName, routingKey: routingKey2);channel.QueueBind(queue: queueName2, exchange: exchangeName, routingKey: routingKey3);//向交换机写10条消息for (int i = 0; i < 10; i++){string message = $"RabbitMQ Direct {i + 1} Message";var body = Encoding.UTF8.GetBytes(message);channel.BasicPublish(exchangeName, routingKey: "aaa.orange.rabbit", null, body);channel.BasicPublish(exchangeName, routingKey: "lazy.aa.rabbit", null, body);Console.WriteLine($"发送Topic消息:{message}");}}}}

这里演示了 routingKey为aaa.orange.rabbit,和lazy.aa.rabbit的情况,第一个匹配到Q1和Q2,第二个匹配到Q2,所以应该Q1是10条,Q2有20条,

执行后看rabbitMQ界面:

 

(6)RPC模式

与上面其他5种所不同之处,该模式是拥有请求/回复的。也就是有响应的,上面5种都没有。

RPC是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的处理业务,处理完后然后在A服务器继续执行下去,把异步的消息以同步的方式执行。

 

 客户端(C)声明一个排他队列自己订阅,然后发送消息到RPC队列同时也把这个排他队列名也在消息里传进去,服务端监听RPC队列,处理完业务后把处理结果发送到这个排他队列,然后客户端收到结果,继续处理自己的逻辑。

RPC的处理流程:

  • 当客户端启动时,创建一个匿名的回调队列。

  • 客户端为RPC请求设置2个属性:replyTo:设置回调队列名字;correlationId:标记request。

  • 请求被发送到rpc_queue队列中。

  • RPC服务器端监听rpc_queue队列中的请求,当请求到来时,服务器端会处理并且把带有结果的消息发送给客户端。接收的队列就是replyTo设定的回调队列。

  • 客户端监听回调队列,当有消息时,检查correlationId属性,如果与request中匹配,那就是结果了。

服务端代码:

 public class RPCServer{public static void RpcHandle(){var connection = RabbitMQHelper.GetConnection();{var channel = connection.CreateModel();{string queueName = "rpc_queue";channel.QueueDeclare(queue: queueName, durable: false,exclusive: false, autoDelete: false, arguments: null);channel.BasicQos(0, 1, false);var consumer = new EventingBasicConsumer(channel);channel.BasicConsume(queue: queueName,autoAck: false, consumer: consumer);Console.WriteLine("【服务端】等待RPC请求...");consumer.Received += (model, ea) =>{string response = null;var body = ea.Body.ToArray();var props = ea.BasicProperties;var replyProps = channel.CreateBasicProperties();replyProps.CorrelationId = props.CorrelationId;try{var message = Encoding.UTF8.GetString(body);Console.WriteLine($"【服务端】接收到数据:{ message},开始处理");response = $"消息:{message},处理完成";}catch (Exception e){Console.WriteLine("错误:" + e.Message);response = "";}finally{var responseBytes = Encoding.UTF8.GetBytes(response);channel.BasicPublish(exchange: "", routingKey: props.ReplyTo,basicProperties: replyProps, body: responseBytes);channel.BasicAck(deliveryTag: ea.DeliveryTag,multiple: false);}};}}}}

客户端:

 public class RPCClient{private readonly IConnection connection;private readonly IModel channel;private readonly string replyQueueName;private readonly EventingBasicConsumer consumer;private readonly BlockingCollection<string> respQueue = new BlockingCollection<string>();private readonly IBasicProperties props;public RPCClient(){connection = RabbitMQHelper.GetConnection();channel = connection.CreateModel();replyQueueName = channel.QueueDeclare().QueueName;consumer = new EventingBasicConsumer(channel);props = channel.CreateBasicProperties();var correlationId = Guid.NewGuid().ToString();props.CorrelationId = correlationId; //给消息idprops.ReplyTo = replyQueueName;//回调的队列名,Client关闭后会自动删除consumer.Received += (model, ea) =>{var body = ea.Body.ToArray();var response = Encoding.UTF8.GetString(body);//监听的消息Id和定义的消息Id相同代表这条消息服务端处理完成if (ea.BasicProperties.CorrelationId == correlationId){respQueue.Add(response);}};channel.BasicConsume(consumer: consumer,queue: replyQueueName,autoAck: true);}public string Call(string message){var messageBytes = Encoding.UTF8.GetBytes(message);//发送消息channel.BasicPublish(exchange: "",routingKey: "rpc_queue",basicProperties: props,body: messageBytes);//等待回复return respQueue.Take();}public void Close(){connection.Close();}}

执行代码:

 static void Main(string[] args){Console.WriteLine("Hello World!");//启动服务端,正常逻辑是在另一个程序RPCServer.RpcHandle();//实例化客户端var rpcClient = new RPCClient();string message = $"消息id:{new Random().Next(1, 1000)}";Console.WriteLine($"【客服端】RPC请求中,{message}");//向服务端发送消息,等待回复var response = rpcClient.Call(message);Console.WriteLine("【客服端】收到回复响应:{0}", response);rpcClient.Close();Console.ReadKey();}

测试效果:

 

 z执行完,客服端close后,可以接着自己的下一步业务处理。

 

 

总结:

以上便是RabbitMQ的6中模式在.net core中实际使用,其中(1)简单队列,(2)工作队列,(4)路由模式,(6)RPC模式的交换机类型都是direct,(3)发布订阅的交换机是fanout,(5)topics的交换机是topic。正常场景用的是direct,默认交换机也是direct类型的,推荐用(4)路由模式,因为指定交换机名比起默认的交换机会容易扩展场景,其他的交换机看业务场景所需使用

下面位置可以看到交换机类型,amq.开头那几个是内置的,避免交换机过多可以直接使用。

 

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

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

相关文章

为什么男性比女性死得更早,心疼一秒钟!

最近娱乐圈的那些大瓜大家都吃了吗&#xff1f;某明星的运动&#xff1f;黑眼圈&#xff1f;&#xff1f;不免让小编想起之前看的文章 男性为啥比女性“去”的早嗯&#xff0c;有可能是操劳过度 生活太累 咳咳咳咳咳以上均是不负责任猜想吃瓜要谨慎呦????不过这篇报道…

(转)C#网络编程(基本概念和操作) - Part.1

源码下载&#xff1a;http://www.tracefact.net/SourceCode/Network-Part1-2.rar C#网络编程(基本概念和操作) - Part.1 引言 C#网络编程系列文章计划简单地讲述网络编程方面的基础知识&#xff0c;由于本人在这方面功力有限&#xff0c;所以只能提供一些初步的入门知识&#x…

jwt如何防止token被窃取_在吗?认识一下JWT(JSON Web Token)?

什么是JSON Web Token &#xff1f;官网介绍&#xff1a;JSON Web Token(JWT)是一个开放标准(RFC 7519)&#xff0c;它定义了一种紧凑且自包含的方式&#xff0c;用于在各方之间安全地将信息作为JSON对象传输。由于此信息是经过数字签名的&#xff0c;因此可以被验证和信任。可…

8月日更,我的困难与感悟

8月份参加了掘金的日更活动&#xff0c;坚持每天写技术文章进行分享&#xff0c;现在活动结束了&#xff0c;来复盘下这一个月来我的困难与感悟。8月日更其实刚开始我是不想参加这个活动的&#xff0c;最近确实比较忙&#xff0c;不管是工作还是自己的私事&#xff0c;都不允许…

那些神一样的学习技巧,专治各种不服!

▲ 点击查看著名的俄国生理学家曾反复对自己的学生提过这个要求&#xff1a;“应当先学会观察&#xff0c;观察。不学会观察&#xff0c;你就永远当不了科学家。”鲁迅也曾这样教导&#xff1a;“如果要创作&#xff0c;第一要观察。”在《神探夏洛克》中&#xff0c;有这么一段…

kafka 怎么样连接图形化界面_图形化编程有多简单,点亮LED不到一分钟

Arduino编程在所有单片机当中应该说是最简单的了&#xff0c;但是还可以更加简单。比如说图形化编程&#xff0c;图形化编程真正让Arduino大众化了&#xff0c;因为谁都可以通过图形化编程方式来制作自己需要的小玩意。啃萝卜关于图形化编程软件有很多&#xff0c;我独宠啃萝卜…

linux驱动内核哪个文件夹,linux设备驱动归纳总结(一):内核的相关基础概念...

linux设备驱动归纳总结(一)&#xff1a;内核的相关基础概念1. 内核与 linux 设备驱动的作用与关系内核&#xff1a;用于管理软硬件资源&#xff0c;并提供运行环境。如分配 4G 虚拟空间等。linux 设备驱动&#xff1a;是连接硬件和内核之间的桥梁。linux 系统按个人理解可按下划…

怪不得超市不让带宠物...

1 难怪超市不让带狗啊▼2 医学生的聊天记录过于硬核▼3 你身边的外卖小哥头盔上都顶着什么呢&#xff1f;▼4 表妹非要把猫脸P到蜜蜂身上▼5 其实主要还是看脸脸到位了&#xff0c;祖安小公举问题都不大▼6 就你们这个送别方式我觉得他是回不来了......▼7 妹妹沦为工具…

自定义控件复选框和单选框的实现

我们先实现单个按钮&#xff0c;为了复用&#xff0c;不管单选还是复选按钮都是使用同一个类来实现&#xff0c;为了区别单选还是复选&#xff0c;我们用一个自定义枚举类型CheckButtonStyle属性style来区别&#xff0c;当其值设置为CheckButtonStyleDefault或CheckButtonStyle…

单文件组件的组件传值_移动端组件化架构(下)

我的组件化方案对于项目架构来说&#xff0c;一定要建立于业务之上来设计架构。不同的项目业务不同&#xff0c;组件化方案的设计也会不同&#xff0c;应该设计最适合公司业务的架构。架构设计以我之前公司项目为例&#xff0c;项目是一个地图导航应用&#xff0c;业务层之下的…

为什么圆是360度?超颠覆的解释

圆为什么有360度&#xff1f;为什么不是300度呢&#xff1f;古文明时期人类把很多不能解释的自然现象归结为“天意”真的有天意吗&#xff1f;我们把圆分成等份&#xff0c;奇迹出现了.....依次等分下去&#xff0c;结果一样...任何被分成等分的角度的所有数字之和为9现在我们来…

我获得“微软MVP”奖项,后续将会贡献更多技术内容

昨天晚上&#xff0c;我收到了微软总部发来的“恭喜获得MVP”的邮件。请点击【阅读原文】查看我的MVP Profile页面。有的朋友说“一直以为你早就是MVP了”。其实这么多年我做的技术贡献主要是录编程视频教程&#xff0c;而这些视频教程都是通过BT下载等方式传播&#xff0c;没有…

[Spring MVC] - InitBinder验证

Spring MVC使用InitBinder验证&#xff1a; 使用InitBinder做验证的情况一般会在此Controller中提交的数据需要有一些是业务性质的&#xff0c;也即比较复杂的验证情况下才会使用。大部份简单的表单验证&#xff0c;使用annotation验证即可以解决。 Annotation验证使用方法可参…

linux6.5进入救援模式,rhel6.5救援模式修复系统

如果系统中很多重要的部分被删除了例如/boot下的所有东西&#xff0c;则可以通过救援模式[rootdazzle1 桌面]# mkdir /backup[rootdazzle1 桌面]# cp /etc/fstab /backup/fstab  //先备份以下fstab文件&#xff0c;也可以不备份自己写[rootdazzle1 桌面]# rm -rf /boot/*  …

一名毕业生的自述:我知道我必须写论文,但没聪明到可以写出来......

全世界只有3.14 % 的人关注了爆炸吧知识2020年转眼就到了4月。在即将毕业的学子之间&#xff0c;每天的狂野问候语是这样的&#xff1a;“你论文改完了吗&#xff1f;”“你论文查重率是多少&#xff1f;”“你什么时候答辩&#xff1f;”在微博上实时搜索“翟天临”三个字&…

不是架构的架构之四:业务层的实现与自动代理

我们在开篇中提到&#xff0c;希望能有一种办法&#xff0c;能自动适应系统的环境配置&#xff0c;在局域网小型应用中将直接访问数据库以获得最高的性能&#xff0c;在分布式环境中自动使用WCF来获得较好的安全性和连通性。 但是&#xff0c;我们不希望这样的特性使我们的开发…

python程序设计实践教程陈东_Python

“我们正步入一个数据或许比软件更重要的新时代。——Tim OReilly” 运用数据是精准刻画事物、呈现发展规律的主要手段&#xff0c;分析数据展示规律&#xff0c;把思想变得更精细&#xff01; 本课程面向各类编程学习者&#xff0c;讲解利用Python语言表达N维数据并结合数据特…

Silverlight中开发和设计人员的合作文档信息

-----------------------------------------------------------------------------------> copyright:http://www.docin.com/p-34191215.html转载于:https://www.cnblogs.com/molin/archive/2009/12/08/silverlight_manager.html

和男朋友一块儿吃VS单独一人在家吃饭

1 和男朋友一块儿吃VS单独一人在家吃饭2 忍不住要为这位跳高选手鼓掌了3 我们家的蔬菜就没有这种觉悟4 这螳螂拳算是练到家了5 现实中的你胖的一批 6 这套户型咋样&#xff1f;7 你能看出几个字你点的每个赞&#xff0c;我都认真当成了喜欢

指针04 - 零基础入门学习C语言44

第八章&#xff1a;指针04 让编程改变世界 Change the world by program 小结 归纳起来, 如果有一个实参数组, 想在函数中改变此数组中的元素的值, 实参与形参的对应关系有以下&#xff14;种情况&#xff1a; (1) 形参和实参都用数组名, 如&#xff1a; [codesyntax lang&…