Package com.netflix.logging.messaging

Source Code of com.netflix.logging.messaging.MessageBatcher$Collector

/*
* Copyright 2012 Netflix, Inc.
*
*    Licensed under the Apache License, Version 2.0 (the "License");
*    you may not use this file except in compliance with the License.
*    You may obtain a copy of the License at
*
*        http://www.apache.org/licenses/LICENSE-2.0
*
*    Unless required by applicable law or agreed to in writing, software
*    distributed under the License is distributed on an "AS IS" BASIS,
*    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*    See the License for the specific language governing permissions and
*    limitations under the License.
*/

package com.netflix.logging.messaging;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.netflix.blitz4j.BlitzConfig;
import com.netflix.blitz4j.LoggingConfiguration;
import com.netflix.config.DynamicBooleanProperty;
import com.netflix.config.DynamicPropertyFactory;
import com.netflix.servo.annotations.DataSourceType;
import com.netflix.servo.annotations.Monitor;
import com.netflix.servo.monitor.Counter;
import com.netflix.servo.monitor.Monitors;
import com.netflix.servo.monitor.Stopwatch;
import com.netflix.servo.monitor.Timer;

/**
* A general purpose batcher that combines messages into batches. Callers of
* process don't block. Configurable parameters control the number of messages
* that may be queued awaiting processing, the maximum size of a batch, the
* maximum time a message waits to be combined with others in a batch and the
* size of the pool of threads that process batches.
*
* <p>
* The implementation aims to avoid congestion, by working more efficiently as
* load increases. As messages arrive faster, the collector executes less code
* and batch sizes increase (up to the configured maximum). It should be more
* efficient to process a batch than to process the messages individually.
* </p>
*
* </p>
* The implementation works by adding the arriving messages to a queue. The collector
* thread takes messages from the queue and collects them into batches. When a
* batch is big enough or old enough, the collector passes it to the processor,
* which passes the batch to the target stream.
* </p>
*
* <p>
* The processor maintains a thread pool. If there's more work than threads, the
* collector participates in processing by default, and consequently stops
* collecting more batches.
* </p>
*
* @author Karthik Ranganathan
*/
public class MessageBatcher<T> {

    private static final BlitzConfig CONFIGURATION = LoggingConfiguration.getInstance().getConfiguration();

    private static final String DOT = ".";

    private static final String BATCHER_PREFIX = "batcher.";

    private static final String COLLECTOR_SUFFIX = ".collector";

    private boolean shouldCollectorShutdown;

    List<Object> batch;

    protected String name;

    protected BlockingQueue queue;

    protected int maxMessages;

    protected static long maxDelay; // in nsec

    protected Collector collector;

    protected ThreadPoolExecutor processor;

    protected MessageProcessor target = null;

    /**
     * The number of batches that are currently being processed by the target
     * stream.
     */
    protected final AtomicInteger concurrentBatches = new AtomicInteger(0);

    protected Timer queueSizeTracer;

    protected Timer batchSyncPutTracer;

    protected Timer threadSubmitTracer;

    protected Timer processTimeTracer;

    protected Timer avgBatchSizeTracer;

    protected Counter queueOverflowCounter;

    private volatile boolean isShutDown;

    private AtomicLong numberAdded = new AtomicLong();

    private AtomicLong numberDropped = new AtomicLong();

    private boolean blockingProperty;

    private boolean isCollectorPaused;

    private Counter processCount;
    public static final String POOL_MAX_THREADS = "maxThreads";
    public static final String POOL_MIN_THREADS = "minThreads";
    public static final String POOL_KEEP_ALIVE_TIME = "keepAliveTime";

