非Spring项目实现RabbitMq消息生产和消费

问题:

​ 如果脱离了Spring要怎么实现一个RabbitMq生产者和消费者的客户端?

方案

资源

依赖

    <dependencies>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j2-impl</artifactId>
            <version>2.23.1</version>
            <scope>optional</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.23.1</version>
            <scope>optional</scope>
        </dependency>

        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.14.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.8.1</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>2.0.48</version>
            <scope>test</scope>
        </dependency>
    </dependencies>


    <build>

        <resources>
            <resource>
                <!-- 设定主资源目录  -->
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.yml</include>
                    <include>README.md</include>
                    <include>log4j2.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>

        <plugins>
            <plugin>
                <artifactId>maven-dependency-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <executions>
                    <execution>
                        <id>attach-sources</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

服务端配置

rabbitmq_host=localhost
rabbitmq_port=5672
rabbitmq_username=admin
rabbitmq_password=123456

日志配置

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <Appenders>
        <Console name="CONSOLE" target="SYSTEM_OUT">
            <PatternLayout charset="UTF-8" pattern="[%-5p] %d %c - %m%n"/>
        </Console>

    </Appenders>
    <Loggers>
        <root level="info">
            <AppenderRef ref="CONSOLE"/>
        </root>
    </Loggers>
</configuration>

代码

工具类

服务器配置文件工具类


import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.io.InputStream;
import java.util.Objects;
import java.util.Properties;

/**
 * rabbitmq 配置工具类
 *
 * @author xuyuansheng
 */
@Data
@Slf4j
public class RabbitConfigUtil {

    private final static String CONFIG_PATH = "rabbitmq.properties";
    public static Properties CONFIG = null;
    private volatile static boolean IS_LOADED = false;

    public static boolean initConfig(String configPath) {
        try {
            InputStream resourceAsStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(configPath);
            Properties properties = new Properties();
            properties.load(resourceAsStream);
            return Objects.nonNull(doInitConfig(properties));
        } catch (Throwable e) {
            log.error(e.toString(), e);
            throw new RuntimeException(e);
        }
    }

    public static boolean initConfig() {
        return initConfig(CONFIG_PATH);
    }

    public static boolean initConfig(Properties config) {
        return Objects.nonNull(doInitConfig(config));
    }

    private static synchronized Properties doInitConfig(Properties config) {
        if (IS_LOADED) {
            return CONFIG;
        }
        if (Objects.isNull(config)) {
            throw new RuntimeException("config is null");
        }
        boolean hostNull = Objects.isNull(config.getProperty("rabbitmq_host"));
        boolean portNull = Objects.isNull(config.getProperty("rabbitmq_port"));
        boolean usernameNull = Objects.isNull(config.getProperty("rabbitmq_username"));
        boolean passwordNull = Objects.isNull(config.getProperty("rabbitmq_password"));
        if (hostNull || portNull || usernameNull || passwordNull) {
            throw new RuntimeException("config properties is null");
        }
        IS_LOADED = true;
        RabbitConfigUtil.CONFIG = config;
        return config;
    }

    public static Properties getConfig() {
        if (IS_LOADED || initConfig()) {
            return CONFIG;
        }
        throw new RuntimeException("not found rabbitmq config");
    }

}

线程池


import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author xuyuansheng
 */
public class ThreadPoolBuilder {
    private static final int corePoolSize = 10;
    private static final int maximumPoolSize = 100;
    private static final long keepAliveTime = 10;
    private static final TimeUnit unit = TimeUnit.SECONDS;
    private static ArrayBlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(500);

    public static ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, new ThreadFactory() {
        private int threadCount = 0;

        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setName("RabbitMqThread-" + (++threadCount));
            return thread;
        }
    });
}

RabbitConnectionPool

RabbitMq Connection(连接)的池化实现

import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.*;
import org.jinko.util.rabbitmq.util.RabbitConfigUtil;

import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.Properties;

/**
 * @author xuyuansheng
 */
@Slf4j
public class RabbitConnectionPool extends GenericObjectPool<Connection> {

