引言
随着互联网的飞速发展,人类社会的数据量迅速激增,据统计目前人类一年产生的数据就相当于人类进入现代化以前所有历史的总和,而且互联网业务的发展通常具有爆发性,业务量很可能在短短的一个月内突然爆发式地增长几千倍,对应的数据也很可能快速地从原来的几百GB飞速上涨到了几百个TB。如果在这爆发的关键时刻,系统不稳定或无法访问,那么对于业务将会是毁灭性的打击。
这时,传统的单机数据库提供的服务,在系统可扩展性、性价比方面已不再适用。伴随着对于系统性能、成本以及扩展性的新需求,分布式数据库系统应运而生,力求突破单机MySQL容量和性能瓶颈,彻底消除单机数据库无法支撑企业业务高速发展的后顾之忧。
在《关于分布式数据库,你需要知道的一些事》系列里,大U将以UCloud分布式数据库产品——UDDB为例,用三篇的篇幅为大家详细解析分布式数据库的一些重要特性和技术实践细节。
在上一篇文章《 关于分布式数据库,你需要知道的一些事(上)》中,我们对 UDDB 产品的功能特性,进行了简洁又全面的勾勒。本篇文章中,我们将为大家重点介绍 UDDB 的六大功能特性。那么,我们开始吧:)
1. 基于 UDB 和 ULB 的架构设计
UDDB 的底层存储,要充分复用 UDB(UCloud DataBase) ,这是我们在研发 UDDB 时确定的首要原则,也是风险最低,能够最大程度防止系统风险,保护业务的方法。
目前,NewSQL 是数据库领域的技术热点。这说明过去十几年来,业内在 NoSQL 方向的尝试,并不成功,最后还是认为 SQL 和关系模型最靠谱。但是,从零开始打造一款存储类的产品,需要很长时间,才能达到工业强度,真正用来承载客户业务。而时间不等人,当越来越多的 UCloud 客户需要分布式数据库时,我们必须想办法尽快满足,但前提是风险足够可控。
基于上述考虑, 我们选择了数据库中间件加 UDB 产品来构建 UCloud 分布式数据库。数据库中间件经过过去十几年的积累,技术上足够成熟,已经是业内使用最多、最广的分布式数据库解决方案。UDB 产品经过多年的运营和迭代,不仅产品本身品质量过硬,还打造了一支高效专业的运营和 DBA 团队,7×24值守线上系统。
UDDB 的最终方案,直接复用高可用 UDB,作为存储节点,确保底层存储无单点并实现高可用,而存储节点下面挂载的只读实例,则复用了普通 UDB 节点。我们认为,这是保障业务数据可靠性和系统稳定性的最优选择。同时,充分复用 UDB 来做存储,也意味着 UDDB 开发团队,可以将精力聚焦在中间件上,集中精力打造精品。
值得一提的是,中间件是无状态的,数据都在 UDB ,即使中间件出现了灾难性的bug,只要数据在 UDB 中不丢失,就可以通过分析业务+中间件日志来还原客户数据。这是从头开发的 NewSQL 产品无法做到的。
同时,我们使用了 ULB 来做中间件节点的双活和负载均衡。ULB 同样为 UCloud 的口碑产品,稳定、性能好、能够迅速应对后端节点的异常,在过去几年经受住了各种业务的检验。
总的来说,通过复用 UDB 和 ULB,稳妥又低成本地解决了数据存储和中间件节点的容灾和负载均衡问题,这体现了基于公有云平台来开发产品的优势:通过复用成熟的标准化产品,项目组只需要专注于核心模块的开发,通过搭积木的方式,搭建出产品整套架构。
UDDB 总体架构图如下:

2. 水平扩容,一键完成
UDDB 六大功能特性中,自动化的水平扩容是我们最想做的功能。使用传统的数据库中间件,扩容依赖手工进行,操作复杂、过程漫长同时风险又高。这其中的痛苦,搞过的人都深有体会。我们先来看一下,传统的水平扩容有哪些步骤:

