RabbitMq 资料

1.win 安装

第一步:下载并安装erlang
  • RabbitMQ服务端代码是使用并发式语言Erlang编写的,安装RabbitMQ的前提是安装Erlan ,下载地址为 :http://www.erlang.org/downloads
  • 配置环境变量 此电脑–>鼠标右键 “属性”–>高级系统设置–>环境变量–>“新建” 系统环境变量 ;值为el 安装路径
  • 双击系统变量path, 将%ERLANG_HOME%\bin加入到path中
  • 最后windows键+R键,输入cmd,再输入erl,看到版本号就说明erlang安装成功了 .
第二步:下载并安装RabbitMQ
  • 并安装RabbitMQ 下载地址:http://www.rabbitmq.com/download.html
  • RabbitMQ安装好后接下来安装RabbitMQ-Plugins。打开命令行cd,输入RabbitMQ的sbin目录。
  • 进入sbin目录后输入 rabbitmq-plugins enable rabbitmq_management进行安装
  • 如果执行失败,执行下面的操作。首先在命令行输入:rabbitmq-service stop,接着输入rabbitmq-service remove,再接着输入rabbitmq-service install,接着输入rabbitmq-service start,最后重新输入rabbitmq-plugins enable rabbitmq_management。
  • 输入 rabbitmqctl status 校验是否安装成功
  • 浏览器输入 http://localhost:15672 就能看到 RabbitMQ的首页,默认账号与密码为 guest

2.后台管理界面

overview

overview→Totals :所有队列的阻塞情况
Ready:待消费的消息总数
Unacked:待应答的消息总数
Total:总数 Ready+Unacked
Publish:producter pub消息的速率。
Publisher confirm:broker确认pub消息的速率。
Deliver(manual ack):customer手动确认的速率。
Deliver( auto ack):customer自动确认的速率。
Consumer ack:customer正在确认的速率。
Redelivered:正在传递’redelivered’标志集的消息的速率。
Get (manual ack):响应basic.get而要求确认的消息的传输速率。
Get (auto ack):响应于basic.get而发送不需要确认的消息的速率。
Return:将basic.return发送给producter的速率。
Disk read:queue从磁盘读取消息的速率。
Disk write:queue从磁盘写入消息的速率。

整体角色的个数
Connections:client的tcp连接的总数。
Channels:通道的总数。
Exchange:交换器的总数。
Queues:队列的总数。
Consumers:消费者的总数。

Overview→Nodes
broker的属性
Name:broker名称
File descriptors:broker打开的文件描述符和限制。
Socket descriptors:broker管理的网络套接字数量和限制。当限制被耗尽时,RabbitMQ将停止接受新的网络连接。
Erlang processes:erlang启动的进程数。
Memory:当前broker占用的内存。
Disk space:当前broker占用的硬盘。
Uptime:当前broker持续运行的时长。
Info:集群的信息。
Reset stats:重启单节点或整个集群。

Overview->Export definitions
定义由用户,虚拟主机,权限,参数,交换,队列和绑定组成。 它们不包括队列的内容或集群名称。 独占队列不会被导出。
Overview->Import definitions
导入的定义将与当前定义合并。 如果在导入过程中发生错误,则所做的任何更改都不会回滚。

Connections 连接的属性

Virtual host:所属的虚拟主机。
Name:名称。
User name:使用的用户名。
State:当前的状态,running:运行中;idle:空闲。
SSL/TLS:是否使用ssl进行连接。
Protocol:使用的协议。
Channels:创建的channel的总数。
From client:每秒发出的数据包。
To client:每秒收到的数据包。

Channels 当前连接所有创建的通道

通道的属性
channel:名称。
Node:节点名称。
Virtual host:所属的虚拟主机。
User name:使用的用户名。
Mode:渠道保证模式。 可以是以下之一,或者不是:C: confirm。T:transactional(事务)。
State :当前的状态,running:运行中;idle:空闲。
Unconfirmed:待confirm的消息总数。
Prefetch:设置的prefetch的个数。
Unacker:待ack的消息总数。
publish:producter pub消息的速率。
confirm:producter confirm消息的速率。
deliver/get:consumer 获取消息的速率。
ack:consumer ack消息的速率。

Exchanges 交换器属性

Virtual host:所属的虚拟主机。
Name:名称。
Type:exchange type,
Features:功能。 可以是以下之一,或者不是:D: 持久化。T:Internal,存在改功能表示这个exchange不可以被client用来推送消息,仅用来进行exchange和exchange之间的绑定,否则可以推送消息也可以绑定。
Message rate in:消息进入的速率。
Message rate out:消息出去的速率。

Queues 队列的属性

Virtual host:所属的虚拟主机。
Name:名称。
Features:功能。 可以是以下之一,或者不是:D: 持久化。
State:当前的状态,running:运行中;idle:空闲。
Ready:待消费的消息总数。
Unacked:待应答的消息总数。
Total:总数 Ready+Unacked。
incoming:消息进入的速率。
deliver/get:消息获取的速率。
ack:消息应答的速率。

3.基本概念

rabbitMq简介
   ```

1.MQ(Message Queue,消息队列)是一种应用系统之间的通信方法。是通过读写出入队列的消息来通信(RPC则是通过直接调用彼此来通信的)。消息发送后可以立即返回,由消息系统来确保消息的可靠传递。消息发布者只管把消息发布到 MQ 中而不用管谁来取,消息使用者只管从 MQ 中取消息而不管是谁发布的。这样发布者和使用者都不用知道对方的存在。
2.AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。
AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。
3.RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。常见场景包括:业务解耦、最终一致性、广播、错峰流控等等
```

