消息,是MetaQ最重要的资源,在分析MetaQ之前必须了解的概念,我们所做的一切都是围绕消息进行的,让我们看看MetaQ中消息的定义是怎样的,MetaQ的类Message定义了消息的格式:
- public class Message implements Serializable {
- private long id; //消息的ID
- private String topic; //消息属于哪个主题
- private byte[] data; //消息的内容
- private String attribute; //消息的属性
- private int flag; //属性标志位,如果属性不为空,则该标志位true
- private Partition partition; //该主题下的哪个分区,简单的理解为发送到该主题下的哪个队列
- private transient boolean readOnly; //消息是否只读
- private transient boolean rollbackOnly = false; //该消息是否需要回滚,主要用于事务实现的需要
- }
从对消息的定义,我们可以看出消息都有一个唯一的ID,并且归属于某个主题,发送到该主题下的某个分区,具有一些基本属性。
MetaQ分为Broker和Client以及Zookeeper,系统结构如下:
下面我们先分析Broker源码。
Broker主要围绕发送消息和消费消息的主线进行,对于Broker来说就是输入、输出流的处理。在该主线下,Broker主要分为如下模块:网络传输模块、消息存储模块、消息统计模块以及事务模块,本篇首先针对独立性较强的消息存储模块进行分析。
在进行存储模块分析之前,我们得了解Broker中的一个重要的类 MetaConfig,MetaConfig是Broker配置加载器,通过MetaConfig可以获取到各模块相关的配置,所以MetaConfig 是贯穿所有模块的类。MetaConfig实现MetaConfigMBean接口,该接口定义如下:
- public interface MetaConfigMBean {
- /**
- * Reload topics configuration
- */
- public void reload();
- /**关闭分区 */
- public void closePartitions(String topic, int start, int end);
- /**打开一个topic的所有分区 */
- public void openPartitions(String topic);
- }
MetaConfig注册到了MBeanServer上,所以可以通过JMX协议重新加 载配置以及关闭和打开分区。为了加载的配置立即生效,MetaConfig内置了一个通知机制,可以通过向MetaConfig注册监听器的方式关注相关 配置的变化,监听器需实现PropertyChangeListener接口。
- public void addPropertyChangeListener(final String propertyName, final PropertyChangeListener listener) {
- this.propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
- }
- public void removePropertyChangeListener(final PropertyChangeListener listener) {
- this.propertyChangeSupport.removePropertyChangeListener(listener);
- }
目前MetaConfig发出的事件通知有三种:配置文件发生变化(configFileChecksum)、主题发生变化(topics,topicConfigMap)以及刷新存储的频率发生变化(unflushInterval),代码如下:
- //configFileChecksum通知
- private Ini createIni(final File file) throws IOException, InvalidFileFormatException {
- ……
- this.propertyChangeSupport.firePropertyChange("configFileChecksum", null, null);
- return conf;
- }
- public void setConfigFileChecksum(long configFileChecksum) {
- this.configFileChecksum = configFileChecksum;
- this.propertyChangeSupport.firePropertyChange("configFileChecksum", null, null);
- }
- //topics、topicConfigMap和unflushInterval通知
- private void populateTopicsConfig(final Ini conf) {
- ……
- if (!newTopicConfigMap.equals(this.topicConfigMap)) {
- this.topics = newTopics;
- this.topicConfigMap = newTopicConfigMap;
- this.propertyChangeSupport.firePropertyChange("topics", null, null);
- this.propertyChangeSupport.firePropertyChange("topicConfigMap", null, null);
- }
- this.propertyChangeSupport.firePropertyChange("unflushInterval", null, null);
- ……
需要注意的是,调用reload方法时,只对topic的配置生效,对全局配置不生效,只重载topic的配置。
好吧,废话了许多,让我们正式进入存储模块的分析吧。
Broker的存储模块用于存储Client发送的等待被消费的消息,Broker采用文件存储的方式来存储消息,存储模块类图如下:
MessageSet代表一个消息集合,可能是一个文件也可能是文件的一部分,其定义如下:
- /**
- * 消息集合
- */
- public interface MessageSet {
- public MessageSet slice(long offset, long limit) throws IOException; //获取一个消息集合
- public void write(GetCommand getCommand, SessionContext ctx);
- public long append(ByteBuffer buff) throws IOException; //存储一个消息,这时候还没有存储到磁盘,需要调用flush方法才能保证存储到磁盘
- public void flush() throws IOException; //提交到磁盘
- public void read(final ByteBuffer bf, long offset) throws IOException; //读取消息
- public void read(final ByteBuffer bf) throws IOException; //读取消息
- public long getMessageCount();//该集合的消息数量
- }
FileMessageSet实现了MessageSet接口和Closeable接口,实现Closeable接口主要是为了在文件关闭的时候确保文件通道关闭以及内容是否提交到磁盘
- public void close() throws IOException {
- if (!this.channel.isOpen()) {
- return;
- }
- //保证在文件关闭前,将内容提交到磁盘,而不是在缓存中
- if (this.mutable) {
- this.flush();
- }
- //关闭文件通道
- this.channel.close();
- }
下面让我们来具体分析一下FileMessageSet这个类,
- public class FileMessageSet implements MessageSet, Closeable {
- ……
- private final FileChannel channel; //对应的文件通道
- private final AtomicLong messageCount; //内容数量
- private final AtomicLong sizeInBytes;
- private final AtomicLong highWaterMark; // 已经确保写入磁盘的水位
- private final long offset; // 镜像offset
- private boolean mutable; // 是否可变
- public FileMessageSet(final FileChannel channel, final long offset, final long limit, final boolean mutable) throws IOException {
- this.channel = channel;
- this.offset = offset;
- this.messageCount = new AtomicLong(0);
- this.sizeInBytes = new AtomicLong(0);
- this.highWaterMark = new AtomicLong(0);
- this.mutable = mutable;
- if (mutable) {
- final long startMs = System.currentTimeMillis();
- final long truncated = this.recover();
- if (this.messageCount.get() > 0) {
- log.info("Recovery succeeded in " + (System.currentTimeMillis() - startMs) / 1000 + " seconds. " + truncated + " bytes truncated.");
- }
- } else {
- try {
- this.sizeInBytes.set(Math.min(channel.size(), limit) - offset);
- this.highWaterMark.set(this.sizeInBytes.get());
- } catch (final Exception e) {
- log.error("Set sizeInBytes error", e);
- }
- }
- }
- // 注意FileMessageSet的mutable属性,如果mutable为true的时候,将会调用recover()方法,该方法主要是验证文件内 容的完整性,后面会详细介绍,如果mutable为false的时候,表明该文件不可更改,这个时候磁盘水位和sizeInBytes的值均为文件大小。
- ……
- public long append(final ByteBuffer buf) throws IOException {
- //如果mutable属性为false的时候,不允许追加消息在文件尾
- if (!this.mutable) {
- throw new UnsupportedOperationException("Immutable message set");
- }
- final long offset = this.sizeInBytes.get();
- int sizeInBytes = 0;
- while (buf.hasRemaining()) {
- sizeInBytes += this.channel.write(buf);
- }
- //在这个时候并没有将内容写入磁盘,还在通道的缓存中,需要在适当的时候调用flush方法
- //这个时候磁盘的水位是不更新的,只有在保证写入磁盘才会更新磁盘的水位信息
- this.sizeInBytes.addAndGet(sizeInBytes); 、
- this.messageCount.incrementAndGet();
- return offset;
- }
- //提交到磁盘
- public void flush() throws IOException {
- this.channel.force(true);
- this.highWaterMark.set(this.sizeInBytes.get());
- }
- ……
- @Override
- public MessageSet slice(final long offset, final long limit) throws IOException {
- //返回消息集
- return new FileMessageSet(this.channel, offset, limit, false);
- }
- static final Logger transferLog = LoggerFactory.getLogger("TransferLog");
- @Override
- public void read(final ByteBuffer bf, final long offset) throws IOException {
- //读取内容
- int size = 0;
- while (bf.hasRemaining()) {
- final int l = this.channel.read(bf, offset + size);
- if (l < 0) {
- break;
- }
- size += l;
- }
- }
- @Override
- public void read(final ByteBuffer bf) throws IOException {
- this.read(bf, this.offset);
- }
- //下面的write方法主要是提供zero拷贝
- @Override
- public void write(final GetCommand getCommand, final SessionContext ctx) {
- final IoBuffer buf = this.makeHead(getCommand.getOpaque(), this.sizeInBytes.get());
- // transfer to socket
- this.tryToLogTransferInfo(getCommand, ctx.getConnection());
- ctx.getConnection().transferFrom(buf, null, this.channel, this.offset, this.sizeInBytes.get());
- }
- public long write(final WritableByteChannel socketChanel) throws IOException {
- try {
- return this.getFileChannel().transferTo(this.offset, this.getSizeInBytes(), socketChanel);
- } catch (final IOException e) {
- // Check to see if the IOException is being thrown due to
- // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5103988
- final String message = e.getMessage();
- if (message != null && message.contains("temporarily unavailable")) {
- return 0;
- }
- throw e;
- }
- }
- ……
- private static boolean fastBoot = Boolean.valueOf(System.getProperty("meta.fast_boot", "false"));
- private long recover() throws IOException {
- //如果在System属性里设置了 meta.fast_boot为true,表示快速启动,快速启动不检测文件是否损坏,不对内容进行校验
- if (fastBoot) {
- final long size = this.channel.size();
- this.sizeInBytes.set(size);
- this.highWaterMark.set(size);
- this.messageCount.set(0);
- this.channel.position(size);
- return 0;
- }
- if (!this.mutable) {
- throw new UnsupportedOperationException("Immutable message set");
- }
- final long len = this.channel.size();
- final ByteBuffer buf = ByteBuffer.allocate(MessageUtils.HEADER_LEN);
- long validUpTo = 0L;
- long next = 0L;
- long msgCount = 0;
- do {
- next = this.validateMessage(buf, validUpTo, len);
- if (next >= 0) {
- msgCount++;
- validUpTo = next;
- }
- } while (next >= 0);
- this.channel.truncate(validUpTo);
- this.sizeInBytes.set(validUpTo);
- this.highWaterMark.set(validUpTo);
- this.messageCount.set(msgCount);
- this.channel.position(validUpTo);
- return len - validUpTo;
- }
- 消息在磁盘上存储结构如下:
- /*
- * 20个字节的头部 *
- * <ul>
- * <li>message length(4 bytes),including attribute and payload</li>
- * <li>checksum(4 bytes)</li>
- * <li>message id(8 bytes)</li>
- * <li>message flag(4 bytes)</li>
- * </ul>
- * length长度的内容
- */
- //在存储之前将checksum的信息存入到磁盘,读取的时候再进行校验,比较前后的
- //checksum是否一致,防止消息被篡改
- private long validateMessage(final ByteBuffer buf, final long start, final long len) throws IOException {
- buf.rewind();
- long read = this.channel.read(buf);
- if (read < MessageUtils.HEADER_LEN) {
- return -1;
- }
- buf.flip();
- final int messageLen = buf.getInt();
- final long next = start + MessageUtils.HEADER_LEN + messageLen;
- if (next > len) {
- return -1;
- }
- final int checksum = buf.getInt();
- if (messageLen < 0) {
- // 数据损坏
- return -1;
- }
- final ByteBuffer messageBuffer = ByteBuffer.allocate(messageLen);
- // long curr = start + MessageUtils.HEADER_LEN;
- while (messageBuffer.hasRemaining()) {
- read = this.channel.read(messageBuffer);
- if (read < 0) {
- throw new IOException("文件在recover过程中被修改");
- }
- // curr += read;
- }
- if (CheckSum.crc32(messageBuffer.array()) != checksum) {
- //采用crc32对内容进行校验是否一致
- return -1;
- } else {
- return next;
- }
- }
FileMessageSet是MetaQ 服务器端存储的一个元素,代表了对一个文件的读写操作,能够对文件内容进行完整性和一致性校验,并能提供统计数据,接下来要介绍的是通过 MessageStore怎样的组合将一个个的FileMessageSet,单纯的讲,MetaQ的存储非常值得借鉴。
相关推荐
二、MetaQ架构 MetaQ采用主从复制的架构,确保服务的高可用性。每个主题(Topic)可以有多个分区(Partition),每个分区有一个主节点和多个备份节点。当主节点故障时,备份节点能够快速接管,保证服务不中断。此外...
Metaq 是一种高性能、高可用的消息中间件,其设计灵感来源于 Kafka,但并不严格遵循任何特定的规范,如 JMS(Java Message Service)或 CORBA Notification 规范。Metaq 提供了丰富的特性来解决 Messaging System 中...
在大数据处理领域,MetaQ和Spark是两个非常关键的组件。MetaQ是腾讯开源的一款分布式消息中间件,常用于实时数据处理系统中的消息传递。而Spark则是一个强大的、通用的并行计算框架,专为大数据分析设计,尤其擅长...
二、MetaQ Server 1.4.6.2特性 1. 高可用:MetaQ Server通过主备切换机制确保服务的连续性,当主节点故障时,备份节点可以快速接管服务,保证业务不受影响。 2. 高性能:采用多线程并行处理和异步I/O模型,实现...
MetaQ是阿里巴巴开源的一款分布式消息中间件,它主要用于在大规模分布式系统中提供高效、可靠的消息传递服务。MetaQ Server 1.4.6.2版本是这个中间件的一个特定发行版,包含了服务端和客户端的组件,以及相关的...
本文将深入解析这一异常的具体情况,分析其原因,并提出相应的解决方案。 异常现象主要表现为:在尝试清理内存映射文件时,由于Java反射机制调用了`java.nio.DirectByteBuffer`类中的`viewedBuffer()`方法,导致`...
最后,MetaQ还提供了丰富的监控和管理工具,包括日志分析、性能指标监控和Web管理界面,方便运维人员进行故障排查和系统调优。 综上所述,MetaQ服务器1.4.6.2版本在保持原有功能的基础上,可能针对性能、稳定性和...
《Metaq详细手册》 Metaq,源自LinkedIn的开源消息中间件Kafka的Java实现——Memorphosis,针对淘宝内部的应用需求进行了定制和优化。它遵循一系列设计原则,旨在提供高效、可靠且灵活的消息传递服务。 1. **消息...
- 日志分析:监控日志可以帮助排查问题,优化系统性能。 - 性能调优:根据系统负载调整 Broker 和 Controller 的配置,如内存分配、线程池大小等。 - 故障恢复:如果某台服务器出现故障,可以通过 ZooKeeper 自动...
Metamorphosis是淘宝开源的一个Java消息中间件,他类似apache-kafka,但不是一个简单的山寨拷贝,而是做了很多改进和优化,项目的主页在淘蝌蚪上。服务端、客户端、javadoc都包含在内。
MetaQ是一款分布式消息服务中间件,其核心功能基于发布-订阅模型。在这一模型中,发布者(Producer)将消息发布到MetaQ,MetaQ会储存这些消息,而订阅者(Consumer)则通过pull方式来消费这些消息。具体而言,消费者...
Metamorphosis(MetaQ)是一个高性能、高可用、可扩展的分布式消息中间件,类似于LinkedIn的Kafka,具有消息存储顺序写、吞吐量大和支持本地和XA事务等特性,适用于大吞吐量、顺序消息、广播和日志数据传输等场景,在...
在实际应用中,RocketMQ常用于构建大型分布式系统的事件驱动架构,例如订单处理、日志收集、用户行为分析等场景。由于其优秀的性能和稳定性,RocketMQ在电商、金融、物联网等多个领域得到了广泛应用。学习和掌握...
通过上述分析可以看出,支付宝钱包系统的架构设计不仅考虑到了高并发场景下的性能优化问题,还特别注重了系统的稳定性和安全性。其中,Metamorphosis (MetaQ)作为支付宝内部广泛使用的一款分布式消息中间件,凭借其...
RocketMQ 的前世今生是 Metaq,Metaq 在阿里巴巴集团内部、蚂蚁金服、菜鸟等各业务中被广泛使用,接入了上万个应用系统中。 RocketMQ 的使用场景包括应用解耦、流量削峰等。应用解耦系统的耦合性越高,容错性就越低...
Metamorphosis, 一种高可用高性能的分布式 #新闻MetaQ 1.4.6.2 发布。更新日志MetaQ 1.4.6.1 发布。更新日志MetaQ 1.4.5.1 发布。更新日志MetaQ 1.4.5发布。更新日志meta: 一个用于的ruby 客户端。 源代码
该文档为storm模拟项目系列文档之一,是MetaQ与storm接口的说明文档,主要介绍了如何集成MetaQ到项目代码中。软件(阿里),其对应的许多技术文档还是比较容易看的,并且Github提供了许多的应用实例,所以使用MetaQ...