Java tutorial
/** * 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 com.taobao.metamorphosis.server.transaction.store; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.google.protobuf.ByteString; import com.taobao.metamorphosis.network.ByteUtils; import com.taobao.metamorphosis.network.PutCommand; import com.taobao.metamorphosis.server.store.AppendCallback; import com.taobao.metamorphosis.server.store.Location; import com.taobao.metamorphosis.server.store.MessageStore; import com.taobao.metamorphosis.server.store.MessageStoreManager; import com.taobao.metamorphosis.server.transaction.TransactionRecoveryListener; import com.taobao.metamorphosis.server.transaction.TransactionStore; import com.taobao.metamorphosis.server.transaction.store.TransactionCommands.AppendMessageCommand; import com.taobao.metamorphosis.server.transaction.store.TransactionCommands.TransactionOperation; import com.taobao.metamorphosis.server.transaction.store.TransactionCommands.TransactionType; import com.taobao.metamorphosis.server.transaction.store.TransactionCommands.TxCommand; import com.taobao.metamorphosis.server.transaction.store.TransactionCommands.TxCommandType; import com.taobao.metamorphosis.server.utils.MetaConfig; import com.taobao.metamorphosis.transaction.TransactionId; import com.taobao.metamorphosis.transaction.XATransactionId; import com.taobao.metamorphosis.utils.CheckSum; import com.taobao.metamorphosis.utils.MessageUtils; import com.taobao.metamorphosis.utils.MetaMBeanServer; /** * * * @author boyan(boyan@taobao.com) * @date 2011-8-22 * */ public class JournalTransactionStore implements TransactionStore, JournalTransactionStoreMBean { private final JournalStore journalStore; private final Map<Object, Tx> inflightTransactions = new LinkedHashMap<Object, Tx>(); private final Map<TransactionId, Tx> preparedTransactions = new LinkedHashMap<TransactionId, Tx>(); private boolean doingRecover; /** * * * @author boyan(boyan@taobao.com) * @date 2011-8-22 * */ public static interface TxOperation { static final byte ADD_OP = 0; public byte getType(); } public static class AddMsgOperation implements TxOperation { public MessageStore store; public long msgId; public PutCommand putCmd; @Override public byte getType() { return ADD_OP; } public AddMsgOperation(final MessageStore store, final long msgId, final PutCommand putCmd) { super(); this.store = store; this.msgId = msgId; this.putCmd = putCmd; } } /** * storechecksum * * @author boyan(boyan@taobao.com) * @date 2011-8-23 * */ public static class AddMsgLocation extends Location { public final int checksum; // message public final String storeDesc; // public AddMsgLocation(final long offset, final int length, final int checksum, final String storeDesc) { super(offset, length); this.checksum = checksum; this.storeDesc = storeDesc; } private ByteBuffer buf; public static AddMsgLocation decode(final ByteBuffer buf) { if (!buf.hasRemaining()) { return null; } final long offset = buf.getLong(); final int length = buf.getInt(); final int checksum = buf.getInt(); final int descLen = buf.getInt(); final byte[] descBytes = new byte[descLen]; buf.get(descBytes); final String desc = ByteUtils.getString(descBytes); return new AddMsgLocation(offset, length, checksum, desc); } /** * : * <ul> * <li>8offset</li> * <li>4</li> * <li>4checksum:checksummessagechecksum</li> * <li>4</li> * <li></li> * </ul> * * @return */ public ByteBuffer encode() { if (this.buf == null) { final byte[] storeDescBytes = ByteUtils.getBytes(this.storeDesc); final ByteBuffer buf = ByteBuffer.allocate(4 + 4 + 8 + 4 + this.storeDesc.length()); buf.putLong(this.getOffset()); buf.putInt(this.getLength()); buf.putInt(this.checksum); buf.putInt(storeDescBytes.length); buf.put(storeDescBytes); buf.flip(); this.buf = buf; } return this.buf; } } /** * * * @author boyan(boyan@taobao.com) * @date 2011-8-22 * */ public static class Tx { private final JournalLocation location; private final ConcurrentHashMap<MessageStore, Queue<TxOperation>> operations = new ConcurrentHashMap<MessageStore, Queue<TxOperation>>(); JournalLocation getLocation() { return this.location; } public Tx(final JournalLocation location) { this.location = location; } public void add(final MessageStore store, final long msgId, final PutCommand putCmd) { final AddMsgOperation addMsgOperation = new AddMsgOperation(store, msgId, putCmd); Queue<TxOperation> ops = this.operations.get(store); if (ops == null) { ops = new ConcurrentLinkedQueue<TxOperation>(); final Queue<TxOperation> oldOps = this.operations.putIfAbsent(store, ops); if (oldOps != null) { ops = oldOps; } } ops.add(addMsgOperation); } public Map<MessageStore, List<Long>> getMsgIds() { final Map<MessageStore, List<Long>> rt = new LinkedHashMap<MessageStore, List<Long>>(); for (final Map.Entry<MessageStore, Queue<TxOperation>> entry : this.operations.entrySet()) { final MessageStore store = entry.getKey(); final Queue<TxOperation> opQueue = entry.getValue(); final List<Long> ids = new ArrayList<Long>(); rt.put(store, ids); for (final TxOperation to : opQueue) { if (to.getType() == TxOperation.ADD_OP) { ids.add(((AddMsgOperation) to).msgId); } } } return rt; } public PutCommand[] getRequests() { final List<PutCommand> list = new ArrayList<PutCommand>(this.operations.size() * 2); for (final Map.Entry<MessageStore, Queue<TxOperation>> entry : this.operations.entrySet()) { for (final TxOperation op : entry.getValue()) { if (op.getType() == TxOperation.ADD_OP) { list.add(((AddMsgOperation) op).putCmd); } } } final PutCommand[] rt = new PutCommand[list.size()]; return list.toArray(rt); } public Map<MessageStore, List<PutCommand>> getPutCommands() { final Map<MessageStore, List<PutCommand>> rt = new LinkedHashMap<MessageStore, List<PutCommand>>(); for (final Map.Entry<MessageStore, Queue<TxOperation>> entry : this.operations.entrySet()) { final MessageStore store = entry.getKey(); final Queue<TxOperation> opQueue = entry.getValue(); final List<PutCommand> ids = new ArrayList<PutCommand>(); rt.put(store, ids); for (final TxOperation to : opQueue) { if (to.getType() == TxOperation.ADD_OP) { ids.add(((AddMsgOperation) to).putCmd); } } } return rt; } public Map<MessageStore, Queue<TxOperation>> getOperations() { return this.operations; } } private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); static final Log log = LogFactory.getLog(JournalLocation.class); public JournalTransactionStore(final String dataPath, final MessageStoreManager storeManager, final MetaConfig metaConfig) throws Exception { this.journalStore = new JournalStore(dataPath, storeManager, this, metaConfig.getMaxCheckpoints(), metaConfig.getFlushTxLogAtCommit()); this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { JournalTransactionStore.this.makeCheckpoint(); } catch (final Exception e) { log.error("Execute checkpoint failed", e); } } }, metaConfig.getCheckpointInterval(), metaConfig.getCheckpointInterval(), TimeUnit.MILLISECONDS); MetaMBeanServer.registMBean(this, null); } @Override public void prepare(final TransactionId txid) throws IOException { Tx tx = null; synchronized (this.inflightTransactions) { tx = this.inflightTransactions.remove(txid); } if (tx == null) { return; } final TransactionOperation to = TransactionOperation.newBuilder().setType(TransactionType.XA_PREPARE) .setTransactionId(txid.getTransactionKey()).setWasPrepared(false).build(); // prepare,force final TxCommand msg = TxCommand.newBuilder().setCmdType(TxCommandType.TX_OP) .setCmdContent(to.toByteString()).setForce(true).build(); this.journalStore.write(msg, null, tx.location, false); synchronized (this.preparedTransactions) { this.preparedTransactions.put(txid, tx); } } public JournalStore getJournalStore() { return this.journalStore; } // public void setJournalStore(final JournalStore journalStore) { // this.journalStore = journalStore; // } public void replayPrepare(final TransactionId txid) throws IOException { Tx tx = null; synchronized (this.inflightTransactions) { tx = this.inflightTransactions.remove(txid); } if (tx == null) { return; } synchronized (this.preparedTransactions) { this.preparedTransactions.put(txid, tx); } } public Tx getInflyTx(final Object txid) { synchronized (this.inflightTransactions) { return this.inflightTransactions.get(txid); } } @Override public int getActiveTransactionCount() { int count = 0; synchronized (this.preparedTransactions) { count += this.preparedTransactions.size(); } synchronized (this.inflightTransactions) { count += this.inflightTransactions.size(); } return count; } Tx getPreparedTx(final Object txid) { synchronized (this.preparedTransactions) { return this.preparedTransactions.get(txid); } } public Tx getTx(final Object txid, final JournalLocation location) { synchronized (this.inflightTransactions) { Tx tx = this.inflightTransactions.get(txid); if (tx == null) { tx = new Tx(location); this.inflightTransactions.put(txid, tx); } return tx; } } @Override public void commit(final TransactionId txid, final boolean wasPrepared) throws IOException { final Tx tx; if (wasPrepared) { synchronized (this.preparedTransactions) { tx = this.preparedTransactions.remove(txid); } } else { synchronized (this.inflightTransactions) { tx = this.inflightTransactions.remove(txid); } } if (tx == null) { return; } // Append messages final Map<MessageStore, List<Long>> msgIds = tx.getMsgIds(); final Map<MessageStore, List<PutCommand>> putCommands = tx.getPutCommands(); final Map<String, AddMsgLocation> locations = new LinkedHashMap<String, JournalTransactionStore.AddMsgLocation>(); final int count = msgIds.size(); for (final Map.Entry<MessageStore, List<Long>> entry : msgIds.entrySet()) { final MessageStore msgStore = entry.getKey(); final List<Long> ids = entry.getValue(); final List<PutCommand> cmds = putCommands.get(msgStore); // Append message msgStore.append(ids, cmds, new AppendCallback() { @Override public void appendComplete(final Location location) { // Calculate checksum final int checkSum = CheckSum.crc32(MessageUtils.makeMessageBuffer(ids, cmds).array()); final String description = msgStore.getDescription(); // Store append location synchronized (locations) { locations.put(description, new AddMsgLocation(location.getOffset(), location.getLength(), checkSum, description)); // if (locations.size() == count) { // tx // commandreplay final ByteBuffer localtionBytes = AddMsgLocationUtils.encodeLocation(locations); TxCommand msg = null; // Log transaction final int attachmentLen = localtionBytes.remaining(); if (txid.isXATransaction()) { final TransactionOperation to = TransactionOperation.newBuilder() // .setType(TransactionType.XA_COMMIT) // .setTransactionId(txid.getTransactionKey()) // .setWasPrepared(wasPrepared) // .setDataLength(attachmentLen) // .build(); msg = TxCommand.newBuilder().setCmdType(TxCommandType.TX_OP) .setCmdContent(to.toByteString()).build(); } else { final TransactionOperation to = TransactionOperation.newBuilder() // .setType(TransactionType.LOCAL_COMMIT) // .setTransactionId(txid.getTransactionKey()) // .setWasPrepared(wasPrepared) // .setDataLength(attachmentLen)// .build(); msg = TxCommand.newBuilder().setCmdType(TxCommandType.TX_OP) .setCmdContent(to.toByteString()).build(); } // commit try { JournalTransactionStore.this.journalStore.write(msg, localtionBytes, tx.location, true); } catch (final IOException e) { throw new RuntimeException("Write tx log failed", e); } } } } }); } } public Tx replayCommit(final TransactionId txid, final boolean wasPrepared) throws IOException { if (wasPrepared) { synchronized (this.preparedTransactions) { return this.preparedTransactions.remove(txid); } } else { synchronized (this.inflightTransactions) { return this.inflightTransactions.remove(txid); } } } @Override public void rollback(final TransactionId txid) throws IOException { Tx tx = null; synchronized (this.inflightTransactions) { tx = this.inflightTransactions.remove(txid); } if (tx == null) { synchronized (this.preparedTransactions) { tx = this.preparedTransactions.remove(txid); } } if (tx != null) { if (txid.isXATransaction()) { final TransactionOperation to = TransactionOperation.newBuilder() // .setType(TransactionType.XA_ROLLBACK) // .setTransactionId(txid.getTransactionKey()) // .setWasPrepared(false) // .build(); final TxCommand msg = TxCommand.newBuilder().setCmdType(TxCommandType.TX_OP) .setCmdContent(to.toByteString()).build(); this.journalStore.write(msg, null, tx.location, true); } else { final TransactionOperation to = TransactionOperation.newBuilder() // .setType(TransactionType.LOCAL_ROLLBACK) // .setTransactionId(txid.getTransactionKey()) // .setWasPrepared(false) // .build(); final TxCommand msg = TxCommand.newBuilder().setCmdType(TxCommandType.TX_OP) .setCmdContent(to.toByteString()).build(); this.journalStore.write(msg, null, tx.location, true); } } } public void replayRollback(final TransactionId txid) throws IOException { boolean inflight = false; synchronized (this.inflightTransactions) { inflight = this.inflightTransactions.remove(txid) != null; } if (!inflight) { synchronized (this.preparedTransactions) { this.preparedTransactions.remove(txid); } } } @Override public void init() { } @Override public void dispose() { this.scheduledExecutorService.shutdown(); try { this.journalStore.close(); } catch (final IOException e) { throw new RuntimeException(e); } } @Override public synchronized void recover(final TransactionRecoveryListener listener) throws IOException { // Map<Object, Tx> copyMap = null; synchronized (this.inflightTransactions) { copyMap = new HashMap<Object, JournalTransactionStore.Tx>(this.inflightTransactions); // this.inflightTransactions.clear(); } for (final Map.Entry<Object, Tx> entry : copyMap.entrySet()) { this.rollback((TransactionId) entry.getKey()); if (log.isDebugEnabled()) { log.debug("Rollback inflight transaction:" + entry.getKey()); } } // XAprepared this.doingRecover = true; try { Map<TransactionId, Tx> txs = null; synchronized (this.preparedTransactions) { txs = new LinkedHashMap<TransactionId, Tx>(this.preparedTransactions); } for (final Map.Entry<TransactionId, Tx> entry : txs.entrySet()) { final Object txid = entry.getKey(); final Tx tx = entry.getValue(); listener.recover((XATransactionId) txid, tx.getRequests()); } } finally { this.doingRecover = false; } } /** * */ @Override public void addMessage(final MessageStore store, final long msgId, final PutCommand putCmd, JournalLocation location) throws IOException { if (location == null) { // put final AppendMessageCommand appendCmd = AppendMessageCommand.newBuilder().setMessageId(msgId) .setPutCommand(ByteString.copyFrom(putCmd.encode().array())).build(); final TxCommand txCommand = TxCommand.newBuilder().setCmdType(TxCommandType.APPEND_MSG) .setCmdContent(appendCmd.toByteString()).build(); final Tx tx = this.getInflyTx(putCmd.getTransactionId()); if (tx != null) { location = this.journalStore.write(txCommand, null, tx.location, false); } else { location = this.journalStore.write(txCommand, null, null, false); } } final Tx tx = this.getTx(putCmd.getTransactionId(), location); tx.add(store, msgId, putCmd); } @Override public void makeCheckpoint() throws Exception { this.journalStore.checkpoint(); } public JournalLocation checkpoint() throws IOException { // checkpointcheckpoint JournalLocation rc = null; synchronized (this.inflightTransactions) { for (final Iterator<Tx> iter = this.inflightTransactions.values().iterator(); iter.hasNext();) { final Tx tx = iter.next(); final JournalLocation location = tx.location; if (rc == null || rc.compareTo(location) < 0) { rc = location; } } } synchronized (this.preparedTransactions) { for (final Iterator<Tx> iter = this.preparedTransactions.values().iterator(); iter.hasNext();) { final Tx tx = iter.next(); final JournalLocation location = tx.location; if (rc == null || rc.compareTo(location) < 0) { rc = location; } } return rc; } } public boolean isDoingRecover() { return this.doingRecover; } }