    /*  第个连接对象的不可用时间 */
    private final static HashMap<PooledObject<Connection>, Instant> CONNECTIONS_CLOSE_TIME = new HashMap<>();
    private final static Duration TIME = Duration.of(2, ChronoUnit.HOURS);


    public RabbitConnectionPool() {
        super(new PooledObjectFactory());
        this.setConfig(poolConfig());
    }

    private GenericObjectPoolConfig<Connection> poolConfig() {
        Properties config = RabbitConfigUtil.getConfig();
        GenericObjectPoolConfig<Connection> poolConfig = new GenericObjectPoolConfig<>();
        // 最大连接数
        poolConfig.setMaxTotal(Integer.parseInt(config.getProperty("connect_pool_max_total", "3")));
        // 最大空闲连接数
        poolConfig.setMaxIdle(Integer.parseInt(config.getProperty("connect_pool_max_idle", "3")));
        // 最小空闲连接数
        poolConfig.setMinIdle(Integer.parseInt(config.getProperty("connect_pool_min_idle", "0")));
        // 借用连接时是否进行有效性检查
        poolConfig.setTestOnBorrow(Boolean.parseBoolean(config.getProperty("connect_pool_test_on_borrow", "true")));
        // 归还连接时是否进行有效性检查
        poolConfig.setTestOnReturn(Boolean.parseBoolean(config.getProperty("connect_pool_test_on_return", "true")));
        // 空闲连接是否进行有效性检查
        poolConfig.setTestWhileIdle(Boolean.parseBoolean(config.getProperty("connect_pool_test_while_idle", "true")));
        // 空闲对象检查间隔时间
        poolConfig.setTimeBetweenEvictionRunsMillis(Integer.parseInt(config.getProperty("connect_pool_time_between_eviction_runs_millis", "30000")));
        poolConfig.setEvictionPolicy(new EvictionPolicy<Connection>() {
            @Override
            public boolean evict(EvictionConfig config, PooledObject<Connection> underTest, int idleCount) {

                if (underTest.getObject().isOpen()) {
                    /*  不主动回收任何正常连接对象 */
                    CONNECTIONS_CLOSE_TIME.put(underTest, Instant.now());
                    return false;
                }
                Duration between = Duration.between(CONNECTIONS_CLOSE_TIME.get(underTest), Instant.now());
                if (between.compareTo(TIME) > 0) {
                    log.error("connection State {} BorrowedCount {} ErrorTime {}", underTest.getState(), underTest.getBorrowedCount(), between);
                    return true;
                }
                return false;
            }
        });
        return poolConfig;
    }

    private static class PooledObjectFactory extends BasePooledObjectFactory<Connection> {
        private static final String RABBITMQ_VIRTUAL_HOST = "/";
        private static final int RABBITMQ_CONNECTION_TIMEOUT = 30000;
        private static final int RABBITMQ_REQUESTED_HEARTBEAT = 60;

        private final ConnectionFactory connectionFactory;

        public PooledObjectFactory() {
            this.connectionFactory = new ConnectionFactory();
            initFactory();
        }

        private void initFactory() {
            Properties config = RabbitConfigUtil.getConfig();
            connectionFactory.setHost(config.getProperty("rabbitmq_host"));
            connectionFactory.setPort(Integer.parseInt(config.getProperty("rabbitmq_port")));
            connectionFactory.setUsername(config.getProperty("rabbitmq_username"));
            connectionFactory.setPassword(config.getProperty("rabbitmq_password"));
            connectionFactory.setConnectionTimeout(Integer.parseInt(config.getProperty("rabbitmq_connection_timeout", RABBITMQ_CONNECTION_TIMEOUT + "")));
            connectionFactory.setRequestedHeartbeat(Integer.parseInt(config.getProperty("rabbitmq_requested_heartbeat", RABBITMQ_REQUESTED_HEARTBEAT + "")));
            connectionFactory.setVirtualHost(config.getProperty("rabbitmq_virtual_host", RABBITMQ_VIRTUAL_HOST));
        }

