MetaQ源码分析——架构、配置、消息存储

本文深入分析MetaQ的整体架构,包括MetaQ的工程结构、Broker和Client的角色以及Zookeeper的使用。在源码层面,详细探讨了Broker的配置模块、消息存储模块,特别是MessageSet和MessageStore的实现。MessageSet代表文件消息集合,负责文件读写和完整性校验;MessageStore通过Segment组织多文件存储,保证分区消息的有序性。
摘要由CSDN通过智能技术生成

MetaQ总体架构

在这里插入图片描述

MetaQ工程结构

在这里插入图片描述

  • metamorphosis-client:生产者和消费者客户端
  • metamorphosis-client-extension:扩展的客户端。用于将消费处理失败的消息存入notify(未提供),和使用meta作为log4j appender,可以透明地使用log4j API发送消息到meta
  • metamorphosis-commons:客户端和服务端一些公用的东西
  • metamorphosis-dashboard,:metaQ的Web信息展示
  • metamorphosis-example: 客户端使用的例子
  • metamorphosis-http-client: 使用http协议的客户端
  • metamorphosis-server: 服务端工程
  • metamorphosis-server-wrapper: 扩展的服务端,用于将其他插件集成到服务端,提供扩展功能,支持高可用的同步异步复制
  • metamorphosis-storm-spout: 用于将meta消息接入到twitter storm集群做实时分析
  • metamorphosis-tools: 提供服务端管理和操作的一些工具

工程依赖图大致如下:
在这里插入图片描述
源码分析也分为Server和Client进行


MetaQ-Server源码分析

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实现上面这个接口
在这里插入图片描述

public class MetaConfig extends Config implements Serializable, MetaConfigMBean {
   
    static final long serialVersionUID = -1L;
    private int brokerId = 0;
    private String dataPath = System.getProperty("user.home") + File.separator + "meta";
    private int serverPort = 8123;
    private int dashboardHttpPort = 8120;
    private String hostName;
    private int numPartitions = 1;
    private int unflushThreshold = 1000;
    private int unflushInterval = 10000;
    private int maxSegmentSize = 1 * 1024 * 1024 * 1024;
    private int maxTransferSize = 1024 * 1024;

    private List<String> topics = new ArrayList<String>();

	......

MetaConfig注册到了MBeanServer上,所以可以通过JMX协议重新加载配置以及关闭和打开分区。

@Override
    public void reload() {
   
        final File file = new File(this.path);
        if (file.lastModified() != this.lastModified) {
   
            try {
   
                log.info("Reloading topics......");
                final Ini conf = this.createIni(file);
                MetaConfig.this.populateTopicsConfig(conf);
                log.info("Reload topics successfully");
            }
            catch (final Exception e) {
   
                log.error("Reload config failed", e);
            }
        }
    }

  • Ini可以看做是处理配置的一个类
  • 调用reload之后,可以看到只对topic配置生效,而对于全局的配置不生效,只是重新加载topic的配置。

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);

消息存储模块

Broker的存储模块用于存储Client发送的等待被消费的消息,Broker采用文件存储的方式来存储消息,存储模块的类图如下:
在这里插入图片描述

1. MessageSet模块

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 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; // 是否可变

    static final Log log = LogFactory.getLog(FileMessageSet.class);

关闭文件通道函数:

public void close() throws IOException {
   
      if (!this.channel.isOpen()) {
   
          return;
      }
      //保证在文件关闭前,将内容提交到磁盘,而不是在缓存中
      if (this.mutable) {
   
          this.flush();
      }
      //关闭文件通道
      this.channel.close();
  }

因为MetaQ的消息都是使用文件存储的,因此具体分析一下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; // 是否可变

    static final Log log = LogFactory.getLog(FileMessageSet.class);
  • 说一下文件通道
    • 通道既不是一个扩展也不是一项增强,而是全新的、极好的Java I/O示例,提供与I/O服务的直接连接。Channel用于在字节缓冲区和位于通道另一侧的实体(通常是一个文件或套接字)之间有效地传输数据。
    • 一个FileChannel对象却只能通过在一个打开的RandomAccessFile、FileInputStream或FileOutputStream对象上调用getChannel()方法来获取,开发者不能直接创建一个FileChannel。调用getChannel()方法会返回一个连接到相同文件的FileChannel对象且该FileChannel对象具有与File对象相同的访问权限,然后就可以使用通道对象来利用强大的FileChannel API了。
    • FileChannel对象是线程安全的,多个进程可以在同一个实例上并发调用方法而不会引起任何问题,不过并非所有的操作都是多线程的。影响通道位置或者影响文件的操作都是单线程的,如果有一个线程已经在执行会影响通道位置或文件大小的操作,那么其他尝试进行此类操作之一的线程必须等待,并发行为也会受到底层操作系统或文件系统的影响。
    • 通道只能使用ByteBuffer。write方法写ByteBuffer中的内容至文件中,注意写之前还是要先把ByteBuffer给flip一下。

FileMessageSet构造函数

public FileMessageSet(final FileChannel channel, final long offset, final long limit, final boolean mutable)
            throws IOException {
   
        super();
        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(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值