图解Mycat所有分片算法+每种分片最佳场景适配(案例篇一)

171 阅读9分钟

肖哥弹架构 跟大家“弹弹” mycat设计与实战应用,需要代码关注

欢迎 点赞,关注,转发。

关注公号Solomon肖哥弹架构获取更多精彩内容

当单表数据突破5000万行,查询延迟从毫秒级飙升至秒级;当大促流量如洪水般涌来,数据库CPU持续100%告警;当凌晨3点扩容命令敲下,却要面对8小时停机和50%数据迁移的噩梦——这就是每个技术人终将直面的数据围城

在数据爆炸式增长的时代,传统数据库架构已无法承载现代业务的洪流。分片(Sharding)  作为分布式数据库的核心支柱,成为突破单机瓶颈的唯一路径。但面对十数种分片方案,开发者们常深陷选择困境:

  • ❌ 取模分片扩容难?
  • ❌ 范围分片数据倾斜?
  • ❌ 跨分片查询如龟速?
  • ❌ 业务耦合难解耦?

本文将深入MyCat分片引擎的核心理念,通过:
1️⃣ 六大分片方案原理图解——从一致性哈希到动态扩容
2️⃣ 金融级生产案例——电商平台如何抗住亿级订单
3️⃣ 避坑指南——血泪教训淬炼的黄金法则
4️⃣ 决策树模型——三步锁定最优分片策略

带您掌握既能承载海量数据洪流,又能优雅应对业务变迁的分布式架构艺术。无论您是正被性能问题困扰的开发者,还是规划新系统的架构师,这里都有您需要的答案。接下来,让我们拆解MyCat分片这把数据世界的万能钥匙。

1、基础分片算法(直接路由)

类型算法类名 (MyCat 1.x)MyCat 2.x 配置名原理描述适用场景
取模分片PartitionByModmod_hash对分片键取模:slot = id % N数据均匀分布(用户ID、订单ID)
范围分片PartitionByRangerange按区间分配:[0-100万)→分片1时序数据(日期、金额范围)
枚举分片PartitionByFileMapsharding-by-mapfile配置映射文件:北京→分片1地理分区、租户隔离
固定哈希PartitionByLongfixed_hash预设哈希槽位需固定分片数量的场景

1.1 取模分片原理

场景案例:用户分库

<!-- schema.xml -->
<table name="user" dataNode="dn1,dn2,dn3,dn4" rule="mod_rule" />

<!-- rule.xml -->
<tableRule name="mod_rule">
    <rule>
        <columns>user_id</columns>
        <algorithm>mod_hash</algorithm>
    </rule>
</tableRule>

<function name="mod_hash" class="io.mycat.route.function.PartitionByMod">
    <property name="count">4</property> <!-- 分片数量 -->
</function>

业务SQL示例

-- 自动路由到分片1(10000002 % 4 = 2)
INSERT INTO user (user_id, name) VALUES (10000002, '张三');

-- 自动查询分片3(10000003 % 4 = 3)
SELECT * FROM user WHERE user_id = 10000003;
  • ✅ 优势
    • 数据分布绝对均匀(偏差<3%)
    • 路由计算O(1)时间复杂度
    • 配置简单仅需定义分片数
  • ❌ 弊端
    • 扩容需迁移50%以上数据
    • 不支持高效的范围查询
    • 分片键值变更导致路由错误
  • ⛔ 避坑要点
    1. **分片数必须为2^N**:避免扩容时数据迁移量过大(如从4→8仅迁移25%)  
    2. **禁止更新分片键**:若user_id更新,数据将"消失"在旧分片  
    3. **避免热点键**:如特殊user_id=0全部分片0,需过滤异常值  
    

适用场景举例

  • 场景1:千万级用户系统
    • 用户表按user_id % 1024分片
    • 每个分片存储约10万用户数据
    • SQL示例
      -- 用户注册(自动路由到分片512)
      INSERT INTO users (user_id, name) VALUES (1000512, '张三');
      
      -- 查询用户(精确路由到分片512)
      SELECT * FROM users WHERE user_id = 1000512;
      
  • 场景2:电商订单系统
    • 订单表按order_id % 64分片
    • 大促期间分散写入压力
    • 优势:新订单均匀分布到不同物理节点
  • 场景3:社交关注关系
    • 关注表按follower_id % 512分片
    • 避免明星用户(如ID=1)所在分片成为热点

