Mur*_*ock 2 .net c# actor azure-table-storage orleans
我正在评估奥尔良的一个我们即将开始的新项目。
最终我们想要运行一群持久的演员,但我目前正在努力让奥尔良的内存版本的基线变得高性能。
给定以下颗粒
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;
}
}
Run Code Online (Sandbox Code Playgroud)
以下筒仓配置:
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;
}
Run Code Online (Sandbox Code Playgroud)
以及以下客户端代码:
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 \n");
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\n");
}
Run Code Online (Sandbox Code Playgroud)
我可以在 30 秒内发送 100,000 条消息。相当于每秒约 3333 条消息。这比我在查看时所期望的要少得多(https://github.com/yevhen/Orleans.PingPong)
我从 10 粒、100 粒或 1000 粒开始似乎并不重要。
当我添加配置表存储的持久性时
.AddAzureTableGrainStorage(
name: "OrleansMemoryProvider",
configureOptions: options =>
{
options.UseJson = true;
options.ConfigureTableServiceClient(
"secret);
})
Run Code Online (Sandbox Code Playgroud)
还有一个单
等待 WriteStateAsync(); 在 CreateOrder 中,情况变得更加糟糕,大约为 280 条消息/秒
当我更进一步并实现一些基本的领域逻辑时。调用其他参与者等我们基本上以 1.2 条消息/秒的蜗牛速度
是什么赋予了?
编辑:
Reu*_*ond 10
构建高性能应用程序可能很棘手且微妙。奥尔良的一般解决方案是你有很多grain和很多调用者,这样你就可以实现高度的并发性和吞吐量。在您的情况下,您有很多颗粒(100),但调用者很少(我相信默认情况下每个核心都有一个Parallel.ForEachAsync),并且每个调用者在每次调用后都会写入控制台,这会大大减慢速度。
如果我删除Console.WriteLine并使用 Orleans 7.0-rc2 在我的机器上运行您的代码,则对 100 个grains 的 100K 次调用将在大约 850 毫秒内完成。如果我将CreateOrderRequest&CreateOrderResponse类型从类更改为结构,持续时间会减少到 750 毫秒。
如果我运行更优化的 ping 测试(来自 Orleans 存储库的测试),我会看到我的计算机上每秒大约有 550K 个请求,其中一个客户端和一个筒仓进程共享同一 CPU。对于 Orleans 3.x,这个数字大约是这个数字的一半。如果我在筒仓进程中共同托管客户端(即IClusterClient从筒仓中拉取IServiceProvider),那么我会看到每秒超过 500 万个请求。
一旦你开始在每个颗粒上做大量的工作,你就会开始遇到其他限制。我最近测试了从同一进程中调用单个grain,发现如果一个grain正在做琐碎的工作(乒乓球),它可以处理500K RPS。如果grain必须在每个请求上写入存储并且每个存储写入需要1毫秒,那么它将无法处理超过1000 RPS,因为默认情况下每个调用都会等待前一个调用完成。如果您想选择退出该行为,可以通过启用grain的重入性来实现,如此处文档中所述:https: //learn.microsoft.com/en-us/dotnet/orleans/grains/reentrancy。Chirper 示例提供了有关如何通过存储更新实现重入的更多详细信息: https: //github.com/dotnet/orleans/tree/main/samples/Chirper。
当grain方法变得更加复杂并且grain需要执行大量I/O来服务每个请求(例如,存储更新和后续grain调用)时,每个单独grain的吞吐量将会下降,因为每个请求涉及更多工作。希望上述数字能为您提供大概的指导。
| 归档时间: |
|
| 查看次数: |
936 次 |
| 最近记录: |