数据库也可以像电脑一样组装:使用Kafka建立关系数据库 – Robert Yokota


在本文中,我将展示如何扩展KCache以实现称为KarelDB的功能齐全的关系数据库。。此外,我将展示当今如何从现有的开放源代码组件中组装数据库体系结构,就像通过组装Web服务器(Jetty)、RESTful API框架(Jersey)、 JSON序列化框架(Jackson)和对象关系映射层(JDBI或Hibernate)等组件来创建Dropwizard这样的Web框架一样。

您好,KARELDB
在深入研究构成KarelDB的组件之前,首先让我向您展示如何快速启动并运行它。首先,下载一个版本,解压缩,然后进行修改config/kareldb.properties以指向现有的Kafka代理。然后运行以下命令:

$ bin/kareldb-start config/kareldb.properties

KarelDB运行会,在另一个终端上,输入以下命令来启动sqlline,这是用于访问JDBC数据库的命令行实用程序:

$ bin/sqlline
sqlline version 1.8.0
 
sqlline> !connect jdbc:avatica:remote:url=http://localhost:8765 admin admin
 
sqlline> create table books (id int, name varchar, author varchar);
No rows affected (0.114 seconds)
 
sqlline> insert into books values (1, 'The Trial', 'Franz Kafka');
1 row affected (0.576 seconds)
 
sqlline> select * from books;
+----+-----------+-------------+
| ID |   NAME    |   AUTHOR    |
+----+-----------+-------------+
| 1  | The Trial | Franz Kafka |
+----+-----------+-------------+
1 row selected (0.133 seconds)

KarelDB现在为您服务啦。

卡夫卡的持久性
KarelDB的核心是KCache,这是由Kafka支持的嵌入式键值存储。许多组件使用Kafka作为简单的键值存储,包括Kafka ConnectConfluent Schema Registry。KCache不仅概括了此功能,而且还提供了Map基于简单API的易用性。此外,KCache可以对由Kafka支持的嵌入式键值存储使用不同的实现。
CockroachDB是建立在RocksDB键值存储之上的SQL层,而YugaByteDB既是文档又是建立在RocksDB之上的SQL层。其他数据库(例如FoundationDB)声称是多模型的,因为它们使用键值存储作为基础一次支持多种类型的模型。
对于KarelDB,默认情况下,KCache配置为由Kafka支持的RocksDB缓存。这使KarelDB支持更大的数据集和更快的启动时间。如果需要,还可以将KCache配置为使用内存中的缓存而不是RocksDB。

AVRO用于序列化和架构演进
Kafka几乎已经采用Apache Avro作为其事实上的数据格式,这是有充分理由的。Avro不仅提供紧凑的二进制格式,而且还对模式演变提供了出色的支持。这种支持是为什么Confluent Schema Registry选择Avro作为其提供架构管理的第一种格式的原因。
KarelDB使用Avro定义关系(表)并序列化这些关系的数据。通过使用Avro,KarelDB在执行ALTER TABLE命令时可免费获得架构演化。

sqlline> !connect jdbc:avatica:remote:url=http://localhost:8765 admin admin 
 
sqlline> create table customers (id int, name varchar);
No rows affected (1.311 seconds)
 
sqlline> alter table customers add address varchar not null;
Error: Error -1 (00000) : 
Error while executing SQL
"alter table customers add address varchar not null"
org.apache.avro.SchemaValidationException: Unable to read schema:
{
 
"type" : "record",
 
"name" : "CUSTOMERS",
 
"fields" : [ {
   
"name" : "ID",
   
"type" : "int",
   
"sql.key.index" : 0
  }, {
   
"name" : "NAME",
   
"type" : [ "null", "string" ],
   
"default" : null
  } ]
}
using schema:
{
 
"type" : "record",
 
"name" : "CUSTOMERS",
 
"fields" : [ {
   
"name" : "ID",
   
"type" : "int",
   
"sql.key.index" : 0
  }, {
   
"name" : "NAME",
   
"type" : [ "null", "string" ],
   
"default" : null
  }, {
   
"name" : "ADDRESS",
   
"type" : "string"
  } ]
}
 
sqlline> alter table customers add address varchar null;
No rows affected (0.024 seconds)

如上所示,当我们第一次尝试添加具有NOT NULL约束的列时,Avro会拒绝模式更改,因为添加具有NOT NULL约束的新字段将导致反序列化失败,相反,对于没有该约束的旧记录领域,当我们添加具有NULL约束的同一列时,ALTER TABLE命令成功。
通过使用Avro进行反序列化,将使用默认值(null如果该字段是可选的)适当地填充添加到架构中的字段(无NOT NULL约束)。这些全部由基础的Avro框架自动处理。
Avro的另一个重要方面是,它定义了数据的标准排序顺序,以及比较功能,该功能直接对二进制编码的数据进行操作,而无需先反序列化。例如,这允许KarelDB有效地处理键范围查询。

