Cassandra 收到了对等方的无效八卦生成

Cassandra received an invalid gossip generation for peer

我们有一个基本的 2 节点 Cassandra 集群。两个节点 运行 版本 3.9 具有启用集群的最小配置。其中一个节点正在发送错误的生成,这导致另一个节点显示警告

WARN  [GossipStage:1] Gossiper.java:1146 - received an invalid gossip generation for peer /10.3.185.234; local time = 1479469393, received generation = 1872927836

导致问题的节点 1 具有来自

的输出
nodetool gossipinfo

/10.3.185.234

generation: 1872927836

1872927836 纪元是一个遥远的日期(2029 年 5 月 8 日,星期二 09:43:56 GMT)。节点 2 合法地丢弃数据包。我能以某种方式修复 node-1 以发送正确的生成吗?

通过使用 cqlsh

更改 system.local table 中的 gossip_generation 值解决了问题
update system.local  set gossip_generation = 1479472176 where key='local';

此更改后重新启动服务

我不知道手动设置它有什么影响,但另一种修复它的方法是立即重新启动整个集群。这可能对我们有用。 (在我们修复后发现了这个)。

文档说要进行多次滚动重启直到它被修复(这对我们不起作用。)但是重启整个集群的大锤确实起作用了。所有世代都设置得当。

我们使用 scylla(版本 = 2.1.3-0.20180501.8e33e80ad)并且我们能够在不重启整个集群的情况下解决这个问题。

我们的集群最近一直在丢失节点,因为这些节点在启动的八卦阶段重新启动并且不允许加入集群。原因是:status=UN(启动和正常)的节点正在喷出以下错误并且不允许那些受影响的节点在八卦阶段加入集群。在我们的例子中,错误信息是:

Jul 04 01:54:17 host-10.3.7.77 scylla[30263]: [shard 0] gossip - received an invalid gossip generation for peer 10.3.7.7; local generation = 1526993447, received generation = 1562158865

现在让我们深入了解上述错误消息的详细信息和上下文:

  • 每个节点都配置了一个种子列表,它会在启动期间尝试向其传播和收集集群信息。
  • 在启动时,它会创建一个“世代”编号(世代编号是一个纪元),它在八卦期间与种子主机共享。

gossiper.register(this->shared_from_this());

auto generation_number=db::system_keyspace::increment_and_get_generation().get0();

_gossiper.start_gossiping(generation_number, app_states, gms::bind_messaging_port(bool(do_bind))).get();

  • 节点在其第一次启动时将其世代号发送给种子并与其他种子八卦以传递信息。种子存储这个世代号作为参考。这被称为上述错误消息中提到的 local_generation 术语,即 UN 节点 10.3.7.77 说对等节点 10.3.7.7 正在发送生成编号 1562158865(即被称为 receive_generation),但它已存储为参考 1526993447。您会注意到 1526993447 指的是 2018 年 5 月 22 日的纪元,而 1562158865 指的是 2019 年 7 月 3 日的纪元,即节点 10.3.7.7 于 2018 年 5 月 22 日首次启动,并将其生成编号发送为 1526993447。
  • 由于2个纪元之间的差异大于1年,UN节点将拒绝允许其他节点加入

int64_t MAX_GENERATION_DIFFERENCE = 86400 * 365;

