/* * (C) 2007-2012 Alibaba Group Holding Limited. * * 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. * Authors: * dogun (yuexuqiang at gmail.com) */ package com.taobao.common.store.journal; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import com.taobao.common.store.journal.JournalStore.InflyWriteData; import com.taobao.common.store.util.BytesKey; public class DataFileAppender { private volatile boolean shutdown = false; private boolean running = false; private Thread thread; private final Lock enqueueLock = new ReentrantLock(); private final Condition notEmpty = this.enqueueLock.newCondition(); private final Condition empty = this.enqueueLock.newCondition(); protected int maxWriteBatchSize; protected final Map<BytesKey, InflyWriteData> inflyWrites = new ConcurrentHashMap<BytesKey, InflyWriteData>(256); private WriteBatch nextWriteBatch; private final JournalStore journal; public DataFileAppender(final JournalStore journalStore) { this.maxWriteBatchSize = journalStore.maxWriteBatchSize; this.journal = journalStore; } public OpItem remove(final OpItem opItem, final BytesKey bytesKey, final boolean sync) throws IOException { if (this.shutdown) { throw new RuntimeException("DataFileAppender�Ѿ��ر�"); } // sync = true; final WriteCommand writeCommand = new WriteCommand(bytesKey, opItem, null, sync); return this.enqueueTryWait(opItem, sync, writeCommand); } public OpItem store(final OpItem opItem, final BytesKey bytesKey, final byte[] data, final boolean sync) throws IOException { if (this.shutdown) { throw new RuntimeException("DataFileAppender�Ѿ��ر�"); } opItem.key = bytesKey.getData(); opItem.length = data.length; final WriteCommand writeCommand = new WriteCommand(bytesKey, opItem, data, sync); return this.enqueueTryWait(opItem, sync, writeCommand); } private OpItem enqueueTryWait(final OpItem opItem, final boolean sync, final WriteCommand writeCommand) throws IOException { final WriteBatch batch = this.enqueue(writeCommand, sync); if (sync) { try { batch.latch.await(); } catch (final InterruptedException e) { Thread.currentThread().interrupt(); } final IOException exception = batch.exception; if (exception != null) { throw exception; } } return opItem; } public void close() { this.enqueueLock.lock(); try { if (!this.shutdown) { this.shutdown = true; this.running = false; this.empty.signalAll(); } } finally { this.enqueueLock.unlock(); } while (this.thread.isAlive()) { try { this.thread.join(); } catch (final InterruptedException e) { Thread.currentThread().interrupt(); } } } public void processQueue() { while (true) { WriteBatch batch = null; this.enqueueLock.lock(); try { while (true) { if (this.nextWriteBatch != null) { batch = this.nextWriteBatch; this.nextWriteBatch = null; break; } if (this.shutdown) { return; } try { this.empty.await(); } catch (final InterruptedException e) { break; } } this.notEmpty.signalAll(); } finally { this.enqueueLock.unlock(); } if (batch != null) { final DataFile dataFile = batch.dataFile; final LogFile logFile = batch.logFile; final List<WriteCommand> cmdList = batch.cmdList; try { this.writeDataAndLog(batch, dataFile, logFile, cmdList); this.processRemove(batch, dataFile, logFile); } finally { batch.latch.countDown(); } } } } private void processRemove(final WriteBatch batch, final DataFile df, final LogFile lf) { if (df != null && lf != null) { df.decrement(batch.removeOPCount); this.enqueueLock.lock(); try { if (df.getLength() >= JournalStore.FILE_SIZE && df.isUnUsed()) { if (this.journal.dataFile == df) { // �ж�����ǵ�ǰ�ļ��������µ� this.journal.newDataFile(); } this.journal.dataFiles.remove(Integer.valueOf(df.getNumber())); this.journal.logFiles.remove(Integer.valueOf(df.getNumber())); // System.out.println("delete " + df.getNumber()); // System.out.println(batch.cmdList.get(0).opItem); df.delete(); lf.delete(); } } catch (final Exception e) { if (e instanceof IOException) { batch.exception = (IOException) e; } else { batch.exception = new IOException(e); } } finally { this.enqueueLock.unlock(); } } } public byte[] getDataFromInFlyWrites(final BytesKey key) { final InflyWriteData inflyWriteData = this.inflyWrites.get(key); if (inflyWriteData != null && inflyWriteData.count > 0) { return inflyWriteData.data; } else { return null; } } private void writeDataAndLog(final WriteBatch batch, final DataFile dataFile, final LogFile logFile, final List<WriteCommand> dataList) { ByteBuffer dataBuf = null; // Contains op add if (batch.dataSize > 0) { dataBuf = ByteBuffer.allocate(batch.dataSize); } final ByteBuffer opBuf = ByteBuffer.allocate(dataList.size() * OpItem.LENGTH); for (final WriteCommand cmd : dataList) { opBuf.put(cmd.opItem.toByte()); if (cmd.opItem.op == OpItem.OP_ADD) { dataBuf.put(cmd.data); } } if (dataBuf != null) { dataBuf.flip(); } opBuf.flip(); try { if (dataBuf != null) { dataFile.write(dataBuf); } logFile.write(opBuf); } catch (final IOException e) { batch.exception = e; } this.enqueueLock.lock(); try { // ��ͬ������inflyWrites�������Ƴ� for (final WriteCommand cmd : dataList) { if (!cmd.sync && cmd.opItem.op == OpItem.OP_ADD) { final InflyWriteData inflyWriteData = this.inflyWrites.get(cmd.bytesKey); if (inflyWriteData != null) { // decrease reference count inflyWriteData.count--; // remove it if there is no reference if (inflyWriteData.count <= 0) { this.inflyWrites.remove(cmd.bytesKey); } } } } } finally { this.enqueueLock.unlock(); } } final Condition notSync = this.enqueueLock.newCondition(); public void sync() { this.enqueueLock.lock(); try { while (this.nextWriteBatch != null) { try { this.notEmpty.await(); } catch (final InterruptedException e) { Thread.currentThread().interrupt(); } } for (final DataFile df : this.journal.dataFiles.values()) { try { df.sync(this.notSync); } catch (final Exception e) { } } } finally { this.enqueueLock.unlock(); } } private WriteBatch enqueue(final WriteCommand writeCommand, final boolean sync) throws IOException { WriteBatch result = null; this.enqueueLock.lock(); try { // ���û����������������appender�߳� this.startAppendThreadIfNessary(); if (this.nextWriteBatch == null) { result = this.newWriteBatch(writeCommand); this.empty.signalAll(); } else { if (this.nextWriteBatch.canAppend(writeCommand)) { this.nextWriteBatch.append(writeCommand); result = this.nextWriteBatch; } else { while (this.nextWriteBatch != null) { try { this.notEmpty.await(); } catch (final InterruptedException e) { Thread.currentThread().interrupt(); } } result = this.newWriteBatch(writeCommand); this.empty.signalAll(); } } if (!sync) { final InflyWriteData inflyWriteData = this.inflyWrites.get(writeCommand.bytesKey); switch (writeCommand.opItem.op) { case OpItem.OP_ADD: if (inflyWriteData == null) { this.inflyWrites.put(writeCommand.bytesKey, new InflyWriteData(writeCommand.data)); } else { // update and increase reference count; inflyWriteData.data = writeCommand.data; inflyWriteData.count++; } break; case OpItem.OP_DEL: // ������ɾ�� if (inflyWriteData != null) { this.inflyWrites.remove(writeCommand.bytesKey); } } } return result; } finally { this.enqueueLock.unlock(); } } private WriteBatch newWriteBatch(final WriteCommand writeCommand) throws IOException { WriteBatch result = null; // ����offset��number if (writeCommand.opItem.op == OpItem.OP_ADD) { // Ԥ���ж�һ�� if (this.journal.indices.containsKey(writeCommand.bytesKey)) { throw new IOException("�����ظ���key"); } final DataFile dataFile = this.getDataFile(); writeCommand.opItem.offset = dataFile.position(); writeCommand.opItem.number = dataFile.getNumber(); // �ƶ�dataFileָ�� dataFile.forward(writeCommand.data.length); this.nextWriteBatch = new WriteBatch(writeCommand, dataFile, this.journal.logFile); result = this.nextWriteBatch; } else { final DataFile dataFile = this.journal.dataFiles.get(writeCommand.opItem.number); final LogFile logFile = this.journal.logFiles.get(writeCommand.opItem.number); if (dataFile != null && logFile != null) { this.nextWriteBatch = new WriteBatch(writeCommand, dataFile, logFile); result = this.nextWriteBatch; } else { // System.out.println(this.journal.dataFiles); throw new IOException("��־�ļ��Ѿ���ɾ�������Ϊ" + writeCommand.opItem.number); } } return result; } private void startAppendThreadIfNessary() { if (!this.running) { this.running = true; this.thread = new Thread() { @Override public void run() { DataFileAppender.this.processQueue(); } }; this.thread.setPriority(Thread.MAX_PRIORITY); this.thread.setDaemon(true); this.thread.setName("Store4j file appender"); this.thread.start(); } } private DataFile getDataFile() throws IOException { DataFile dataFile = this.journal.dataFile; if (dataFile.getLength() >= JournalStore.FILE_SIZE) { // ���� dataFile = this.journal.newDataFile(); } return dataFile; } private class WriteCommand { final BytesKey bytesKey; final OpItem opItem; final byte[] data; final boolean sync; public WriteCommand(final BytesKey bytesKey, final OpItem opItem, final byte[] data, final boolean sync) { super(); this.bytesKey = bytesKey; this.opItem = opItem; this.data = data; this.sync = sync; } @Override public String toString() { return this.opItem.toString(); } } /** * һ������д���¼ * * @author dennis * */ private class WriteBatch { final CountDownLatch latch = new CountDownLatch(1); final List<WriteCommand> cmdList = new ArrayList<WriteCommand>(); // ɾ�������ĸ��� int removeOPCount; final DataFile dataFile; final LogFile logFile; // д���data��С int dataSize; // DataFileд������ long offset = -1; volatile IOException exception; // д���ļ��ı�� final int number; public WriteBatch(final WriteCommand writeCommand, final DataFile dataFile, final LogFile logFile) { super(); this.dataFile = dataFile; this.number = writeCommand.opItem.number; this.logFile = logFile; switch (writeCommand.opItem.op) { case OpItem.OP_DEL: this.removeOPCount++; break; case OpItem.OP_ADD: this.offset = writeCommand.opItem.offset; this.dataSize += writeCommand.data.length; this.dataFile.increment(); break; default: throw new RuntimeException("Unknow op type " + writeCommand.opItem); } this.cmdList.add(writeCommand); } public boolean canAppend(final WriteCommand command) throws IOException { switch (command.opItem.op) { case OpItem.OP_DEL: // ɾ�����ڱ���batch���ļ��ϣ����ɺϲ� if (command.opItem.number != this.number) { return false; } break; case OpItem.OP_ADD: // �ļ�����̫�� if (this.dataFile.getLength() + command.data.length >= JournalStore.FILE_SIZE) { return false; } // һ��batch����̫�� if (this.dataSize + command.data.length >= DataFileAppender.this.maxWriteBatchSize) { return false; } break; default: throw new RuntimeException("Unknow op type " + command.opItem); } return true; } public void append(final WriteCommand writeCommand) throws IOException { switch (writeCommand.opItem.op) { case OpItem.OP_ADD: // ��Ӳ�����Ҫ�趨offset // 1����һ��op��remove����� if (this.offset == -1) { this.offset = this.dataFile.position(); writeCommand.opItem.offset = this.dataFile.position(); writeCommand.opItem.number = this.dataFile.getNumber(); this.dataFile.forward(writeCommand.data.length); this.dataSize += writeCommand.data.length; } else { writeCommand.opItem.offset = this.offset + this.dataSize; writeCommand.opItem.number = this.dataFile.getNumber(); this.dataFile.forward(writeCommand.data.length); this.dataSize += writeCommand.data.length; } this.dataFile.increment(); break; case OpItem.OP_DEL: this.removeOPCount++; break; default: throw new RuntimeException("Unknow op type " + writeCommand.opItem); } this.cmdList.add(writeCommand); } } }