2.1 传统扩容方式的弊端
-
操作复杂: 一个扩容操作,涉及到5个步骤,诸如部署新节点、迁移数据、修改路由规则等操作,需要严谨的操作和细心的验证。这其中又以迁移数据这一步最为繁琐(想象一下将某数据库节点下面的一半数据,按照迁移规则迁移到新的节点,最终要保证一条数据都不能错);
-
过程漫长:迁移数据过程涉及到几十GB乃至几百GB数据,在不同数据库节点间的导入导出,迁移完成之后还需要对数据的正确性进行验证,这些都是极其消耗时间的操作;
-
高风险:扩容过程涉及到架构的变更和数据的改动,配置文件的一个小纰漏, 或者迁移/验证脚本的一个小问题,都可能导致整个流程回滚甚至数据丢失。
2.2 解决方案
在 UDDB 产品中,我们设计了一个极简的交互流程: 只需要点击一个按钮,即可完成全部水平扩容操作。

在这个按钮背后,是精心编写并严格测试的近5000行代码,将手工扩容操作各步骤进行了自动化。在水平扩容期间,UDDB 不停服,所有数据库表均能正常访问(只是在每迁移一张子表到目标 UDB 节点后,UDDB 会有几毫秒到零点几秒的访问中断)。
3. 读写分离功能,业内最好用
前面提到,水平扩容是 UDDB 产品特性中我们最想做的功能,那么读写分离则是最需要做的功能。通过回访 UDB 客户,我们发现很多客户的业务数据量并不大,单个UDB 实例就能够容纳,但是读请求远大于写请求,以至于最高配的UDB实例都无法支持。

