本系列文章主要介绍 BaikalDB在同程艺龙的落地实践
作者简介:王勇,同程艺龙架构师,BaikalDB Column Store Contributor,专注于分布式数据库方向的研发工作
欢迎Star关注 BaikalDB (github.com/baidu/BaikalDB)
BaikalDB高可用与HTAP特性实践
我们从2019年开始调研开源NewSQL数据库BaikalDB,尝试解决工作中遇到的一些实际问题,例如OLAP业务跑在行存数据库上查询速度慢,数据库跨中心部署高可用方案待完善,在近6个月的研究与实践中,我们向社区提交了列存特性,并使用BaikalDB分别部署了基于列存的OLAP类业务,基于行存的OLTP类业务,及基于双中心的高可用部署方案,有效的解决了相关问题,在这里做一个相关使用经验的分享,希望可以给遇到类似问题的同学提供参考。
1 BaikalDB选型考虑
1.1 业界纷纷布局NewSQL
1.2 NewSQL数据库核心技术对比
- 注1: ShardingSphere基于MySQL MGR的Paxos复制协议尚未发布。
- 注2:TiDB 3.0起已同时支持乐观事务与悲观事务。
- 注3:由于笔者精力所限,尚有很多NewSQL未参与对比:Amazon Aurora,Aliyun PolarDB, AnalyticDB,Apple FoundationDB, CockroachDB, 华为GaussDB, Yandex ClickHouse等。
1.3 NewSQL技术选型
路径选择
- 纯自研:能力有限,投入有限
- 纯开源:无法及时满足定制化需求
- 云服务:安全与成本考虑,短期内核心业务自建IDC,k8s化
- 半自研:我们的选择,不重复造轮子,主体功能交由社区完成,集中有限力量满足公司需求,可供选择的NewSQL有: TiDB,BaikalDB,CockRoachDB等。
从以上几款开源DB中,最终选择BaikalDB的原因有:
- 背景相似:BaikalDB来源于百度凤巢广告业务团队,由于广告业务的增长走过了从单机到分库分表到分布式的全过程,而我们面临类似的问题。
- 经受考验:已经有百度广告平台多个业务实际使用经验,千级别集群节点,PB级数据规模,我们跟进使用,风险可控。
- 技术栈匹配:BaikalDB(c++实现, 10万行代码精炼),依赖少而精(brpc,braft,rocksdb),社区友好,部署简单,技术栈匹配。
- 特性比较完善:基本满足我们需求,我们可以专注于满足公司需求。
1.4 BaikalDB简介
BaikalDB是一款百度开源(github.com/baidu/BaikalDB )分布式关系型HTAP数据库。支持PB级结构化数据的随机实时读写。架构如下:
其中:
- BaikalStore 负责数据存储,用 region 组织,三个 Store 的 三个region形成一个 Raft group 实现三副本,多实例部署,Store实例宕机可以自动迁移 Region数据。
- BaikalMeta 负责元信息管理,包括分区,容量,权限,均衡等,Raft保障的3副本部署,Meta 宕机只影响数据无法扩容迁移,不影响数据读写。
- BaikalDB 负责前端SQL解析,查询计划生成执行,无状态全同构多实例部署,宕机实例数不超过 qps 承载极限即可。
核心特性:
- 强一致:实现Read Committed 级别的分布式事务,保证数据库的ACID 特性
- 高可用:Multi Raft协议保证数据多副本一致性,少数节点故障自愈, 支持跨机房部署,异地多活,可用性>99.99%, RTO=0, RPO<30s
- 高扩展:Share-nothing架构,存储与计算分离, 在线缩扩容不停服 5分钟内完成,动态变更schema 30s生效
- 高性能:表1000张,单表:10亿行,1千列情况下:QPS >1W 点查 P95 < 100ms
- 易用性:兼容MySQL 5.6协议
2.BaikalDB线上迁移过程
我们在线上已部署了50个存储节点百亿行数据规模的BaikalDB集群,本章将重点讲述业务迁移到BaikalDB的步骤以确保上线过程平稳与业务无缝迁移。 整体的上线过程可分为如下几个阶段:
2.1 列存特性开发
由于我们上线的首个业务是分析类业务,适合列式存储,在社区的帮助与指导下,我们开发并提交了列存特性,原理见列式存储引擎
2.2 配套运维工具
- 部署工具:线上暂以自研部署脚本为主,未来会对接公司k8s;
- 监控工具:Prometheus,BaikalDB对接Prometheus非常简单,参见监控指标导出到Prometheus
- 数据同步工具:Canal + 数据同步平台,原理类似不再展开;
- 物理备份工具:SSTBackup,使用说明见基于SST的备份与恢复 ,可以实现全量+增量的物理备份,内部数据格式,仅适用于BaikalDB;
- 逻辑备份工具:BaikalDumper,模拟MySQLDumper,导出的是SQL语句,可以导入到其他数据库,目前只支持全量导出;
- 测试工具:Sysbench 使用说明见BaikalDB Sysbench
- 欠缺的工具:基于MySQL binlog的数据订阅工具,开发中。
2.3 数据迁移
数据同步采用全量+增量同步方式,全量采用批量插入,增量原理类似于MySQL binlog订阅,采用Replace模式同步。共计80亿条数据全量同步约3天,增量同步稳定在10ms以内。
2.4 业务测试
经过数据同步环节,BaikalDB已经具备与线上等价数据集,业务测试阶段重点在于对待上线系统真实SQL的支持能力,我们采用全流量回放的方式进行,如下图:
通过实时回放线上真实流量我们主要验证了:
- 业务使用SQL的100%兼容性
- 业务峰值的性能承载能力
- 7*24小时的稳定性
2.5 业务上线
经过以上环节,业务上线需要的唯一操作就是更改数据库连接配置。
2.6 运维与监控
下图是上线后部分指标Prometheus监控截图,可以看到从qps,响应时间,环比变化看均十分平稳。
2.7 注意事项
BaikalDB尚在完善的功能:
- 子查询:开发中
- information_schema:图形化工具支持与系统变量支持不全,开发中
- 行列并存:一张表可选择行存或列存,但不能并存,开发中
- 分布式时钟:影响事务隔离级别,与Follower一致性读,开发中
- 视图:排期中
- 触发器,存储过程等传统关系型数据库功能,暂无规划
使用BaikalDB需要注意的事项:
数据建模:DB并不能取代数据库使用者的角色,好的数据模型,表结构的设计,主键与索引的使用,SQL语句,在我们实际测试中比坏的用法会有10倍以上的提升;
写放大:与RocksDB层数有关,建议单store数据大小控制在1T以内;
参数调优:默认配置已非常合理,仅有少量参数建议根据实际情况修改:
export TCMALLOC_SAMPLE_PARAMETER=524288 #512k export TCMALLOC_MAX_TOTAL_THREAD_CACHE_BYTES=209715200 #200M,当大value时,调大可以避免线程竞争,提高性能 -bthread_concurrency=200 # 建议(cpu核数- 4)* 10 -low_query_timeout_s=60 # 慢查询阈值,建议根据需求设置 -peer_balance_by_ip=false # 默认为false,单机多实例时建议开启 -max_background_jobs = 24 #建议(cpu核数- 4) -cache_size = 64M #建议不超过单实例内存空间40%
大事务限制:行锁限制:per_txn_max_num_locks 默认100w;DB与Store RPC 包的大小限制:max_body_size默认256M;消息体限制:max protobuf size =2G,此为protobuf限制不可修改。一般建议事务影响行数不超过10M;
双机房资源预留Buffer:资源使用率建议40%左右,容灾时单机房需要承载double的压力,分配副本时disk_used_percent 超过80%的store将被剔除。
3 高可用与HTAP部署方案
我们的BaikalDB一套集群部署在两个城市4个IDC机房,同时支撑基于行存的OLTP业务与基于列存的OLAP业务,本章将说明我们是如何通过设计部署方案来发挥BaikalDB高可用与HTAP能力的。
双中心HTAP部署示意图
- 注1:图中省去了meta节点部署
- 注2:每个IDC会实际部署了多个store与db节点。
如上图城市 A与城市 B双中心采用完全对称的部署结构,在BaikalDB里Region是存储的基本单位,每张表至少有一个Region组成,每个Region有若干个peer合在一起构成了一组Raft Group。每个store即可创建行存表也可以创建列存表,业务建表时可以根据场景的不同进行选择,另外业务也可以根据地域的不同选择副本的分布,例如城市 A的业务可以选择城市 A 2副本+城市 B 1副本,城市 B的业务可以选择城市 A 1副本+城市 B 2副本。
假设有2家业务在使用BaikalDB集群,业务A是一个部署在城市 A的OLAP类业务创建了表ctable用Region1代表。业务B是一个部署在城市 B的OLTP类业务创建了表rtable用Region2代表。则相关的配置过程如下:
初始化meta机房信息
echo -e "场景:添加逻辑机房bj sz\n" curl -d '{ "op_type": "OP_ADD_LOGICAL", "logical_rooms": { "logical_rooms" : ["bj", "sz"] } }' http://$1/MetaService/meta_manager
echo -e "插入bj物理机房\n" curl -d '{ "op_type": "OP_ADD_PHYSICAL", "physical_rooms": { "logical_room" : "bj", "physical_rooms" : ["bj1","bj2"] } }' http://$1/MetaService/meta_manager echo -e "\n"
echo -e "插入sz物理机房\n" curl -d '{ "op_type": "OP_ADD_PHYSICAL", "physical_rooms": { "logical_room" : "sz", "physical_rooms" : ["sz1","sz2"] } }' http://$1/MetaService/meta_manager
设置每个baikalstore的物理机房信息
#IDC1 store的机房信息配置 #vim store/conf/gflag -default_physical_room=bj1
设置每个baikaldb的物理机房信息
#IDC1 db的机房信息配置 #vim db/conf/gflag -default_physical_room=bj1
创建表时根据需要指定副本策略与存储类型
--业务A是一家部署在城市 A的OLAP类型请求采用列存表,建表语句如下: CREATE TABLE
TestDB
.ctable
(N_NATIONKEY
INTEGER NOT NULL,N_NAME
CHAR(25) NOT NULL,N_REGIONKEY
INTEGER NOT NULL,N_COMMENT
VARCHAR(152), PRIMARY KEY (N_NATIONKEY
))ENGINE=Rocksdb_cstore COMMENT='{"comment":"这是一张列存表", "resource_tag":"bizA", "namespace":"TEST_NAMESPACE","dists": [ {"logical_room":"bj", "count":2}, {"logical_room":"sz", "count":1}] }';--业务B是一家部署在城市 B的OLTP类型请求采用行存表,建表语句如下: CREATE TABLE
TestDB
.rtable
(N_NATIONKEY
INTEGER NOT NULL,N_NAME
CHAR(25) NOT NULL,N_REGIONKEY
INTEGER NOT NULL,N_COMMENT
VARCHAR(152), PRIMARY KEY (N_NATIONKEY
))ENGINE=Rocksdb COMMENT='{"comment":"这是一张行存表", "resource_tag":"bizB", "namespace":"TEST_NAMESPACE","dists": [ {"logical_room":"bj", "count":1}, {"logical_room":"sz", "count":2}] }';
优点:
- 容灾能力:任何少数节点故障,无论是机器级,机房级还是城市级故障,均可做到RPO(数据丢失时长) = 0s,RTO(数据恢复时长)< 30s。
- Async Write:由于Raft多数Peer写成功即可返回,虽然3peer会有1个peer分布在另一个城市存在延迟,但写操作一般写完同城的2个peer即可,异地的peer在进行异步写,写性能接近同城写性能。
- Follower Read:由于每个城市至少有一个副本,对于读业务需要分别部署在两个城市的场景,BaikalDB提供了就近读功能,路由选择时会优先选择与DB同在一个逻辑机房的Region进行读操作,所以读性能可以在两地均得到保证。
- HTAP能力:业务可以根据业务场景分别选择行存表与列存表,每个store可以同时支持这两种表。
- 资源隔离:如果担心HTAP的业务workload会互相影响,业务可以通过resource_tag字段对store进行分组,例如store1的resource_tag = bizA, 那么store1只会给建表时指定resource_tag = bizA的表分配Region。
待完善:
- 容灾能力: 多数节点故障RPO最大可到3s,BaikalDB副本的分配策略后续可以增加降级策略,如果多数机房故障,降级到在少数机房分配副本,从而保证RPO依旧为0。
- Async Write:当写发生在少数城市时依旧会存在延迟,但这种情况实际业务很少发生;如确有需要例如两地业务均需对同一张表发生写操作,必然有一个处于异地写状态,这种情况建议写时拆成两张表,读时用Union或视图。
- Follower Read:可以增强为Follower一致性读。就是对Follower可能落后与Leader的请求进行补偿,需要分布式时钟特性(开发中)的支持。