前言

Mocha 是一个基于 .NET 开发的 APM 系统,同时提供可伸缩的可观测性数据分析和存储平台。

更多关于 Mocha 的介绍,可以参考 https://www.cnblogs.com/eventhorizon/p/17979677

Mocha 会需要收集大量的数据,为处理这些数据,我们需要有一个缓冲区。初期我们实现了一个基于内存的缓冲区,下文称之为 MemoryBufferQueue。

Buffer 模块的代码地址:

https://github.com/dotnetcore/mocha/tree/main/src/Mocha.Core/Buffer

本文介绍的版本是 v0.1.0,后续版本可能会有变化。

MemoryBufferQueue 功能概述

MemoryBufferQueue 将数据缓冲到内存中,消费者可以从队列中获取数据,当队列中无数据时,消费者会异步等待数据到来。

MemoryBufferQueue 提供了以下功能:

  1. 支持创建多个 Topic,每个 Topic 都是一个独立的队列。
  2. 支持创建多个 Consumer Group,每个 Consumer Group 的消费进度都是独立的。支持多个 Consumer Group 并发消费同一个 Topic。
  3. 支持同一个 Consumer Group 创建多个 Consumer,以负载均衡的方式消费数据。
  4. 支持数据的批量消费,可以一次性获取多条数据。
  5. 支持重试机制,当消费者处理数据失败时,可以选择不确认消费,这样数据会被重新消费。

需要注意的是,当前版本出于简化实现的考虑,暂不支持消费者的动态扩容和缩容,需要在创建消费者时指定消费者数量。

Buffer 模块 API 设计

MemoryBufferQueue 的出发点的是在项目初期提供一个性能足够高的内存缓存队列。后期随着项目的发展,我们可能会将其替换为别的实现,比如支持持久化的队列。

为了解耦,Buffer 模块使用 Interface 进行了抽象。

public interface IBufferQueue
{
IBufferProducer<T> CreateProducer<T>(string topicName); IBufferConsumer<T> CreateConsumer<T>(BufferConsumerOptions options); IEnumerable<IBufferConsumer<T>> CreateConsumers<T>(BufferConsumerOptions options, int consumerNumber);
} internal interface IBufferQueue<T>
{
string TopicName { get; } IBufferProducer<T> CreateProducer(); IBufferConsumer<T> CreateConsumer(BufferConsumerOptions options); IEnumerable<IBufferConsumer<T>> CreateConsumers(BufferConsumerOptions options, int consumerNumber);
} public interface IBufferProducer<in T>
{
string TopicName { get; } ValueTask ProduceAsync(T item);
}
public interface IBufferConsumer<out T>
{
string TopicName { get; } string GroupName { get; } IAsyncEnumerable<IEnumerable<T>> ConsumeAsync(CancellationToken cancellationToken = default); ValueTask CommitAsync();
} public class BufferConsumerOptions
{
public required string TopicName { get; init; } public required string GroupName { get; init; } public bool AutoCommit { get; init; } public int BatchSize { get; init; } = 100;
}

数据通过 Producer 写入 BufferQueue,由 Consumer 进行消费。

我们对 BufferQueue 有以下的要求:

  • 同一个数据类型 下的 不同 Topic 的 BufferQueue 互不干扰。

  • 同一个 Topic 下的 不同数据类型 的 BufferQueue 互不干扰。

因此我们设计了两个层级的接口:

  • IBufferQueue:根据 TopicName类型参数 T 将请求转发给具体的 IBufferQueue<T> 实现(借助 KeyedService 实现),其中参数 T 代表 Buffer 所承载的数据实体的类型。

  • IBufferQueue<T>:具体的 BufferQueue 实现,负责管理 Topic 下的数据。属于 Buffer 模块的内部实现,不对外暴露。

Buffer 模块提供了通过 ServiceCollection 进行注册的扩展方法:

public static class BufferServiceCollectionExtensions
{
public static IServiceCollection AddBuffer(
this IServiceCollection services,
Action<BufferOptionsBuilder> configure)
{
services.AddSingleton<IBufferQueue, BufferQueue>();
configure(new BufferOptionsBuilder(services));
return services;
}
}

MemoryBufferQueue 模块通过提供 BufferOptionsBuilder 来进行配置:

