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数字类型自增的坑
May 07 MySQL
修改MySQL的数据库引擎为INNODB的方法
May 26 MySQL
浅谈mysql返回Boolean类型的几种情况
Jun 04 MySQL
mysql insert 存在即不插入语法说明
Mar 25 MySQL
mysql 索引的数据结构为什么要采用B+树
Apr 26 MySQL
详解Mysq MVCC多版本的并发控制
Apr 29 MySQL
MySQL脏读,幻读和不可重复读
May 11 MySQL
详解Mysql数据库平滑扩容解决高并发和大数据量问题
May 25 MySQL
MySQL中order by的执行过程
Jun 05 MySQL
MySQL数据库之内置函数和自定义函数 function
Jun 16 MySQL
Mysql数据库group by原理详解
Jul 07 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
thinkphp普通查询与表达式查询实例分析
2014/11/24 PHP
Ecshop 后台添加新功能栏目及管理权限设置教程
2017/11/21 PHP
Laravel中10个有用的用法小结
2019/05/06 PHP
Yii2处理密码加密及验证的方法
2019/05/12 PHP
JS 用6N±1法求素数 实例教程
2009/10/20 Javascript
基于javascript 闭包基础分享
2013/07/10 Javascript
js 数值转换为3位逗号分隔的示例代码
2014/02/19 Javascript
js中的onchange和onpropertychange (onchange无效的解决方法)
2014/03/08 Javascript
JS+CSS实现可拖动的弹出提示框
2015/02/16 Javascript
理解javascript中的原型和原型链
2015/07/30 Javascript
windows下安装nodejs及框架express
2015/08/07 NodeJs
解决jQuery使用JSONP时产生的错误
2015/12/02 Javascript
AngularJS 2.0新特性有哪些
2016/02/18 Javascript
JavaScript学习笔记之数组求和方法
2016/03/23 Javascript
javascript构造函数以及原型对象的理解
2017/01/13 Javascript
Angular4 中常用的指令入门总结
2017/06/12 Javascript
Vue2 Vue-cli中使用Typescript的配置详解
2017/07/24 Javascript
AngularJS中下拉框的基本用法示例
2017/10/11 Javascript
vue单页面打包文件大?首次加载慢?nginx带你飞,从7.5M到1.3M蜕变过程(推荐)
2018/01/16 Javascript
vue-router传递参数的几种方式实例详解
2018/11/13 Javascript
bootstrap下拉分页样式 带跳转页码
2018/12/29 Javascript
小程序的上传文件接口的注意要点解析
2019/09/17 Javascript
vue+elementui 对话框取消 表单验证重置示例
2019/10/29 Javascript
python爬取微信公众号文章的方法
2019/02/26 Python
Python爬虫爬取、解析数据操作示例
2020/03/27 Python
TensorFlow使用Graph的基本操作的实现
2020/04/22 Python
css3 实现滚动条美化效果的实例代码
2021/01/06 HTML / CSS
DVF官方网站:美国时装界尊尚品牌
2017/08/29 全球购物
计算机科学系职业生涯规划书
2014/03/08 职场文书
心理健康活动总结
2014/04/30 职场文书
应届生求职信
2014/05/31 职场文书
2015元旦家电促销活动策划方案
2014/12/09 职场文书
打架检讨书范文
2015/01/27 职场文书
关于开学的感想
2015/08/10 职场文书
学生会自荐信
2019/05/16 职场文书
详解CSS中的特指度和层叠问题
2021/07/15 HTML / CSS