MySQL分库分表详情


Posted in MySQL onSeptember 25, 2021

一、业务场景介绍

假设目前有一个电商系统使用的是MySQL,要设计大数据量存储、高并发、高性能可扩展的方案,数据库中有用户表。用户会非常多,并且要实现高扩展性,你会怎么去设计? OK咱们先看传统的分库分表方式

MySQL分库分表详情

当然还有些小伙伴知道按照省份/地区或一定的业务关系进行数据库拆分

MySQL分库分表详情

OK,问题来了,如何保证合理的让数据存储在不同的库不同的表里呢?让库减少并发压力?应该怎么去制定分库分表的规则?不用急,这不就来了

二、水平分库分表方法

1.RANGE

第一种方法们可以指定一个数据范围来进行分表,例如从1~1000000,1000001-2000000,使用一百万一张表的方式,如下图所示

MySQL分库分表详情

在这里插入图片描述 当然这种方法需要维护表的ID,特别是分布式环境下,这种分布式ID,在不使用第三方分表工具的情况下,建议使用RedisRedisincr操作可以轻松的维护分布式的表ID。

RANGE方法优点: 扩容简单,提前建好库、表就好

RANGE方法缺点: 大部分读和写都访会问新的数据,有IO瓶颈,这样子造成新库压力过大,不建议采用。

2.HASH取模

针对上述RANGE方式分表有IO瓶颈的问题,咱们可以采用根据用户ID HASG取模的方式进行分库分表,如图所示: MySQL分库分表详情

这样就可以将数据分散在不同的库、表中,避免了IO瓶颈的问题。

HASH取模方法优点: 能保证数据较均匀的分散落在不同的库、表中,减轻了数据库压力

HASH取模方法缺点: 扩容麻烦、迁移数据时每次都需要重新计算hash值分配到不同的库和表

3.一致性HASH

通过HASH取模也不是最完美的办法,那什么才是呢?

使用一致性HASH算法能完美的解决问题

普通HASH算法:

普通哈希算法将任意长度的二进制值映射为较短的固定长度的二进制值,这个小的二进制值称为哈希值。哈希值是一段数据唯一且极其紧凑的数值表示形式。

普通的hash算法在分布式应用中的不足:在分布式的存储系统中,要将数据存储到具体的节点上,如果我们采用普通的hash算法进行路由,将数据映射到具体的节点上,如key%nkey是数据的key,n是机器节点数,如果有一个机器加入或退出集群,则所有的数据映射都无效了,如果是持久化存储则要做数据迁移,如果是分布式缓存,则其他缓存就失效了。

一致性HASH算法: 按照常用的hash算法来将对应的key哈希到一个具有2^32次方个节点的空间中,即0~ (2^32)-1的数字空间中。现在我们可以将这些数字头尾相连,想象成一个闭合的环形,如下图所示。

MySQL分库分表详情

这个圆环首尾相连,那么假设现在有三个数据库服务器节点node1node2node3三个节点,每个节点负责自己这部分的用户数据存储,假设有用户user1、user2、user3,我们可以对服务器节点进行HASH运算,假设HASH计算后,user1落在node1上,user2落在node2上,user3落在user3上

MySQL分库分表详情

OK,现在咱们假设node3节点失效了

MySQL分库分表详情

 user3将会落到node1上,而之前的node1和node2数据不会改变,再假设新增了节点node4

MySQL分库分表详情

你会发现user3会落到node4上,你会发现,通过对节点的添加和删除的分析,一致性哈希算法在保持了单调性的同时,还是数据的迁移达到了最小,这样的算法对分布式集群来说是非常合适的,避免了大量数据迁移,减小了服务器的的压力。

当然还有一个问题还需要解决,那就是平衡性。从图我们可以看出,当服务器节点比较少的时候,会出现一个问题,就是此时必然造成大量数据集中到一个节点上面,极少数数据集中到另外的节点上面。

为了解决这种数据倾斜问题,一致性哈希算法引入了虚拟节点机制,即对每一个服务节点计算多个哈希,每个计算结果位置都放置一个节点,称为虚拟节点。具体做法可以先确定每个物理节点关联的虚拟节点数量,然后在ip或者主机名后面增加编号。例如上面的情况,可以为每台服务器计算三个虚拟节点,于是可以分别计算 “node 1-1”、“node 1-2”、“node 1-3”、“node 2-1”、“node 2-2”、“node 2-3”、“node 3-1”、“node 3-2”、“node 3-3”的哈希值,这样形成九个虚拟节点