public static class BufferOptionsBuilderExtensions
{
public static BufferOptionsBuilder UseMemory(
this BufferOptionsBuilder builder,
Action<MemoryBufferOptions> configure)
{
var options = new MemoryBufferOptions(builder.Services);
configure(options); return builder;
}
}

下面是配置和使用 MemoryBufferQueue 的示例:

var services = new ServiceCollection();

services.AddBuffer(options =>
{
options.UseMemory(bufferOptions =>
{
bufferOptions.AddTopic<MochaSpan>("otlp-span", Environment.ProcessorCount);
});
}); var provider = services.BuildServiceProvider(); var bufferQueue = provider.GetRequiredService<IBufferQueue>(); var producer = bufferQueue.CreateProducer<MochaSpan>("otlp-span"); var consumers = bufferQueue.CreateConsumers<MochaSpan>(new BufferConsumerOptions
{
TopicName = "otlp-span",
GroupName = "test",
AutoCommit = true, // 配置为 false 时,需要手动调用 CommitAsync 方法
BatchSize = 100
}, 2); var consumerTasks = consumers.Select(async consumer =>
{
await foreach (var batch in consumer.ConsumeAsync())
{
foreach (var item in batch)
{
Console.WriteLine(item);
}
// 如果 AutoCommit 为 false,需要手动调用 CommitAsync 方法
// await consumer.CommitAsync();
}
}); Task.Run(async () =>
{
for (int i = 0; i < 1000; i++)
{
await producer.ProduceAsync(new MochaSpan());
}
}); await Task.WhenAll(consumerTasks);

MemoryBufferQueue 的设计

Partition 的设计

为了保证消费速度,MemoryBufferQueue 将数据划分为多个 Partition,每个 Partition 都是一个独立的队列,每个 Partition 都有一个对应的消费者线程。

Producer 以轮询的方式往每个 Partition 中写入数据。

Consumer 最多不允许超过 Partition 的数量,Partition 按平均分配到组内每个 Customer 上。

当一个 Consumer 被分配了多个 Partition 时,以轮训的方式进行消费。

每个 Partition 上会记录不同消费组的消费进度,不同组之间的消费进度互不干扰。

对并发的支持

Producer 支持并发写入。

Consumer 消费时是绑定 Partition 的,为保证能正确管理 Partition 的消费进度,Consumer 不支持并发消费。

如果要增加消费速度,需创建多个 Consumer。

Partition 的动态扩容

Partition 的基本组成单元是 Segment,Segment 代表保存数据的数组,多个 Segment 通过链表的形式组合成一个 Partition。

当一个 Segment 写满后,通过在其后面追加一个 Segment 实现扩容。

Segment 中用于保存数据的数组的每一个元素称为 Slot,每个 Slot 都有一个Partition 内唯一的自增 Offset。

Segment 的回收机制

每次在 Partition 中新增 Segment 时,会从头判断此前的 Segment 是否已经被所有消费组消费完,回收最后一个消费完的 Segment 作为新的 Segment 追加到 Partition 末尾使用。

欢迎关注个人技术公众号

