package storm.emq; import backtype.storm.Config; import backtype.storm.spout.SpoutOutputCollector; import backtype.storm.task.TopologyContext; import backtype.storm.topology.OutputFieldsDeclarer; import backtype.storm.topology.base.BaseRichSpout; import backtype.storm.utils.Utils; import com.xiaomi.infra.galaxy.emq.client.EMQClientFactory; import com.xiaomi.infra.galaxy.emq.thrift.*; import libthrift091.TException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; import java.util.Map; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; /** * Created by jiasheng on 15-12-23. */ public class EMQSpout extends BaseRichSpout { public static final Logger LOG = LoggerFactory.getLogger(EMQSpout.class); final EMQConfig emqConfig; DeleteMessageThread deleteMessageThread; FetchMessageThread fetchMessageThread; LinkedBlockingQueue<String> ackedMessagesQueue; LinkedBlockingQueue<ReceiveMessageResponse> fetchedMessageQueue; SpoutOutputCollector collector; MessageService.Iface messageClient; public EMQSpout(EMQConfig emqConfig) { this.emqConfig = emqConfig; } @Override public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) { outputFieldsDeclarer.declare(emqConfig.emqScheme.getOutputFields()); } private void checkTopologyTimeout(QueueService.Iface queueClient, Map map) { int topologyTimeout = Utils.getInt(map.get(Config.TOPOLOGY_MESSAGE_TIMEOUT_SECS)); GetQueueInfoRequest getQueueInfoRequest = new GetQueueInfoRequest(emqConfig.queueName); GetQueueInfoResponse response = null; try { response = queueClient.getQueueInfo(getQueueInfoRequest); } catch (TException e) { throw new RuntimeException("Get EMQ queue info failed: " + e); } int emqInvisibleTime = response.getQueueAttribute().getInvisibilitySeconds(); if (emqInvisibleTime < topologyTimeout) throw new RuntimeException("TOPOLOGY_MESSAGE_TIMEOUT_SECS(" + topologyTimeout + "s) must small than EMQ queue invisibilitySeconds(" + emqInvisibleTime + "s)"); } @Override public void open(Map map, TopologyContext topologyContext, SpoutOutputCollector spoutOutputCollector) { collector = spoutOutputCollector; EMQClientFactory clientFactory = new EMQClientFactory(emqConfig.credential); messageClient = clientFactory.newMessageClient(emqConfig.endpoint); checkTopologyTimeout(clientFactory.newQueueClient(emqConfig.endpoint), map); deleteMessageThread = new DeleteMessageThread(); fetchMessageThread = new FetchMessageThread(); ackedMessagesQueue = new LinkedBlockingQueue<String>(); int capacity = emqConfig.emqCoordinator.newReceiveMessageRequest().getMaxReceiveMessageNumber(); fetchedMessageQueue = new LinkedBlockingQueue<ReceiveMessageResponse>(capacity); fetchMessageThread.start(); deleteMessageThread.start(); LOG.info("Open EMQSpout"); } @Override public void close() { fetchMessageThread.stopRunning(); deleteMessageThread.stopRunning(); LOG.info("Close EMQSpout"); } @Override public void nextTuple() { try { ReceiveMessageResponse response = fetchedMessageQueue.poll(emqConfig.generateTupleTimeoutMs, TimeUnit.MILLISECONDS); if (response != null) collector.emit(emqConfig.emqScheme.deserialize(response), response.getReceiptHandle()); } catch (InterruptedException e) { LOG.warn("Poll message from queue to generate tuple failed:" + e); } } @Override public void ack(Object msgId) { try { ackedMessagesQueue.put(msgId.toString()); } catch (InterruptedException e) { LOG.warn("Ack message failed: " + e); } } @Override public void fail(Object msgId) { LOG.warn("Fail message with ReceiptHandle: " + msgId); } private class FetchMessageThread extends Thread { private volatile boolean running = true; public FetchMessageThread() { super("EMQSpout-fetch-messages-thread"); } @Override public void run() { while (running) { try { List<ReceiveMessageResponse> messageResponseList = messageClient.receiveMessage( new ReceiveMessageRequest(emqConfig.emqCoordinator.newReceiveMessageRequest())); if (messageResponseList != null && !messageResponseList.isEmpty()) { for (ReceiveMessageResponse response : messageResponseList) { fetchedMessageQueue.put(response); } } } catch (Exception e) { LOG.warn("Fetch messages exception: " + e); try { Thread.sleep(100); } catch (InterruptedException e1) { } } } } public void stopRunning() { running = false; } } private class DeleteMessageThread extends Thread { private long lastSend = System.currentTimeMillis(); private volatile boolean running = true; public DeleteMessageThread() { super("EMQSpout-delete-messages-thread"); } @Override public void run() { while (running) { try { DeleteMessageBatchRequest deleteRequest = new DeleteMessageBatchRequest(); deleteRequest.setQueueName(emqConfig.queueName); int count = 0; while (System.currentTimeMillis() - lastSend <= emqConfig.deleteMessageMaxDelayMs && count <= emqConfig.deleteMessageMaxNumPerBatch) { String ackedMessage = ackedMessagesQueue.poll(); if (ackedMessage != null) { deleteRequest.addToDeleteMessageBatchRequestEntryList(new DeleteMessageBatchRequestEntry(ackedMessage)); count++; } } if (count > 0) messageClient.deleteMessageBatch(deleteRequest); lastSend = System.currentTimeMillis(); } catch (Exception e) { LOG.warn("Delete messages failed: " + e); } } } public void stopRunning() { running = false; } } }