troupe.cr
Troupe.cr
面向 Crystal 的 Virtual Actor 框架,语义对标 Orleans,沿用 Troupe 的剧团术语。
当前状态:M1 单机核心 + M2 持久化/语义硬化/可观测/基准 + M3 PostgreSQL Provider(dev)。 已实现 ActorId / Role manifest / Memory 与 SQLite / PostgreSQL PropStore(CAS+fencing,PG 为
cross_node_safe? = true的生产 Provider)/ Stage / Agent / ActorSlot(single-flight)/ 有界 CallBoard / Intermission / Rehearsal / 幂等记录 / 协作取消 / metrics 与 W3C trace 钩子 / 基线基准。TCP transport、集群路由、持久 reminder 与 Show 尚未实现;多进程 kill -9 级故障注入矩阵尚未执行,不要据此宣称 production-ready 或跨语言互操作。
文档
- DESIGN.md:目标架构、Crystal 执行模型和核心契约。
- PLAN.md:分阶段方案、验收门禁和非目标。
- Orleans 差距
- 执行模型
- 持久化与所有权
- 集群与协议
- 当前验证
- 多语言统一规划
验证
要求 Crystal >= 1.22。
crystal spec
crystal tool format --check src spec
crystal build src/troupe.cr --no-codegen
--no-codegen 只验证库源码可编译,不生成可执行程序。各阶段真实命令、环境与结果记录在 docs/verification.md。
最小用法(单机 Stage)
require "troupe"
class CartProps
include JSON::Serializable
property items : Array(String) = [] of String
def initialize
end
end
class CartActor < Troupe::Actor(CartProps)
def add(item : String) : Int32
props.items << item
save
props.items.size
end
end
stage = Troupe::Stage.new
stage.register(Troupe::Manifest(CartProps).define("cart", CartActor) do |m|
m.schema_version 1
m.initial { CartProps.new }
m.turn("add", CartActor) do |actor, ctx, args|
JSON::Any.new(actor.add(args["item"].as_s).to_i64)
end
end)
id = Troupe::ActorId.new("app", "cart", "cart-1")
result = Troupe::Agent.new(stage).call!(id, "add", JSON.parse(%({"item": "pear"})))
Turn 闭包走 JSON 参数/结果边界;save 以 revision CAS + epoch fencing 显式提交。测试请使用 Troupe::Rehearsal(固定 namespace、Memory Provider、虚拟时钟、pump 确定性调度)。
类型安全 Agent(宏 manifest)
class CartActor < Troupe::Actor(CartProps)
troupe_manifest "cart", schema_version: 1 do
turn add_item, {item: String}, returns: Int32
end
def add_item(item : String) : Int32
props.items << item
save
props.items.size
end
end
stage.register(CartActor.troupe_manifest) # 宏生成 manifest(含参数 schema)
cart = CartActor::Agent.new(stage) # 类型安全代理
cart.add_item(id, "pear") # 编译期类型安全;缺参/类型不符排队前拒绝
完整示例见 examples/cart.cr(crystal run examples/cart.cr)。
本地持久化(M2,开发用)
store = Troupe::SQLitePropStore.open("./dev-troupe.db") # WAL 模式
stage = Troupe::Stage.new(store: store, ownership: store.ownership)
SQLite Provider 的 CAS 与 fencing 均为单条 SQL 原子语句;进程重启后从文件恢复。 注意:仅进程/单机持久(store.cross_node_safe? == false),不承诺节点丢失或跨机恢复。
PostgreSQL Provider(M3,生产)
store = Troupe::PostgreSQLPropStore.open("postgres://troupe:troupe@127.0.0.1:5432/troupe",
owner_instance: "silo-1", lease_ms: 30_000)
stage = Troupe::Stage.new(store: store, ownership: store.ownership)
所有权带租期(数据库服务器时钟判定):跨实例有效租期内 acquire 显式拒绝(可重试), 过期后可取得且 epoch 单调递增;旧 epoch 写一律被 fencing 拒绝。多节点接管语义 (cross_node_safe? == true)。测试需设置 TROUPE_PG_URL(CI 已内置 postgres service)。
可观测(M2)
stage.on_event { |event| ... } # 结构化生命周期事件
stage.on_trace { |trace| ... } # Turn 边界 TraceEvent(W3C traceparent)
stage.metrics.counter("turns_completed") # lifecycle/call 计数器
future = agent.call(id, "charge", args, nil, "00-<32hex>-<16hex>-01") # 传入调用方 W3C context
ctx.traceparent # Turn 内互调时显式作为父传递
核心零 OTel 依赖:on_trace 即桥接真实 OTel SDK 的钩子(对齐 troupejs onSpan 模式)。
基线基准(M2)
crystal run --release bench/run.cr
五场景:激活吞吐 / KB per Actor / 热点 vs 分散调用 / CPU 阻塞调度延迟 / 队列饱和。 口径声明与方法见 docs/benchmarks.md——本仓自基线,不与其他语言异口径比较。
实施阶段
按 PLAN.md 已完成 M1 单机核心与 M2 全部五项(SQLite 本地 Provider、语义硬化、 可观测、类型安全 Agent/宏 manifest、基线基准)。下一阶段 M3:PostgreSQL Provider、 TCP transport、持久 reminder 与生产容错验收。
许可
MIT,见 LICENSE。
troupe.cr
- 0
- 0
- 0
- 0
- 2
- about 1 hour ago
- November 14, 2026
MIT License
Sat, 14 Nov 2026 16:03:28 GMT