Cassandra为同伴收到了无效的八卦一代

时间:2016-11-18 11:57:42

标签: cassandra cassandra-3.0 database-cluster

我们有一个基本的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以发送正确的代吗?

3 个答案:

答案 0 :(得分:4)

通过使用cqlsh更改system.local表中的gossip_generation值来解决此问题

(void*)

此更改后重新启动服务

答案 1 :(得分:1)

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

文档说要进行多次滚动重启,直到它被修复(这对我们没用。)然而,重启整个集群的大锤确实有效。所有的代都设置得恰当。

答案 2 :(得分:0)

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

我们的集群最近丢失了节点,因为这些节点已重新启动,并且在启动的八卦阶段不允许加入集群。原因是:状态= UN(正常)的节点发出以下错误,并且在闲聊阶段不允许受影响的节点加入群集。在我们的例子中,错误消息是:

  

Jul 04 01:54:17 host-10.3.7.77 scylla [30263]:[碎片0]八卦-接收到对等体10.3.7.7的无效八卦;本地生成= 1526993447,收到的生成= 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术语,即联合国节点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日启动,其发送代号为15269993447。
  • 由于两个纪元之间的差异大于1年,因此联合国节点将拒绝允许另一个节点加入
  

int64_t MAX_GENERATION_DIFFERENCE = 86400 * 365;

     

如果(local_generation> 2 && remote_generation> local_generation +   MAX_GENERATION_DIFFERENCE)   {//假设某些同位体已损坏内存   并且正在广播有关另一个同伴(或   本身)

     

logger.warn(“收到对等体.....}的无效八卦消息

  • 现在在启动过程中,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.local表中查找世代号。如果该值为空,则它将生成一个新的数字,即当前时间,因为生成世代号的逻辑仅取决于当前时间。如果其不为空,则将其与当前时间进行比较并使用较大的值(即,较新的时间)并将其写回到system.local表
  

int get_generation_number(){....现在自动=   high_resolution_clock :: now()。time_since_epoch();整型   generation_number = duration_cast(now).count(); ....}

  • 因此,节点在启动时生成并发送给种子的世代号通常总是接近当前时间,但是种子UN节点作为本地引用存储的世代号不会改变。

  • 要完全避免集群重新启动:我们在生产中基于上述代码逻辑采用了这种方法。

    -根本问题是有问题的本地一代    存储在联合国种子节点中的节点未更改。 (但    每次重新启动时有问题的节点都会发送一个新的发电代号    接近当前时间)

    -IDEA:让我们更新存储在UN节点中的有问题节点的本地生成,以便有问题节点发送的远程生成编号将在1年内。

    -那么我们如何在UN种子节点中更新此值?我们需要使有问题的节点发送一个Gen编号(纪元),其值落入存储在联合国种子节点中的本地Gen编号的1年窗口中。但是由于代码始终将当前时间作为发电机组编号,并且当前时间是2019年7月,所以我们该怎么办?

    -我们将有问题的节点上的TIME更改为1529993447的1年之内的值。在1年窗口的结尾选择一个纪元值,即将系统时间更改为例如2019年3月31日的值,即epoch 1554030000,而不是2018年10月2日并重启节点。该节点将重新启动并发送gen 1554030000(因为它查找system.local表)或当前时间(无论如何是2019年3月31日)作为种子。

    -联合国种子节点获得此值并验证有问题的节点发送的远程发电数量在2018年5月22日的1年内,因此,它将继续更新其参考(本地发电)。

  

else if(remote_generation> local_generation){logger.trace(“将心跳状态的生成从{}的{}更新为{}”,remote_generation,local_generation,ep);   //主要状态更改将通过直接插入远程状态来处理更新   this-> handle_major_state_change(ep,remote_state);   } ....

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

-我们建议您在1年窗口结束时选择一个纪元/日期,但在1年内,越晚越好,因为新的1年窗口从您选择的日期开始,并且此问题已得到缓解大声笑–是的,长时间运行的群集上会出现此问题。这意味着您需要每年不时进行一次滚动重启,以延长1年窗口。

以下是该过程的步骤:

步骤:

  1. 如果有问题的节点为10.3.7.7,并且报告说错误 10.3.7.77(UN节点),请确保10.3.7.7的种子为10.3.7.77,这样我们就可以保证与该节点进行通讯,而不必 搜索以找出集群中谁也在讲话。如果种子 7.7节点与报告错误的节点不同,则 查看种子节点打印的错误消息,以确定哪个 太重设了。在我们的案例中,由于我看到了7.77的错误, 将7.7的种子更改为7.77节点。

  2. 启动有问题的节点。

  3. 种子节点应开始打印错误。捕获节点的错误消息,并记下本地发电机号,以便我们选择要重置的日期。在我们的情况下,味精如下:
  

Jul 04 01:54:17 host-10.3.7.77 scylla [30263]:[shard 0]八卦–   接收到对等体10.3.7.7的无效八卦生成;本地   世代= 1526993447,收到的世代= 1562158865

  1. cqlsh到有问题的节点10.3.7.7,并在1526993447的1年内将世代号更新为一个纪元,但请在1年窗口结束时选择一个纪元,例如1554030000(2019年3月31日),而不是说7月/ 2018年10月,以便您拥有更长的1年新窗口。

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

    5.1'更新system.local设置gossip_generation = 1554030000,其中key ='local';'

    5.2'nodetool刷新'

  3. 停止有问题的节点

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

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

  6. 验证运行时间命令已更改系统时间
  7. 启动有问题的节点和联合国种子节点的尾部日志,错误应该消失。
  8. 等待一段时间(只需几分钟)就可以进行八卦,并验证是否有问题的节点现在是联合国。
  9. 在另一个节点上运行命令“ nodetool status”以检查其UN。
  10. 您可以跟踪UN种子节点的日志并检查是否仍然收到错误。如果您确实再次看到错误,请从头开始再次重复这些步骤。螨虫错过了一些东西。
  11. 将节点声明为UN:

    14.1关闭节点

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

    14.3在包装盒上重置系统时间

    14.4验证系统时间是否恢复正常

  12. 更改回退时间和端口后,
  13. 启动节点。并且该节点仍应为UN。

自白:

  1. 是的,我们在生产中进行了此练习。无论如何,该节点都被视为已死,因此将死节点更糟的机会很小,因此风险很小,如果过程失败,我们将只牺牲1个节点,因此只有一个选项可以集群重启。
  2. 我们扫描了master分支的scylla代码库,以了解集群通信中系统时间的使用情况,结果发现只有2个地方使我们有信心更改系统时间是可行的。通过将CQL端口更改为9043,我们消除了客户对现有数据的任何污染。

故事的道德人:

  1. 这发生在scylla的2.1版本中,并且直到今天2019年7月4日,scylla的master分支仍然具有相同的代码逻辑,因此这也可能发生在版本3及更高版本中。 2。每隔几个月最好对节点进行滚动重启,以便节点为八卦发送新的发电编号,并延长1年的窗口期。
  2. 如果集群运行时间超过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

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