        @Override
        public void destroyObject(PooledObject<Connection> p) throws Exception {
            p.getObject().close();
            CONNECTIONS_CLOSE_TIME.remove(p);
        }

        @Override
        public Connection create() throws Exception {
            return connectionFactory.newConnection();
        }

        @Override
        public PooledObject<Connection> wrap(Connection obj) {
            DefaultPooledObject<Connection> pooledObject = new DefaultPooledObject<>(obj);
            CONNECTIONS_CLOSE_TIME.put(pooledObject, Instant.now());
            return pooledObject;
        }
    }

}

RabbitChannelPool

RabbitMq Channel(通道)的池化实现


import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import lombok.SneakyThrows;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

import java.util.Objects;

/**
 * @author xuyuansheng
 */
public class RabbitChannelPool extends GenericObjectPool<Channel> {

    private static RabbitChannelPool SINGLETON = null;

    private RabbitChannelPool() {
        super(new PooledObjectFactory());
        this.setConfig(poolConfig());
    }

    @SneakyThrows
    public static Channel getChannel() {
        if (Objects.nonNull(SINGLETON)) {
            return SINGLETON.borrowObject();
        }
        synchronized (RabbitChannelPool.class) {
            SINGLETON = new RabbitChannelPool();
        }
        return SINGLETON.borrowObject();
    }

    private GenericObjectPoolConfig<Channel> poolConfig() {
        GenericObjectPoolConfig<Channel> poolConfig = new GenericObjectPoolConfig<>();
        // 最大连接数
        poolConfig.setMaxTotal(1000);
        // 最大空闲连接数
        poolConfig.setMaxIdle(10);
        // 最小空闲连接数
        poolConfig.setMinIdle(1);
        // 借用连接时是否进行有效性检查
        poolConfig.setTestOnBorrow(true);
        // 归还连接时是否进行有效性检查
        poolConfig.setTestOnReturn(true);
        // 空闲连接是否进行有效性检查
        poolConfig.setTestWhileIdle(true);
        // 空闲连接检查间隔时间
        poolConfig.setTimeBetweenEvictionRunsMillis(30000);
        return poolConfig;
    }

    private static class PooledObjectFactory extends BasePooledObjectFactory<Channel> {


        private final RabbitConnectionPool connectionPool;

        public PooledObjectFactory() {
            connectionPool = new RabbitConnectionPool();
        }

        @Override
        public void destroyObject(PooledObject<Channel> p) throws Exception {
            p.getObject().close();
        }

        @Override
        public Channel create() throws Exception {
            Connection connection = connectionPool.borrowObject();
            connectionPool.returnObject(connection);
            return connection.createChannel();
        }

        @Override
        public PooledObject<Channel> wrap(Channel obj) {
            return new DefaultPooledObject<>(obj);
        }
    }

}

CommonConsumerWrapper

消费者实现

import com.rabbitmq.client.*;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.jinko.util.rabbitmq.pool.RabbitChannelPool;
import org.jinko.util.rabbitmq.pool.ThreadPoolBuilder;

import java.io.IOException;
import java.util.Objects;
import java.util.Random;
import java.util.function.BiConsumer;

/**
 * @author xuyuansheng
 */

@Accessors(chain = true)
@Slf4j
public class CommonConsumerWrapper extends DefaultConsumer {


    private final String queueName;
    private final boolean autoAck;
    private final String consumerTag;
    @Setter
    private DeliverCallback deliverCallback;
    @Setter
    private BiConsumer<DefaultConsumer, Delivery> deliverAckCallback;
    @Setter
    private BiConsumer<DefaultConsumer, String> handleConsumeOk;
    @Setter
    private BiConsumer<DefaultConsumer, String> handleCancelOk;
    @Setter
    private BiConsumer<DefaultConsumer, String> handleCancel;
    @Setter
    private BiConsumer<DefaultConsumer, Object[]> handleShutdownSignal;
    @Setter
    private BiConsumer<DefaultConsumer, String> handleRecoverOk;


    public CommonConsumerWrapper(String queueName, boolean autoAck, String consumerTag) {
        this(queueName, autoAck, consumerTag, null, null, null, null, null, null);
    }

