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

前言

生产者发送消息到了队列,队列推送数据给了消费者,这里存在一些问题需要思考下

  • 生产者如何确保消息一定投递到了队列中

  • RabbitMQ 丢失了消息(下文暂不涉及这块)

  • 队列如何确保消费者收到了消息呢
    ec443621cb2d4c30e8ca59d0d9ebe7f4.png

生产者可靠发送

执行流程

当生产者将消息发送出去后,如果不进行特殊设置,默认情况下,发送消息操作后是没有返回任何消息给生产者的,这时生产者是不知道消息有没有真的到达了RabbitMQ,消息可能在到达RabbitMQ前已经丢了。

19dda379c7d438c3ff14a06bd4d21bf4.png

对于这种情况,有两种解决方案来应对,

  • 事务机制,该机制是AMQP协议层面提供的解决方案

  • 发送方确认机制,这是RabbitMQ提供的解决方案

事务机制

可以将当前使用的channel设置成事务模式。借助如下与事务机制相关的三个方法来完成事务操作。

  • channel.TxSelect() 启动事务模式

  • channel.TxCommit() 提交事务

  • channel.TxRollback() 回滚事务

TXCommit

事务模式下,时序图中发布消息前后增加了一点动作,设置信道为事务模式与提交事务。如果TXCommit()调用成功,则说明事务提交成功,消息一定到达了RabbitMQ中。

1de04eceb516f402e9365739cad08851.png

TXRollback

当事务提交执行之前,由于RabbitMQ异常崩溃或其他原因抛出异常,则可以捕获异常,再执行TXRollback(),完成事务回滚。

37fe48f40f51eb3c474fe402c25936e8.png

生产者代码

在原有Basic代码基础上增加事务操作,此处先注释掉与事务有关的代码(16、34和40行),并且在发送消息后设置异常模拟

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_tx";channel.QueueDeclare(queue: queueName, durable: false, exclusive: false, autoDelete: false, arguments: null);//channel.TxSelect();        while (true){Console.WriteLine("消息内容(exit退出):");var message = Console.ReadLine();if (message.Trim().ToLower() == "exit"){break;}try{var body = Encoding.UTF8.GetBytes(message);channel.BasicPublish(exchange: "", routingKey: queueName, basicProperties: null, body: body);if (message.Trim().ToLower() == "9527"){throw new Exception("触发异常");}//channel.TxCommit();                Console.WriteLine("消息内容发送完毕:" + message);}catch (Exception ex){Console.WriteLine("消息发送出现异常:" + ex.Message);//channel.TxRollback();            }}}
}

消费端代码保持不变(仅改动下队列名称)

56695b3856c7130e1aa14cf1e9e4600c.png

因生产者已经将消息发送到了队列中,后触发了异常,实则已经和消息能不能到达队列无关了,在消费端也就正常消费了。

取消16、34和40行的注释后,在事务模式下,当再次触发异常后,能够看见,消费端并没有收到消息(这不是说消费端那边出现异常了,而是消息没有到达队列中)。发送消息与其他操作在同一事务下提供了一致性。

4215966f495d783f4984418c98a99a99.png

局限性

事务模式下,只有收到了Broker的TX.Commit-OK才算提交成功,否则便可以捕获异常执行事务回滚,所以能够解决生产者和RabbitMQ之间消息确认的问题。但是使用事务机制下有一个缺点,在一条消息发送之后会使得发送方阻塞,以等待RabbitMQ的回应,之后才能发送下一条消息,严重降低RabbitMQ的性能与吞吐量,一般不使用。

确认机制

生产者将channel设置为确认模式(confirm),在该信道上发送的消息都会分配一个唯一Id(从1开始),当消息进入到队列中后,RabbitMQ会发送一个ack(Acknowledgement)命令(Basic.Ack)给生产者,这样确保生产者知道这条消息是真的到了。

65cc5932a97f65eddd1dff1910d26472.png

相比于事务机制下的阻塞,发送方确认机制下是异步的,发布一条消息后,可以等待信道确认的同时发送下一条消息,当信道收到确认时,生产者可以通过回调方法来处理该消息。当RabbitMQ自身内部错误导致消息丢失,会发送nack(Negative Acknowledgement)命令(Basic.Nack),生产者可以在回调方法中处理该命令,诸如重试,记录等操作,所有消息在确认模式下,都会被ack或是nack一次。

