package com.alibaba.rocketmq.storm.spout; import java.util.Map; import java.util.Queue; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeUnit; import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.lang.builder.ToStringStyle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import backtype.storm.spout.SpoutOutputCollector; import backtype.storm.task.TopologyContext; import backtype.storm.topology.OutputFieldsDeclarer; import backtype.storm.tuple.Fields; import backtype.storm.tuple.Values; import backtype.storm.utils.RotatingMap; import backtype.storm.utils.RotatingMap.ExpiredCallback; import com.alibaba.rocketmq.common.message.MessageExt; import com.alibaba.rocketmq.storm.annotation.Extension; import com.alibaba.rocketmq.storm.domain.BatchMessage; import com.alibaba.rocketmq.storm.domain.MessageCacheItem; import com.google.common.collect.Sets; /** * @author Von Gosling */ @Extension("stream") public class StreamMessageSpout extends BatchMessageSpout { private static final long serialVersionUID = 464153253576782163L; private static final Logger LOG = LoggerFactory .getLogger(StreamMessageSpout.class); private final Queue<MessageCacheItem> msgQueue = new ConcurrentLinkedQueue<MessageCacheItem>(); private RotatingMap<String, MessageCacheItem> msgCache; /** * This field is used to check whether one batch is finish or not */ private Map<UUID, BatchMsgsTag> batchMsgsMap = new ConcurrentHashMap<UUID, BatchMsgsTag>(); public void open(@SuppressWarnings("rawtypes") final Map conf, final TopologyContext context, final SpoutOutputCollector collector) { super.open(conf, context, collector); ExpiredCallback<String, MessageCacheItem> callback = new ExpiredCallback<String, MessageCacheItem>() { public void expire(String key, MessageCacheItem val) { LOG.warn("Long time no ack,key is {},value is {} !", key, val); msgCache.put(key, val); fail(key); } }; msgCache = new RotatingMap<String, MessageCacheItem>(3600 * 5, callback); LOG.info("Topology {} opened {} spout successfully !", new Object[] { topologyName, config.getTopic() }); } public void prepareMsg() { while (true) { BatchMessage msgTuple = null; try { msgTuple = super.getBatchQueue().poll(1, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { return; } if (msgTuple == null) { return; } if (msgTuple.getMsgList().size() == 0) { super.finish(msgTuple.getBatchId()); return; } BatchMsgsTag partTag = new BatchMsgsTag(); Set<String> msgIds = partTag.getMsgIds(); for (MessageExt msg : msgTuple.getMsgList()) { String msgId = msg.getMsgId(); msgIds.add(msgId); MessageCacheItem item = new MessageCacheItem(msgTuple.getBatchId(), msg, msgTuple.getMessageStat()); msgCache.put(msgId, item); msgQueue.offer(item); } batchMsgsMap.put(msgTuple.getBatchId(), partTag); } } @Override public void nextTuple() { MessageCacheItem cacheItem = msgQueue.poll(); if (cacheItem != null) { Values values = new Values(cacheItem.getMsg(), cacheItem.getMsgStat()); String messageId = cacheItem.getMsg().getMsgId(); collector.emit(values, messageId); LOG.debug("Emited tuple {},mssageId is {} !", values, messageId); return; } prepareMsg(); return; } public void finish(String msgId) { MessageCacheItem cacheItem = (MessageCacheItem) msgCache.remove(msgId); if (cacheItem == null) { LOG.warn("Failed to get from cache {} !", msgId); return; } UUID batchId = cacheItem.getId(); BatchMsgsTag partTag = batchMsgsMap.get(batchId); if (partTag == null) { throw new RuntimeException("In partOffset map, no entry of " + batchId); } Set<String> msgIds = partTag.getMsgIds(); msgIds.remove(msgId); if (msgIds.size() == 0) { batchMsgsMap.remove(batchId); super.finish(batchId); } } @Override public void ack(final Object id) { if (id instanceof String) { finish((String) id); return; } else { LOG.error("Id isn't Long, type is {} !", id.getClass().getName()); } } public void handleFail(String msgId) { MessageCacheItem cacheItem = msgCache.get(msgId); if (cacheItem == null) { LOG.warn("Failed to get cached values {} !", msgId); return; } LOG.info("Failed to handle {} !", cacheItem); int failTime = cacheItem.getMsgStat().getFailureTimes().incrementAndGet(); if (config.getMaxFailTimes() < 0 || failTime < config.getMaxFailTimes()) { msgQueue.offer(cacheItem); return; } else { LOG.info("Skip message {} !", cacheItem.getMsg().toString()); finish(msgId); return; } } @Override public void fail(final Object id) { if (id instanceof String) { handleFail((String) id); return; } else { LOG.error("Id isn't Long, type is {}", id.getClass().getName()); } } public void declareOutputFields(final OutputFieldsDeclarer declarer) { declarer.declare(new Fields("MessageExt")); } public static class BatchMsgsTag { private final Set<String> msgIds; private final long createTs; public BatchMsgsTag() { this.msgIds = Sets.newHashSet(); this.createTs = System.currentTimeMillis(); } public Set<String> getMsgIds() { return msgIds; } public long getCreateTs() { return createTs; } @Override public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); } } }