/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.usergrid.count; import java.util.ArrayList; import java.util.Arrays; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.usergrid.count.common.Count; import com.yammer.metrics.Metrics; import com.yammer.metrics.core.Counter; import com.yammer.metrics.core.Timer; import com.yammer.metrics.core.TimerContext; /** * Base batcher implementation, handles concurrency and locking throughput throttling. * * @author zznate */ public abstract class AbstractBatcher implements Batcher { protected BatchSubmitter batchSubmitter; protected static final Logger logger = LoggerFactory.getLogger( AbstractBatcher.class ); private volatile Batch batch; private final AtomicLong opCount = new AtomicLong(); private final Timer addTimer = Metrics.newTimer( AbstractBatcher.class, "add_invocation", TimeUnit.MICROSECONDS, TimeUnit.SECONDS ); protected final Counter invocationCounter = Metrics.newCounter( AbstractBatcher.class, "batch_add_invocations" ); // TODO add batchCount, remove shouldSubmit, impl submit, change simpleBatcher to just be an extension protected int batchSize = 500; protected int batchIntervalSeconds = 10; private final AtomicLong batchSubmissionCount = new AtomicLong(); /** * Create our scheduler to fire our execution */ private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool( 1 ); /** * Set the batch interval in seconds * @param batchIntervalSeconds */ public void setBatchInterval(int batchIntervalSeconds){ this.batchIntervalSeconds = batchIntervalSeconds; } public void setBatchSize( int batchSize ) { this.batchSize = batchSize; } public void setBatchSubmitter( BatchSubmitter batchSubmitter ) { this.batchSubmitter = batchSubmitter; } /** * Individual {@link Count} for the same counter get rolled up, so we track the individual number of operations. * * @return the number of operation against this SimpleBatcher */ public long getOpCount() { return opCount.get(); } /** Add a count object to this batcher */ public void add( Count count ) throws CounterProcessingUnavailableException { invocationCounter.inc(); final TimerContext context = addTimer.time(); if ( batchSize == 1 ) { getBatch().addSerial( count ); } else { getBatch().add( count ); } context.stop(); } private Batch getBatch() { Batch active = batch; if ( active == null ) { synchronized ( this ) { active = batch; if ( active == null ) { batch = active = new Batch(); //now schedule our task for execution since we're creating a batch scheduler.scheduleWithFixedDelay( new BatchFlusher(), this.batchIntervalSeconds, this.batchIntervalSeconds, TimeUnit.SECONDS ); } } } //we want to flush, and we have no capacity left, perform a flush if ( batchSize > 1 && active.getCapacity() == 0 ) { synchronized ( this ) { if ( active.getCapacity() == 0 ) { active.flush(); } } } return active; } private void flush(){ synchronized(this) { getBatch().flush(); } } /** * Runnable that will flush the batch every 30 seconds */ private final class BatchFlusher implements Runnable { @Override public void run() { //explicitly flush the batch AbstractBatcher.this.flush(); } } public long getBatchSubmissionCount() { return batchSubmissionCount.get(); } class Batch { private BlockingQueue<Count> counts; private final AtomicInteger localCallCounter = new AtomicInteger(); Batch() { counts = new ArrayBlockingQueue<>( batchSize ); } int getCapacity() { return counts.remainingCapacity(); } void flush() { ArrayList<Count> flushed = new ArrayList<Count>( batchSize ); counts.drainTo( flushed ); batchSubmitter.submit( flushed ); batchSubmissionCount.incrementAndGet(); opCount.incrementAndGet(); localCallCounter.incrementAndGet(); } void add( Count count ) { try { counts.offer( count, 500, TimeUnit.MILLISECONDS ); } catch ( Exception ex ) { logger.error( "Unable to add count, dropping count {}", count, ex ); } } void addSerial( Count count ) { Future f = batchSubmitter.submit( Arrays.asList( count ) ); try { f.get(); } catch ( Exception ex ) { logger.error( "Unable to add count, dropping count {}", count, ex ); } batchSubmissionCount.incrementAndGet(); opCount.incrementAndGet(); localCallCounter.incrementAndGet(); } } }