/** * $Id: TransactionStateService.java 1831 2013-05-16 01:39:51Z shijia.wxr $ */ package com.alibaba.rocketmq.store.transaction; import java.nio.ByteBuffer; import java.util.Iterator; import java.util.List; import java.util.TreeSet; import java.util.concurrent.atomic.AtomicLong; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.rocketmq.common.Message; import com.alibaba.rocketmq.common.MessageExt; import com.alibaba.rocketmq.common.MixAll; import com.alibaba.rocketmq.common.sysflag.MessageSysFlag; import com.alibaba.rocketmq.store.ConsumeQueue; import com.alibaba.rocketmq.store.DefaultMessageStore; import com.alibaba.rocketmq.store.MapedFile; import com.alibaba.rocketmq.store.MapedFileQueue; import com.alibaba.rocketmq.store.SelectMapedBufferResult; /** * ������񣬴洢ÿ�������״̬��Prepared��Commited��Rollbacked��<br> * ���ʽ��ͣ�<br> * clOffset - Commit Log Offset<br> * tsOffset - Transaction State Table Offset * * @author vintage.wang@gmail.com shijia.wxr@taobao.com * */ public class TransactionStateService { private static final Logger log = LoggerFactory.getLogger(MixAll.StoreLoggerName); // �洢��Ԫ��С public static final int TSStoreUnitSize = 24; // �����ָ�����״̬���redolog public static final String TRANSACTION_REDOLOG_TOPIC = "TRANSACTION_REDOLOG_TOPIC_XXXX"; public static final int TRANSACTION_REDOLOG_TOPIC_QUEUEID = 0; // �洢������� private final DefaultMessageStore defaultMessageStore; // �洢����״̬�ı�� private MapedFileQueue tranStateTable; // �ظ������ڴ�Buffer private final ByteBuffer byteBufferAppend = ByteBuffer.allocate(TSStoreUnitSize); // ����״̬��Redolog������������崵�����ͨ��redolog�ָ����������״̬ // Redolog��ʵ�����������Ѷ��У���ҪΪ�˻ָ����� private final ConsumeQueue tranRedoLog; public final static long PreparedMessageTagsCode = -1; // ��������״̬���������λ�� private final static int TS_STATE_POS = 20; // State Table Offset������ʱ��������� private final AtomicLong tranStateTableOffset = new AtomicLong(0); public TransactionStateService(final DefaultMessageStore defaultMessageStore) { this.defaultMessageStore = defaultMessageStore; this.tranStateTable = new MapedFileQueue(defaultMessageStore.getMessageStoreConfig().getTranStateTableStorePath(), defaultMessageStore.getMessageStoreConfig().getTranStateTableMapedFileSize(), defaultMessageStore.getAllocateMapedFileService()); this.tranRedoLog = new ConsumeQueue(// TRANSACTION_REDOLOG_TOPIC,// TRANSACTION_REDOLOG_TOPIC_QUEUEID,// defaultMessageStore.getMessageStoreConfig().getTranRedoLogStorePath(),// defaultMessageStore.getMessageStoreConfig().getTranRedoLogMapedFileSize(),// defaultMessageStore); } public boolean load() { boolean result = this.tranRedoLog.load(); result = result && this.tranStateTable.load(); return result; } public int deleteExpiredStateFile(long offset) { int cnt = this.tranStateTable.deleteExpiredFileByOffset(offset, TSStoreUnitSize); return cnt; } public void recoverStateTable(final boolean lastExitOK) { if (lastExitOK) { this.recoverStateTableNormal(); } else { // ��һ����ɾ��State Table this.tranStateTable.destroy(); // �ڶ�����ͨ��RedoLogȫ���ָ�StateTable this.recreateStateTable(); } } private void recreateStateTable() { this.tranStateTable = new MapedFileQueue(defaultMessageStore.getMessageStoreConfig().getTranStateTableStorePath(), defaultMessageStore.getMessageStoreConfig().getTranStateTableMapedFileSize(), defaultMessageStore.getAllocateMapedFileService()); final TreeSet<Long> preparedItemSet = new TreeSet<Long>(); // ��һ������ͷɨ��RedoLog final long minOffset = this.tranRedoLog.getMinOffsetInQuque(); long processOffset = minOffset; while (true) { SelectMapedBufferResult bufferConsumeQueue = this.tranRedoLog.getIndexBuffer(processOffset); if (bufferConsumeQueue != null) { try { long i = 0; for (; i < bufferConsumeQueue.getSize(); i += ConsumeQueue.CQStoreUnitSize) { long offsetMsg = bufferConsumeQueue.getByteBuffer().getLong(); int sizeMsg = bufferConsumeQueue.getByteBuffer().getInt(); long tagsCode = bufferConsumeQueue.getByteBuffer().getLong(); // Prepared if (TransactionStateService.PreparedMessageTagsCode == tagsCode) { preparedItemSet.add(offsetMsg); } // Commit/Rollback else { preparedItemSet.remove(tagsCode); } } processOffset += i; } finally { // �����ͷ���Դ bufferConsumeQueue.release(); } } else { break; } } log.info("scan transaction redolog over, End offset: {}, Prepared Transaction Count: {}", processOffset, preparedItemSet.size()); // �ڶ������ؽ�StateTable Iterator<Long> it = preparedItemSet.iterator(); while (it.hasNext()) { Long offset = it.next(); MessageExt msgExt = this.defaultMessageStore.lookMessageByOffset(offset); if (msgExt != null) { this.appendPreparedTransaction(msgExt.getCommitLogOffset(), msgExt.getStoreSize(), (int) (msgExt .getStoreTimestamp() / 1000), msgExt.getProperty(Message.PROPERTY_PRODUCER_GROUP).hashCode()); } } } private void recoverStateTableNormal() { final List<MapedFile> mapedFiles = this.tranStateTable.getMapedFiles(); if (!mapedFiles.isEmpty()) { // �ӵ����������ļ���ʼ�ָ� int index = mapedFiles.size() - 3; if (index < 0) index = 0; int mapedFileSizeLogics = this.tranStateTable.getMapedFileSize(); MapedFile mapedFile = mapedFiles.get(index); ByteBuffer byteBuffer = mapedFile.sliceByteBuffer(); long processOffset = mapedFile.getFileFromOffset(); long mapedFileOffset = 0; while (true) { for (int i = 0; i < mapedFileSizeLogics; i += TSStoreUnitSize) { long offset = byteBuffer.getLong(); int size = byteBuffer.getInt(); long tagsCode = byteBuffer.getLong(); // ˵����ǰ�洢��Ԫ��Ч // TODO �����ж���Ч�Ƿ���� if (offset >= 0 && size > 0) { mapedFileOffset = i + TSStoreUnitSize; } else { log.info("recover current logics file over, " + mapedFile.getFileName() + " " + offset + " " + size + " " + tagsCode); break; } } // �ߵ��ļ�ĩβ���л�����һ���ļ� if (mapedFileOffset == mapedFileSizeLogics) { index++; if (index >= mapedFiles.size()) { // ��ǰ������֧�����ܷ��� log.info("recover last logics file over, last maped file " + mapedFile.getFileName()); break; } else { mapedFile = mapedFiles.get(index); byteBuffer = mapedFile.sliceByteBuffer(); processOffset = mapedFile.getFileFromOffset(); mapedFileOffset = 0; log.info("recover next logics file, " + mapedFile.getFileName()); } } else { log.info("recover current logics queue over " + mapedFile.getFileName() + " " + (processOffset + mapedFileOffset)); break; } } processOffset += mapedFileOffset; this.tranStateTable.truncateDirtyFiles(processOffset); } } /** * ���̵߳��� */ public boolean appendPreparedTransaction(// final long clOffset,// final int size,// final int timestamp,// final int groupHashCode// ) { MapedFile mapedFile = this.tranStateTable.getLastMapedFile(); if (null == mapedFile) { log.error("appendPreparedTransaction: create mapedfile error."); return false; } this.byteBufferAppend.position(0); this.byteBufferAppend.limit(TSStoreUnitSize); // Commit Log Offset this.byteBufferAppend.putLong(clOffset); // Message Size this.byteBufferAppend.putInt(size); // Timestamp this.byteBufferAppend.putInt(timestamp); // Producer Group Hashcode this.byteBufferAppend.putInt(groupHashCode); // Transaction State this.byteBufferAppend.putInt(MessageSysFlag.TransactionPreparedType); return mapedFile.appendMessage(this.byteBufferAppend.array()); } /** * ���̵߳��� */ public boolean updateTransactionState(// final long tsOffset,// final long clOffset,// final int groupHashCode,// final int state// ) { SelectMapedBufferResult selectMapedBufferResult = this.findTransactionBuffer(tsOffset); if (selectMapedBufferResult != null) { try { final long clOffset_read = selectMapedBufferResult.getByteBuffer().getLong(); final int size_read = selectMapedBufferResult.getByteBuffer().getInt(); final int timestamp_read = selectMapedBufferResult.getByteBuffer().getInt(); final int groupHashCode_read = selectMapedBufferResult.getByteBuffer().getInt(); final int state_read = selectMapedBufferResult.getByteBuffer().getInt(); // У��������ȷ�� if (clOffset != clOffset_read) { log.error("updateTransactionState error clOffset: {} clOffset_read: {}", clOffset, clOffset_read); return false; } // У��������ȷ�� if (groupHashCode != groupHashCode_read) { log.error("updateTransactionState error groupHashCode: {} groupHashCode_read: {}", groupHashCode, groupHashCode_read); return false; } // �ж��Ƿ��Ѿ����¹� if (MessageSysFlag.TransactionPreparedType != state_read) { log.warn("updateTransactionState error, the transaction is updated before."); return true; } // ��������״̬ selectMapedBufferResult.getByteBuffer().putInt(TS_STATE_POS, state); } catch (Exception e) { log.error("updateTransactionState exception", e); } finally { selectMapedBufferResult.release(); } } return false; } private SelectMapedBufferResult findTransactionBuffer(final long tsOffset) { final int mapedFileSize = this.defaultMessageStore.getMessageStoreConfig().getTranStateTableMapedFileSize(); final long offset = tsOffset * TSStoreUnitSize; MapedFile mapedFile = this.tranStateTable.findMapedFileByOffset(offset); if (mapedFile != null) { SelectMapedBufferResult result = mapedFile.selectMapedBuffer((int) (offset % mapedFileSize)); return result; } return null; } public AtomicLong getTranStateTableOffset() { return tranStateTableOffset; } public ConsumeQueue getTranRedoLog() { return tranRedoLog; } }