Orleans 流订阅类型检测机制详解

概述

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;
}

检查步骤

  1. 命名空间检查:验证流是否有有效的命名空间
  2. 订阅者查找:查找该命名空间的所有隐式订阅者
  3. 类型匹配:检查当前 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 订阅创建时的判断

  1. 用户调用订阅方法

    await stream.SubscribeAsync(observer);
    
  2. 系统检查订阅类型

    // 在 StreamPubSubImpl.cs 中
    return implicitPubSub.IsImplicitSubscriber(streamConsumer, streamId)
        ? implicitPubSub.CreateSubscriptionId(streamId, streamConsumer)
        : explicitPubSub.CreateSubscriptionId(streamId, streamConsumer);
    
  3. 生成相应类型的订阅ID

    • 隐式订阅:使用确定性算法 + 隐式标记
    • 显式订阅:使用随机GUID + 显式标记

5.2 消息投递时的判断

  1. 接收消息时检查订阅ID
    // 通过订阅ID的位标记快速判断类型
    if (SubscriptionMarker.IsImplicitSubscription(subscriptionId.Guid))
    {
        // 处理隐式订阅
    }
    else
    {
        // 处理显式订阅
    }
    

6. 关键设计特点

6.1 多层判断机制

  • 编译时:通过属性标记
  • 运行时:通过类型和命名空间检查
  • 消息时:通过订阅ID位标记

6.2 性能优化

  • 使用位标记进行快速类型判断
  • 缓存隐式订阅者信息
  • 避免重复的类型检查

6.3 灵活性

  • 支持多种命名空间匹配模式
  • 支持正则表达式匹配
  • 支持自定义谓词

7. 总结

Orleans 通过以下机制确定订阅类型:

  1. 编译时标记[ImplicitStreamSubscription] 属性
  2. 运行时检查:Grain 类型和流命名空间匹配
  3. ID标记:GUID 最后字节的高位标记
  4. 分层判断:从属性到类型到ID的多层验证

这种设计既保证了类型判断的准确性,又提供了良好的性能,同时支持灵活的订阅模式配置。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

helloworddm

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值