Orleans 通过内置成员身份协议(有时称为 群集成员身份)提供群集管理。 该协议的目标是让所有接收器(Orleans 服务器)就一组当前存活接收器达成一致,检测失败的接收器,并允许新接收器加入群集。
成员协议配置
成员身份协议使用以下默认配置:
- 每个孤岛由 10 个其他孤岛监视
- 需要 2 次怀疑才能声明 silo 消亡
- 怀疑有效期为 3 分钟
- 每 10 秒发送一次探测
- 3 个未检测到的探测信号触发怀疑
在这些默认值中,典型的故障检测时间约为 15 秒。 在孤岛崩溃且未正确清理的灾难恢复场景中,群集使用 IAmAlive 时间戳(默认每 30 秒更新一次)进行恢复;在启动时进行连接检查时,会忽略多个时段未更新时间戳的孤岛。 通过跳过这些无响应的 silo,新的 silo 可以启动,并通过宣布失效 silo 已消亡来快速从群集中清除它们。
配置
您可以使用 ClusterMembershipOptions 配置成员身份协议设置。
siloBuilder.Configure<ClusterMembershipOptions>(options =>
{
// Number of silos each silo monitors (default: 10)
options.NumProbedSilos = 10;
// Number of suspicions required to declare a silo dead (default: 2)
options.NumVotesForDeathDeclaration = 2;
// Time window for suspicions to be valid (default: 180 seconds)
options.DeathVoteExpirationTimeout = TimeSpan.FromSeconds(180);
// Interval between probes (default: 10 seconds)
options.ProbeTimeout = TimeSpan.FromSeconds(10);
// Number of missed probes before suspecting a silo (default: 3)
options.NumMissedProbesLimit = 3;
});
何时调整设置
在大多数情况下,默认设置是适当的。 但是,你可能会考虑在以下方案中进行调整:
-
高延迟网络:如果您的数据孤岛分布在网络延迟较高的地区,请增加
ProbeTimeout。 -
关键可用性要求:减小
DeathVoteExpirationTimeout以实现更快的故障检测,但需谨慎防范误报。
成员协议配置
成员身份协议使用以下默认配置:
- 每个孤岛由另外 3 个孤岛监视
- 需要 2 次怀疑才能声明 silo 消亡
- 怀疑有效期为 3 分钟
- 每 10 秒发送一次探测
- 3 个未检测到的探测信号触发怀疑
成员协议配置
成员身份协议使用以下默认配置:
- 每个孤岛由另外 3 个孤岛监视
- 需要 2 次怀疑才能声明 silo 消亡
- 怀疑有效期为 3 分钟
该协议依赖于外部服务来提供 IMembershipTable 的抽象。
IMembershipTable 是一种平坦、耐用的桌子,用于两个用途。 首先,它用作会合点,供 silo 相互查找彼此,以及供 Orleans 客户查找 silo。 其次,它存储当前成员身份视图(存活 silo 列表),并帮助协调对此视图的共识。
目前提供了以下官方实现 IMembershipTable :
- ADO.NET (PostgreSQL、MySQL/MariaDB、SQL Server、Oracle)、
- AWS DynamoDB、
- Apache Cassandra,
- Apache ZooKeeper,
- Azure Cosmos DB,
- Azure 表存储,
- 哈希公司领事,
- Redis、
- 以及用于开发的内存中实现。
配置 Redis 群集
使用 UseRedisClustering 扩展方法将 Redis 配置为群集提供程序:
using StackExchange.Redis;
var builder = Host.CreateApplicationBuilder(args);
builder.UseOrleans(siloBuilder =>
{
siloBuilder.UseRedisClustering(options =>
{
options.ConfigurationOptions = new ConfigurationOptions
{
EndPoints = { "localhost:6379" },
AbortOnConnectFail = false
};
});
});
或者,可以使用连接字符串:
siloBuilder.UseRedisClustering("localhost:6379");
该 RedisClusteringOptions 类提供以下配置选项:
| 资产 | 类型 | Description |
|---|---|---|
ConfigurationOptions |
ConfigurationOptions |
StackExchange.Redis 客户端配置。 必填。 |
EntryExpiry |
TimeSpan? |
条目的可选过期时间。 仅为临时环境(如测试)设置此设置。 默认值为 null。 |
CreateMultiplexer |
Func<RedisClusteringOptions, Task<IConnectionMultiplexer>> |
用于创建 Redis 连接多路复用器的自定义工厂。 |
CreateRedisKey |
Func<ClusterOptions, RedisKey> |
用于为成员身份表生成 Redis 密钥的自定义函数。 默认格式为 {ServiceId}/members/{ClusterId}. |
重要
接口的 IMembershipTable 实现必须使用持久数据存储。 例如,如果使用 Redis,请确保显式启用持久性。 可变配置可能会导致群集不可用。
用于群集的 .NET Aspire 集成
使用 .NET Aspire时,可以在 AppHost 项目中以声明方式配置 Orleans 群集。 Aspire 通过环境变量自动将必要的配置注入silo项目中。
使用 Aspire 的 Redis 群集功能
AppHost 项目(Program.cs):
var builder = DistributedApplication.CreateBuilder(args);
var redis = builder.AddRedis("redis");
var orleans = builder.AddOrleans("cluster")
.WithClustering(redis);
builder.AddProject<Projects.MySilo>("silo")
.WithReference(orleans)
.WithReference(redis);
builder.Build().Run();
Silo项目(Program.cs):
var builder = Host.CreateApplicationBuilder(args);
builder.AddServiceDefaults();
builder.AddKeyedRedisClient("redis");
builder.UseOrleans();
builder.Build().Run();
使用 Aspire 进行 Azure 表存储 群集
AppHost 项目(Program.cs):
var builder = DistributedApplication.CreateBuilder(args);
var storage = builder.AddAzureStorage("storage")
.RunAsEmulator(); // Use Azurite for local development
var tables = storage.AddTables("clustering");
var orleans = builder.AddOrleans("cluster")
.WithClustering(tables);
builder.AddProject<Projects.MySilo>("silo")
.WithReference(orleans)
.WaitFor(storage);
builder.Build().Run();
Silo项目(Program.cs):
var builder = Host.CreateApplicationBuilder(args);
builder.AddServiceDefaults();
builder.AddKeyedAzureTableServiceClient("clustering");
builder.UseOrleans();
builder.Build().Run();
小窍门
若要使用 Azurite 模拟器进行本地开发,请调用 .RunAsEmulator() Azure 存储资源。 如果没有此调用,Aspire需要真正的Azure 存储连接。
使用 Aspire 的 Azure Cosmos DB 群集功能
AppHost 项目(Program.cs):
var builder = DistributedApplication.CreateBuilder(args);
var cosmos = builder.AddAzureCosmosDB("cosmos")
.RunAsEmulator(); // Use emulator for local development
var database = cosmos.AddCosmosDatabase("orleans");
var orleans = builder.AddOrleans("cluster")
.WithClustering(database);
builder.AddProject<Projects.MySilo>("silo")
.WithReference(orleans)
.WaitFor(cosmos);
builder.Build().Run();
Silo项目(Program.cs):
var builder = Host.CreateApplicationBuilder(args);
builder.AddServiceDefaults();
builder.AddKeyedAzureCosmosClient("cosmos");
builder.UseOrleans();
builder.Build().Run();
注释
Aspire 当前的 Cosmos DB 集成对于 Orleans 仅支持群集。 对于使用 Cosmos DB 的粒度存储和提醒,你需要在 silo 项目中手动配置这些提供程序。
重要
必须调用适当的 AddKeyed* 方法(例如 AddKeyedRedisClient, AddKeyedAzureTableServiceClient或 AddKeyedAzureCosmosClient)在依赖项注入容器中注册支持资源。
Orleans 提供程序按其键型服务名称查找资源 — 如果跳过此步骤, Orleans 将无法解析资源,并在运行时引发依赖项解析错误。
有关 Orleans 和 .NET Aspire 集成的详细信息,请参阅 Orleans 和 .NET Aspire 集成。
配置 Cassandra 群集
使用 UseCassandraClustering 扩展方法将 Apache Cassandra 配置为群集提供程序。 安装 Microsoft.Orleans。Clustering.Cassandra NuGet 包:
dotnet add package Microsoft.Orleans.Clustering.Cassandra
使用连接字符串配置 Cassandra 群集:
using Orleans.Clustering.Cassandra.Hosting;
var builder = Host.CreateApplicationBuilder(args);
builder.UseOrleans(siloBuilder =>
{
siloBuilder.UseCassandraClustering(
connectionString: "Contact Points=localhost;Port=9042",
keyspace: "orleans");
});
或者,使用基于选项的配置进行更多控制:
siloBuilder.UseCassandraClustering(options =>
{
options.ConfigureClient("Contact Points=cassandra-node1,cassandra-node2;Port=9042", "orleans");
options.UseCassandraTtl = true;
options.InitializeRetryMaxDelay = TimeSpan.FromSeconds(30);
});
或者为高级场景提供自定义会话工厂:
using Cassandra;
siloBuilder.UseCassandraClustering(async serviceProvider =>
{
var cluster = Cluster.Builder()
.AddContactPoints("cassandra-node1", "cassandra-node2")
.WithPort(9042)
.WithCredentials("username", "password")
.WithQueryOptions(new QueryOptions().SetConsistencyLevel(ConsistencyLevel.Quorum))
.Build();
return await cluster.ConnectAsync("orleans");
});
该 CassandraClusteringOptions 类提供以下配置选项:
| 资产 | 类型 | 违约 | Description |
|---|---|---|---|
UseCassandraTtl |
bool |
false |
如果为 true,则为 Cassandra 中的成员身份表行配置生存时间,即使群集不再运行,也允许清理失效的 silo。 使用 ClusterMembershipOptions 中的 DefunctSiloExpiration。 |
InitializeRetryMaxDelay |
TimeSpan | 20 秒 | 在初始化期间遇到资源争用时,重试操作之间的最大延迟。 将大量 silo 同时连接到多数据中心 Cassandra 群集时,通常需要这样做。 |
何时使用 Cassandra 群集
在以下情况下,请考虑使用 Cassandra 进行集群:
- 组织中已有 Cassandra 基础结构
- 需要一个群集提供程序,该提供程序可跨多个数据中心运行,且具有可调整一致性
- 即使Orleans群集未运行,您也需要通过 Cassandra TTL 自动清理已废弃的数据孤岛。
- 大型群集需要高写入吞吐量
配置 Azure Cosmos DB 群集
使用 UseCosmosClustering 扩展方法将 Azure Cosmos DB 配置为群集提供程序。 安装 Microsoft.Orleans。Clustering.Cosmos NuGet 包:
dotnet add package Microsoft.Orleans.Clustering.Cosmos
使用连接字符串配置 Cosmos DB 群集:
using Azure.Identity;
var builder = Host.CreateApplicationBuilder(args);
builder.UseOrleans(siloBuilder =>
{
siloBuilder.UseCosmosClustering(options =>
{
options.ConfigureCosmosClient(
"https://myaccount.documents.azure.com:443/",
new DefaultAzureCredential());
options.DatabaseName = "Orleans";
options.ContainerName = "OrleansCluster";
options.IsResourceCreationEnabled = true;
});
});
或者,可以使用连接字符串:
siloBuilder.UseCosmosClustering(options =>
{
options.ConfigureCosmosClient("AccountEndpoint=https://myaccount.documents.azure.com:443/;AccountKey=...");
});
该 CosmosClusteringOptions 类继承自 CosmosOptions 并提供以下配置选项:
| 资产 | 类型 | 违约 | Description |
|---|---|---|---|
DatabaseName |
string |
"Orleans" |
Cosmos DB 数据库的名称。 |
ContainerName |
string |
"OrleansCluster" |
群集成员身份数据的容器名称。 |
IsResourceCreationEnabled |
bool |
false |
当true时,如果数据库和容器不存在,将自动创建它们。 |
DatabaseThroughput |
int? |
null |
数据库的预配吞吐量。 如果null,则使用无服务器模式。 |
ContainerThroughputProperties |
ThroughputProperties? |
null |
容器的吞吐量属性。 |
ClientOptions |
CosmosClientOptions |
new() |
传递给 Cosmos DB 客户端的选项。 |
CleanResourcesOnInitialization |
bool |
false |
在初始化时删除数据库。 仅用于测试。 |
何时使用 Cosmos DB 群集
在以下情况下,请考虑使用 Azure Cosmos DB 进行群集分析:
- 您的应用程序已使用了 Azure Cosmos DB
- 需要具有多区域写入功能的全局分布式数据库
- 您想要一个具备自动伸缩的完整托管无服务器解决方案。
- 你需要具有保证 SLA 的低延迟读写
对于 Orleans 客户端,请使用 UseCosmosGatewayListProvider 来配置网关发现。
builder.UseOrleansClient(clientBuilder =>
{
clientBuilder.UseCosmosGatewayListProvider(options =>
{
options.ConfigureCosmosClient(
"https://myaccount.documents.azure.com:443/",
new DefaultAzureCredential());
});
});
除了 IMembershipTable 以外,每个 silo 都参与完全分布式的对等成员身份协议,该协议检测失败的 silo 并就一组存活 silo 达成一致。
Orleans的成员身份协议的内部实现如下所述。
成员身份协议
启动后,每个 silo 都会使用 IMembershipTable 的实现将自身条目添加到众所周知的共享表中。 Orleans 将孤岛标识(
ip:port:epoch)和服务部署 ID(群集 ID)组合用作表中的唯一键。 Epoch 只是此 silo 启动时的时间(刻度),因此可以保证ip:port:epoch在给定 Orleans 部署中是唯一的。silo 直接通过应用程序探测(“是否存活”
heartbeats)相互监视。 探测作为直接消息,通过用于常规通信的相同 TCP 套接字从一个 silo 发送到另一个 silo。 这样,探测与实际的网络问题和服务器运行状况完全关联。 每个 silo 都会对一组可配置的其他 silo 进行探测操作。 silo 通过计算其他 silo 的标识中的一致哈希来选择要探测的对象,从而构成所有标识的虚拟环,并在该环中拾取 X 个后继 silo。 (这是一种称为 一致哈希 的已知分布式技术,在许多分布式哈希表中(如 Chord DHT)中广泛使用。如果 silo S 未收到来自受监视服务器 P 的 Y 次 探测回复,则它会通过将带时间戳的怀疑写入
IMembershipTable中 P 的行来表示怀疑。如果 P 在 K 秒内有超过 Z 次的怀疑,S 会将 P 已消亡信息写入 P 的行,并将当前成员身份表的快照广播给所有其他 silo。 Silo 会定期刷新表,因此快照是一种优化方法,可以减少所有 silo 获取新的成员身份视图所需的时间。
更详细地说:
怀疑将写入
IMembershipTable中与 P 对应的行的特殊列中。当 S 怀疑 P 时,它会写入:“在时间点 TTT,S 怀疑了 P”。一个怀疑不足以宣布P死。 在可配置的时间窗口 T(通常为 3 分钟)内,需要有来自不同 silo 的 Z 次怀疑才能将 P 声明为已消亡。 怀疑是使用
IMembershipTable提供的乐观并发控制写入的。怀疑方 silo S 读取 P 的行。
如果
S最后一名嫌疑人(在 T 期间内已有 Z-1 嫌疑人,如怀疑列中记录),S 决定宣布 P 死亡。 在这种情况下,S 会将自身添加到怀疑方列表中,并在 P 的状态列中写入“P 已消亡”。否则,如果 S 不是最后一个怀疑者,则 S 只会将自身添加到嫌疑人的列中。
在任一情况下,写回都使用之前读取的版本号或 ETag,从而序列化对此行的更新。 如果写入由于版本/ETag 不匹配而失败,S 重试(再次读取并尝试写入,除非 P 已标记为死)。
总体来说,这个“读取、本地修改、写回”的序列是一个事务处理。 然而,存储事务未必会被使用。 “事务”代码在服务器上本地执行,
IMembershipTable提供的乐观并发可确保隔离性和原子性。
每个 silo 定期读取整个成员身份表来进行部署。 这样,silo 便知道有新的 silo 加入并有其他 silo 被声明为消亡。
快照广播:为了降低定期表读取的频率,每当一个孤岛写入表(例如怀疑、新加入等),它都会向所有其他孤岛发送当前表状态的快照。 由于成员身份表是一致的,并且版本是单调的,因此每次更新都会生成一个可以安全共享的唯一版本快照。 这样就可以立即传播成员身份更改,而无需等待定期读取周期。 在快照分发失败的情况下,定期读取仍被作为备份机制进行维护。
有序成员身份视图:成员身份协议可确保全局完全排序所有成员身份配置。 此排序提供两个关键优势:
连接性保证:当新的仓加入集群时,它必须验证与其他每个活动仓的双向连接。 如果任何现有的孤岛没有响应(这可能表示网络连接问题),就不允许新孤岛加入。 这可确保在启动时群集中的所有孤岛之间完全连接。 有关灾难恢复场景中的例外,请参阅以下有关
IAmAlive的说明。一致的目录更新:高级协议(如分布式粒度目录)依赖于具有一致、单调成员身份视图的所有孤岛。 这样就可以更智能地解析重复的粒度激活。 有关详细信息,请参阅 Grain 目录 文档。
实现的详细信息:
IMembershipTable需要原子更新才能保证更改的全局总顺序:- 实现必须以原子方式更新表项(孤岛列表)和版本号。
- 通过使用数据库事务(如在 SQL Server 中)或使用 ETag 的原子比较和交换操作(如在 Azure 表存储中)来实现这一点。
- 特定机制取决于基础存储系统的功能。
表中的特殊会员版本行用于跟踪更改:
- 对表的每次写入(怀疑、消亡声明、加入操作)都会递增此版本号。
- 所有写入都通过此行使用原子更新进行序列化。
- 单调递增的版本确保所有成员身份更改有一个总排序。
当接收器 S 更新接收器 P 的状态时:
- S 首先读取最新的表状态。
- 在单个原子操作中,它同时更新 P 的行并递增版本号。
- 如果原子更新失败(例如,由于并发修改),操作会使用指数回退进行重试。
可伸缩性注意事项:
由于争用增加,通过版本行序列化所有写入可能会影响可伸缩性。 该协议在生产过程中已被证明在多达200个筒仓的情况下有效,但在超过一千个筒仓时可能会面临挑战。 对于非常大的部署,即使成员身份更新成为瓶颈,Orleans(消息传递、粒度目录、托管)的其他部分仍可缩放。
默认配置:在 Azure 的生产使用期间,已手动调整默认配置。 默认情况下:每个筒仓都由另外三个筒仓监视,两个怀疑足以宣布某个筒仓失效,且只有过去三分钟内的怀疑才被考虑(否则它们已过时)。 探测信号每 10 秒发送一次,如果连续错过三个探测信号,则可能怀疑有发射仓问题。
自我监视:故障检测器整合了 HashiCorp 的 Lifeguard 研究(论文、讲座、博客)中的想法,以在灾难性事件期间,当群集中的很大一部分经历部分失效时,提高群集稳定性。
LocalSiloHealthMonitor组件使用多个启发式方法对每个 silo 的运行状况进行评分:- 成员身份表中的活动状态
- 没有来自其他 silo 的怀疑
- 最近成功的探测响应
- 最近收到的探测请求
- 线程池响应能力(在 1 秒内执行的工作项)
- 计时器准确性(在计划的 3 秒内触发)
silo 的运行状况分数会影响其探测超时:与运行正常的 silo(评分 0)相比,运行不正常的 silo(评分 1-8)的超时时间增加。 这提供了两个优势:
- 当网络或系统承受压力时,为探测提供更多时间才能成功。
- 这使得运行不正常的 silo 在错误地投票淘汰运行正常的 silo 之前,更有可能被投票淘汰。
这在线程池资源不足等方案中尤其有价值;在这种情况下,慢速节点可能会因为无法足够快地处理响应而错误地怀疑正常节点。
间接探测:另一个 Lifeguard 启发的功能,通过减少运行不正常或分区 silo 错误地声明正常 silo 消亡的可能性来提高故障检测准确性。 当一个监视 silo 在投票宣布其消亡之前,对目标 silo 还有两次探测尝试时,它会采用间接探测:
- 监控筒仓随机选择另一个筒仓作为中介,并要求它探测目标。
- 中介尝试联系目标筒仓。
- 如果目标在超时期限内未能响应,中介会发送否定确认。
- 如果监控孤岛收到中介的否定确认,并且中介声明自己正常(通过上述自我监视),则监控孤岛将投票决定宣布目标死亡。
- 由于默认配置了两个必需的投票,间接探测中的否定确认视为两个投票,当多个角度确认故障时,可以更快地宣布消亡 silo。
强制实施完美故障检测:一旦 silo 在表中被声明为消亡,它就会被每个 silo 视为消亡,即使它实际上并未消亡(只是已暂时分区或检测信号消息丢失)。 每个人都停止与它沟通。 一旦孤岛得知它已死(通过从表中读取其新状态),它就会终止其进程。 因此,必须有一个基础结构来以新进程的形式重启 silo(在启动时生成新的周期数)。 在 Azure 中托管时,会自动发生这种情况。 否则,需要另一个基础结构,例如配置为在故障或 Kubernetes 部署时自动重启的 Windows 服务。
如果表在一段时间内无法访问,会发生什么情况:
当存储服务关闭、不可用或遇到通信问题时, Orleans 协议不会错误地声明孤岛死亡。 正常运行的 silo 将继续正常工作,而不会出现任何问题。 但是,Orleans 无法将 silo 声明为消亡(如果通过未命中的探测检测到某个 silo 消亡,则无法将这一事实写入表中),并且无法允许新的 silo 加入。 因此,完整性将受到影响,但准确度不受影响 - 在表中分区永远不会导致 Orleans 错误地将 silo 声明为消亡。 此外,在部分网络分区(有些 silo 可以访问表,而有些则不可以)中,Orleans 可能会将某个 silo 声明为消亡,但所有其他 silo 需要在一段时间后才知道这一点。 检测可能会延迟,但 Orleans 不会因为表不可用而错误地灭杀某个 silo。
IAmAlive为诊断和灾难恢复撰写文章除了在 silo 之间发送的检测信号之外,每个 silo 会定期更新表的该 silo 自身的行中的“I Am Alive”时间戳。 这有两个用途:
诊断:为系统管理员提供了一种简单的方法来检查群集的运行情况,并确定 silo 最后一次运行的时间。 时间戳默认每 30 秒更新一次。
灾难恢复:如果孤岛在多个时间段内未更新其时间戳(通过
NumMissedTableIAmAliveLimit配置,默认:3),则新孤岛会在启动连接性检查期间忽略它。 这样可以帮助群集在孤岛崩溃而未进行正确清理的情况下进行恢复。
成员身份表
如前所述, IMembershipTable 作为孤岛相互查找的交会点,供 Orleans 客户端找到孤岛。 它还有助于协调关于成员资格观点的协议。
以下列表包含 IMembershipTable 的一些官方实现的实现说明:
Azure 表存储:在此实现中,Azure 部署 ID 用作分区键,孤立体标识(
ip:port:epoch)用作行键。 它们共同保证了每个 silo 的键是唯一的。 对于并发控制,使用了基于 Azure 表 ETag 的乐观并发控制。 每次从表中读取数据时,都会存储每个读取行的 ETag,并在尝试写回时使用。 Azure 表服务会在每次写入时自动分配和检查 ETag。 对于多行事务,利用了 Azure 表提供的批处理事务支持,从而保证基于具有相同分区键的行完成可序列化事务。SQL Server:在此实现中,配置的部署 ID 用于区分不同的部署以及哪些孤岛属于哪些部署。 silo 标识定义为相应表和列中
deploymentID, ip, port, epoch的组合。 关系后端使用乐观并发控制和事务,类似于在 Azure 表实现中使用 ETag。 关系实现需要数据库引擎生成 ETag。 对于 SQL Server 2000,生成的 ETag 是从调用NEWID()中获取的。 在 SQL Server 2005 及更高版本中,使用 ROWVERSION 。 Orleans 以不透明的VARBINARY(16)标记形式读取和写入关系 ETag,并将其以 base64 编码字符串的形式存储在内存中。 Orleans 支持使用UNION ALL(适用于Oracle,包括DUAL,目前用于插入统计数据)执行多行插入。 SQL Server 的确切实现和理由在 CreateOrleansTables_SqlServer.sql提供。Apache ZooKeeper:在此实现中,配置的部署 ID 作为根节点,而孤立标识(
ip:port@epoch)作为其子节点。 它们共同保证了每个 silo 的路径是唯一的。 对于并发控制,使用了基于节点版本的乐观并发控制。 每次从部署根节点读取数据时,都会存储每个读取子槽节点的版本,并在尝试将数据写回时使用。 每次节点的数据更改时,ZooKeeper 服务都会原子地增加版本号。 对于多行事务,利用了 multi 方法,从而保证基于具有相同父部署 ID 节点的 silo 节点完成可序列化事务。HashiCorp Consul 的键/值存储用于实现 Consul 的成员资格表。 有关更多详细信息 ,请参阅 Consul Deployment 。
AWS DynamoDB:在此实现中,群集部署ID用作分区键,Silo身份(
ip-port-generation)用作排序键,使记录唯一。 通过在 DynamoDB 上进行条件写入而使用ETag属性实现乐观并发。 实现逻辑与 Azure 表存储非常相似。Apache Cassandra:在此实现中,服务 ID 和群集 ID 的复合用作分区键,孤岛标识(
ip:port:epoch)作为行键。 它们共同保证了每个 silo 的行是唯一的。 对于并发控制,使用了基于使用轻型事务的静态列版本的乐观并发控制。 此版本列针对分区/群集中的所有行共享,为每个群集的成员身份表提供一致的递增版本号。 此实现中没有多行事务。用于开发设置的内存中仿真:此实现使用了一个特殊的系统粒度。 此 grain 驻留在某个指定的主要 silo 上,该 silo 仅用于开发设置。 在任何实际生产应用中不需要主要 silo。
设计理由
一个合理的问题是,为何不完全依赖 Apache ZooKeeper 或 etcd 通过潜在地使用 ZooKeeper 对包含临时节点的组成员身份的现成支持来实现群集成员身份? 为什么推行我们的会员制度? 主要有三个原因:
在云中部署/托管:
Zookeeper 不是托管服务。 这意味着在云环境中, Orleans 客户必须部署、运行和管理其 ZK 群集实例。 这是一个不必要的负担,并不是客户被强加的。 通过使用 Azure 表,Orleans 依赖于托管服务,使客户的生活变得更加简单。 基本上,在云中,使用云作为平台,而不是基础结构。 另一方面,在本地运行并管理服务器时,依靠 ZK 来实现
IMembershipTable是一个可行的选择。直接故障检测:
使用包含临时节点的 ZK 组成员身份时,将在 Orleans 服务器(ZK 客户端)与 ZK 服务器之间执行故障检测。 这不一定与服务器之间的 Orleans 实际网络问题相关联。 希望故障检测准确反映通信的群集内部状态。 具体而言,在此设计中,如果 Orleans silo 无法与
IMembershipTable通信,则它不被视为消亡,可以继续工作。 与此相反,如果使用了包含临时节点的 ZK 组成员身份,则与 ZK 服务器断开连接可能会导致将 Orleans silo(ZK 客户端)声明为消亡,而实际上它可能是存活的,并可完全正常运行。可移植性和灵活性:
作为Orleans理念的一部分,Orleans不强制对任何特定技术有强依赖,而是提供灵活的设计,不同的组件可以轻松与不同的实现进行切换。 这正是
IMembershipTable抽象所服务的目的。
成员身份协议的属性
可以处理任意数量的故障:
此算法可以处理任意数量的故障(f<=n),包括完全群集重启。 这与基于Paxos的“传统”解决方案形成鲜明对比,这些解决方案需要法定人数(通常是多数)。 生产状况显示,超过一半的筒仓出现故障的情况。 此系统可保持正常运行,而基于 Paxos 的成员身份无法继续。
发送到表的流量很少:
实际探测直接在服务器之间进行,而不是发送到表。 从故障检测的角度看,通过表路由探测将产生大量流量,并且准确度会下降 - 如果某个 silo 无法访问表,它就会遗漏写入“I am alive”检测信号,而其他 silo 将会声明其消亡。
可调精确度与完整性之间的比较
虽然无法实现完美和准确的故障检测,但通常希望能够权衡准确性(不想声明活孤岛死亡)与完整性(希望尽快声明死岛死亡)。 使用可配置的投票(用于声明消亡和未命中的探测)可以在两个方面之间进行权衡。 有关详细信息,请参阅耶鲁大学文章 Computer Science Failure Detectors(计算机科学故障检测器)。
规模:
协议可以处理数千台甚至数万台服务器。 这与传统的基于 Paxos 的解决方案(如组通信协议)形成鲜明对比,这些协议已知不会扩展到数十个节点之外。
诊断:
该表还能够为诊断和故障排除提供方便。 系统管理员可以立即在表中找到当前存活的 silo 列表,以及查看所有已灭杀 silo 和怀疑的历史记录。 这些信息在诊断问题时特别有用。
实现以下各项需要
IMembershipTable可靠的持久存储的原因:永久性存储用于
IMembershipTable有两个目的。 首先,它用作会合点,供 silo 相互查找彼此,以及供 Orleans 客户查找 silo。 其次,可靠存储有助于协调关于成员视图的协议。 虽然直接在 silo 之间以对等方式执行故障检测,但我们会将成员身份视图存储在可靠存储中,并使用此存储提供的并发控制机制来达成有关哪个 silo 存活、哪个 silo 消亡的协议。 从某种意义上说,此协议将分布式共识的难题外包给云。 这样做时,将利用基础云平台的全部功能,将其真正用作平台即服务(PaaS)。直接将
IAmAlive写入表中(仅用于诊断):除了 silo 之间发送的检测信号外,每个 silo 还会定期在其表行中更新“I Am Alive”列。 此“I Am Alive”列仅用 于手动故障排除和诊断,成员身份协议本身不使用此列。 通常以低得多的频率写入此列(每 5 分钟一次),对于系统管理员而言,这是一种非常有用的手段,可以检查群集的存活状态,或者轻松发现 silo 的上次存活时间。
致谢
感谢 Alex Kogan 在此协议第一个版本的设计和实现方面所做的贡献。 这项工作是作为 2011 年夏季 Microsoft Research 暑期实习的一部分完成的。
基于 IMembershipTable ZooKeeper 的实现由 Shay Hazor 完成,SQL IMembershipTable 的实现由 Veikko Eeva 完成,AWS DynamoDB IMembershipTable 的实施由 Gutemberg Ribeiro 完成,基于 IMembershipTable Consul 的实施由 Paul North 完成,最后 Apache Cassandra IMembershipTableOrleansCassandraUtils 的实施由 Arshia001 改编。