核心方法

  • channel.ConfirmSelect 设置信道为确认模式(Confirm),收到Confirm.Select-OK命令表示同意将当前信道设置为Confirm模式。
    031a99ebaebab9035c9ee66813bd7daa.png

  • channel.WaitForConfirms 信道等待Broker回执确认信息

三种模式

生产者实现confirm有三种方式,取决于confirm的时机

  • 同步确认模式

    • 单条确认模式:发送完消息后等待确认

    • 批量确认模式:发送完一批消息后等待确认

  • 异步确认模式:发送完消息后,提供一个回调方法,Broker回执了一条或多条后由生产者回调执行,异步等待确认

单条确认模式

生产者通过调用channel.ConfirmSelect()将信道设置为确认模式(Confirm)。发送消息到RabbitMQ,当消息进入到队列后,发送Basic.Ack给生产者,信道等待获取确认信息,执行回调逻辑。

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_producerack_singleconfirm";channel.QueueDeclare(queue: queueName, durable: false, exclusive: false, autoDelete: false, arguments: null);channel.ConfirmSelect();        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);Console.WriteLine(channel.WaitForConfirms(new TimeSpan(0, 0, 1)) ? "消息发送成功" : "消息发送失败");}}
}

消息发送完毕,同步等待获取到Broker的回执确认消息,这样来判断是否ack或是nack了,如此可确定生产者消息是否真的到达了Broker。

cfed1af20a016a0124c242b0f1be583d.png

批量确认模式

信道开启confirm模式,与单条发送后确认不同,批量发送多条之后再调用waitForConfirms确认,这样发送多条之后才会等待一次确认消息,极大提升confirm效率,但问题在于出现返回basic.nack或者超时的情况时,生产者需要将这批消息全部重发,则会带来明显的重复消息数量,并且当消息经常丢失时,批量confirm性能应该是不升反降的。

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_producerack_batchconfirm";channel.QueueDeclare(queue: queueName, durable: false, exclusive: false, autoDelete: false, arguments: null);channel.ConfirmSelect();while (true){for (int i = 0; i < 3; i++){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);}Console.WriteLine(channel.WaitForConfirms() ? "所有消息内容发送完毕" : "存在消息发送失败");}}
}

此处对于批量,我设置三个为一组,三个执行完毕后,获得Broker的回执确认消息。

f6b814866f1c062acfdccce07a9af189.png

异步确认模式

异步模式下,额外增加回调方法来处理,Channel对象提供的BasicAcks,BasicNacks两个回调事件

//  // Summary:  //     Signalled when a Basic.Nack command arrives from the broker.  event EventHandler<BasicNackEventArgs> BasicNacks;//  // Summary:  //     Signalled when a Basic.Ack command arrives from the broker.  event EventHandler<BasicAckEventArgs> BasicAcks;

BasicAckEventArg和BasicNackEventArgs都包含deliveryTag(当前Chanel在发送消息时给的消息序号)。

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_producerack_asyncconfirm";channel.QueueDeclare(queue: queueName, durable: false, exclusive: false, autoDelete: false, arguments: null);channel.ConfirmSelect();channel.BasicAcks += new EventHandler<BasicAckEventArgs>((o, basic) =>{Console.WriteLine($"调用ack回调方法: DeliveryTag: {basic.DeliveryTag};Multiple: { basic.Multiple }");});channel.BasicNacks += new EventHandler<BasicNackEventArgs>((o, basic) =>{Console.WriteLine($"调用Nacks回调方法; DeliveryTag: {basic.DeliveryTag};Multiple: { basic.Multiple }");});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);}}
}

生产者发送消息,生产者收到回执确认消息,调用回调方法

5d9244163e99abe745679fd9afe90b36.png

在此基础上,我们可以为channel维护一个unconfirm的消息序号集合,用来控制nack回调下的失败重试。具体实现为

  • 当发布一条消息时,从channel获取当前的唯一的Id存入集合中。

  • 当回调一次BasicAcks事件方法时,unconfirm集合删掉相应的一条或多条记录。