CALCITE SQL
Apache Calcite是一个SQL框架,用于处理查询解析,优化和执行,但不包含数据存储。Calcite允许将关系表达式下推到数据存储中,以进行更有效的处理。否则,Calcite可以使用内置的枚举来处理查询调用约定,该约定允许将数据存储表示为可以通过迭代器接口访问的一组元组。嵌入式键值存储是此类元组的完美表示,因此KarelDB将处理键查找和键范围过滤(使用Avro的排序顺序支持),但将查询处理推迟到Calcite的可枚举约定。Calcite项目的一个不错的方面是它将继续为可枚举的约定开发优化,这将自动使KarelDB受益。
Calcite支持符合ANSI的SQL,包括一些较新的功能,例如JSON_VALUE和JSON_QUERY。

sqlline> create table authors (id int, json varchar);
No rows affected (0.132 seconds)
 
sqlline> insert into authors 
       > values (1, '{"name":"Franz Kafka", "book":"The Trial"}');
1 row affected (0.086 seconds)
 
sqlline> insert into authors 
       > values (2, '{
"name":"Karel Capek", "book":"R.U.R."}');
1 row affected (0.036 seconds)
 
sqlline> select json_value(json, 'lax $.name') as author from authors;
+-------------+
|   AUTHOR    |
+-------------+
| Franz Kafka |
| Karel Capek |
+-------------+
2 rows selected (0.027 seconds)


支持事务和MVCC的OMID
尽管Apache Omid最初旨在与HBase一起使用,但它是用于支持键值存储上的事务的通用框架。此外,Omid使用基础键值存储来持久保存与交易有关的元数据。这使得将Omid与现有键值存储(例如KCache)集成起来特别容易。
Omid实际上需要键值存储中的一些功能,即多版本数据和原子比较和设置功能。KarelDB将这些功能放在KCache之上,以便可以利用Omid对事务管理的支持。Omid利用键值存储的这些功能来使用多版本并发控制(MVCC)提供快照隔离。MVCC是用于在其他关系数据库(例如Oracle和PostgreSQL)中实现快照隔离的一种常用技术。
下面我们可以看到一个示例,该示例说明回滚事务将如何在事务开始之前恢复数据库的状态。

sqlline> !autocommit off
 
sqlline> select * from books;
+----+-----------+-------------+
| ID |   NAME    |   AUTHOR    |
+----+-----------+-------------+
| 1  | The Trial | Franz Kafka |
+----+-----------+-------------+
1 row selected (0.045 seconds)
 
sqlline> update books set name ='The Castle' where id = 1;
1 row affected (0.346 seconds)
 
sqlline> select * from books;
+----+------------+-------------+
| ID |    NAME    |   AUTHOR    |
+----+------------+-------------+
| 1  | The Castle | Franz Kafka |
+----+------------+-------------+
1 row selected (0.038 seconds)
 
sqlline> !rollback
Rollback complete (0.059 seconds)
 
sqlline> select * from books;
+----+-----------+-------------+
| ID |   NAME    |   AUTHOR    |
+----+-----------+-------------+
| 1  | The Trial | Franz Kafka |
+----+-----------+-------------+
1 row selected (0.032 seconds)

事务当然可以跨越多行和多个表。

AVATICA 实现JDBC
KarelDB实际上可以以两种模式运行,即作为嵌入式数据库还是作为服务器。对于服务器,KarelDB使用Apache Avatica提供RPC协议支持。Avatica既提供包装KarelDB的服务器框架,又提供可以使用Avatica RPC与服务器通信的JDBC驱动程序。
使用Kafka的一个优点是多个服务器都可以“尾随”同一组主题。这允许多个KarelDB服务器作为群集运行,而没有单点故障。在这种情况下,其中一台服务器将被选为领导者,而其他服务器将成为跟随者(或副本)。当关注者收到JDBC请求时,它将使用Avatica JDBC驱动程序将JDBC请求转发给领导者。如果领导者失败,将选出一名跟随者为新领导者。

组件数据库
如今,开源库已经实现了很多年前基于组件的软件开发所希望实现的目标。使用开源库,可以通过集成一些精心设计的组件来组装诸如关系数据库之类的复杂系统,其中每个组件都专门从事一项功能特别出色的事情。
上面我已经展示了KarelDB是几个现有开源组件的集合:


目前,KarelDB被设计为可复制的单节点数据库,但它不是分布式数据库。同样,KarelDB是一个普通的关系数据库,并且不处理流处理。对于分布式,流相关的数据库,请考虑改用经过生产验证的KSQL
KarelDB仍处于初期阶段,但是如果您对使用Kafka备份原始的关系数据感兴趣,请尝试一下。