schema.xml
<table name="travelrecord" dataNode="dn1,dn2,dn3" rule="sharding-by-murmur" />
rule.xml
<tableRule name="sharding-by-murmur">
<rule>
<columns>id</columns>
<algorithm>murmur</algorithm>
</rule>
</tableRule>
<function name="murmur"
class="io.mycat.route.function.PartitionByMurmurHash">
<property name="seed">0</property><!-- 默认是0 -->
<property name="count">3</property><!-- 要分片的数据库节点数量,必须指定,否则没法分片 -->
<property name="virtualBucketTimes">160</property><!-- 一个实际的数据库节点被映射为这么多虚拟节点,默认是160倍,也就是虚拟节点数是物理节点数的160倍 -->
<!-- <property name="weightMapFile">weightMapFile</property> 节点的权重,没有指定权重的节点默认是1。以properties文件的格式填写,以从0开始到count-1的整数值也就是节点索引为key,以节点权重值为值。所有权重值必须是正整数,否则以1代替 -->
<!-- <property name="bucketMapPath">/etc/mycat/bucketMapPath</property>
用于测试时观察各物理节点与虚拟节点的分布情况,如果指定了这个属性,会把虚拟节点的murmur hash值与物理节点的映射按行输出到这个文件,没有默认值,如果不指定,就不会输出任何东西 -->
</function>
用的是google的murmur算法,开源项目Redis,Memcache也在使用
源码
package io.mycat.route.function;
import io.mycat.util.StringUtil;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.SortedMap;
import java.util.TreeMap;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import io.mycat.config.model.rule.RuleAlgorithm;
import io.mycat.util.exception.MurmurHashException;
/**
* consistancy hash, murmur hash
* implemented by Guava
* @author wuzhih
*
*/
public class PartitionByMurmurHash extends AbstractPartitionAlgorithm implements RuleAlgorithm {
private static final int DEFAULT_VIRTUAL_BUCKET_TIMES=160;
private static final int DEFAULT_WEIGHT=1;
private static final Charset DEFAULT_CHARSET=Charset.forName("UTF-8");
private int seed;
private int count;
private int virtualBucketTimes=DEFAULT_VIRTUAL_BUCKET_TIMES;
private Map<Integer,Integer> weightMap=new HashMap<>();
private HashFunction hash;
private SortedMap<Integer,Integer> bucketMap;
@Override
public void init() {
try{
bucketMap=new TreeMap<>();
generateBucketMap();
}catch(Exception e){
throw new MurmurHashException(e);
}
}
private void generateBucketMap(){
hash=Hashing.murmur3_32(seed);//计算一致性哈希的对象
for(int i=0;i<count;i++){//构造一致性哈希环,用TreeMap表示
StringBuilder hashName=new StringBuilder("SHARD-").append(i);
for(int n=0,shard=virtualBucketTimes*getWeight(i);n<shard;n++){
bucketMap.put(hash.hashUnencodedChars(hashName.append("-NODE-").append(n)).asInt(),i);
}
}
weightMap=null;
}
/**
* 得到桶的权重,桶就是实际存储数据的DB实例
* 从0开始的桶编号为key,权重为值,权重默认为1。
* 键值必须都是整数
* @param bucket
* @return
*/
private int getWeight(int bucket){
Integer w=weightMap.get(bucket);
if(w==null){
w=DEFAULT_WEIGHT;
}
return w;
}
/**
* 创建murmur_hash对象的种子,默认0
* @param seed
*/
public void setSeed(int seed){
this.seed=seed;
}
/**
* 节点的数量
* @param count
*/
public void setCount(int count) {
this.count = count;
}
/**
* 虚拟节点倍数,virtualBucketTimes*count就是虚拟结点数量
* @param virtualBucketTimes
*/
public void setVirtualBucketTimes(int virtualBucketTimes){
this.virtualBucketTimes=virtualBucketTimes;
}
/**
* 节点的权重,没有指定权重的节点默认是1。以properties文件的格式填写,以从0开始到count-1的整数值也就是节点索引为key,以节点权重值为值。
* 所有权重值必须是正整数,否则以1代替
* @param weightMapPath
* @throws IOException
* @throws
*/
public void setWeightMapFile(String weightMapPath) throws IOException{
Properties props=new Properties();
try(BufferedReader reader=new BufferedReader(new InputStreamReader(this.getClass().getClassLoader().getResourceAsStream(weightMapPath), DEFAULT_CHARSET))){
props.load(reader);
for(Map.Entry entry:props.entrySet()){
int weight=Integer.parseInt(entry.getValue().toString());
weightMap.put(Integer.parseInt(entry.getKey().toString()), weight>0?weight:1);
}
}
}
@Override
public Integer calculate(String columnValue) {
SortedMap<Integer, Integer> tail = bucketMap.tailMap(hash.hashUnencodedChars(columnValue).asInt());
if (tail.isEmpty()) {
return bucketMap.get(bucketMap.firstKey());
}
return tail.get(tail.firstKey());
}
@Override
public int getPartitionNum() {
int nPartition = this.count;
return nPartition;
}
}
如需扩展,只需要修改rule.xml配置文件中的数据库节点数即可。