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
相关产品推荐
相关产品推荐