    public CommonConsumerWrapper(String queueName, boolean autoAck, String consumerTag, DeliverCallback deliverCallback) {
        this(queueName, autoAck, consumerTag, deliverCallback, null, null, null, null, null);
    }

    public CommonConsumerWrapper(String queueName, boolean autoAck, String consumerTag, DeliverCallback deliverCallback, BiConsumer<DefaultConsumer, String> handleConsumeOk, BiConsumer<DefaultConsumer, String> handleCancelOk, BiConsumer<DefaultConsumer, String> handleCancel, BiConsumer<DefaultConsumer, Object[]> handleShutdownSignal, BiConsumer<DefaultConsumer, String> handleRecoverOk) {
        super(RabbitChannelPool.getChannel());
        this.queueName = queueName;
        this.autoAck = autoAck;
        this.consumerTag = queueName + ((Objects.isNull(consumerTag) ? String.valueOf(new Random().nextLong()) : consumerTag));
        this.deliverCallback = deliverCallback;
        this.handleConsumeOk = handleConsumeOk;
        this.handleCancelOk = handleCancelOk;
        this.handleCancel = handleCancel;
        this.handleShutdownSignal = handleShutdownSignal;
        this.handleRecoverOk = handleRecoverOk;
    }

    @SneakyThrows
    public void consumer() {
        ThreadPoolBuilder.executor.submit(() -> {
            try {
                getChannel().basicConsume(queueName, autoAck, consumerTag, this);
            } catch (IOException e) {
                log.error(e.toString(), e);
            }
        });
    }

    @Override
    public void handleConsumeOk(String consumerTag) {
        /*  当消费者通过调用任何Channel. basicConsum方法注册时调用。 */
        if (Objects.isNull(this.handleConsumeOk)) {
            super.handleConsumeOk(consumerTag);
        } else {
            this.handleConsumeOk.accept(this, consumerTag);
        }
    }

    @Override
    public void handleCancelOk(String consumerTag) {
        /*  当消费者通过调用Channel. basicCanel取消时调用。 */
        if (Objects.isNull(this.handleCancelOk)) {
            super.handleConsumeOk(consumerTag);
        } else {
            this.handleCancelOk.accept(this, consumerTag);
        }
    }

    @Override
    public void handleCancel(String consumerTag) throws IOException {
        /* 当消费者因不是通过调用Channel. basicCanel的原因而被取消时调用。例如,队列已被删除。有关由于Channel.basicCanel导致消费者取消的通知,请参阅handleCUelOk。 */
        if (Objects.isNull(this.handleCancel)) {
            super.handleCancel(consumerTag);
        } else {
            this.handleCancel.accept(this, consumerTag);
        }
    }

    @Override
    public void handleShutdownSignal(String consumerTag, ShutdownSignalException sig) {
        /*  当通道或基础连接已关闭时调用。  */
        if (Objects.isNull(this.handleShutdownSignal)) {
            super.handleShutdownSignal(consumerTag, sig);
        } else {
            this.handleShutdownSignal.accept(this, new Object[]{consumerTag, sig});
        }
    }

    @Override
    public void handleRecoverOk(String consumerTag) {
        /*  Called when a basic.recover-ok is received in reply to a basic.recover. All messages received before this is invoked that haven't been ack'ed will be re-delivered. All messages received afterwards won't be. */
        if (Objects.isNull(this.handleRecoverOk)) {
            super.handleRecoverOk(consumerTag);
        } else {
            this.handleRecoverOk.accept(this, consumerTag);
        }
    }

    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        /*  当收到此消费者的基本交付时调用 */
        Delivery delivery = new Delivery(envelope, properties, body);
        try {
            this.deliverCallback.handle(consumerTag, delivery);
        } catch (Exception e) {
            log.error("message handleDelivery Error ", e);
            if (autoAck || Objects.nonNull(this.deliverAckCallback)) {
                /*  当策略是自动提交时,如果发生异常直接拒绝消息,且重新放回队列中 */
                getChannel().basicNack(delivery.getEnvelope().getDeliveryTag(), false, true);
            }
        } finally {
            if (!autoAck && Objects.nonNull(this.deliverAckCallback)) {
                this.deliverAckCallback.accept(this, delivery);
            }
        }

    }
}

