耗时 18 个月,我们构建了一个真正可扩展的无服务器 SQL 数据库( 二 )


要想有意义地解释多租户的工作方式 , 我需要回顾一下单租户架构 。 首先 , 一个单租户的CockroachDB集群由任意数量的节点组成 。 每一个节点都用于数据存储和计算 , 它们通常托管在自己的机器上 。 CockroachDB在单个节点上具有分层架构 。 最高层是SQL层 , 用于解析、优化和执行SQL语句 。 这是通过将高级SQL语句巧妙地转换为简单的读写请求 , 然后发送给底层的键值层 。
键值层维护一个事务性的、分布式的、复制的键值存储 。 这很拗口 , 所以让我把它分解一下 。 每一个键都是一个唯一的字符串 , 映射为任意值 , 就像在字典里一样 。 键值将这些键值对以排序的方式存储 , 以便快速查找 。 键值对也被分组为范围 。 每个范围包含全部键值对中连续的、不重叠的部分 , 按键排序 。 范围分布在可用的节点上 , 为了高可用性 , 至少要复制三次 。 在全有或全无事务中 , 可以添加、删除和更新键值对 。 下面有一个简化的示例 , 展示了如何将更高级的SQL语句转换为简单的KVGET调用:
耗时 18 个月,我们构建了一个真正可扩展的无服务器 SQL 数据库

在单租户CockroachDB中 , 每个节点上的SQL层与键值层都在在同一个进程中 。 虽然SQL层总是调用在同一个节点上运行的键值实例 , 但是键值通常会“扇出”其他节点上运行的其他键值实例额外调用 。 这是因为SQL所需要的数据往往位于分散在集群中各节点的范围内 。
多租户架构
怎样扩展这种单租户架构以支持多个租户?每个租户都会觉得自己拥有自己专用的CockroachDB集群 , 并在性能和安全方面与其他租户隔离 。 但是如果我们试图在租户之间共享SQL层 , 这就很难实现了 。 某个租户失控的SQL查询可以轻易地影响同一进程中其他租户的性能 。 另外 , 共享同一个进程会带来很多难以可靠缓解的跨租户安全威胁 。
一种可能的解决方法是为每一个租户提供一套自己的隔离进程 , 同时运行SQL和键值层 。 但是 , 这造成另一个问题:我们不能在租户之间共享键值存储 。 这样就消除了对租户架构的一个主要优点:有效地将许多小型租户的数据打包到共享的存储层中 。
考虑过这个问题后 , 我们认识到 , 通过分离一些组件并共享其他组件 , 可以很好的解决这个难题 。 由于SQL层难以共享 , 因此我们决定在每个租户的进程中将它的键值层的事务组件和分发组件隔离开来 。 与此同时 , 在所有租户共享的存储节点上继续运行键值的复制组件和存储组件 。 这样 , 我们可以得到“两全其美”的效果:对于每个租户来说 , SQL进程的安全性和隔离性 , 以及共享存储节点的效率 。 下面是最新的图表 , 展示了两个隔离的每租户SQL节点与共享存储层之间的交互情况:
耗时 18 个月,我们构建了一个真正可扩展的无服务器 SQL 数据库

存储节点不再运行租户的SQL查询 , 但它们仍然利用复杂的基础设施 , 为单租户CockroachDB提供支持 。 节点故障的检测和修复不会影响到数据的可用性 。 租赁者 , 为每个范围提供读取服务 , 协调写入 , 根据活动情况进行移动 。 繁忙范围会被自动分割;安静范围会被合并 。 基于负载的不同 , 范围在各节点之间重新平衡 。 存储层将热范围缓存在内存中 , 并将冷范围推送到磁盘 。 跨可用性区域的三向复制确保你的数据存储得到安全性和高可用性 。
在看到这种架构后 , 你可能会想知道共享存储节点的安全性问题 。 为了保护租户数据 , 我们花费了大量时间设计和实施强有力的安全措施 。 每一个租户都会键值密钥空间的一个隔离的、受保护的部分 。 实现方法是在SQL层生成的每个密钥之前加上租户的唯一标识符 。 而不是生成一个像///的键 , SQL将生成一个像////的键 。 这意味着由不同租户生成的键值对在各自的范围内进行隔离 。 另外 , 存储节点还将认证所有来自SQL节点的通信 , 并每个租户只能访问以他们自己的租户标识符为前缀的密钥 。