例如user1定位到node 1-1node 1-2node 1-3上其实都是定位到node1这个节点上,这样能够解决服务节点少时数据倾斜的问题,当然这个虚拟节点的个数不是说固定三个或者至多、至少三个,这里只是一个例子,具体虚拟节点的多少,需要根据实际的业务情况而定。

一致性HASH方法优点: 通过虚拟节点方式能保证数据较均匀的分散落在不同的库、表中,并且新增、删除节点不影响其他节点的数据,高可用、容灾性强。

一致性取模方法缺点: 嗯,比起以上两种,可以认为没有。

三、单元测试

OK,不废话,接下来上单元测试,假设有三个节点,每个节点有三个虚拟节点的情况

package com.hyh.core.test;

import com.hyh.utils.common.StringUtils;
import org.junit.Test;

import java.util.LinkedList;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * 一致性HASH TEST
 *
 * @Author heyuhua
 * @create 2021/1/31 19:50
 */
public class ConsistentHashTest {

    //待添加入Hash环的服务器列表
    private static String[] servers = {"192.168.5.1", "192.168.5.2", "192.168.5.3"};

    //真实结点列表,考虑到服务器上线、下线的场景,即添加、删除的场景会比较频繁,这里使用LinkedList会更好
    private static List<String> realNodes = new LinkedList<>();

    //虚拟节点,key表示虚拟节点的hash值,value表示虚拟节点的名称
    private static SortedMap<Integer, String> virtualNodes = new TreeMap<>();

    //一个真实结点对应3个虚拟节点
    private static final int VIRTUAL_NODES = 3;

    /**
     * 测试有虚拟节点的一致性HASH
     */
    @Test
    public void testConsistentHash() {
        initNodes();
        String[] users = {"user1", "user2", "user3", "user4", "user5", "user6", "user7", "user8", "user9"};
        for (int i = 0; i < users.length; i++)
            System.out.println("[" + users[i] + "]的hash值为" +
                    getHash(users[i]) + ", 被路由到结点[" + getServer(users[i]) + "]");
    }

    /**
     * 先把原始的服务器添加到真实结点列表中
     */
    public void initNodes() {
        for (int i = 0; i < servers.length; i++)
            realNodes.add(servers[i]);
        for (String str : realNodes) {
            for (int i = 0; i < VIRTUAL_NODES; i++) {
                String virtualNodeName = str + "-虚拟节点" + String.valueOf(i);
                int hash = getHash(virtualNodeName);
                System.out.println("虚拟节点[" + virtualNodeName + "]被添加, hash值为" + hash);
                virtualNodes.put(hash, virtualNodeName);
            }
        }
        System.out.println();
    }

    //使用FNV1_32_HASH算法计算服务器的Hash值,这里不使用重写hashCode的方法,最终效果没区别
    private static int getHash(String str) {
        final int p = 16777619;
        int hash = (int) 2166136261L;
        for (int i = 0; i < str.length(); i++)
            hash = (hash ^ str.charAt(i)) * p;
        hash += hash << 13;
        hash ^= hash >> 7;
        hash += hash << 3;
        hash ^= hash >> 17;
        hash += hash << 5;

        // 如果算出来的值为负数则取其绝对值
        if (hash < 0)
            hash = Math.abs(hash);
        return hash;
    }

    //得到应当路由到的结点
    private static String getServer(String key) {
        //得到该key的hash值
        int hash = getHash(key);
        // 得到大于该Hash值的所有Map
        SortedMap<Integer, String> subMap = virtualNodes.tailMap(hash);
        String virtualNode;
        if (subMap.isEmpty()) {
            //如果没有比该key的hash值大的,则从第一个node开始
            Integer i = virtualNodes.firstKey();
            //返回对应的服务器
            virtualNode = virtualNodes.get(i);
        } else {
            //第一个Key就是顺时针过去离node最近的那个结点
            Integer i = subMap.firstKey();
            //返回对应的服务器
            virtualNode = subMap.get(i);
        }
        //virtualNode虚拟节点名称要截取一下
        if (StringUtils.isNotBlank(virtualNode)) {
            return virtualNode.substring(0, virtualNode.indexOf("-"));
        }
        return null;
    }
}