案例

消费者

import com.alibaba.fastjson2.JSON;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.DeliverCallback;
import com.rabbitmq.client.ShutdownSignalException;
import lombok.extern.slf4j.Slf4j;
import org.jinko.util.rabbitmq.consumer.CommonConsumerWrapper;
import org.jinko.util.rabbitmq.util.RabbitConfigUtil;

import java.nio.charset.StandardCharsets;

/**
 * @author xuyuansheng
 */
@Slf4j
public class RabbitConsumerDemo {


    static {
        /*  这块代码全局只需要执行一次 */
        boolean b = RabbitConfigUtil.initConfig("rabbitmq.properties");
    }

    public static void main(String[] args) {

        RabbitConsumerDemo rabbitConsumerDemo = new RabbitConsumerDemo();
        for (int i = 0; i < 1; i++) {
            rabbitConsumerDemo.init();
        }


    }

    public void init() {
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {

            try {
                /*  消费者业务逻辑代码 */
                String message = new String(delivery.getBody(), StandardCharsets.ISO_8859_1);
                log.info("consumerTag {} Received {}", consumerTag, message);
                JSON.parse(message);
            } catch (Exception e) {
                log.error("业务逻辑异常", e);
            }

        };
        /*  声明一个消费者,参数:队列名称,是否自动提交,业务逻辑(DeliverCallback),消费者标签 */
        CommonConsumerWrapper consumer = new CommonConsumerWrapper("hello", false, "消费者Tag", deliverCallback)
                .setDeliverCallback(deliverCallback)
                .setDeliverAckCallback((ack, delivery) -> {
                    try {
                        /*  手动确认  拒绝 */
//                        ack.getChannel().basicNack(delivery.getEnvelope().getDeliveryTag(), false, false);
                        /*  手动确认  完成 */
                        ack.getChannel().basicAck(delivery.getEnvelope().getDeliveryTag(), false);
                    } catch (Throwable e) {
                        log.error("Throwable", e);
                    }
                })
                .setHandleShutdownSignal((DefaultConsumer handleShutdownSignal, Object[] errorInfo) -> {
                    /*  业务逻辑异常处理 */
                    String consumerTag = (String) errorInfo[0];
                    ShutdownSignalException exception = (ShutdownSignalException) errorInfo[1];
                    log.error("[*] 消费者发生异常,consumerTag: {}", consumerTag, exception);
                }).setHandleCancelOk((handleCancelOk, consumerTag) -> {
                    log.info("handleCancel ,consumerTag: {}", consumerTag);
                }).setHandleCancel((handleCancel, consumerTag) -> {
                    log.info("handleCancel ,consumerTag: {}", consumerTag);
                }).setHandleRecoverOk((handleRecoverOk, consumerTag) -> {
                    log.info("handleRecoverOk ,consumerTag: {}", consumerTag);
                });

        consumer.consumer();
        System.out.println("[*] Waiting for messages. To exit press CTRL+C");
    }

}

生产者

/**
 * @author xuyuansheng
 */
public class RabbitProducerDemo {


    static {
        /*  这块代码全局只需要执行一次 */
        boolean b = RabbitConfigUtil.initConfig("rabbitmq.properties");
    }

    @SneakyThrows
    public static void main(String[] args) {
        boolean b = RabbitConfigUtil.initConfig("rabbitmq.properties");
        try (Channel channel = RabbitChannelPool.getChannel()) {
            String message = "Hello, RabbitMQ!";
            channel.basicPublish("", "hello", null, message.getBytes(StandardCharsets.UTF_8));
            System.out.println(" [x] Sent '" + message + "'");
        }
    }
}

问题:

  1. 连接关闭,消费者丢失
posted @ 2024-03-18 17:13  菜阿  阅读(51)  评论(0编辑  收藏  举报