3.1 业界常见做法
读写分离,是业内解决这个问题的常见方法。 具体的做法是添加几个从库,在主库和从库之间建立好复制关系,构成一个读写分离组;然后修改业务逻辑,将部分或者全部读请求分流到从库,通过从库来分担读请求的压力。
但这需要一定的开发和运维工作量:需要部署从库,配置复制关系,还有修改业务程序,对读请求做路由。虽然目前 UDB 产品,可以通过管理页面,一键创建从库并配置主从复制关系,但是业务程序的修改,依然无法避免。
3.2 读写分离一揽子支持
在客户回访当中,我们发现不少客户,希望 UCloud 提供对读写分离问题的一揽子支持。为此,UDDB 的一个核心目标,就是完美支持读写分离,做到好用且功能强大。下面是我们的方案:
-
读写分离集群创建:在创建 UDDB 实例页面中,存储节点选择一个,并指定挂载到存储节点下的,只读实例的个数,即可创建一个一主多从的读写分离集群:
-
业务访问: 业务访问该读写分离集群,如同访问一个单机数据库,不需要做任何修改。由UDDB的中间件节点,负责识别业务的读请求,然后分流到只读实例。如果业务中包含事务,则事务下的所有读写请求都路由到主节点。
-
读请求分流比例配置: UDDB 默认的读请求分流策略是均匀分流,即将读请求均匀分摊到主节点和从节点。同时,UDDB 还提供了其他三种分流策略,总共四种。
-
存储节点: 选择该配置项,中间件会将所有读请求,都发往主节点,而从节点不接收任何读请求;
-
均衡:选择该配置项,中间件会将读请求,均匀分流到主节点和各从节点,这也是UDDB默认的分流方式;
-
只读均衡:选择该配置项,中间件会将读请求,均匀分流到各从节点,而主节点不接收任何读请求;
-
自定义:选择该配置项,能够自由配置分流到每个节点的读请求比例。
这四种分流策略,可在管理页面进行配置:
-
总之,UDDB 为读写分离场景提供了极简的系统创建方式,同时提供了四种读请求分流配置策略,可以覆盖绝大部分业务的需求。系统创建极简,读请求配置功能强大,这是对于读写分离问题,UDDB能够给到客户的支持。
另外,对于有多个存储节点的 UDDB,亦可以提供读写分离。可以在各存储节点后面,再挂载若干只读实例,将本来落到该存储节点的读请求,分流到只读实例。
4. 垂直分表和水平分表完美结合
4.1 业务拆分需求
当业务的数据量,达到单机数据库能够承载的上限,则需要做数据拆分。通过回访使用UDB的客户,我们发现,业务数据拆分需求,分两种情况:
-
垂直分表:业务的增长,导致数据总量达到了,单个 UDB 节点的存储容量上限,为此需要将一部分表,迁移到新的 UDB 节点,但客户希望所有数据库表的访问地址,仍然是同一个。
-
水平分表:业务的增长,导致某张大表的数据总量,达到单个 UDB 节点的存储上限,为此需要增加新的 UDB 节点,并将这张表进行水平拆分,将其数据均匀划分到各UDB 节点。
传统的中间件,有的只支持水平分表,但是不支持垂直分表; 有的两种都能够支持,但是对垂直表的 SQL 语句,有限制,其 SQL 支持范围,等同于水平分表。比如,无法支持垂直表的复杂 JOIN 和嵌套子查询等。
4.2 UDDB的解决方案
-
垂直分表和水平分表同时支持:如果客户提交的检表语句是普通的建表语句,则 UDDB 将会选择在第一顺位的 UDB 节点创建该表; 客户也可以指定在某个 UDB 节点上,创建该表,例如:
CREATE TABLE `t2_common`( `id` int(11) NOT NULL, `price` int(11) NOT NULL, `name` varchar(64) DEFAULT NULL, `dt` varchar(32) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1 ON UDB node 'UDBha-vh3jmr';
-
垂直表 SQL 的支持程度和单机 MySQL 基本一致:包括支持复杂的JOIN和嵌套子查询等。示例:
SELECT st.create_dept AS dept_id, COUNT(*) AS total FROM db1.special_task st RIGHT JOIN db1.alarm_event AS ae ON ae.event_uuid LIKE CONCAT('zxjc\_4\_',st.create_dept) OR ae.event_uuid LIKE CONCAT('zxjc\_4\_',st.id,'\_%') RIGHT JOIN db1.alarm_action_status_history AS ash ON ash.risk_event_id = ae.id AND ash.old_status in (2010,2020) AND ash.new_status = 2020 AND ash.change_time >= '2013-01-01' AND ash.change_time <= '2013-12-31' WHERE st.create_dept = 34 AND st.type = 22 AND st.create_time >= '2013-01-01' AND st.create_time <= '2013-12-31';
目前,UDDB 对水平表的SQL 支持程度,受限于跨节点 JOIN 和分布式事务这两个问题,和主流的数据库中间件基本一致。包括:
-
支持单个分片的 Insert/Update/Delete/Repalce 语句
-
支持单表的 Select 语句,支持复杂的 Group by、Order by、Limit和集函数
-
支持分片规则一致的两个表的JOIN操作
5. 优于传统中间件的 SQL 支持
传统的数据库中间件以 SQL 作为业务访问接口,但对SQL支持力度不够。一直以来,这点为用户所诟病,也是 NewSQL 抨击中间件的一大理由(即使传统中间件事实上支持了90%以上的项目)。
若仔细分析各类 SQL 的语法特点,及客户业务场景,可以看到,中间件模式,在 SQL 支持上并非天生羸弱。当然,中间件模式下,SQL 支持的提升并非一蹴而就,而是需要长期的打磨,作为一个公有云服务,UDDB 将以快速迭代的方式,持续完善对 SQL 的支持。在本节先总结下,目前 UDDB 对 SQL 的支持相较传统中间件完善的地方。
5.1 DDL 语句支持
用户可以使用标准的 MySQL 客户端,或者业务程序通过标准的 MySQL API,向 UDDB 提交 DDL 语句,使用方式如同单机 MySQL 。

5.2 业内最好的聚合 Select 的支持
聚合 Select 语句支持(语句中包含Group by、Order by、Distinct、集函数、Limit等指令), 一直是传统数据库中间件的弱项。如果您手上刚好有数据库中间件,可以测试下该软件,对下面这条SQL语句的支持:
SELECT DISTINCT id, AVG(price) FROM t1 WHERE id>=1 GROUP BY CONCAT(id,name) ORDER BY AVG(price) LIMIT 10;
我们的测试下来,发现不少数据库中间件无法支持该 SQL ,或者语法错误,或者 SQL 的解释逻辑出错,或者返回结果不正确。
在我们看来,实现对聚合 Select 100%的支持,是必须的。传统的数据库中间件,对聚合类 SQL 支持不够,是因为这些中间件往往来源于互联网项目,而这类项目对聚合Select 使用较少。但 UDDB 的设计目标,是通用的分布式数据库,不限定用户业务场景,因此应该尽量提高 SQL 支持能力,对聚合 Select 的支持,是我们走出的第一步。
为此,我们花费不少时间,设计了一套完整全面覆盖聚合 Select 的算法,来做到对聚合 Select 100% 的支持。
所以,对于上面给出的 SQL , UDDB 是支持的。不仅如此,UDDB 还支持更多复杂的聚合 Select 。 您可以实际开通一个 UDDB 实例,来进行相关的测试。
5.3 水平分表建表语法,返璞归真
业内各大基于中间件构建的分布式数据库,都有自定义的水平分表建表语法,这些语法或者简单清晰,或者全面而有繁复,都各有自己的特点,但总让人感觉缺少点什么。
而 UDDB 的水平分表的建表语法,和 MySQL 水平分区表建表语法几乎完全一致(只是在关键字前面增加字符:U):
CREATE TABLE t1(
uid INT NOT NULL,
dt DATE
)
UPARTITION BY HASH(uid)
UPARTITIONS 8;
CREATE TABLE t3(
id INT NOT NULL ,
price int NOT NULL
)
UPARTITION BY RANGE(id)
USUBPARTITION BY HASH(price)
USUBPARTITIONS 4(
UPARTITION p1 VALUES LESS THAN (100000),
UPARTITION p2 VALUES LESS THAN maxvalue
);
我们之所以这样做,是因为在深入考察了其他基于中间件的分布式数据库的建表语法后,发现不管是从功能的全面性和简洁度, 还是从客户的接受度上,各分布式数据库使用的语法,其实都没有超过 MySQL 官方的水平分区表建表语法。
一方面,MySQL 这套建表语法,提供了Hash、Key、List、Range四种数据划分方式,而且 Hash、Key 和 List、Range 还可以复合,进行二次划分,这就给水平分表提供了足够的灵活度;同时,四种基础的划分方式简单易懂,而组合之后又功能强大,充分体现了 SQL 语法设计方面的功力(当然,MySQL 这套并非自创,而是沿用自 Oracle ,在客户接口上,这也可能是 MySQL 为数不多的模仿 Oracle 的功能点之一)。
另一方面,MySQL 建表语法更有传播度和客户基础,网上也有很多资料可供学习。因此,与其自己造轮子,不如尊重 MySQL 和客户习惯,直接复用大家熟悉的语法。
唯一的问题,是 MySQL 水平分区表建表语法的实现稍显复杂,但为了达到最好的客户体验,我们选择了把这套建表语法规则全部实现了一遍(除了不支持 KEY 划分方式外)。
6. 极简的业务接入流程:如何不停服从 UDB 迁移数据?
一款存储产品,除了要有好用的功能和优良的品质,也需要方便客户迁入业务,否则客户的使用成本就会比较高。客户甚至会因为不方便迁入业务数据这个原因,放弃使用。
为了让业务更好地接入 UDDB ,我们提供了一条迁移指令,通过这条指令,可以在不停服的情况下,将业务数据,从 UDB(包括运行在 UHost 上的自建MySQL实例)迁移到 UDDB 。迁移期间,业务仍然访问原有的 UDB 实例,一旦迁移完成,业务的全部访问请求,将被切换到 UDDB 。
6.1 业务数据迁移流程
假如客户购买了一个 UDB 实例,或者在 UHost 上自建了一个 MySQL 实例,(为叙述方面,下面统一称其为 UDB 实例),且在上面部署了业务。当这个 UDB 实例容量或性能达到极限时,客户可以购买一个 UDDB ,来做一个分布式数据库的解决方案。 购买并创建 UDDB 实例后,要将业务接入到 UDDB ,总共只需要做四步操作:
-
制定基于 UDDB 的数据存储和访问方案(垂直分表、水平分表、读写分离),并且确保业务的所有 SQL ,包括对事务的要求,UDDB 都能够支持。在少数情况下,业务中会有部分 SQL , UDDB 不能支持,此时可以考虑调整下业务程序的 SQL 或程序逻辑,或联系 UDDB 团队来启动迭代进行支持;
-
根据第一步制定的方案,在 UDDB 实例上,创建业务的数据库表;
-
向 UDDB 提交数据迁移命令,将数据从 UDB 迁移到 UDDB ,迁移指令示例如下:
CREATE UDB_import_task( src_UDB_id:"UDBha-k1wtyz", src_UDB_addr:"10.0.0.1:3306", src_UDB_user:"root", src_UDB_passwd:"your_udb_password", import_dbs:"import1,import2", notes:"1st create trans task in bjd" );
该命令一旦提交成功,则意味着 UDDB 内部做了两件事情:
- 修改了 UDDB 中间件节点的路由信息,后面所有访问 import1,import2 这两个库的请求,发到 UDDB 后,都将被转发到原来的UDB节点
- 开始从 UDB 迁移数据到 UDDB
-
修改业务的 MySQL 访问地址,由 UDB 切换到 UDDB 。此后,业务发往 UDDB 的请求,都将通过中间件,被路由到原来的 UDB 。
如此,UDDB 一边做数据迁移,一边将 SQL 请求路由到原来的 UDB 。一旦数据迁移完成,UDDB 将修改路由表,将 SQL 请求,路由到 UDDB ,最终完成将整个业务接入到 UDDB 的流程。
6.2 迁移进度查询
还可以通过下面命令,查询迁移进度:
其中,Status 的取值如下:
const(
UDB_IMPORT_TASK_NOTSTART = 0
UDB_IMPORT_TASK_ROUTETBL_SET = 1
UDB_IMPORT_TASK_COMPLETE = 2
UDB_IMPORT_TASK_FAILED = 3
UDB_IMPORT_TASK_CANCEL = 4
UDB_IMPORT_TASK_CANCELED = 5
)
当 Status 变为2时,意味着迁移完成,同时,业务发往 UDDB 的请求,都被路由到了UDDB 下面的 UDB 节点。此时,可以将原来的 UDB 节点下线。
6.3 撤销正在执行的迁移任务
如果对迁移任务进度或者其他能力不够满意,还可以通过以下命令,中止迁移任务。中止后,迁移任务会停止,但是中间件仍然将请求路由到原有的 UDB 节点。客户可以将业务的MySQL访问地址,修改回 UDB 节点,最终完成撤销。
DROP UDB_import_task (
src_UDB_id:"UDB-88",
import_dbs:"import1,import2"
);
结语
以 UDB 为主打产品的 UCloud 云数据库团队,成立以来,一直专注于 OLTP 场景下的结构化数据存储服务,力求精益求精,止于至善。OLTP 场景下的结构化数据存储,看似平淡无奇,不似大数据领域中,有琳琅满目的各种技术和产品,也不如机器学习领域充满未来感和想象力,但要做好,要保证每一条数据都不丢失,每一次请求都不出错,为客户的业务提供最基础的数据保障,需要扎实的研发和运维基本功,需要踏踏实实地从客户需求痛点出发,去提炼需求,研发产品,规范运营,不断优化。
延续着 UDB 产品的气质,UDDB 六大功能特性,依然是围绕 OLTP 场景下,客户在海量数据存储和处理中的痛点来打造,目标是切实有效地解决客户的问题。我们并不追求前沿的,高大上的技术,只是怀着严肃的态度,本着为客户业务负责的想法,基于目前广泛使用的中间件技术和稳定的 UDB、ULB 产品,来构建 UDDB。
总的来说,我们做了两方面的事情:
-
通过利用公有云的优势,为客户提供传统中间件缺失的功能点
比如支持 DDL ,不停服水平扩容,以及业内最好用的读写分离。
-
加入了一些微创新,来扩展中间件的功能,不断提高对业务的支持度
比如垂直分表和水平分表无缝结合,返璞归真的水平分表语法,不停服迁移 UDB 节点数据等。
最终的目标,是构建一个如同单机数据库一样对业务友好,运行稳定,而又方便管理和运维的分布式数据库,有效解决 UCloud 客户在使用单机数据库时遇到的容量和性能问题。
在后续一系列文章中,我们还将一一解析UDDB 产品设计理念、UDDB 技术内幕、UDDB 性能对比测试等,篇篇干货。
敬请期待:)
——————相关阅读推荐:
关于直播,所有的技术细节都在这里了(一)本文由『UCloud关系型存储研发团队』提供。
关于作者
Robert(@robert ),UCloud「应用云研发中心-结构化存储」研发工程师。毕业后从事数据库内核研发两年,之后混迹互联网多年,主要从事分布式后台系统的研发,目前专注在分布式数据库研发和运营工作。爱好足球、游泳,阅读,Bob Dylan和周云蓬的粉丝,锤子手机T1、T2的忠实用户。
「UCloud机构号」将独家分享云计算领域的技术洞见、行业资讯以及一切你想知道的相关讯息。
欢迎提问&求关注 o(*////▽////*)q~
以上。