Linux系统安装RabbitMQ(Centos 7)

一. Linux下RabbitMQ环境搭建

1.首先装erlang语言

  Yum安装socat 

# yum -y install socat

  下载erlang软件包,本文使用erlang-19.0.4版本,下面给出下载链接

# wget http://www.rabbitmq.com/releases/erlang/erlang-19.0.4-1.el7.centos.x86_64.rpm

  安装erlang 

# rpm -ivh erlang-19.0.4-1.el7.centos.x86_64.rpm

  安装完成后执行erl命令,出现下图则代表成功

2. 安装RabbitMQ

  下载rabbitmq软件包

#  wget  http://www.rabbitmq.com/releases/rabbitmq-server/v3.6.10/rabbitmq-server-3.6.10-1.el7.noarch.rpm

  安装rabbitmq

# rpm -ivh rabbitmq-server-3.6.10-1.el7.noarch.rpm

  启动命令

# systemctl start rabbitmq-server

  查看rabbitmq 启动后的情况

# rabbitmqctl status

3. 配置网页插件

  首先创建目录,否则可能报错:

# mkdir /etc/rabbitmq

  然后启用插件:

# rabbitmq-plugins enable rabbitmq_management

4.配置防火墙

配置linux 端口 15672 网页管理 5672 AMQP端口:

# firewall-cmd --permanent --add-port=15672/tcp
# firewall-cmd --permanent --add-port=5672/tcp
# systemctl restart firewalld.service

5.配置web端访问账号密码和权限

默认网页是不允许访问的,需要增加一个用户修改一下权限,代码如下:

  添加用户:(后面两个参数分别是用户名和密码)

# rabbitmqctl add_user myrabbitmq rabbitmq123

  添加权限:

# rabbitmqctl set_permissions -p / myrabbitmq ".*" ".*" ".*"

  修改用户角色:

# rabbitmqctl set_user_tags myrabbitmq administrator

6. ECS云服务器添加安全组规则

7. 访问web端管理界面

在浏览器输入服务器ip:15672,即可看到RabbitMQ的WEB管理页面,账号密码分别为myrabbitmq,rabbitmq123


常用命令:

# systemctl stop firewalld.service   关闭防火墙

# service rabbitmq-server start       启动RabbitMQ

 

二. SpringBoot使用死信队列

在 RabbitMQ 3.6.x 之前我们一般采用死信队列+TTL过期时间来实现延迟队列。

在 RabbitMQ 3.6.x 开始,RabbitMQ 官方提供了延迟队列的插件。

1. 安装延迟插件(用于死信队列)

SpringBoot项目想要使用死信队列,首先先安装延时插件:

插件下载地址:https://www.rabbitmq.com/community-plugins.html

找到与rabbitmq对应的版本下载即可。

① rpm安装的rabbitmq,方法如下:

解压下载好的插件,拷贝到linux服务器如下目录:

重启rabbitmq,执行如下命令:

rabbitmq-plugins enable rabbitmq_delayed_message_exchange

② 如果是zip安装的rabbitmq,方法如下:

2. SpringBoot使用死信队列

场景分析:一些电商网站或者外卖APP,在我们提交订单后,24小时或者半小时未支付,则该笔订单作废,即会变为失效状态,如果用传统的定时任务,订单数量上去了,轻则性能非常非常低,重则服务器宕机,为了解决该场景,可以采用RabbitMQ的死信队列(也叫延时队列)。

准备工作:maven依赖,application.yml配置,实体类,数据传输对象:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/rabbitmq-plugin?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
    username: root
    password: 123456

  rabbitmq:
    publisher-confirms: true
    publisher-returns: true
    template:
      mandatory: true
    listener:
      simple:
        # 消费者监听并发数
        concurrency: 10
        # 最大并发数
        max-concurrency: 20
        # 签收模式,手动    
        acknowledge-mode: manual
        # 限流(预拉取量5条)
        prefetch: 5
    host: 192.168.10.10
    port: 5672
    username: myrabbit
    password: 123456
    connection-timeout: 15000
    virtual-host: /
