You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Aeron性能异常:90分位延迟<5ms但吞吐量仅2k,求排查优化方案

Aeron往返测试性能低于预期的问题分析与优化方案

我基于Aeron实现了单客户端、单服务端的往返延迟测试:客户端发送数据至服务端,服务端返回后统计耗时,但最终结果远低于预期。以下是问题代码、配置及监控数据,以及对应的优化建议:


客户端发布代码

// client publication
@Scheduled(fixedRate = 1000)
private void start() {
    if (!publishTaskEnable) {
        return;
    }

    ConcurrentPublication publication = aeron.addPublication(
            aeronProperties.getClient().getPublication().getChannel(),
            aeronProperties.getClient().getPublication().getStream());
    while (!publication.isConnected()) {
        try {
            TimeUnit.MILLISECONDS.sleep(100);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
    AtomicLong counter = new AtomicLong(0);
    LongStream.range(0, aeronProperties.getRound()).forEach(i -> publish(publication, counter));
}

private void publish(Publication publication, AtomicLong counter) {
    UnsafeBuffer buffer = new UnsafeBuffer(BufferUtil.allocateDirectAligned(16, 8));
    long count = counter.incrementAndGet();
    buffer.putLong(0, count);
    publication.offer(buffer, 0, 16);
    startTimeMap.put(count, System.currentTimeMillis());
}

客户端订阅代码

@SneakyThrows
public void receive(String channel, Integer stream) {
    clientPublication.setAeron(Aeron.connect(new Aeron.Context()
            .aeronDirectoryName(aeronProperties.getDirname())
            .idleStrategy(new NoOpIdleStrategy())));
    try (Subscription subscription = clientPublication.getAeron().addSubscription(channel, stream)) {
        IdleStrategy idleStrategy = new BusySpinIdleStrategy();
        FragmentHandler fragmentHandler = this::onMessage;
        while (true) {
            int fragmentsRead = subscription.poll(fragmentHandler, 1000);
            idleStrategy.idle(fragmentsRead);
        }
    }
}

@SneakyThrows
public void onMessage(DirectBuffer buffer, int offset, int length, Header header) {
    long data = buffer.getLong(offset);
    Long startTime = clientPublication.getStartTimeMap().get(data);
    if (startTime == null) {
        return;
    }
    long interval = System.currentTimeMillis() - startTime;
    clientPublication.getTimeList().add(interval);
    clientPublication.getStartTimeMap().remove(data);
}

Media Driver启动命令

java  --add-opens=java.base/sun.nio.ch=ALL-UNNAMED \
       -XX:+UseBiasedLocking \
       -XX:BiasedLockingStartupDelay=0 \
       -XX:+UnlockExperimentalVMOptions \
       -XX:+UseParallelGC \
       -XX:+TrustFinalNonStaticFields \
       -XX:+UnlockDiagnosticVMOptions \
       -XX:GuaranteedSafepointInterval=300000 \
       -Djava.net.preferIPv4Stack=true \
       -Daeron.dir=$AERON_DIR \
       -Daeron.threading.mode=DEDICATED \
       -Daeron.conductor.idle.strategy=org.agrona.concurrent.BusySpinIdleStrategy \
       -Daeron.sender.idle.strategy=org.agrona.concurrent.NoOpIdleStrategy \
       -Daeron.receiver.idle.strategy=org.agrona.concurrent.NoOpIdleStrategy \
       -Daeron.term.buffer.length=16M \
       -Daeron.socket.so_sndbuf=2m \
       -Daeron.socket.so_rcvbuf=2m \
       -Daeron.rcv.initial.window.length=2m \
       -Dagrona.disable.bounds.checks=true \
       -Daeron.pre.touch.mapped.memory=true \
       -cp aeron-all-1.44.1.jar io.aeron.driver.MediaDriver

监控数据

iostat

Linux 5.15.0-112-generic (VM-0-2-ubuntu)    06/23/2024  _x86_64_    (8 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          20.43    0.01    5.24    0.13    0.00   74.19

Device             tps      kB/s    rqm/s   await  areq-sz  aqu-sz  %util
loop0             0.02      0.15     0.00    0.98     7.33    0.00   0.00
loop1             0.02      0.15     0.00    1.16     8.05    0.00   0.00
loop2             0.09      0.92     0.00    0.66    10.14    0.00   0.01
loop3             0.02      0.46     0.00    1.04    21.18    0.00   0.00
loop4             0.03      0.47     0.00    1.23    15.56    0.00   0.00
loop5             0.02      0.15     0.00    1.00     8.39    0.00   0.00
loop6             0.81     31.72     0.00    0.22    39.30    0.00   0.27
loop7             0.01      0.01     0.00    0.00     1.36    0.00   0.00
sr0               1.54     54.31     0.00    0.24    35.33    0.00   0.05
vda              15.58    418.32    12.74    1.85    26.85    0.03   1.44

sar -n UDP

Linux 5.15.0-112-generic (VM-0-2-ubuntu)    06/23/2024  _x86_64_    (8 CPU)

02:18:57 PM    idgm/s    odgm/s  noport/s idgmerr/s
02:18:58 PM   7436.00    303.00      0.00      0.00
02:18:59 PM  14266.00    482.00      0.00      0.00
02:19:00 PM  18488.00    151.00      0.00      0.00
02:19:01 PM  13230.00    149.00      0.00      0.00
02:19:02 PM  16710.00    153.00      0.00      0.00
02:19:03 PM  15754.00    173.00      0.00      0.00

pidstat -t -r -p 4517

Linux 5.15.0-112-generic (VM-0-2-ubuntu)    06/23/2024  _x86_64_    (8 CPU)

01:47:56 PM   UID      TGID       TID  minflt/s  majflt/s     VSZ     RSS   %MEM  Command
01:47:56 PM  1000      4517         -     18.98      0.01  661284  111056   0.70  aeronmd
01:47:56 PM  1000         -      4517     18.98      0.01  661284  111056   0.70  |__aeronmd
01:47:56 PM  1000         -      4518      0.00      0.00  661284  111056   0.70  |__aeron_executor
01:47:56 PM  1000         -      4519      0.00      0.00  661284  111056   0.70  |__sender
01:47:56 PM  1000         -      4520      0.00      0.00  661284  111056   0.70  |__receiver

问题分析

  1. 资源重复创建:

    • 每次定时任务执行都新建ConcurrentPublication,重复建立连接、初始化资源,带来额外开销。
    • 每次publish都新建UnsafeBuffer,频繁分配直接内存,触发GC并增加分配延迟。
  2. 消息发送未处理背压:

    • publication.offer()直接调用未处理返回值,遇到背压(如BACK_PRESSURED)会丢消息,导致统计数据缺失且内存泄漏。
  3. 计时精度不足:

    • 使用System.currentTimeMillis()计时,精度仅为毫秒级,无法准确统计Aeron的微秒级延迟。
  4. 线程安全与资源管理问题:

    • receive方法重复创建Aeron实例,破坏单例设计,导致资源浪费。
    • 未明确startTimeMap和timeList的线程安全实现,多线程操作可能引发异常或数据错乱。
  5. Media Driver配置不合理:

    • Sender/Receiver线程使用NoOpIdleStrategy,低负载时会空转CPU,高负载时无法最优利用资源;应使用低延迟的BusySpinIdleStrategy。
    • 16M的Term Buffer对于16字节小消息过大,增加内存占用与缺页概率。

优化方案

代码层面优化

  1. 复用核心资源:

    • 全局初始化ConcurrentPublication,避免重复创建:
      private ConcurrentPublication publication;
      
      @PostConstruct
      public void initPublication() {
          publication = aeron.addPublication(
                  aeronProperties.getClient().getPublication().getChannel(),
                  aeronProperties.getClient().getPublication().getStream());
          while (!publication.isConnected()) {
              try {
                  TimeUnit.MILLISECONDS.sleep(100);
              } catch (InterruptedException e) {
                  Thread.currentThread().interrupt();
                  throw new RuntimeException(e);
              }
          }
      }
      
    • 复用UnsafeBuffer,避免频繁内存分配:
      private final UnsafeBuffer sendBuffer = new UnsafeBuffer(BufferUtil.allocateDirectAligned(16, 8));
      
      private void publish(Publication publication, AtomicLong counter) {
          long count = counter.incrementAndGet();
          sendBuffer.putLong(0, count);
          // 处理背压,重试发送
          long result;
          IdleStrategy idleStrategy = new BusySpinIdleStrategy();
          do {
              result = publication.offer(sendBuffer, 0, 16);
              if (result > 0) {
                  startTimeMap.put(count, System.nanoTime());
                  break;
              }
              if (result == Publication.NOT_CONNECTED) {
                  throw new IllegalStateException("Publication disconnected");
              }
              idleStrategy.idle();
          } while (true);
      }
      
  2. 提升计时精度:

    • 改用System.nanoTime()记录时间,统计时转换为微秒:
      // 记录发送时间
      startTimeMap.put(count, System.nanoTime());
      
      // 计算耗时(微秒)
      long interval = (System.nanoTime() - startTime) / 1000;
      timeList.add(interval);
      
  3. 线程安全与单例优化:

    • 使用ConcurrentHashMap和ConcurrentLinkedQueue保证线程安全:
      private final ConcurrentHashMap<Long, Long> startTimeMap = new ConcurrentHashMap<>();
      private final ConcurrentLinkedQueue<Long> timeList = new ConcurrentLinkedQueue<>();
      
    • 全局单例Aeron实例,避免重复创建:
      private final Aeron aeron = Aeron.connect(new Aeron.Context()
              .aeronDirectoryName(aeronProperties.getDirname())
              .idleStrategy(new NoOpIdleStrategy()));
      
      public void receive(String channel, Integer stream) {
          try (Subscription subscription = aeron.addSubscription(channel, stream)) {
              IdleStrategy idleStrategy = new BusySpinIdleStrategy();
              FragmentHandler fragmentHandler = this::onMessage;
              while (!Thread.currentThread().isInterrupted()) {
                  int fragmentsRead = subscription.poll(fragmentHandler, 1000);
                  idleStrategy.idle(fragmentsRead);
              }
          }
      }
      

Media Driver配置优化

  1. 调整Idle策略:
    将Sender/Receiver线程改为BusySpinIdleStrategy以降低延迟:

    -Daeron.sender.idle.strategy=org.agrona.concurrent.BusySpinIdleStrategy \
    -Daeron.receiver.idle.strategy=org.agrona.concurrent.BusySpinIdleStrategy \
    
  2. 减小Term Buffer大小:
    针对小消息场景,将Term Buffer调整为1M:

    -Daeron.term.buffer.length=1M \
    
  3. 优化GC参数:
    改用G1GC降低GC停顿对延迟的影响:

    -XX:+UseG1GC \
    -XX:MaxGCPauseMillis=10 \
    

系统层面优化

  1. 绑定CPU核心:
    将Media Driver和客户端线程绑定到固定CPU核心,减少上下文切换:

    # Media Driver绑定核心0-2
    taskset -c 0-2 java ... io.aeron.driver.MediaDriver
    
  2. 确认Socket Buffer生效:
    检查系统参数确保Socket Buffer配置生效:

    sysctl -w net.core.wmem_max=2097152
    sysctl -w net.core.rmem_max=2097152
    

内容的提问来源于stack exchange,提问作者litchi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 21:38:09