百亿级别的用户关系表,mysql由于大v问题导致数据倾斜严重怎么处理?

百亿级别的用户关系表,mysql由于大v问题导致数据倾斜严重怎么处理?

一、 问题分析:为什么 MySQL 扛不住?

首先,我们要清晰地认识到问题的根源:

  1. 百亿级别的存储瓶颈
    • 单张 MySQL 表存储百亿行数据,即使做了分库分表,单个分片的数据量依然巨大,B+ 树的层级会非常深,导致查询性能急剧下降。
    • 索引本身也会占用巨大的磁盘空间,甚至可能比数据本身还大。
  2. 数据倾斜(大 V 问题)- 致命伤
    • 粉丝列表(我关注了谁):这个查询相对均匀,因为一个普通用户关注的人数有限(比如几百到几千)。
    • 关注者列表(谁关注了我):这是问题的核心。一个明星“大 V”可能有数千万甚至上亿的粉丝。当查询这个大 V 的关注者列表时,所有请求都打到存储这个大 V 数据的那个数据库分片上,形成一个巨大的**热点 (Hotspot)**。
    • 这个热点分片会因为 CPU、I/O、网络带宽被占满而变得极度缓慢,甚至崩溃,进而影响到存储在该分片上的其他普通用户。
  3. 写入风暴(关注/取关操作)
    • 当大 V 发布一条动态时,可能会在短时间内引发大量的“关注”操作,形成写入热点。
  4. 关系复杂性
    • 粉丝关系不仅仅是 A 关注 B,还涉及到“共同关注”、“好友关系(互相关注)”等复杂查询,这对关系型数据库的 JOIN 操作是巨大考验。

二、 解决方案:分层架构与综合治理

单一技术无法解决这个问题,必须采用组合拳。核心思路是:分类治理,读写分离,冷热分离

1. 基础存储层:分库分表是前提

