package com.alibaba.rocketmq.storm.spout; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import org.apache.storm.guava.collect.Lists; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import backtype.storm.spout.SpoutOutputCollector; import backtype.storm.task.TopologyContext; import backtype.storm.topology.IRichSpout; import backtype.storm.topology.OutputFieldsDeclarer; import backtype.storm.tuple.Fields; import backtype.storm.tuple.Values; import com.alibaba.rocketmq.client.consumer.PullResult; import com.alibaba.rocketmq.common.message.MessageQueue; import com.alibaba.rocketmq.storm.MessagePullConsumer; import com.alibaba.rocketmq.storm.domain.BatchMessage; import com.alibaba.rocketmq.storm.domain.QueueOffsetCache; import com.alibaba.rocketmq.storm.domain.RocketMQConfig; import com.google.common.collect.MapMaker; /** * @author von gosling */ public class SimplePullMessageSpout implements IRichSpout { private static final long serialVersionUID = -5561450001033205169L; private static final Logger LOG = LoggerFactory .getLogger(SimplePullMessageSpout.class); private MessagePullConsumer consumer; protected SpoutOutputCollector collector; protected TopologyContext context; private RocketMQConfig config; protected Map<UUID, BatchMessage> batchCache = new MapMaker().makeMap(); @Override public void open(@SuppressWarnings("rawtypes") Map conf, TopologyContext context, SpoutOutputCollector collector) { this.collector = collector; this.context = context; if (consumer == null) { try { config.setInstanceName(String.valueOf(context.getThisTaskId())); consumer = new MessagePullConsumer(config); Set<MessageQueue> mqs = consumer.getConsumer().fetchSubscribeMessageQueues( config.getTopic()); consumer.getTopicQueueMappings().put(config.getTopic(), Lists.newArrayList(mqs)); } catch (Exception e) { LOG.error("Error occured !", e); throw new IllegalStateException(e); } } } @Override public void close() { consumer.shutdown(); } @Override public void activate() { consumer.resume(); } @Override public void deactivate() { consumer.suspend(); } @Override public void nextTuple() { handleAndEmit(); } private void handleAndEmit() { List<MessageQueue> mqs = consumer.getTopicQueueMappings().get(config.getTopic()); for (MessageQueue mq : mqs) { try { PullResult pullResult = consumer.getConsumer().pullBlockIfNotFound(mq, config.getTopicTag(), QueueOffsetCache.getMessageQueueOffset(mq), config.getPullBatchSize()); QueueOffsetCache.putMessageQueueOffset(mq, pullResult.getNextBeginOffset()); switch (pullResult.getPullStatus()) { case FOUND: BatchMessage msg = new BatchMessage(pullResult.getMsgFoundList(), mq); batchCache.put(msg.getBatchId(), msg); collector.emit(new Values(msg, msg.getBatchId())); break; case OFFSET_ILLEGAL: throw new IllegalStateException("Illegal offset " + pullResult); default: LOG.warn("Unconcerned status {} for result {} !", pullResult.getPullStatus(), pullResult); break; } } catch (Exception e) { LOG.error("Error occured in queue {} !", new Object[] { mq }, e); } } } @Override public void ack(final Object id) { BatchMessage batchMsgs = batchCache.remove((UUID) id); if (batchMsgs == null) { LOG.warn("Failed to get cached value from key {} !", id); } } @Override public void fail(final Object id) { BatchMessage msg = batchCache.get((UUID) id); int failureTimes = msg.getMessageStat().getFailureTimes().incrementAndGet(); if (config.getMaxFailTimes() < 0 || failureTimes < config.getMaxFailTimes()) { collector.emit(new Values(msg, msg.getBatchId())); } else { LOG.warn("Skip message {}!", msg); } } @Override public void declareOutputFields(OutputFieldsDeclarer declarer) { declarer.declare(new Fields("MessageExtList")); } @Override public Map<String, Object> getComponentConfiguration() { return null; } public void setConfig(RocketMQConfig config) { this.config = config; } }