/* * 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.flume.channel; import java.util.Map; import org.apache.flume.Context; import org.apache.flume.Event; import org.apache.flume.annotations.Disposable; import org.apache.flume.annotations.InterfaceAudience; import org.apache.flume.annotations.InterfaceStability; import org.apache.flume.channel.file.FileChannel; import org.apache.flume.conf.Configurables; import org.apache.flume.instrumentation.ChannelCounter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicBoolean; /** * <p> * DualChannel is the mixed channel of MemoryChannel and FileChannel. * Important: @InterfaceAudience.Private and @Disposable is from FileChannel, * espically Disposable decided DualChannel created or not when reconfiguration. * </p> */ @InterfaceAudience.Private @InterfaceStability.Stable @Disposable public class DualChannel extends BasicChannelSemantics { private static Logger LOG = LoggerFactory.getLogger(DualChannel.class); /*** * putToMemChannel indicate put event to memChannel or fileChannel * takeFromMemChannel indicate take event from memChannel or fileChannel * */ private AtomicBoolean putToMemChannel = new AtomicBoolean(true); private AtomicBoolean takeFromMemChannel = new AtomicBoolean(true); private MemoryChannel memChannel = new MemoryChannel(); private final ThreadLocal<BasicTransactionSemantics> memTransactions = new ThreadLocal<BasicTransactionSemantics>(); private FileChannel fileChannel = new FileChannel(); private final ThreadLocal<BasicTransactionSemantics> fileTransactions = new ThreadLocal<BasicTransactionSemantics>(); private AtomicLong handleEventCount = new AtomicLong(); private AtomicLong memHandleEventCount = new AtomicLong(); private AtomicLong fileHandleEventCount = new AtomicLong(); private ChannelCounter channelCounter; private boolean switchon = true; public DualChannel() { super(); } /** * Read parameters from context * <li>capacity = type long that defines the total number of events allowed at one time in the queue. * <li>transactionCapacity = type long that defines the total number of events allowed in one transaction. * <li>byteCapacity = type long that defines the max number of bytes used for events in the queue. * <li>byteCapacityBufferPercentage = type int that defines the percent of buffer between byteCapacity and the estimated event size. * <li>keep-alive = type int that defines the number of second to wait for a queue permit */ @Override public void configure(Context context) { if (channelCounter == null) { channelCounter = new ChannelCounter(getName()); } this.switchon = context.getBoolean("switchon", true); if(! switchon){ LOG.info("DualChannel switchon set off, will always put events to file channel"); } memChannel.setName(getName() + "-memory"); //configure mc try { Map<String, String> memoryParams = context.getSubProperties("memory."); Context memContext = new Context(); memContext.putAll(memoryParams); Configurables.configure(memChannel, memContext); } catch (Exception e) { String msg = String.format("DualChannel %s configure memChannel, an " + "error during configuration", getName()); LOG.error(msg, e); } fileChannel.setName(getName() + "-file"); //configure fc try { Map<String, String> fileParams = context.getSubProperties("file."); Context fileContext = new Context(); fileContext.putAll(fileParams); Configurables.configure(fileChannel, fileContext); } catch (Exception e) { String msg = String.format("DualChannel %s configure fileChannel, an " + "error during configuration", getName()); LOG.error(msg, e); } } @Override public synchronized void start() { channelCounter.start(); //channelCounter.setChannelSize(queue.size()); //channelCounter.setChannelCapacity(Long.valueOf( // queue.size() + queue.remainingCapacity())); super.start(); memChannel.start(); fileChannel.start(); } @Override public synchronized void stop() { //channelCounter.setChannelSize(queue.size()); //channelCounter.stop(); memChannel.stop(); fileChannel.stop(); super.stop(); } @Override protected BasicTransactionSemantics createTransaction() { //create MemoryTransaction BasicTransactionSemantics curMemTransaction = memTransactions.get(); if (curMemTransaction == null || curMemTransaction.getState().equals( BasicTransactionSemantics.State.CLOSED)) { curMemTransaction = memChannel.createTransaction(); memTransactions.set(curMemTransaction); } //create FileTransaction BasicTransactionSemantics curfileTransaction = fileTransactions.get(); if (curfileTransaction == null || curfileTransaction.getState().equals( BasicTransactionSemantics.State.CLOSED)) { curfileTransaction = fileChannel.createTransaction(); fileTransactions.set(curfileTransaction); } return new DualTransaction(curMemTransaction, curfileTransaction, channelCounter); } private class DualTransaction extends BasicTransactionSemantics { private final BasicTransactionSemantics memTransaction; private final BasicTransactionSemantics fileTransaction; private final ChannelCounter channelCounter; public DualTransaction(BasicTransactionSemantics mt, BasicTransactionSemantics ft, ChannelCounter counter) { memTransaction = mt; fileTransaction = ft; channelCounter = counter; } @Override protected void doBegin() throws InterruptedException { memTransaction.begin(); fileTransaction.begin(); super.doBegin(); } @Override protected void doPut(Event event) throws InterruptedException { channelCounter.incrementEventPutAttemptCount(); handleEventCount.incrementAndGet(); if (switchon && putToMemChannel.get()) { memHandleEventCount.incrementAndGet(); memTransaction.put(event); /** * check whether memChannel queueRemaining to 30%, or fileChannel has event? * if true, change to fileChannel next event. * */ if ( memChannel.isFull() || fileChannel.getQueueSize() > 100) { putToMemChannel.set(false); } } else { fileHandleEventCount.incrementAndGet(); fileTransaction.put(event); } } @Override protected Event doTake() throws InterruptedException { channelCounter.incrementEventTakeAttemptCount(); Event event = null; if ( takeFromMemChannel.get() ) { event = memTransaction.take(); if (event == null) { takeFromMemChannel.set(false); } } else { event = fileTransaction.take(); if (event == null) { takeFromMemChannel.set(true); putToMemChannel.set(true); } } return event; } @Override protected void doCommit() throws InterruptedException { memTransaction.commit(); fileTransaction.commit(); //print stat information if (handleEventCount.get() >= 50000) { String msg = String.format("DualChannel-STAT name[%s] " + "totalEvent[%d] memEvent[%d] fileEvent[%d] " + "memQueueSize[%d] fileQueueSize[%d]", getName(), handleEventCount.get(), memHandleEventCount.get(), fileHandleEventCount.get(), memChannel.getQueueSize(), fileChannel.getQueueSize()); LOG.info(msg); handleEventCount.set(0); memHandleEventCount.set(0); fileHandleEventCount.set(0); } } @Override protected void doRollback() throws InterruptedException { memTransaction.rollback(); fileTransaction.rollback(); } @Override protected void doClose() { memTransaction.close(); fileTransaction.close(); super.doClose(); } } }