Flink消费Rabbitmq匿名队列的数据(只需要绑定exchange和router-key)

Flink消费Rabbitmq匿名队列的数据(只需要绑定exchange和router-key)

1. 环境准备(使用的docker中的rabbitmq)

rabbitmq

# 查找rabbitmq应用
➜  ~ sudo docker search rabbitmq
# 下载镜像 (有时候网络问题超时,多尝试几次即可)
➜  ~ sudo docker pull rabbitmq:management
# 创建容器并运行(15672是管理界面的端口,5672是服务的端口。)
➜  ~ docker run -d -p 5672:5672 -p 15672:15672 --name myrabbitmq rabbitmq:management

2. JAVA代码

producer

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class MyProducer {
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("10.8.29.30");
        factory.setPort(5672);
        factory.setUsername("admin");
        factory.setPassword("admin");
        factory.setVirtualHost("/");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        String message = "hello message ";
        for (int i = 0; i < 100000; i++) {
            channel.basicPublish("amq.direct", "4xs-54mk5v2rVJ0KYZs_tA==", null, (message + i).getBytes());
        }
        channel.close();
        connection.close();
    }
}

main

import com.ruizhan.data.source.MySource;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.common.serialization.SimpleStringSchema;
import org.apache.flink.streaming.api.environment.LocalStreamEnvironment;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.connectors.rabbitmq.common.RMQConnectionConfig;


public class FlinkStreamingJob {
    public static void main(String[] args) throws Exception {
        LocalStreamEnvironment env = StreamExecutionEnvironment.createLocalEnvironment();
        env.enableCheckpointing(1000);
        // 线下
        RMQConnectionConfig config = new RMQConnectionConfig.Builder()
                .setHost("10.8.29.30")
                .setPort(5672)
                .setUserName("admin")
                .setPassword("admin")
                .setVirtualHost("/")
                .build();
        MySource<String> mySource = new MySource<String>(config, "", "amq.direct","4xs-54mk5v2rVJ0KYZs_tA==", false,
                                                         new SimpleStringSchema()) {
        };
        // RMQSource<String> rmqSource = new RMQSource<>(config, "queuename", new SimpleStringSchema());
        env.addSource(mySource).map((MapFunction<String, String>) value -> value).print();
        env.execute();
    }
}

MySource

flink消费rabbitmq有官方提供的RMQSource,但是使用这个的前提是明确队列名,但是业务上使用的队列是默认生成的,队列名类似“amq.gen-KiQAO2YsoywQX2kew2DDsg”,只能根据提供的direct和router-key来消费数据

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.ruizhan.data.consumer.MyConsumer;
import org.apache.flink.api.common.functions.RuntimeContext;
import org.apache.flink.api.common.serialization.DeserializationSchema;
import org.apache.flink.api.common.typeinfo.TypeInformation;
import org.apache.flink.api.java.typeutils.ResultTypeQueryable;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.functions.source.MultipleIdsMessageAcknowledgingSourceBase;
import org.apache.flink.streaming.api.operators.StreamingRuntimeContext;
import org.apache.flink.streaming.connectors.rabbitmq.common.RMQConnectionConfig;
import org.apache.flink.util.Collector;
import org.apache.flink.util.Preconditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.List;