百亿数据,分库分表是必须的,但要精细化设计。

  • 表结构设计

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    -- 关注关系表 (Follows Table)
    CREATE TABLE `follows` (
    `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
    `user_id` bigint(20) unsigned NOT NULL COMMENT '粉丝的用户ID',
    `follower_id` bigint(20) unsigned NOT NULL COMMENT '被关注者的用户ID (大V的ID)',
    `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    -- 关键索引:满足两种核心查询
    UNIQUE KEY `uk_user_follower` (`user_id`, `follower_id`),
    KEY `idx_follower_created` (`follower_id`, `created_at`) -- 查询“谁关注了我”,并按时间排序
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  • 分片键 (Shard Key) 的选择

    • 查询“我关注了谁”:使用 user_id 作为分片键。SELECT follower_id FROM follows WHERE user_id = ?; 这样查询可以精确路由到单个分片,性能很好。
    • 查询“谁关注了我”:使用 follower_id 作为分片键。SELECT user_id FROM follows WHERE follower_id = ?;
    • 矛盾出现:一个表只能有一个分片键。如果用 user_id 分片,查询大 V 的粉丝列表就会变成一个广播查询(需要查询所有分片),性能灾难。如果用 follower_id 分片,则查询“我关注了谁”又会变成广播查询。
  • 解决方案:数据冗余/索引表

    • 主表: follows 表,使用 user_id 作为分片键,用于存储“关注”关系。
    • 索引表: followers 表,结构与 follows 几乎一样,但是使用 follower_id 作为分片键,用于存储“被关注”关系。
    • 写入操作: 当用户 A 关注用户 B 时,需要同时写入 follows 表和 followers 表。这是一种“写扩散”的策略,用写入成本换取查询性能。
    • 事务保证: 这两次写入需要保证最终一致性,可以通过分布式事务或更常用的可靠消息队列来实现。

2. 核心解决方案:大 V 数据识别与隔离 (冷热分离)

即使分了表,大 V 的粉丝列表依然可能撑爆一个分片。因此,必须对大 V 进行特殊处理。

  • 步骤一:识别大 V
    • 离线计算:通过定时任务(如 Spark/Hive)统计每个用户的粉丝数。
    • 定义一个阈值,例如粉丝数 > 1000 万的,标记为“大 V”。将大 V 的列表维护在一个配置中心或者 Redis 中。
  • 步骤二:读路径隔离 - 缓存优先
    • 普通用户:
      1. 查询“谁关注了我”时,请求路由到 followers 表的对应分片。
      2. 可以在数据库上层加一层缓存(如 Redis),缓存用户最近的 N 个粉丝。
    • 大 V 用户(核心):
      1. 禁止直接查库! 查询大 V 粉丝的请求,绝对不能穿透到后端的 MySQL
      2. 数据流向:
        • 当发生关注事件时,除了写入 MySQL,同时将这条关注关系发送到**消息队列 (Kafka)**。
        • 有一个或多个离线/实时处理服务消费 Kafka 的数据。
        • 将大 V 的全量粉丝列表存储在更适合大规模读取的系统中,如 Redis 的 Set/ZSetHBase、或者**搜索引擎 (Elasticsearch)**。
      3. 查询路径:
        • 当用户请求大 V 的第一页粉丝列表时,直接从 Redis/HBase/ES 中查询。
        • 为什么这些系统可以?
          • Redis: 内存数据库,速度极快。但存储全量粉丝成本高,适合存储活跃粉丝或最新粉丝。
          • HBase (推荐): 分布式 NoSQL 数据库,天然支持海量行存储和范围扫描,没有单点瓶颈,非常适合存储大 V 的全量粉丝列表。RowKey 可以设计为 bigV_id + reverse_timestamp 来支持分页查询。
          • Elasticsearch: 不仅能存,还能支持复杂的粉丝搜索(如按昵称、地域搜索)。
  • 步骤三:写路径优化
    • 当大 V 被关注时,写入操作 (user_id, bigV_id) 会集中在 followers 表的某个分片上。
    • 写操作合并/异步化:
      • 将关注请求先写入**消息队列 (Kafka)**。
      • 由后端消费者批量地、异步地写入数据库。这样可以削峰填谷,将瞬时的高并发写入压力平摊开。
      • 前端可以给用户一个“操作成功”的即时反馈,后台慢慢处理即可。

三、 整体架构图

四、 总结方案

  1. 基础存储层 (MySQL)
    • 采用分库分表是基本盘。
    • 使用“关注表”(按 user_id 分片)和“粉丝表”(按 follower_id 分片)两份数据,通过写扩散解决不同维度的查询问题。
  2. 缓存层 (Redis)
    • 为普通用户缓存粉丝列表、关注列表,提高性能。
    • 为大 V 缓存最新或最活跃的一部分粉丝(例如,首页展示的粉丝)。
  3. 大 V 粉丝存储 (HBase/Elasticsearch)
    • 识别大 V: 离线计算粉丝数,定义阈值。
    • 存储隔离: 将大 V 的全量粉丝列表从 MySQL 中剥离出来,存储到更适合海量数据扫描的 NoSQL 数据库中(HBase 是一个非常好的选择)。
    • 数据同步: 通过消息队列(Kafka)实时同步关注/取关事件,更新 HBase 中的数据。
  4. 服务层
    • 服务层需要有逻辑判断:根据请求的用户 ID,判断其是否为大 V。
    • 如果是普通用户,查询缓存 + MySQL
    • 如果是大 V,查询缓存 + HBase/ES
  5. 写入优化
    • 引入**消息队列 (Kafka)**,将对大 V 的关注/取关操作异步化处理,削峰填谷,保护数据库。

这个方案的核心是“分而治之”“对症下药”。通过识别出导致问题的“大 V”,并为他们设计一套独立的、更强大的存储和查询链路,从而将他们与普通用户隔离开,保证整个系统的稳定和高性能。


百亿级别的用户关系表,mysql由于大v问题导致数据倾斜严重怎么处理?
https://blog.longpi1.com/2025/09/19/百亿级别的用户关系表,mysql由于大v问题导致数据倾斜严重怎么处理?/