RabbitMQ 实现延迟队列
业务场景:
1.生成订单30分钟未支付,则自动取消,我们该怎么实现呢?
2.生成订单60秒后,给用户发短信
1 安装rabbitMq
windows安装
ubuntu中安装
2 添加maven依赖
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-amqp --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency>
3 在application.properties配置
spring.application.name=rabbitmq-hello
# 配置rabbbitMq
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=springCloud
spring.rabbitmq.password=123456
4 具体的实现
rabbitmq本身是没有延迟发送的功能,但是我们通过消息的TTL(Time To Live)来实现,所谓TTL就是指消息的存活时间,RabbitMQ可以对队列和消息分别设置TTL。
对队列设置就是队列没有消费者连着的保留时间,也可以对每一个单独的消息做单独的设置。超过了这个时间,我们认为这个消息就死了,称之为死信。如果队列设置了,消息也设置了,那么会取小的。所以一个消息如果被路由到不同的队列中,这个消息死亡的时间有可能不一样(不同的队列设置)。这里单讲单个消息的TTL,因为它才是实现延迟任务的关键。
我们可以通过设置消息的expiration字段或者x-message-ttl属性来设置时间,两者是一样的效果。只是expiration字段是字符串参数,所以要写个int类型的字符串:
byte[] messageBodyBytes ="Hello, world!".getBytes();AMQP.BasicProperties properties =newAMQP.BasicProperties();
properties.setExpiration("60*1000");
channel.basicPublish("my-exchange","routing-key", properties, messageBodyBytes);
当上面的消息扔到队列中后,过了60秒,如果没有被消费,它就死了。不会被消费者消费到。
这个消息后面的,没有“死掉”的消息对顶上来,被消费者消费。死信在队列中并不会被删除和释放,它会被统计到队列的消息数中去。单靠死信还不能实现延迟任务,还要靠Dead Letter Exchange。
下面我大致解释一下Dead Letter Exchanges
4.1 Dead Letter Exchanges
一个消息在满足如下条件下,会进死信路由,记住这里是路由而不是队列,一个路由可以对应很多队列。
1.一个消息被Consumer拒收了,并且reject方法的参数里requeue是false。也就是说不会被再次放在队列里,被其他消费者使用。
2.上面的消息的TTL到了,消息过期了。
3.队列的长度限制满了。排在前面的消息会被丢弃或者扔到死信路由上。
Dead Letter Exchange其实就是一种普通的exchange,和创建其他exchange没有两样。只是在某一个设置Dead Letter Exchange的队列中有消息过期了,会自动触发消息的转发,发送到Dead Letter Exchange中去。
4.2 实现延迟队列
延迟任务通过消息的TTL和Dead Letter Exchange来实现。我们需要建立2个队列,一个用于发送消息,一个用于消息过期后的转发目标队列,大致原理如下图所示。

