1、背景
系统中有一张交易记录表,从系统商用到现在,尽管MySQL单表数据已经累计到一亿,但是基于72核CPU和384G内存的配置,导也是相安无事。不过防患于未然,运维向我们提出了整改的要求,并限制单表数据不超过2000万。
2、分表方案
业务上这张表只会根据userId进行查询,对此我们决定采取水平分表的方案,目前单表总共一亿的数据,并且每个月产生1000万的增量数据。
在此我们要算出满足未来三年数据量增长的总表数。
12kw*3+10kw=46kw,46kw/2kw=23,为了有更多的容错空间,我们最终确定分为30张表,从tabale01-table30。
初步讨论对userId取模来确定对应分表,但是这里存在两个问题
1.userId并不是连续不断的数字,对其取余会出现分配不均的情况
2.如果数据增长太快,超出预计,就需要再添加分表,取模的值发生改变,所有的数据都需要重新迁移到分表中,工作量太大。
3、一致性Hash算法
就有大神给我们提出办法,让我们使用一致性Hash算法进行分表,就能解决以上的问题,那什么是一致性Hash算法呢?
一致性hash:对节点和数据,都做一次hash运算,然后比较节点和数据的hash值,数据值和节点最相近的节点作为处理节点。为了分布得更均匀,通过使用虚拟节点的方式,每个节点计算出n个hash值,均匀地放在hash环上这样数据就能比较均匀地分布到每个节点。
(1)环形Hash空间
这里有一个叫一致性Hash环的数据结构,环的起点是0,终点是2^32 - 1,并且首尾相连,环的中间的整数按逆时针分布,这个环的整数分布范围是[0, 2^32-1]。如下图:
(2)对表名进行Hash获取对应key并映射到Hash环上,相同的对象Hash值是一致的,如下图:
(3)对userId进行Hash获取对应key并映射到Hash环上,如下图:
(4)然后Hash(userId)的key以顺时针方向计算,得到与Hash(分表名)的key最近的节点对应的分表名,如下图:
经过以上的四个步骤完成了一致性Hash算法,得到userId对应的分表。
4、一致性Hash算法Java实现
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collection;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* 一致性Hash算法
*
* @param <T> 节点类型
*/
public class ConsistentHash<T> {
/**
* 复制的节点个数
*/
private final int numberOfReplicas;
/**
* 一致性Hash环
*/
private final SortedMap<Long, T> circle = new TreeMap<>();
/**
* Hash计算对象,用于自定义hash算法
*/
HashFunction hashFunction;
/**
* 构造,使用Java默认的Hash算法
*
* @param numberOfReplicas 复制的节点个数,增加每个节点的复制节点有利于负载均衡
* @param nodes 节点对象
*/
public ConsistentHash(int numberOfReplicas, Collection<T> nodes) {
this.numberOfReplicas = numberOfReplicas;
this.hashFunction = new HashFunction() {
@Override
public Long hash(Object key) {
//return fnv1HashingAlg(key.toString());
return md5HashingAlg(key.toString());
}
};
//初始化节点
for (T node : nodes)