1.2 范围分片原理

场景案例:订单金额分级存储

<!-- rule.xml -->
<tableRule name="range_rule">
    <rule>
        <columns>order_amount</columns>
        <algorithm>range_func</algorithm>
    </rule>
</tableRule>

<function name="range_func" class="io.mycat.route.function.PartitionByRange">
    <property name="mapFile">partition-range.txt</property>
</function>

partition-range.txt内容

0-1000=0
1001-5000=1
5001-20000=2
20001-10000000=3

业务SQL示例

-- 路由到分片0(金额999在0-1000范围)
INSERT INTO orders (order_id, amount) VALUES (1001, 999);

-- 路由到分片3(金额25000在20001+范围)
SELECT * FROM orders WHERE amount > 20000;
  • ✅ 优势
    • 高效范围查询:WHERE amount BETWEEN 1500-3000
    • 天然支持冷热分离:旧数据归档到廉价存储
    • 扩容只需添加新分片范围
  • ❌ 弊端
    • 数据倾斜风险:90%订单集中在1000元以下
    • 热点分片:大促期间新分片写入瓶颈
    • 边界值管理复杂
  • ⛔ 避坑要点
    1. **动态调整分片边界**:基于历史数据分布优化范围  
       `每月分析:SELECT MAX(amount), PERCENTILE(amount,90) FROM orders`
    2. **设置溢出分片**:预留`overflow`分片接收超范围数据  
    3. **配合缓存**:对热点分片(如低价商品)增加Redis缓存层  
    

适用场景举例

  • 场景1:电商订单金额分级
    • 分片规则:
      0-500元    → 分片1(低价订单)
      501-2000元 → 分片2(中价订单)
      2001+元    → 分片3(高价订单)
      
    • 业务价值:VIP客服优先处理高价订单分片
  • 场景2:物联网时序数据
    • 按设备数据写入时间分片:
      2023-Q1 → 分片1
      2023-Q2 → 分片2
      
    • 冷热分离:自动将2年前数据归档到廉价存储
  • 场景3:商品价格分区
    • 分片策略:
      0-99元     → 分片1(引流商品)
      100-999元  → 分片2(主力商品)
      1000+元    → 分片3(奢侈品)
      
    • 查询优化
      -- 仅扫描分片1
      SELECT * FROM products WHERE price < 100;
      

1.3 枚举分片原理

<!-- rule.xml -->
<tableRule name="enum_rule">
    <rule>
        <columns>city_code</columns>
        <algorithm>enum_func</algorithm>
    </rule>
</tableRule>

<function name="enum_func" class="io.mycat.route.function.PartitionByFileMap">
    <property name="mapFile">partition-enum.txt</property>
    <property name="type">0</property> <!-- 0:Integer 1:String -->
</function>

partition-enum.txt内容

BJ=0  # 北京 -> 分片0
SH=1  # 上海 -> 分片1
GZ=2  # 广州 -> 分片2
SZ=3  # 深圳 -> 分片3

业务SQL示例

-- 路由到北京分片(city_code=BJ)
INSERT INTO merchant (mch_id, name, city_code) 
VALUES (1001, '王府井百货', 'BJ');

-- 查询上海商户(自动路由到上海分片)
SELECT * FROM merchant WHERE city_code = 'SH';
  • ✅ 优势
    • 业务可读性强:直接映射业务属性
    • 支持地理亲和性:北京用户访问北京分片
    • 灵活调整:修改映射文件即可变更路由
  • ❌ 弊端
    • 数据分布依赖业务:小型城市数据量少
    • 分片规模受限:超过100个枚举值难以维护
    • 跨分片事务复杂
  • ⛔ 避坑要点
    1. **设置默认分片**:处理未定义枚举值  
       `<property name="defaultNode">0</property>`
    2. **定期合并小分片**:将数据量<10万的城市合并到`other`分片  
    3. **版本化管理映射文件**:避免误操作导致路由错乱  
    