RabbitMQ基本概念
Message:消息,包含消息头(即附属的配置信息)和消息体(即消息的实体内容)
Publisher:生产者,向交换机发布消息的主体
Exchange:交换机,用来接收生产者发送的消息并将这些消息路由给服务器中的队列
Binding:绑定,用于给Exchange和Queue建立关系,就是我们熟知的配对的红娘
Queue:消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。
Connection:连接
Channel:通道,MQ与外部打交道都是通过Channel来的,发布消息、订阅队列还是接收消息,这些动作都是通Channel完成;简单来说就是消息通过Channel塞进队列或者流出队列
Consumer:消费者,从消息队列中获取消息的主体
Virtual Host: 虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在连接时指定,RabbitMQ 默认的 vhost 是 /
Broker:消息队列服务器实体
Routing Key: 路由关键字,exchange根据这个关键字进行消息投递
AMQP 中的消息路由
AMQP 中消息的路由过程和 Java 开发者熟悉的 JMS 存在一些差别,AMQP 中增加了 Exchange 和 Binding 的角色。生产者把消息发布到 Exchange 上,消息最终到达队列并被消费者接收,而 Binding 决定交换器的消息应该发送到那个队列。
Exchange 类型

Exchange分发消息时根据类型的不同分发策略有区别,目前共四种类型:direct、fanout、topic、headers 。headers 匹配 AMQP 消息的 header 而不是路由键,此外 headers 交换器和 direct 交换器完全一致,但性能差很多,目前几乎用不到了,所以直接看另外三种类型:

Default Exchange
这种是特殊的Direct Exchange,是rabbitmq内部默认的一个交换机。该交换机的name是空字符串,所有queue都默认binding 到该交换机上。所有binding到该交换机上的queue,routing-key都和queue的name一样
direct 完全匹配(单播)交换机
消息中的路由键(routing key)如果和 Binding 中的 binding key 一致, 交换器就将消息发到对应的队列中。路由键与队列名完全匹配,如果一个队列绑定到交换机要求路由键为“test”,则只转发 routing key 标记为“test”的消息,不会转发“test.test”,也不会转发“test.test1”等等。它是完全匹配、单播的模式。
fanout 扇形(广播式)交换机
每个发到 fanout 类型交换器的消息都会分到所有绑定的队列上去。fanout 交换器不处理路由键,只是简单的将队列绑定到交换器上,每个发送到交换器的消息都会被转发到与该交换器绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。fanout 类型转发消息是最快的。
topic 通配符交换机
通配符 说明 示例
* 匹配一个或多个内容 bigdata. * 可以匹配到bigdata.spark或者 bigdata.hadoop.hive等
# 匹配一个内容 bigdata.# 只能匹配到bigdata.spark或者bigdata.hadoop
topic 交换器通过模式匹配分配消息的路由键属性,将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上。它将路由键和绑定键的字符串切分成单词,这些单词之间用点隔开。它同样也会识别两个通配符:符号“#”和符号“*”。#匹配0个或多个单词,*匹配不多不少一个单词
Headers header attribute参数类型的交换机
headers 也是根据规则匹配, 相较于 direct 和 topic 固定地使用 routing_key , headers 则是一个自定义匹配规则的类型.在队列与交换器绑定时, 会设定一组键值对规则, 消息中也包括一组键值对( headers 属性), 当这些键值对有一对, 或全部匹配时, 消息被投送到对应队列
ACK 确认机制
每个Consumer可能需要一段时间才能处理完收到的数据。如果在这个过程中,Consumer出错了,异常退出了,而数据还没有处理完成,那么 非常不幸,这段数据就丢失了。因为我们采用no-ack的方式进行确认,也就是说,每次Consumer接到数据后,而不管是否处理完成,RabbitMQ Server会立即把这个Message标记为完成,然后从queue中删除了。
如果一个Consumer异常退出了,它处理的数据能够被另外的Consumer处理,这样数据在这种情况下就不会丢失了(注意是这种情况下)。
为了保证数据不被丢失,RabbitMQ支持消息确认机制,即acknowledgments。
为了保证数据能被正确处理而不仅仅是被Consumer收到,那么我们不能采用no-ack。而应该是在处理完数据后发ack。
在处理数据后发送的ack,就是告诉RabbitMQ数据已经被接收,处理完成,RabbitMQ可以去安全的删除它了。
如果Consumer退出了但是没有发送ack,那么RabbitMQ就会把这个Message发送到下一个Consumer。
这样就保证了在Consumer异常退出的情况下数据也不会丢失。
死信队列
死信队列同其他的队列一样都是普通的队列。在RabbitMQ中并没有特定的“死信队列”类型,而是通过配置,将其实现。
当我们在创建一个业务的交换机和队列的时候,可以配置参数,指明另一个队列为当前队列的死信队列,在RabbitMQ中,死信队列(严格的说应该是死信交换机)被称为DLX Exchange。当消息“死掉”后,会被自动路由到DLX Exchange的queue中.
什么样的消息会进入死信队列?
1.消息的TTL过期(Time To Live)-存活时间已经过期
2.消费者对broker应答Nack,并且消息禁止重回队列。(basic.reject or basic.nack) 且带 requeue=false不重新入队参数或达到的retry重新入队的上限次数
3.Queue队列长度已达上限。队列满,queue的"x-max-length"参数)
应用场景:
重要的业务队列如果失败,就需要重新将消息用另一种业务逻辑处理;如果是正常的业务逻辑故意让消息中不合法的值失败,就不需要死信
配置参数详解
#属性文件:org.springframework.boot.autoconfigure.amqp.RabbitProperties
#Config:
# base
spring.rabbitmq.host: 服务Host
spring.rabbitmq.port: 服务端口
spring.rabbitmq.username: 登陆用户名
spring.rabbitmq.password: 登陆密码
spring.rabbitmq.virtual-host: 连接到rabbitMQ的vhost
spring.rabbitmq.addresses: 指定client连接到的server的地址,多个以逗号分隔(优先取addresses,然后再取host)
spring.rabbitmq.requested-heartbeat: 指定心跳超时,单位秒,0为不指定;默认60s
spring.rabbitmq.publisher-confirms: 是否启用【发布确认】
spring.rabbitmq.publisher-returns: 是否启用【发布返回】
spring.rabbitmq.connection-timeout: 连接超时,单位毫秒,0表示无穷大,不超时
spring.rabbitmq.parsed-addresses:
# ssl
spring.rabbitmq.ssl.enabled: 是否支持ssl
spring.rabbitmq.ssl.key-store: 指定持有SSL certificate的key store的路径
spring.rabbitmq.ssl.key-store-password: 指定访问key store的密码
spring.rabbitmq.ssl.trust-store: 指定持有SSL certificates的Trust store
spring.rabbitmq.ssl.trust-store-password: 指定访问trust store的密码
spring.rabbitmq.ssl.algorithm: ssl使用的算法,例如,TLSv1.1
# cache
spring.rabbitmq.cache.channel.size: 缓存中保持的channel数量
spring.rabbitmq.cache.channel.checkout-timeout: 当缓存数量被设置时,从缓存中获取一个channel的超时时间,单位毫秒;如果为0,则总是创建一个新channel
spring.rabbitmq.cache.connection.size: 缓存的连接数,只有是CONNECTION模式时生效
spring.rabbitmq.cache.connection.mode: 连接工厂缓存模式:CHANNEL 和 CONNECTION
# listener
spring.rabbitmq.listener.simple.auto-startup: 是否启动时自动启动容器
spring.rabbitmq.listener.simple.acknowledge-mode: 表示消息确认方式,其有三种配置方式,分别是none、manual和auto;默认auto
spring.rabbitmq.listener.simple.concurrency: 最小的消费者数量
spring.rabbitmq.listener.simple.max-concurrency: 最大的消费者数量
spring.rabbitmq.listener.simple.prefetch: 指定一个请求能处理多少个消息,如果有事务的话,必须大于等于transaction数量.
spring.rabbitmq.listener.simple.transaction-size: 指定一个事务处理的消息数量,最好是小于等于prefetch的数量.
spring.rabbitmq.listener.simple.default-requeue-rejected: 决定被拒绝的消息是否重新入队;默认是true(与参数acknowledge-mode有关系)
spring.rabbitmq.listener.simple.idle-event-interval: 多少长时间发布空闲容器时间,单位毫秒
spring.rabbitmq.listener.simple.retry.enabled: 监听重试是否可用
spring.rabbitmq.listener.simple.retry.max-attempts: 最大重试次数
spring.rabbitmq.listener.simple.retry.initial-interval:第一次和第二次尝试发布或传递消息之间的间隔
spring.rabbitmq.listener.simple.retry.multiplier: 应用于上一重试间隔的乘数
spring.rabbitmq.listener.simple.retry.max-interval: 最大重试时间间隔
spring.rabbitmq.listener.simple.retry.stateless: 重试是有状态or无状态
# template
spring.rabbitmq.template.mandatory: 启用强制信息;默认false
spring.rabbitmq.template.receive-timeout: receive() 操作的超时时间
spring.rabbitmq.template.reply-timeout: sendAndReceive() 操作的超时时间
spring.rabbitmq.template.retry.enabled: 发送重试是否可用
spring.rabbitmq.template.retry.max-attempts: 最大重试次数
spring.rabbitmq.template.retry.initial-interval: 第一次和第二次尝试发布或传递消息之间的间隔
spring.rabbitmq.template.retry.multiplier: 应用于上一重试间隔的乘数
spring.rabbitmq.template.retry.max-interval: 最大重试时间间隔