生产者输出消息到Queue1,并且这个消息是设置有有效时间的,比如60s。消息会在Queue1中等待60s,如果没有消费者收掉的话,它就是被转发到Queue2,Queue2有消费者,收到,处理延迟任务。
接下来正式进入代码阶段
代码
声明交换机、队列以及他们的绑定关系:
importorg.springframework.amqp.core.*;importorg.springframework.beans.factory.annotation.Qualifier;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importjava.util.HashMap;importjava.util.Map;@ConfigurationpublicclassRabbitMQConfig{publicstaticfinalString DELAY_EXCHANGE_NAME ="delay.queue.demo.business.exchange";//普通的交换机publicstaticfinalString DELAY_QUEUEA_NAME ="delay.queue.demo.business.queuea";//声明两个队列 A BpublicstaticfinalString DELAY_QUEUEB_NAME ="delay.queue.demo.business.queueb";publicstaticfinalString DELAY_QUEUEA_ROUTING_KEY ="delay.queue.demo.business.queuea.routingkey";publicstaticfinalString DELAY_QUEUEB_ROUTING_KEY ="delay.queue.demo.business.queueb.routingkey";publicstaticfinalString DEAD_LETTER_EXCHANGE ="delay.queue.demo.deadletter.exchange";//Dead Letter ExchangespublicstaticfinalString DEAD_LETTER_QUEUEA_ROUTING_KEY ="delay.queue.demo.deadletter.delay_10s.routingkey";//死信交换机publicstaticfinalString DEAD_LETTER_QUEUEB_ROUTING_KEY ="delay.queue.demo.deadletter.delay_60s.routingkey";publicstaticfinalString DEAD_LETTER_QUEUEA_NAME ="delay.queue.demo.deadletter.queuea";publicstaticfinalString DEAD_LETTER_QUEUEB_NAME ="delay.queue.demo.deadletter.queueb";// 声明延时Exchange@Bean("delayExchange")publicDirectExchangedelayExchange(){returnnewDirectExchange(DELAY_EXCHANGE_NAME);}// 声明死信Exchange@Bean("deadLetterExchange")publicDirectExchangedeadLetterExchange(){returnnewDirectExchange(DEAD_LETTER_EXCHANGE);}// 声明延时队列A 延时10s// 并绑定到对应的死信交换机@Bean("delayQueueA")publicQueuedelayQueueA(){Map<String,Object> args =newHashMap<>(2);// x-dead-letter-exchange 这里声明当前队列绑定的死信交换机args.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE);// x-dead-letter-routing-key 这里声明当前队列的死信路由keyargs.put("x-dead-letter-routing-key", DEAD_LETTER_QUEUEA_ROUTING_KEY);// x-message-ttl 声明队列的TTLargs.put("x-message-ttl",1000*10);returnQueueBuilder.durable(DELAY_QUEUEA_NAME).withArguments(args).build();}// 声明延时队列B 延时 60s// 并绑定到对应的死信交换机@Bean("delayQueueB")publicQueuedelayQueueB(){Map<String,Object> args =newHashMap<>(2);// x-dead-letter-exchange 这里声明当前队列绑定的死信交换机args.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE);// x-dead-letter-routing-key 这里声明当前队列的死信路由keyargs.put("x-dead-letter-routing-key", DEAD_LETTER_QUEUEB_ROUTING_KEY);// x-message-ttl 声明队列的TTLargs.put("x-message-ttl",60000);returnQueueBuilder.durable(DELAY_QUEUEB_NAME).withArguments(args).build();}// 声明死信队列A 用于接收延时10s处理的消息@Bean("deadLetterQueueA")publicQueuedeadLetterQueueA(){returnnewQueue(DEAD_LETTER_QUEUEA_NAME);}// 声明死信队列B 用于接收延时60s处理的消息@Bean("deadLetterQueueB")publicQueuedeadLetterQueueB(){returnnewQueue(DEAD_LETTER_QUEUEB_NAME);}// 声明延时队列A绑定关系@BeanpublicBindingdelayBindingA(@Qualifier("delayQueueA")Queue queue,@Qualifier("delayExchange")DirectExchange exchange){returnBindingBuilder.bind(queue).to(exchange).with(DELAY_QUEUEA_ROUTING_KEY);}// 声明业务队列B绑定关系@BeanpublicBindingdelayBindingB(@Qualifier("delayQueueB")Queue queue,@Qualifier("delayExchange")DirectExchange exchange){returnBindingBuilder.bind(queue).to(exchange).with(DELAY_QUEUEB_ROUTING_KEY);}// 声明死信队列A绑定关系@BeanpublicBindingdeadLetterBindingA(@Qualifier("deadLetterQueueA")Queue queue,@Qualifier("deadLetterExchange")DirectExchange exchange){returnBindingBuilder.bind(queue).to(exchange).with(DEAD_LETTER_QUEUEA_ROUTING_KEY);}// 声明死信队列B绑定关系@BeanpublicBindingdeadLetterBindingB(@Qualifier("deadLetterQueueB")Queue queue,@Qualifier("deadLetterExchange")DirectExchange exchange){returnBindingBuilder.bind(queue).to(exchange).with(DEAD_LETTER_QUEUEB_ROUTING_KEY);}}
消息的生产者
importorg.springframework.amqp.rabbit.core.RabbitTemplate;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Component;importorg.springframework.stereotype.Service;importstaticcom.talent.infocenter.rabbitmq.RabbitMQConfig.*;@ServicepublicclassDelayMessageSender{@AutowiredprivateRabbitTemplate rabbitTemplate;publicenumDelayTypeEnum{DELAY_10s,DELAY_60s;}publicstaticDelayTypeEnumgetByIntValue(int value){switch(value){case10:returnDelayTypeEnum.DELAY_10s;case60:returnDelayTypeEnum.DELAY_60s;default:returnnull;}}publicvoidsendMsg(String msg,DelayTypeEnum type){switch(type){caseDELAY_10s:rabbitTemplate.convertAndSend(DELAY_EXCHANGE_NAME, DELAY_QUEUEA_ROUTING_KEY, msg);break;caseDELAY_60s:rabbitTemplate.convertAndSend(DELAY_EXCHANGE_NAME, DELAY_QUEUEB_ROUTING_KEY, msg);break;}}}
消费者
我们创建两个消费者,分别消费10s和60s的订单
importcom.rabbitmq.client.Channel;importlombok.extern.slf4j.Slf4j;importorg.springframework.amqp.core.Message;importorg.springframework.amqp.rabbit.annotation.RabbitListener;importorg.springframework.stereotype.Component;importjava.io.IOException;importjava.util.Date;importstaticcom.talent.infocenter.rabbitmq.RabbitMQConfig.DEAD_LETTER_QUEUEA_NAME;importstaticcom.talent.infocenter.rabbitmq.RabbitMQConfig.DEAD_LETTER_QUEUEB_NAME;@Slf4j@ComponentpublicclassDeadLetterQueueConsumer{@RabbitListener(queues = DEAD_LETTER_QUEUEA_NAME)publicvoidreceiveA(Message message,Channel channel)throwsIOException{String msg =newString(message.getBody());log.info("当前时间:{},死信队列A收到消息:{}",newDate().toString(), msg);channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);}@RabbitListener(queues = DEAD_LETTER_QUEUEB_NAME)publicvoidreceiveB(Message message,Channel channel)throwsIOException{String msg =newString(message.getBody());log.info("当前时间:{},死信队列B收到消息:{}",newDate().toString(), msg);channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);}}
创建一个接口进行测试
importcom.talent.infocenter.rabbitmq.DelayMessageSender;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RequestMethod;importorg.springframework.web.bind.annotation.RequestParam;importorg.springframework.web.bind.annotation.RestController;importjava.util.Date;importjava.util.Objects;@Slf4j@RestControllerpublicclassRabbitMQMsgController{@AutowiredprivateDelayMessageSender sender;@RequestMapping(value ="sendmsg", method =RequestMethod.GET)publicvoidsendMsg(@RequestParam(value ="msg")String msg,@RequestParam(value ="delayType")Integer delayType){log.info("当前时间:{},收到请求,msg:{},delayType:{}",newDate(), msg, delayType);sender.sendMsg(msg,Objects.requireNonNull(DelayMessageSender.getByIntValue(delayType)));}}
接下来开始测试,我用的是swagger,大家可以用postman等其他方法自行测试