Mocha MemoryBufferQueue 设计概述的更多相关文章

  1. Android设计 - 图标设计概述(Iconography)

    2014-10-30 张云飞VIR 翻译自:https://developer.android.com/design/style/iconography.html Iconography 图标设计概述 ...

  2. HTML&CSS精选笔记_HTML与CSS网页设计概述

    HTML与CSS网页设计概述 Web基本概念 认识网页 网页主要由文字.图像和超链接等元素构成.当然,除了这些元素,网页中还可以包含音频.视频以及Flash等. 名词解释 Internet网络 就是通 ...

  3. Axure学习笔记1--原型设计概述

    Axure原型 1.原型的出现 -软件功能复杂,用户需求多 -挖掘用户的实际需求 -项目组之间降低沟通成本 2.类型: [草图原型]描述产品大概需求,记录瞬间灵感 [低保真原型]展示系统的大致结构和基 ...

  4. Power Gating的设计(概述)

    Leakage power随着CMOS电路工艺进程,功耗越来越大. Power Domain的开关一般通过硬件中的timer和系统层次的功耗管理软件来进行控制,需要在一下几方面做trade-off: ...

  5. UML+模式设计概述

    转自于:http://blog.csdn.net/rexuefengye/article/details/13020225 工程学:工程庞大到一定程度必须是用工程学方法,好比直接用水泥沙子建设实用的摩 ...

  6. Mysql数据库(一)数据库设计概述

    1.数据库的体系结构 1.1 数据库系统的三级模式结构是指模式.外模式和内模式. 1.2 三级模式之间的映射分为外模式/模式映射和模式/内模式映射. 2.E-R图也称“实体-关系图”,用于描述现实世界 ...

  7. Spring MVC 设计概述

      MVC设计的根本原因在于解耦各个模块 Spring MVC的架构 对于持久层而言,随着软件发展,迁移数据库的可能性很小,所以在大部分情况下都用不到Hibernate的HQL来满足移植数据库的要求. ...

  8. DDD领域驱动设计-概述-Ⅰ

     如果我看得更远,那是因为我站在巨人的肩膀上.(If I have seen further it is by standing on ye shoulder of Giants.)         ...

  9. Databend 设计概述 | 白皮书

    Databend 是一个开源的.完全面向云架构的新式数仓,它提供快速的弹性扩展能力,并结合云的弹性.简单性和低成本,使 Data Cloud 构建变得更加容易. Databend 把数据存储在像 AW ...

  10. 现代JVM内存管理方法的发展历程,GC的实现及相关设计概述(转)

    JVM区域总体分两类,heap区和非heap区.heap区又分:Eden Space(伊甸园).Survivor Space(幸存者区).Tenured Gen(老年代-养老区). 非heap区又分: ...

随机推荐

  1. 【JAVA基础】日志管理

    LOGGER.debug("Request uri: {}, headers: {}", signedRequest.getURI(), signedRequest.getAllH ...

  2. SpringBoot 项目实战 | 瑞吉外卖 Day01

    一.软件开发整体介绍 1.软件开发流程 2.角色分工 项目经理:对整个项目负责,任务分配.把控进度 产品经理:进行需求调研,输出需求调研文档.产品原型等 UI设计师:根据产品原型输出界面效果图 架构师 ...

  3. Printer Queue,UVa 12100 (自定义标记法 + 优先队列)

    题目描述: 我们需要用打印机打印任务.每个任务都有1~9间的优先级,优先级越高,任务越急. 打印机的运作方式:从打印队列里取出一个任务j,如果队列里有比j更急的任务,则直接把j放到打印队列尾部,否则打 ...

  4. Java 并发编程之 JMM & volatile 详解

    本文从计算机模型开始,以及CPU与内存.IO总线之间的交互关系到CPU缓存一致性协议的逻辑进行了阐述,并对JMM的思想与作用进行了详细的说明.针对volatile关键字从字节码以及汇编指令层面解释了它 ...

  5. distributor和gateway联合实现出中继的负载均衡+故障转移

    概述 freeswitch是一款简单好用的VOIP开源软交换平台. 在之前的文章,我们介绍过distributor模块实现多线路分发的配置方法,但是当线路发生故障时,distributor并不会自动跳 ...

  6. MES系统初探(一)

    什么是MES系统 MES系统是制造执行系统(Manufacturing Execution System)的缩写,是一种用于监控.控制和优化制造过程的软件系统.它主要负责协调生产计划.生产调度.生产执 ...

  7. 【Spring 5核心原理】1设计模式

    1.1开闭原则 开闭原则(open-closed principle,OCP)是指一个软件实体(如类,模块和函数)应该对扩展开放,对修改关闭.所谓的开闭,也正是对扩展和修改两个行为的一个原则. 强调用 ...

  8. [转帖]s3对象存储挂载到本地文件夹

    https://www.zhangzhuo.ltd/articles/2021/10/22/1634888049032.html 一.s3fs工具 s3fs-fuse 是一个采用 c++ 开发的开源应 ...

  9. [转帖]configure: error: cannot guess build type;you must specify one

    该问题一般出现在国产平台,从错误描述来看,意思是:无法猜测build类型,你必须指定一个. 解决办法: 1. 在系统/usr路径下搜索 config.guess 和 config.sub 这两个文件. ...

  10. Grafana监控OracleDB的完整过程

    Grafana监控OracleDB的完整过程 背景 两年前曾经写过一个进行Oracle 监控的简单blog 但是周天晚上尝试进行处理时发现很不完整了. 很多数据获取不到. 晚上又熬夜了好久进行处理. ...