// ...channel.ConfirmSelect();var _pendingDeliveryTags = new LinkedList<ulong>();
channel.BasicAcks += new EventHandler<BasicAckEventArgs>((o, basic) =>
{Console.WriteLine($"调用ack回调方法: DeliveryTag: {basic.DeliveryTag};Multiple: { basic.Multiple }");if (_pendingDeliveryTags.Count > 0){return;}if (!basic.Multiple){_pendingDeliveryTags.Remove(basic.DeliveryTag);return;}while (_pendingDeliveryTags.First.Value < basic.DeliveryTag){_pendingDeliveryTags.RemoveFirst();}if (_pendingDeliveryTags.First.Value == basic.DeliveryTag){_pendingDeliveryTags.RemoveFirst();}
});
channel.BasicNacks += new EventHandler<BasicNackEventArgs>((o, basic) =>
{Console.WriteLine($"调用Nacks回调方法; DeliveryTag: {basic.DeliveryTag};Multiple: { basic.Multiple }");    if (_pendingDeliveryTags.Count > 0){        return;}    if (!basic.Multiple){_pendingDeliveryTags.Remove(basic.DeliveryTag);        return;}    while (_pendingDeliveryTags.First.Value < basic.DeliveryTag){_pendingDeliveryTags.RemoveFirst();}    if (_pendingDeliveryTags.First.Value == basic.DeliveryTag){_pendingDeliveryTags.RemoveFirst();}//Todo 执行消息重发});for (int i = 0; i < 5000; i++)
{var message = "第" + (i + 1) + "条消息";var body = Encoding.UTF8.GetBytes(message);var nextSeqNo = channel.NextPublishSeqNo;channel.BasicPublish(exchange: "", routingKey: queueName, basicProperties: null, body: body);_pendingDeliveryTags.AddLast(nextSeqNo);Console.WriteLine("消息内容发送完毕:" + message);
}

异步模式下,当生产者持续向RabbitMQ发送消息,可能生产者端并不会收到100个ack消息 ,也许收到1个,或者2个,或N个ack消息,这些ack消息的multiple都为true,ack消息中的deliveryTag的值的含义也不是一样的,当multiple为true,deliveryTag代表的值是一批的id,像如下图中的4905,则代表是4904和4905两个唯一Id值。而当multiple为false时,则deliveryTag就仅代表一个唯一的Id值。

8688f24745ca6403dc1c6c5c02415b7c.png

从性能方面比对这几种方式,批量和异步确认更胜一筹。可从代码复杂方面来讲,事务机制或是单条确认模式都简单,异步确认模式反而是更复杂。而当遇到批量消息需要重发场景时,批量确认模式却又会存在性能不升反降。因此,也无绝对的优劣,适合的才是最好的。

消费者可靠接收

生产者将消息可靠传递到了RabbitMQ,RabbitMQ将消息推送给消费者,消费者端如何确保消息可靠接收了呢? RabbitMQ提供了消息确认机制,消费者在订阅队列时,可以指定autoAck参数。

AutoAck参数

  • 当autoAck为false时,RabbitMQ会等待消费者显示的回复确认信号后再从内存(或磁盘)中移除消息(先标记再删除)。

  • 当autoAck为true时,RabbitMQ会把消息发送出去的消息自动确认再从内存(或磁盘)中删除,消费者是否接收到了消息不管。

RabbitMQ的Web管理平台上可以看到当前队列是否AutoAck。当AutoAck设置为false时,面板中展示Ack required为勾选。同时队列中的消息分成了两部分:

  • 一部分是等待投递给消费者的消息数 Ready

  • 一部分是已经投递给消费者,但是未收到消费者确认信号的消息数 Unacked

41398e52b9b940bf37e0af555f51b0aa.png

如果 RabbitMQ 服务器端一直没有收到消费者的确认信号,并且消费此消息的消费者已经断开连接,则服务器端会安排该消息重新进入队列,等待投递给下一个消费者(也可能还是原来的那个消费者)。

  • 采用消息确认机制后,只要设置 autoAck 参数为 false,消费者就有足够的时间处理消息(任务),不用担心处理消息过程中消费者进程挂掉后消息丢失的问题,因为 RabbitMQ 会一直等待持有消息直到消费者显式调用 Basic.Ack 命令为止。

  • RabbitMQ 不会为未确认的消息设置过期时间,判断此消息是否需要重新投递给消费者的唯一依据是消费该消息连接是否已经断开,这个设置的原因是 RabbitMQ 允许消费者消费一条消息的时间可以很久

核心方法

  • BasicAck 用于确认当前消

  • BasicNack 用于拒绝当前消息,可以执行批量拒绝

  • BasicReject 用于拒绝当前消息,仅拒绝一条消息

手动确认

生产者端将100条消息存入队列中,可以通过Web面板看到已有Total/Ready都是100条。

47438c5bfe0f211ae627219b1a08ffd2.png

消费者端设置消费能力Qos为5,同时channel.BasicConsume 设置autoAck为false

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_consumerack";channel.QueueDeclare(queue: queueName, durable: false, exclusive: false, autoDelete: false, arguments: null);channel.BasicQos(0, 5, false);var consumer = new EventingBasicConsumer(channel);consumer.Received += (model, ea) =>{Thread.Sleep(10000);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();}
}

运行代码,在web面板中可以开始看到待确认数为5,即RabbitMQ按照消费者最大负载能力推送消息,同时等待消费者手动确认。

1f9dacc4f448500f7f87ed04e5ab41a8.png

这样,能够可靠的确定消费者着实接收到了消息。当消费者因处理业务发生异常下或是因网络不稳定、服务器异常等,消费者没有反馈Ack,这样消息一直会处在队列中,这样就确保了有其他消费者(也许还是自身)有机会执行重试,确保业务正常,仅仅当RabbitMQ收到消费者的Ack反馈后才会删除。

自动确认

RabbitMQ消息确认机制(ACK)默认是自动确认的,自动确认会在消息发送给消费者后立即确认,但存在丢失消息的可能。

生产者发送100条消息到RabbitMQ中等待消费。

518b02d2c0a7407da206bb5b4af682fd.png

消费者端设置自动确认Ack,接收消息中设置了当读取到第50条消息时抛出异常。

var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{Thread.Sleep(1000);var message = ea.Body;Console.WriteLine("接收到信息为:" + Encoding.UTF8.GetString(message.ToArray()));if (Encoding.UTF8.GetString(message.ToArray()).Contains("50")){Console.WriteLine("异常");throw new Exception("模拟异常");}((EventingBasicConsumer)model).Model.BasicAck(ea.DeliveryTag, false);
};channel.BasicConsume(queue: queueName, autoAck: true, consumer: consumer);

当运行程序,消息全部会推送到消费者中(basicQos在AutoAck下无效),出现异常时,RabbitMQ中也不会留有消息,这样一来,消息就丢失了。

50c2761347ae25ea8f944566f4889025.png

这种不安全的模式,使用场景受限,消息易丢失。同时,由于自动确认下,设置Qos无效,当消息数量过多时,RabbitMQ发送到消费者端的消息太快,数量太多,又可能造成消费者端消息挤压而耗尽内存导致进程终止。

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

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

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

相关文章

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

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

【SRM-05 B】无题?

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

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

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

.NET MAUI实战 Routing

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

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

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

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

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

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

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

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

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

nginx安装及负载均衡配置

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

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

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

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

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

VS2017调试闪退之Chrome

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

ios UISearchBar搜索框的基本使用

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

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

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

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

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

.NET Offer 快到碗里来!.NET 招聘季

关注我们谈到 .NET 在中国的推广和发展&#xff0c;.NET 开发者求职就业及 .NET 企业招人用人的问题往往常被提及。初学者会担心学习 .NET 之后的就业问题&#xff0c;.NET 开发者在求职过程中没有足够多的渠道来获取 .NET 招聘信息&#xff0c;而与此同时&#xff0c;采用 .NE…

java的collections_Java中Collection和Collections的区别

1) 排序(Sort)使用sort方法可以根据元素的自然顺序 对指定列表按升序进行排序。列表中的所有元素都必须实现 Comparable 接口。此列表内的所有元素都必须是使用指定比较器可相互比较的1 List list new ArrayList();2 int array[] {112, 111, 23, 456, 231};3 for (int i 0; …

jQuery事件绑定(一)

2019独角兽企业重金招聘Python工程师标准>>> 一、on方法 在Jquery1.7中添加&#xff0c;用来代替其他事件绑定方法。向匹配元素添加一个或多个事件处理程序 使用语法&#xff1a; $(selector).on(event,childselector,data,function) 参数&#xff1a; event&#x…

JDBC学习笔记之JDBC简介

1. 引言 JDBC API是一种Java API&#xff0c;可以访问任何类型的表格数据&#xff0c;特别是存储在关系数据库中的数据。 JDBC可以帮助我们编写下列三种编程活动的java应用程序&#xff1a; 1.连接到数据源&#xff0c;如数据库;2.发送查询和更新语句到数据库;3.检索并处理从数…

PaddleOCR在 windows下的webAPI部署方案

很多小伙伴在使用OCR时都希望能过采用API的方式调用&#xff0c;这样就可以跨端跨平台了。本文将介绍一种基于python的PaddleOCR识方案。喜欢的可以关注公众号&#xff0c;获取更多内容。# 一、 windows环境下部署###1.环境操作系统&#xff1a;windows10&#xff1b;主要软件环…