打开我们的rabbitmq后台就可以看到我们交换机和队列信息

同样的方法,我们创建一个60s之后才能消费的订单


上面的实现仅能设置两个指定的时间10s和60s,接下来我们设置任意时间的延迟队列
4.3 RabbitMq的优化
我们需要一种更通用的方案才能满足需求,那么就只能将TTL设置在消息属性里了,只有如此我们才能更加灵活的实现延迟队列的具体业务开发,方法也很简单,我们只需要增加一个延时队列,用于接收设置为任意延时时长的消息,同时增加一个相应的死信队列和routingkey,但是该方法有个极大的弊端就是如果使用在消息属性上设置TTL的方式,消息可能并不会按时“死亡“,因为RabbitMQ只会检查第一个消息是否过期,如果过期则丢到死信队列,所以如果第一个消息的延时时长很长,而第二个消息的延时时长很短,则第二个消息并不会优先得到执行,此处则不再进行编写代码,但是为了解决这个问题,我们将利用rabbitMq插件实现延迟队列。
4.4 利用插件实现延迟队列
4.4.1 下载插件

下载完成之后进行解压,此处推荐bandzip进行解压,并且将解压之后的文件夹放到rabbitmq的安装目录下的plugins目录下

进入到sbin目录下使用cmd执行以下指令来启用插件
rabbitmq-plugins enable rabbitmq_delayed_message_exchange

