/** * $Id: ConsumerOffsetManager.java 1831 2013-05-16 01:39:51Z shijia.wxr $ */ package com.alibaba.rocketmq.broker.offset; import java.io.File; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.rocketmq.broker.BrokerController; import com.alibaba.rocketmq.common.MixAll; /** * Consumer���ѽ��ȹ��� * * @author vintage.wang@gmail.com shijia.wxr@taobao.com * */ public class ConsumerOffsetManager { private static final Logger log = LoggerFactory.getLogger(MixAll.BrokerLoggerName); private static final String TOPIC_GROUP_SEPARATOR = "@"; private static final String QUEUEID_OFFSET_SEPARATOR = ":"; private static final String OFFSETS_SEPARATOR = " "; private final ConcurrentHashMap<String/* topic@group */, ConcurrentHashMap<Integer, Long>> offsetTable = new ConcurrentHashMap<String, ConcurrentHashMap<Integer, Long>>(512); private volatile ConcurrentHashMap<String, ConcurrentHashMap<Integer, Long>> offsetTableLastLast; private volatile ConcurrentHashMap<String, ConcurrentHashMap<Integer, Long>> offsetTableLast; private final BrokerController brokerController; public ConsumerOffsetManager(BrokerController brokerController) { this.brokerController = brokerController; } public boolean load() { try { String fileName = this.brokerController.getBrokerConfig().getConsumerOffsetPath(); String content = MixAll.file2String(fileName); if (content != null) { Properties prop = MixAll.string2Properties(content); if (prop != null) { return this.decode(prop); } } } catch (Exception e) { } return true; } private boolean decode(final Properties prop) { Set<Object> keyset = prop.keySet(); for (Object object : keyset) { String key = object.toString(); String value = prop.getProperty(key); if (value != null) { String[] items = value.split(OFFSETS_SEPARATOR); if (items != null) { for (String item : items) { String[] pair = item.split(QUEUEID_OFFSET_SEPARATOR); try { int queueId = Integer.parseInt(pair[0]); long offset = Long.parseLong(pair[1]); this.commitOffset(key, queueId, offset); } catch (NumberFormatException e) { e.printStackTrace(); } } } } } return true; } private static ConcurrentHashMap<String, ConcurrentHashMap<Integer, Long>> cloneOffsetTable( final ConcurrentHashMap<String, ConcurrentHashMap<Integer, Long>> input) { ConcurrentHashMap<String, ConcurrentHashMap<Integer, Long>> out = new ConcurrentHashMap<String, ConcurrentHashMap<Integer, Long>>(input.size()); for (String topicgroup : input.keySet()) { ConcurrentHashMap<Integer, Long> map = input.get(topicgroup); if (map != null) { ConcurrentHashMap<Integer, Long> mapNew = new ConcurrentHashMap<Integer, Long>(map.size()); for (Integer queueId : map.keySet()) { Long offset = map.get(queueId); Integer queueIdNew = new Integer(queueId.intValue()); Long offsetNew = new Long(offset.longValue()); map.put(queueIdNew, offsetNew); } String topicgroupNew = new String(topicgroup); out.put(topicgroupNew, mapNew); } } return out; } private String encode() { StringBuilder result = new StringBuilder(); for (String topicgroup : this.offsetTable.keySet()) { ConcurrentHashMap<Integer, Long> map = this.offsetTable.get(topicgroup); if (map != null) { StringBuilder sb = new StringBuilder(); sb.append(topicgroup); sb.append("="); boolean first = true; for (Integer queueId : map.keySet()) { Long offset = map.get(queueId); if (offset != null) { String item = queueId + QUEUEID_OFFSET_SEPARATOR + offset; if (!first) { sb.append(OFFSETS_SEPARATOR); } sb.append(item); first = false; } } if (!first) { sb.append(IOUtils.LINE_SEPARATOR); result.append(sb.toString()); } } } return result.toString(); } /** * ���ж�ʱ�̶߳�ʱˢ�� */ public void flush() { String content = this.encode(); if (content != null && content.length() > 0) { String fileName = this.brokerController.getBrokerConfig().getConsumerOffsetPath(); boolean result = MixAll.string2File(content, fileName); log.info("flush consumer offset, " + fileName + (result ? " OK" : " Failed")); } } /** * ���ж�ʱ�̶߳�ʱˢ�� */ public void flushHistory() { String content = this.encode(); if (content != null && content.length() > 0) { String fileName = this.brokerController.getBrokerConfig().getConsumerOffsetHistoryDir() + File.separator + System.currentTimeMillis(); boolean result = MixAll.string2File(content, fileName); log.info("flush consumer history offset, " + fileName + (result ? " OK" : " Failed")); } } public long computePullTPS(final String topicgroup) { ConcurrentHashMap<Integer, Long> mapLast = this.offsetTableLast.get(topicgroup); ConcurrentHashMap<Integer, Long> mapLastLast = this.offsetTableLastLast.get(topicgroup); long totalMsgs = 0; if (mapLast != null && mapLastLast != null) { for (Integer queueIdLast : mapLast.keySet()) { Long offsetLast = mapLast.get(queueIdLast); Long offsetLastLast = mapLastLast.get(queueIdLast); if (offsetLast != null && offsetLastLast != null) { long diff = offsetLast - offsetLastLast; totalMsgs += diff; } } } if (0 == totalMsgs) return 0; double pullTps = totalMsgs / this.brokerController.getBrokerConfig().getFlushConsumerOffsetHistoryInterval(); pullTps *= 1000; return Double.valueOf(pullTps).longValue(); } public void recordPullTPS() { ConcurrentHashMap<String, ConcurrentHashMap<Integer, Long>> snapshotNow = cloneOffsetTable(this.offsetTable); this.offsetTableLastLast = this.offsetTableLast; this.offsetTableLast = snapshotNow; if (this.offsetTableLast != null && this.offsetTableLastLast != null) { for (String topicgroupLast : this.offsetTableLast.keySet()) { long tps = this.computePullTPS(topicgroupLast); log.info(topicgroupLast + " pull tps, " + tps); } } } public void commitOffset(final String group, final String topic, final int queueId, final long offset) { // topic@group String key = topic + TOPIC_GROUP_SEPARATOR + group; this.commitOffset(key, queueId, offset); } public long queryOffset(final String group, final String topic, final int queueId) { // topic@group String key = topic + TOPIC_GROUP_SEPARATOR + group; ConcurrentHashMap<Integer, Long> map = this.offsetTable.get(key); if (null != map) { Long offset = map.get(queueId); if (offset != null) return offset; } return -1; } private void commitOffset(final String key, final int queueId, final long offset) { ConcurrentHashMap<Integer, Long> map = this.offsetTable.get(key); if (null == map) { map = new ConcurrentHashMap<Integer, Long>(32); map.put(queueId, offset); this.offsetTable.put(key, map); } else { map.put(queueId, offset); } } }