如何序列化数据库中的事件?

时间:2017-04-27 16:20:24

标签: sql database postgresql nosql

我的问题有点复杂,我会尝试用一个例子来简化它。

假设我想实现一个支持SQL数据库的聊天系统。该 系统由一个房间组成,任何人都可以在其上发布消息。

要使用聊天,您需要安装应用(想想一个移动应用)。这个程序保持 设备上的历史记录并定期连接到服务器以获取 新消息。

我想解决的问题是如何只获取这些新消息而不会丢失 其中任何一个

一个天真的实现将使用带有两列(id AUTO_INCREMENT, message)的SQL表。客户端连接,第一次获取历史记录 然后只询问id大于他们拥有的最后一个id的消息。

在实践中,这似乎不起作用,并且您有失踪的风险 消息。我制作了两个使用PostgreSQL的程序。一个程序只有 插入表中,另一个程序定期SELECT *进行检查 在每次迭代时,没有新项目出现,其id小于 上一次迭代的最大ID。

换句话说,让我们在一次迭代中说你SELECT *,得到ids 3,5和 9.然后在下一次迭代中,你得到3,5,8和9.一条新消息出现了 比上一次迭代的最大id小的id。这意味着 如果客户要求所有大于9的ID,则会错过8。

如果我不清楚,我可以提供程序的来源。

现在,如果我运行一个checker程序实例和5个实例 插入器,未命中每分钟发生2至3次。

我需要一个数据库架构才能实现应用程序可以实现的系统 仅获取自上次连接以来的新消息。我以SQL为例, 但我对任何其他奇特的NoSQL替代品持开放态度。如果有人知道多么真实 聊天系统,如Signal,Whatsapp或其他实施,我也会 感兴趣。

编辑:这些例子的表格如下,与上述情况略有不同:

create table test (id serial primary key, value int);

这是检查程序的代码:

#include <iostream>
#include <libpq-fe.h>
#include <stdio.h>
#include <stdlib.h>
#include <unordered_set>

int main(int argc, char** argv)
{
  auto const conninfo = "dbname = test password = postgres";

  PGconn* conn = PQconnectdb(conninfo);
  if (PQstatus(conn) != CONNECTION_OK)
  {
    std::cerr << "error: " << PQerrorMessage(conn) << std::endl;
    return 1;
  }

  std::unordered_set<uint64_t> vals;
  uint64_t lastId = 0;

  while (true)
  {
    PGresult* res = PQexec(conn, "SELECT id FROM test ORDER BY id");
    if (PQresultStatus(res) != PGRES_TUPLES_OK)
    {
      std::cerr << "error: " << PQerrorMessage(conn) << std::endl;
      return 1;
    }

    for (int i = 0; i < PQntuples(res); i++)
    {
      auto const id = std::atoll(PQgetvalue(res, i, 0));
      if (id <= lastId)
      {
        if (!vals.count(id))
        {
          std::cout << id << " was missed" << std::endl;
          vals.insert(id);
        }
      }
      else
      {
        vals.insert(id);
        lastId = id;
      }
    }

    PQclear(res);
  }

  PQfinish(conn);
  return 0;
}

以下是插件程序的代码:

#include <iostream>
#include <libpq-fe.h>
#include <stdio.h>
#include <stdlib.h>
#include <unordered_set>

int main(int argc, char** argv)
{
  auto const conninfo = "dbname = test password = postgres";

  PGconn* conn = PQconnectdb(conninfo);
  if (PQstatus(conn) != CONNECTION_OK)
  {
    std::cerr << "error: " << PQerrorMessage(conn) << std::endl;
    return 1;
  }

  while (true)
  {
    PGresult* res = PQexec(conn, "INSERT INTO test (value) VALUES (1)");
    if (PQresultStatus(res) != PGRES_COMMAND_OK)
    {
      std::cerr << "error: " << PQerrorMessage(conn) << std::endl;
      return 1;
    }
    PQclear(res);
  }

  PQfinish(conn);
  return 0;
}

运行一个检查程序进程和一些5个插入程序(所有编译器至少编译为-O2),您将看到偶尔出现的“错过xxx”消息,这些消息证明在上一次迭代的最后一个id之前生成了id。 / p>

证明这一点的另一种方法如下。启动两个psql shell,1和2.我将命令在每个shell中按顺序输入:

1> create table test (id serial primary key, value int);
1> begin;
1> insert into test (value) values (1);

2> insert into test (value) values (2);
2> select * from test;

-- you see the row (id:2, value:2) in the table

1> commit;

2> select * from test;

-- now you see (id:1, value:1) and (id:2, value:2)
-- an id smaller than 2 has just appeared

1 个答案:

答案 0 :(得分:1)

好吧,根据您自己的解释,您的问题很可能是您的实施不尊重您的“理论”方法。因为你没有提供MCVE,我会根据这个假设回答你。

如果您确实使用id定义AUTO_INCREMENT,那么您将永远不会遇到id低于客户端的新消息的情况。

因此,当客户端写入新消息时,它没有id并且在队列中推送到服务器。在客户端,您可以使用颜色编码(如灰色背景)将队列中的所有消息显示为“暂定”。

当服务器收到消息时,它会执行INSERT并为消息分配ID。

下次发生来自服务器的更新时,客户端会更新自上一个最高id值以来的消息列表。用户自己的消息将按顺序显示在聊天缓冲区中。然后可以同时丢弃队列中的所有消息。

然后,您可以保证数据始终是最新的,因为您的不变“新消息始终具有更高的ID”始终为真。

话虽这么说,最好混合使用ID和时间戳,并使用id字段表示要显示数据的顺序,使用timestamp查询所有更改自上次刷新以来然后,您可以支持消息可变性(修改或删除)。

虽然,这是一个免费的好建议:不要重新发明轮子,使用已经证明多年来运作良好的现有协议。您可以使用XMPP+MUCSILCIRCAxolotlmultiuser上有一篇论文与之聊天。)

基本上,在互联网上聊天是一个问题已经解决了很多次,如果你认为你可以做得更好,不要重新发明轮子,只需添加你对现有堆栈的贡献。您可以与现有的工具,库,框架进行互操作。

关于你的编辑:

你遇到的问题有两个方面:

  1. 你直接在数据库中同时插入;
  2. Postgresql打破了INSERT语句的事务的原子性规则,特别是对于id的AUTO_INCREMENT规则,以帮助实现高效的并发插入。
  3. 因此,您对AUTO_INCREMENT ed id将始终更高的考虑是对您的算法的错误假设。

    作为解决方案,您可以:

    • 强制执行gapless increments,代价是每次在事务中插入时锁定数据库,
    • 您可以根据时间戳查询您的表格(参见上文),
    • 您可以在表格上使用AFTER INSERT触发器来处理插入后自动递增非自动增量id值,
    • 您可以使用队列与客户端进行通信,并让一名工作人员处理数据库中的插入,
    • 你可以不重新发明轮子并使用经过验证的已知工作异步协议来支持多用户聊天的服务器端日志记录,例如XMPP