执行以上步骤之后开始重启我们的rabbitmq服务
1.进入到服务

2.进入到sbin目录,双击rabbitmq-server.bat

验证是否重启成功访问http://localhost:15672
如果能够访问成功说明重启成功
4.4.2 编写代码
重新创建一个配置类
importorg.springframework.amqp.core.Binding;importorg.springframework.amqp.core.BindingBuilder;importorg.springframework.amqp.core.CustomExchange;importorg.springframework.amqp.core.Queue;importorg.springframework.beans.factory.annotation.Qualifier;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importjava.util.HashMap;importjava.util.Map;@ConfigurationpublicclassDelayedRabbitMQConfig{publicstaticfinalString DELAYED_QUEUE_NAME ="delay.queue.demo.delay.queue";publicstaticfinalString DELAYED_EXCHANGE_NAME ="delay.queue.demo.delay.exchange";publicstaticfinalString DELAYED_ROUTING_KEY ="delay.queue.demo.delay.routingkey";@BeanpublicQueueimmediateQueue(){returnnewQueue(DELAYED_QUEUE_NAME);}@BeanpublicCustomExchangecustomExchange(){Map<String,Object> args =newHashMap<>();args.put("x-delayed-type","direct");returnnewCustomExchange(DELAYED_EXCHANGE_NAME,"x-delayed-message",true,false, args);}@BeanpublicBindingbindingNotify(@Qualifier("immediateQueue")Queue queue,@Qualifier("customExchange")CustomExchange customExchange){returnBindingBuilder.bind(queue).to(customExchange).with(DELAYED_ROUTING_KEY).noargs();}}
新建一个消息的发送者
importorg.springframework.amqp.rabbit.core.RabbitTemplate;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;importstaticcom.talent.infocenter.rabbitMq.DelayedRabbitMQConfig.DELAYED_EXCHANGE_NAME;importstaticcom.talent.infocenter.rabbitMq.DelayedRabbitMQConfig.DELAYED_ROUTING_KEY;@ServicepublicclassProvider{@AutowiredprivateRabbitTemplate rabbitTemplate;publicvoidsendDelayMsg(String msg,Integer delayTime){rabbitTemplate.convertAndSend(DELAYED_EXCHANGE_NAME, DELAYED_ROUTING_KEY, msg, a ->{a.getMessageProperties().setDelay(delayTime*1000);return a;});}}
新建一个消息的消费者
importcom.rabbitmq.client.Channel;importlombok.extern.slf4j.Slf4j;importorg.springframework.amqp.core.Message;importorg.springframework.amqp.rabbit.annotation.RabbitListener;importorg.springframework.stereotype.Component;importjava.io.IOException;importjava.util.Date;importstaticcom.talent.infocenter.rabbitMq.DelayedRabbitMQConfig.DELAYED_QUEUE_NAME;@Slf4j@ComponentpublicclassConsumer{@RabbitListener(queues = DELAYED_QUEUE_NAME)publicvoidreceiveD(Message message,Channel channel)throwsIOException{String msg =newString(message.getBody());log.info("当前时间:{},延时队列收到消息:{}",newDate().toString(), msg);channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);}}
修改我们之前的接口
importcom.talent.api.utils.RedisUtils;importcom.talent.infocenter.rabbitMq.Provider;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RequestMethod;importorg.springframework.web.bind.annotation.RequestParam;importorg.springframework.web.bind.annotation.RestController;importjava.util.Date;importjava.util.Objects;@Slf4j@RestControllerpublicclassRabbitMQMsgController{@AutowiredprivateProvider provider;@RequestMapping(value ="sendmsg", method =RequestMethod.GET)publicvoidsendMsg(@RequestParam(value ="msg")String msg,@RequestParam(value ="delayTime")Integer delayTime){log.info("当前时间:{},收到请求,msg:{},delayTime:{}",newDate(), msg, delayTime);provider.sendDelayMsg(msg, delayTime);}}
接下来开始测试

