在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
一、业务场景介绍假设目前有一个电商系统使用的是 当然还有些小伙伴知道按照省份/地区或一定的业务关系进行数据库拆分 OK,问题来了,如何保证合理的让数据存储在不同的库不同的表里呢?让库减少并发压力?应该怎么去制定分库分表的规则?不用急,这不就来了 二、水平分库分表方法1.RANGE第一种方法们可以指定一个数据范围来进行分表,例如从1~1000000,1000001-2000000,使用一百万一张表的方式,如下图所示 在这里插入图片描述 当然这种方法需要维护表的ID,特别是分布式环境下,这种分布式ID,在不使用第三方分表工具的情况下,建议使用 RANGE方法优点: 扩容简单,提前建好库、表就好 RANGE方法缺点: 大部分读和写都访会问新的数据,有IO瓶颈,这样子造成新库压力过大,不建议采用。 2.HASH取模针对上述 这样就可以将数据分散在不同的库、表中,避免了IO瓶颈的问题。 HASH取模方法优点: 能保证数据较均匀的分散落在不同的库、表中,减轻了数据库压力 HASH取模方法缺点: 扩容麻烦、迁移数据时每次都需要重新计算hash值分配到不同的库和表 3.一致性HASH通过HASH取模也不是最完美的办法,那什么才是呢? 使用一致性HASH算法能完美的解决问题 普通HASH算法: 普通哈希算法将任意长度的二进制值映射为较短的固定长度的二进制值,这个小的二进制值称为哈希值。哈希值是一段数据唯一且极其紧凑的数值表示形式。 普通的 一致性HASH算法: 按照常用的 这个圆环首尾相连,那么假设现在有三个数据库服务器节点 OK,现在咱们假设node3节点失效了 你会发现user3会落到node4上,你会发现,通过对节点的添加和删除的分析,一致性哈希算法在保持了单调性的同时,还是数据的迁移达到了最小,这样的算法对分布式集群来说是非常合适的,避免了大量数据迁移,减小了服务器的的压力。 当然还有一个问题还需要解决,那就是平衡性。从图我们可以看出,当服务器节点比较少的时候,会出现一个问题,就是此时必然造成大量数据集中到一个节点上面,极少数数据集中到另外的节点上面。 为了解决这种数据倾斜问题,一致性哈希算法引入了虚拟节点机制,即对每一个服务节点计算多个哈希,每个计算结果位置都放置一个节点,称为虚拟节点。具体做法可以先确定每个物理节点关联的虚拟节点数量,然后在ip或者主机名后面增加编号。例如上面的情况,可以为每台服务器计算三个虚拟节点,于是可以分别计算 “ 例如user1定位到 一致性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后被路由的情况,看下结果 总结: 分库分表在分布式微服务架构环境下建议强烈使用一致性 到此这篇关于 |
请发表评论