4.springboot集成RabbitMQ

  • 配置pom包,主要是添加spring-boot-starter-amqp的支持
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
  • 配置rabbitmq的安装地址、端口以及账户信息
spring:application:name: upms-servicemain:allow-bean-definition-overriding: true #当遇到同样名字的时候,是否允许覆盖注册rabbitmq:username: guestpassword: guestport: 5672host: localhost
direct Exchange 模式
  • springBoot 集成mq 默认是direct模式
//配置test队列
@Configuration
public class RabbitConfig {@Beanpublic Queue helloQueue() {return new Queue("test");}
}// 消费者监听test队列
@Component
public class TestReceiver {@RabbitListener(queues = "test")
public void receiveMessageTest(String test) throws InterruptedException {System.out.println("======处理信息======");Thread.sleep(1000L);System.out.println("test Receiver  : " + test+"===="+System.currentTimeMillis());System.out.println("======处理信息结束======");}
}
//生产者发送消息
@RestController
public class MQController {@AutowiredRabbitTemplate rabbitTemplate;@GetMapping("/send")public void send() {String context = "hello " +                                          System.currentTimeMillis()+":"+Thread.currentThread().getName(); System.out.println("Sender : " + context);rabbitTemplate.convertAndSend("test", context);}
}
fanout Exchange 模式
/*** @描述* 说明:*  1. fanout路由又称为广播路由,会将收到的消息广播到消息对列上。当使用fanout交换器时,它会将消息广播到与该交换器绑定的所有队列上,有利于对单条消息做不同的反应。*  2. fanout路由无需指定route key,即使指定了也会被忽略,只要队列与交换机绑定,那么发送到交换机上的消息就会被分发到消息接收者上。**/
//配置fanout队列和交换机
@Slf4j
@Configuration
public class TestExchangeConfiguration {/*** 交换机名称*/public static final String FANOUT_EXCHANGE_NAME = "fanout.exchange.name";/*** 测试队列名称1*/public static final String TEST_QUEUE1_NAME = "test.queue1.name";/*** 测试队列名称1*/public static final String TEST_QUEUE2_NAME = "test.queue2.name";/*** 创建广播形式的交换机** @return 交换机实例*/@Beanpublic FanoutExchange fanoutExchange() {log.info("【【【交换机实例创建成功】】】");return new FanoutExchange(FANOUT_EXCHANGE_NAME);}/*** 测试队列一** @return 队列实例*/@Beanpublic Queue queue1() {log.info("【【【测试队列一实例创建成功】】】");return new Queue(TEST_QUEUE1_NAME);}/*** 测试队列二** @return 队列实例*/@Beanpublic Queue queue2() {log.info("【【【测试队列二实例创建成功】】】");return new Queue(TEST_QUEUE2_NAME);}/*** 绑定队列一到交换机** @return 绑定对象*/@Beanpublic Binding bingQueue1ToExchange() {log.info("【【【绑定队列一到交换机成功】】】");return BindingBuilder.bind(queue1()).to(fanoutExchange());}/*** 绑定队列二到交换机** @return 绑定对象*/@Beanpublic Binding bingQueue2ToExchange() {log.info("【【【绑定队列二到交换机成功】】】");return BindingBuilder.bind(queue2()).to(fanoutExchange());}
}// 消费者监听队列
@Component
@Slf4j
public class TestReceiver {@RabbitHandler@RabbitListener(queues = "test.queue1.name")public void receiveMessage(String message) {log.info("消息接收者接收到来自【队列一】的消息,消息内容: {}", message);}@RabbitHandler@RabbitListener(queues = "test.queue2.name")public void receiveMessage1(String message) {log.info("消息接收者接收到来自【队列二】的消息,消息内容: {}", message);}
}
//发送消息到交换机,广播到所有队列
@RestController
@Slf4j
public class MQController {@AutowiredRabbitTemplate rabbitTemplate;/*** 发送消息** @param message 消息内容* 说明: routingKey可以指定也可以不指定,这里我们给一个空字符串""*/@GetMapping("/send")public void sendMessage(@RequestParam String message) {log.info("【消息发送者】发送消息到fanout交换机,消息内容为: {}", message);rabbitTemplate.convertAndSend("fanout.exchange.name", "", message);}}
topic Exchange 模式
//配置fanout队列和交换机
@Slf4j
@Configuration
public class TestTopicConfiguration {/*** 交换机名称*/public static final String FANOUT_EXCHANGE_NAME = "Topic.exchange.name";/*** 测试队列名称1*/public static final String TEST_QUEUE1_NAME = "Topic.queue1.name";/*** 测试队列名称1*/public static final String TEST_QUEUE2_NAME = "Topic.queue2.name";/*** 创建广播形式的交换机* @return 交换机实例*/@Beanpublic TopicExchange topicExchange() {log.info("【【【Topic交换机实例创建成功】】】");return new TopicExchange(FANOUT_EXCHANGE_NAME);}/*** 测试队列一     * @return 队列实例*/@Beanpublic Queue queue1() {log.info("【【【Topic测试队列一实例创建成功】】】");return new Queue(TEST_QUEUE1_NAME);/*** 测试队列二** @return 队列实例*/@Beanpublic Queue queue2() {log.info("【【【Topic测试队列二实例创建成功】】】");return new Queue(TEST_QUEUE2_NAME);}/*** 绑定队列一到交换机* 配置该消息队列的  routingKey* Topic.* 匹配 第一个.后面的单词 代表一个 单词* 比如 topic.asd 会被该消息队列接受 topic.asd.dsf不会被该消息队列接受* @return 绑定对象*/@Beanpublic Binding bingQueue1ToExchange() {log.info("【【【绑定队列一到交换机成功】】】");return BindingBuilder.bind(queue1()).to(topicExchange()).with("Topic.*");}/*** 绑定队列二到交换机* 配置该消息队列的  routingKey* Topic.# 匹配 所有.后面的单词代表任意个单词* 比如 topic.asd 会被该消息队列接受 topic.asd.dsf也会被该消息队列接受* @return 绑定对象*/@Beanpublic Binding bingQueue2ToExchange() {log.info("【【【绑定队列二到交换机成功】】】");return BindingBuilder.bind(queue2()).to(topicExchange()).with("Topic.#");}
}/*** @描述*/
// 消费者监听队列
@Component
@Slf4j
public class TestReceiver {@RabbitHandler@RabbitListener(queues = "Topic.queue1.name")public void receiveMessage2(String message) {log.info("Topic消息接收者接收到来自【队列一】的消息,消息内容: {}", message);}@RabbitHandler@RabbitListener(queues = "Topic.queue2.name")public void receiveMessage3(String message) {log.info("Topic消息接收者接收到来自【队列二】的消息,消息内容: {}", message);}
}
/** @描述*/
//发送消息到交换机
@RestController
@Slf4j
public class MQController {@AutowiredRabbitTemplate rabbitTemplate;@GetMapping("send")public void sendTwo(String message){log.info("【Topic消息发送者】发送消息到Topic交换机,消息内容为: {}", message);rabbitTemplate.convertAndSend("Topic.exchange.name","Topic.name",message);rabbitTemplate.convertAndSend("Topic.exchange.name","Topic.a",message);}}
Headers Exchange 模式
/*** @描述* 说明:*/
@Slf4j
@Configuration
public class TestHeadersConfiguration {/*** 交换机名称*/public static final String HEADERS_EXCHANGE_NAME = "Headers.exchange.name";/*** 测试队列名称1*/public static final String TEST_QUEUE1_NAME = "Headers.queue1.name";/*** 测试队列名称1*/public static final String TEST_QUEUE2_NAME = "Headers.queue2.name";/*** 创建广播形式的交换机** @return 交换机实例*/@Beanpublic HeadersExchange headersExchange() {log.info("【Headers交换机实例创建成功】");return new HeadersExchange(HEADERS_EXCHANGE_NAME);}/*** 测试队列一* @return 队列实例*/@Beanpublic Queue queue5() {log.info("【Headers测试队列一实例创建成功】");return new Queue(TEST_QUEUE1_NAME);}/*** 测试队列二* @return 队列实例*/@Beanpublic Queue queue6() {log.info("【Headers测试队列二实例创建成功】");return new Queue(TEST_QUEUE2_NAME);}@Beanpublic Binding bingQueue5ToExchange() {log.info("【Headers绑定队列一到交换机成功】");HashMap<String, Object> header = new HashMap<>();header.put("queue", "queue1");header.put("bindType", "whereAll");return BindingBuilder.bind(queue5()).to(headersExchange()).whereAll(header).match();}@Beanpublic Binding bingQueue6ToExchange() {log.info("【Headers绑定队列二到交换机成功】");HashMap<String, Object> header = new HashMap<>();header.put("queue", "queue2");header.put("bindType", "whereAny");return BindingBuilder.bind(queue6()).to(headersExchange()).whereAny(header).match();}/*** @描述*/
@Component
@Slf4j
public class TestReceiver {@RabbitListener(queues = "Headers.queue1.name")public void receiveMessage4(Message message) {try {log.info("Headers消息接收者接收到来自【队列一】的消息,消息内容: {}",new String(message.getBody(),message.getMessageProperties().getContentType()));} catch (UnsupportedEncodingException e) {e.printStackTrace();}}@RabbitListener(queues = "Headers.queue2.name")public void receiveMessage5(Message message) {try {log.info("Headers消息接收者接收到来自【队列二】的消息,消息内容: {}",new String(message.getBody(),message.getMessageProperties().getContentType()));} catch (UnsupportedEncodingException e) {e.printStackTrace();}}
}/*** @描述*/
@RestController
@Slf4j
public class MQController {@AutowiredRabbitTemplate rabbitTemplate;@GetMapping("send3")public void send3(String message){log.info("【Headers消息发送者】发送消息到Headers交换机,消息内容为: {}", message);MessageProperties messageProperties = new MessageProperties();messageProperties.setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT);messageProperties.setContentType("UTF-8");messageProperties.setHeader("queue", "queue2");messageProperties.setHeader("bindType", "whereAny");Message message1 = new Message(message.getBytes(), messageProperties);rabbitTemplate.convertAndSend("Headers.exchange.name", null, message1);}
}
序列化
    /*** 如果采用的是java的Serializable接口则生产者和消费者的包名必须一致***/@Beanpublic  MessageConverter messageConverter() {return new Jackson2JsonMessageConverter();}
消息持久化

消息在传输过程中,可能会出现各种异常失败甚至宕机情况,为了保证消息传输的可靠性,需要进行持久化,也就是在 数据写在磁盘上。消息队列持久化包括三部分

1.Message持久化

发送时消息持久化。(Message包含body,body为我们需要发送的消息具体内容,一般以json字符串发送,消费端再解析;MessageProperties为Message的一些额外的属性,做一些扩展作用)

public void send(String message){MessageProperties messageProperties = new MessageProperties();//消息持久化 MessageDeliveryMode.PERSISTENTmessageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);messageProperties.setContentType("UTF-8");Message message1 = new Message(message.getBytes(), messageProperties);//发送rabbitTemplate.convertAndSend("exchange.name", null, message1);}
2.队列持久化
@Bean
public Queue helloQueue() { return new Queue("hello",true);
} /***name:队列名*durable:是否持久化 默认为 true*exclusive: 排他队列, 默认 false 如果一个队列被声明为排他队列,该队列仅对首次声明它的连接可见,并在连接断开时自动删除。这里需要注意三点:其一,排他队列是基于连接可见的,同一连接的不同信道channel是可以同时访问同一个连接创建的排他队列的。其二,“首次”,如果一个连接已经声明了一个排他队列,其他连接是不允许建立同名的排他队列的,这个与普通队列不同。其三,即使该队列是持久化的,一旦连接关闭或者客户端退出,该排他队列都会被自动删除的。这种队列适用于只限于一个客户端发送读取消息的应用场景*  autoDelete: 自动删除, 默认 false 如果该队列没有任何订阅的消费者的话,该队列会被自动删除。这种队列适用于临时队列。*/public Queue(String name, boolean durable, boolean exclusive, boolean autoDelete) {this(name, durable, exclusive, autoDelete, (Map)null);}
3.交换机持久化
@Bean
public DirectExchange helloExchange(){ return new DirectExchange("helloexchange",true,false);
}
/** name 交换机名称 * durable 是否持久化 默认true* autoDelete 当交换机没有绑定队列时会自动删除交换机 默认false*/public DirectExchange(String name, boolean durable, boolean autoDelete) {super(name, durable, autoDelete);}
ack模式
应答模式
NONE
可以称之为自动回调,即使无响应或者发生异常均会通知队列消费成功,会丢失数据。
AUTO
自动检测异常或者超时事件,如果发生则返回noack,消息自动回到队尾,但是这种方式可能出现消息体本身有问题,返回队尾其他队列也不能消费,造成队列阻塞。
MANUAL
手动回调,在程序中我们可以对消息异常记性捕获,如果出现消息体格式错误问题,手动回复ack,接着再次调用发送接口把消息推到队尾。
yml配置
spring:rabbitmq:username: guestpassword: guestport: 5672host: localhostpublisher-confirms: true #  消息发送到交换机确认机制,是否确认回调publisher-returns: true  #  消息发送到交换机确认机制,是否返回回馈listener:       # 开启ACKdirect:    #NONE(默认):自动;AUTO:根据情况确认;MANUAL:手动确认acknowledge-mode: manualsimple:acknowledge-mode: manual
回调方法参数
 public class MsgSendConfirmCallBack implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnCallback {/*** 当消息发送到交换机(exchange)时,该方法被调用.* 1.如果消息没有到exchange,则 ack=false* 2.如果消息到达exchange,则 ack=true* @param correlationData 回调id* @param ack* @param cause*/ @Overridepublic void confirm(CorrelationData correlationData, boolean ack, String cause) {System.out.println("MsgSendConfirmCallBack, 回调id:" + correlationData);if (ack) {System.out.println("消息发送到exchange成功");    } else {System.err.println("消息发送到exchange失败");}}/*** 当消息从交换机到队列失败时,该方法被调用。(若成功,则不调用)* 需要注意的是:该方法调用后,MsgSendConfirmCallBack中的confirm方法也会被调用,且ack = true* @param message* @param replyCode* @param replyText* @param exchange* @param routingKey*/@Overridepublic void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {System.out.println("消息从交换机到队列失败]  message:"+message);}}/*** @描述  发送消息*/
@Service
@Slf4j
public class sendMq{@Autowiredprivate RabbitTemplate rabbitTemplate;public void send() {String context = Thread.currentThread().getName()+"你好现在是 " + new Date() +"";// ID标识 这样在RabbitConfirmCallBack中【消息唯一标识】 就不为空了CorrelationData date = new CorrelationData(UUID.randomUUID().toString());// 生产者发送消息到exchange后没有绑定的queue时将消息退回rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {log.info("消息主体: {}", message);log.info("回复编码: {}", replyCode);log.info("回复内容: {}", replyText);log.info("交换器: {}", exchange);log.info("路由键: {}", routingKey);log.info(Thread.currentThread().getName()+"发送消息被退回" + exchange + routingKey);});// 生产者发送消息confirm检测this.rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {log.info("消息唯一标识: {}", correlationData);log.info("确认状态: {}", ack);if (!ack) {log.info("造成原因: {}", cause);} else {System.out.println(Thread.currentThread().getName());log.info("消息发送成功 ");}});this.rabbitTemplate.convertAndSend("Direct.exchange.name", "Direct.queue1.name",context,date);}}
消息确认
          //消息接收者@RabbitListener(queues = "Direct.queue1.name")@RabbitHandlerpublic void receiveMessageTest1(String msg, CorrelationData correlationData, Channel channel, Message message) throws Exception {log.info("消息接收者接收到来自【Direct.exchange.name】的消息,消息内容: 【{}】,【{}】,【{}】,【{}】", message,correlationData,channel,message);//消息的标识,false只确认当前一个消息收到,true确认所有consumer获得的消息channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); //ack返回false,并重新回到队列,并重新发送到消息接收者channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);//丢弃这条消息channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false);//拒绝消息 deliveryTag:该消息的index  requeue:被拒绝的是否重新入队列 true 重新入队列channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);}
SpringRetry重试
rabbitMQ介绍
rabbitMQ有个方法channel.basicNack()能够让消息回到队列中,这样可以实现重试。但是这样没有明确重试次数,如果当前的消息一直重试的话,则后面的消息就会堆积起来,导致后面的消息无法消费。这是一个致命的缺点。因此这就需要设置重试次数来解决这种问题。下面提供几种解决方案。
1.使用redis或者mongo等第三方存储当前重试次数。
2.在header中添加重试次数,并且使用channel.basicPublish() 方法重新将消息发送出去后将重试次数加1。
3.使用spring-rabbit中自带的retry功能
手动确认模式说明
1.监听的方法内部必须使用channel进行消息确认,包括消费成功或消费失败
2.如果不手动确认,也不抛出异常,消息不会自动重新推送(包括其他消费者),因为对于rabbitmq来说始终没有接收到消息消费是否成功的确认,并且Channel是在消费端有缓存的,没有断开连接
3.如果rabbitmq断开,连接后会自动重新推送(不管是网络问题还是宕机)
4.如果消费端应用重启,消息会自动重新推送
5.如果消费端处理消息的时候宕机,消息会自动推给其他的消费者
6.如果监听消息的方法抛出异常,消息会按照listener.retry的配置进行重发,但是重发次数完了之后还抛出异常的话,消息不会重发(也不会重发到其他消费者),只有应用重启后会重新推送。因为retry是消费端内部处理的,包括异常也是内部处理,对于rabbitmq是不知道的(可使用死信队列)
7.spring.rabbitmq.listener.retry配置的重发是在消费端应用内处理的,不是rabbitqq重发
8.可以配置MessageRecoverer对异常消息进行处理,此处理会在listener.retry次数尝试完并还是抛出异常的情况下才会调可以配置MessageRecoverer对异常消息进行处理,此处理会在listener.retry次数尝试完并还是抛出异常的情况下才会调用,默认有两个实现:
//RepublishMessageRecoverer:将消息重新发送到指定队列,需手动配置,如:
@Bean
public MessageRecoverer messageRecoverer(RabbitTemplate rabbitTemplate){return new RepublishMessageRecoverer(rabbitTemplate, "exchangemsxferror", "routingkeymsxferror");
}
//RejectAndDontRequeueRecoverer:如果不手动配置MessageRecoverer,会默认使用这个,实现仅仅是将异常打印抛出,源码如下:
public class RejectAndDontRequeueRecoverer implements MessageRecoverer {protected Log logger = LogFactory.getLog(RejectAndDontRequeueRecoverer.class);@Overridepublic void recover(Message message, Throwable cause) {if (this.logger.isWarnEnabled()) {this.logger.warn("Retries exhausted for message " + message, cause);}throw new ListenerExecutionFailedException("Retry Policy Exhausted", new AmqpRejectAndDontRequeueException(cause), message);}
}
retry yml配置
  rabbitmq:username: guestpassword: guestport: 5672host: localhostpublisher-confirms: true #  消息发送到交换机确认机制,是否确认回调publisher-returns: true  #  消息发送到交换机确认机制,是否返回回馈listener:       # 开启ACKdirect:acknowledge-mode: manualsimple:acknowledge-mode: manualretry:enabled: true         #  允许消息消费失败的重试max-attempts: 6       # 消息最多消费次数6次initial-interval: 1000ms # 消息多次消费的间隔1秒max-interval: 1200000ms #重试最大时间间隔(单位毫秒)multiplier: 2 #应用于上一重试间隔的乘数 即重试时间为  上次重试时间*2stateless: truedefault-requeue-rejected: false  #  设置为false,会丢弃消息或者重新发布到死信队列
死信队列实现
yml配置
spring:rabbitmq:username: guestpassword: guestport: 5672host: localhostpublisher-confirms: true #  消息发送到交换机确认机制,是否确认回调publisher-returns: true  #  消息发送到交换机确认机制,是否返回回馈listener:       # 开启ACKdirect:acknowledge-mode: manualsimple:acknowledge-mode: manualretry:enabled: true         # 是否支持重试max-attempts: 6initial-interval: 5000msmultiplier: 2stateless: truedefault-requeue-rejected: false
死信交换机和队列配置
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.HashMap;
import java.util.Map;/*** @描述*/
@Configuration
@Slf4j
public class TestDLXConfiguration {/*** 延时队列* 发送到该队列的message会在一段时间后过期进入到delay_process_queue* 队列里所有的message都有统一的失效时间*/public static String DELAY_QUEUE   = "delay.queue";/*** 业务交换机*/public static String DELAY_EXCHANGE = "delay.queue.exchange";/*** 实际消费队列* message失效后进入的队列,也就是实际的消费队列*/public static final String PROCESS_QUEUE = "process.queue";/*** 处理的交换器*/public static String PROCESS_EXCHANGE = "process.queue.exchange";/*** 超时时间*/public static Long QUEUE_EXPIRATION = 4000L;/*** 配置处理交换(死信交换机)* @return*/@BeanDirectExchange processExchange() {log.info("【DLX交换机实例创建成功】");return new DirectExchange(PROCESS_EXCHANGE);}/*** 设置处理队列(死信队列)* @return*/@Beanpublic Queue processQueue() {log.info("【DLX测试队列一实例创建成功】");return QueueBuilder.durable(PROCESS_QUEUE).build();}/*** 将DLX绑定到实际消费队列(绑定死信队列到死信交换机)* @return*/@BeanBinding processBinding() {return BindingBuilder.bind(processQueue()).to(processExchange()).with(PROCESS_QUEUE);}/*** 配置业务队列* @return*/@Beanpublic Queue delayQueue() {//Map<String,Object> arguments = new HashMap<>(2);//arguments.put("x-dead-letter-exchange",DIRCET_EXCHANGE_NAME);// arguments.put("x-dead-letter-routing-key",TEST_QUEUE1_NAME);// arguments.put("x-message-ttl", 4000L);// return new Queue(orderQueue,true,false,false,arguments);//构造者模式return QueueBuilder.durable(DELAY_QUEUE)// DLX,dead letter发送到的exchange ,设置死信队列交换器到处理交换器.withArgument("x-dead-letter-exchange", PROCESS_EXCHANGE)// dead letter携带的routing key,配置处理队列的路由key.withArgument("x-dead-letter-routing-key", PROCESS_QUEUE)// 设置过期时间 当配置此事件则为延迟死信队列.withArgument("x-message-ttl", QUEUE_EXPIRATION).build();}/*** 配置业务交换机* @return*/@BeanDirectExchange delayExchange() {return new DirectExchange(DELAY_EXCHANGE);}/*** 将delayQueue2绑定延时交换机中,routingKey为队列名称* @return*/@BeanBinding delayBinding() {return BindingBuilder.bind(delayQueue()).to(delayExchange()).with(DELAY_QUEUE);}}//发送rabbitTemplate.convertAndSend(DelayConfig.DELAY_EXCHANGE,// routingKeyDelayConfig.DELAY_QUEUE,msg);
延迟死信队列两种配置方式
  • 方式一
rabbitTemplate.convertAndSend("Direct.exchange.name", "Direct.queue1.name",msg, message -> {                message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);//此处设置延迟时间 单位毫秒message.getMessageProperties().setExpiration("10000");return message;});
  • 方式二
   /*** 声明业务队列* @return Queue* 在声明业务队列时,创建了一个Map,并且put了两个值,这两个值就是死信队列的声明。* x-dead-letter-exchange:死信交换机的名称* x-dead-letter-routing-key:死信交换机的路由键,因为demo中两个交换机的类型都是direct的,因此路由键必须相同。*/@Beanpublic Queue orderQueue() {Map<String,Object> arguments = new HashMap<>(2);// 绑定该队列到死信交换机arguments.put("x-dead-letter-exchange",DIRCET_EXCHANGE_NAME);arguments.put("x-dead-letter-routing-key",TEST_QUEUE1_NAME);// 设置过期时间arguments.put("x-message-ttl", 4000L);return new Queue(orderQueue,true,false,false,arguments);}
Dcoker 集群
下载images
docker pull rabbitmq:management
运行多个rabbitmq容器
## 多个容器之间使用“--link”连接,此属性不能少;
## Erlang Cookie值必须相同,也就是RABBITMQ_ERLANG_COOKIE参数的值docker run -d --hostname rabbit1 --name myrabbit1 -p 15672:15672 -p 5672:5672 -e RABBITMQ_ERLANG_COOKIE='rabbitcookie' rabbitmq:managementdocker run -d --hostname rabbit2 --name myrabbit2 -p 15673:15672  -p 5673:5672 --link myrabbit1:rabbit1 -e RABBITMQ_ERLANG_COOKIE='rabbitcookie' rabbitmq:managementdocker run -d --hostname rabbit3 --name myrabbit3 -p 15674:15672  -p 5674:5672 --link myrabbit1:rabbit1 --link myrabbit2:rabbit2 -e RABBITMQ_ERLANG_COOKIE='rabbitcookie' rabbitmq:management
挂载运行
docker run -d --hostname rabbit1 --name myrabbit1  -v /usr/herdsric/rabbitmq/rabbitmq1/etc/rabbitmq:/etc/rabbitmq  -v /usr/herdsric/rabbitmq/rabbitmq1/lib/rabbitmq:/var/lib/rabbitmq -v /usr/herdsric/rabbitmq/rabbitmq1/log/rabbitmq:/var/log/rabbitmq -p 15672:15672 -p 5672:5672 -e RABBITMQ_ERLANG_COOKIE='rabbitcookie' rabbitmq:managementdocker run -d --hostname rabbit2 --name myrabbit2   -v /usr/herdsric/rabbitmq/rabbitmq2/etc/rabbitmq:/etc/rabbitmq  -v /usr/herdsric/rabbitmq/rabbitmq2/lib/rabbitmq:/var/lib/rabbitmq -v /usr/herdsric/rabbitmq/rabbitmq2/log/rabbitmq:/var/log/rabbitmq  -p 15673:15672  -p 5673:5672 --link myrabbit1:rabbit1 -e RABBITMQ_ERLANG_COOKIE='rabbitcookie' rabbitmq:managementdocker run -d --hostname rabbit3 --name myrabbit3   -v /usr/herdsric/rabbitmq/rabbitmq3/etc/rabbitmq:/etc/rabbitmq  -v /usr/herdsric/rabbitmq/rabbitmq3/lib/rabbitmq:/var/lib/rabbitmq -v /usr/herdsric/rabbitmq/rabbitmq3/log/rabbitmq:/var/log/rabbitmq -p 15674:15672  -p 5674:5672 --link myrabbit1:rabbit1 --link myrabbit2:rabbit2 -e RABBITMQ_ERLANG_COOKIE='rabbitcookie' rabbitmq:management
复制容器内容到宿主机
物理机复制文件到容器:docker cp 物理机目录 容器名称:容器目录
容器复制文件到物理机:docker cp 容器名称:容器目录 物理机目录
docker cp -a '容器id':/var/log/rabbitmq /usr/herdsric/rabbitmq/rabbitmq3/log/
docker cp -a '容器id'://etc/rabbitmq /usr/herdsric/rabbitmq/rabbitmq3/etc/
docker cp -a '容器id':/var/lib/rabbitmq /usr/herdsric/rabbitmq/rabbitmq3/lib/
加入RabbitMQ节点到集群
  • 设置节点1:
docker exec -it myrabbit1 bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl start_app
exit
  • 设置节点2:
# 参数“--ram”表示设置为内存节点,忽略次参数默认为磁盘节点
docker exec -it myrabbit2 bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster --ram rabbit@rabbit1
rabbitmqctl start_app
exit
  • 设置节点3:
docker exec -it myrabbit3 bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster --ram rabbit@rabbit1
rabbitmqctl start_app
exit
  • 使用http://物理机ip:15672 进行访了,默认账号密码是guest/guest

启动了3个节点,1个磁盘节点和2个内存节点 .容器停止需先停止磁盘节点,再停止内存节点.即需先启动

–link 的依赖。关闭相反

sr/herdsric/rabbitmq/rabbitmq3/log/rabbitmq:/var/log/rabbitmq -p 15674:15672 -p 5674:5672 --link myrabbit1:rabbit1 --link myrabbit2:rabbit2 -e RABBITMQ_ERLANG_COOKIE=‘rabbitcookie’ rabbitmq:management


###### 复制容器内容到宿主机

物理机复制文件到容器:docker cp 物理机目录 容器名称:容器目录
容器复制文件到物理机:docker cp 容器名称:容器目录 物理机目录
docker cp -a ‘容器id’:/var/log/rabbitmq /usr/herdsric/rabbitmq/rabbitmq3/log/
docker cp -a ‘容器id’

史上最全RabbitMq详解相关推荐

  1. css中float详解,CSS浮动属性Float详解?史上最全Float详解

    我们在学习css样式的时候,都知道css是盒概念,并且每一个盒子都是一个元素,下面我们就对CSS浮动属性Float进行详解,让你彻底了解Float. 一:什么是Float浮动? Float浮动是css ...

  2. 史上最全ThreadLocal 详解(一)

    目录 一.ThreadLocal简介 二.ThreadLocal与Synchronized的区别 三.ThreadLocal的简单使用 四.ThreadLocal的原理 4.1 ThreadLocal ...

  3. Unity史上最全旋转详解(Rotate,rotation,localEulerAngles,localRotation,万向节锁)

    Unity史上最全旋转详解 前言 旋转的方法Rotate以及五种重载参数的超级详细理解 Rotate(float xAngle, float yAngle, float zAngle); Unity绕 ...

  4. Java泛型详解,史上最全图文详解

    泛型在java中有很重要的地位,无论是开源框架还是JDK源码都能看到它. 毫不夸张的说,泛型是通用设计上必不可少的元素,所以真正理解与正确使用泛型,是一门必修课. 一:泛型本质 Java 泛型(gen ...

  5. show processlist 史上最全参数详解及解决方案

    总目录 一.show processlist 简介 二.show processlist 参数分析 三.show processlist--State参数分析 四.问题排查及解决方案 1.sql语句 ...

  6. 史上最全二叉查找树详解——带详细图解

    1.二叉查找树的性质与规则 若一个结点的左子树不为空,则它左子树上所有的结点都小于该结点:若一个结点的右子树的不为空,则它右子树上所有的结点都大于该结点 2.二叉查找树的创建 a.二叉查找树的结点类 ...

  7. android emoji吏上最全的详解

    如果没有时间看这么多历史介绍知识点,请直接跳到 第五小节:<java删除字符串中的所有emoji字符> 一.emoji简介 简介的内容摘自 wiki 百科对emoji的描述.这里把基本概念 ...

  8. 史上最全java架构师技能图谱(下)

    "java架构史上最全技能图谱分为上下两篇,这是java架构史上最全图谱下篇,包含: 大数据以及性能.设计模式.UML.中间件.分布式集群.负载均衡.通讯协议.架构设计等技术图谱等章节. 如 ...

  9. 史上最全java架构师技能图谱(上)

    java架构师最全技能图谱上篇,包含:数结构算法.java进阶.web开发.框架与工具四大技能图谱. 下篇将包含大数据以及性能.设计模式.UML.中间件.分布式集群.负载均衡.通讯协议.架构设计等技术 ...

最新文章

  1. 有了它,AI甚至可以让你知道对方是否真的爱你?
  2. Linux_Bash常用脚本
  3. redis主从_Redis主从复制部署
  4. 目标检测之RCNN,SPP-NET,Fast-RCNN,Faster-RCNN
  5. 飞鸽传书网站最新改版
  6. 【模拟】NCPC 2014 E ceremony
  7. 高通9008端口刷linux,高通黑砖强制9008_高通救砖9008刷底层
  8. (含代码)基于51单片机电子密码锁设计
  9. 【专升本计算机】计算机文化基础练习题(选择题300道附答案)
  10. 操作系统概念之定义和功能
  11. 写给小白:AWS,开启新世界大门的钥匙
  12. 基于android的五子棋开发
  13. y空间下载_阿里云网盘来啦!大咖手把手教你如何搭建免费quot;5G极速下载”私人网盘...
  14. 照片、摄影处理中的基本知识
  15. 如何设置微信公众号粉丝关注后自动推送小程序
  16. 28 岁程序员身价过亿,选择退休东渡日本
  17. Android-记账本(一)-效果图
  18. 关于win10打开文件安全警告怎么关闭
  19. matlab 回归 工具箱,matlab回归分析工具箱
  20. 24 基于单片机空气PM2.5浓度粉尘颗粒物检测系统设计

热门文章

  1. 名悦集团带你解析汽车底盘维护的那些小知识
  2. spark任务运行源码
  3. 远程访问服务器Jupyter Notebook的两种方法
  4. 计算机图形学——二维卡通人物交互设计
  5. 【网络时间同步】基于马尔科夫随机场最大后验估计和Gardner环的无线传感器网络时间同步算法matlab仿真
  6. Web 前端基础知识面试大全
  7. rk3288 调试dvp摄像头_RK3288 - 双路摄像头的硬件外接实现
  8. 成都的IT研发产业和芯片产业等情况:2006年初的数据。
  9. 总结两个最近遇到 校园网连不上或丢失WLAN如何解决的方案
  10. MBT测试实例:做个“机器人”,使其随机、持续的对“web页面”做交互性测试(二)涉及工具