使用 MinIO 的 AIStor 构建实时数据管道

当我们的企业客户描述他们的数据管道挑战时,一个共同的主题浮现出来:他们需要在云端和本地环境中无缝衔接的实时数据处理能力。正因如此,灵活性对于 MinIO 的 AIStor 至关重要。

在本次技术深度探讨中,我将演示 AIStor 如何为需要支持从传统分析到尖端 AI/ML 工作负载的数据工程师创建统一的基础。我们将不介绍理论示例,而是研究一个基于 Kafka 的流式传输管道的实际实现,该管道同时处理两个关键功能:为数据科学家保存原始事件,同时为业务仪表板生成聚合洞察。最棒的是?无论您是在 AWS、Azure 还是您自己的数据中心运行,相同的代码都能无缝运行——无需再为了数据主权要求而牺牲功能。

此实现演示了 AIStor 如何实现基于 Java 的数据管道,无需复杂的基础架构即可处理数千万个 Kafka 事件。我们提供了完整的源代码,展示了如何将其构建为企业级解决方案,该解决方案能够同时保存合规性和数据科学所需的原始事件,并为 KPI 和仪表板生成预聚合数据集。该架构可从开发笔记本电脑线性扩展到生产集群,从而消除了灵活性、性能和部署位置之间的传统权衡。

数据管道的强大功能和灵活性

数据管道的设计方式多种多样,具体取决于您的具体需求。无论您需要流处理、批处理还是混合处理,您选择的架构都应该符合您的业务需求。今天我们将要研究的示例演示了一种简单而强大的 Kafka 事件数据处理方法,同时也适用于其他类似的流处理技术。

了解此示例管道

该管道展示了数据处理中的一种常见模式:从消息系统(Kafka)消费事件,将其以原始形式存储以供将来参考,并同时执行聚合以提取即时见解。

让我们直观地看一下整体架构:

该代码库的主要组成部分如下:

先决条件

  • x86 Linux操作系统/mac操作系统
  • 运行 Docker 引擎(colima)
    • docker cli
    • docker-compose 命令行
  • JDK 17+
  • maven
  • git
  • curl

配置参数

配置参数在以下文件中:

1、src/main/java/com/minio/training/labs/pipeline/config/AppConfig.java:

eventsToGenerate:要生成的合成事件的数量
maxPollRecords:一次从 Kafka 轮询并写入 MinIO Raw 和 Aggregate Dataset 的最大记录数

2、src/main/java/com/minio/training/labs/pipeline/processor/DirectKafkaEventProcessor.java:

POLL_TIMEOUT_MS:Kafka 消费者轮询的超时时间(以毫秒为单位)
FLUSH_THRESHOLD:在写入 MinIO 聚合数据集之前,聚合器生成的最小数据颗粒数

关键组件说明

数据生成

user_id此管道始于数据生成器,它根据预定义的模式创建合成事件。我们使用一个包含、country和action,等字段的电子商务模式进行演示value。

SchemaDefinition下面是一个事件示例,其中生成了数千万个具有基数的事件。

{
        "eventGuid": "67f4dfdd-c4db-4f8d-aa90-24da30f760ac",
        "timestamp": 1742199657865,
        "country": "country_20",
        "is_returning": true,
        "user_id": "user_id_694",
        "action": "action_3",
        "device_type": "device_type_1",
        "page": "page_9",
        "category": "category_13",
        "value": 39.259011259415466,
        "session_duration": 243
    }
    

这就是事件产生的方式。

public Event generateEvent() {
    Map<String, Object> fields = new HashMap<>();
    
    // Generate values for each column in the schema
    for (SchemaDefinition.ColumnDefinition column : schema.getColumns()) {
        fields.put(column.getName(), column.generateValue(random));
    }
    
    return new Event(fields);
}

Kafka 集成

事件被发送到 Kafka,它作为中央消息系统,负责KafkaEventProducer处理此事。


