实现本案例之前先 来手动的安装一遍rabbitMQ吧,非Docker安装
实验环境:阿里云centos7.2(也可以安装在本地)
物理设备 :一台笔记本
安装步骤如下:
一 安装软件
1. 安装依赖环境
在线安装依赖环境:
yum install build-essential openssl openssl-devel unixODBC unixODBC-devel make
gcc gcc-c++ kernel-devel m4 ncurses-devel tk tc xz
2. 安装Erlang
上传
erlang-18.3-1.el7.centos.x86_64.rpm
socat-1.7.3.2-5.el7.lux.x86_64.rpm
rabbitmq-server-3.6.5-1.noarch.rpm
这三个可以自己去官网下载,或者后台私信我邮箱,我看到了就发给你
# 安装
rpm -ivh erlang-18.3-1.el7.centos.x86_64.rpm
如果出现如下错误
说明gblic 版本太低。我们可以查看当前机器的gblic 版本
strings /lib64/libc.so.6 | grep GLIBC
当前最⾼版本2.12,需要2.15.所以需要升级glibc
使⽤yum更新安装依赖
sudo yum install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlitedevel readline-devel tk-devel gcc make -y
下载rpm包
wget http://copr-be.cloud.fedoraproject.org/results/mosquito/myrepoel6/epel-6-x86_64/glibc-2.17-55.fc20/glibc-utils-2.17-55.el6.x86_64.rpm &
wget http://copr-be.cloud.fedoraproject.org/results/mosquito/myrepoel6/epel-6-x86_64/glibc-2.17-55.fc20/glibc-static-2.17-55.el6.x86_64.rpm &
wget http://copr-be.cloud.fedoraproject.org/results/mosquito/myrepoel6/epel-6-x86_64/glibc-2.17-55.fc20/glibc-2.17-55.el6.x86_64.rpm &
wget http://copr-be.cloud.fedoraproject.org/results/mosquito/myrepoel6/epel-6-x86_64/glibc-2.17-55.fc20/glibc-common-2.17-55.el6.x86_64.rpm &
wget http://copr-be.cloud.fedoraproject.org/results/mosquito/myrepoel6/epel-6-x86_64/glibc-2.17-55.fc20/glibc-devel-2.17-55.el6.x86_64.rpm &
wget http://copr-be.cloud.fedoraproject.org/results/mosquito/myrepoel6/epel-6-x86_64/glibc-2.17-55.fc20/glibc-headers-2.17-55.el6.x86_64.rpm &
wget http://copr-be.cloud.fedoraproject.org/results/mosquito/myrepoel6/epel-6-x86_64/glibc-2.17-55.fc20/nscd-2.17-55.el6.x86_64.rpm &
安装rpm包
sudo rpm -Uvh socat-1.7.3.2-1.1.el7.x86_64.rpm --force --nodeps
安装完毕后再查看glibc版本,发现glibc版本已经到2.17
strings /lib64/libc.so.6 | grep GLIBC
3. 安装RabbitMQ
# 安装
rpm -ivh socat-1.7.3.2-1.1.el7.x86_64.rpm --force --nodeps
# 安装
rpm -ivh rabbitmq-server-3.6.5-1.noarch.rpm
4. 开启管理界⾯及配置
# 开启管理界⾯
rabbitmq-plugins enable rabbitmq_management
# 修改默认配置信息
vim /usr/lib/rabbitmq/lib/rabbitmq_server-3.6.5/ebin/rabbit.app
# ⽐如修改密码、配置等等,例如:loopback_users 中的 <<"guest">>,只保留guest
5. 启动
service rabbitmq-server start # 启动服务
service rabbitmq-server stop # 停⽌服务
service rabbitmq-server restart # 重启服务
设置配置⽂件
cd /usr/share/doc/rabbitmq-server-3.6.5/
cp rabbitmq.config.example /etc/rabbitmq/rabbitmq.config
其中在按照阿里云图形化界面的时候会遇到一点小问题:
[root@iZ28jyxu47dZ sbin]# ./rabbitmq-plugins enable rabbitmq_management
The following plugins have been enabled:
mochiweb
webmachine
rabbitmq_web_dispatch
amqp_client
rabbitmq_management_agent
rabbitmq_management
Applying plugin configuration to rabbit@iZ28jyxu47dZ… failed.
- Could not contact node rabbit@iZ28jyxu47dZ.
Changes will take effect at broker restart. - Options: --online - fail if broker cannot be contacted.
–offline - do not try to contact broker.
我的服务器是阿里云 这个问题在安装好rabbitmq以后开启一个节点
rabbitmqctl start_app
然后是这样的:
Starting node rabbit@iZ28jyxu47dZ ...
这样就好了,然后重启一下rabbitmq的服务,再次运行
rabbitmq-plugins enable rabbitmq_management
看到是这样就成功了:
那么用阿里云访问不成功的原因在于你可能在终端没有开启rabbitmq的端口,再去安全组里放行 15672端口就行,然后就可以看到访问默认的登录账号和密码是 guest
⻆⾊说明:
1、 超级管理员(administrator)
可登陆管理控制台,可查看所有的信息,并且可以对⽤户,策略(policy)进⾏操作。
2、 监控者(monitoring)
可登陆管理控制台,同时可以查看rabbitmq节点的相关信息(进程数,内存使⽤情况,磁盘使⽤情况等)
3、 策略制定者(policymaker)
可登陆管理控制台, 同时可以对policy进⾏管理。但⽆法查看节点的相关信息(上图红框标识的部分)。
4、 普通管理者(management)
仅可登陆管理控制台,⽆法看到节点信息,也⽆法对策略进⾏管理。
5、 其他
⽆法登陆管理控制台,通常就是普通的⽣产者和消费者。
–停止服务
rabbitmqctl app_stop
yum list | grep rabbitmq
–卸载 rabbitmq-server
yum -y remove rabbitmq-server.noarch
–卸载erlang
yum list | grep erlang
yum -y remove erlang-*
yum remove erlang.x86_64
删除残余文件
rm -rf /usr/lib64/erlang
rm -rf /var/lib/rabbitmq
二 SpringBoot+RabbitMQ实现消息延迟推送前言
如今我们用的软件都有消息延迟推送,应用比较广泛,比如:
淘宝签收后7天自动确认收货,在签收购买的商品后,物流系统会在7天后延时发送一个消息给支付系统,告诉支付系统将钱打给商家,这个过程持续7天,就是使用了消息中间件的延迟推送。
12306购票支付确认页面,在选好票点击确定跳转的页面中一般都有倒计时,代表30分钟订单不确认的话将自动取消订单。其实系统在下订单那一刻就发送一个延时消息给订单系统,延时30分钟,告诉订单系统订单未完成,如果在30分钟内完成订单了,则可以通过逻辑代码判断忽略收到的消息。
使用延时消息我们有这样几种解决方案:
使用Redis给订单设置过期时间,最后通过判断Redis中是否还有该订单来决定订单是否已经完成。这种解决方案相较于消息延迟推送性能较低,因为Redis存于内存中,遇到恶意下单的或者刷单的话,无疑会给内存带来巨大压力。
使用传统数据库轮询来判断数据库表订单的状态,这无疑增加了IO次数,数据库压力大,性能极低。
使用JVM原生的DelayQueue,这也是大量占用内存,并且还没有持久化策略,系统宕机或者重启的话会丢失订单信息。
消息延迟推送的实现
插件
在 RabbitMQ 3.6.x 之前一般采用死信队列 + TTL过期时间来实现延迟队列,这里不做过多介绍。
在 RabbitMQ 3.6.x 开始,RabbitMQ 官方提供了延迟队列的插件,可以下载放置到 RabbitMQ 根目录下的 plugins 下。
去官网下载延时队列插件:https://www.rabbitmq.com/community-plugins.html ,然后将其放置到RabbitMQ 根目录下的 plugins 下。
查看rabbitmq的安装目录
whereis rabbitmq
使用命令启用插件:
# linux
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
# windows 进入sbin目录
rabbitmq-plugins.bat enable rabbitmq_delayed_message_exchange
报错
这个问题解决了好久,我把上面的都删了,又重新跑了一遍,还是不行,最终发现了解决问题的精髓,博主说没有解决这个问题,但是我用了下面的那个方法,直接 rm-rf 掉了
这次在用上面的重启命令
service rabbitmq-server restart #
这回终于跑起来了
本文参考的原型是下面参考文章里那篇,他的里面是用到的插件,但是我按照插件的时候就出现了上面那一幕,所以为了稳定,决定不用插件,改用ttl+死信队列实现,具体实现过程如下:
在 Exchange 的声明中可以设置exchange.setDelayed(true)来开启延迟队列,也可以设置参数内容传入交换机声明的方法中,因为exchange.setDelayed(true)的底层就是设置参数内容来实现的。
目录结构
代码如下:
配置文件,因为我用的是阿里云,并且在该云平台上开启了nginx反向代理,禁止通过ip访问,所以这里的host配置的是域名
账号密码的话不新增的话可以用默认的guest
pom文件:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.3.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>2.3.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
统一配置交换机的信息类:
public class DelayQueueContent {
/**
* ttl(延时)交换机名称
*/
public static final String DELAY_EXCHANGE="message.ttl.exchange";
/**
* ttl(延时)队列名称
*/
public static final String DELAY_QUEUE_NAME ="message.ttl.queue";
/**
* dlx(死信)队列名称
*/
public static final String DELAYMSG_RECEIVE_QUEUE_NAME="message.dlx.queue";
/**
* 绑定键
*/
public static final String DELAY_KEY = "message.dlx.routing";
/**
* TTL 有效时间 3小时
*/
public static final int EXPERI_TIME = 20*10*1000; // 3*60*60*1000;
}
交换机的配置信息:
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DelayConfig {
/**
* 延时交换机 --- 交换机用于重新分配队列(接收死信队列中的过期消息,将其转发到需要延迟消息的模块队列)
* @return
*/
@Bean
public DirectExchange exchange() {
return new DirectExchange(DelayQueueContent.DELAY_EXCHANGE);
}
/**
* 实际消费队列
* 用于延时消费的队列
*/
@Bean
public Queue repeatTradeQueue() {
Queue queue = new Queue(DelayQueueContent.DELAYMSG_RECEIVE_QUEUE_NAME,
true,false,false);
return queue;
}
/**
* 绑定交换机并指定routing key(死信队列绑定延迟交换机和实际消费队列绑定延迟交换机的路由键一致)
* @return
*/
@Bean
public Binding repeatTradeBinding() {
return BindingBuilder.bind(repeatTradeQueue())
.to(exchange())
.with(DelayQueueContent.DELAY_KEY);
}
//死信队列
@Bean
public Queue deadLetterQueue() {
Map<String,Object> args = new HashMap<>();
args.put("x-message-ttl", DelayQueueContent.EXPERI_TIME);
args.put("x-dead-letter-exchange", DelayQueueContent.DELAY_EXCHANGE);
args.put("x-dead-letter-routing-key", DelayQueueContent.DELAY_KEY);
return new Queue(DelayQueueContent.DELAY_QUEUE_NAME, true, false, false, args);
}
}
生产者:
package com.rabbitmq;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
@RestController
@RequestMapping("/sendMqMessage")
@Slf4j
public class SendMqMessageController {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 发送延迟消息
* @return
*/
@GetMapping("/send")
public String sendDelayMsg(){
rabbitTemplate.convertAndSend(DelayQueueContent.DELAY_QUEUE_NAME,
"hello,this is message!");
log.info("发送时间:"+ LocalDateTime.now());
return "success";
}
}
消费者:
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.stereotype.Component;
import org.springframework.amqp.core.Message;
import java.time.LocalDateTime;
import java.util.Map;
/**
* 队列延时消费
*/
@Slf4j
@Component
public class DelayListener {
/**
* 接收延迟消息
* @param channel
* @param json
* @param message
* @param map
*/
@RabbitHandler
@RabbitListener(queues = DelayQueueContent.DELAYMSG_RECEIVE_QUEUE_NAME)
public void receiveDelayMsg(Channel channel, String json,
Message message, @Headers Map<String,Object> map){
try {
log.info("接收到的消息: {}", json);
log.info("接收时间:{}" ,LocalDateTime.now());
// 在这里实现具体的逻辑
// todo……
Thread.sleep(2000);
//代码为在消费者中开启消息接收确认的手动ack
//channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
log.info("消息消费成功!",json);
} catch (Exception e) {
log.error("消息消费失败!",json);
e.printStackTrace();
}
}
}
启动类:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
执行结果:
可以看到经过一段时间消息被正常消费。
参考文章:
解决rabbitmq网页管理不成功