我正在尝试理解 Microsoft Orleans 中 grains 的单线程处理。我使用了 这里 的代码,并对其进行了一些修改以测试我的场景。
我的客户端代码和 silo 构建代码
我的谷物界面和实际谷物实现
我基本上所做的是每1秒向颗粒发送请求,而我允许颗粒内的每个方法调用至少需要10秒。现在,根据颗粒的单线程执行和Orleans运行时调度的描述(参见这里),我希望请求将被排队,除非当前请求的方法完成,否则下一个请求不会被颗粒接受。然而,控制台输出并不支持这一点。控制台输出如下:
这表明在当前请求完成之前,下一个请求正在由谷粒处理。
问题:
1. 那么,这是否违反了Orleans单线程执行模型,还是我理解有误?
2. 此外,当我在谷粒内使用Thread.sleep(10000)而不是Task.Delay(10000)时,我几乎得到相同的控制台输出,除了每个请求调用都会有一个额外的警告 - “Task [Id=1, Status=RanToCompletion] in WorkGroup [Activation: S127.0.0.1:11111:270246987*grn/6424EE47/00000028@cafcc6a5 #GrainType=Orleans2GettingStarted.TemperatureSensorGrain Placement=RandomPlacement State=Valid] took elapsed time 0:00:10.0019256 for execution, which is longer than 00:00:00.2000000”。 这是否意味着每个谷粒理想情况下应该在200毫秒内处理完毕?如果谷粒处理时间更长会发生什么?
static async Task Main(string[] args)
{
var siloBuilder = new SiloHostBuilder()
.UseLocalhostClustering()
.UseDashboard(options => { })
.Configure<ClusterOptions>(options =>
{
options.ClusterId = "dev";
options.ServiceId = "Orleans2GettingStarted";
})
.Configure<EndpointOptions>(options =>
options.AdvertisedIPAddress = IPAddress.Loopback)
.ConfigureLogging(logging => logging.SetMinimumLevel(LogLevel.Warning).AddConsole());
using (var host = siloBuilder.Build())
{
await host.StartAsync();
var clientBuilder = new ClientBuilder()
.UseLocalhostClustering()
.Configure<ClusterOptions>(options =>
{
options.ClusterId = "dev";
options.ServiceId = "Orleans2GettingStarted";
})
.ConfigureLogging(logging => logging.AddConsole());
using (var client = clientBuilder.Build())
{
await client.Connect();
var random = new Random();
string sky = "blue";
while (sky == "blue") // if run in Ireland, it exits loop immediately
{
Console.WriteLine("Client giving another request");
int grainId = random.Next(0, 500);
double temperature = random.NextDouble() * 40;
var sensor = client.GetGrain<ITemperatureSensorGrain>(grainId);
// Not awaiting this task so that next call to grain
// can be made without waiting for current call to complete
Task t = sensor.SubmitTemperatureAsync((float)temperature);
Thread.Sleep(1000);
}
}
}
}
我的谷物界面和实际谷物实现
public interface ITemperatureSensorGrain : IGrainWithIntegerKey
{
Task SubmitTemperatureAsync(float temperature);
}
public class TemperatureSensorGrain : Grain, ITemperatureSensorGrain
{
public async Task SubmitTemperatureAsync(float temperature)
{
long grainId = this.GetPrimaryKeyLong();
Console.WriteLine($"{grainId} received temperature: {temperature}");
await Task.Delay(10000);
// Thread.Sleep(10000);
Console.WriteLine($"{grainId} complete");
// return Task.CompletedTask;
}
}
我基本上所做的是每1秒向颗粒发送请求,而我允许颗粒内的每个方法调用至少需要10秒。现在,根据颗粒的单线程执行和Orleans运行时调度的描述(参见这里),我希望请求将被排队,除非当前请求的方法完成,否则下一个请求不会被颗粒接受。然而,控制台输出并不支持这一点。控制台输出如下:
Client giving another request
344 received temperature: 8.162848
Client giving another request
357 received temperature: 10.32219
Client giving another request
26 received temperature: 1.166182
Client giving another request
149 received temperature: 37.74038
Client giving another request
60 received temperature: 26.72013
Client giving another request
218 received temperature: 24.19116
Client giving another request
269 received temperature: 17.1897
Client giving another request
318 received temperature: 8.562404
Client giving another request
372 received temperature: 8.865559
Client giving another request
443 received temperature: 5.254442
Client giving another request
344 complete <-------------- The first request completed here
97 received temperature: 19.24687
这表明在当前请求完成之前,下一个请求正在由谷粒处理。
问题:
1. 那么,这是否违反了Orleans单线程执行模型,还是我理解有误?
2. 此外,当我在谷粒内使用Thread.sleep(10000)而不是Task.Delay(10000)时,我几乎得到相同的控制台输出,除了每个请求调用都会有一个额外的警告 - “Task [Id=1, Status=RanToCompletion] in WorkGroup [Activation: S127.0.0.1:11111:270246987*grn/6424EE47/00000028@cafcc6a5 #GrainType=Orleans2GettingStarted.TemperatureSensorGrain Placement=RandomPlacement State=Valid] took elapsed time 0:00:10.0019256 for execution, which is longer than 00:00:00.2000000”。 这是否意味着每个谷粒理想情况下应该在200毫秒内处理完毕?如果谷粒处理时间更长会发生什么?
TemperatureSensorGrain(grainId: 344)
不会并行执行多个轮次,但TemperatureSensorGrain
可以并行执行许多不同的标识符。 - Dan Wilson