public class MySource<OUT> extends MultipleIdsMessageAcknowledgingSourceBase<OUT, String, Long>
        implements ResultTypeQueryable<OUT> {

    private final RMQConnectionConfig rmqConnectionConfig;

    private static final long serialVersionUID = 1L;

    private static final Logger LOG = LoggerFactory.getLogger(MySource.class);

    protected String queueName;
    private final boolean usesCorrelationId;
    protected DeserializationSchema<OUT> schema;

    protected Connection connection;
    protected Channel channel;
    protected MyConsumer consumer;
    protected String exchange;
    protected String routerKey;

    protected transient boolean autoAck;

    private transient volatile boolean running;

    public MySource(RMQConnectionConfig rmqConnectionConfig, String queueName, String exchange, String routerKey,
                    DeserializationSchema<OUT> deserializationSchema) {
        this(rmqConnectionConfig, queueName, exchange, routerKey, false, deserializationSchema);
    }

    public MySource(RMQConnectionConfig rmqConnectionConfig,
                    String queueName, String exchange, String routerKey, boolean usesCorrelationId,
                    DeserializationSchema<OUT> deserializationSchema) {
        super(String.class);
        this.rmqConnectionConfig = rmqConnectionConfig;
        this.queueName = queueName;
        this.exchange = exchange;
        this.routerKey = routerKey;
        this.usesCorrelationId = usesCorrelationId;
        this.schema = deserializationSchema;
    }

    @Override
    public void open(Configuration config) throws Exception {
        super.open(config);
        try {
            connection = rmqConnectionConfig.getConnectionFactory().newConnection();
            channel = connection.createChannel();
            String queue = channel.queueDeclare().getQueue();
            System.out.println(queue);
            if (channel == null) {
                throw new RuntimeException("None of RabbitMQ channels are available");
            }
            //setupQueue();
            consumer = new MyConsumer(channel);

            RuntimeContext runtimeContext = getRuntimeContext();
            if (runtimeContext instanceof StreamingRuntimeContext
                    && ((StreamingRuntimeContext) runtimeContext).isCheckpointingEnabled()) {
                autoAck = false;
                // enables transaction mode
                channel.txSelect();
            } else {
                autoAck = true;
            }

            LOG.debug("Starting RabbitMQ source with autoAck status: " + autoAck);
            channel.queueBind(queue, this.exchange, this.routerKey);
//            channel.queueBind(queue, "mfp.exchange.distribute.FaceCapture", "4xs-54mk5v2rVJ0KYZs_tA==");
            channel.basicConsume(queue, false, consumer);

        } catch (IOException e) {
            throw new RuntimeException("Cannot create RMQ connection with " + queueName + " at "
                                               + rmqConnectionConfig.getHost(), e);
        }
        this.schema.open(() -> getRuntimeContext().getMetricGroup().addGroup("user"));
        running = true;
    }

    @Override
    public void close() throws Exception {
        super.close();

        try {
            if (consumer != null && channel != null) {
                channel.basicCancel(consumer.getConsumerTag());
            }
        } catch (IOException e) {
            throw new RuntimeException("Error while cancelling RMQ consumer on " + queueName
                                               + " at " + rmqConnectionConfig.getHost(), e);
        }

        try {
            if (channel != null) {
                channel.close();
            }
        } catch (IOException e) {
            throw new RuntimeException("Error while closing RMQ channel with " + queueName
                                               + " at " + rmqConnectionConfig.getHost(), e);
        }

        try {
            if (connection != null) {
                connection.close();
            }
        } catch (IOException e) {
            throw new RuntimeException("Error while closing RMQ connection with " + queueName
                                               + " at " + rmqConnectionConfig.getHost(), e);
        }
    }

    @Override
    public void run(SourceContext<OUT> ctx) throws Exception {
        final MySource.RMQCollector collector =
                new MySource.RMQCollector(ctx);
        while (running) {
            MyConsumer.Delivery delivery = consumer.nextDelivery();

            synchronized (ctx.getCheckpointLock()) {
                if (!autoAck) {
                    final long deliveryTag = delivery.getEnvelope().getDeliveryTag();
                    if (usesCorrelationId) {
                        final String correlationId = delivery.getProperties().getCorrelationId();
                        Preconditions.checkNotNull(correlationId, "RabbitMQ source was instantiated " +
                                "with usesCorrelationId set to true but a message was received with " +
                                "correlation id set to null!");
                        if (!addId(correlationId)) {
                            // we have already processed this message
                            continue;
                        }
                    }
                    sessionIds.add(deliveryTag);
                }
				// 数据下放的重点
                schema.deserialize(delivery.getBody(), collector);
                if (collector.isEndOfStreamSignalled()) {
                    this.running = false;
                    return;
                }
            }
        }
    }

    private class RMQCollector implements Collector<OUT> {

        private final SourceContext<OUT> ctx;
        private boolean endOfStreamSignalled = false;

        private RMQCollector(SourceContext<OUT> ctx) {
            this.ctx = ctx;
        }

        @Override
        public void collect(OUT record) {
            if (endOfStreamSignalled || schema.isEndOfStream(record)) {
                this.endOfStreamSignalled = true;
                return;
            }

            ctx.collect(record);
        }

        public boolean isEndOfStreamSignalled() {
            return endOfStreamSignalled;
        }

        @Override
        public void close() {

        }
    }

    @Override
    public void cancel() {
        running = false;
    }

    @Override
    protected void acknowledgeSessionIDs(List<Long> sessionIds) {
        try {
            for (long id : sessionIds) {
                channel.basicAck(id, false);
            }
            channel.txCommit();
        } catch (IOException e) {
            throw new RuntimeException("Messages could not be acknowledged during checkpoint creation.", e);
        }
    }

    @Override
    public TypeInformation<OUT> getProducedType() {
        return schema.getProducedType();
    }

}