package com.example.entity;
import java.io.Serializable;
import java.util.Date;
@Data
public class OrderEntity implements Serializable{
    private Integer id;
    private String orderNo;
    private Integer userId;
    private Integer status;
    private Date createTime;
    private Date updateTime;
}
package com.example.entity;
import java.io.Serializable;
@Data
public class OrderDto implements Serializable{
    private String orderNo;
    private Integer userId;
}

数据表结构如下:

接下来进行rabbitmq的具体配置:

 ① 首先建立RabbitMq的配置文件

  声明处理消息的队列,通过routingkey绑定交换机,注意交换机为CustomExchange。

package com.example.rabbitmq;

import java.util.HashMap;
import java.util.Map;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.CustomExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMqConfig {

    public static final String DELAY_EXCHANGE_NAME = "plugin-dead-exchange";
	
    public static final String DELAY_QUEUE_NAME = "plugin-dead-queue";
	
    public static final String ROUTING_KRY = "plugin-routing-key";
	
    /**
     * 声明一个死信队列
     * @return
     */
    @Bean
    Queue delayQueue(){
        return QueueBuilder.durable(DELAY_QUEUE_NAME)
            .build();
    }
    /**
     * 声明一个交换机
     * @return
     */
    @Bean
    CustomExchange delayExchange(){
        Map<String, Object> args = new HashMap<>();
        args.put("x-delayed-type", "direct"); // 直连模式
        return new CustomExchange(DELAY_EXCHANGE_NAME, "x-delayed-message", true,false, args);
    }
    /**
    * 绑定
    * @param delayQueue
    * @param delayExchange
    * @return
    */
    @Bean
    Binding queueBinding(Queue delayQueue, CustomExchange delayExchange){
        return BindingBuilder.bind(delayQueue)
            .to(delayExchange)
            .with(ROUTING_KRY)
            .noargs();
    }

}

 ② 编写生产者模板

package com.example.rabbitmq;

import com.example.entity.OrderEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Producer {

    private static final Logger log = LoggerFactory.getLogger(Producer.class);

    @Autowired
    private RabbitTemplate rabbitTemplate;
	
    /**
     * 监听函数:confirm确认消息投递成功
     */
    /*
    final ConfirmCallback confirmCallback = new RabbitTemplate.ConfirmCallback() {
        @Override
        public void confirm(CorrelationData correlationData, boolean ack, String cause) {
            String messageId = correlationData.getId();
            if (ack) {
                log.info("消息投递成功,{}",messageId);
                //进行消息记录的数据库更新	
            }else{
                log.info("消息投递失败");
            }	
        }
    };
	*/
    /**
     * 发送消息
     * @param msg 消息
     * @param expiration 延迟时间
     */
    public void sendMessage(OrderEntity msg, Long expiration){
        // 绑定异步监听回调函数
        // rabbitTemplate.setConfirmCallback(this.confirmCallback);

      rabbitTemplate.convertAndSend(RabbitMqConfig.DELAY_EXCHANGE_NAME,RabbitMqConfig.ROUTING_KRY, msg,(message)->{
            // MessageProperties properties=message.getMessageProperties();
            // properties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);//持久化
            // properties..setHeader("x-delay", expiration); // 设置延迟时间
            message.getMessageProperties().setHeader("x-delay", expiration); // 设置延迟时间
            return message;
        },new CorrelationData(msg.getOrderNo()));
    }

}

 ③ 编写Dao层

package com.example.dao;

import com.example.entity.OrderEntity;
import com.example.provider.OrderProvider;
import org.apache.ibatis.annotations.*;

@Mapper
public interface OrderDao {

    /**
     * 提交订单
     * @param orderEntity
     * @return
     */
    @InsertProvider(type= OrderProvider.class,method="insertOrder")
    @Options(useGeneratedKeys=true)
    Integer insertOrder(OrderEntity orderEntity);

    /**
     * 根据订单号查询订单
     * @param orderNo
     * @return
     */
    @Select("select * from user_order where order_no=#{orderNo} and status=1")
    OrderEntity getByOrderNo(@Param("orderNo")String orderNo);

