package org.apache.rocketmq.store.transaction; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.DispatchRequest; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.transaction.jdbc.JDBCTransactionStore; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; /** * @author fengjian * @version V1.0 * @title: rocketmq-all * @Package org.apache.rocketmq.store.transaction * @Description: * @date 2017/11/7 上午11:25 */ public class TransactionRecordFlush2DBService extends ServiceThread { private static final Logger log = LoggerFactory.getLogger(LoggerName.TRANSACTION_LOGGER_NAME); private final TransactionOffsetConifgService transactionOffsetConifgService; private final TransactionStore transactionStore; public TransactionRecordFlush2DBService(DefaultMessageStore defaultMessageStore) { MessageStoreConfig messageStoreConfig = defaultMessageStore.getMessageStoreConfig(); this.transactionOffsetConifgService = new TransactionOffsetConifgService(defaultMessageStore); TransactionTableDefConfigService transactionTableDefConfigService = new TransactionTableDefConfigService(defaultMessageStore); transactionTableDefConfigService.load(); int retry = 0; boolean isCheckTable = false; this.transactionStore = new JDBCTransactionStore(messageStoreConfig); this.transactionStore.load(); do { int tableSuffix = transactionTableDefConfigService.getTableSuffix(messageStoreConfig); this.transactionStore.setTableSuffix(tableSuffix); log.info("loadTableStoreConfig tableSuffix={}", tableSuffix); } while (!(isCheckTable = transactionStore.createTableIfNotExists()) && ++retry < 5); if (!isCheckTable) { throw new RuntimeException("check db info error!"); } else { transactionTableDefConfigService.persist(); } for (int i = 0; i < REQUEST_BUFFER_IN_QUEUE; i++) { dispatchRequestBufferQueue.add(new DispatchRequestCollections(new AtomicInteger(0), new ArrayList<>())); } } public AtomicLong queryTransactionOffset() { return transactionOffsetConifgService.queryOffset(); } @Override public void start() { super.start(); transactionOffsetConifgService.start(); } public void load() { transactionOffsetConifgService.load(); long maxOffset = transactionStore.maxPK(); long minOffset = transactionStore.minPK(); long transactionOffset = queryTransactionOffset().get(); //set parpare offset if (maxOffset > transactionOffset) { queryTransactionOffset().compareAndSet(transactionOffset, maxOffset); this.maxTransOffset.set(maxOffset); } else { this.maxTransOffset.set(transactionOffset); } //set confirm offset if (minOffset < transactionOffset && minOffset > 0) { this.minTransOffset.set(minOffset); } else { this.minTransOffset.set(transactionOffset); } } class DispatchRequestCollections { private AtomicInteger latch; private List<DispatchRequest> requestlist; DispatchRequestCollections(AtomicInteger latch, List<DispatchRequest> requestlist) { this.latch = latch; this.requestlist = requestlist; } } private static final int REQUEST_BUFFER_IN_QUEUE = 5; private final AtomicLong maxTransOffset = new AtomicLong(0); private final AtomicLong minTransOffset = new AtomicLong(0); private static final int FLOW_CONTROLLER = 20000; private static final int CHECK_THREAD_LOOP = 100; private volatile int flushCounter = 0; private volatile ConcurrentLinkedQueue<DispatchRequestCollections> dispatchRequestBufferQueue = new ConcurrentLinkedQueue<>(); private volatile Semaphore flowController = new Semaphore(FLOW_CONTROLLER); private void putEmptyRequestList() { dispatchRequestBufferQueue.add(new DispatchRequestCollections(new AtomicInteger(0), new CopyOnWriteArrayList<>())); } @Override public String getServiceName() { return TransactionRecordFlush2DBService.class.getName(); } public void appendPreparedTransaction(DispatchRequest dispatchRequest) { try { flowController.acquire(1); while (true) { DispatchRequestCollections requests = dispatchRequestBufferQueue.peek(); if (requests.latch.getAndIncrement() >= 0) { requests.requestlist.add(dispatchRequest); break; } } } catch (InterruptedException e) { log.error("putDispatchRequest interrupted"); } } @Override public void run() { log.info(this.getServiceName() + " service started"); while (!this.isStopped()) { try { DispatchRequestCollections requests = dispatchRequestBufferQueue.peek(); if (requests.requestlist.size() > FLOW_CONTROLLER / REQUEST_BUFFER_IN_QUEUE || flushCounter > CHECK_THREAD_LOOP) { this.doFlushDB(false); flushCounter = 0; continue; } ++flushCounter; ThreadUtils.sleep(10); } catch (Exception e) { log.warn(this.getServiceName() + " service has exception. ", e); } } log.info(this.getServiceName() + " service end"); } private void doFlushDB(boolean shutdown) { DispatchRequestCollections requests = dispatchRequestBufferQueue.poll(); if (requests == null) { return; } if (!shutdown) { putEmptyRequestList(); } boolean addSuccess = false, removeSuccess = false; LinkedHashMap<Long, TransactionRecord> prepareTrs = null; LinkedHashMap<Long, Void> confirmTrs = null; while (true) { if (requests.latch.get() != requests.requestlist.size() && requests.latch.get() > 0) { continue; } requests.latch.set(Integer.MIN_VALUE); if (requests.requestlist.size() == 0) { break; } try { long transactionOffset = -1L; //数据处理 if (prepareTrs == null && confirmTrs == null) { prepareTrs = new LinkedHashMap<Long, TransactionRecord>(); confirmTrs = new LinkedHashMap<Long, Void>(); for (DispatchRequest request : requests.requestlist) { final int tranType = MessageSysFlag.getTransactionValue(request.getSysFlag()); switch (tranType) { case MessageSysFlag.TRANSACTION_NOT_TYPE: break; case MessageSysFlag.TRANSACTION_PREPARED_TYPE: if (this.maxTransOffset.get() < request.getCommitLogOffset()) { prepareTrs.put(request.getCommitLogOffset(), new TransactionRecord(request.getCommitLogOffset(), request.getCheckImmunityTimeOutTimestamp(), request.getMsgSize(), request.getProducerGroup())); this.maxTransOffset.set(request.getCommitLogOffset()); } else { log.info("[PREPARED] request ignore offset =" + request.getCommitLogOffset()); } if (request.getPreparedTransactionOffset() == 0L) { break; } case MessageSysFlag.TRANSACTION_COMMIT_TYPE: case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE: if (this.maxTransOffset.get() < request.getCommitLogOffset()) { if (prepareTrs.containsKey(request.getPreparedTransactionOffset())) { prepareTrs.remove(request.getPreparedTransactionOffset()); } else { confirmTrs.put(request.getPreparedTransactionOffset(), null); } } else { log.info("[COMMIT] request ignore offset =" + request.getCommitLogOffset() + ",isCommitMessge=" + (tranType == MessageSysFlag.TRANSACTION_COMMIT_TYPE)); } break; } } transactionOffset = requests.requestlist.get(requests.requestlist.size() - 1).getCommitLogOffset(); } long startTime = System.currentTimeMillis(); addSuccess = addSuccess || transactionStore.parpare(new ArrayList<>(prepareTrs.values())); if (addSuccess && (removeSuccess = transactionStore.confirm(new ArrayList<>(confirmTrs.keySet())))) { log.info("pull TransactionRecord consume {}ms ,size={},realParpareSize={},realConfirmSize:{}", (System.currentTimeMillis() - startTime), requests.requestlist.size(), prepareTrs.size(), confirmTrs.size()); //更新最新的offset if (transactionOffset > 0) { transactionOffsetConifgService.putOffset(transactionOffset); } break; } } catch (Throwable e) { log.error("transactionStore error:", e); ThreadUtils.sleep(2000); } finally { if (addSuccess && removeSuccess) { flowController.release(requests.requestlist.size()); } } } } public void shutdown() { super.shutdown(); while (dispatchRequestBufferQueue.peek() != null) { this.doFlushDB(true); } transactionOffsetConifgService.persist(); } public TransactionStore getTransactionStore() { return transactionStore; } public long getMaxTransOffset() { return maxTransOffset.get(); } public long getMinTransOffset() { return minTransOffset.get(); } }