public void sendEvent(Event event) {
    try {
        String json = objectMapper.writeValueAsString(event);
        ProducerRecord<String, String> record = new ProducerRecord<>(
            config.getKafka().getTopic(),
            event.getEventGuid(),
            json
        );
        
        producer.send(record, (metadata, exception) -> {
            if (exception != null) {
                log.error("Error sending event to Kafka", exception);
            } else {
                profiler.recordProducerEvent();
            }
        });
    } catch (Exception e) {
        log.error("Error serializing event", e);
    }
}

事件处理和 MinIO 的 AIStor 集成

该管道的核心是DirectKafkaEventProcessor,它使用来自 Kafka 的事件并执行两个主要操作:

1、原始事件存储:以原始格式存储事件以供将来参考或详细分析

2、实时聚合:创建数据聚合视图以提供即时洞察
事件被消费后,会根据 进行批处理batchSize并存储到 AiStor 中。对于实时聚合,事件也会进行聚合处理,使用eventGuid滑动窗口检查重复数据,将其分组到单维度或多维度,然后写入聚合缓冲区。达到 后FLUSH_THRESHOLD,缓冲区将链接到 AIStor 并清空,以处理下一组事件。

AIStor 集成使此存储过程变得非常简单。以下是我们写入原始事件的方法:

private void writeRawEventsToJson(List<Event> events, String timestamp) {
    if (events.isEmpty()) {
        return;
    }

    try {
        // Include UUID in filename to ensure uniqueness
        String filename = String.format("events_%s_%s.json", 
            timestamp, UUID.randomUUID().toString().substring(0, 8));
        
        // Partition by year/month/day/hour for efficient querying
        String s3Key = String.format("raw-json/%s/%s/%s/%s/%s",
            "year=" + timestamp.substring(0, 4),
            "month=" + timestamp.substring(5, 7),
            "day=" + timestamp.substring(8, 10),
            "hour=" + timestamp.substring(11, 13),
            filename);

        String jsonContent = objectMapper.writeValueAsString(events);
        byte[] content = jsonContent.getBytes(StandardCharsets.UTF_8);

        // Simple AIStor/S3 PUT operation
        s3Client.putObject(PutObjectRequest.builder()
            .bucket(config.getS3().getS3Bucket())
            .key(s3Key)
            .contentType("application/json")
            .build(), 
            RequestBody.fromBytes(content));
            
        log.debug("Wrote {} raw events to MinIO: {}", events.size(), s3Key);
    } catch (Exception e) {
        log.error("Failed to write raw events to MinIO", e);    
    }
}

对于聚合事件:

private void writeAggregatedEventsToJson(List<AggregatedEvent> aggregatedEvents, String timestamp) {
    // Similar to raw events, but with processed aggregated data
    // ...
    s3Client.putObject(PutObjectRequest.builder()
        .bucket(config.getS3().getS3Bucket())
        .key(s3Key)
        .contentType("application/json")
        .build(), 
            RequestBody.fromBytes(content));
    // ...
}

使用 MinIO 的 AIStor 简化存储

该管道的一大亮点是通过 AIStor 轻松存储数据。这意味着该管道无需更改代码即可处理不断增长的数据量。

连接 AIStor 的配置很简单:

AwsBasicCredentials credentials = AwsBasicCredentials.create(
        config.getS3().getS3AccessKey(),
        config.getS3().getS3SecretKey());

s3Client = S3Client.builder()
        .endpointOverride(URI.create(config.getS3().getS3Endpoint()))
        .credentialsProvider(StaticCredentialsProvider.create(credentials))
        .region(Region.US_EAST_1)
        .httpClient(UrlConnectionHttpClient.builder().build())
        .forcePathStyle(true)
        .build();
        

实时聚合

此管道演示了一种简单但功能强大的实时聚合模式。当事件流经系统时,它们会按各种维度进行聚合,time window例如countryactioncategory.