   public MessageBatcher(String name, MessageProcessor target) {
        this.name = BATCHER_PREFIX + name;
        this.target = target;
        queue = new ArrayBlockingQueue<T>(CONFIGURATION.getBatcherQueueMaxMessages(this.name));
        setBatchMaxMessages(CONFIGURATION.getBatchSize(this.name));
        batch = new ArrayList<Object>(maxMessages);
        setBatchMaxDelay(CONFIGURATION
                .getBatcherMaxDelay(this.name));
        collector = new Collector(this, this.name + COLLECTOR_SUFFIX);
        // Immediate Executor creates a factory that uses daemon threads
        createProcessor(this.name);
        queueSizeTracer = Monitors.newTimer("queue_size");
        batchSyncPutTracer = Monitors.newTimer("waitTimeforBuffer");
        avgBatchSizeTracer = Monitors.newTimer("batch_size");
        processCount = Monitors.newCounter("messages_processed");

        threadSubmitTracer = Monitors.newTimer("thread_invocation_time");
        processTimeTracer = Monitors.newTimer("message_processTime");
        queueOverflowCounter = Monitors.newCounter("queue_overflow");
        blockingProperty = CONFIGURATION
                .shouldWaitWhenBatcherQueueNotEmpty(this.name);
        collector.setDaemon(true);
        collector.start();
        try {
            Monitors.registerObject(this.name, this);
        } catch (Throwable e) {
            if (CONFIGURATION
                    .shouldPrintLoggingErrors()) {
                e.printStackTrace();
            }
        }
    }

    /** Set the stream that will process each batch of messages. */
    public synchronized void setTarget(MessageProcessor target) {
        this.target = target;
    }

    /**
     * Set the maximum number of messages in a batch. Setting this to 1 will
     * prevent batching; that is, messages will be passed to
     * target.processMessage one at a time.
     */
    public synchronized void setBatchMaxMessages(int maxMessages) {
        this.maxMessages = maxMessages;
    }

    /**
     * Set the maximum time a message spends waiting to complete a full batch,
     * in seconds. This doesn't limit the time spent in the queue.
     */
    public synchronized void setBatchMaxDelay(double maxDelaySec) {
        maxDelay = (long) (maxDelaySec * 1000000000);
    }

    /**
     * Set the max threads for the processors
     * @param maxThreads - max threads that can be launched for processing
     */
    public void setProcessorMaxThreads(int maxThreads) {
        if (processor.getCorePoolSize() > maxThreads) {
            processor.setCorePoolSize(maxThreads);
        }
        processor.setMaximumPoolSize(maxThreads);
    }

    /**
     * Checks to see if there is space available in the queue
     *
     * @return - true, if available false otherwise
     */
    public boolean isSpaceAvailable() {
        return (queue.remainingCapacity() > 0);
    }

    /**
     * Processes the message sent to the batcher. This method just writes the
     * message to the queue and returns immediately. If the queue is full, the
     * messages are dropped immediately and corresponding counter is
     * incremented.
     *
     * @param message
     *            - The message to be processed
     * @return boolean - true if the message is queued for processing,false(this
     *         could happen if the queue is full) otherwise
     */
    public boolean process(T message) {
        // If this batcher has been shutdown, do not accept any more messages
        if (isShutDown) {
            return false;
        }
        try {
            queueSizeTracer.record(queue.size());
        } catch (Throwable ignored) {

        }
        if (!queue.offer(message)) {
            numberDropped.incrementAndGet();
            queueOverflowCounter.increment();
            return false;
        }
        numberAdded.incrementAndGet();
        return true;
    }

    /**
     * Processes the message sent to the batcher. This method tries to write to
     * the queue. If the queue is full, the send blocks and waits for the
     * available space.
     *
     * @param message
     *            - The message to be processed
     */
    public void processSync(T message) {
        // If this batcher has been shutdown, do not accept any more messages
        if (isShutDown) {
            return;
        }

        try {
            queueSizeTracer.record(queue.size());
        } catch (Throwable ignored) {
        }

        try {
            Stopwatch s = batchSyncPutTracer.start();
            queue.put(message);
            s.stop();
        } catch (InterruptedException e) {
            return;
        }
        numberAdded.incrementAndGet();
    }

    /**
     * Processes the messages sent to the batcher. This method just writes the
     * message to the queue and returns immediately. If the queue is full, the
     * messages are dropped immediately and corresponding counter is
     * incremented.
     *
     * @param message
     *            - The messages to be processed
     */
    public void process(List<T> objects) {
        for (T message : objects) {
            // If this batcher has been shutdown, do not accept any more
            // messages
            if (isShutDown) {
                return;
            }
            process(message);
        }
    }

