/** * 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.batch.impl; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.jstorm.batch.BatchId; import com.alibaba.jstorm.batch.ICommitter; import com.alibaba.jstorm.batch.IPostCommit; import com.alibaba.jstorm.batch.IPrepareCommit; import com.alibaba.jstorm.batch.util.BatchCommon; import com.alibaba.jstorm.batch.util.BatchDef; import com.alibaba.jstorm.batch.util.BatchStatus; import com.alibaba.jstorm.cluster.ClusterState; import com.alibaba.jstorm.utils.TimeCacheMap; import backtype.storm.task.OutputCollector; import backtype.storm.task.TopologyContext; import backtype.storm.topology.BasicOutputCollector; import backtype.storm.topology.FailedException; import backtype.storm.topology.IBasicBolt; import backtype.storm.topology.IRichBolt; import backtype.storm.topology.OutputFieldsDeclarer; import backtype.storm.topology.ReportedFailedException; import backtype.storm.tuple.Tuple; public class CoordinatedBolt implements IRichBolt { private static final long serialVersionUID = 5720810158625748046L; public static Logger LOG = LoggerFactory.getLogger(CoordinatedBolt.class); private IBasicBolt delegate; private BasicOutputCollector basicCollector; private OutputCollector collector; private String taskId; private String taskName; private boolean isCommiter = false; private String zkCommitPath; private TimeCacheMap<Object, Object> commited; public CoordinatedBolt(IBasicBolt delegate) { this.delegate = delegate; } // use static variable to reduce zk connection private static ClusterState zkClient = null; public void mkCommitDir(Map conf) { try { zkClient = BatchCommon.getZkClient(conf); zkCommitPath = BatchDef.ZK_COMMIT_DIR + BatchDef.ZK_SEPERATOR + taskId; if (zkClient.node_existed(zkCommitPath, false)) { zkClient.delete_node(zkCommitPath); } zkClient.mkdirs(zkCommitPath); LOG.info(taskName + " successfully create commit path" + zkCommitPath); } catch (Exception e) { LOG.error("Failed to create zk node", e); throw new RuntimeException(); } } public void prepare(Map conf, TopologyContext context, OutputCollector collector) { taskId = String.valueOf(context.getThisTaskId()); taskName = context.getThisComponentId() + "_" + context.getThisTaskId(); this.basicCollector = new BasicOutputCollector(collector); this.collector = collector; if (delegate instanceof ICommitter) { isCommiter = true; commited = new TimeCacheMap<Object, Object>(context.maxTopologyMessageTimeout()); mkCommitDir(conf); } delegate.prepare(conf, context); } public void removeUseless(String path, int reserveSize) throws Exception { List<String> childs = zkClient.get_children(path, false); Collections.sort(childs, new Comparator<String>() { @Override public int compare(String o1, String o2) { try { Long v1 = Long.valueOf(o1); Long v2 = Long.valueOf(o2); return v1.compareTo(v2); } catch (Exception e) { return o1.compareTo(o2); } } }); for (int index = 0; index < childs.size() - reserveSize; index++) { zkClient.delete_node(path + BatchDef.ZK_SEPERATOR + childs.get(index)); } } public String getCommitPath(BatchId id) { return zkCommitPath + BatchDef.ZK_SEPERATOR + id.getId(); } public void updateToZk(Object id, byte[] commitResult) { try { removeUseless(zkCommitPath, BatchDef.ZK_COMMIT_RESERVER_NUM); String path = getCommitPath((BatchId) id); byte[] data = commitResult; if (data == null) { data = new byte[0]; } zkClient.set_data(path, data); LOG.info("Update " + path + " to zk"); } catch (Exception e) { LOG.warn("Failed to update to zk,", e); } } public byte[] getCommittedData(Object id) { try { String path = getCommitPath((BatchId) id); byte[] data = zkClient.get_data(path, false); return data; } catch (Exception e) { LOG.error("Failed to visit ZK,", e); return null; } } public void handleRegular(Tuple tuple) { basicCollector.setContext(tuple); try { delegate.execute(tuple, basicCollector); collector.ack(tuple); } catch (FailedException e) { if (e instanceof ReportedFailedException) { collector.reportError(e); } collector.fail(tuple); } } public void handlePrepareCommit(Tuple tuple) { basicCollector.setContext(tuple); try { BatchId id = (BatchId) tuple.getValue(0); ((IPrepareCommit) delegate).prepareCommit(id, basicCollector); collector.ack(tuple); } catch (FailedException e) { if (e instanceof ReportedFailedException) { collector.reportError(e); } collector.fail(tuple); } } public void handleCommit(Tuple tuple) { Object id = tuple.getValue(0); try { byte[] commitResult = ((ICommitter) delegate).commit((BatchId) id); collector.ack(tuple); updateToZk(id, commitResult); commited.put(id, commitResult); } catch (Exception e) { LOG.error("Failed to commit ", e); collector.fail(tuple); } } public void handleRevert(Tuple tuple) { try { Object id = tuple.getValue(0); byte[] commitResult = null; if (commited.containsKey(id)) { commitResult = (byte[]) commited.get(id); } else { commitResult = getCommittedData(id); } if (commitResult != null) { ((ICommitter) delegate).revert((BatchId) id, commitResult); } } catch (Exception e) { LOG.error("Failed to revert,", e); } collector.ack(tuple); } public void handlePostCommit(Tuple tuple) { basicCollector.setContext(tuple); try { BatchId id = (BatchId) tuple.getValue(0); ((IPostCommit) delegate).postCommit(id, basicCollector); } catch (Exception e) { LOG.info("Failed to do postCommit,", e); } collector.ack(tuple); } public void execute(Tuple tuple) { BatchStatus batchStatus = getBatchStatus(tuple); if (batchStatus == BatchStatus.COMPUTING) { handleRegular(tuple); } else if (batchStatus == BatchStatus.PREPARE_COMMIT) { handlePrepareCommit(tuple); } else if (batchStatus == BatchStatus.COMMIT) { handleCommit(tuple); } else if (batchStatus == BatchStatus.REVERT_COMMIT) { handleRevert(tuple); } else if (batchStatus == BatchStatus.POST_COMMIT) { handlePostCommit(tuple); } else { throw new RuntimeException("Receive commit tuple, but not committer"); } } public void cleanup() { delegate.cleanup(); } public void declareOutputFields(OutputFieldsDeclarer declarer) { delegate.declareOutputFields(declarer); } @Override public Map<String, Object> getComponentConfiguration() { return delegate.getComponentConfiguration(); } private BatchStatus getBatchStatus(Tuple tuple) { String streamId = tuple.getSourceStreamId(); if (streamId.equals(BatchDef.PREPARE_STREAM_ID)) { return BatchStatus.PREPARE_COMMIT; } else if (streamId.equals(BatchDef.COMMIT_STREAM_ID)) { return BatchStatus.COMMIT; } else if (streamId.equals(BatchDef.REVERT_STREAM_ID)) { return BatchStatus.REVERT_COMMIT; } else if (streamId.equals(BatchDef.POST_STREAM_ID)) { return BatchStatus.POST_COMMIT; } else { return BatchStatus.COMPUTING; } } }