这里模拟9个用户对象hash后被路由的情况,看下结果

MySQL分库分表详情

总结:

分库分表在分布式微服务架构环境下建议强烈使用一致性HASH算法来做,当然分布式环境下也会产生业务数据数据一致性、分布式事务问题,下期咱们再来探讨数据一致性、分布式事务的解决方案

到此这篇关于MySQL分库分表详情的文章就介绍到这了,更多相关MySQL分库分表内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

MySQL 相关文章推荐
MySQL表的增删改查(基础)
Apr 05 MySQL
多表查询、事务、DCL
Apr 05 MySQL
MySQL创建索引需要了解的
Apr 08 MySQL
linux下导入、导出mysql数据库命令的实现方法
May 26 MySQL
mysql如何配置白名单访问
Jun 30 MySQL
浅谈MySQL函数
Oct 05 MySQL
MySQL创建定时任务
Jan 22 MySQL
Mysql分库分表之后主键处理的几种方法
Feb 15 MySQL
关于MySQL临时表为什么可以重名的问题
Mar 22 MySQL
pt-archiver 主键自增
Apr 26 MySQL
MySQL聚簇索引和非聚簇索引的区别详情
Jun 14 MySQL
MySQL数据管理操作示例讲解
Dec 24 MySQL
MySQL空间数据存储及函数
Sep 25 #MySQL
MySQL基础快速入门知识总结(附思维导图)
MySQL连接控制插件介绍
Sep 25 #MySQL
Mysql案例刨析事务隔离级别
Sep 25 #MySQL
MySQL定时备份数据库(全库备份)的实现
Sep 25 #MySQL
MySQL修改默认引擎和字符集详情
Sep 25 #MySQL
MySQL 用 limit 为什么会影响性能
Sep 15 #MySQL
You might like
关于js与php互相传值的介绍
2013/06/25 PHP
php中文验证码实现示例分享
2014/01/12 PHP
PHP信号处理机制的操作代码讲解
2019/04/19 PHP
关于javaScript注册click事件传递参数的不成功问题
2014/07/18 Javascript
13个PHP函数超实用
2015/10/21 Javascript
js实现div模拟模态对话框展现URL内容
2016/05/27 Javascript
Augularjs-起步详解
2016/07/08 Javascript
深入浅析knockout源码分析之订阅
2016/07/12 Javascript
浅谈JS读取DOM对象(标签)的自定义属性
2016/11/21 Javascript
vue.js指令和组件详细介绍及实例
2017/04/06 Javascript
详解webpack4升级指南以及从webpack3.x迁移
2018/06/12 Javascript
Vue.js中使用iView日期选择器并设置开始时间结束时间校验功能
2018/08/12 Javascript
Vue实现将数据库中带html标签的内容输出(原始HTML(Raw HTML))
2019/10/28 Javascript
python实现的简单猜数字游戏
2015/04/04 Python
Python引用类型和值类型的区别与使用解析
2017/10/17 Python
Python cookbook(数据结构与算法)将多个映射合并为单个映射的方法
2018/04/19 Python
Python PO设计模式的具体使用
2019/08/16 Python
django 做 migrate 时 表已存在的处理方法
2019/08/31 Python
pytorch中的自定义数据处理详解
2020/01/06 Python
pytorch实现CNN卷积神经网络
2020/02/19 Python
Django数据库操作之save与update的使用
2020/04/01 Python
150行python代码实现贪吃蛇游戏
2020/04/24 Python
一文弄懂Pytorch的DataLoader, DataSet, Sampler之间的关系
2020/07/03 Python
Python 防止死锁的方法
2020/07/29 Python
Django缓存Cache使用详解
2020/11/30 Python
CSS实现定位元素居中的方法
2015/06/23 HTML / CSS
h5封装下拉刷新
2020/08/25 HTML / CSS
H&M旗下高端女装品牌:& Other Stories
2018/05/07 全球购物
简述网络文件系统NFS,并说明其作用
2016/10/19 面试题
Linux文件操作命令都有哪些
2016/07/23 面试题
入党思想汇报
2014/01/05 职场文书
致100米运动员广播稿
2014/02/14 职场文书
《散步》教学反思
2014/03/02 职场文书
委托公证书格式
2015/01/26 职场文书
家长意见和建议怎么写
2015/06/04 职场文书
go goroutine 怎样进行错误处理
2021/07/16 Golang