    /**
     * Processes the messages sent to the batcher. The messages are first queued
     * and then will be processed by the
     * {@link com.netflix.logging.messaging.MessageProcessor}
     *
     * @param message
     *            - The messages to be processed
     * @param sync
     *            - if true, waits for the queue to make space, if false returns
     *            immediately after dropping the message
     */
    public void process(List<T> objects, boolean sync) {
        for (T message : objects) {
            // If this batcher has been shutdown, do not accept any more
            // messages
            if (isShutDown) {
                return;
            }
            if (sync) {
                processSync(message);
            } else {
                process(message);
            }
        }
    }

    /**
     * Pause the collector. The collector stops picking up messages from the
     * queue.
     */
    public void pause() {
        if (!isShutDown) {
            this.isCollectorPaused = true;
        }
    }

    public boolean isPaused() {
        return this.isCollectorPaused;
    }

    /**
     * Resume the collector. The collector resumes picking up messages from the
     * queue and calling the processors.
     */
    public void resume() {
        if (!isShutDown) {
            this.isCollectorPaused = false;
        }
    }

    /**
     * Stops the batcher. The Batcher has to wait for the other processes like
     * the Collector and the Executor to complete. It waits until it is notified
     * that the other processes have completed gracefully. The collector waits
     * until there are no more messages in the queue(tries 3 times waiting for
     * 0.5 seconds each) and then shuts down gracefully.
     *
     *
     */
    public void stop() {
        /*
         * Sets the shutdown flag. Future sends to the batcher are not accepted.
         * The processors wait for the current messages in the queue and with
         * the processor or collector to complete
         */
        isShutDown = true;
       
        int waitTimeinMillis = CONFIGURATION.getBatcherWaitTimeBeforeShutdown(this.name);
        long timeToWait = waitTimeinMillis + System.currentTimeMillis();
        while ((queue.size() > 0 || batch.size() > 0)
                && (System.currentTimeMillis() < timeToWait)) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                break;
            }
        }
        try {
            shouldCollectorShutdown = true;
            processor.shutdownNow();
            /*
             * processor.awaitTermination(10000, TimeUnit.SECONDS); if
             * (!processor.isShutdown()) { processor.shutdownNow(); }
             */
        } catch (Throwable e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * The class that processes the messages in a batch by calling the
     * implementor of the MessageProcessor interface.
     *
     *
     */
    private static class ProcessMessages implements Runnable {
        public ProcessMessages(MessageBatcher stream, List batch) {
            this.stream = stream;
            this.batch = batch;
            this.processMessagesTracer = stream.processTimeTracer;
            this.avgConcurrentBatches = Monitors.newTimer(stream.name
                    + ".concurrentBatches");
        }

        private final MessageBatcher stream;

        private List batch;

        private Timer processMessagesTracer;
        private Timer avgConcurrentBatches;

        /** Process the batch. */
        public void run() {
            try {
                if (batch == null) {
                    return;
                }
                int inProcess = stream.concurrentBatches.incrementAndGet();
                try {
                    avgConcurrentBatches.record(inProcess);

                    Stopwatch s = processMessagesTracer.start();
                    stream.target.process(batch);
                    s.stop();
                } finally {
                    stream.concurrentBatches.decrementAndGet();
                }
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }
    }

    private class Collector extends Thread {
        private static final int SLEEP_TIME_MS = 1;
        private Timer processTimeTracer;
        private Counter rejectedCounter = Monitors.newCounter(processCount
                + ".rejected");
        private static final int RETRY_EXECUTION_TIMEOUT_MS = 1;

        public Collector(MessageBatcher stream, String name) {
            super(name);
            processTimeTracer = Monitors.newTimer(name + ".processTime");
            this.stream = stream;
            queueSizeTracer = Monitors.newTimer(name
                    + ".queue_size_at_drain");
        }

        private final MessageBatcher stream;

        private final Timer queueSizeTracer;

        /** Process messages from the queue, after grouping them into batches. */
        public void run() {

            int batchSize = 0;
            while (!shouldCollectorShutdown) {
                if (isCollectorPaused) {
                    try {
                        Thread.sleep(SLEEP_TIME_MS);
                    } catch (InterruptedException ignore) {
                    }
                    continue;
                }
                try {
                    if (batch.size() < stream.maxMessages) {
                        long now = System.nanoTime();
                        final long firstTime = now;
                        do {
                            if (stream.queue.drainTo(batch, stream.maxMessages
                                    - batch.size()) <= 0) {
                                long maxWait = firstTime + stream.maxDelay
                                        - now;
                                if (maxWait <= 0) { // timed out
                                    break;
                                }
                                // Wait for a message to arrive:
                                Object nextMessage = null;
                                try {
                                    nextMessage = stream.queue.poll(maxWait,
                                            TimeUnit.NANOSECONDS);
                                } catch (InterruptedException ignore) {
                                }
                                if (nextMessage == null) { // timed out
                                    break;
                                }
                                batch.add(nextMessage);
                                now = System.nanoTime();
                            }
                        } while (batch.size() < stream.maxMessages);
                    }
                    batchSize = batch.size();
                    if (batchSize > 0) {
                        try {
                            queueSizeTracer.record(stream.queue.size());
                        } catch (Exception ignored) {
                        }
                        avgBatchSizeTracer.record(batchSize);
                        Stopwatch s = processTimeTracer.start();
                        boolean retryExecution = false;
                        do {
                            try {
                                stream.processor.execute(new ProcessMessages(
                                        stream, batch));
                                retryExecution = false;
                            } catch (RejectedExecutionException re) {
                                rejectedCounter.increment();
                                retryExecution = true;
                                Thread.sleep(RETRY_EXECUTION_TIMEOUT_MS);
                            }
                        } while (retryExecution);
                        processCount.increment(batchSize);
                        s.stop();
                        batch = new ArrayList(stream.maxMessages);
                    }
                } catch (Throwable e) {
                    if (CONFIGURATION.shouldPrintLoggingErrors()) {
                    e.printStackTrace();
                    }
                }
            } // - while (!shutdownCollector)
        } // - run()

    }

    /**
     * The size of the the queue in which the messages are batches
     *
     * @return- size of the queue
     */
    @Monitor(name = "batcherQueueSize", type = DataSourceType.GAUGE)
    public int getSize() {
        if (queue != null) {
            return queue.size();
        } else {
            return 0;
        }
    }

    /**
     * Resets the statistics that keeps the count of number of messages added to
     * this batcher.
     */
    public void resetNumberAdded() {
        numberAdded.set(0);
    }

    /**
     * Resets the statistics that keeps the count of number of messages dropped
     * by this batcher.
     */

    public void resetNumberDropped() {
        numberDropped.set(0);
    }

    /**
     * Gets the statistics count of number of messages added to this batcher.
     */
    @Monitor(name = "numberAdded", type = DataSourceType.GAUGE)
    public long getNumberAdded() {
        return numberAdded.get();
    }

    /**
     * Gets the statistics count of number of messages dropped by this batcher.
     */
    @Monitor(name = "numberDropped", type = DataSourceType.GAUGE)
    public long getNumberDropped() {
        return numberDropped.get();
    }

    /**
     * Gets the information whether the batcher is blocking or not blocking. By
     * default, the batcher is non-blocking and the messages are just dropped if
     * the queue is full.
     *
     * If the batcher is made blocking, the sends block and wait indefinitely
     * until space is made in the batcher.
     *
     * @return - true if blocking, false otherwise
     */
    @Monitor(name = "blocking", type = DataSourceType.INFORMATIONAL)
    public boolean isBlocking() {
        return blockingProperty;
    }


    private void createProcessor(String name) {
        int minThreads = CONFIGURATION
                .getBatcherMinThreads(this.name);
        int maxThreads = CONFIGURATION
                .getBatcherMaxThreads(this.name);
        int keepAliveTime = CONFIGURATION.getBatcherThreadKeepAliveTime(this.name);

        ThreadFactory threadFactory = new ThreadFactoryBuilder()
                .setDaemon(true).setNameFormat(this.name + "-process").build();

        this.processor = new ThreadPoolExecutor(minThreads, maxThreads,
                keepAliveTime, TimeUnit.SECONDS, new SynchronousQueue(),
                threadFactory);
        boolean shouldRejectWhenFull = CONFIGURATION
                .shouldRejectWhenAllBatcherThreadsUsed(this.name);
        if (!shouldRejectWhenFull) {
            this.processor
                    .setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy() {
                        @Override
                        public void rejectedExecution(Runnable r,
                                ThreadPoolExecutor e) {
                            super.rejectedExecution(r, e);
                        }
                    });
        }

    }

}
TOP

Related Classes of com.netflix.logging.messaging.MessageBatcher$Collector

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.