适用场景举例

  • 场景1:SaaS多租户系统
    • 租户分片映射:
      tenant_apple   → 分片1
      tenant_google  → 分片2
      tenant_microsoft → 分片3
      
    • 数据隔离:每个租户数据物理隔离
    • SQL示例
      /*!mycat:sql=SELECT * FROM sales WHERE tenant_id='apple'*/
      
  • 场景2:本地生活服务
    • 按城市分片:
      bj=0 # 北京
      sh=1 # 上海
      gz=2 # 广州
      
    • 地域亲和:北京用户请求直连北京分片
  • 场景3:多游戏服务器
    • 玩家数据按服务器分片:
      server_001 → 分片1
      server_002 → 分片2
      
    • 实现效果:不同服玩家数据完全隔离

1.4 固定分片原理

场景案例:物联网设备数据存储

<!-- rule.xml -->
<tableRule name="fixed_hash_rule">
    <rule>
        <columns>device_id</columns>
        <algorithm>fixed_hash_func</algorithm>
    </rule>
</tableRule>

<function name="fixed_hash_func" class="io.mycat.route.function.PartitionByLong">
    <property name="partitionCount">4</property>
    <property name="partitionLength">1024</property>
</function>

分片分配逻辑

# 伪代码实现
def get_partition(device_id):
    hash_val = hash(device_id) % 4096  # 总槽位=4*1024
    if hash_val < 1024: return 0
    elif hash_val < 2048: return 1
    elif hash_val < 3072: return 2
    else: return 3

业务SQL示例

-- 根据哈希值路由到分片2
INSERT INTO device_data (device_id, metric) 
VALUES ('D-7F3A9B', 25.6);

-- 精确查询自动路由到对应分片
SELECT * FROM device_data WHERE device_id = 'D-7F3A9B';
  • ✅ 优势
    • 扩容无需数据迁移:调整槽位映射即可
    • 分布相对均匀:离散性优于范围分片
    • 支持非数字分片键:如字符串设备ID
  • ❌ 弊端
    • 配置复杂:需理解partitionCountpartitionLength
    • 范围查询效率低
    • 哈希冲突可能导致小范围不均匀
  • ⛔ 避坑要点
    1. **槽位数=partitionCount×partitionLength**  
       总槽位需覆盖所有可能哈希值(建议2^N)
    2. **预热哈希分布**:模拟10万键值验证分布均匀性  
    3. **避免长字符串分片键**:截取前N字符提升效率  
       `columns = device_id.substr(0,8)`
    

适用场景举例

  • 场景1:百万级设备管理
    • 设备数据表配置:
      <function class="PartitionByLong">
        <property name="partitionCount">8</property>
        <property name="partitionLength">1024</property>
      </function>
      
    • 哈希槽分布:总槽位8192(8×1024)
    • 路由计算
      slot = hash(device_id) % 8192
      if slot < 1024: return 0
      elif slot < 2048: return 1
      ...
      
  • 场景2:分布式用户会话
    • 会话表按session_id哈希分片
    • 优势:同一用户会话始终路由到相同分片
  • 场景3:实时监控数据
    • 监控指标按metric_name前缀哈希:
      CPU_ → 分片1
      MEM_ → 分片2
      DISK_ → 分片3
      
    • 写入优化:同类指标集中存储

1.5 基础分片特性对比总表

评估维度取模分片范围分片枚举分片固定哈希
典型场景用户ID、订单ID日期、金额、年龄城市、租户ID、品类设备ID、会话ID
数据分布⭐⭐⭐⭐⭐ 绝对均匀⭐⭐☆☆☆ 可能倾斜⭐☆☆☆☆ 依赖业务分布⭐⭐⭐⭐☆ 较均匀
扩容复杂度⚠️⚠️⚠️ 需迁移50%+数据⚠️ 需调整范围边界✅ 添加映射即可⚠️⚠️ 需重分布槽位
范围查询效率❌ 全分片扫描✅✅ 精准定位分片✅ 按业务分组查询❌ 全分片扫描
业务亲和性❌ 与业务无关⭐⭐ 部分相关✅✅✅ 强业务关联❌ 与业务无关