写点什么

TiDB 常见问题处理 - 热点

  • 2022 年 7 月 11 日
  • 本文字数:3711 字

    阅读完需:约 12 分钟

原文来源:https://tidb.net/blog/ce3c059a

作者:李坤,PingCAP 互联网架构师,TUG Ambassador,前美团、去哪儿数据库专家。

背景

从现有的数据库使用场景来看,随着数据规模的爆发式增长,考虑采用 TiDB 这种分布式存储的业务,通常都是由于触发了单机数据库的瓶颈,我认为瓶颈分为 3 点:存储瓶颈、写入瓶颈、读取瓶颈。我们希望 TiDB 能够解决这 3 个瓶颈,而存储瓶颈是可以首先被解决的,随着机器的扩容,存储瓶颈自然就可以几乎线性的趋势扩展;而写入和读取,我们希望能够利用上多机的 CPU、内存,这样才能突破单机的读写性能瓶颈,这就需要将压力分摊到多台机器上。如果需要读写的数据,集中到了一台机器上,甚至一个 region 上,这就必然导致热点。


这里就从使用者的角度和大家分享下,处理和热点相关的问题一些思考和手段。


  1. TiDB 和 MySQL 在设计 Schema 的时候,有什么区别?

  2. 当集群性能出现问题,如何分析是否由热点导致?

  3. 热点是读热点,还是写热点?

  4. 如何确定热点是产生在哪个表上?是数据热点还是索引热点?

  5. 确定热点后,如何处理?

TiDB 和 MySQL 在设计 Schema 主键的时候,有什么区别

MySQL 的使用习惯

MySQL 上通常都建议使用 int(bigint) 类型的自增 id 作为主键,这是由于以下几个原因:


  1. 性能:随机写改为顺序写,性能更好

  2. 功能:更容易运维,比如 osc 改表,归档等

  3. 易用:方便 id 范围检索,分段 update、delete 数据

TiDB 的使用对比

使用 TiDB 的用户,有相当一部分都是从 MySQL 迁移过来的,可能就会错误的认为任何情况都可以沿用 MySQL 设计 Schema 的习惯,而没有考虑分布式数据库的特点。我们分别针对以上几点,考虑 TiDB 该如何应对。(当写入 qps 达到一定大小时考虑,比如超过 1k,否则可以沿用 MySQL 的方式)


  1. 从性能考虑,TiDB 使用自增 id 并没有带来性能的好处。我们知道 TiDB 是按照 range 的方式将数据分为一个个 region,我们希望 TiDB 的写入分散到各个 region 中把多机利用起来,而不是集中在一个 range,新数据都集中在一起,这会带来数据热点,包括读热点和写热点,所以采用自增 id 反倒影响性能。

  2. TiDB 可以在线改表,直接改元数据,不需要依靠自增 id 改表。分布式数据库,应该也不用怎么归档。

  3. TiDB 根据业务需求,或许也需要分段检索,可以通过业务的一些特征来处理,比如订单号、时间等。

  4. 在 MySQL 中,基本上单机容量关系,单表到达不了自增 ID 的瓶颈值,但是在特殊的 replace into 场景中,一旦写入了一个大值却能够到达这一瓶颈,TiDB 也是一样,这种问题是不好处理的。

SHARD_ROW_ID_BITS

TiDB 会在以下情况下采用一个隐式的自增 _tidb_rowid,作为数据的 key


  1. 对于 PK 非整数的表

  2. 对于没有 PK 的表

  3. 对于组合主键,无论是否都为 int 类型


通过设置 SHARD_ROW_ID_BITS 可以把 _tidb_rowid 自动打散,解决写入热点问题。比如设置为 4,代表 16 个分片 ,如果你的 tikv 个数在 16 个以内是足够用了。设置的过大会造成 RPC 请求数增多,增加 CPU 和网络开销。举例如下:


# 创建一个分16片的表,产生的key就是随机的了,可以分散在不同range范围的region中,最大可以同时利用16个region
CREATE TABLE t (c int) SHARD_ROW_ID_BITS = 4; insert into t values(1);insert into t values(2);insert into t values(3);insert into t values(4);insert into t values(5);
mysql> SELECT _tidb_rowid,c from t order by c;+---------------------+------+| _tidb_rowid | c |+---------------------+------+| 1 | 1 || 6917529027641081858 | 2 || 5188146770730811395 | 3 || 3458764513820540932 | 4 || 1729382256910270469 | 5 |+---------------------+------+5 rows in set (0.00 sec)
复制代码


我们来解析一下 _tidb_rowid 的真面目,转换为 2 进制就很清晰了。总共 64 位,它的前 4 位是 4bit 的随机数,也就是我们设置的 SHARD_ROW_ID_BITS = 4,将顺序值转换为随机值解决热点问题;后面 60 位是自增的值,足够用了。


[~]$ echo "obase=2;ibase=10;1"|bc_0000_00000000000000000000000000000000000000000000000000000000_001
[~]$ echo "obase=2;ibase=10;6917529027641081858"|bc_1100_00000000000000000000000000000000000000000000000000000000_010
[~]$ echo "obase=2;ibase=10;5188146770730811395"|bc_1001_00000000000000000000000000000000000000000000000000000000_011
[~]$ echo "obase=2;ibase=10;3458764513820540932"|bc_0110_00000000000000000000000000000000000000000000000000000000_100
[~]$ echo "obase=2;ibase=10;1729382256910270469"|bc_0011_00000000000000000000000000000000000000000000000000000000_101
复制代码

