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
Mysql 性能监控及调优
Apr 06 MySQL
MySQL通过binlog恢复数据
May 27 MySQL
MySQL CHAR和VARCHAR该如何选择
May 31 MySQL
mysql中between的边界,范围说明
Jun 08 MySQL
浅谈MySQL user权限表
Jun 18 MySQL
MySQL系列之十四 MySQL的高可用实现
Jul 02 MySQL
MySQL 5.7常见数据类型
Jul 15 MySQL
MySQL优化常用的19种有效方法(推荐!)
Mar 17 MySQL
MySQL日期时间函数知识汇总
Mar 17 MySQL
MySQL学习必备条件查询数据
Mar 25 MySQL
mysql的单列多值存储实例详解
Apr 05 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
初探PHP5
2006/10/09 PHP
Mysql数据库操作类( 1127版,提供源码下载 )
2010/12/02 PHP
php file_get_contents取文件中数组元素的方法
2017/04/01 PHP
Yii2 中实现单点登录的方法
2018/03/09 PHP
php判断电子邮件是否正确方法
2018/12/04 PHP
PHP检查URL包含特定字符串实例方法
2019/02/11 PHP
laravel实现查询最后执行的一条sql语句的方法
2019/10/09 PHP
JS批量操作CSS属性详细解析
2013/12/16 Javascript
js图片滚动效果时间可随意设定当鼠标移上去时停止
2014/06/26 Javascript
js 动态修改css文件用到了cssRule
2014/08/20 Javascript
如何解决easyui自定义标签 datagrid edit combobox 手动输入保存不上
2015/12/26 Javascript
Vue的Flux框架之Vuex状态管理器
2017/07/30 Javascript
React复制到剪贴板的示例代码
2017/08/22 Javascript
AnglarJs中的上拉加载实现代码
2018/02/08 Javascript
vue+mousemove实现鼠标拖动功能(拖动过快失效问题解决方法)
2018/08/24 Javascript
在vue中根据光标的显示与消失实现下拉列表
2019/09/29 Javascript
Python学习资料
2007/02/08 Python
Python+django实现文件下载
2016/01/17 Python
利用Python实现图书超期提醒
2016/08/02 Python
PyTorch读取Cifar数据集并显示图片的实例讲解
2018/07/27 Python
Python常见数据结构之栈与队列用法示例
2019/01/14 Python
Python可视化mhd格式和raw格式的医学图像并保存的方法
2019/01/24 Python
python实现可逆简单的加密算法
2019/03/22 Python
pyqt5移动鼠标显示坐标的方法
2019/06/21 Python
Python 使用PyQt5 完成选择文件或目录的对话框方法
2019/06/27 Python
Django框架视图函数设计示例
2019/07/29 Python
Python模块汇总(常用第三方库)
2019/10/07 Python
深入理解Tensorflow中的masking和padding
2020/02/24 Python
Python列表解析操作实例总结
2020/02/26 Python
利用Python过滤相似文本的简单方法示例
2021/02/03 Python
学校春季防火方案
2014/06/08 职场文书
社区服务标语
2014/07/01 职场文书
2014年学校党建工作汇报材料
2014/11/02 职场文书
2015初中生物教研组工作总结
2015/07/21 职场文书
老生常谈 使用 CSS 实现三角形的技巧(多种方法)
2021/04/13 HTML / CSS
java后台调用接口及处理跨域问题的解决
2022/03/24 Java/Android