案例背景
某原有系统为虚拟机环境部署,整体性能不满足预期。为提升集群整体性能,计划分阶段采购物理机,并以扩缩容的方式逐渐把物理机添加到现有集群中,逐渐淘汰虚拟机节点。
原始部署拓扑中,每个虚拟机节点只部署一个 TiKV 实例,且未设置标签。扩容物理机时,为了简便起见,每个物理机暂时也只部署了一个 TiKV 实例,同样未设置任何标签。
集群当前处于物理机与虚拟机混合部署的中间态,由于下一批次物理机将在数月后就绪,这意味着物理机与虚拟机混合架构将持续几个月时间。在这个阶段,为了能够充分发挥物理机性能,期望能有一个方案将重要业务数据的 leader 副本尽量分布在物理机上。
解决思路
首先需要能够识别出哪些实例在物理机哪些在虚拟机,通过 TiDB 在线打标签的方式,将物理机和虚拟机上的实例划分到 2 个标签组。然后,结合 Placements Rules 功能将重要业务数据的 leader 副本固定在物理机所属的组。以下通过实际测试环境验证此方法的有效性,供大家参考。
测试步骤
环境准备
首先,需要准备一个 TiDB 测试环境,且当前所有节点上均未设置标签(本示例中使用 3 节点混合部署的 TiDB 集群)。使用 config show 命令查看集群当前的 label 情况,确认没有打任何标签。
tiup ctl-ee:v7.1.1-3 pd -u http://xx.xx.x.151:12379 -i
Starting component `ctl-ee`: /home/tidb/.tiup/components/ctl-ee/v7.1.1-3/ctl pd -u http://xx.xx.x.151:12379 -i
» config show
{
"replication": {
"enable-placement-rules": "true",
"enable-placement-rules-cache": "false",
"isolation-level": "",
"location-labels": "",
"max-replicas": 3,
"strictly-match-label": "false"
},
从 Grafana 中查看 Region 及 Leader 的分布情况,可以发现 3 个节点上的 leader 和 region 副本个数完全均衡。由于此时所有节点均未设置标签且未设置任何 Placement Rules 策略,根据 PD 自动调度均衡的原则,符合预期。
在线打标签
如果使用 TiUP 部署集群,可以在初始化配置文件中统一进行 location 相关配置。然而,如果是一个生产运行中的集群,我们需要通过在线的方式添加标签从而不影响业务的正常运行。
在不考虑 TiFlash 组件的前提下,在线打标签需要同时配置 PD 的 location-labels
和 TiKV 的 labels
参数。PD 的 location-labels
是一个字符串数组,该配置的每一项与 TiKV labels
的 key 是对应的,而且其中每个 key 的顺序代表不同标签的级别关系。
PD 配置 location-labels
在线给 PD 配置 location-labels 需要使用 pd-ctl 工具进行更改,使用 config set location-labels 命令。下面示例输出中显示设置的 location-lables 为 region,zone,host。
[tidb@host-xx-xx-x-151 ~]$ tiup ctl-ee:v7.1.1-3 pd -u http://xx.xx.x.151:12379 -i
» config set location-labels region,zone,host
Success!
» config show
{
"replication": {
"enable-placement-rules": "true",
"enable-placement-rules-cache": "false",
"isolation-level": "",
"location-labels": "region,zone,host",
"max-replicas": 3,
"strictly-match-label": "false"
},
...
TiKV 配置 labels
配置好 PD 的 location-labels 之后,我们还需要给每个 TiKV 打上标签,这仍然通过 pd-ctl 在线更改,使用 store label 命令。由于 store label 命令需要指定具体的 store id,因此我们首先需要执行 store 命令来查看并确定每个 store id 与具体物理节点的映射关系,也可以直接从 information_schema.tikv_store_status 表中获取。
» store
{
"count": 3,
"stores": [
{
"store": {
"id": 2,
"address": "xx.xx.x.152:30160",
...
},
...
{
"store": {
"id": 3,
"address": "xx.xx.x.151:30160",
...
},
...
{
"store": {
"id": 1,
"address": "xx.xx.x.153:30160",
...
},
从上面输出中我们已经能够找到节点 IP 与 store id 的映射关系,假设我们知道这 3 个节点的机器类型,那么便可以画出以下表格。
IP |
STORE ID |
机器类型 |
xx.xx.x.151 |
3 |
物理机 |
xx.xx.x.152 |
2 |
虚拟机 |
xx.xx.x.153 |
1 |
虚拟机 |
下一步,我们便可以基于上述的 store id 来给物理机和虚拟机上的实例添加不同的标签从而分成不同的组。打完标签后,再次使用 store 命令查看每个 store,发现 store id 为 3 的被打上了 physical 标签而 store id 为 2 和 3 的被打上了 virtual 标签。
» store label 1 region=virtual
pd/api/v1/store/1/label
Success!
» store label 2 region=virtual
pd/api/v1/store/2/label
Success!
» store label 3 region=physical
pd/api/v1/store/3/label
Success!
» store
{
"count": 3,
"stores": [
{
"store": {
"id": 3,
"address": "xx.xx.x.151:30160",
"labels": [
{
"key": "region",
"value": "physical"
}
],
...
{
"store": {
"id": 1,
"address": "xx.xx.x.153:30160",
"labels": [
{
"key": "region",
"value": "virtual"
}
],
...
{
"store": {
"id": 2,
"address": "xx.xx.x.152:30160",
"labels": [
{
"key": "region",
"value": "virtual"
}
],
...
配置 Placement Rules
节点打了标签之后,我们便可以配置 Placement Rules 来规划副本的放置策略。TiDB v5.3 版本中引入了 Placement Rules in SQL,可以采用 SQL 的方式更方便的配置数据的副本策略,本文我们使用这种方式来进行配置。
首先,我们可以使用 SQL 命令 show placement labels 来查看当前集群中有哪些 labels。
mysql> show placement labels;
+--------+-------------------------+
| Key | Values |
+--------+-------------------------+
| region | ["physical", "virtual"] |
+--------+-------------------------+
创建放置策略 (placement policy)
上述输出证明之前在线打标签操作的正确性。在此基础上,我们便可以通过 create placement policy 的方式创建放置策略。下述示例意味着我们将创建一个放置策略 mypolicy,并指定 leader 放置在标签为 physical 的实例上,followers 被放置在标签为 physical 和 virtual 的实例上。
mysql> create placement policy mypolicy primary_region="physical" regions="physical,virtual,virtual" ;
Query OK, 0 rows affected (0.54 sec)
mysql> show create placement policy mypolicy;
+----------+-------------------------------------------------------------------------------------------------+
| Policy | Create Policy |
+----------+-------------------------------------------------------------------------------------------------+
| mypolicy | CREATE PLACEMENT POLICY `mypolicy` PRIMARY_REGION="physical" REGIONS="physical,virtual,virtual" |
+----------+-------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> select * from information_schema.placement_policies;
+-----------+--------------+-------------+----------------+--------------------------+-------------+--------------------+----------------------+---------------------+----------+-----------+----------+
| POLICY_ID | CATALOG_NAME | POLICY_NAME | PRIMARY_REGION | REGIONS | CONSTRAINTS | LEADER_CONSTRAINTS | FOLLOWER_CONSTRAINTS | LEARNER_CONSTRAINTS | SCHEDULE | FOLLOWERS | LEARNERS |
+-----------+--------------+-------------+----------------+--------------------------+-------------+--------------------+----------------------+---------------------+----------+-----------+----------+
| 3 | def | mypolicy | physical | physical,virtual,virtual | | | | | | 2 | 0 |
+-----------+--------------+-------------+----------------+--------------------------+-------------+--------------------+----------------------+---------------------+----------+-----------+----------+
1 row in set (0.00 sec)
绑定放置策略
创建了 placement policy 之后,需要告诉数据库指定哪些数据绑定这个放置策略。placement policy 对应的数据范围可以基于集群级、数据库级、表级和分区级进行配置。不同的级别使用不同的 SQL 命令,如下表格所示。
级别 |
SQL 命令 |
描述 |
集群 |
alter range [global|meta] placement policy xx |
为集群配置全局放置策略,v7.5 版本开始支持 |
数据库 |
alter database xx placement policy xx |
为指定的 Database 配置放置策略 |
表 |
alter table xx placement policy xx |
为指定的 Table 配置放置策略 |
分区 |
alter table xx partition xx placement policy xx |
为表中不同的 Row 创建分区,并单独对分区配置放置策略 |
假设此处我们期望对集群内某个数据库级别绑定以上放置策略,应该使用命令 alter database placement policy 实现。绑定完成后可以使用 show create database 查看到 database 已经被添加的策略,也可以通过 show placement 来查看策略的调度进度,SCHEDULED 表示 PD 调度完成。
mysql> alter database test_dbsuat placement policy=mypolicy;
Query OK, 0 rows affected (0.53 sec)
mysql> show create database test_dbsuat;
+-------------+------------------------------------------------------------------------------------------------------------------------+
| Database | Create Database |
+-------------+------------------------------------------------------------------------------------------------------------------------+
| test_dbsuat | CREATE DATABASE `test_dbsuat` /*!40100 DEFAULT CHARACTER SET utf8mb4 */ /*T![placement] PLACEMENT POLICY=`mypolicy` */ |
+-------------+------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)
mysql> show placement;
+-------------------------------------------------------------+--------------------------------------------------------------+------------------+
| Target | Placement | Scheduling_State |
+-------------------------------------------------------------+--------------------------------------------------------------+------------------+
| POLICY mypolicy | PRIMARY_REGION="physical" REGIONS="physical,virtual,virtual" | NULL |
| DATABASE test_dbsuat | PRIMARY_REGION="physical" REGIONS="physical,virtual,virtual" | SCHEDULED |
+-------------------------------------------------------------+--------------------------------------------------------------+------------------+
2 rows in set (0.06 sec)
需要注意的是,alter database placement policy 是修改数据库默认的放置策略,它只对之后新建的表生效,对于已有的表需要逐个使用命令 alter table placement policy 使之生效。因此,如果想对此数据库下属所有表应用放置策略,我们需要执行以下步骤来完成。
mysql> SELECT * FROM information_schema.tables WHERE tidb_placement_policy_name IS NOT NULL;
Empty set (0.04 sec)
use test_dbsuat;
alter table table1 placement policy=mypolicy;
alter table table2 placement policy=mypolicy;
alter table table3 placement policy=mypolicy;
...
//验证有多少个表绑定了放置策略
mysql> SELECT count(*) FROM information_schema.tables WHERE tidb_placement_policy_name IS NOT NULL;
+----------+
| count(*) |
+----------+
| 40 |
+----------+
1 row in set (0.04 sec)
//查看哪些对象配置了放置策略及状态
mysql> show placement;
+--------------------------+--------------------------------------------------------------+------------------+
| Target | Placement | Scheduling_State |
+--------------------------+--------------------------------------------------------------+------------------+
| POLICY mypolicy | PRIMARY_REGION="physical" REGIONS="physical,virtual,virtual" | NULL |
| DATABASE test_dbsuat | PRIMARY_REGION="physical" REGIONS="physical,virtual,virtual" | SCHEDULED |
| TABLE test_dbsuat.table1 | PRIMARY_REGION="physical" REGIONS="physical,virtual,virtual" | SCHEDULED |
| TABLE test_dbsuat.table2 | PRIMARY_REGION="physical" REGIONS="physical,virtual,virtual" | SCHEDULED |
| TABLE test_dbsuat.table3 | PRIMARY_REGION="physical" REGIONS="physical,virtual,virtual" | SCHEDULED |
...
上述步骤完成后,我们可以通过 SQL 语句检查此数据库下面表的 leader 及所有副本分布情况。
select distinct t2.db_name,t2.table_name,t2.region_id,t3.peer_id,t3.is_leader,t1.address,replace(replace(t1.label,', "value"',''),'"key": ','') as label
from information_schema.tikv_store_status t1,information_schema.tikv_region_status t2,information_schema.tikv_region_peers t3
where t2.db_name='dbname' and t2.region_id=t3.region_id and t3.store_id=t1.store_id order by 1,2,3,4;
如若输出结果显示所有的 leader 均在标签为 physical 的 Region,说明 leader 副本已经按照我们配置的策略完成了调度。以下是一个被正确调度后的输出示例。
+-------------+------------+-----------+----------+-----------+-------------------+--------------------------+
| db_name | table_name | region_id | peer_id | is_leader | address | label |
+-------------+------------+-----------+----------+-----------+-------------------+--------------------------+
| test_dbsuat | table1 | 57954143 | 57954144 | 1 | xx.xx.x.151:30160 | [{"region": "physical"}] |
| test_dbsuat | table1 | 57954143 | 57954145 | 0 | xx.xx.x.153:30160 | [{"region": "virtual"}] |
| test_dbsuat | table1 | 57954143 | 57954146 | 0 | xx.xx.x.152:30160 | [{"region": "virtual"}] |
| test_dbsuat | table2 | 57954499 | 57954500 | 0 | xx.xx.x.152:30160 | [{"region": "virtual"}] |
| test_dbsuat | table2 | 57954499 | 57954501 | 1 | xx.xx.x.151:30160 | [{"region": "physical"}] |
| test_dbsuat | table2 | 57954499 | 57954502 | 0 | xx.xx.x.153:30160 | [{"region": "virtual"}] |
| test_dbsuat | table3 | 57954507 | 57954508 | 0 | xx.xx.x.152:30160 | [{"region": "virtual"}] |
| test_dbsuat | table3 | 57954507 | 57954509 | 1 | xx.xx.x.151:30160 | [{"region": "physical"}] |
| test_dbsuat | table3 | 57954507 | 57954510 | 0 | xx.xx.x.153:30160 | [{"region": "virtual"}] |
...
再次从 Grafana 中查看 Region 及 Leader 的分布情况,此时发现其中一个节点 (标签为 physical) 的 leader 数量明显高于另外两个节点 (标签为 virtual)。
通过上述步骤,我们成功的实现了 test_dbsuat 数据库下面所有表的 leader 固定在物理机节点、follower 分散在所有节点上面的要求。
在集群拓扑配置中持久化标签
使用 pd-ctl 在线配置的标签虽然会持久化,但是却并不会同步到集群拓扑配置当中。当我们使用 tiup cluster show-config 查看时并不会显示这些标签,容易造成不一致的错觉。
要解决此问题,我们可以使用 tiup cluster edit-config 在拓扑配置文件中将标签维护到配置文件中,并使用 tiup cluster reload xx --skip-restart 将配置重载。这样无论之后集群怎么重启,始终可以保持一致性。
...
server_configs:
pd:
replication.location-labels: ["region", "zone", "host"]
...
tikv_servers:
- host: xx.xx.x.151
...
config:
server.labels:
region: physical
...
- host: xx.xx.x.152
...
config:
server.labels:
region: virtual
...
- host: xx.xx.x.153
...
config:
server.labels:
region: virtual
...
//reload 集群,--skip-restart 表示不重启
tiup cluster reload tidb-ee -R pd,tikv --skip-restart
Reload 也将拓扑配置文件中的标签持久化到 PD 和 TiKV 各自的配置文件中,我们可以进一步查看相应的配置文件以确保 reload 生效。
[tidb@host-xx-xx-x-151 conf]$ cat tikv.toml
...
[server]
[server.labels]
region = "physical"
...
[tidb@host-xx-xx-x-151 conf]$ cat pd.toml
...
[replication]
location-labels = ["region", "zone", "host"]
...
总结
在线打标签及配置 Placement Rules 是 TiDB 数据库中在不影响生产运行的同时实现数据副本按需调度的一种有效手段。本文结合一个实际应用场景,通过测试环境模拟的方式验证方案的有效性。为了突出重点内容,本文未考虑有 TiFlash 副本的情况,也没有考虑配置 PD 的 isolation-level。TiFlash 添加标签的步骤与 TiKV 几乎相同,isolation-level 主要用于节点上部署多个 TiKV 实例的场景,两者在官网文档 "通过拓扑 label 进行副本调度" 一文中均有描述,本文不再赘述。