【问题标题】:How to handle race condition with cassandra updates?如何使用 cassandra 更新处理竞争条件?
【发布时间】:2017-04-18 20:20:37
【问题描述】:

我正在学习 Cassandra。我正在为特定用例建模 cassandra 表。下面描述的用例 -

用户可以写帖子。 其他用户可以回复帖子。 用户还可以对帖子“投赞成票”或“投反对票”。 用户按日期或赞成票或反对票对帖子进行排序。

这是我的表定义 -

CREATE TABLE post.comments_by_post (
postid text,
parentpostid text,
createdon bigint,
username text,
userid text,
displayname text,
upvotes int,
downvotes int,
comment text,
PRIMARY KEY ((postid, parentpostid), createdon)
) WITH CLUSTERING ORDER BY (createdon DESC);

为了增加“upvote”,我有一个更新查询 -

UPDATE post.comments_by_post SET upvotes = incrementedValue where postid=1 and parentpostid = 2 ;

incrementedValue 是在前一个值中加 1 计算得出的。

incrementedValue = previousValue + 1

我的问题是,如果我必须根据表中的前一个值计算增量,这将导致竞争条件和数据损坏。

我们有更好的方法吗?

我知道cassandra有counter列定义类型,可以用于这样的增量值,但是需要额外的表。计数器列不能与不属于主键的普通列​​一起使用。

【问题讨论】:

    标签: cassandra race-condition cassandra-2.0


    【解决方案1】:

    下面的表和二级索引将允许您在没有 Counter 表和任何锁的情况下实现计数:

    CREATE TABLE votes_by_comment (
       postid text,
       parentpostid text,
       userid text,
       vote text, //can be 'up' or 'down'
    PRIMARY KEY (( postid, parentpostid ), userid))
    
    CREATE INDEX ON votes_by_comment (vote);
    

    当用户“投票”时:

    INSERT INTO votes_by_comment (postid, parentpostid, userid, vote) VALUES ('comment1', 'post1', 'user1', 'up');
    

    当用户“投反对票”时:

    INSERT INTO votes_by_comment (postid, parentpostid, userid, vote) VALUES ('comment1', 'post1', 'user1', 'down');
    

    userid 作为聚类列将允许它避免竞争条件并限制一个用户进行多次投票。

    统计选票:

    SELECT count(*) from votes_by_comment WHERE postid='comment1' AND parentpostid='post1' and vote='up';
    

    二级索引将允许它通过vote值进行选择,因为二级索引的选择将在一个分区键内执行,它会有很好的性能。

    但是这种方法不允许你在 Cassandra 端实现投票排序,它应该在应用端实现。

    【讨论】:

    • 插入记录将解决所有竞争条件问题。
    【解决方案2】:

    发生并发更新时,您将丢失一些更新。
    前任。用户 A 读取当前值,假设为 10。同时另一个用户 B 也读取了当前值,他将得到 10。然后用户 A 用新值 11 发出更新请求。然后用户 B 也将用新值发出更新请求11.所以你丢失了用户A更新。

    柜台是您的最佳选择。

    计数器是一个特殊的列,用于存储以增量方式更改的数字。 Cassandra 计数器在 Cassandra 2.1 中进行了重新设计,以减轻一些困难。阅读What’s New in Cassandra 2.1: Better Implementation of Counters 了解计数器的改进。

    您可以创建一个像这样的计数器表:

    CREATE TABLE vote_counter (
       postid text,
       parentpostid text,
       upvotes counter,
       downvotes counter,
       PRIMARY KEY((postid,parentpostid))
    )
    

    现在你可以这样查询了:

    UPDATE vote_counter SET upvotes = upvotes + 1 WHERE postid = ? AND parentpostid = ?
    UPDATE vote_counter SET upvotes = upvotes - 1 WHERE postid = ? AND parentpostid = ?
    UPDATE vote_counter SET downvotes = downvotes + 1 WHERE postid = ? AND parentpostid = ?
    UPDATE vote_counter SET downvotes = downvotes - 1 WHERE postid = ? AND parentpostid = ?
    

    【讨论】:

    • 感谢 Ashraful。创建另一个表将打破数据建模的规则。就像读取数据的单个查询一样。
    【解决方案3】:

    根据您的描述:

    ...用户按日期或赞成票或反对票对帖子进行排序

    您的目标是三个用例,但您的表定义只解决了第一个(按日期)。为了解决另外两个问题,您需要创建两个表,分别使用 upvotesdownvotes 字段作为集群键,并努力使所有三个表保持同步:

    CREATE TABLE post.comments_by_post (
        postid text,
        parentpostid text,
        createdon bigint,
        username text,
        userid text,
        displayname text,
        upvotes int,
        downvotes int,
        comment text,
        PRIMARY KEY ((postid, parentpostid), upvotes) 
    ) WITH CLUSTERING ORDER BY (createdon DESC);
    

    如果您升级 C* 并使用 3.0,您可以节省大量工作并创建 Materialized View

    回到你的并发问题,在分布式环境中计数真的很难。根据您的要求,我建议您两种可能的解决方案

    1) 您不需要精确(您可以容忍计数过多/过少)。在这种情况下,我建议您使用新的 Cassandra 柜台来存放您的柜台。这种方法的主要缺点是您实际上失去了(从您的应用程序的角度来看)按顺序获得结果的能力,因此您需要在应用程序杠杆上应用排序。您还保存了上述其他两个表,因为计数器保留在另一个表中。

    2) 您需要精确。在这种情况下,您需要序列化对每个帖子计数器的访问。您可以通过保留一个小缓存您将要更新或最近已更新的帖子计数器来实现这一点,并在应用程序的每个项目上获取一个每次你想更新它的水平。 64k 个帖子应该足够了。现在您知道,对于每个帖子,您按顺序执行更新。你不会出错,因为你不应用 global 锁,你只应用 local 锁。您仍然需要三个带有 C* 2.0 的表,或者一个 + 带有 C* 3.0 的实体化视图。

    【讨论】:

      猜你喜欢
      • 2019-06-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-05
      相关资源
      最近更新 更多