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 22 MySQL
MySQL之PXC集群搭建的方法步骤
May 25 MySQL
MySQL查看表和清空表的常用命令总结
May 26 MySQL
Mysql 如何实现多张无关联表查询数据并分页
Jun 05 MySQL
MySQL的Query Cache图文详解
Jul 01 MySQL
SQL实现LeetCode(175.联合两表)
Aug 04 MySQL
mysql如何能有效防止删库跑路
Oct 05 MySQL
MySQL三种方式实现递归查询
Apr 18 MySQL
MySQL索引 高效获取数据的数据结构
May 02 MySQL
MySQL的prepare使用以及遇到的bug
May 11 MySQL
MYSQL事务的隔离级别与MVCC
May 25 MySQL
MySQL数据库安装方法与图形化管理工具介绍
May 30 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
简单的用PHP编写的导航条程序
2006/10/09 PHP
PHP错误Allowed memory size of 67108864 bytes exhausted的3种解决办法
2014/07/28 PHP
PHP预定义接口――Iterator用法示例
2020/06/05 PHP
超级退弹代码
2008/07/07 Javascript
基于Asp.net与Javascript控制的日期控件
2010/05/22 Javascript
multiSteps 基于Jquery的多步骤滑动切换插件
2011/07/22 Javascript
将查询条件的input、select清空
2014/01/14 Javascript
如何防止回车(enter)键提交表单
2014/05/11 Javascript
JS实现兼容各种浏览器的高级拖动方法完整实例【测试可用】
2016/06/21 Javascript
AngularJS入门教程之模块化操作用法示例
2016/11/02 Javascript
根据输入邮箱号跳转到相应登录地址的解决方法
2016/12/13 Javascript
JS中showModalDialog关闭子窗口刷新主窗口用法详解
2017/03/25 Javascript
详解Angular中的自定义服务Service、Provider以及Factory
2017/04/22 Javascript
vue脚手架及vue-router基本使用
2018/04/09 Javascript
一个基于react的图片裁剪组件示例
2018/04/18 Javascript
快速解决vue动态绑定多个class的官方实例语法无效的问题
2018/09/05 Javascript
微信小程序实现弹出菜单动画
2019/06/21 Javascript
Vue中util的工具函数实例详解
2019/07/08 Javascript
node.js中对Event Loop事件循环的理解与应用实例分析
2020/02/14 Javascript
python在windows下实现ping操作并接收返回信息的方法
2015/03/20 Python
深入理解python函数递归和生成器
2016/06/06 Python
python2.7 json 转换日期的处理的示例
2018/03/07 Python
Python找出微信上删除你好友的人脚本写法
2018/11/01 Python
python直接获取API传递回来的参数方法
2018/12/17 Python
tensorflow 查看梯度方式
2020/02/04 Python
基于python生成英文版词云图代码实例
2020/05/16 Python
HTML5 Canvas的常用线条属性值总结
2016/03/17 HTML / CSS
随机分配座位,共50个学生,使学号相邻的同学座位不能相邻
2014/01/18 面试题
SQL面试题
2013/12/09 面试题
房地产开盘策划方案
2014/02/10 职场文书
对祖国的寄语大全
2014/04/11 职场文书
《云房子》教学反思
2014/04/20 职场文书
复试通知单模板
2015/04/24 职场文书
刑事撤诉申请书
2015/05/18 职场文书
小区物业管理2015年度工作总结
2015/10/22 职场文书
springcloud整合seata
2022/05/20 Java/Android