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

Orleans极简场景性能低下,添加持久化后性能骤降求助

Orleans性能不达预期的原因分析

背景

正在为新项目评估Orleans,计划运行大量持久化Actor,但基础内存版本性能远低于预期,添加持久化和业务逻辑后性能骤降。

Grain代码

using Common.UserWallet;
using Common.UserWallet.Messages;
using Microsoft.Extensions.Logging;

namespace Grains;

public class UserWalletGrain : Orleans.Grain, IUserWalletGrain
{
    private readonly ILogger _logger;

    public UserWalletGrain(ILogger<UserWalletGrain> logger)
    {
        _logger = logger;
    }

    public async Task<CreateOrderResponse> CreateOrder(CreateOrderCommand command)
    {
        return new CreateOrderResponse(Guid.NewGuid());
    }

    public Task Ping()
    {
        return Task.CompletedTask;
    }
}

Silo配置

static async Task<IHost> StartSiloAsync()
{
    ServicePointManager.UseNagleAlgorithm = false;

    var builder = new HostBuilder()
        .UseOrleans(c =>
        {
            c.UseLocalhostClustering()
            .Configure<ClusterOptions>(options =>
            {
                options.ClusterId = "dev";
                options.ServiceId = "OrleansBasics";
            })
            .ConfigureApplicationParts(
                parts => parts.AddApplicationPart(typeof(HelloGrain).Assembly).WithReferences())

            .AddMemoryGrainStorage("OrleansMemoryProvider");
        });

    var host = builder.Build();
    await host.StartAsync();

    return host;
}

客户端代码

static async Task<IClusterClient> ConnectClientAsync()
{
    var client = new ClientBuilder()
        .UseLocalhostClustering()
        .Configure<ClusterOptions>(options =>
        {
            options.ClusterId = "dev";
            options.ServiceId = "OrleansBasics";
        })
        //.ConfigureLogging(logging => logging.AddConsole())
        .Build();

    await client.Connect();
    Console.WriteLine("Client successfully connected to silo host");

    return client;
}

static async Task DoClientWorkAsync(IClusterClient client)
{
    List<IUserWalletGrain> grains = new List<IUserWalletGrain>();

    foreach (var _ in Enumerable.Range(1, 100))
    {
        var walletGrain = client.GetGrain<IUserWalletGrain>(Guid.NewGuid());
        await walletGrain.Ping(); //make sure grain is loaded
        grains.Add(walletGrain);
    }

    var sw = Stopwatch.StartNew();
    await Parallel.ForEachAsync(Enumerable.Range(1, 100000), async (o, token) =>
    {
        var command = new Common.UserWallet.Messages.CreateOrderCommand(Guid.NewGuid(), 4, 5, new List<Guid> { Guid.NewGuid(), Guid.NewGuid() });

        var response = await grains[o % 100].CreateOrder(command);

        Console.WriteLine($"{o%10}:{o}");
    });

    Console.WriteLine($"\nElapsed:{sw.ElapsedMilliseconds}\n");
}

测试结果

  • 内存版本:100,000条消息耗时30秒,约3333条/秒,远低于预期;调整初始Grain数量(10/100/1000)性能无明显差异。
  • 配置Azure Table存储并在CreateOrder中添加await WriteStateAsync();:性能降至约280条/秒。
  • 添加基础领域逻辑、调用其他Actor后:性能低至1.2条/秒。
  • 补充:CPU使用率约50%。

性能问题原因分析

1. 客户端并发控制缺失

客户端使用Parallel.ForEachAsync默认配置发起请求,未限制并发度,会导致同时发起大量请求,超出Orleans客户端默认连接池和并发处理能力,引发请求排队、线程池阻塞,拖慢整体处理速度。

2. 同步控制台输出拖慢性能

客户端每次请求后执行Console.WriteLine,同步IO操作在高并发场景下会严重阻塞任务执行,成为性能瓶颈。

3. Azure Table存储的远程IO开销

Azure Table属于远程存储,单次WriteStateAsync()会产生网络往返开销,每个请求都触发写入会将Grain处理能力绑定到存储的IO吞吐量,280条/秒基本符合未优化下单表的写入上限。

4. 跨Grain调用的串行等待

添加跨Grain调用后,每个调用涉及序列化/反序列化、网络传输、目标Grain调度等待,若业务逻辑采用同步串行调用链,会叠加存储IO延迟,最终导致性能暴跌。

5. Grain单线程调度限制

Orleans每个Grain激活实例在单线程上处理请求(保证并发安全),即使预热100个Grain,每个Grain仍串行处理请求,无法充分利用多核CPU,这也是调整Grain数量性能无差异、CPU使用率仅50%的原因。

优化建议

  • 限制客户端并发度:在Parallel.ForEachAsync中指定MaxDegreeOfParallelism(如100-200),避免客户端过载。
  • 移除同步控制台输出:改用异步日志框架输出,或直接注释以测试真实性能。
  • 优化持久化策略:采用批量写入、缓存写入合并执行WriteStateAsync(),或切换至Redis、Cosmos DB等高性能存储。
  • 优化跨Grain调用:用异步并行调用替代串行调用,利用批量请求API减少网络往返。
  • 充分利用多核资源:增加Silo实例数,让Grain分布到更多节点,或调整Orleans调度配置启用细粒度调度。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 13:16:01