MyConsumer

这里使用自己的消费者而没有使用原本的消费者只是为了方便自己debugger时,确认数据是否正确到达消费端

import com.rabbitmq.client.*;
import com.rabbitmq.utility.Utility;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class MyConsumer extends DefaultConsumer {
    private final BlockingQueue<MyConsumer.Delivery> _queue;

    private volatile ShutdownSignalException _shutdown;
    private volatile ConsumerCancelledException _cancelled;

    private static final MyConsumer.Delivery POISON =
            new MyConsumer.Delivery(null, null, null);

    public MyConsumer(Channel ch) {
        this(ch, new LinkedBlockingQueue<>());
    }

    public MyConsumer(Channel ch, BlockingQueue<MyConsumer.Delivery> q) {
        super(ch);
        this._queue = q;
    }

    @Override public void handleShutdownSignal(String consumerTag,
                                               ShutdownSignalException sig) {
        _shutdown = sig;
        _queue.add(POISON);
    }

    @Override public void handleCancel(String consumerTag) {
        _cancelled = new ConsumerCancelledException();
        _queue.add(POISON);
    }

    @Override
    public void handleDelivery(String consumerTag,
                                         Envelope envelope,
                                         AMQP.BasicProperties properties,
                                         byte[] body) {
        checkShutdown();
        this._queue.add(new MyConsumer.Delivery(envelope, properties, body));
    }

    /**
     * Encapsulates an arbitrary message - simple "bean" holder structure.
     */
    public static class Delivery {
        private final Envelope _envelope;
        private final AMQP.BasicProperties _properties;
        private final byte[] _body;

        public Delivery(Envelope envelope, AMQP.BasicProperties properties, byte[] body) {
            _envelope = envelope;
            _properties = properties;
            _body = body;
        }

        /**
         * Retrieve the message envelope.
         * @return the message envelope
         */
        public Envelope getEnvelope() {
            return _envelope;
        }

        /**
         * Retrieve the message properties.
         * @return the message properties
         */
        public AMQP.BasicProperties getProperties() {
            return _properties;
        }

        /**
         * Retrieve the message body.
         * @return the message body
         */
        public byte[] getBody() {
            return _body;
        }
    }

    /**
     * Check if we are in shutdown mode and if so throw an exception.
     */
    private void checkShutdown() {
        if (_shutdown != null)
            throw Utility.fixStackTrace(_shutdown);
    }

    /**
     * If delivery is not POISON nor null, return it.
     * <p/>
     * If delivery, _shutdown and _cancelled are all null, return null.
     * <p/>
     * If delivery is POISON re-insert POISON into the queue and
     * throw an exception if POISONed for no reason.
     * <p/>
     * Otherwise, if we are in shutdown mode or cancelled,
     * throw a corresponding exception.
     */
    private MyConsumer.Delivery handle(MyConsumer.Delivery delivery) {
        if (delivery == POISON ||
                delivery == null && (_shutdown != null || _cancelled != null)) {
            if (delivery == POISON) {
                _queue.add(POISON);
                if (_shutdown == null && _cancelled == null) {
                    throw new IllegalStateException(
                            "POISON in queue, but null _shutdown and null _cancelled. " +
                                    "This should never happen, please report as a BUG");
                }
            }
        }
        return delivery;
    }

    /**
     * Main application-side API: wait for the next message delivery and return it.
     * @return the next message
     * @throws InterruptedException if an interrupt is received while waiting
     * @throws ShutdownSignalException if the connection is shut down while waiting
     * @throws ConsumerCancelledException if this consumer is cancelled while waiting
     */
    public MyConsumer.Delivery nextDelivery()
            throws InterruptedException, ShutdownSignalException, ConsumerCancelledException
    {
        return handle(_queue.take());
    }

}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值