Tarantool中的冲突解决(在多主模式下解决复制冲突的方法)

我在使用Tarantool进行多主场景下的开发时,如何实现冲突解决?

我正在开发一个需要高可用性的服务,因此决定将nginx作为负载均衡器(使用backup指令)用于两个Tarantool节点(禁用只读选项)。如果请求失败,它会重试到其他节点,但在网络问题(例如Tarantool节点之间的问题)的情况下,可能会发生冲突。

我该如何实现以下场景之一:

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

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

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

索引:

id - PK
user_id + type - 独特的可空树形索引(type可以为空)
user_id具有非唯一的树形索引
点赞
用户1229313
用户1229313
  1. 你需要在可能发生冲突的 space 上设置 before_replace 触发器,以实现应用程序的冲突解决规则。在触发器中,你可以比较旧记录和新记录,选择使用哪一个(或完全跳过更新,或将两个记录合并在一起)。

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

  1. 你需要在 space 接收任何更新之前设置正确的触发器。通常设置 before_replace 触发器的方法是在创建 space 后立即进行,所以你需要在系统 space _space 上设置另一个触发器,以捕获你的 space 被创建的时刻并在那里设置触发器。 这可以是 on_replace 触发器,

https://www.tarantool.io/en/doc/2.1/book/box/box_space/#box-space-on-replace before_replace 和 on_replace 之间的区别在于 on_replace 调用了行插入 space 后,而 before_replace 在调用之前调用。

  1. 要设置 _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

2019-06-25 07:48:35
用户9141779
用户9141779

关于 Kostja 回答的第二点(on_ctl_init+_space:on_replace 的组合),还有一个技巧:你需要利用 box.on_commit 来获取正在创建的 space。生成的代码片段如下所示:

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)
2019-07-11 13:09:37
用户11896037
用户11896037

关于2) 我遇到了问题。在我的情况下启用触发器"在创建空间时"会导致只读错误。原因是:触发器在从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之后才能启用触发器。并且在复制同步开始之前(否则我可能会遇到冲突或丢失统计数据)。我发现这里是合适的时间。我在 box.info.status 从 "loading" 变化后启用触发器。类似这样:

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

因此,在初始WAL读取后,before_replace() 触发器将在首次提交到“myspace”时执行和启用。

也许有可能在 box.info.status 的变化时触发触发器?这可能会使代码更清晰。但我不知道这是否可行。

2019-08-07 13:50:49