private void processEventForAggregation(Event event) {
    // Skip if event was already processed within our deduplication window
    if (!processedEventGuids.add(event.getEventGuid())) {
        return;
    }

    LocalDateTime eventTime = LocalDateTime.ofInstant(
        Instant.ofEpochMilli(event.getTimestamp()),
        ZoneOffset.UTC
    );
    
    LocalDateTime windowStart = eventTime.truncatedTo(ChronoUnit.HOURS);
    
    String userId = event.getFields().getOrDefault(
        "user_id", "unknown").toString();
    
    for (Map.Entry<String, Object> field : event.getFields().entrySet()) {
        String fieldName = field.getKey();
        
        if (!fieldName.equals("country") && !fieldName.equals("device_type") && 
            !fieldName.equals("action") && !fieldName.equals("page") &&
            !fieldName.equals("category")) {
            continue;
        }
        
        Object fieldValue = field.getValue();
        if (fieldValue == null) {
            continue;
        }   
        // single dimension 
        String key = String.format("%s::%s:%s", 
            windowStart.format(pathFormatter), 
            fieldName, fieldValue
        );

        // multi dimension
        // String key = String.format("%s::%s:%s::%s:%s::%s:%s::%s:%s::%s:%s", 
        //         windowStart.format(pathFormatter),
        //         "country", event.getFields().get("country"),
        //         "device_type", event.getFields().get("device_type"),
        //         "action", event.getFields().get("action"),
        //         "page", event.getFields().get("page"),
        //         "category", event.getFields().get("category")
        // );

        // Update aggregations...
    }
}

这种方法如何运作

1、双重存储策略:通过存储原始数据和汇总数据,您可以获得两全其美的效果,即用于审计或深度分析的详细记录,以及通过聚合快速获取见解。

2、灵活性:无需改变核心架构,代码就可以扩展以支持不同的聚合维度或额外的处理步骤。

3、可扩展性:随着数据量的增长,系统可以通过添加更多 Kafka 分区或增加处理能力进行水平扩展。

虽然此示例展示了一种特定的实现,但相同的架构可以适用于各种工作负载

高速交易数据:通过调整聚合窗口和缓冲策略

  • 物联网传感器数据:通过修改模式并添加时间序列特定的聚合
  • 用户行为分析:通过实现更复杂的基于会话的聚合
  • 支付处理:通过添加交易验证规则和实时聚合来检测欺诈,同时保留合规的原始交易记录和财务报告的汇总数据。
  • 日志数据处理:通过实现各种日志格式的解析器,并添加错误率、性能指标和安全事件的实时聚合,并基于时间进行分区,以支持即时警报和历史分析。

在生产环境中运行管道

该数据管道旨在跨各种环境灵活部署,通过容器化可以轻松地从开发转移到生产。

Java 运行时环境

核心管道是用 Java 实现的,具有以下几个优点

  • 跨平台兼容性:在不同的操作系统上一致运行
  • 丰富的生态系统:利用成熟的库实现 Kafka、AIStor 集成
  • 企业就绪:支持监控、指标收集和日志记录集成

容器化部署

整个管道[Code]使用Docker进行容器化,简化了部署并确保了一致性。

./run_pipeline.sh

这个脚本

  • 构建 Java 应用程序

  • 启动基础设施容器(Kafka、AIStor/MinIO、Prometheus、Grafana)

  • 部署数据处理容器

  • 设置监控服务

  • Docker Compose 配置句柄

    • 容器网络
    • 持久存储的卷管理
    • 配置的环境变量注入
    • 服务依赖关系和启动顺序

凭借一致的行为和性能特征,该管道可以部署在各种环境中,从本地开发机器到生产 Kubernetes 集群。

结论

构建数据管道并不复杂。借助 AIStor 和 Kafka,您可以快速实现一个同时处理原始数据存储和实时聚合的解决方案。本示例仅演示了一种处理 Kafka 事件的方法,但相同的原理可以应用于各种数据处理需求。

Kafka 用于消息传递,AIStor 用于存储,两者的结合为构建可扩展、高弹性的数据管道奠定了坚实的基础,并可随着您的业务需求而增长。通过分离数据提取、处理和存储的关注点,您可以创建一个更易于维护和演进的系统。无论您是刚开始使用数据管道,还是希望优化现有解决方案,都可以将此架构视为一个可根据您的特定需求进行定制的模板。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值