/** * 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.alibaba.jstorm.transactional.bolt; import backtype.storm.task.OutputCollector; import backtype.storm.task.TopologyContext; import backtype.storm.tuple.Values; import backtype.storm.utils.Utils; import com.alibaba.jstorm.cluster.Common; import com.alibaba.jstorm.task.master.ctrlevent.TopoMasterCtrlEvent; import com.alibaba.jstorm.task.master.ctrlevent.TopoMasterCtrlEvent.EventType; import com.alibaba.jstorm.transactional.TransactionCommon; import com.alibaba.jstorm.transactional.state.TransactionState; import com.alibaba.jstorm.transactional.state.TransactionState.State; import com.alibaba.jstorm.utils.Pair; import java.util.List; import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingDeque; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class TransactionStatefulBolt extends TransactionBolt { private static final long serialVersionUID = 6590819896204525523L; private static Logger LOG = LoggerFactory.getLogger(TransactionStatefulBolt.class); private ITransactionStatefulBoltExecutor boltExecutor; // async jobs to do commit private BlockingQueue<Pair<EventType, Object>> committingQueue; private AsyncCommittingThread asyncCommittingThread; private class AsyncCommittingThread extends Thread { AsyncCommitRunnable runnable; public AsyncCommittingThread(AsyncCommitRunnable runnable) { super(runnable); this.runnable = runnable; } public void deactivate() { runnable.isActive = false; } public void activate() { runnable.isActive = true; } } private class AsyncCommitRunnable implements Runnable { public boolean isActive = true; @Override public void run() { long batchId = -1; Pair<EventType, Object> commitEvent = null; BatchTracker commitBatch = null; while (true) { try { if (boltStatus.equals(State.ACTIVE) && isActive) { commitEvent = committingQueue.take(); switch (commitEvent.getFirst()) { case transactionCommit: commitBatch = (BatchTracker) commitEvent.getSecond(); batchId = commitBatch.getBatchId(); if (commitBatch != null) { persistCommit(commitBatch); commitBatch = null; } break; case transactionAck: List<Object> value = (List<Object>) commitEvent.getSecond(); batchId = (long) value.get(0); boltExecutor.ackCommit(batchId, (long) value.get(1)); break; default: LOG.warn("Unexpected commit event: {}", commitEvent.getFirst()); break; } } else { Thread.sleep(100); } } catch (InterruptedException e) { if (isActive) { LOG.info("Aysnc thread was interrupted. Current committing batch: {}", commitBatch); } } catch (Exception e) { LOG.error("Failed to process event=" + commitEvent + " for batch-" + batchId, e); } } } } public TransactionStatefulBolt(ITransactionStatefulBoltExecutor boltExecutor) { super(boltExecutor); this.boltExecutor = boltExecutor; } @Override public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) { super.prepare(stormConf, context, collector); this.committingQueue = new LinkedBlockingDeque<>(50); startAsyncCommitThread(); } @Override protected void initState(TransactionState state) { super.initState(state); boltExecutor.initState(state.getUserCheckpoint()); } @Override protected void commit() { BatchTracker committingBatch = currentBatchTracker; committingBatch.getState().setUserCheckpoint(Utils.trySerialize(boltExecutor.finishBatch(currentBatchTracker.getBatchId()))); try { committingQueue.put(new Pair<EventType, Object>(EventType.transactionCommit, committingBatch)); } catch (InterruptedException e) { LOG.error("Error when committing for batch " + committingBatch, e); fail(committingBatch.getBatchId()); } } @Override protected void ackCommit(List<Object> value) { try { committingQueue.put(new Pair<EventType, Object>(EventType.transactionAck, value)); } catch (InterruptedException e) { LOG.warn("Failed to publish ack commit for batch-{}", (long) value.get(0)); } } @Override protected void rollback(TransactionState state) { LOG.info("Start to rollback, state={}\n currentBatchStatus: {}", state, currentBatchStatusInfo()); boltExecutor.rollBack(state.getUserCheckpoint()); long batchId = state.getCurrBatchId(); lastSuccessfulBatch = batchId; } @Override protected void cleanupBuffer() { super.cleanupBuffer(); cleanupCommittingJobs(); } private void startAsyncCommitThread() { asyncCommittingThread = new AsyncCommittingThread(new AsyncCommitRunnable()); asyncCommittingThread.setName("AsyncCommitThread-task-" + taskId); asyncCommittingThread.start(); } protected void persistCommit(BatchTracker committingBatch) { TopoMasterCtrlEvent event = new TopoMasterCtrlEvent(EventType.transactionCommit); event.addEventValue(committingBatch.getBatchId()); byte[] persistData = (byte[]) committingBatch.getState().getUserCheckpoint(); Object persistKey; try { persistKey = boltExecutor.commit(committingBatch.getBatchId(), Utils.maybe_deserialize(persistData)); } catch (Exception e) { LOG.warn("Failed to persist user checkpoint for batch-" + committingBatch.getBatchId(), e); fail(committingBatch.getBatchId()); return; } if (persistKey == TransactionCommon.COMMIT_FAIL) { LOG.warn("Failed to persist user checkpoint for batch-{}", committingBatch.getBatchId()); fail(committingBatch.getBatchId()); return; } committingBatch.getState().setUserCheckpoint(persistKey); event.addEventValue(committingBatch.getState()); outputCollector.emitDirectCtrl(topologyMasterId, Common.TOPOLOGY_MASTER_CONTROL_STREAM_ID, null, new Values(event)); } private void cleanupCommittingJobs() { asyncCommittingThread.deactivate(); asyncCommittingThread.interrupt(); committingQueue.clear(); asyncCommittingThread.activate(); } }