rocketmq 概述
********************************
集群组成
name server:在内存中存储集群的元数据,定时接受各节点上传数据,并为proudcer、consumer提供路由服务;name server之间相互独立,无节点通信
broker:存储消息,并与name server建立长连接,定时发送心跳;可主从部署,提高集群的可用性
producer:从name server获取元数据,与broker建立长连接,发送消息
consumer:从name server获取元数据,与broker建立长连接,消费消息
启动流程:先开启name server,再开启broker,然后使用producer、consumer收发消息
********************************
name server
name server中的数据存储在内存,不会持久化到磁盘;
name server可部署多个,相互之间独立,接受broker上传元数据信息;
producer、consumer从name server拉取元数据,路由到broker进行消息收发
name server存储的元数据
public class RouteInfoManager {
private static final InternalLogger log = InternalLoggerFactory.getLogger("RocketmqNamesrv");
private static final long BROKER_CHANNEL_EXPIRED_TIME = 120000L;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final HashMap<String, List<QueueData>> topicQueueTable = new HashMap(1024);
//key为topic name,value为topic对应的队列
private final HashMap<String, BrokerData> brokerAddrTable = new HashMap(128);
//key为brokerName,value为有相同brokerName的broker信息
private final HashMap<String, Set<String>> clusterAddrTable = new HashMap(32);
//key为clueterName,vlaue为同一clusterName下的brokerName集合
private final HashMap<String, BrokerLiveInfo> brokerLiveTable = new HashMap(256);
//key为brokerAddr,vlaue为broker的状态信息
private final HashMap<String, List<String>> filterServerTable = new HashMap(256);
//key为brokerAddr,vlaue为broker上的过滤服务器的名称
*****************************************
public class QueueData implements Comparable<QueueData> {
private String brokerName;
private int readQueueNums;
private int writeQueueNums;
private int perm;
private int topicSynFlag;
**********************************************
public class BrokerData implements Comparable<BrokerData> {
private String cluster;
private String brokerName;
private HashMap<Long, String> brokerAddrs;
********************************
消息存储 broker
broker负责接收producer发送的消息进行存储,并接受消费端的消息拉取请求;
同时进行主从部署,将消息发送给slave broker,实现主从备份,部署多主多从,实现高可用;
broker也提供消息的过滤服务
存储结构
commitLog:存储消息,producer端发送的消息按顺序写入commitlog,commitLog是一个目录,实际使用mapfile文件保存消息,目录中可包含多个mapfile文件,每个mapfile文件大小为1g,文件剩余空间不足则新建mapfile文件存储消息;每台broker上的commitLog被本机上所有的consume queue共享
consume queue:类似于数据库索引文件,消息写入commitlog中后,会将消息在commitlog中的偏移量等信息发送到consume queue(每个topic下的message queue都对应一个consume queue),consumer根据消息偏移量读取commitlog中的消息;每个consume queue可存储30万个节点,consume queue的内容也会写到磁盘持久存储;
index file:索引文件,根据key查询消息,使用hash+链表存储节点,方便快速查询消息;index file是一个目录,包含多个文件,单个index文件有500万个hash槽,存储2000万个节点
public class ConsumeQueue { //consume queue是个文件,每个节点存储消息在commitLog中的偏移量
private static final InternalLogger log = InternalLoggerFactory.getLogger("RocketmqStore");
public static final int CQ_STORE_UNIT_SIZE = 20;
private static final InternalLogger LOG_ERROR = InternalLoggerFactory.getLogger("RocketmqStoreError");
private final DefaultMessageStore defaultMessageStore;
private final MappedFileQueue mappedFileQueue;
private final String topic; //topic 名称
private final int queueId; //队列id
private final ByteBuffer byteBufferIndex;
private final String storePath; //consume queue存储路径
private final int mappedFileSize;
private long maxPhysicOffset = -1L;
private volatile long minLogicOffset = 0L;
private ConsumeQueueExt consumeQueueExt = null;
****************
MessageQueue:对应topic在某个master broker上的consume queue
public class MessageQueue implements Comparable<MessageQueue>, Serializable {
private static final long serialVersionUID = 6191200464116433425L;
private String topic; //topic 名称
private String brokerName; //broker 名称
private int queueId; //队列id
文件刷盘方式:同步刷盘、异步刷盘
同步刷盘:文件写入磁盘后返回写入成功状态
异步刷盘:文件提交到内存后,即可返回写成功状态,由后台线程负责刷盘,将文件写入磁盘
主从同步方式:同步主从、异步主从
同步主从:消息写入slave broker成功后,返回写成功状态
异步主从:消息发送给slave broker后即可返回,不需要接受slave broker的写入状态
********************************
消息发送
消息发送流程
设置发送组、name server、发送失败重试次数等信息;
创建要发送的消息对象;
启动producer,选择消息发送方式发送消息,根据发送方式不同进行不同的处理
说明:当topic下的某个master故障时,会自动向该topic下的可用master发送消息,保证了发送端的高可用;创建topic时,需将topic分布在多个master上,保证消息发送的高可用;如果只将topic创建在一个master上,则master故障后,消息不会正常发送;
4.5版本后,提供了主从自动切换,master故障后,slave会自动切换为master,将topic创建在一个master上,master故障后消息仍可正常发送
消息发送方式
同步发送:发送端等待获取broker响应信息,写入成功后写后续消息
异步发送:设置回调函数,发送后不需要等待就可继续写后续消息
单向发送:不等待获取broker端的响应信息,不设置回调函数
消息发送的返回状态
public enum SendStatus {
SEND_OK, //消息正常发送
FLUSH_DISK_TIMEOUT, //同步刷盘是没在规定的时间完成刷盘
FLUSH_SLAVE_TIMEOUT, //同步主从,slave刷盘超时
SLAVE_NOT_AVAILABLE; //同步主从,slave异常
private SendStatus() {
}
}
消息类型:普通消息、顺序消息、延时消息、事务消息
普通消息:不对消息设置延时发送,不顺序消费或者需要事务消费
顺序消息:消息的发送、消费有先后顺序,一般使用局部顺序消息,producer将需要顺序消费的消息发送到同一个队列;顺序消息消费的时consumer需要对consume queue加锁,broker端锁的存活时间默认为60s,持有锁的consumer故障后锁会自动释放
延时消息:消息到达broker后,按照指定的级别延时消费
事务消息:消息是否消费需要根据本地函数的执行状态来决定是否需要消费
消息重发
同步发送:发送异常、超时未收到响应信息
异步发送:超时时间内收到异常响应信息(超时未收到响应信息不会重发)
单向发送:消息不会重发,消息可能会丢失,适用于消息发送要求不高的场景
消息重复:同步发送时消息写入成功,但是超时未收到响应信息导致消息重发,造成消息重复
解决方法:消费端幂等(多次调用与一次调用效果相同);
维护已消费消息记录,消费消息前查询消息是否已经消费
********************************
消息消费
消息消费流程
设置消费组、name server、消费模式等信息;
订阅topic,设置过滤标识tag;
注册消息监听(并发或者顺序消费),从consume queue拉取消息消费;
启动consumer处理消息
说明:消息消费不需要设置从master、slave读取,如果master繁忙或者故障时,consumer会自动切换到slave读取消息,保证了消费端的高可用
消费模式:广播消费、集群消费
广播消费:消息被consumer group中所有的consumer消费,消费偏移量offset存储在消费端
集群消费:消息只会被consumer group中的一个consumer消费,消费偏移量offset存储在broker端
消息拉取过程(长轮询):broker收到consumer的获取消息的请求,如果有消息则返回消息;如果没有消息,每隔5秒查看是否有消息,如果15秒依然没有消息,则返回空结果,在此期间,如果有消息达到,则直接返回消息
consumer拉取消息:消费流量控制
DefaultMQPushConsumer消息拉取的主动权在consumer端,consumer为每个message queue分配一个processQueue对象,从consumer queue拉取的消息全部存储在processQueue中;当processQueue存储的未处理消息个数、大小或者偏移量超过设定的值时,则过一段时间再去拉取消息
public class ProcessQueue {
public static final long REBALANCE_LOCK_MAX_LIVE_TIME = Long.parseLong(System.getProperty("rocketmq.client.rebalance.lockMaxLiveTime", "30000"));
public static final long REBALANCE_LOCK_INTERVAL = Long.parseLong(System.getProperty("rocketmq.client.rebalance.lockInterval", "20000"));
private static final long PULL_MAX_IDLE_TIME = Long.parseLong(System.getProperty("rocketmq.client.pull.pullMaxIdleTime", "120000"));
private final InternalLogger log = ClientLogger.getLog();
private final ReadWriteLock lockTreeMap = new ReentrantReadWriteLock();
//读写锁控制线程对消息的并发访问
private final TreeMap<Long, MessageExt> msgTreeMap = new TreeMap();
//存储拉取的的消息,key为消息偏移量,value为消息
private final AtomicLong msgCount = new AtomicLong(); //拉取的消息数量
private final AtomicLong msgSize = new AtomicLong(); //拉取的消息大小
private final Lock lockConsume = new ReentrantLock(); //使用锁实现消息的顺序消费
private final TreeMap<Long, MessageExt> consumingMsgOrderlyTreeMap = new TreeMap();
//顺序消费时存储从broker中拉取的消息
private final AtomicLong tryUnlockTimes = new AtomicLong(0L);
private volatile long queueOffsetMax = 0L;
private volatile boolean dropped = false;
private volatile long lastPullTimestamp = System.currentTimeMillis();
private volatile long lastConsumeTimestamp = System.currentTimeMillis();
private volatile boolean locked = false;
private volatile long lastLockTimestamp = System.currentTimeMillis();
private volatile boolean consuming = false;
private volatile long msgAccCnt = 0L;
public ProcessQueue() {
}
...
public long getMaxSpan() { //获取消息跨度
try {
this.lockTreeMap.readLock().lockInterruptibly();
long var1;
try {
if (this.msgTreeMap.isEmpty()) {
return 0L;
}
var1 = (Long)this.msgTreeMap.lastKey() - (Long)this.msgTreeMap.firstKey();
} finally {
this.lockTreeMap.readLock().unlock();
}
return var1;
} catch (InterruptedException var7) {
this.log.error("getMaxSpan exception", var7);
return 0L;
}
}