    /**
     * 修改订单状态为已失效
     * @param orderNo
     * @return
     */
    @Update("update user_order set status=4 where order_no=#{orderNo}")
    int updateStatusToLoseEfficacy(@Param("orderNo")String orderNo);

}
package com.example.provider;

import com.example.entity.OrderEntity;
import org.apache.ibatis.jdbc.SQL;

public class OrderProvider {

    public String insertOrder(OrderEntity orderEntity){
        return new SQL(){{
            INSERT_INTO("user_order");
            if(orderEntity.getId() != null){
                VALUES("id","#{id}");
            }
            if(orderEntity.getOrderNo() != null){
                VALUES("order_no","#{orderNo}");
            }
            if(orderEntity.getUserId() != null){
                VALUES("user_id","#{userId}");
            }
            if(orderEntity.getStatus() != null){
                VALUES("status","#{status}");
            }
            if(orderEntity.getCreateTime() != null){
                VALUES("create_time","#{createTime}");
            }
            if(orderEntity.getUpdateTime() != null){
                VALUES("update_time","#{updateTime}");
            }
        }}.toString();
    }

}

 ④ 编写提交订单接口(生产者

package com.example.controller;

import com.example.dao.OrderDao;
import com.example.entity.OrderDto;
import com.example.entity.OrderEntity;
import com.example.rabbitmq.Producer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.yuyi.full.handler.exception.ResultBO;
import org.yuyi.full.handler.exception.ResultTool;
import java.util.Date;


@RestController
public class OrderController {

    private static final Logger log = LoggerFactory.getLogger(OrderController.class);

    @Autowired
    private OrderDao orderDao;
    @Autowired
    private Producer producer;

    private static final Long EXPIRE_TIME = 60000L; // 1分钟

    /**
     * 提交订单
     */
    @PostMapping("/submitOrder")
    public ResultBO<?> submitOrder(@RequestBody OrderDto orderDto){
        // 数据库操作
        OrderEntity orderEntity = new OrderEntity();
        BeanUtils.copyProperties(orderDto,orderEntity);
        orderEntity.setStatus(1);
        orderEntity.setCreateTime(new Date());
        orderDao.insertOrder(orderEntity);

        // RabbitMQ操作
        producer.sendMessage(orderEntity, EXPIRE_TIME);
        log.info("【RabbitMQ发送消息成功】:{}",orderEntity);

        return ResultTool.success(orderEntity);
    }

}

 编写RabbitMQ监听器(消费者

package com.example.rabbitmq;

import java.time.LocalDateTime;
import java.util.Map;
import java.util.Objects;
import com.example.dao.OrderDao;
import com.example.entity.OrderEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
import com.rabbitmq.client.Channel;

@Component
public class Consumer {

    private static final Logger log = LoggerFactory.getLogger(Consumer.class);

    @Autowired
    private OrderDao orderDao;

    @RabbitListener(queues = "plugin-dead-queue")
    @RabbitHandler
    public void consumer(@Payload OrderEntity msg, @Headers Map<String, Object> headers, Channel channel) throws Exception {
        log.info("【RabbitMQ监听到消息】:{}",msg);
        String orderNo = msg.getOrderNo();

        // 根据订单号查询订单,如果订单状态还是未付款,则修改为已失效
        OrderEntity orderEntity = orderDao.getByOrderNo(orderNo);
        if(Objects.nonNull(orderEntity)){
            // 24小时未付款,设为失效状态
            orderDao.updateStatusToLoseEfficacy(orderNo);
        }

        // ack手工签收,通知RabbitMQ,消费端消费成功
        Long deliveryTag = (Long)headers.get(AmqpHeaders.DELIVERY_TAG);
        channel.basicAck(deliveryTag,false);
    }

}

此时,代码编写完毕!下面用postman测试一下:

  

可以看到提交订单后数据库会生成一条记录,默认状态为1(待付款),IDEA控制台也会打印发送者日志:

24小时太长,本人设置的延时时间为1分钟,所以耐心等待1分钟后,再观察数据库和控制台如下:

可以发现,在没付款的情况下,死信队列会修改订单状态为1(已失效)。

通过死信队列解决订单超时失效场景,异步操作,性能非常高,这是互联网电商项目必不可少的技术点 ~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值