【问题标题】:Conflict resolution in Tarantool (how to fix replication in master-master mode in case of conflicts)Tarantool 中的冲突解决(如何在主-主模式下修复复制以防发生冲突)
【发布时间】:2019-11-06 03:00:36
【问题描述】:

在多主场景中使用 Tarantool 时如何解决冲突?

我正在开发一个应该具有高可用性的服务,因此决定使用 nginx 作为 tarantool 的两个节点的负载平衡器(带有备份指令)(禁用只读选项)。它会重试对其他节点的失败请求,但如果网络问题(例如,tarantool 的节点之间)可能会发生冲突。

如何实现以下场景之一:

  1. 在每个节点上选择一个更新的元组
  2. 自定义逻辑(可能是冲突等的另一个空间)

另一个问题是如何定义唯一的可空复合索引(null 是一个可以多次出现的值)

| id | user_id | type | {some data} |

索引:

id - PK
user_id + type - unique nullable tree index (type is nullable)
user_id has non unique tree index

【问题讨论】:

  • 所以你希望user_id + type 是唯一的,除非type 为空?
  • @DarkWiiPlayer,非常接近。我在这里问了这个问题:stackoverflow.com/questions/56737313/…
  • 这很难实现;如果可能,您应该考虑使用主从设置,或者只是以无冲突的方式构建数据库交互。

标签: lua tarantool


【解决方案1】:

1) 您需要在可能发生冲突的空间上设置一个 before_replace 触发器,以实现您的应用程序的冲突解决规则。

https://www.tarantool.io/en/doc/2.1/book/box/box_space/#box-space-before-replace

在触发器中,您可以比较新旧副本记录并选择使用哪一个(或完全跳过更新,或将两条记录合并在一起)。

2) 您需要在正确的时间设置触发器,在空间开始接收任何更新之前。 通常在创建空间时设置 before_replace 触发器的方式是正确的,因此您需要一个触发器在系统空间 _space 上设置另一个触发器,以捕捉创建空间的时刻并将触发器设置在那里。这可以是on_replace触发器,https://www.tarantool.io/en/doc/2.1/book/box/box_space/#box-space-on-replacebefore_replaceon_replace的区别是*on_replace是在插入一行到空间之后调用的,而before_replace 之前被调用过。 3) 要设置 _space:on_replace() 触发器,您还需要正确的时机。使用的最佳时机是刚刚创建 _space 时,即 box.ctl.on_schema_init() 触发器。 https://www.tarantool.io/en/doc/2.1/book/box/box_ctl/#lua-function.box.ctl.on_schema_init

【讨论】:

    【解决方案2】:

    关于 Kostja 回答的第二点(on_ctl_init+_space:on_replace 的组合),还有一个技巧:您需要利用 box.on_commit 来访问正在创建的空间。生成的 sn-p 如下:

    local my_space_name = 'ny_space'
    local my_trigger = function(old, new) ... end
    box.schema.on_schema_init(function()
        box.space._space:on_replace(function(_, new_space)
            if new_space.name == my_space_name then
                box.on_commit(function()
                    box.space[my_space_name]:before_replace(my_trigger)
                end
            end
        end)
    end)
    

    【讨论】:

      【解决方案3】:

      关于 2) 我遇到了问题。在我的情况下,启用“在创建空间时”触发会导致 read_only 错误。原因是:触发器在从 WAL 读取时尝试 upsert 到统计表。

      local function before_replace(old, new)
          -- collision resolving here
          if box.session.type() ~= 'applier' then
              box.space.stat:upsert(
                  { "key", 0 },
                  {
                      {"+", stat.COUNT, 1}
                  })
          end
          return
      end
      

      在这种情况下,我只需要在读取 WAL 时启用触发器。在复制同步开始之前(否则我可能会遇到冲突或统计数据松散)。我发现this 是这样做的正确时间。在 box.info.status 从“正在加载”更改后,我启用了触发器。像这样:

      local my_space_name = 'myspace'
      local function loading_before_replace(old, new)
          if box.info.status == "loading" then
              return
          end
          box.space.my_space_name:before_replace(before_replace, loading_before_replace)
          return before_replace(old,new)
      end
      
      local function _space_on_replace(old, new)  
          if not new or not new.name then
              return
          end
          -- skip system spaces
          if string.startswith(new.name, "_") then
              return
          end
      
          if new.name == my_space_name  then
              box.on_commit(function()
                  box.space.my_space_name:before_replace(loading_before_replace)
              end)
          end
      end
      
      local function set_triggers()
          box.ctl.on_schema_init(function()
              box.space._space:on_replace(_space_on_replace)
          end)
      end
      

      所以before_replace() 触发器将在初始 WAL 读取后第一次提交到myspace 时执行并启用。

      也许有可能在 box.info.status 改变时触发?这可以使代码更干净。但我不知道这是否可能。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-06-24
        • 1970-01-01
        • 1970-01-01
        • 2013-08-28
        • 1970-01-01
        • 2021-06-26
        • 2019-07-03
        • 1970-01-01
        相关资源
        最近更新 更多