再接着测试一下我们不同顺序的订单是否是按照时间顺序进行消费的
我们将订单0002设置为60s,订单0003设置为15s,看看订单0003能否在0002之前消费

结果显而易见订单0003确实在第15s的时候被消费掉
5.总结
延时队列在需要延时处理的场景下非常有用,而且十分稳定,使用RabbitMQ来实现延时队列可以很好的利用RabbitMQ的特性,如:消息可靠发送、消息可靠投递、死信队列来保障消息至少被消费一次以及未被正确处理的消息不会被丢弃。另外,通过RabbitMQ集群的特性,可以很好的解决单点故障问题,不会因为单个节点挂掉导致延时队列不可用或者消息丢失。
当然,延时队列还有很多其它选择,比如利用Java的DelayQueu,利用Redis的zset,利用Quartz或者利用kafka的时间轮,这些方式各有特点。
相关文章:

RabbitMQ 实现延迟队列
业务场景:1.生成订单30分钟未支付,则自动取消,我们该怎么实现呢?2.生成订单60秒后,给用户发短信1 安装rabbitMqwindows安装ubuntu中安装2 添加maven依赖<!-- https://mvnrepository.com/artifact/org.springframework.boot/spr…...

Spring Bean 生命周期,好像人的一生
简单说说IoC和Bean IoC,控制反转,想必大家都知道,所谓的控制反转,就是把new对象的权利交给容器,所有的对象都被容器控制,这就叫所谓的控制反转。 控制反转 Bean,也不是什么新鲜玩意儿…...

C++算法基础课 05 —— 数据结构1_单链表/双链表/栈/单调栈/队列/单调队列/KMP
文章目录 1. 单链表(用数组模拟链表)1.1 模板1.1.1 插入操作1.1.2 删除操作1.2 习题1 —— 826.单链表2. 双链表2.1 模板2.1.1 插入操作2.1.2 删除操作2.2 习题1 —— 827.双链表3. 栈(用数组模拟栈)3.1 模板3.2 习题1 —— 828.模拟栈4. 单调栈4.1 模板4.2 习题1 —— 830.单调…...

小型水库大坝安全监测的主要对象
一、监测背景 大坝监测的目的分成两个大的方面,一方面是为了验证设计、指导施工、为科研提供必要的资料;另一方面,也可以说是更重要的方面,就是为了长期监视大坝的安全运行。因此,一个成功的监测设计者不仅要能充分领会…...

常见软件开源(alpha,beta等)版本介绍
一、开发期Alpha:是内部测试版,一般不向外部发布,会有很多Bug.一般只有测试人员使用。Beta:也是测试版,这个阶段的版本会一直加入新的功能。在Alpha版之后推出。-RC(ReleaseCandidate):最终测试版本;可能成为最终产品的…...

凌恩生物资讯|抗性宏基因组又一力作|抗性基因+可移动元件研究新成果!
凌恩生物合作客户:合肥工业大学崔康平老师团队利用凌恩生物宏基因组抗性基因研究解决方案,对污水处理厂活性污泥中的钆(Gd(III))和抗生素磺胺甲噁唑(SMX)的联合污染情况进行了调查&a…...

常见前端基础面试题(HTML,CSS,JS)(二)
ES6 新增哪些东西 箭头函数字符串模板支持模块化(import、export)类(class、constructor、extends)let、const 关键字新增一些数组、字符串等内置构造函数方法,例如 Array.from、Array.of 、Math.sign、Math.trunc 等…...

