Aeron-001

7 阅读4分钟

代码


package com.study;

// ========================================
// Aeron 高性能消息传递示例 - IPC 通信
// ========================================

import io.aeron.Aeron;                 // Aeron 客户端主类,用于创建发布者和订阅者
import io.aeron.Publication;            // 发布者接口,用于发送消息
import io.aeron.Subscription;           // 订阅者接口,用于接收消息
import io.aeron.driver.MediaDriver;     // MediaDriver: Aeron 的核心组件,负责底层网络通信
import io.aeron.logbuffer.FragmentHandler; // 消息片段处理器接口
import lombok.extern.slf4j.Slf4j;       // Lombok 注解,自动生成 SLF4J 日志对象
import org.agrona.concurrent.IdleStrategy;           // 空闲策略接口,用于控制 CPU 占用
import org.agrona.concurrent.SleepingIdleStrategy;    // 基于睡眠的空闲策略实现
import org.agrona.concurrent.UnsafeBuffer; // 高性能缓冲区,避免 JVM 堆内存拷贝

import java.nio.ByteBuffer; // NIO 字节缓冲区


/**
 * Simple001: Aeron IPC(进程间通信)示例
 *
 * 本示例演示如何使用 Aeron 在同一台机器上的两个进程之间进行高效消息传递。
 * 使用 IPC(Unix Domain Socket)通道,延迟极低。
 */
@Slf4j  // Lombok 注解:为类自动生成 log 字段,用于日志输出
public class Simple001 {

    public static void main(String[] args) {
        // 入口方法:启动示例程序
        sample_run();
    }

    public static void sample_run() {
        // -------------------------------------------------------
        // 第一步:定义通信参数
        // -------------------------------------------------------

        // channel:通信通道地址
        // "aeron:ipc" 表示使用 IPC(进程间通信)协议,通过 Unix Domain Socket 传输
        // 相比 UDP/TCP,IPC 延迟更低,适合同一机器上的进程间通信
        final String channel = "aeron:ipc";

        // 要发送的消息内容
        final String message = "hello aeron";

        // streamId:流 ID,用于区分同一通道上的不同数据流
        // 类似 UDP 的端口号,可以在同一通道上建立多个独立的流
        final Integer streamId = 10;

        // -------------------------------------------------------
        // 第二步:初始化 Aeron 组件
        // -------------------------------------------------------

        // IdleStrategy(空闲策略):当没有数据时可控制 CPU 占用
        // SleepingIdleStrategy:使用 Thread.sleep() 释放 CPU,比自旋更节能
        // 其他策略还有:BusySpinIdleStrategy(忙等)、YieldingIdleStrategy(让出CPU)等
        final IdleStrategy idleStrategy = new SleepingIdleStrategy();

        // UnsafeBuffer: Aeron 使用的高性能缓冲区
        // allocateDirect(256):分配 256 字节的直接内存缓冲区
        // 直接内存(Direct Memory)不受 JVM 堆管理,可避免 GC 压力和内存拷贝
        // Aeron 底层使用这种缓冲区进行零拷贝数据传输
        final UnsafeBuffer unsafeBuffer = new UnsafeBuffer(ByteBuffer.allocateDirect(256));

        // -------------------------------------------------------
        // 第三步:启动 MediaDriver 并创建 Aeron 客户端
        // -------------------------------------------------------

        // try-with-resources:自动资源管理,确保所有资源使用完毕后自动关闭
        // MediaDriver.launch():启动嵌入式 MediaDriver
        // MediaDriver 负责底层的发送接收队列管理、可靠性和流量控制
        try (
                MediaDriver driver = MediaDriver.launch();

                // Aeron.connect():创建 Aeron 客户端
                // 客户端是应用与 MediaDriver 之间的桥梁
                Aeron aeron = Aeron.connect();

                // aeron.addSubscription():添加订阅者(接收端)
                // 订阅者监听指定 channel 和 streamId 上的消息
                // 注意:此时订阅者尚未与任何发布者建立连接
                Subscription subscription = aeron.addSubscription(channel, streamId);

                // aeron.addPublication():添加发布者(发送端)
                // 发布者向指定 channel 和 streamId 发送消息
                // 注意:此时发布者尚未与任何订阅者建立连接
                Publication publication = aeron.addPublication(channel, streamId)
        ) {

            // -------------------------------------------------------
            // 第四步:等待发布者与订阅者建立连接
            // -------------------------------------------------------

            // publication.isConnected():检查发布者是否已与至少一个订阅者建立连接
            // 在发布者和订阅者完成握手之前,发送操作不会成功
            // 这是一个可靠性机制:确保消息不会丢失在无人接收的通道中
            while (!publication.isConnected()) {
                // 尚未连接时,调用 idle() 释放 CPU 并等待
                // 这里使用 SleepingIdleStrategy,所以会使用短时睡眠
                idleStrategy.idle();
            }
            // 连接建立后,可以开始发送消息

            // -------------------------------------------------------
            // 第五步:发送消息
            // -------------------------------------------------------

            // unsafeBuffer.putStringAscii(0, message):
            // 将 Java 字符串以 ASCII 编码写入缓冲区的起始位置(偏移量 0)
            // 写入内容包括字符串长度信息和实际字节数据
            unsafeBuffer.putStringAscii(0, message);

            // 日志输出:使用 Slf4j 的 log.info()
            // {} 是占位符,会被 message 的值替换
            log.info("send {}", message);

            // publication.offer(unsafeBuffer):
            // 尝试将缓冲区内容作为消息发送
            // 返回值:
            //   - 正数(位置索引):消息已入队,等待发送
            //   - 0 或负数:通道背压(channel back-pressure),需要重试
            // 这是非阻塞操作,如果通道满会立即返回而不等待
            while (publication.offer(unsafeBuffer) < 0) {
                // 发送失败时,调用 idle() 等待一下再重试
                // 实际应用中可以通过调整空闲策略来控制重试频率
                idleStrategy.idle();
            }

            // -------------------------------------------------------
            // 第六步:接收消息
            // -------------------------------------------------------

            // FragmentHandler:消息片段处理器函数式接口
            // lambda 表达式实现:(buffer, offset, length, header) -> { ... }
            // 参数说明:
            //   - buffer:包含消息的 UnsafeBuffer
            //   - offset:消息在缓冲区中的起始偏移量
            //   - length:消息的实际长度(字节数)
            //   - header:消息头信息(通常用不到)
            final FragmentHandler handler = (buffer, offset, length, header) -> {
                // buffer.getStringAscii(offset):
                // 从缓冲区指定偏移量读取 ASCII 字符串
                // 注意:这里使用 length 参数更准确,但 getStringAscii 会自动读取长度信息
                log.info("receive {}", buffer.getStringAscii(offset));
            };

            // subscription.poll(handler, 1):
            // 轮询订阅者队列,尝试接收消息
            // 参数:
            //   - handler:消息处理器
            //   - 1:每次最多处理 1 个消息片段(fragment)
            // 返回值:实际处理的消息数量
            // 返回值 <= 0 表示没有收到消息
            while (subscription.poll(handler, 1) <= 0) {
                // 没有消息时,调用 idle() 等待
                idleStrategy.idle();
            }

            // 当 subscription.poll() 返回 > 0 时,handler 会被调用,日志会输出 "receive hello aeron"

        }
        // try-with-resources 自动关闭:
        // 关闭顺序与创建顺序相反:publication -> subscription -> aeron -> driver
        // 确保所有资源正确释放

    }
}

仓库地址