rabbitmq
一、 什么是 RabbitMQ
二、为什么要使用 RabbitMQ?他解决了什么问题?
四、 消息队列基础知识。
1 Provider
2 Consumer
3 没有使用消息队列时消息传递方式
5 什么是队列?
队列就像存放了商品的仓库或者商店,是生产商品的工厂和购买商品的用户之间的中转站。
6 队列里存储了什么?
在 rabbitMQ 中,信息流从你的应用程序出发,来到 Rabbitmq 的队列,所有信息可以只
存储在一个队列中。队列可以存储很多信息,因为它基本上是一个无限制的缓冲区,前提是
你的机器有足够的存储空间。
7 队列和应用程序的关系?
多个生产者可以将消息发送到同一个队列中,多个消息者也可以只从同一个队列接收数
据。
五、 编写 RabbitMQ 的入门案例
1 搭建项目环境
1.1创建项目
1.2修改 pom 文件添加 RabbitMQ 坐标
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
1.3修改全局配置文件,添加 RabbitMQ 相关的配置
spring.application.name=springcloud-mq spring.rabbitmq.host=192.168.70.131 spring.rabbitmq.port=5672 spring.rabbitmq.username=oldlu spring.rabbitmq.password=123456
2 编写代码
创建队列
/** * 创建消息队列 * @author Administrator * */ @Configuration public class QueueConfig { /** * 创建队列 * @return */ @Bean public Queue createQueue(){ return new Queue("hello-queue"); } }
创建消息提供者
/** * 消息发送者 * @author Administrator * */ @Component public class Sender { @Autowired private AmqpTemplate rabbitAmqpTemplate; /* * 发送消息的方法 */ public void send(String msg){
//向消息队列发送消息
//参数一:队列的名称。
//参数二:消息
this.rabbitAmqpTemplate.convertAndSend("hello-queue",
msg);
}
}
消息接收者
/** * 消息接收者 * @author Administrator * */ @Component public class Receiver { /** * 接收消息的方法。采用消息队列监听机制 * @param msg */ @RabbitListener(queues="hello-queue") public void process(String msg){ System.out.println("receiver: "+msg); } }
启动类
@SpringBootApplication public class SpringbootServerApplication { public static void main(String[] args) { SpringApplication.run(SpringbootServerApplication.class, args); } }
测试代码 /** * 消息队列测试类 * @author Administrator */ @RunWith(SpringRunner.class) @SpringBootTest(classes=SpringbootServerApplication.class) public class QueueTest { @Autowired private Sender sender; /* * 测试消息队列 */ @Test public void test1(){ this.sender.send("Hello RabbitMQ"); } }
六、 RabbitMQ 原理图
1.Message 消息。消息是不具名的,它由消息头消息体组成。消息体是不透明的,而消息头则由 一系列可选属性组成,这些属性包括:routing-key(路由键)、priority(相对于其他消息的优先 权)、delivery-mode(指出消息可能持久性存储)等。 2.Publisher 消息的生产者。也是一个向交换器发布消息的客户端应用程序。 3.Consumer 消息的消费者。表示一个从消息队列中取得消息的客户端应用程序。 4.Exchange 交换器。用来接收生产者发送的消息并将这些消息路由给服务器中的队列。 三种常用的交换器类型 1. direct(发布与订阅 完全匹配) 2. fanout(广播) 3. topic(主题,规则匹配) 5.Binding 绑定。用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息 队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。 6.Queue 消息队列。用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一 个消息可投入一个或多个队列。消息一直在队列里面,等待消费者链接到这个队列将其取 走。 7.Routing-key 路由键。RabbitMQ 决定消息该投递到哪个队列的规则。 队列通过路由键绑定到交换器。 消息发送到 MQ 服务器时,消息将拥有一个路由键,即便是空的,RabbitMQ 也会将其 和绑定使用的路由键进行匹配。 如果相匹配,消息将会投递到该队列。 如果不匹配,消息将会进入黑洞。 8.Connection 链接。指 rabbit 服务器和服务建立的 TCP 链接。 9.Channel 信道。 1,Channel 中文叫做信道,是 TCP 里面的虚拟链接。例如:电缆相当于 TCP,信道是 一个独立光纤束,一条 TCP 连接上创建多条信道是没有问题的。 2,TCP 一旦打开,就会创建 AMQP 信道。 3,无论是发布消息、接收消息、订阅队列,这些动作都是通过信道完成的。 10.Virtual Host 虚拟主机。表示一批交换器,消息队列和相关对象。虚拟主机是共享相同的身份认证 和加密环境的独立服务器域。每个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器,拥有 自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在链接时指定, RabbitMQ 默认的 vhost 是/ 11.Borker 表示消息队列服务器实体。 交换器和队列的关系 交换器是通过路由键和队列绑定在一起的,如果消息拥有的路由键跟队列和交换器的 路由键匹配,那么消息就会被路由到该绑定的队列中。 也就是说,消息到队列的过程中,消息首先会经过交换器,接下来交换器在通过路由 键匹配分发消息到具体的队列中。 路由键可以理解为匹配的规则。 RabbitMQ 为什么需要信道?为什么不是 TCP 直接通信? 1. TCP 的创建和销毁开销特别大。创建需要 3 次握手,销毁需要 4 次分手。 2. 如果不用信道,那应用程序就会以 TCP 链接 Rabbit,高峰时每秒成千上万条链接 会造成资源巨大的浪费,而且操作系统每秒处理 TCP 链接数也是有限制的,必定造成性能 瓶颈。 3. 信道的原理是一条线程一条通道,多条线程多条通道同用一条 TCP 链接。一条 TCP 链接可以容纳无限的信道,即使每秒成千上万的请求也不会成为性能的瓶颈。
Broker:rabbitmq的服务节点
Queue:队列,是RabbitMQ的内部对象,用于存储消息。RabbitMQ中消息只能存储在队列中。生产
者投递消息到队列,消费者从队列中获取消息并消费。多个消费者可以订阅同一个队列,这时队列中的
消息会被平均分摊(轮询)给多个消费者进行消费,而不是每个消费者都收到所有的消息进行消费。(注
意:RabbitMQ不支持队列层面的广播消费,如果需要广播消费,可以采用一个交换器通过路由Key绑
定多个队列,由多个消费者来订阅这些队列的方式。
Exchange:交换器。生产者将消息发送到Exchange,由交换器将消息路由到一个或多个队列中。如果
路由不到,或返回给生产者,或直接丢弃,或做其它处理。
RoutingKey:路由Key。生产者将消息发送给交换器的时候,一般会指定一个RoutingKey,用来指定
这个消息的路由规则。这个路由Key需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效。
在交换器类型和绑定键固定的情况下,生产者可以在发送消息给交换器时通过指定RoutingKey来决定消
息流向哪里。
Binding:通过绑定将交换器和队列关联起来,在绑定的时候一般会指定一个绑定键,这样RabbitMQ
就可以指定如何正确的路由到队列了。
交换器和队列实际上是多对多关系。就像关系数据库中的两张表。他们通过BindingKey做关联(多对多
关系表)。在投递消息时,可以通过Exchange和RoutingKey(对应BindingKey)就可以找到相对应的队
列。
信道:信道是建立在Connection 之上的虚拟连接。当应用程序与Rabbit Broker建立TCP连接的时候,
客户端紧接着可以创建一个AMQP 信道(Channel) ,每个信道都会被指派一个唯一的D。RabbitMQ 处
理的每条AMQP 指令都是通过信道完成的。信道就像电缆里的光纤束。一条电缆内含有许多光纤束,允
许所有的连接通过多条光线束进行传输和接收。
七、 Rabbit 交换器讲解
1 Direct 交换器(发布与订阅 完全匹配)
1.1需求
1.2搭建环境
1.2.1创建项目
1.2.2修改全局配置文件
修改 Consumer 的配置文件
spring.application.name=springcloud-mq spring.rabbitmq.host=192.168.70.131 spring.rabbitmq.port=5672 spring.rabbitmq.username=oldlu spring.rabbitmq.password=123456 #设置交换器的名称 mq.config.exchange=log.direct #info 队列名称 mq.config.queue.info=log.info #info 路由键 mq.config.queue.info.routing.key=log.info.routing.key #error 队列名称 mq.config.queue.error=log.error #error 路由键 mq.config.queue.error.routing.key=log.error.routing.key
修改 Provider 的配置文件
spring.application.name=springcloud-mq spring.rabbitmq.host=192.168.70.131 spring.rabbitmq.port=5672 spring.rabbitmq.username=oldlu spring.rabbitmq.password=123456 #设置交换器的名称 mq.config.exchange=log.direct #info 路由键 mq.config.queue.info.routing.key=log.info.routing.key #error 路由键 mq.config.queue.error.routing.key=log.error.routing.key #error 队列名称 mq.config.queue.error=log.error
1.3编写 Consumer
InfoReceiver
/** * 消息接收者 * @author Administrator * @RabbitListener bindings:绑定队列 * @QueueBinding value:绑定队列的名称 * exchange:配置交换器 * * @Queue value:配置队列名称 * autoDelete:是否是一个可删除的临时队列 * * @Exchange value:为交换器起个名称 * type:指定具体的交换器类型 */ @Component @RabbitListener( bindings=@QueueBinding( value=@Queue(value="${mq.config.queue.info}",autoDelete="tr ue"), exchange=@Exchange(value="${mq.config.exchange}",type=Excha ngeTypes.DIRECT), key="${mq.config.queue.info.routing.key}" ) ) public class InfoReceiver { /** * 接收消息的方法。采用消息队列监听机制 * @param msg */ @RabbitHandler public void process(String msg){ System.out.println("Info........receiver: "+msg); } }
ErrorReceiver
/** * 消息接收者 * @author Administrator * @RabbitListener bindings:绑定队列 * @QueueBinding value:绑定队列的名称 * exchange:配置交换器 * * @Queue value:配置队列名称 * autoDelete:是否是一个可删除的临时队列 * * @Exchange value:为交换器起个名称 * type:指定具体的交换器类型 */ @Component @RabbitListener( bindings=@QueueBinding( value=@Queue(value="${mq.config.queue.error}",autoDelete="t rue"), exchange=@Exchange(value="${mq.config.exchange}",type=Excha ngeTypes.DIRECT), key="${mq.config.queue.error.routing.key}" ) ) public class ErrorReceiver { /** * 接收消息的方法。采用消息队列监听机制 * @param msg */ @RabbitHandler public void process(String msg){ System.out.println("Error..........receiver: "+msg); } }
1.4编写 Provider
/** * 消息发送者 * @author Administrator * */ @Component public class Sender { @Autowired private AmqpTemplate rabbitAmqpTemplate; //exchange 交换器名称 @Value("${mq.config.exchange}") private String exchange; //routingkey 路由键 @Value("${mq.config.queue.error.routing.key}") private String routingkey; /* * 发送消息的方法 */ public void send(String msg){ //向消息队列发送消息 //参数一:交换器名称。 //参数二:路由键 //参数三:消息 this.rabbitAmqpTemplate.convertAndSend(this.exchange, this.routingkey, msg); } }
2 Topic 交换器(主题,规则匹配)
2.1需求
2.2搭建环境
2.2.1创建项目
2.2.2修改配置文件
Provider
spring.application.name=springcloud-mq spring.rabbitmq.host=192.168.70.131 spring.rabbitmq.port=5672 spring.rabbitmq.username=oldlu spring.rabbitmq.password=123456 #设置交换器的名称 mq.config.exchange=log.topic
Consumer
spring.application.name=springcloud-mq spring.rabbitmq.host=192.168.70.131 spring.rabbitmq.port=5672 spring.rabbitmq.username=oldlu spring.rabbitmq.password=123456 #设置交换器的名称 mq.config.exchange=log.topic #info 队列名称 mq.config.queue.info=log.info #error 队列名称 mq.config.queue.error=log.error #log 队列名称 mq.config.queue.logs=log.all
/** * 消息发送者 * @author Administrator * */ @Component public class UserSender { @Autowired private AmqpTemplate rabbitAmqpTemplate; //exchange 交换器名称 @Value("${mq.config.exchange}") private String exchange; /* * 发送消息的方法 */ public void send(String msg){ //向消息队列发送消息 //参数一:交换器名称。 //参数二:路由键 //参数三:消息 this.rabbitAmqpTemplate.convertAndSend(this.exchange,"user. log.debug", "user.log.debug....."+msg); this.rabbitAmqpTemplate.convertAndSend(this.exchange,"user. log.info", "user.log.info....."+msg); this.rabbitAmqpTemplate.convertAndSend(this.exchange,"user. log.warn","user.log.warn....."+msg); this.rabbitAmqpTemplate.convertAndSend(this.exchange,"user. log.error", "user.log.error....."+msg); } }
/** * 消息发送者 * @author Administrator * */ @Component public class ProductSender { @Autowired private AmqpTemplate rabbitAmqpTemplate; //exchange 交换器名称 @Value("${mq.config.exchange}") private String exchange; /* * 发送消息的方法 */ public void send(String msg){ //向消息队列发送消息 //参数一:交换器名称。 //参数二:路由键 //参数三:消息 this.rabbitAmqpTemplate.convertAndSend(this.exchange,"produ ct.log.debug", "product.log.debug....."+msg); this.rabbitAmqpTemplate.convertAndSend(this.exchange,"produ ct.log.info", "product.log.info....."+msg); this.rabbitAmqpTemplate.convertAndSend(this.exchange,"produ ct.log.warn","product.log.warn....."+msg); this.rabbitAmqpTemplate.convertAndSend(this.exchange,"produ ct.log.error", "product.log.error....."+msg); } }
/** * 消息发送者 * @author Administrator * */ @Component public class OrderSender { @Autowired private AmqpTemplate rabbitAmqpTemplate; //exchange 交换器名称 @Value("${mq.config.exchange}") private String exchange; /* * 发送消息的方法 */ public void send(String msg){ //向消息队列发送消息 //参数一:交换器名称。 //参数二:路由键 //参数三:消息 this.rabbitAmqpTemplate.convertAndSend(this.exchange,"order .log.debug", "order.log.debug....."+msg); this.rabbitAmqpTemplate.convertAndSend(this.exchange,"order .log.info", "order.log.info....."+msg); this.rabbitAmqpTemplate.convertAndSend(this.exchange,"order .log.warn","order.log.warn....."+msg); this.rabbitAmqpTemplate.convertAndSend(this.exchange,"order .log.error", "order.log.error....."+msg); } }
2.4编写 Consumer
/** * 消息接收者 * @author Administrator * @RabbitListener bindings:绑定队列 * @QueueBinding value:绑定队列的名称 * exchange:配置交换器 * * @Queue value:配置队列名称 * autoDelete:是否是一个可删除的临时队列 * * @Exchange value:为交换器起个名称 * type:指定具体的交换器类型 */ @Component @RabbitListener( bindings=@QueueBinding( value=@Queue(value="${mq.config.queue.info}",autoDelete="tr ue"), exchange=@Exchange(value="${mq.config.exchange}",type=Excha ngeTypes.TOPIC), key="*.log.info" ) ) public class InfoReceiver { /** * 接收消息的方法。采用消息队列监听机制 * @param msg */ @RabbitHandler public void process(String msg){ System.out.println("......Info........receiver: "+msg); } }
/** * 消息接收者 * @author Administrator * @RabbitListener bindings:绑定队列 * @QueueBinding value:绑定队列的名称 * exchange:配置交换器 * * @Queue value:配置队列名称 * autoDelete:是否是一个可删除的临时队列 * * @Exchange value:为交换器起个名称 * type:指定具体的交换器类型 */ @Component @RabbitListener( bindings=@QueueBinding( value=@Queue(value="${mq.config.queue.error}",autoDelete="t rue"), exchange=@Exchange(value="${mq.config.exchange}",type=Excha ngeTypes.TOPIC), key="*.log.error" ) ) public class ErrorReceiver { /** * 接收消息的方法。采用消息队列监听机制 * @param msg */ @RabbitHandler public void process(String msg){ System.out.println("......Error........receiver: "+msg); } }
/** * 消息接收者 * @author Administrator * @RabbitListener bindings:绑定队列 * @QueueBinding value:绑定队列的名称 * exchange:配置交换器 * * @Queue value:配置队列名称 * autoDelete:是否是一个可删除的临时队列 * * @Exchange value:为交换器起个名称 * type:指定具体的交换器类型 */ @Component @RabbitListener( bindings=@QueueBinding( value=@Queue(value="${mq.config.queue.logs}",autoDelete="tr ue"), exchange=@Exchange(value="${mq.config.exchange}",type=Excha ngeTypes.TOPIC), key="*.log.*" ) ) public class LogsReceiver { /** * 接收消息的方法。采用消息队列监听机制 * @param msg */ @RabbitHandler public void process(String msg){ System.out.println("......All........receiver: "+msg); } }
/** * 消息队列测试类 * @author Administrator * */ @RunWith(SpringRunner.class) @SpringBootTest(classes=SpringbootServerApplication.class) public class QueueTest { @Autowired private UserSender usersender; @Autowired private ProductSender productsender; @Autowired private OrderSender ordersender; /* * 测试消息队列 */ @Test public void test1(){ this.usersender.send("UserSender....."); this.productsender.send("ProductSender...."); this.ordersender.send("OrderSender......"); } }
3Fanout 交换器(广播)
3.2搭建环境
3.2.1创建项目
3.2.2修改配置文件
Consumer
spring.application.name=springcloud-mq spring.rabbitmq.host=192.168.70.131 spring.rabbitmq.port=5672 spring.rabbitmq.username=oldlu spring.rabbitmq.password=123456 #设置交换器的名称 mq.config.exchange=order.fanout #短信服务队列名称 mq.config.queue.sms=order.sms #push 服务队列名称 mq.config.queue.push=order.push
spring.application.name=springcloud-mq spring.rabbitmq.host=192.168.70.131 spring.rabbitmq.port=5672 spring.rabbitmq.username=oldlu spring.rabbitmq.password=123456 #设置交换器的名称 mq.config.exchange=order.fanout
3.3编写 Consumer
SmsReceiver
/** * 消息接收者 * @author Administrator * @RabbitListener bindings:绑定队列 * @QueueBinding value:绑定队列的名称 * exchange:配置交换器 * key:路由键 * * @Queue value:配置队列名称 * autoDelete:是否是一个可删除的临时队列 * * @Exchange value:为交换器起个名称 * type:指定具体的交换器类型 */ @Component @RabbitListener( bindings=@QueueBinding( value=@Queue(value="${mq.config.queue.sms}",autoDelete="tru e"), exchange=@Exchange(value="${mq.config.exchange}",type=Excha ngeTypes.FANOUT) ) ) public class SmsReceiver { /** * 接收消息的方法。采用消息队列监听机制 * @param msg */ @RabbitHandler public void process(String msg){ System.out.println("Sms........receiver: "+msg); } }
PushReceiver
/** * 消息接收者 * @author Administrator * @RabbitListener bindings:绑定队列 * @QueueBinding value:绑定队列的名称 * exchange:配置交换器 * * @Queue value:配置队列名称 * autoDelete:是否是一个可删除的临时队列 * * @Exchange value:为交换器起个名称 * type:指定具体的交换器类型 */ @Component @RabbitListener( bindings=@QueueBinding( value=@Queue(value="${mq.config.queue.push}",autoDelete="tr ue"), exchange=@Exchange(value="${mq.config.exchange}",type=Excha ngeTypes.FANOUT) ) ) public class PushReceiver { /** * 接收消息的方法。采用消息队列监听机制 * @param msg */ @RabbitHandler public void process(String msg){ System.out.println("Push..........receiver: "+msg); } }
3.4编写 Provider
/** * 消息发送者 * @author Administrator * */ @Component public class Sender { @Autowired private AmqpTemplate rabbitAmqpTemplate; //exchange 交换器名称 @Value("${mq.config.exchange}") private String exchange; /* * 发送消息的方法 */ public void send(String msg){ //向消息队列发送消息 //参数一:交换器名称。 //参数二:路由键 //参数三:消息 this.rabbitAmqpTemplate.convertAndSend(this.exchange,"", msg); } }
八、 使用 RabbitMQ 实现松耦合设计
1 需求
2 搭建环境
2.1修改配置文件
spring.rabbitmq.host=192.168.70.131 spring.rabbitmq.port=5672 spring.rabbitmq.username=oldlu spring.rabbitmq.password=123456 #设置交换器的名称 mq.config.exchange=order.fanout #短信服务队列名称 mq.config.queue.sms=order.sms #push 服务队列名称 mq.config.queue.push=order.push #红包服务队列名称 mq.config.queue.red=red
2.2添加 RedReceiver
/** * 消息接收者 * @author Administrator * @RabbitListener bindings:绑定队列 * @QueueBinding value:绑定队列的名称 * exchange:配置交换器 * key:路由键 * * @Queue value:配置队列名称 * autoDelete:是否是一个可删除的临时队列 * * @Exchange value:为交换器起个名称 * type:指定具体的交换器类型 */ @Component @RabbitListener( bindings=@QueueBinding( value=@Queue(value="${mq.config.queue.red}",autoDelete="tru e"), exchange=@Exchange(value="${mq.config.exchange}",type=Excha ngeTypes.FANOUT) ) ) public class RedReceiver { /** * 接收消息的方法。采用消息队列监听机制 * @param msg */ @RabbitHandler public void process(String msg){ System.out.println("给用户发送 10 元红 包........receiver: "+msg); } }
九、 RabbitMQ 消息处理
1 RabbitMQ 的消息持久化处理
消息的可靠性是 RabbitMQ 的一大特色,那么 RabbitMQ 是如何保证消息可
靠性的呢——消息持久化。
1.1创建项目
1.2autoDelete 属性
@Queue: 当所有消费客户端连接断开后,是否自动删除
队列 true:删除 false:不删除
@Exchange:当所有绑定队列都不在使用时,是否自动
删除交换器 true:删除 false:不删除
2 RabbitMQ 中的消息确认 ACK 机制
消费端 配置了消息确认后,生产端可以配置消息回调机制。(rabbitTemplate.setConfirmCallback() 、rabbitTemplate.setReturnCallback() )
https://blog.csdn.net/qq_35387940/article/details/100514134(看消息确认和回调)
修改 Consusmer 配置文件解决 ACK 反馈问题
spring.application.name=springcloud-mq spring.rabbitmq.host=192.168.70.131 spring.rabbitmq.port=5672 spring.rabbitmq.username=oldlu spring.rabbitmq.password=123456 #设置交换器的名称 mq.config.exchange=log.direct #info 队列名称 mq.config.queue.info=log.info #info 路由键 mq.config.queue.info.routing.key=log.info.routing.key #error 队列名称 mq.config.queue.error=log.error #error 路由键 mq.config.queue.error.routing.key=log.error.routing.key #开启重试 spring.rabbitmq.listener.retry.enabled=true #重试次数,默认为 3 次 spring.rabbitmq.listener.retry.max-attempts=5
my mqconfig
package com.zhetang.config; import com.rabbitmq.client.AMQP; import org.springframework.amqp.core.*; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * Created with IntelliJ IDEA. * User:wq * Date:2021/4/22 * Time: 10:04 * Description: No Description */ @Configuration public class QueueConfig { @Value("${mq.config.exchange}") private String exchange; @Value("${mq.config.queue.name}") private String queue; @Value("${mq.config.queue.routing.key}") private String routeKey; @Bean public Queue createQueue(){ return new Queue(exchange); } @Bean public DirectExchange createExchange(){ return new DirectExchange(exchange,true,false); } //绑定 将队列和交换机绑定, 并设置用于匹配键 @Bean Binding bindingDirect() { return BindingBuilder.bind(createQueue()).to(createExchange()).with(routeKey); } @Bean DirectExchange lonelyDirectExchange() { return new DirectExchange("lonelyDirectExchange"); } }
java rabbitmq ack消息确认机制
ackage com.example.demo.ConsumerDemo; import com.rabbitmq.client.Channel; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; import java.io.IOException; @Component public class ConsumerDemo implements ChannelAwareMessageListener{ @Override public void onMessage(Message message, Channel channel){ String messageRec = new String(message.getBody()); System.out.println("接收到的字符串消息是 => " + messageRec); try{ 调用方法 //消息消费掉 channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); }catch(Exception ex){ //消息重新回到队列 channel.basicAck(message.getMessageProperties().getDeliveryTag(), false,true); logger.error(“Exception”,ex); } } }
spring-mq配置文件:手动消息确认
spring.rabbitmq.listener.simple.acknowledge-mode=manual
SpringBoot RabbitMQ 集成 六 ACK
//消息的标识,false只确认当前一个消息收到,true确认所有consumer获得的消息 //channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); //ack返回false,并重新回到队列,api里面解释得很清楚 //channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); //拒绝消息 //channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
https://blog.csdn.net/weixin_34367257/article/details/91849513
rabbitmq basicReject / basicNack / basicRecover区别
channel.basicReject(deliveryTag, true);
basic.reject方法拒绝deliveryTag对应的消息,第二个参数是否requeue,true则重新入队列,否则丢弃或者进入死信队列。
该方法reject后,该消费者还是会消费到该条被reject的消息。
channel.basicNack(deliveryTag, false, true);
basic.nack方法为不确认deliveryTag对应的消息,第二个参数是否应用于多消息,第三个参数是否requeue,与basic.reject区别就是同时支持多个消息,可以nack该消费者先前接收未ack的所有消息。nack后的消息也会被自己消费到。
channel.basicRecover(true);
basic.recover是否恢复消息到队列,参数是是否requeue,true则重新入队列,并且尽可能的将之前recover的消息投递给其他消费者消费,而不是自己再次消费。false则消息会重新被投递给自己。
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
需要传递的两个参数如下:
- deliveryTag(唯一标识 ID):当一个消费者向 RabbitMQ 注册后,会建立起一个 Channel ,RabbitMQ 会用 basic.deliver 方法向消费者推送消息,这个方法携带了一个 delivery tag, 它代表了 RabbitMQ 向该 Channel 投递的这条消息的唯一标识 ID,是一个单调递增的正整数,delivery tag 的范围仅限于 Channel
- multiple:为了减少网络流量,手动确认可以被批处理,当该参数为 true 时,则可以一次性确认 delivery_tag 小于等于传入值的所有消息
作者:林塬
链接:https://www.jianshu.com/p/2c5eebfd0e95
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
https://blog.csdn.net/fly_leopard/article/details/102821776
死信队列:
什么是死信队列:
一般来说,producer将消息投递到queue中,consumer从queue取出消息进行消费,但某些时候由于特定的原因导致queue中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信(Dead Letter),所有的死信都会放到死信队列中。 “死信”消息会被RabbitMQ进行特殊处理,如果配置了死信队列信息,那么该消息将会被丢进死信队列中,如果没有配置,则该消息将会被丢弃。 消费者消费消息 1)正常消费–>手动ack–>MQ从队列中删除消息 2)消费者报错-->没有ack-->消息是待应答状态-->channel断开后-->消费恢复为待分配状态 3)消费者报错-->手动nack-->如果配置了死信队列消息会被发送到死信队列中,如果没有配置消息会被丢弃。
死信队列的来源
- 消息被拒绝(basic.reject或basic.nack)并且requeue=false.
- 消息TTL过期
- 队列达到最大长度(队列满了,无法再添加数据到mq中)
配置死信队列
@Configuration public class RabbitMQConfig { // 声明业务Exchange @Bean public TopicExchange businessExchange(){ return new TopicExchange("businessExchange"); } // 声明业务队列A @Bean public Queue businessQueue(){ Map<String, Object> args = new HashMap<>(); // x-dead-letter-exchange 这里声明当前队列绑定的死信交换机 args.put("x-dead-letter-exchange", "deadLetterExchange"); // x-dead-letter-routing-key 这里声明当前队列的死信路由key args.put("x-dead-letter-routing-key", "dle.err"); return new Queue("businessQueue",true,false,false,args); } // 声明业务队列A绑定关系 @Bean public Binding businessBinding(Queue businessQueue, TopicExchange businessExchange){ return BindingBuilder.bind(businessQueue).to(businessExchange).with("emp.*"); } //声明死信Exchange @Bean public TopicExchange deadLetterExchange(){ return new TopicExchange("deadLetterExchange"); } // 声明死信队列A @Bean public Queue deadLetterQueue(){ return new Queue("dle-queue"); } @Bean public Binding deadLetterQueueBinding(Queue deadLetterQueue, TopicExchange deadLetterExchange){ return BindingBuilder.bind(deadLetterQueue).to(deadLetterExchange).with("dle.*"); } }
YML配置
spring: rabbitmq: host: 192.168.193.88 port: 5672 username: guest password: guest virtual-host: / listener: simple: acknowledge-mode: manual # 设置手动ack
设置消费者
@Component public class DedaLetterListener { // 监听业务队列 @RabbitListener(queues = "businessQueue") public void businessQueue(String msg, Channel channel, Message message) throws IOException { if ("error".equals(msg)) { System.out.println("业务消费者出现问题:" + msg); try { throw new RuntimeException(); }catch (Exception e){ // 无法消费消息,nack channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,false); } } else { System.out.println("正常消费消息:" + msg); // 正常消费了消息,手动ack channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } } // 监听死信队列 @RabbitListener(queues = "dle-queue") public void deadLetterQueue(String msg, Channel channel, Message message) throws IOException { System.out.println("死信队列消费消息:" + msg); channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } }
参数说明
// deliveryTag:该消息的index // multiple:是否批量.true:将一次性ack所有小于deliveryTag的消息。 public void basicAck(long deliveryTag, boolean multiple) //deliveryTag:该消息的index //multiple:是否批量.true:将一次性拒绝所有小于deliveryTag的消息。 //requeue:是否重新入队列 public void basicNack(long deliveryTag, boolean multiple, boolean requeue)
设置提供者
@Autowired RabbitTemplate rabbitTemplate; @RequestMapping("/send") public void send(String msg){ System.out.println("msg = [" + msg + "]"); rabbitTemplate.convertAndSend("businessExchange","emp.add",msg); }
死信消息的变化
如果队列配置了参数 x-dead-letter-routing-key 的话,“死信”的路由key将会被替换成该参数对应的值。如果没有设置,则保留该消息原有的路由key。 比如: 如果原有消息的路由key是testA,被发送到业务Exchage中,然后被投递到业务队列QueueA中,如果该队列没有配置参数x-dead-letter-routing-key,
则该消息成为死信后,将保留原有的路由keytestA,如果配置了该参数,并且值设置为testB,那么该消息成为死信后,路由key将会被替换为testB,然后被抛
到死信交换机中。
死信队列
一般用在较为重要的业务队列中,确保未被正确消费的消息不被丢弃,一般发生消费异常可能原因主要有由于消息信息本身存在错误导致处理异常,处理过程中参
数校验异常,或者因网络波动导致的查询异常等等,当发生异常时,当然不能每次通过日志来获取原消息,然后让运维帮忙重新投递消息。通过配置死信队列,可以让未正确
处理的消息暂存到另一个队列中,待后续排查清楚问题后,编写相应的处理代码来处理死信消息,这样比手工恢复数据要好太多了 。
延迟队列
什么是延时队列
延迟队列存储的对象肯定是对应的延时消息,所谓"延时消息"是指当消息被发送以后,并不想让消费者立即拿到消息,而是等待指定时间后,消费者才拿到这个消息进
行消费。
普通队列中的元素总是等着希望被早点取出处理,而延时队列中的元素则是希望被在指定时间得到取出和处理 。
延时队列的设置
RbbitMQ中存在TTL机制,一条消息或者该队列中的所有消息的最大存活时间,单位是毫秒。换句话说,如果一条消息设置了TTL属性或者进入了设置TTL属性的队列,
那么这条消息如果在TTL设置的时间内没有被消费,则会成为“死信”。如果同时配置了队列的TTL和消息的TTL,那么较小的那个值将会被使用。
给消息设置TTL时间
Map<String, Object> args = new HashMap<String, Object>(); args.put("x-message-ttl", 6000);// 但是毫秒 channel.queueDeclare(queueName, durable, exclusive, autoDelete, args);
每条消息的超时时间是6s,如果6s内没有被消费者消费,该消息就会变成死信。
给队列设置超时时间
@Bean public Queue businessQueue1(){ Map<String, Object> args = new HashMap<>(); args.put("x-message-ttl", 5000); // 这个队列中的所有的消息最多能活6s return new Queue("5-queue",true,false,false,args); }
但这两种方式是有区别的,如果设置了队列的TTL属性,那么一旦消息过期,就会被队列丢弃,而第二种方式,消息即使过期,也不一定会被马上丢弃,因为消息是否过期是在即将投递到消费者之前判定的,如果当前队列有严重的消息积压情况,则已过期的消息也许还能存活较长时间
配置延时队列
@Configuration public class RabbitMQConfigTTL { // 声明业务Exchange @Bean public TopicExchange businessExchange(){ return new TopicExchange("ttl-Exchange"); } // 创建延时队列1 @Bean public Queue businessQueue1(){ Map<String, Object> args = new HashMap<>(); args.put("x-dead-letter-exchange", "deadLetterExchange"); args.put("x-dead-letter-routing-key", "dle.err"); args.put("x-message-ttl", 5000); // 超时时间是5s return new Queue("5-queue",true,false,false,args); } // 创建延时队列2 @Bean public Queue businessQueue2(){ Map<String, Object> args = new HashMap<>(); args.put("x-dead-letter-exchange", "deadLetterExchange"); args.put("x-dead-letter-routing-key", "dle.err"); args.put("x-message-ttl", 20000); // // 超时时间是20s return new Queue("20-queue",true,false,false,args); } // 延时队列绑定关系 @Bean public Binding businessBinding1(Queue businessQueue1, TopicExchange businessExchange){ return BindingBuilder.bind(businessQueue1).to(businessExchange).with("emp.*"); } // 延时队列绑定 @Bean public Binding businessBinding2(Queue businessQueue2, TopicExchange businessExchange){ return BindingBuilder.bind(businessQueue2).to(businessExchange).with("user.*"); } //声明死信Exchange @Bean public TopicExchange deadLetterExchange(){ return new TopicExchange("deadLetterExchange"); } // 声明死信队列 @Bean public Queue deadLetterQueue(){ return new Queue("dle-queue",true,false,false,null); } // 死信队列绑定交换机 @Bean public Binding deadLetterQueueBinding(Queue deadLetterQueue, TopicExchange deadLetterExchange){ return BindingBuilder.bind(deadLetterQueue).to(deadLetterExchange).with("dle.*"); } }
YAML配置
spring: rabbitmq: host: 192.168.193.88 port: 5672 username: guest password: guest virtual-host: / listener: simple: acknowledge-mode: manual # 设置手动ack
设置提供者
@RequestMapping("/ttl") public void test1(String msg) { System.out.println("p:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); if ("5".equals(msg)) { // 添加到5s队列 rabbitTemplate.convertAndSend("ttl-Exchange", "emp.add", msg); } else if ("20".equals(msg)) { // 添加到20s队列中 rabbitTemplate.convertAndSend("ttl-Exchange", "user.add", msg); } }
设置消费者
@RabbitListener(queues = "dle-queue") public void dleQueue(String msg, Channel channel, Message message) throws IOException { System.out.println("dleQueue1:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); }
AMQP事务
1、使用java原生事务
我们知道事务可以保证消息的传递,使得可靠消息最终一致性。接下来我们先来探究一下RabbitMQ的事务机制。
RabbitMQ中与事务有关的主要有三个方法:
- txSelect()
- txCommit()
- txRollback()
txSelect主要用于将当前channel设置成transaction模式,txCommit用于提交事务,txRollback用于回滚事务。
当我们使用txSelect提交开始事务之后,我们就可以发布消息给Broke代理服务器,如果txCommit提交成功了,则消息一定到达了Broke了,如果在txCommit执行之前Broker出现异常崩溃或者由于其他原因抛出异常,这个时候我们便可以捕获异常通过txRollback方法进行回滚事务了。
所以RabbitMQ事务中的主要代码为:
channel.txSelect();
channel.basicPublish(exchange, routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes());
channel.txCommit();
先进行事务提交,然后开始发送消息,最后提交事务。
还是在原来的demo代码基础下,在sender和receiver包下分别新建TransactionSender1.java和TransactionReceiver1.java。分别如下所示:
package net.anumbrella.rabbitmq.sender; import java.io.IOException; import java.util.concurrent.TimeoutException; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; public class TransactionSender1 { private final static String QUEUE_NAME = "transition"; public static void main(String[] args) throws IOException, TimeoutException { /** * 创建连接连接到MabbitMQ */ ConnectionFactory factory = new ConnectionFactory(); // 设置MabbitMQ所在主机ip或者主机名 factory.setUsername("guest"); factory.setPassword("guest"); factory.setHost("127.0.0.1"); factory.setVirtualHost("/"); factory.setPort(5672); // 创建一个连接 Connection connection = factory.newConnection(); // 创建一个频道 Channel channel = connection.createChannel(); // 指定一个队列 channel.queueDeclare(QUEUE_NAME, false, false, false, null); // 发送的消息 String message = "This is a transaction message!"; try { // 开启事务 channel.txSelect(); // 往队列中发出一条消息,使用rabbitmq默认交换机 channel.basicPublish("", QUEUE_NAME, null, message.getBytes()); // 提交事务 channel.txCommit(); } catch (Exception e) { e.printStackTrace(); // 事务回滚 channel.txRollback(); } System.out.println(" TransactionSender1 Sent '" + message + "'"); // 关闭频道和连接 channel.close(); connection.close(); }
消费者
package net.anumbrella.rabbitmq.receiver; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.TimeoutException; import com.rabbitmq.client.AMQP; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import com.rabbitmq.client.Consumer; import com.rabbitmq.client.DefaultConsumer; import com.rabbitmq.client.Envelope; public class TransactionReceiver1 { private final static String QUEUE_NAME = "transition"; public static void main(String[] argv) throws IOException, InterruptedException, TimeoutException { ConnectionFactory factory = new ConnectionFactory(); factory.setUsername("guest"); factory.setPassword("guest"); factory.setHost("127.0.0.1"); factory.setVirtualHost("/"); factory.setPort(5672); // 打开连接和创建频道,与发送端一样 Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); // 声明队列,主要为了防止消息接收者先运行此程序,队列还不存在时创建队列。 channel.queueDeclare(QUEUE_NAME, false, false, false, null); System.out.println("Receiver1 waiting for messages. To exit press CTRL+C"); // 创建队列消费者 final Consumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { SimpleDateFormat time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSSS"); String message = new String(body, "UTF-8"); System.out.println(" TransactionReceiver1 : " + message); System.out.println(" TransactionReceiver1 Done! at " + time.format(new Date())); } }; channel.basicConsume(QUEUE_NAME, true, consumer); }
息的接收者跟原来是一样的,因为事务主要是保证消息要发送到Broker当中。
2、结合Spring Boot来使用事务
我们一般在Spring Boot使用RabbitMQ,主要是通过封装的RabbitTemplate模板来实现消息的发送,这里主要也是分为两种情况,使用RabbitTemplate同步发送,或者异步发送。
注意:发布确认和事务。(两者不可同时使用)在channel为事务时,不可引入确认模式;同样channel为确认模式下,不可使用事务。
所以在使用事务时,在application.properties中,需要将确认模式更改为false。
# 支持发布确认 spring.rabbitmq.publisher-confirms=false
A、同步
通过设置RabbitTemplate的channelTransacted为true,来设置事务环境,使得可以使用RabbitMQ事务。如下:
template.setChannelTransacted(true);
在demo代码里面,主要是在config包下的RabbitConfig.java里的rabbitTemplateNew方法里面配置,如下:
@Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public RabbitTemplate rabbitTemplateNew() { RabbitTemplate template = new RabbitTemplate(connectionFactory()); template.setChannelTransacted(true); return template; }
TransactionSender2.java
package net.anumbrella.rabbitmq.sender; 1 import java.text.SimpleDateFormat; import java.util.Date; import org.springframework.amqp.core.AmqpTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @Component public class TransactionSender2 { @Autowired private AmqpTemplate rabbitTemplate; @Transactional(rollbackFor = Exception.class) public void send(String msg) { SimpleDateFormat time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String sendMsg = msg + time.format(new Date()) + " This is a transaction message! "; /** * 这里可以执行数据库操作 * **/ System.out.println("TransactionSender2 : " + sendMsg); this.rabbitTemplate.convertAndSend("transition", sendMsg); } }
在上面代码中,我们通过调用者提供外部事务@Transactional(rollbackFor = Exception.class),来现实事务方法。一旦方法中抛出异常,比如执行数据库操作时,就会被捕获到,同时事务将进行回滚,并且向外发送的消息将不会发送出去。
TransactionReceiver2.java
package net.anumbrella.rabbitmq.receiver; import java.io.IOException; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; import com.rabbitmq.client.Channel; @Component public class TransactionReceiver2 { @RabbitListener(queues = "transition") public void process(Message message, Channel channel) throws IOException { System.out.println("TransactionReceiver2 : " + new String(message.getBody())); } }
https://blog.csdn.net/why191314/article/details/103061527/
HN_mq
redisRabbitReceiver
package com.zhetang.admin.opc.conusmer; import com.rabbitmq.client.Channel; import com.zhetang.admin.opc.mapper.PointOpcDataMapper; import com.zhetang.admin.opc.model.Point; import com.zhetang.admin.opc.model.PointOpcData; import com.zhetang.admin.opc.utils.OpcStatus; import com.zhetang.admin.opc.utils.RedisService; import net.sf.json.JSONObject; import org.influxdb.InfluxDB; import org.springframework.amqp.rabbit.annotation.*; import org.springframework.amqp.support.AmqpHeaders; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.Message; import org.springframework.stereotype.Component; import java.io.IOException; import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; @Component public class RedisRabbitReceiver { @Autowired private RedisService redisService; @Autowired private PointOpcDataMapper pointOpcDataMapper; @RabbitListener(bindings = @QueueBinding( value = @Queue(value = "queue-2", durable="true"), exchange = @Exchange(value = "exchange-2", durable="true", type= "topic", ignoreDeclarationExceptions = "true"), key = "springboot.*" ) ) @RabbitHandler public void onMessage(Message message, Channel channel){ try { JSONObject objJson = JSONObject.fromObject(message.getPayload()); Point st2 = (Point) JSONObject.toBean(objJson,Point.class); Pattern pattern = Pattern.compile("-?[0-9]+(\\.[0-9]+)?"); String s = "0020"; if(pattern.matcher(st2.getPointValue()).matches()){ s = firstAlarm(st2.getPointId(), Double.parseDouble(st2.getPointValue())); } System.out.println("Redis:"+st2.getPointId()+":"+st2.getPointValue()); redisService.set(st2.getPointId(),st2.getPointValue()+";"+st2.getTimes()+";"+s); }catch (Exception e){ e.printStackTrace(); System.out.println(JSONObject.fromObject(message.getPayload())); } Long deliveryTag = (Long)message.getHeaders().get(AmqpHeaders.DELIVERY_TAG); //手工ACK try { channel.basicAck(deliveryTag, false); } catch (IOException e) { e.printStackTrace(); } } public String firstAlarm (String id,Double value){ PointOpcData pointOpcData = pointOpcDataMapper.selectBySensor(id); if(pointOpcData!=null){ if("1".equals(pointOpcData.getAlarmType())){ if("1".equals(pointOpcData.getSensorType())){ //监测数值 if(pointOpcData.getOne()!=null && !pointOpcData.getOne().equals("") && Double.parseDouble(pointOpcData.getOne())>=value){ //低低低低限报警 return OpcStatus.LLLLALAEM.type; } //监测数值 if(pointOpcData.getTwo()!=null && !pointOpcData.getTwo().equals("") && Double.parseDouble(pointOpcData.getTwo())>= value ){ if(pointOpcData.getOne()!=null && !pointOpcData.getOne().equals("") ){ if(Double.parseDouble(pointOpcData.getOne())< value){ //低低低限报警 return OpcStatus.LLLALAEM.type; } }else{ //低低低限报警 return OpcStatus.LLLALAEM.type; } } //监测数值 if(pointOpcData.getThree()!=null && !pointOpcData.getThree().equals("") && Double.parseDouble(pointOpcData.getThree())>= value ){ if(pointOpcData.getTwo()!=null && !pointOpcData.getTwo().equals("")){ if(Double.parseDouble(pointOpcData.getTwo())< value){ //低低限报警 return OpcStatus.LLALAEM.type; } }else{ //低低限报警 return OpcStatus.LLALAEM.type; } } //监测数值 if(pointOpcData.getFour()!=null && !pointOpcData.getFour().equals("") && Double.parseDouble(pointOpcData.getFour())>= value ){ if(pointOpcData.getThree()!=null && !pointOpcData.getThree().equals("") ){ if(Double.parseDouble(pointOpcData.getThree())< value){ //低限报警 return OpcStatus.LALAEM.type; } }else{ //低限报警 return OpcStatus.LALAEM.type; } } //监测数值 if(pointOpcData.getFive()!=null && !pointOpcData.getFive().equals("") && Double.parseDouble(pointOpcData.getFive())<= value ){ if(pointOpcData.getSix()!=null && !pointOpcData.getSix().equals("") ){ if(Double.parseDouble(pointOpcData.getSix()) > value){ //高限报警 return OpcStatus.HALAEM.type; } }else{ //高限报警 return OpcStatus.HALAEM.type; } } //监测数值 if(pointOpcData.getSix()!=null && !pointOpcData.getSix().equals("") && Double.parseDouble(pointOpcData.getSix())<= value ){ if(pointOpcData.getSeven()!=null && !pointOpcData.getSeven().equals("") ){ if(Double.parseDouble(pointOpcData.getSeven())>value){ //高高限报警 return OpcStatus.HHALAEM.type; } }else{ //高高限报警 return OpcStatus.HHALAEM.type; } }//监测数值 if(pointOpcData.getSeven()!=null && !pointOpcData.getSeven().equals("") && Double.parseDouble(pointOpcData.getSeven())<=value ){ if(pointOpcData.getEight()!=null && !pointOpcData.getEight().equals("") ){ if(Double.parseDouble(pointOpcData.getEight())> value){ //高高高限报警 return OpcStatus.HHHALAEM.type; } }else{ //高高高限报警 return OpcStatus.HHHALAEM.type; } }//监测数值 if(pointOpcData.getEight()!=null && !pointOpcData.getEight().equals("") && Double.parseDouble(pointOpcData.getEight())<= value){ //高高高高限报警 return OpcStatus.HHHHALAEM.type; } } // if("2".equals(pointOpcData.getSensorType())){ // //开关信号 // if(Double.parseDouble(pointOpcData.getNormalType())!=(value)){ // return OpcStatus.KGALAEM.type; // } // } } } return OpcStatus.NOALAEM.type; } // // /** // * // * spring.rabbitmq.listener.order.queue.name=queue-2 // spring.rabbitmq.listener.order.queue.durable=true // spring.rabbitmq.listener.order.exchange.name=exchange-1 // spring.rabbitmq.listener.order.exchange.durable=true // spring.rabbitmq.listener.order.exchange.type=topic // spring.rabbitmq.listener.order.exchange.ignoreDeclarationExceptions=true // spring.rabbitmq.listener.order.key=springboot.* // * @param order // * @param channel // * @param headers // * @throws Exception // */ // @RabbitListener(bindings = @QueueBinding( // value = @Queue(value = "${spring.rabbitmq.listener.order.queue.name}", // durable="${spring.rabbitmq.listener.order.queue.durable}"), // exchange = @Exchange(value = "${spring.rabbitmq.listener.order.exchange.name}", // durable="${spring.rabbitmq.listener.order.exchange.durable}", // type= "${spring.rabbitmq.listener.order.exchange.type}", // ignoreDeclarationExceptions = "${spring.rabbitmq.listener.order.exchange.ignoreDeclarationExceptions}"), // key = "${spring.rabbitmq.listener.order.key}" // ) // ) // @RabbitHandler // public void onOrderMessage(@Payload com.bfxy.springboot.entity.Point order, // Channel channel, // @Headers Map<String, Object> headers) throws Exception { // System.err.println("--------------------------------------"); // System.err.println("消费端order: " + order.getPointId()); // Long deliveryTag = (Long)headers.get(AmqpHeaders.DELIVERY_TAG); // //手工ACK // channel.basicAck(deliveryTag, false); // } }
rabbitReceiver
package com.zhetang.admin.opc.conusmer; import com.rabbitmq.client.Channel; import com.zhetang.admin.opc.mapper.PointOpcDataMapper; import com.zhetang.admin.opc.model.Point; import com.zhetang.admin.opc.model.PointOpcData; import com.zhetang.admin.opc.utils.OpcStatus; import net.sf.json.JSONObject; import org.influxdb.InfluxDB; import org.springframework.amqp.rabbit.annotation.*; import org.springframework.amqp.support.AmqpHeaders; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.Message; import org.springframework.stereotype.Component; import java.io.IOException; import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; @Component public class RabbitReceiver { @Autowired private InfluxDB influxDB; @Autowired private PointOpcDataMapper pointOpcDataMapper; @Autowired private RabbitSender rabbitSender; @RabbitListener(bindings = @QueueBinding( value = @Queue(value = "queue-1", durable="true"), exchange = @Exchange(value = "exchange-1", durable="true", type= "topic", ignoreDeclarationExceptions = "true"), key = "springboot.*" ) ) @RabbitHandler public void onMessage(Message message, Channel channel){ try { JSONObject objJson = JSONObject.fromObject(message.getPayload()); Point st2 = (Point) JSONObject.toBean(objJson,Point.class); Pattern pattern = Pattern.compile("-?[0-9]+(\\.[0-9]+)?"); String s = "0020"; if(pattern.matcher(st2.getPointValue()).matches()){ s = firstAlarm(st2.getPointId(), Double.parseDouble(st2.getPointValue())); } // if(!"0000".equals(s)){ // //进三交换机 // rabbitSender.alarmSendMsg(st2); // } System.out.println("rabbit:"+st2.getPointId()+":"+st2.getPointValue()); org.influxdb.dto.Point point = org.influxdb.dto.Point.measurement("opc") .time(System.currentTimeMillis(), TimeUnit.MILLISECONDS) .addField("value",st2.getPointValue()) .addField("status",st2.getStatus()) .addField("times",st2.getTimes()) .addField("alarmStatus",s) .tag("tag1",st2.getPointId()) .build(); influxDB.write(point); }catch (Exception e){ System.out.println(e); System.out.println(JSONObject.fromObject(message.getPayload())); } Long deliveryTag = (Long)message.getHeaders().get(AmqpHeaders.DELIVERY_TAG); //手工ACK try { channel.basicAck(deliveryTag, false); } catch (IOException e) { e.printStackTrace(); } } public String firstAlarm (String id,Double value){ PointOpcData pointOpcData = pointOpcDataMapper.selectBySensor(id); if(pointOpcData!=null){ if("1".equals(pointOpcData.getAlarmType())){ if("1".equals(pointOpcData.getSensorType())){ //监测数值 if(pointOpcData.getOne()!=null && !pointOpcData.getOne().equals("") && Double.parseDouble(pointOpcData.getOne())>=value){ //低低低低限报警 return OpcStatus.LLLLALAEM.type; } //监测数值 if(pointOpcData.getTwo()!=null && !pointOpcData.getTwo().equals("") && Double.parseDouble(pointOpcData.getTwo())>= value ){ if(pointOpcData.getOne()!=null && !pointOpcData.getOne().equals("") ){ if(Double.parseDouble(pointOpcData.getOne())< value){ //低低低限报警 return OpcStatus.LLLALAEM.type; } }else{ //低低低限报警 return OpcStatus.LLLALAEM.type; } } //监测数值 if(pointOpcData.getThree()!=null && !pointOpcData.getThree().equals("") && Double.parseDouble(pointOpcData.getThree())>= value ){ if(pointOpcData.getTwo()!=null && !pointOpcData.getTwo().equals("")){ if(Double.parseDouble(pointOpcData.getTwo())< value){ //低低限报警 return OpcStatus.LLALAEM.type; } }else{ //低低限报警 return OpcStatus.LLALAEM.type; } } //监测数值 if(pointOpcData.getFour()!=null && !pointOpcData.getFour().equals("") && Double.parseDouble(pointOpcData.getFour())>= value ){ if(pointOpcData.getThree()!=null && !pointOpcData.getThree().equals("") ){ if(Double.parseDouble(pointOpcData.getThree())< value){ //低限报警 return OpcStatus.LALAEM.type; } }else{ //低限报警 return OpcStatus.LALAEM.type; } } //监测数值 if(pointOpcData.getFive()!=null && !pointOpcData.getFive().equals("") && Double.parseDouble(pointOpcData.getFive())<= value ){ if(pointOpcData.getSix()!=null && !pointOpcData.getSix().equals("") ){ if(Double.parseDouble(pointOpcData.getSix()) > value){ //高限报警 return OpcStatus.HALAEM.type; } }else{ //高限报警 return OpcStatus.HALAEM.type; } } //监测数值 if(pointOpcData.getSix()!=null && !pointOpcData.getSix().equals("") && Double.parseDouble(pointOpcData.getSix())<= value ){ if(pointOpcData.getSeven()!=null && !pointOpcData.getSeven().equals("") ){ if(Double.parseDouble(pointOpcData.getSeven())>value){ //高高限报警 return OpcStatus.HHALAEM.type; } }else{ //高高限报警 return OpcStatus.HHALAEM.type; } }//监测数值 if(pointOpcData.getSeven()!=null && !pointOpcData.getSeven().equals("") && Double.parseDouble(pointOpcData.getSeven())<=value ){ if(pointOpcData.getEight()!=null && !pointOpcData.getEight().equals("") ){ if(Double.parseDouble(pointOpcData.getEight())> value){ //高高高限报警 return OpcStatus.HHHALAEM.type; } }else{ //高高高限报警 return OpcStatus.HHHALAEM.type; } }//监测数值 if(pointOpcData.getEight()!=null && !pointOpcData.getEight().equals("") && Double.parseDouble(pointOpcData.getEight())<= value){ //高高高高限报警 return OpcStatus.HHHHALAEM.type; } } } } return OpcStatus.NOALAEM.type; } // // /** // * // * spring.rabbitmq.listener.order.queue.name=queue-2 // spring.rabbitmq.listener.order.queue.durable=true // spring.rabbitmq.listener.order.exchange.name=exchange-1 // spring.rabbitmq.listener.order.exchange.durable=true // spring.rabbitmq.listener.order.exchange.type=topic // spring.rabbitmq.listener.order.exchange.ignoreDeclarationExceptions=true // spring.rabbitmq.listener.order.key=springboot.* // * @param order // * @param channel // * @param headers // * @throws Exception // */ // @RabbitListener(bindings = @QueueBinding( // value = @Queue(value = "${spring.rabbitmq.listener.order.queue.name}", // durable="${spring.rabbitmq.listener.order.queue.durable}"), // exchange = @Exchange(value = "${spring.rabbitmq.listener.order.exchange.name}", // durable="${spring.rabbitmq.listener.order.exchange.durable}", // type= "${spring.rabbitmq.listener.order.exchange.type}", // ignoreDeclarationExceptions = "${spring.rabbitmq.listener.order.exchange.ignoreDeclarationExceptions}"), // key = "${spring.rabbitmq.listener.order.key}" // ) // ) // @RabbitHandler // public void onOrderMessage(@Payload com.bfxy.springboot.entity.Point order, // Channel channel, // @Headers Map<String, Object> headers) throws Exception { // System.err.println("--------------------------------------"); // System.err.println("消费端order: " + order.getPointId()); // Long deliveryTag = (Long)headers.get(AmqpHeaders.DELIVERY_TAG); // //手工ACK // channel.basicAck(deliveryTag, false); // } }
rabbitSender
package com.zhetang.admin.opc.conusmer; import com.zhetang.admin.opc.model.Point; import com.zhetang.admin.opc.utils.JsonMapper; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessageProperties; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.rabbit.core.RabbitTemplate.ConfirmCallback; import org.springframework.amqp.rabbit.core.RabbitTemplate.ReturnCallback; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.support.MessageBuilder; import org.springframework.stereotype.Component; import java.util.Map; import java.util.UUID; @Component public class RabbitSender { @Autowired private RabbitTemplate rabbitTemplate; final ConfirmCallback confirmCallback = new RabbitTemplate.ConfirmCallback() { @Override public void confirm(CorrelationData correlationData, boolean ack, String cause) { System.err.println("correlationData: " + correlationData); System.err.println("ack: " + ack); System.out.println(cause); if(!ack){ System.err.println("异常处理...."); } } }; final ReturnCallback returnCallback = new RabbitTemplate.ReturnCallback() { @Override public void returnedMessage(org.springframework.amqp.core.Message message, int replyCode, String replyText, String exchange, String routingKey) { System.err.println("return exchange: " + exchange + ", routingKey: " + routingKey + ", replyCode: " + replyCode + ", replyText: " + replyText); } }; public void send(Object message, Map<String, Object> properties) throws Exception { MessageProperties messageProperties = new MessageProperties(); MessageHeaders mhs = new MessageHeaders(properties); Message msg = (Message) MessageBuilder.createMessage(message, mhs); // rabbitTemplate.setReturnCallback(returnCallback); //id + 时间戳 全局唯一 CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString().replace("-","")); rabbitTemplate.convertAndSend("exchange-1", "springboot.abc", msg); } public void sendOrder(Point order) throws Exception { rabbitTemplate.setConfirmCallback(confirmCallback); // rabbitTemplate.setReturnCallback(returnCallback); //id + 时间戳 全局唯一 CorrelationData correlationData = new CorrelationData(order.getPointId()); rabbitTemplate.convertAndSend("exchange-2", "springboot.def", order, correlationData); } public void testSendMsg(Object message){ MessageProperties messageProperties = new MessageProperties(); messageProperties.setContentType("text/plain"); String s = JsonMapper.obj2String(message); Message msg = new Message(s.getBytes(), messageProperties); //id + 时间戳 全局唯一 CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString().replace("-","")); rabbitTemplate.convertAndSend("exchange-1","springboot.abc",msg,correlationData); } public void alarmSendMsg(Object message){ MessageProperties messageProperties = new MessageProperties(); messageProperties.setContentType("text/plain"); String s = JsonMapper.obj2String(message); Message msg = new Message(s.getBytes(), messageProperties); //id + 时间戳 全局唯一 CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString().replace("-","")); rabbitTemplate.convertAndSend("exchange-3","springboot.abc",msg,correlationData); } }
application.properties
spring.datasource.url=jdbc:mysql://192.168.1.205:3306/baseline_zhewang?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=GMT%2B8&useSSL=true spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.username=root #spring.datasource.password=330682 spring.datasource.password=ZHEtang403~! #spring.datasource.url=jdbc:oracle:thin:@117.80.3.36:1521:psmp_fn_test #spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver #spring.datasource.username=hainei001 #spring.datasource.password=hainei001 #�����ʹ��Ĭ�ϵ�����Դ ��com.zaxxer.hikari.HikariDataSource�� spring.datasource.type=com.zaxxer.hikari.HikariDataSource #mybatis�»���ת�շ����� mybatis.configuration.map-underscore-to-camel-case=true mybatis.mapper-locations= classpath*:mapper/*.xml mybatis.type-aliases-package=com.zhetang.admin.opc.mapper ##�ļ� uploadPath= /usr/local/tomcat-8082/apache-tomcat-8.5.38/webapps/img/ readPath= /img/ spring.servlet.multipart.max-file-size=10MB spring.servlet.multipart.max-request-size=10MB server.port=8083 spring.rabbitmq.addresses=192.168.1.102:5672 spring.rabbitmq.username=guest spring.rabbitmq.password=guest spring.rabbitmq.virtual-host=/ spring.rabbitmq.connection-timeout=15000 #�������Ѷ��ֶ� ack spring.rabbitmq.listener.simple.acknowledge-mode=manual #��������С���� spring.rabbitmq.listener.simple.concurrency=1 #����֮������� spring.rabbitmq.listener.simple.max-concurrency=10 #���������� spring.rabbitmq.listener.simple.prefetch=1 spring.rabbitmq.listener.order.queue.name=queue-2 spring.rabbitmq.listener.order.queue.durable=true spring.rabbitmq.listener.order.exchange.name=exchange-2 spring.rabbitmq.listener.order.exchange.durable=true spring.rabbitmq.listener.order.exchange.type=topic spring.rabbitmq.listener.order.exchange.ignoreDeclarationExceptions=true spring.rabbitmq.listener.order.key=springboot.* ################# #设置交换器名称 #安全监测点位、?艺监测DCS监测点位实时数据 #mq.config.detect.realtime.exchange = des.realtime ##设置路由键 #mq.config.detect.realtime.queue.routing.key = des.realtime.routing.key ##设置队列名称 ##安全监测点位、?艺监测DCS监测点位实时数据 #mq.config.detect.realtime.queue.name = des.realtime spring.rabbitmq.listener.order.detect.realtime.queue.name=des.realtime spring.rabbitmq.listener.order.detect.realtime.queue.durable=true spring.rabbitmq.listener.order.detect.realtime.exchange.name=des.realtime spring.rabbitmq.listener.order.detect.realtime.exchange.durable=true spring.rabbitmq.listener.order.detect.realtime.exchange.type=direct spring.rabbitmq.listener.order.detect.realtime.exchange.ignoreDeclarationExceptions=true spring.rabbitmq.listener.order.detect.realtime.key=des.realtime.routing.key #设置交换器名称 #(6)二道门人员出入 #mq.config.two.doors.inout.exchange = two.doors.inout ##设置路由键 #mq.config.two.doors.inout.queue.routing.key = two.doors.inout.routing.key ##设置队列名称 ##(6)二道门人员出入 #mq.config.two.doors.inout.queue.name = two.doors.inout spring.rabbitmq.listener.order.two.doors.inout.queue.name=two.doors.inout spring.rabbitmq.listener.order.two.doors.inout.queue.durable=true spring.rabbitmq.listener.order.two.doors.inout.exchange.name=two.doors.inout spring.rabbitmq.listener.order.two.doors.inout.exchange.durable=true spring.rabbitmq.listener.order.two.doors.inout.exchange.type=direct spring.rabbitmq.listener.order.two.doors.inout.exchange.ignoreDeclarationExceptions=true spring.rabbitmq.listener.order.two.doors.inout.key=two.doors.inout.routing.key #设置交换器名称 #(7)生产区人员统计 #mq.config.two.doors.workshop.exchange = two.doors.workshop.total ##设置路由键 #mq.config.two.doors.workshop.queue.routing.key = two.doors.workshop.total.routing.key ##设置队列名称 ##(7)生产区人员统计 #mq.config.two.doors.workshop.queue.name = two.doors.workshop.total spring.rabbitmq.listener.order.two.doors.workshop.queue.name=two.doors.workshop.total spring.rabbitmq.listener.order.two.doors.workshop.queue.durable=true spring.rabbitmq.listener.order.two.doors.workshop.exchange.name=two.doors.workshop.total spring.rabbitmq.listener.order.two.doors.workshop.exchange.durable=true spring.rabbitmq.listener.order.two.doors.workshop.exchange.type=direct spring.rabbitmq.listener.order.two.doors.workshop.exchange.ignoreDeclarationExceptions=true spring.rabbitmq.listener.order.two.doors.workshop.key=two.doors.workshop.total.routing.key ################# #influxDB influxDBUserName=admin influxDBPassword=hainei1205 influxDBOpenurl=http://192.168.1.233:8086 influxDBDatabase=chunan
maven.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.zhetang</groupId> <artifactId>garden-enterprise</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>garden-enterprise</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <druid.version>1.1.6</druid.version> </properties> <dependencies> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${druid.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- jackson --> <dependency> <groupId>org.codehaus.jackson</groupId> <artifactId>jackson-core-asl</artifactId> <version>1.9.13</version> </dependency> <dependency> <groupId>org.codehaus.jackson</groupId> <artifactId>jackson-mapper-asl</artifactId> <version>1.9.13</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <dependency> <groupId>org.influxdb</groupId> <artifactId>influxdb-java</artifactId> <version>2.15</version> </dependency> <!-- <dependency>--> <!-- <groupId>org.opc</groupId>--> <!-- <artifactId>j-interop</artifactId>--> <!-- <version>2.0.4</version>--> <!-- </dependency>--> <!-- <dependency>--> <!-- <groupId>org.opc</groupId>--> <!-- <artifactId>j-interopdeps</artifactId>--> <!-- <version>2.0.4</version>--> <!-- </dependency>--> <!-- <dependency>--> <!-- <groupId>org.opc</groupId>--> <!-- <artifactId>openscada.dcom</artifactId>--> <!-- <version>1.2.0</version>--> <!-- </dependency>--> <!-- <dependency>--> <!-- <groupId>org.opc</groupId>--> <!-- <artifactId>openscada.lib</artifactId>--> <!-- <version>1.3.0</version>--> <!-- </dependency>--> <!-- <dependency>--> <!-- <groupId>org.opc</groupId>--> <!-- <artifactId>jcifs</artifactId>--> <!-- <version>1.2.19</version>--> <!-- </dependency>--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.2</version> </dependency> <!-- mysql驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.41</version> </dependency> <!-- mybatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.0</version> </dependency> <!-- 通用mapper逆向工具 --> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper-spring-boot-starter</artifactId> <version>2.1.5</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.68</version> </dependency> <dependency> <groupId>net.sf.json-lib</groupId> <artifactId>json-lib</artifactId> <version>2.4</version> <classifier>jdk15</classifier> </dependency> <!--redis 依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>1.4.2.RELEASE</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.68</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
opc rabbit
application.properties
#spring.datasource.url=jdbc:mysql://localhost:3306/ldarten?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=GMT%2B8&useSSL=true #spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver #spring.datasource.username=root #spring.datasource.password=330682 spring.datasource.test1.jdbc-url=jdbc:mysql://10.25.110.102:3306/tx_test?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8 spring.datasource.test1.driver-class-name=com.mysql.jdbc.Driver spring.datasource.test1.username=root spring.datasource.test1.password=HaiNei1205~! #spring.datasource.url=jdbc:oracle:thin:@117.80.3.36:1521:psmp_fn_test #spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver #spring.datasource.username=hainei001 #spring.datasource.password=hainei001 #�����ʹ��Ĭ�ϵ�����Դ ��com.zaxxer.hikari.HikariDataSource�� #spring.datasource.type=com.alibaba.druid.pool.DruidDataSource #mybatis�»���ת�շ����� mybatis.configuration.map-underscore-to-camel-case=true mybatis.mapper-locations= classpath*:mapper/*/*.xml mybatis.type-aliases-package=com.zhetang.admin.* ##�ļ� uploadPath= /usr/local/tomcat-8082/apache-tomcat-8.5.38/webapps/img/ readPath= /img/ spring.servlet.multipart.max-file-size=10MB spring.servlet.multipart.max-request-size=10MB server.port=8083 #influxDB influxDBUserName=root influxDBPassword=root influxDBOpenurl=http://192.168.1.142:8086 influxDBDatabase=newopc opcIp=192.168.100.231 opcUserName=Administrator opcPassWord=Abc.123+ opcCode=D66FD91F-927D-47df-B074-EB2CD3F85C18 prefix=Channel1.消防. spring.rabbitmq.addresses=10.25.110.102:5672 spring.rabbitmq.username=guest spring.rabbitmq.password=guest spring.rabbitmq.virtual-host=/ spring.rabbitmq.connection-timeout=15000 spring.rabbitmq.publisher-confirms=true spring.rabbitmq.publisher-returns=true spring.rabbitmq.template.mandatory=true
RabbitSender
package com.zhetang.admin.opc.rabbit; import com.zhetang.admin.opc.model.Point; import com.zhetang.admin.opc.utils.JsonMapper; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessageProperties; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.rabbit.core.RabbitTemplate.ConfirmCallback; import org.springframework.amqp.rabbit.core.RabbitTemplate.ReturnCallback; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.support.MessageBuilder; import org.springframework.stereotype.Component; import java.util.Map; import java.util.UUID; @Component public class RabbitSender { @Autowired private RabbitTemplate rabbitTemplate; final ConfirmCallback confirmCallback = new RabbitTemplate.ConfirmCallback() { @Override public void confirm(CorrelationData correlationData, boolean ack, String cause) { System.err.println("correlationData: " + correlationData); System.err.println("ack: " + ack); System.out.println(cause); if(!ack){ System.err.println("异常处理...."); } } }; final ReturnCallback returnCallback = new RabbitTemplate.ReturnCallback() { @Override public void returnedMessage(org.springframework.amqp.core.Message message, int replyCode, String replyText, String exchange, String routingKey) { System.err.println("return exchange: " + exchange + ", routingKey: " + routingKey + ", replyCode: " + replyCode + ", replyText: " + replyText); } }; public void send(Object message, Map<String, Object> properties) throws Exception { MessageProperties messageProperties = new MessageProperties(); MessageHeaders mhs = new MessageHeaders(properties); Message msg = (Message) MessageBuilder.createMessage(message, mhs); // rabbitTemplate.setReturnCallback(returnCallback); //id + 时间戳 全局唯一 CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString().replace("-","")); rabbitTemplate.convertAndSend("exchange-1", "springboot.abc", msg); } public void sendOrder(Point order) throws Exception { rabbitTemplate.setConfirmCallback(confirmCallback); // rabbitTemplate.setReturnCallback(returnCallback); //id + 时间戳 全局唯一 CorrelationData correlationData = new CorrelationData(order.getPointId()); rabbitTemplate.convertAndSend("exchange-2", "springboot.def", order, correlationData); } public void influxdbSendMsg(Object message){ MessageProperties messageProperties = new MessageProperties(); messageProperties.setContentType("text/plain"); String s = JsonMapper.obj2String(message); Message msg = new Message(s.getBytes(), messageProperties); //id + 时间戳 全局唯一 CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString().replace("-","")); rabbitTemplate.convertAndSend("exchange-101","springboot.abc",msg,correlationData); } public void redisSendMsg(Object message){ MessageProperties messageProperties = new MessageProperties(); messageProperties.setContentType("text/plain"); String s = JsonMapper.obj2String(message); Message msg = new Message(s.getBytes(), messageProperties); //id + 时间戳 全局唯一 CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString().replace("-","")); rabbitTemplate.convertAndSend("exchange-201","springboot.abc",msg,correlationData); } }
opc 工具类
RunThread
package com.zhetang.admin.opc.utils; import com.zhetang.admin.opc.influxdb.schedule.PointSchedule; import com.zhetang.admin.opc.model.Point; import com.zhetang.admin.opc.rabbit.RabbitSender; import org.influxdb.InfluxDB; import org.influxdb.dto.BatchPoints; import org.jinterop.dcom.common.JIException; import org.openscada.opc.lib.common.ConnectionInformation; import org.openscada.opc.lib.common.NotConnectedException; import org.openscada.opc.lib.da.*; import org.openscada.opc.lib.da.browser.FlatBrowser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.net.UnknownHostException; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.Executors; import java.util.regex.Pattern; @Component @Service("RunThread") @Transactional @PropertySource(value = {"classpath:application.properties"},encoding="UTF-8") public class RunThread extends Observable implements Runnable{ @Autowired private PointSchedule pointSchedule; SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); @Value("${influxDBUserName}") private String influxDBUserName ; @Value("${influxDBPassword}") private String influxDBPassword ; @Value("${influxDBOpenurl}") private String influxDBOpenurl ; @Value("${influxDBDatabase}") private String influxDBDatabase ; @Value("${opcIp}") private String opcIp ; @Value("${opcUserName}") private String opcUserName ; @Value("${opcPassWord}") private String opcPassWord ; @Value("${opcCode}") private String opcCode ; @Value("${prefix}") private String prefix ; // @Value("${opcName}") // private String opcName ; @Autowired private InfluxDB influxDB; @Autowired private BatchPoints batchPoints; // 此方法一经调用,立马可以通知观察者,在本例中是监听线程 public void doBusiness(){ if(true){ super.setChanged(); } notifyObservers(); } @Autowired private RabbitSender rabbitSender; /** * 普通 */ @Override public synchronized void run() { System.out.println("***********************"); System.out.println("prefix:"+prefix); System.out.println("opcIp:"+opcIp); while(true){ System.out.println("一个程序"); ConnectionInformation ci = new ConnectionInformation(); //这个根据实际情况调整--就是要传这些参数 String[] arg = new String[]{"opc", opcIp, opcUserName, opcPassWord, opcCode}; //logger.info(args[0] + " --> opc 采集开始..... "); String host = arg[1]; ci.setHost(host); //IP String user = arg[2]; ci.setUser(user); //用户 String pwd = arg[3]; ci.setPassword(pwd); //密码 String clsid = arg[4]; ci.setClsid(clsid); //opc服务软件注册表标识 //创建服务,初始10个线程 -- 支持多线程,自由设置 final Server server = new Server(ci, Executors.newScheduledThreadPool(1)); //连接服务 --> 枚举所有的item点位 Collection<String> itemIds = null; try { server.connect(); FlatBrowser browser = server.getFlatBrowser(); //所有的item点位 itemIds = browser.browse(); //logger.info("一共有点位 : " + itemIds.size()); System.out.println("一共有点位 : " + itemIds.size()); } catch (Exception e) { //logger.error("服务连接异常:" + e.getMessage()); System.out.println("服务连接异常:" + e.getMessage()); doBusiness();//在抛出异常时调用,通知观察者,让其重启线程 //break;//异常抛出之后,一定要跳出循环,保证将线程送进地狱 } //重新连接监测数据 AutoReconnectController controller = new AutoReconnectController(server); try { controller.connect(); //2000表示频率2000毫秒即2秒 final AccessBase access = new SyncAccess(server, 30000);//final AccessBase access = new Async20Access(server, 1000, false); access.bind(); //绑定连接服务 //循环监听点位 final int i = 0; Integer p = 0; for (String itemId : itemIds) { access.addItem(itemId, (item, state) -> { //这里会依据上面设置的采集频率(2000毫秒)实时进入这个回调函数。 try { if(isNumeric(state.getValue().getObject().toString())){ System.out.println("点位实时值 " + itemId + " --> " + new BigDecimal(state.getValue().getObject().toString()).toString()); if (itemId.indexOf(prefix) != -1) { if ("192".equals(state.getQuality().toString())) { Point point = new Point(itemId.substring(prefix.length()), new BigDecimal(state.getValue().getObject().toString()).toString(), state.getTimestamp().getTime().getTime(), "good"); rabbitSender.influxdbSendMsg(point); rabbitSender.redisSendMsg(point); } else { Point point = new Point(itemId.substring(prefix.length()), new BigDecimal(state.getValue().getObject().toString()).toString(), state.getTimestamp().getTime().getTime(), "bad"); rabbitSender.influxdbSendMsg(point); rabbitSender.redisSendMsg(point); } } else { Point point = new Point(itemId, new BigDecimal(state.getValue().getObject().toString()).toString(), state.getTimestamp().getTime().getTime(), "good"); rabbitSender.influxdbSendMsg(point); rabbitSender.redisSendMsg(point); } }else{ System.out.println("点位实时值 " + itemId + " --> " + state.getValue().getObject().toString()); if (itemId.indexOf(prefix) != -1) { if ("192".equals(state.getQuality().toString())) { Point point = new Point(itemId.substring(prefix.length()), state.getValue().getObject().toString(), state.getTimestamp().getTime().getTime(), "good"); rabbitSender.influxdbSendMsg(point); rabbitSender.redisSendMsg(point); } else { Point point = new Point(itemId.substring(prefix.length()), state.getValue().getObject().toString(), state.getTimestamp().getTime().getTime(), "bad"); rabbitSender.influxdbSendMsg(point); rabbitSender.redisSendMsg(point); } } else { Point point = new Point(itemId, state.getValue().getObject().toString(), state.getTimestamp().getTime().getTime(), "good"); rabbitSender.influxdbSendMsg(point); rabbitSender.redisSendMsg(point); } } } catch (JIException e) { e.printStackTrace(); doBusiness();//在抛出异常时调用,通知观察者,让其重启线程 } }); } //这里得让程序不停止,暂时线程停止10秒 Thread.sleep(30000); } catch (final JIException e) { //logger.error(String.format("%08X: %s", e.getErrorCode(), server.getErrorMessage(e.getErrorCode()))); e.printStackTrace(); doBusiness();//在抛出异常时调用,通知观察者,让其重启线程 break;//异常抛出之后,一定要跳出循环,保证将线程送进地狱 } catch (NotConnectedException e) { e.printStackTrace(); doBusiness();//在抛出异常时调用,通知观察者,让其重启线程 break;//异常抛出之后,一定要跳出循环,保证将线程送进地狱 } catch (UnknownHostException e) { e.printStackTrace(); doBusiness();//在抛出异常时调用,通知观察者,让其重启线程 break;//异常抛出之后,一定要跳出循环,保证将线程送进地狱 } catch (DuplicateGroupException e) { e.printStackTrace(); doBusiness();//在抛出异常时调用,通知观察者,让其重启线程 break;//异常抛出之后,一定要跳出循环,保证将线程送进地狱 } catch (InterruptedException e) { e.printStackTrace(); doBusiness();//在抛出异常时调用,通知观察者,让其重启线程 } catch (AddFailedException e) { e.printStackTrace(); doBusiness();//在抛出异常时调用,通知观察者,让其重启线程 break;//异常抛出之后,一定要跳出循环,保证将线程送进地狱 } finally { controller.disconnect(); server.disconnect(); doBusiness();//在抛出异常时调用,通知观察者,让其重启线程 break;//异常抛出之后,一定要跳出循环,保证将线程送进地狱 } } } public static boolean isNumeric(String str) { if (null == str || "".equals(str)) { return false; } String regx = "[+-]*\\d+\\.?\\d*[Ee]*[+-]*\\d+"; Pattern pattern = Pattern.compile(regx); boolean isNumber = pattern.matcher(str).matches(); if (isNumber) { return isNumber; } regx = "^[-\\+]?[.\\d]*$"; pattern = Pattern.compile(regx); return pattern.matcher(str).matches(); } // /** // * 金路 // */ // @Override // public synchronized void run() { // while(true){ // System.out.println("一个程序"); // ConnectionInformation ci = new ConnectionInformation(); // //这个根据实际情况调整--就是要传这些参数 // String[] arg = new String[]{"opc", opcIp, opcUserName, opcPassWord, opcCode}; // //logger.info(args[0] + " --> opc 采集开始..... "); // String host = arg[1]; // ci.setHost(host); //IP // String user = arg[2]; // ci.setUser(user); //用户 // String pwd = arg[3]; // ci.setPassword(pwd); //密码 // String clsid = arg[4]; // ci.setClsid(clsid); //opc服务软件注册表标识 // // //创建服务,初始10个线程 -- 支持多线程,自由设置 // final Server server = new Server(ci, Executors.newScheduledThreadPool(1)); // //连接服务 --> 枚举所有的item点位 // final String frex = "channel1.Device1.Group1."; // // //重新连接监测数据 // AutoReconnectController controller = new AutoReconnectController(server); // try { // controller.connect(); // //2000表示频率2000毫秒即2秒 // final AccessBase access = new SyncAccess(server, 10000);//final AccessBase access = new Async20Access(server, 1000, false); // access.bind(); //绑定连接服务 // //循环监听点位 // final int i = 0; // Integer p = 0; // String[] split = opcName.split(","); // for (String str: split) { // final String itemId = frex+str; // access.addItem(itemId, (item, state) -> { // //这里会依据上面设置的采集频率(2000毫秒)实时进入这个回调函数。 // // try { // System.out.println("点位实时值 " + itemId.substring(frex.length()) + " --> " + state.getValue().getObject()); // if ("192".equals(state.getQuality().toString())) { // Point point = new Point(itemId.substring(frex.length()), state.getValue().getObject().toString(), state.getTimestamp().getTime().getTime(), "good"); // rabbitSender.testSendMsg(point); // } else { // Point point = new Point(itemId.substring(frex.length()), state.getValue().getObject().toString(), state.getTimestamp().getTime().getTime(), "bad"); // rabbitSender.testSendMsg(point); // } // // } catch (JIException e) { // e.printStackTrace(); // doBusiness();//在抛出异常时调用,通知观察者,让其重启线程 // // } // // }); // } // //这里得让程序不停止,暂时线程停止10秒 // Thread.sleep(10000); // } catch (final JIException e) { // //logger.error(String.format("%08X: %s", e.getErrorCode(), server.getErrorMessage(e.getErrorCode()))); // e.printStackTrace(); // doBusiness();//在抛出异常时调用,通知观察者,让其重启线程 // break;//异常抛出之后,一定要跳出循环,保证将线程送进地狱 // } catch (NotConnectedException e) { // e.printStackTrace(); // doBusiness();//在抛出异常时调用,通知观察者,让其重启线程 // break;//异常抛出之后,一定要跳出循环,保证将线程送进地狱 // } catch (UnknownHostException e) { // e.printStackTrace(); // doBusiness();//在抛出异常时调用,通知观察者,让其重启线程 // break;//异常抛出之后,一定要跳出循环,保证将线程送进地狱 // } catch (DuplicateGroupException e) { // e.printStackTrace(); // doBusiness();//在抛出异常时调用,通知观察者,让其重启线程 // break;//异常抛出之后,一定要跳出循环,保证将线程送进地狱 // } catch (InterruptedException e) { // e.printStackTrace(); // doBusiness();//在抛出异常时调用,通知观察者,让其重启线程 // } catch (AddFailedException e) { // e.printStackTrace(); // doBusiness();//在抛出异常时调用,通知观察者,让其重启线程 // break;//异常抛出之后,一定要跳出循环,保证将线程送进地狱 // } finally { // controller.disconnect(); // server.disconnect(); // doBusiness();//在抛出异常时调用,通知观察者,让其重启线程 // break;//异常抛出之后,一定要跳出循环,保证将线程送进地狱 // } // } //} }
Listener
package com.zhetang.admin.opc.utils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.Observable; import java.util.Observer; @Component public class Listener implements Observer { @Override public void update(Observable o, Object arg) { RunThread run = (RunThread)SpringUtil.getBean("RunThread"); System.out.println("RunThread死机"); // RunThread run = new RunThread(); run.addObserver(this); new Thread(run).start(); System.out.println("RunThread重启"); } }
OpcStart
package com.zhetang.admin.opc.utils;//package com.zhetang.admin.opc.service.impl; import com.zhetang.admin.opc.influxdb.schedule.PointSchedule; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.CommandLineRunner; import org.springframework.context.annotation.PropertySource; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.text.SimpleDateFormat; import java.util.Date; import java.util.UUID; /** * @Author: WangJunsa * @Date: 2020/3/11 10:32 * @Version 1.0 */ @Component @Service("OpcStart") @Transactional @Order(value = 1) public class OpcStart implements CommandLineRunner { @Autowired private RunThread run; @Override public void run(String... args) throws Exception { Listener listen = new Listener(); run.addObserver(listen); new Thread(run).start(); } }
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.zhetang</groupId> <artifactId>garden-enterprise</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>garden-enterprise</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!-- jackson --> <dependency> <groupId>org.codehaus.jackson</groupId> <artifactId>jackson-core-asl</artifactId> <version>1.9.13</version> </dependency> <dependency> <groupId>org.codehaus.jackson</groupId> <artifactId>jackson-mapper-asl</artifactId> <version>1.9.13</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.43</version> <scope>runtime</scope> </dependency> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper-spring-boot-starter</artifactId> <version>1.2.4</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.4.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.4.0</version> </dependency> <dependency> <groupId>org.drools</groupId> <artifactId>drools-compiler</artifactId> <version>6.5.0.Final</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxws</artifactId> <version>3.2.6</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http</artifactId> <version>3.2.6</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.3.5</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.68</version> </dependency> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> </dependency> <dependency> <groupId>org.influxdb</groupId> <artifactId>influxdb-java</artifactId> <version>2.15</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxws</artifactId> <version>3.2.6</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http</artifactId> <version>3.2.6</version> </dependency> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk16</artifactId> <version>1.46</version> </dependency> <!-- <dependency>--> <!-- <groupId>org.opc</groupId>--> <!-- <artifactId>j-interop</artifactId>--> <!-- <version>2.0.4</version>--> <!-- </dependency>--> <!-- <dependency>--> <!-- <groupId>org.opc</groupId>--> <!-- <artifactId>j-interopdeps</artifactId>--> <!-- <version>2.0.4</version>--> <!-- </dependency>--> <!-- <dependency>--> <!-- <groupId>org.opc</groupId>--> <!-- <artifactId>openscada.dcom</artifactId>--> <!-- <version>1.2.0</version>--> <!-- </dependency>--> <!-- <dependency>--> <!-- <groupId>org.opc</groupId>--> <!-- <artifactId>openscada.lib</artifactId>--> <!-- <version>1.3.0</version>--> <!-- </dependency>--> <!-- <dependency>--> <!-- <groupId>org.opc</groupId>--> <!-- <artifactId>jcifs</artifactId>--> <!-- <version>1.2.19</version>--> <!-- </dependency>--> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
1111