按关键词搜索,商品详情采集,API接口
公共参数 名称类型必须描述keyString是 调用key(必须以GET方式拼接在URL中) 注册Key和secret测试: https://o0b.cn/anzexi secretString是调用密钥api_nameString是API接口名称(包括在请求地址中)[item_search,item_g…...

C++的纯虚函数使用与接口实现
虚函数主要是为了父类指针访问子类同名成员方法而引入的,即通过重写了父类的方法,从而实现多态。 01 为何引入纯虚函数 对于普通虚函数,如果子类没有重写相应的虚函数,那么父类指针就只能调用父类函数实现,然而父类有…...

Exception has occurred: ModuleNotFoundErrorNo module named ‘urllib3‘【已解决】
问题描述 实际上只是想要测试一下torch是否安装成功,输出相应版本。谁知道就报错了。 Exception has occurred: ModuleNotFoundError No module named urllib3 解决方案 (1)使用pip或者conda卸载urllib3 pip uninstall urllib3conda unin…...

CSS 盒子模型【快速掌握知识点】
目录 一、什么是盒子模型 二、边框border-color 三、边框粗细border-width 四、边框样式border-style 五、外边距margin 六、内边距padding 七、圆角边框 八、圆形 九、盒子阴影 一、什么是盒子模型 css盒子模型又称为框模型,盒子的最内部是元素的实际内容…...

公网远程连接Oracle数据库【内网穿透】
文章目录1. 数据库搭建2. 内网穿透2.1 安装cpolar内网穿透2.2 创建隧道映射3. 公网远程访问4. 配置固定TCP端口地址4.1 保留一个固定的公网TCP端口地址4.2 配置固定公网TCP端口地址4.3 测试使用固定TCP端口地址远程OracleOracle,是甲骨文公司的一款关系数据库管理系…...

国内售价仅10元的鸭子滑梯玩具TK卖到20美元,相关视频获400万+播放!
在TikTok上玩具一直是增速极快的一个类目,不同于很多其他品类在疫情期间取得了巨大增长但在疫情后销售大幅下降的现象不同,全球玩具市场继续表现并保持稳定的较高的销售水平。美国市场研究机构NPD的统计,2021年,全球玩具市场的销售…...

直播平台的视频美颜sdk是什么?
直播平台的视频美颜sdk是什么,可以做什么?简而言之,直播美颜sdk是将直播平台的视频美颜效果做成一个sdk,给用户提供美颜效果选择,同时提供不同的视频分辨率,可以让用户在观看直播时有更好的体验。那么具体有…...

实现Vue组件库
实现Vue组件库 如何实现一个Vue组件库 Vue组件库是一种常见的前端工具,可以提供可复用的UI组件来简化应用程序的开发和维护。本文将介绍如何实现一个基本的Vue组件库。 步骤一:创建Vue项目 首先,我们需要使用Vue CLI创建一个Vue项目。打开…...

面试 | 移位妙解递归乘法【细节决定成败】
不用[ * ]如何使两数相乘❓一、题目明细二、思路罗列 & 代码解析1、野蛮A * B【不符合题意】2、sizeof【可借鉴】解析3、简易递归【推荐】① 解析(递归展开图)② 时间复杂度分析4、移位<<运算【有挑战性💪】① 思路顺理② 算法图解…...

项目缓存问题处理
1、public/index.html文件头部配置 <meta http-equiv"pragram" content"no-cache"> <meta http-equiv"cache-control" content"no-cache,no-store,must-revalidate"> <meta http-equiv"expires" content&…...

DS期末复习卷(八)
一、选择题(30分) 1.字符串的长度是指( C )。 (A) 串中不同字符的个数 (B) 串中不同字母的个数 (C ) 串中所含字符的个数 (D) 串中不同数字的个数 2.建立一个长度为n的有序单链表的时间复杂度为( C ) (A) O(n) (B) O(1) © …...

第50讲:SQL优化之LIMIT分页查询的优化
文章目录 1.LIMIT分页查询的优化概念2.LIMIT分页查询优化前后的效果2.1.LIMIT分页查询优化前2.2.LIMIT分页查询优化后1.LIMIT分页查询的优化概念 当表中数据量小时,分页查询基本上没有什么压力,查询速度也会很快,但是一般当表的数据量很庞大时,上千万条数据,此时分页查询…...

