Tarantool中的冲突解决(如果发生冲突,如何在主-主模式下修复复制)

时间:2019-06-24 10:06:56

标签: lua tarantool

在多主场景中使用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

3 个答案:

答案 0 :(得分:2)

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-replace before_replace on_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

答案 1 :(得分:2)

关于2)(on_ctl_init + _space:on_replace的组合),还有另外一个技巧:您需要利用box.on_commit来获取要创建的空间的权限。结果片段如下:

$career_solutions_data = DB::select("
    SELECT 
    career_solutions.id, 
    career_solutions.subject, 
    career_solutions.date, 
    career_solutions.public, 
    career_solutions.views, 
    career_solutions.optional, 
    career_solutions.on_offer, 
    users.username, 
    users.profile_picture, 
    categories.category, 
    categories.category_url,
    categories.color, 
    career_solutions_categories.category as sub_category 

    FROM career_solutions 

    INNER JOIN categories 
        ON categories.id = career_solutions.topic_category_id 

    INNER JOIN career_solutions_categories 
        ON career_solutions_categories.id = career_solutions.topic_subcategory_id 

    INNER JOIN users 
        ON users.id = career_solutions.user_id 

    INNER JOIN privacy_settings 
        ON privacy_settings.user_id = users.id 

    WHERE users.deleted_at IS NULL 
    AND (
            (privacy_settings.career_solutions = 0 AND public = 1 ) 
            OR (users.id IN (

                SELECT contacts.contact_id 
                FROM contacts 
                WHERE contacts.user_id = $id
                )
            )
        ) 

    OR users.id = $id

    ORDER BY date desc limit 5000
");

答案 2 :(得分:0)

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

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从“ 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更改时点击触发器?这可以使代码更整洁。但是我不知道是否可能。