云原生数据库架构系列之 session manager

2022-03-02

接下来打算写一个"云原生数据库架构"为主题的系列博客。背景是贵司的战略方向上,cloud first 将是非常重要的一环。 先写个免责声明:一是只讨论技术,针对这个主题的;二是内容纯粹是个人观点,跟 TiDB 的产品设计或者路线规划完全无关。

内容会非常散乱,想到哪写到哪。反正散乱大概也是本博客的风格了...目前在计划阶段会先写以下几篇:

  • session manager
  • cockroachdb severless 解读
  • "下一代云原生数据库设计"点评

话题很大,先从 session manager 这个很小的点作为开场。

假设一个数据库要放到云上,并且是以服务形式去售卖,运维由厂商去提供。那么一个很容易想到的问题是,如何去升级和迭代产品?

如果是停机升级,服务就不可用了,这个都是会影响 SLA 的,用户会投诉。所以需要把稳定的部分和不稳定的部分拆开来,加一层代理,让靠近用户连接的那一端不会变化,而核心重启不影响到用户连接。也就是需要 session manager 这一层。

session manager 首先是一个 proxy。那这个 proxy 需要满足哪些要求呢?我们从基本的 proxy 开始看起。

L4 proxy,即 tcp 层的 proxy,只能做到负载均衡,限流这一些,它是不能识别 tcp 承载的上层的协议内容(也就是应用层)的。所以如果通过这种 proxy 后面接入 TiDB,其中一个 TiDB 节点挂了,proxy 可以选择其它的 TiDB 节点继续服务,可以避免单点故障导致整体数据库不可用。由于 tcp 断了,旧的连接会挂,只能对新建的连接继续提供可用性。这个明显不能够满足让用户无感知地升级数据库的需求。

L7 proxy,也就是应用层的 proxy。SQL 层协议包括建立连接,用户登陆认识,以及具体的 SQL 请求处理。假设只处理建立连接及登陆认识,至于所有 SQL 请求都当作黑盒内容进行转发...如果 TiDB 节点挂了,客户端可以不用重新做登陆过程,连接不断开,只是 SQL 请求挂掉了。用户还是有感知的。假设是一个 SQL proxy,那么它需要再解析 SQL 层协议。如果 TiDB 节点挂掉,proxy 可以知道是执行哪一条 SQL 语句挂掉,通过重试执行这条 SQL,让这条请求不会失败。但是 SQL 能不能执行重试呢?这是一个问题。

所以我们得到一条结论:只有 proxy 帮客户自动重试,才能让客户端对服务重启无感知,然而只有无状态,才能够做自动重试。

通对过 L4 和 L7 proxy 能力对比,很显然的是 proxy 在越上层,能够做的事情越多,做到用户无感知的程度也越高;从整个连接不可用,到连接和会话可用,只是请求层失败...当然,越往高层的 proxy,资源使用开销也是越大的。

SQL 是不是无状态的呢?显然不是。只读没有问题,但是如果存在写操作,存在事务,情况就不一样的。begin .. select .. insert .. insert,这个操作之后节点挂了,insert 的数据就丢了,而 select 已经返回过结果给用户,是不能够通过重试这一系列的 SQL 来恢复的,只能给用户返回失败。 也就是说 begin 之后的失败,基本上是不能做自动重试的。

分布式系统里面,有成功/失败/不可知状态,不可知状态也很难处理。比如单条 insert 或者是 begin 之后的最后一条 commit 操作,发出去之后,收到成功失败的消息,节点挂了。这跟 commit 返回失败了是不一样的,这时的状态其实是"不可知",是不能返回成功,注意!!也不能返回失败。那怎么办?把不可知状态传回给客户端...极端的手段,通过断开连接,让客户端也变成不可知状态...显然这是用户很不友好的。

所以 SQL proxy 程度还是不够的,处理部分只读 SQL 可以,带事务之后就有状态就呵呵了。如果 session manager 要做到完全的重启服务对客户透明,就需要在状态管理上继续进一步。

一个用户的 session 包括哪些信息呢?最基础的部分,用户的登陆信息,这一层是比较容易做的。

接下来,是跟 SQL 执行环境相关的信息,比如当前的全局 session 变量和局部 session 变量;

这一层的数据量也不多。再往细了拆,事务以及 statement 的。如果一个 statement 完全无状态,可重试,那么 session manager 不需要为这个 statement 存储特别的东西,失败了直接重试就行。

随便看一个特殊的场景,prepare plan cache 的处理。查询计划是应该缓存在 session manager 里面呢,还是在 server 里面? 如果缓存在 session manager 里面,当 server 节点挂掉之后,缓存是不会丢失的。但是这就需要每次请求发送到 server 那边,会有传输成本。 如果缓存在 server 里面呢,节点挂了 plan cache 就没有了。不过没关系,注意 plan cache 是可以通过 SQL 重建出来的... 这说明什么? 缓存并不算是有状态的,session manager 里面记录原始的 SQL 信息,而 plan cache 缓存在节点,这种方式是可行的。

然后看最难处理的事务部分。事务是有状态的,对于 TiDB,每个 session 执行事务过程中,会把数据先写到本地内存(membuffer),等到事务提交的时候,再做两阶段提交写到 TiKV。如果在 TiDB 节点随时可能 crash 的情况下,事务的修改的内存就不能放在节点自身的内存里面了,

  • 一种思路是直接存到 TiKV 层
  • 另一种思路是存到 session manager 中

按第一种思路,会对事务模块做出较多的修改。比如改造成未提交的事务每次写都写到 tikv,这个引入的改动复杂度比较高。不改造事务模块情况下,membuffer 要么选择用一个 redis 类的 kv 缓存,要么在 session manager 内置一套 kv 缓存。

事务的另一个依赖是 tso。如果把取 tso 也移到 session manager,那么好处是 tso 的性能和稳定性会得到很多提升。当事务移到 session manager 中会出现什么状况?原本的 TiDB server 变成了纯粹的无状态的 planner server!

那换一种思考角度,其实是把 planner / executor 独立出 TiDB server,变成无状态的查询计划服务器,而不是把 session 和事务移出去,变成所谓 session manager。

这...好奇怪的一个结论。说到底,还是只挪 SQL proxy 层,作为 session manager,是不能真正做到完全的状态解耦的,事务处理不了。

退而求其次的话,就是设计成服务挂掉时,请求可以失败,但是不断连接...在这个假设的基础上,再去优化,尽量降低影响。

session 需要保存的状态信息方面,基础的登陆认证的部分是肯定的;然后是 session 变量相关的修改的,通过 SQL parser 出来,截持下来在 session manager 里面做。事务部分处理不了,就不处理了,聊一聊优化。

前面的假设是 server 部分可能随时 panic 掉,这时进行中的事务状态是不可控的。真实的场景,虽然可能有 panic 发生,但是对于升级这种重启,其实是可控的,并不是任意的。如果让 session manager 和 server 可以定义好交互协议,来沟通升级之前,让 session manager 先做一些准备,就可以避免这种场景下的请求失败问题。

cloud nativearchitecturedatabase

HNS.to is a highly insecure way of browsing Handshake domains and should only be used for demo or educational purposes. Click to see preferable resolutions methods