做独立开发者,能在AppStore赚到多少钱?
成为一名独立开发者,不用朝九晚五的上班,开发自己感兴趣的产品,在AppStore里赚美金,这可能是很多程序员的梦想,今天就来盘一盘,这个梦想实现的概率有多少。 先来了解一些数据: 2022年5月26日&am…...

CSS 基础【快速掌握知识点】
目录 一、什么是CSS 二、CSS发展史 三、CSS基本语法结构 1、语法 2、例如 四、style标签 五、HTML中引入CSS样式 1、行内样式 2、内部样式表 3、外部样式表 六、CSS基本选择器 1、标签选择器 2、类选择器 3、ID选择器 4、总结 5、基本选择器的优先级 七、CSS的…...

Linux 驱动基础
注册驱动模块时给模块传递参数 在一些情况下,我们要动态的改变驱动中某个变量的值,那么就可以在注册时给驱动模块传递参数。 给驱动模块中传递参数,需要定义好接受参数值的全局变量,并调用module_param 来引用它,具体…...

linux 共享内存操作(shm_open、mmap、编译链接库:-lz -lrt -lm -lc都是什么库)
文章目录linux 共享内存操作(shm_open)一、背景二、函数使用说明shm_openftruncate(改变文件大小)mmap共享内存三、示例代码创建内存共享文件并写入数据打开内存共享文件读数据四、问题总结shm_write.c:(.text0x18): undefined re…...

做出改变:农业科技和区块链在为地球的未来而战中的力量
到2050年,全球有100亿人需要养活,全世界都在关注区块链和农业信息化,以推动发展中国家的技术革新。 自成立以来,区块链技术已经找到了多样化和有价值的应用,以帮助提高效率和激励社区在不同领域和行业的参与。 农业是…...

树莓派介绍
文章目录一.树莓派介绍二.树莓派分类一.树莓派介绍 树莓派,(英语:Raspberry Pi,简写为RPi,别名为RasPi / RPI)是为学习计算机编程教育而设计,只有信用卡大小的微型电脑,其系统基于L…...

[神经网络]基干网络之VGG、ShuffleNet
一、VGG VGG是传统神经网络堆叠能达到的极限深度。 VGG分为VGG16和VGG19,其均有以下特点: ①按2x2的Pooling层,网络可以分成若干段 ②每段之内由若干same卷积操作构成,段内Feature Map数量固定不变; ③Feature Map按2的…...

Java 日期时间与正则表达式,超详细整理,适合新手入门
目录 1、java.time.LocalDate类表示日期; 2、java.time.LocalTime类表示时间; 3、java.time.LocalDateTime类表示日期和时间; 4、java.time.format.DateTimeFormatter类用于格式化日期和时间; 5、创建正则表达式对象 6、匹配…...

用Netty实现物联网04:自定义通信协议
上一讲咱们澄清了Netty的一些基本概念,然后也写了一个服务端与客户端通信的简单应答程序。从这一讲开始,就来一步步搭建一个Netty物联网应用。 大多数硬件电子产品,都自带了嵌入式软件,或者说固件。这些嵌入式软件/固件基本上都是用C/C++编写的。由于这些小微电子设备资源极…...

「smardaten」上架钉钉应用中心!让进步再一次发生
使用钉钉的团队小伙伴们,smardaten给您送来福利啦~为了给更多团队提供更优质的应用开发体验,方便用户在线、快速使用无代码,数睿数据近期在【钉钉应用中心】发布smardaten在线版本。继与华为云、亚马逊云建立战略合作之后,smardat…...

3、Maven安装
前言:工具下载地址阿里云盘:Maven:https://www.aliyundrive.com/s/SgHKjQ5doSp提取码: ml40一、什么是maven?Apache Maven是个项目管理和自动构建工具,基于项目对象模型(POM)的概念。作用:完成…...