`
udukwilliam
  • 浏览: 33737 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

MetaQ技术内幕——源码分析(二)

 
阅读更多

消息,是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的存储非常值得借鉴。

    

    未完待续。。。。。。。

        

 

 

 

  • 大小: 41.7 KB
  • 大小: 69.7 KB
分享到:
评论

相关推荐

    metamorphosis(metaq)

    二、MetaQ架构 MetaQ采用主从复制的架构,确保服务的高可用性。每个主题(Topic)可以有多个分区(Partition),每个分区有一个主节点和多个备份节点。当主节点故障时,备份节点能够快速接管,保证服务不中断。此外...

    Metaq原理与应用

    Metaq 是一种高性能、高可用的消息中间件,其设计灵感来源于 Kafka,但并不严格遵循任何特定的规范,如 JMS(Java Message Service)或 CORBA Notification 规范。Metaq 提供了丰富的特性来解决 Messaging System 中...

    metaQ向spark传数据

    在大数据处理领域,MetaQ和Spark是两个非常关键的组件。MetaQ是腾讯开源的一款分布式消息中间件,常用于实时数据处理系统中的消息传递。而Spark则是一个强大的、通用的并行计算框架,专为大数据分析设计,尤其擅长...

    metaq-server-1.4.6.2.tar.gz

    二、MetaQ Server 1.4.6.2特性 1. 高可用:MetaQ Server通过主备切换机制确保服务的连续性,当主节点故障时,备份节点可以快速接管服务,保证业务不受影响。 2. 高性能:采用多线程并行处理和异步I/O模型,实现...

    metaq-server-1.4.6.2客户端+服务端

    MetaQ是阿里巴巴开源的一款分布式消息中间件,它主要用于在大规模分布式系统中提供高效、可靠的消息传递服务。MetaQ Server 1.4.6.2版本是这个中间件的一个特定发行版,包含了服务端和客户端的组件,以及相关的...

    Metaq在JDk 7下的异常及解决方案

    本文将深入解析这一异常的具体情况,分析其原因,并提出相应的解决方案。 异常现象主要表现为:在尝试清理内存映射文件时,由于Java反射机制调用了`java.nio.DirectByteBuffer`类中的`viewedBuffer()`方法,导致`...

    RocketMQ最全介绍与实战.pdf

    RocketMQ 的前世今生是 Metaq,Metaq 在阿里巴巴集团内部、蚂蚁金服、菜鸟等各业务中被广泛使用,接入了上万个应用系统中。 RocketMQ 的使用场景包括应用解耦、流量削峰等。应用解耦系统的耦合性越高,容错性就越低...

    Metaq详细手册.docx

    Metaq,源自LinkedIn的开源消息中间件Kafka的Java实现——Memorphosis,针对淘宝内部的应用需求进行了定制和优化。它遵循一系列设计原则,旨在提供高效、可靠且灵活的消息传递服务。 1. **消息持久化**:Metaq保证...

    metaq-server-1.4.6.2.zip 和原版一样就是换了个名字

    最后,MetaQ还提供了丰富的监控和管理工具,包括日志分析、性能指标监控和Web管理界面,方便运维人员进行故障排查和系统调优。 综上所述,MetaQ服务器1.4.6.2版本在保持原有功能的基础上,可能针对性能、稳定性和...

    metaQ的安装包

    MetaQ,全称为“Meta Message Queue”,是阿里巴巴开源的一款分布式消息中间件,主要用于解决大规模分布式系统中的消息传递问题。MetaQ 提供了高可用、高可靠的消息服务,支持多种消息模型,如点对点(Point-to-...

    metaq消息中间件服务端、客户端资源汇集

    Metamorphosis是淘宝开源的一个Java消息中间件,他类似apache-kafka,但不是一个简单的山寨拷贝,而是做了很多改进和优化,项目的主页在淘蝌蚪上。服务端、客户端、javadoc都包含在内。

    MetaQ 分布式消息服务中间件.pdf

    MetaQ是一款分布式消息服务中间件,其核心功能基于发布-订阅模型。在这一模型中,发布者(Producer)将消息发布到MetaQ,MetaQ会储存这些消息,而订阅者(Consumer)则通过pull方式来消费这些消息。具体而言,消费者...

    阿里消息中间件MetaQ学习Demo.zip

    阿里消息中间件MetaQ学习Demo

    阿里巴巴企业诚信体系——从大数据到场景应用.pdf

    阿里巴巴企业诚信体系是基于大数据和先进技术构建的一套全面的安全架构,旨在从多个维度评估和管理企业信用风险。这个体系不仅涵盖了安全威胁情报、安全建设、应急响应和法律法规等多个关键领域,还利用自动化手段...

    万亿级数据洪峰下的消息引擎——Apache RocketMQ--阿里.pdf

    ### 万亿级数据洪峰下的消息引擎——Apache RocketMQ #### 阿里消息中间件的演变历史 自2007年起,阿里巴巴集团在消息中间件领域不断探索与实践,经历了从Notify到MetaQ再到Apache RocketMQ的发展历程。以下是这一...

    支付宝钱包系统架构内部剖析(架构图)

    - **事务支持**:MetaQ支持两种类型的事务——本地事务和XA分布式事务。这两种事务类型能够满足支付宝钱包系统在处理复杂金融交易时对数据一致性的需求。 - **高可用复制**:MetaQ提供了异步复制和同步复制两种模式...

    实时数仓2.0——打怪升级之路.pdf

    综上所述,实时数仓2.0是一种先进的数据处理框架,它通过优化数据模型、提升处理速度、确保数据质量,以及利用高级状态管理技术,来满足企业对实时数据分析的高要求。这一解决方案为企业提供了更敏捷的业务洞察,...

    开源技术大会2014-CSDN-蒋涛《关于开源的思考》

    在开源的推动下,许多技术都得到了长足的发展,如LVS(Linux Virtual Server)、Tengine、MetaQ、dubbo、cobar、Fastjson等。这些技术的成功案例表明,开源不仅是技术的共享,也是知识和创新的共享。 蒋涛的讲话...

Global site tag (gtag.js) - Google Analytics