TiDB 使用建议

从以上几点可以看到 TiDB 并没有那么依赖自增 id,那么 TiDB 用什么作为主键呢?


  1. 如果写入 qps 很小,依然用自增 id 也是没问题的

  2. 如果业务有一个随机并唯一的 int 类型的业务 id,建议用该值作为主键,这样可以自然分散到不同 region 中;

  3. 如果业务有一个自增并唯一的 int 类型的业务 id,建议不使用主键,将该 id 设为唯一索引,使用 SHARD_ROW_ID_BITS = [4~6],分别对应最多 16~64 个 tikv。

  4. 如果业务有一个唯一的字符串类型的业务 id,同上。

当集群性能出现问题,如何分析是否由热点导致

当我们接手一个业务,或者业务在测试时反馈性能不理想,如何确定是否是热点导致的呢?

需要从几个方面观察,如果某个节点相比其他节点特别突出,说明这个节点有热点。

  1. 是否某个 tikv 节点的 raft store、apply 的 cpu 使用率偏高




  1. 是否某个 tikv 节点 grpc 消息偏多



  1. 是否某个 tikv 节点流量偏多,重点关注下面 3 个指标(prometheus 公式增加 by (instance)

  2. keys flow:按 keys 个数统计

  3. read flow:读流量

  4. write flow:写流量





  1. 直接看监控中的 hotregion 面板,这里展示的是 pd 识别到的热点

  2. 目前统计热点的标准是根据总流量信息动态算分一个阈值,pd 会将统计为热点的 region,在每个 store 上面的个数保持均衡。如果某个 store 的热点 region 集中,则表示有热点。


如何确定热点是读还是写

  1. 参考上一步 read flow、write flow,看是哪一个类型流量不均匀

  2. 可以用官方 pd_ctl 命令行工具,执行 hot store、hot read、hot write 3 种命令,可以直接看到哪个节点上是热点,读写热点的 region 分别存在哪些,来实时观察读写热点


举例一小段输出:可以看出 store 10 上有 1 个热点 region,在近 643 次都被上报为热点,该 region 分裂 231 次,很有可能是一直在 region 尾部追加数据导致的热点


  "as_peer": null,  "as_leader": {    "10": {      "total_flow_bytes": 2307649,      "regions_count": 1,      "statistics": [        {          "region_id": 7705,          "flow_bytes": 2341686,          "hot_degree": 643,          "last_update_time": "2019-08-05T18:30:15.901413761+08:00",          "AntiCount": 1,          "Version": 231,          "Stats": null        }      ]    },
复制代码

如何确定热点是哪张表,是表数据还是索引

使用 tidb 的 http 接口,根据上一步的 region_id,查看 region,可以看到是表还是索引,如果是索引是哪个索引,如下面的例子,可以看到 region 为库 db1 的表 table1 的索引 idx_column1


curl http://{tidb_ip}:10080/regions/12345{"region_id":12345,"start_key":"xxxAAAAAJLX2mAAAAAAAAAAwOAAAAAATPu8gOAAAAAAB6M4gOAAAAAANMeywOAAAAAANMezA==","end_key":"xxxxAAAAAJLX2mAAAAAAAAAAwOAAAAAATPu8gOAAAAAAB6ZaAOAAAAAAMNDgQOAAAAAAMNDgg==","frames":[{"db_name":"db1","table_name":"table1","table_id":123,"is_record":false,"index_name":"idx_column1","index_id":3,"index_values":["11111","22222","33333","44444"]}]}
复制代码


甚至可以使用 mok 工具,分析 region 的 start_key 和 end_key 对应的数据,[https://github.com/disksing/mok](),根据 start 和 end 返回的 table 和 row,分析具体是哪部分数据

确定热点后,如何处理

  1. 如果是热点 region 不是唯一的,有多个,但都集中到某一个 store 上,先确认热点调度是否可运行(如果没有调整过默认是打开的),从 pd_ctl 执行 config show all,查看 2 个地方:


  • region-schedule-limit 是否大于 0

  • hot-region 部分是否开启


如果这 2 处正常,观察 pd 下的 operator 监控,调度中是否有较多的 balance-region,而一直没有热点调度的指标,则可能是热点调度被抢占,这个问题在 2.1.14 解决,该版本加了 hot-region-schedule-limit,将热点调度从 region-schedule-limit 调度中拆分开了



  1. 如果是由于前期设计不当,导致类似自增 id 的情形,导致热点 region,目前是不能在线调整主键的,只可以去掉自增属性,需要业务配合整改 schema 设计;

  2. 如果是热点小表的问题,可以通过命令手动 split region

后续

目前分析热点问题,对于新手会有一点门槛,有时候复杂时要靠按 region、store 流量排序,才能分析出来。据了解官方在 3.0 和 4.0 的规划中,都将热点问题的排查和调度进行了优化,到时可能热点调度很可能就会变得很自动和直观,但我们了解其内部原理还是非常有好处的。


发布于: 刚刚阅读数: 2
用户头像

TiDB 社区官网:https://tidb.net/ 2021.12.15 加入

TiDB 社区干货传送门是由 TiDB 社区中布道师组委会自发组织的 TiDB 社区优质内容对外宣布的栏目,旨在加深 TiDBer 之间的交流和学习。一起构建有爱、互助、共创共建的 TiDB 社区 https://tidb.net/

评论

发布
暂无评论
TiDB 常见问题处理 - 热点_TiDB 社区干货传送门_InfoQ写作社区