if (local_generation > 2 && remote_generation > local_generation + MAX_GENERATION_DIFFERENCE) { // assume some peer has corrupted memory and is broadcasting an unbelievable generation about another peer (or itself)

logger.warn("received an invalid gossip generation for peer ..... }

  • 现在在启动期间,increment_and_get 的逻辑是:
auto req = format("SELECT gossip_generation FROM system.{} WHERE key='{}'", LOCAL, LOCAL);
return qctx->qp().execute_internal(req).then([] (auto rs) {
    int generation;
    if (rs->empty() || !rs->one().has("gossip_generation")) {
        // seconds-since-epoch isn't a foolproof new generation
        // (where foolproof is "guaranteed to be larger than the last one seen at this ip address"),
        // but it's as close as sanely possible
        generation = service::get_generation_number();
    } else {
        // Other nodes will ignore gossip messages about a node that have a lower generation than previously seen.
        int stored_generation = rs->one().template get_as<int>("gossip_generation") + 1;
        int now = service::get_generation_number();
        if (stored_generation >= now) {
            slogger.warn("Using stored Gossip Generation {} as it is greater than current system time {}."
                        "See CASSANDRA-3654 if you experience problems", stored_generation, now);
            generation = stored_generation;
        } else {
            generation = now;
        }
    }
    auto req = format("INSERT INTO system.{} (key, gossip_generation) VALUES ('{}', ?)", LOCAL, LOCAL);
  • 从上面的逻辑来看,服务器首先从system.localtable中查找世代号。如果该值为空,它会生成一个新数字,即当前时间,因为生成世代号的逻辑仅取决于当前时间。如果它不为空,它与当前时间进行比较并使用较大的值,即最近的时间并将其写回 system.local table

int get_generation_number() { .... auto now = high_resolution_clock::now().time_since_epoch(); int generation_number = duration_cast(now).count(); ....}

  • 因此节点在启动时生成并发送给种子的 generation-number 通常总是更接近当前时间,但种子 UN 节点作为本地参考存储的世代号并不变化。

  • 为了完全避免集群重启:我们根据上面解释的代码逻辑在生产中采用了这种方法。

    -- 根本问题是本地生成的有问题 存储在 UN 种子节点中的节点没有改变。 (但是 每次重新启动时有问题的节点都会发送一个新的生成编号,即 接近当前时间)

    -- 想法:让我们更新存储在 UN 节点中的问题节点的本地生成,以便问题节点发送的 remote-generation 号码将落在 1 年内。

    -- 那么我们如何更新联合国种子节点中的这个值呢?我们需要让有问题的节点发送一个 gen 编号(epoch),其值落在 UN 种子节点中存储的本地 gen 编号的 1 年 window 内。但由于代码总是以当前时间为 gen 编号,而当前时间是 2019 年 7 月,我们该怎么办?

    -- 我们将有问题的节点上的 TIME 改回 1526993447 1 年以内的值。选择 1 年末的纪元值 window,即将系统时间更改为一个值 say 2019 年 3 月 31 日,即纪元 1554030000 而不是 2018 年 10 月 2 日并重新启动节点。该节点将重新启动并发送生成编号 1554030000(因为它查找 system.local table)或当前时间 2019 年 3 月 31 日无论如何到种子。

    -- UN 种子节点获取此值并验证问题节点发送的 remote-generation 号码在 2018 年 5 月 22 日的 1 年内,因此它继续更新其参考(本地生成)。

else if (remote_generation > local_generation) { logger.trace("Updating heartbeat state generation to {} from {} for {}", remote_generation, local_generation, ep); // major state change will handle the update by inserting the remote state directly this->handle_major_state_change(ep, remote_state); } ....

-- 我们已成功更新存储在UN种子节点中的问题节点的引用(local gen)。 -- 现在我们停止有问题的节点,将有问题的节点上的时间重置为当前时间并重新启动,有问题的节点将发送 2019 年 7 月 4 日的最新纪元,即纪元 1562215230 -- 现在时间重置和重启后,由于 1562215230(使用最新时间发送的是有问题的节点)减去 1554030000(存储在 UN 种子节点中的本地引用)< 1 年,有问题的节点将被允许加入集群。

-- 我们建议您在第 1 年 window 结束时选择 epoch/date,但在 1 年内,越晚越好,因为新的 1 年 window 从您选择的日期&这个问题对于那么长的 LOL 已经得到缓解 - 是的,这个问题发生在 运行ning 长的集群上。这意味着您需要每年偶尔进行一次滚动重启以延长 1 年 window.

步骤如下或程序:

步骤:

  1. 如果有问题的节点是10.3.7.7并且报错say 10.3.7.77(UN 节点),确保 10.3.7.7 的种子是 10.3.7.77,这样我们就可以保证它与这个节点对话,而我们不必 搜索以找出集群中的谁也在说话。如果种子 for 7.7节点与报错的节点不同,则 查看种子节点打印的错误消息来决定哪个 纪元也要重置。在我们的例子中,因为我在 7.77 上看到错误,我 将 7.7 的种子更改为 7.77 节点。

  2. 启动有问题的节点。

  3. 种子节点应该开始打印错误。捕获我们节点的错误消息并记下本地生成编号,以便我们也选择要重置的日期。在我们的例子中,味精如下:

Jul 04 01:54:17 host-10.3.7.77 scylla[30263]: [shard 0] gossip – received an invalid gossip generation for peer 10.3.7.7; local generation = 1526993447, received generation = 1562158865

  1. cqlsh 到有问题的节点 10.3.7.7 并将世代号更新为 1526993447 1 年内的一个纪元,但在 1 年 window 结束时选择一个纪元,例如 1554030000(3 月 31 日) , 2019) 而不是说 july/october 2018,这样您就有更长的新 1 年 window.

  2. 在有问题的节点上,运行 命令

    5.1 'update system.local set gossip_generation = 1554030000 where key='local';'

    5.2 'nodetool flush'

  3. 停止有问题的节点

  4. 编辑配置文件并将 CQL (native_transport_port) 从 9042 更改为 9043,以便客户端无法连接并插入数据 – 在此阶段插入数据将设置时间戳为2019 年 3 月这是不正确的,即防止数据损坏。这是预防措施

  5. 更改系统时间即“date -s ’31 MAR 2019 11:03:25′”

  6. 验证系统时间已通过 运行ning date 命令
  7. 更改
  8. 启动UN种子节点的问题节点和尾日志,错误应该消失。
  9. 等待一段时间(几分钟就足够了)让八卦发生并验证有问题的节点现在是否是 UN。
  10. 运行 在另一个节点上命令 'nodetool status' 以检查其是否为 UN。
  11. 您可以追踪 UN 种子节点的日志并检查您是否仍然收到错误。如果您再次看到错误 - 从头开始​​重复这些步骤。你错过了什么。
  12. 一旦节点声明为 UN:

    14.1 关闭节点

    14.2 在配置文件中将 CQL (native_transport_port) 从 9043 改回 9042。

    14.3 重置盒子上的系统时间

    14.4 验证系统时间恢复正常

  13. 一旦你改回时间和端口,启动节点。并且节点应该仍然是 UN.

自白:

  1. 是的,我们在生产中做了这个练习。该节点无论如何都被认为是死的,因此风险很小,因为搞砸一个死节点更不会产生影响,如果程序失败,我们将只牺牲 1 个节点,因此剩下集群重启的唯一选择。
  2. 我们扫描了 master 分支的 scylla 代码库以了解系统时间在集群通信中的用法,发现只有 2 个地方让我们相信更改系统时间会起作用。同样通过将 CQL 端口更改为 9043,我们消除了客户对现有数据的任何污染。

故事寓意:

  1. 这发生在 2.1 版本的 scylla 中,截至今天 2019 年 7 月 4 日,scylla 的 master 分支仍然具有相同的代码逻辑,因此这也可能发生在版本 3 及更高版本中。 2 .每隔几个月最好对节点进行滚动重启,以便节点发送一个新的 gen 号进行八卦,并延长 1 年 window。
  2. 如果你有一个长运行ning集群> 1年,如果一个节点重启,它会受到这个错误的影响,发生的节点重启越多,流行病传播得越多。
  3. 如果代码逻辑相同,这对 cassandra 也适用,我认为是。

参考文献:

https://github.com/scylladb/scylla/blob/134b59a425da71f6dfa86332322cc63d47a88cd7/gms/gossiper.cc

https://github.com/scylladb/scylla/blob/94d2194c771dfc2fb260b00f7f525b8089092b41/service/storage_service.cc

https://github.com/scylladb/scylla/blob/077c639e428a643cd4f0ffe8e90874c80b1dc669/db/system_keyspace.cc

您还可以在我的博客上找到上述 explanation/fix 详细信息 https://mash213.wordpress.com/2019/07/05/scylla-received-an-invalid-gossip-generation-for-peer-how-to-resolve/