Orleans框架
基本概念理解
Virtual Actor核心机制
- 自动管理生命周期
自动实例化
- 触发条件:消息发送至未激活Actor时,运行时自动创建Grain实例并调用
ActivateAsync()
- 资源回收:闲置实例触发
DeactivateAsync()
清理,内存释放
- 触发条件:消息发送至未激活Actor时,运行时自动创建Grain实例并调用
故障恢复
- 服务器宕机:运行时在下一请求时自动在新节点重建Grain,无需应用层监控
位置透明
- 调用抽象:通过Grain ID调用Actor,物理位置由运行时目录服务动态映射,缓存命中率>90%
扩展模式
- 无状态Worker:同一Grain多实例并行处理请求,适用无状态场景(如只读缓存)
Grain
Grain是Orleans中的虚拟Actor,代表一个独立的状态实体或计算单元(如玩家、订单、房间)。每个Grain拥有唯一的标识符(Grain ID),通过接口定义其行为
在基于Orleans框架的游戏开发中,Grains作为分布式Actor模型的核心单元,将需要独立状态、并发安全、生命周期管理的实体抽象为Grains(如玩家、房间、全局服务)
2. 单线程执行的技术实现
Orleans通过以下机制实现Grain的单线程执行:
Orleans运行时确保每个激活的Grain在任何时刻仅在一个线程上执行。这意味着对Grain内部状态的访问天然无需锁或其他同步机制
异步消息队列:
所有对Grain的调用(即使代码表现为方法调用)均被转化为异步消息,存入该Grain专属的WorkItemGroup
队列
协作式调度:
运行时使用少量线程(通常等于CPU核心数) 处理所有Grain的消息队列。消息按顺序从队列中取出,由同一线程连续执行直至完成(无抢占)
状态隔离性:
Grain的状态(如玩家血量、位置)仅能通过消息修改,外部无法直接访问,从物理上杜绝共享内存冲
Silo
Silo是物理资源单位(如一台服务器)
调用机制本质:消息传递
- 调用方(如
PlayerGrain
)向RoomGrain
发送请求消息。 运行时调度:
若
RoomGrain
与调用方在同一Silo,消息通过本地队列直接传递,无网络开销- 若在不同Silo,消息经TCP连接(默认端口11111)路由至目标Silo的
RoomGrain
- 若在不同Silo,消息经TCP连接(默认端口11111)路由至目标Silo的
RoomGrain
处理 :消息进入其专属WorkItemGroup
队列, 单线程串行执行(即使多个请求并发到达)
单线程执行模型:
每个Grain(包括
RoomGrain
)绑定独立队列,请求严格串行处理,避免状态竞争- 例如:玩家A和B同时向
RoomGrain
发送消息,消息按到达顺序依次处理。
- 例如:玩家A和B同时向
状态隔离性:
-
RoomGrain
封装房间状态(如玩家列表、位置),外部仅能通过消息修改,天然线程安全
-
消息顺序保障:
- 同一发送方的消息默认按序传递(除非标记
[Unordered]
)
- 同一发送方的消息默认按序传递(除非标记
所有Grain交互均为消息驱动,所谓“直接调用”只是语法糖,底层仍由Orleans消息系统保障顺序性,这样可以避免资源竞争等问题,也是实现无锁编程的关键
直接调用消息转化位异步消息的实现
Grain引用抽象
当开发者调用GrainFactory.GetGrain<T>()
时,Orleans运行时动态生成代理对象(如PlayerGrainReference
),而非真实Grain实例- 代理对象实现与Grain接口相同的公开方法(如
Move()
、Attack()
)。 - 调用代理方法时,实际触发消息封装流程。
- 代理对象实现与Grain接口相同的公开方法(如
透明代理技术
基于.NET的RealProxy
或DispatchProxy
,在方法调用时拦截参数并构造消息体1 2 3 4 5 6 7
// 示例:代理对象拦截方法调用 public class GrainReferenceProxy : DispatchProxy { protected override object Invoke(MethodInfo method, object[] args) { var request = new InvokeMethodRequest(method.Name, args); // 封装方法名和参数 return SendMessage(request); // 转入消息发送 } }
消息结构定义
每个方法调用被封装为InvokeMethodRequest
对象,包含:
目标Grain标识(Grain ID)
- 方法签名(方法名、参数类型)
- 参数值(序列化后的二进制数据)
。
- 高效序列化
- 默认使用
Bond
或MessagePack
二进制序列化,压缩数据体积 - 若方法标记
[Immutable]
,参数按值传递;否则按引用传递(需生成Grain引用)。
远程调用路径(跨Silo)
步骤 | 技术实现 |
---|---|
网关路由 | 客户端通过配置的Gateway端口(默认30000)发送消息至Silo集群 。 |
一致性哈希定位 | 根据Grain ID哈希值确定目标Silo,路由表缓存于本地目录服务(LocalGrainDirectory )。 |
Silo间通信 | 使用自定义二进制协议(或gRPC)经TCP端口(默认11111)传输 |
1
是对于Orleans中基本概念的理解,喜欢这套框架的很大原因是无锁开发,高并发的天然优势,以及方便扩展集群,再加上本身又在从事unity开发,对于C#用起来也更得心应手。
具体API和开发手册可以查看https://learn.microsoft.com/zh-cn/dotnet/orleans/