概述
Orleans 流系统通过多层机制来判断一个订阅是显式订阅还是隐式订阅。这个判断过程涉及编译时属性标记、运行时类型检查、以及订阅ID的位标记等多个层面。
1. 编译时标记机制
1.1 ImplicitStreamSubscriptionAttribute
开发者通过在 Grain 类上添加 [ImplicitStreamSubscription] 属性来标记隐式订阅:
[ImplicitStreamSubscription("MyNamespace")]
public class MyGrain : Grain, IMyGrain
{
// Grain 实现
}
1.2 属性处理机制
// 在 StreamSubscriptionAttributes.cs 中
public class ImplicitStreamSubscriptionAttribute : Attribute, IGrainBindingsProviderAttribute
{
public IEnumerable<Dictionary<string, string>> GetBindings(IServiceProvider services, Type grainClass, GrainType grainType)
{
var binding = new Dictionary<string, string>
{
[WellKnownGrainTypeProperties.BindingTypeKey] = WellKnownGrainTypeProperties.StreamBindingTypeValue,
[WellKnownGrainTypeProperties.StreamBindingPatternKey] = this.Predicate.PredicatePattern,
[WellKnownGrainTypeProperties.StreamIdMapperKey] = this.StreamIdMapper,
};
yield return binding;
}
}
关键点:
- 属性实现了
IGrainBindingsProviderAttribute接口 - 在 Grain 注册时,系统会收集这些绑定信息
- 绑定信息存储在 Grain 的元数据中
2. 运行时检测机制
2.1 主要判断入口
// 在 StreamPubSubImpl.cs 中
public Task RegisterConsumer(GuidId subscriptionId, QualifiedStreamId streamId, GrainId streamConsumer, string filterData)
{
return implicitPubSub.IsImplicitSubscriber(streamConsumer, streamId)
? implicitPubSub.RegisterConsumer(subscriptionId, streamId, streamConsumer, filterData)
: explicitPubSub.RegisterConsumer(subscriptionId, streamId, streamConsumer, filterData);
}
判断逻辑:
- 首先调用
IsImplicitSubscriber()检查是否为隐式订阅 - 如果是隐式订阅,使用
ImplicitStreamPubSub - 否则使用
GrainBasedPubSubRuntime(显式订阅)
2.2 隐式订阅检查
// 在 ImplicitStreamPubSub.cs 中
internal bool IsImplicitSubscriber(GrainId grainId, QualifiedStreamId streamId)
{
return implicitTable.IsImplicitSubscriber(grainId, streamId);
}
2.3 详细检查逻辑
// 在 ImplicitStreamSubscriberTable.cs 中
internal bool IsImplicitSubscriber(GrainId grainId, QualifiedStreamId streamId)
{
var streamNamespace = streamId.GetNamespace();
// 1. 检查流命名空间是否有效
if (!IsImplicitSubscribeEligibleNameSpace(streamNamespace))
{
return false;
}
// 2. 获取该命名空间的隐式订阅者
foreach (var entry in GetOrAddImplicitSubscribers(streamNamespace))
{
// 3. 检查 Grain 类型是否匹配
if (entry.GrainType == grainId.Type)
return true;
}
return false;
}
检查步骤:
- 命名空间检查:验证流是否有有效的命名空间
- 订阅者查找:查找该命名空间的所有隐式订阅者
- 类型匹配:检查当前 Grain 类型是否在隐式订阅者列表中
2.4 命名空间有效性检查
internal static bool IsImplicitSubscribeEligibleNameSpace(string streamNameSpace)
{
return !string.IsNullOrWhiteSpace(streamNameSpace);
}
规则:
- 只有非空的流命名空间才支持隐式订阅
- 空或空白的命名空间只能使用显式订阅
3. 订阅ID标记机制
3.1 位标记检查
// 在 SubscriptionMarker.cs 中
internal static bool IsImplicitSubscription(Guid subscriptionGuid)
{
Span<byte> guidBytes = stackalloc byte[16];
subscriptionGuid.TryWriteBytes(guidBytes);
// 检查最后字节的高位是否设置
return (guidBytes[15] & 0x80) != 0;
}
标记规则:
- 隐式订阅:GUID 最后字节的高位设置为 1 (0x80)
- 显式订阅:GUID 最后字节的高位设置为 0 (0x7F)
3.2 通过订阅ID判断
// 在 ImplicitStreamPubSub.cs 中
internal bool IsImplicitSubscriber(GuidId subscriptionId, QualifiedStreamId streamId)
{
return SubscriptionMarker.IsImplicitSubscription(subscriptionId.Guid);
}
4. 订阅ID生成时的类型确定
4.1 显式订阅ID生成
// 在 GrainBasedPubSubRuntime.cs 中
public GuidId CreateSubscriptionId(QualifiedStreamId streamId, GrainId streamConsumer)
{
Guid subscriptionId = SubscriptionMarker.MarkAsExplicitSubscriptionId(Guid.NewGuid());
return GuidId.GetGuidId(subscriptionId);
}
4.2 隐式订阅ID生成
// 在 ImplicitStreamSubscriberTable.cs 中
private Guid MakeSubscriptionGuid(GrainType grainType, QualifiedStreamId streamId)
{
Span<byte> bytes = stackalloc byte[16];
BinaryPrimitives.WriteUInt32LittleEndian(bytes, grainType.GetUniformHashCode());
BinaryPrimitives.WriteUInt32LittleEndian(bytes[4..], streamId.StreamId.GetUniformHashCode());
BinaryPrimitives.WriteUInt32LittleEndian(bytes[8..], streamId.StreamId.GetKeyIndex());
BinaryPrimitives.WriteUInt32LittleEndian(bytes[12..], StableHash.ComputeHash(streamId.ProviderName));
return SubscriptionMarker.MarkAsImplictSubscriptionId(new(bytes));
}
5. 完整的判断流程
5.1 订阅创建时的判断
-
用户调用订阅方法
await stream.SubscribeAsync(observer); -
系统检查订阅类型
// 在 StreamPubSubImpl.cs 中 return implicitPubSub.IsImplicitSubscriber(streamConsumer, streamId) ? implicitPubSub.CreateSubscriptionId(streamId, streamConsumer) : explicitPubSub.CreateSubscriptionId(streamId, streamConsumer); -
生成相应类型的订阅ID
- 隐式订阅:使用确定性算法 + 隐式标记
- 显式订阅:使用随机GUID + 显式标记
5.2 消息投递时的判断
- 接收消息时检查订阅ID
// 通过订阅ID的位标记快速判断类型 if (SubscriptionMarker.IsImplicitSubscription(subscriptionId.Guid)) { // 处理隐式订阅 } else { // 处理显式订阅 }
6. 关键设计特点
6.1 多层判断机制
- 编译时:通过属性标记
- 运行时:通过类型和命名空间检查
- 消息时:通过订阅ID位标记
6.2 性能优化
- 使用位标记进行快速类型判断
- 缓存隐式订阅者信息
- 避免重复的类型检查
6.3 灵活性
- 支持多种命名空间匹配模式
- 支持正则表达式匹配
- 支持自定义谓词
7. 总结
Orleans 通过以下机制确定订阅类型:
- 编译时标记:
[ImplicitStreamSubscription]属性 - 运行时检查:Grain 类型和流命名空间匹配
- ID标记:GUID 最后字节的高位标记
- 分层判断:从属性到类型到ID的多层验证
这种设计既保证了类型判断的准确性,又提供了良好的性能,同时支持灵活的订阅模式配置。

4900

被折叠的 条评论
为什么被折叠?



