All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.netflix.suro.sink.QueuedSink Maven / Gradle / Ivy

The newest version!
package com.netflix.suro.sink;

import com.google.common.annotations.VisibleForTesting;
import com.netflix.servo.annotations.DataSourceType;
import com.netflix.servo.annotations.Monitor;
import com.netflix.servo.monitor.DynamicCounter;
import com.netflix.servo.monitor.MonitorConfig;
import com.netflix.suro.TagKey;
import com.netflix.suro.message.Message;
import com.netflix.suro.queue.MessageQueue4Sink;
import com.netflix.suro.servo.Meter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Asynchronous sink would have the internal buffer to return on
 * {@link Sink#writeTo(com.netflix.suro.message.MessageContainer)} method.
 * Implementing asynchronous sink can be done by simply extending this class
 * and initialize itself with {@link MessageQueue4Sink}. This is extending
 * {@link Thread} and its start() method should be called.
 *
 * @author jbae
 */
public abstract class QueuedSink extends Thread {
    protected static Logger log = LoggerFactory.getLogger(QueuedSink.class);

    private long lastBatch = System.currentTimeMillis();
    protected volatile boolean isRunning = false;
    private volatile boolean isStopped = false;

    public static int MAX_PENDING_MESSAGES_TO_PAUSE = 1000000; // not final for the test

    @VisibleForTesting
    protected MessageQueue4Sink queue4Sink;
    private int batchSize;
    private int batchTimeout;
    private String sinkId;

    protected Meter throughput;
    private boolean pauseOnLongQueue;

    // make queue occupied more than half, then should be paused
    // if throughput is too small at the beginning, pause can be too big
    // so, setting up minimum threshold as 1 message per millisecond
    public long checkPause() {
        if (pauseOnLongQueue &&
                (getNumOfPendingMessages() > queue4Sink.remainingCapacity() ||
                getNumOfPendingMessages() > MAX_PENDING_MESSAGES_TO_PAUSE)) {
            double throughputRate = Math.max(throughput.meanRate(), 1.0);
            return (long) (getNumOfPendingMessages() / throughputRate);
        } else {
            return 0;
        }
    }

    public String getSinkId() { return sinkId; }

    protected void initialize(
            String sinkId,
            MessageQueue4Sink queue4Sink,
            int batchSize, int batchTimeout,
            boolean pauseOnLongQueue) {
        this.sinkId = sinkId;
        this.queue4Sink = queue4Sink;
        this.batchSize = batchSize == 0 ? 1000 : batchSize;
        this.batchTimeout = batchTimeout == 0 ? 1000 : batchTimeout;
        this.pauseOnLongQueue = pauseOnLongQueue;

        throughput = new Meter(MonitorConfig.builder(sinkId + "_throughput_meter").build());
    }

    protected void initialize(MessageQueue4Sink queue4Sink, int batchSize, int batchTimeout) {
        initialize("empty_sink_id", queue4Sink, batchSize, batchTimeout, false);
    }

    @VisibleForTesting
    protected AtomicLong droppedMessagesCount = new AtomicLong(0);

    protected void enqueue(Message message) {
        if (!queue4Sink.offer(message)) {
            droppedMessagesCount.incrementAndGet();
            DynamicCounter.increment(
                    MonitorConfig.builder(TagKey.DROPPED_COUNT)
                            .withTag("reason", "queueFull")
                            .withTag("sink", sinkId)
                            .build());
        }
    }

    @Override
    public void run() {
        isRunning = true;
        List msgList = new LinkedList<>();

        while (isRunning || !msgList.isEmpty()) {
            try {
                beforePolling();

                boolean full = (msgList.size() >= batchSize);
                boolean expired = false;

                if (!full) {
                    Message msg = queue4Sink.poll(
                            Math.max(0, lastBatch + batchTimeout - System.currentTimeMillis()),
                            TimeUnit.MILLISECONDS);
                    expired = (msg == null);
                    if (!expired) {
                        msgList.add(msg);
                        queue4Sink.drain(batchSize - msgList.size(), msgList);
                    }
                }
                full = (msgList.size() >= batchSize);
                if (expired || full) {
                    if (msgList.size() > 0) {
                        write(msgList);
                        msgList.clear();
                    }
                    lastBatch = System.currentTimeMillis();
                }
            } catch (Throwable e) {
                log.error("Thrown on running: " + e.getMessage(), e);
                droppedMessagesCount.addAndGet(msgList.size());
                DynamicCounter.increment(
                        MonitorConfig.builder(TagKey.DROPPED_COUNT)
                                .withTag("reason", "sinkException")
                                .withTag("sink", sinkId)
                                .build(),
                        msgList.size());
                msgList.clear(); // drop messages, otherwise, it will retry forever
            }
        }
        log.info("Shutdown request exit loop ..., queue.size at exit time: " + queue4Sink.size());
        try {
            while (!queue4Sink.isEmpty()) {
                if (queue4Sink.drain(batchSize, msgList) > 0) {
                    write(msgList);
                    msgList.clear();
                }
            }

            log.info("Shutdown request internalClose done ...");
        } catch (Exception e) {
            log.error("Exception on terminating: " + e.getMessage(), e);
        } finally {
            isStopped = true;
        }
    }

    public void close() {
        isRunning = false;
        log.info("Starting to close and set isRunning to false");
        do {
            try {
                Thread.sleep(500);
                isRunning = false;
            } catch (Exception ignored) {
                log.error("ignoring an exception on close");
            }
        } while (!isStopped);

        try {
            queue4Sink.close();
            innerClose();
        } catch (Exception e) {
            // ignore exceptions when closing
            log.error("Exception while closing: " + e.getMessage(), e);
        } finally {
            log.info("close finished");
        }
    }

    /**
     * Some preparation job before polling messages from the queue
     *
     * @throws IOException
     */
    abstract protected void beforePolling() throws IOException;

    /**
     * Actual writing to the sink
     *
     * @param msgList
     * @throws IOException
     */
    abstract protected void write(List msgList) throws IOException;

    /**
     * Actual close implementation
     *
     * @throws IOException
     */
    abstract protected void innerClose() throws IOException;

    @Monitor(name = "numOfPendingMessages", type = DataSourceType.GAUGE)
    public long getNumOfPendingMessages() {
        return queue4Sink.size();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy