伴鱼技术团队

Technology changes the world

伴鱼开放平台 上线了! 源于实践的解决方案,助力企业成就未来!

伴鱼数据库中间件平台实践

本文将主要介绍伴鱼在TiDB数据库中间件平台建设过程中的实践与思考。

背景

伴鱼是一家在线教育公司,目前正处于业务高速增长期。伴鱼从2018年开始使用TiDB分布式数据库作为核心关系型数据库,在生产环境已经大规模使用,现有10+套TiDB集群,服务于绘本、口语、中台等核心业务线。

在技术架构层面,我司采用了微服务架构,各个服务之间通过通过自研的服务治理框架实现相互访问。对TiDB数据库的访问也有相应的SDK封装。SDK集成了数据库路由的功能,通过集群名和表名即可获得访问对应数据库的权限,而无须关注用户名密码等连接相关配置。这种数据库访问方式确实为微服务研发同学提供了便利,但是随着数据规模增大、公司业务线逐渐复杂,这种方式暴露出越来越多的问题。

第一,缺少访问控制机制。只要能拿到相应的集群名和表名,即可获取对应账号信息访问数据库,实际在公司内部也确实存在多个微服务通过同一个路由访问数据库的情况。当出现数据库相关问题时,难以快速定位相关服务。

第二,难以实现数据库统一接入。SDK路由查询获取TiDB集群对应的SLB地址,直接使用后端TiDB账号信息访问数据库。统一接入能力重度依赖SDK,对原生数据库客户端支持较差。

第三,兼容性、灵活性差。目前只能通过内部SDK访问数据库。如果使用原生客户端,需要向DBA提特殊申请,获取集群地址和账号信息,而这样又会失去SDK提供的统一监控、熔断等治理能力。

我们希望借助数据库中间件解决以上问题,然而经过调研发现,目前的开源数据库中间件大多面向MySQL、PostgreSQL等传统数据库,以提供分库分表功能为主,而我们所需要的统一接入、多租户、数据库治理等功能相对比较薄弱,而且对接其他内部系统时的二次开发成本也比较高。因此我们决定研发一套面向TiDB分布式数据库的中间件平台,一站式解决数据库管理和治理的问题。本文将从整体架构、功能实现、未来规划这几个方面,详细介绍伴鱼数据库中间件建设历程。

整体架构

在内部我们将该平台命名为Weir。整个Weir的系统架构如下图所示:

其中,proxy是代理层中间件,负责数据库访问层的统一接入、SQL请求的转发、并提供一定的数据库治理能力,是整个weir平台的核心组件。dashboard是控制台组件,面向数据库使用者和数据库管理员,提供数据库集群管理、路由管理、工单审批等功能。controller是中控管理组件,提供路由配置下发、proxy上下线管理及健康检查等功能,并作为dashboard的后端服务,提供API接口供前端调用。monitor是weir平台的监控框架,通过对接prometheus和grafana提供监控和告警功能。

功能实现

Weir的proxy组件和controller组件主要使用Go语言实现。下面将结合目前已经实现的一些核心功能,介绍我们在实现weir平台过程中的实践和思考。

基本概念

首先介绍weir租户管理中的几个核心概念。

  • cluster: 集群,weir平台本身支持多集群部署。
  • namespace: 租户,代表可访问的数据库资源和访问权限。
  • user: 用户,即访问weir平台的用户名和密码信息。用户信息是集群内唯一的,且只能关联到一个租户。
  • db cluster: 数据库集群。租户会与一个数据库集群进行关联,一个数据库集群可关联多个租户。
  • db: 数据库列表,租户只能访问数据库集群中指定的数据库,实现资源访问的逻辑隔离。

SQL处理流程

weir的proxy组件支持MySQL协议,客户端可以像直接访问TiDB / MySQL一样访问proxy。连接proxy需要使用租户的用户名+密码。proxy会根据用户名找到对应的租户,经过握手和认证通过后,连接建立成功,客户端连接即可访问租户中的数据库资源。

客户端向proxy发送SQL请求时,proxy会按MySQL协议对请求解码,按照不同的命令类型执行。对于普通的查询请求,proxy会解析SQL得到AST,根据AST执行不同的处理逻辑。在真正执行之前,proxy还可以执行一段用户自定义的预处理逻辑,如对特定SQL或AST进行拦截等。对DQL语句,proxy会直接转发请求到后端的TiDB集群,得到执行结果后返回给客户端。对于SET系统变量、事务管理等语句,proxy会修改当前连接的状态,并根据状态执行对应的操作,这部分处理流程会在下一节“连接状态管理”介绍。

连接状态管理

proxy作为代理层中间件,需要同时维护客户端连接和数据库连接。为了使代理中间件能够承载海量客户端连接,同时使数据库一侧连接数量可控,我们使用的连接池机制,为每个租户中的数据库集群创建一个独立的数据库连接池。多个租户即使关联到同一个集群,他们的连接池也是各自独立的,既能降低租户间的相互影响,又能复用集群配置方便管理。客户端连接执行普通查询时,proxy组件会从租户的数据库连接池中获取连接,使用该连接执行SQL语句,完成后再将连接放回连接池。

数据库连接可以通过执行特定的语句改变自身的状态,常见的如:执行SET语句设置变量,执行BEGIN、COMMIT、ROLLBACK语句进行事务操作等等。对于使用连接池机制的代理层中间件来说,如果想尽可能保持中间件的行为与数据库一致,至少需要处理好3种状态:SET设置变量、管理事务、执行Prepare语句。

对于SET设置变量的状态改变,weir proxy的处理方式是:在执行SET语句时,解析SET语句并校验。如果校验通过,proxy会将变量缓存在QueryCtx中(对应客户端连接)。当执行查询语句,获取数据库连接后,proxy会将QueryCtx中的系统变量与数据库连接的系统变量“同步”,将所有系统变量拼接成一条SQL语句发往数据库执行,成功后再执行查询语句。目前proxy仅支持设置SESSION级别的系统变量。

管理事务和执行Prepare语句都需要保持数据库连接状态,仅通过在QueryCtx中保存状态并不能正确处理这两种类型的语句。weir proxy通过连接绑定机制解决该问题。在开启事务、禁用AUTOCOMMIT、发起Prepare时,QueryCtx会从连接池中取出一个连接并与自身绑定,直到事务提交、启用AUTOCOMMIT、关闭Prepare时再释放该连接。由于存在3种状态的交叠,连接的绑定与释放的时机变得越发复杂,这里proxy使用状态机控制连接的绑定与释放,具体来说,状态机的每个状态唯一对应绑定连接的状态,QueryCtx会根据当前状态和控制语句执行特定的动作,根据动作的结果决定目标状态,并根据该状态控制连接的绑定与释放。

租户配置更新

weir平台支持多租户,proxy组件需要支持租户配置的动态变更,使客户端长连接对配置变更无感知。租户配置变更可通过控制台手动发起(例如为租户添加新的用户),或者由controller组件自动操作(例如controller检测到集群某个实例宕机后将该实例从集群列表中下线)。租户配置存放在weir的配置中心(生产环境一般使用etcd),通过两阶段提交触发集群内的所有proxy组件更新租户配置,保证proxy的一致性。

监控

weir proxy作为L7代理所有SQL请求流量,能够获得细粒度的监控信息。目前可以统计租户DB级别的QPS、错误数、客户端连接数、事务数、事务执行时间、数据库连接池状态等信息。未来还会根据需要开放更多的统计信息。

工单审批

为了将数据库的申请、使用、管理流程规范化,weir平台提供了建库、创建租户的工单审批流程。应用研发填写建库信息,提交工单经过逐级审批后,由DBA根据工单信息选择相应的TiDB集群,由controller组件执行自动化建库、创建租户的流程。整个流程已经与伴鱼技术中台的流程管理服务和钉钉通知服务打通,流程状态变化会通过钉钉消息通知到相关责任人,加快处理进度,大大优化了研发同学和DBA的操作体验。

未来规划

weir平台已经在伴鱼的测试环境稳定运行,部分服务已经接入weir。由于过去的SDK与原生客户端使用方式区别很大,迁移时需要修改服务内访问数据库部分的代码,因此整个迁移过程预计还会持续一段时间。

weir已实现了核心代理转发和租户管理功能,未来我们还会实现更多功能,包括但不限于:

  • 数据库治理能力(熔断、限流等)
  • 数据库防火墙
  • 性能优化
  • 云原生部署

目前weir proxy的代码仓库在Github托管,是PingCAP孵化器项目之一,我们期待与各位技术同仁一起,将weir平台打造成业界优秀的TiDB中间件平台。

欢迎关注我的其它发布渠道