? ? hbase region 切分是hbases水平擴(kuò)展一個(gè)重要因素,將一個(gè)region切分為兩個(gè)小region,并將切分后的region放在不同的節(jié)點(diǎn)上埋凯,以達(dá)到將負(fù)載進(jìn)行均衡到其他節(jié)點(diǎn)。下面從split的策略、split流程以及split策略的設(shè)置三方面進(jìn)行講解region split雾棺。
split策略
????region split的策略分為如下幾種DisabledRegionSplitPolicy、ConstantSizeRegionSplitPolicy衬浑、IncreasingToUpperBoundRegionSplitPolicy捌浩、DelimitedKeyPrefixRegionSplitPolicy?、KeyPrefixRegionSplitPolicy以及SteppingSplitPolicy工秩。本節(jié)將從split的觸發(fā)條件尸饺、split切分的切分點(diǎn)以及相應(yīng)核心源碼三個(gè)方面進(jìn)行講解进统。
DisabledRegionSplitPolicy
? ? 禁用split策略,使用該策略浪听,region不會(huì)進(jìn)行自動(dòng)切分螟碎,可以進(jìn)行人為手動(dòng)的切分。
ConstantSizeRegionSplitPolicy
? ? 固定大小自動(dòng)split策略迹栓,在hbase?0.94.0之前使用的默認(rèn)策略?掉分。
split觸發(fā)條件
????當(dāng)region中存在一個(gè)store的大小大于desiredMaxFileSize值則觸發(fā)split。
????當(dāng)創(chuàng)建表時(shí)指定了MAX_FILESIZE屬性克伊,則desiredMaxFileSize =?MAX_FILESIZE * (1+浮動(dòng)率)酥郭,浮動(dòng)率由配置hbase.hregion.max.filesize.jitter計(jì)算得出。
????當(dāng)創(chuàng)建表時(shí)未指定MAX_FILESIZE屬性愿吹,則desiredMaxFileSize = hbase.hregion.max.filesize*1+浮動(dòng)率)不从,浮動(dòng)率由配置hbase.hregion.max.filesize.jitter計(jì)算得出。
? ? 核心觸發(fā)條件源碼如下:
protected boolean shouldSplit() {
? boolean force = region.shouldForceSplit();
? boolean foundABigStore = false;
//(1)逐個(gè)計(jì)算region中的store是否有大于desiredMaxFileSize的犁跪。
? for (Store store : region.getStores()) {
? ? if ((!store.canSplit())) {
? ? ? return false;
? ? }
? ? if (store.getSize() > desiredMaxFileSize) {
? ? ? foundABigStore = true;
? ? }
? }
? return foundABigStore || force;
}
//desiredMaxFileSize的賦值邏輯
protected void configureForRegion(HRegion region) {
? super.configureForRegion(region);
? Configuration conf = getConf();
//(1)當(dāng)表指定了MAX_FILESIZE屬性椿息,則desiredMaxFileSize 為該屬性的值
? HTableDescriptor desc = region.getTableDesc();
? if (desc != null) {
? ? this.desiredMaxFileSize = desc.getMaxFileSize();
? }
//(2)?表未指定MAX_FILESIZE屬性,則desiredMaxFileSize = hbase.hregion.max.filesize配置的值
? if (this.desiredMaxFileSize <= 0) {
? ? this.desiredMaxFileSize = conf.getLong(HConstants.HREGION_MAX_FILESIZE,
? ? ? HConstants.DEFAULT_MAX_FILE_SIZE);
? }
//(3)將desiredMaxFileSize 乘以浮動(dòng)系數(shù)耘拇。
? double jitter = conf.getDouble("hbase.hregion.max.filesize.jitter", 0.25D);
? this.jitterRate = (RANDOM.nextFloat() - 0.5D) * jitter;
? long jitterValue = (long) (this.desiredMaxFileSize * this.jitterRate);
? // make sure the long value won't overflow with jitter
? if (this.jitterRate > 0 && jitterValue > (Long.MAX_VALUE - this.desiredMaxFileSize)) {
? ? this.desiredMaxFileSize = Long.MAX_VALUE;
? } else {
? ? this.desiredMaxFileSize += jitterValue;
? }
}
split觸發(fā)點(diǎn)計(jì)算
? ? 本策略的切分點(diǎn)為該region上最大的store中最大的hfile的中間block的startkey作為splitpoint撵颊,如果該splitpoint等于該hfile的startkey或等于endkey則不進(jìn)行分割。
? ? 核心源碼如下:
protected byte[] getSplitPoint() {
//(1)是否有指定切分點(diǎn)惫叛,指定了切分點(diǎn)則使用指定的切分點(diǎn)進(jìn)行切分倡勇。
? byte[] explicitSplitPoint = this.region.getExplicitSplitPoint();
? if (explicitSplitPoint != null) {
? ? return explicitSplitPoint;
? }
? List<Store> stores = region.getStores();
? byte[] splitPointFromLargestStore = null;
? long largestStoreSize = 0;
? for (Store s : stores) {
? ? //(2)獲取該store中最大的hfile文件,選取該hfile的midkey作為splitpoint嘉涌,如果midkey等于該hfile的startkey或等于endkey則返回null妻熊。
? ? byte[] splitPoint = s.getSplitPoint();
? ? long storeSize = s.getSize();
? ? if (splitPoint != null && largestStoreSize < storeSize) {
? ? ? splitPointFromLargestStore = splitPoint;
? ? ? largestStoreSize = storeSize;
? ? }
? }
? return splitPointFromLargestStore;
}
public final byte[] getSplitPoint() throws IOException {
? if (this.storefiles.isEmpty()) {
? ? return null;
? }
//(1)獲取store上最大storefile,獲取該storefile的中間key作為切分點(diǎn)
? return StoreUtils.getLargestFile(this.storefiles).getFileSplitPoint(this.kvComparator);
}
byte[] getFileSplitPoint(KVComparator comparator) throws IOException {
? if (this.reader == null) {
? ? LOG.warn("Storefile " + this + " Reader is null; cannot get split point");
? ? return null;
? }
//(1)獲取該hfile的中間block的startkey作為midkey仑最,由于并不會(huì)遍歷block扔役,只是從block的元數(shù)據(jù)中獲取startkey,所以這個(gè)過(guò)程基本不耗時(shí)警医。
? byte [] midkey = this.reader.midkey();
//(2)判斷獲取的midkey是否與該hfile的startkey或endkey相同亿胸,相同則返回null即不進(jìn)行切分。
? if (midkey != null) {
? ? KeyValue mk = KeyValue.createKeyValueFromKey(midkey, 0, midkey.length);
? ? byte [] fk = this.reader.getFirstKey();
? ? KeyValue firstKey = KeyValue.createKeyValueFromKey(fk, 0, fk.length);
? ? byte [] lk = this.reader.getLastKey();
? ? KeyValue lastKey = KeyValue.createKeyValueFromKey(lk, 0, lk.length);
? ? if (comparator.compareRows(mk, firstKey) == 0 || comparator.compareRows(mk, lastKey) == 0) {
? ? ? if (LOG.isDebugEnabled()) {
? ? ? ? LOG.debug("cannot split because midkey is the same as first or last row");
? ? ? }
? ? ? return null;
? ? }
? ? return mk.getRow();
? }
? return null;
}
IncreasingToUpperBoundRegionSplitPolicy
? ? 動(dòng)態(tài)調(diào)整觸發(fā)切分大小策略预皇,從hbase0.94.0開(kāi)始默認(rèn)的自動(dòng)切分策略侈玄,繼承自ConstantSizeRegionSplitPolicy。
split觸發(fā)條件
一:指定表在當(dāng)前region對(duì)應(yīng)的regionserver上的region個(gè)數(shù)等于0或大于100吟温,則返回desiredMaxFileSize作為觸發(fā)大小序仙。
二:指定表在當(dāng)前region對(duì)應(yīng)的regionserver上的region個(gè)數(shù)大于0并且小于100,則動(dòng)態(tài)修改split觸發(fā)的大小鲁豪,計(jì)算公式: min(desiredMaxFileSize潘悼,initialSize*tableregioncount*tableregioncount*tableregioncount)
1)initialSize(初始大新赏骸):
如果配置文件指定了hbase.increasing.policy.initial.size,則使用該值治唤。
如果配置文件未指定hbase.increasing.policy.initial.size棒动,則使用創(chuàng)建表時(shí)指定的MEMSTORE_FLUSHSIZE屬性值*2,未指定表的MEMSTORE_FLUSHSIZE屬性則使用hbase.hregion.memstore.flush.size*2肝劲。
2)tableregioncount:指定表在當(dāng)前region對(duì)應(yīng)的regionserver上有多少個(gè)region迁客。
例如:
前提:
????1.分割之后的子region任然在父region的regionserver上
? ? 2.未指定hbase.increasing.policy.initial.size和表屬性MEMSTORE_FLUSHSIZE,指定hbase.hregion.memstore.flush.size為256M辞槐。
? ??3.desiredMaxFileSize為10G
第一次分割的觸發(fā)大兄朗:256*2*1*1*1 = 256M
第二次分割的觸發(fā)大小:256*2*2*2*2 = 2048M
第三次分割的觸發(fā)大虚省:256*2*4*4*4 =16384M >desiredMaxFileSize(10G)卜范,取desiredMaxFileSize(10G)
之后所有分割的觸發(fā)大小都是desiredMaxFileSize(10G)。
? ? 核心源碼如下:
protected boolean shouldSplit() {
? boolean force = region.shouldForceSplit();
? boolean foundABigStore = false;
? // (1)獲取指定表在當(dāng)前region對(duì)應(yīng)的regionserver上的region個(gè)數(shù)
? int tableRegionsCount = getCountOfCommonTableRegions();
? // (2)獲取檢查大小
? long sizeToCheck = getSizeToCheck(tableRegionsCount);
? for (Store store : region.getStores()) {
? ? if (!store.canSplit()) {
? ? ? return false;
? ? }
? ? // Mark if any store is big enough
? ? long size = store.getSize();
? ? if (size > sizeToCheck) {
? ? ? LOG.debug("ShouldSplit because " + store.getColumnFamilyName() + " size=" + size
? ? ? ? ? ? ? ? + ", sizeToCheck=" + sizeToCheck + ", regionsWithCommonTable="
? ? ? ? ? ? ? ? + tableRegionsCount);
? ? ? foundABigStore = true;
? ? }
? }
? return foundABigStore | force;
}
//計(jì)算check size
protected long getSizeToCheck(final int tableRegionsCount) {? // (1)region個(gè)數(shù)等于0或大于100鹿榜,則返回desiredMaxFileSize作為觸發(fā)大小
//? (2)region個(gè)數(shù)大于0并且小于100海雪,則動(dòng)態(tài)修改split觸發(fā)的大小,計(jì)算公式: min(desiredMaxFileSize舱殿,initialSize*tableregioncount*tableregioncount*tableregioncount)
? return tableRegionsCount == 0 || tableRegionsCount > 100
? ? ? ? ? ? ? getDesiredMaxFileSize()
? ? ? ? ? ? : Math.min(getDesiredMaxFileSize(),
? ? ? ? ? ? ? ? ? ? ? ? initialSize * tableRegionsCount * tableRegionsCount * tableRegionsCount);
}
split觸發(fā)點(diǎn)計(jì)算
? ? 和ConstantSizeRegionSplitPolicy策略的split觸發(fā)點(diǎn)計(jì)算一致奥裸。
KeyPrefixRegionSplitPolicy
? ? 根據(jù)key的前綴進(jìn)行切分,繼承自IncreasingToUpperBoundRegionSplitPolicy沪袭。
split觸發(fā)條件
? ? split觸發(fā)條件和IncreasingToUpperBoundRegionSplitPolicy一致湾宙。
split觸發(fā)點(diǎn)計(jì)算
? ??根據(jù)rowKey的指定長(zhǎng)度的前綴對(duì)數(shù)據(jù)進(jìn)行分組,以便于將這些數(shù)據(jù)分到相同的Region中冈绊,長(zhǎng)度指定由創(chuàng)建表時(shí)的KeyPrefixRegionSplitPolicy.prefix_length屬性指定(舊版本的屬性為:prefix_split_key_policy.prefix_length)?比如rowKey都是8位的侠鳄,指定前3位是前綴,那么前3位相同的rowKey在進(jìn)行region split的時(shí)候會(huì)分到相同的region中死宣。
? ? 核心源碼如下:
protected byte[] getSplitPoint() {
//(1)調(diào)用父類(lèi)的方法伟恶,獲取該region上最大的store中最大的hfile的中間block的startkey作為splitpoint。
? byte[] splitPoint = super.getSplitPoint();
//(2)指定的前綴長(zhǎng)度大于0毅该,則獲取splitPoint的指定長(zhǎng)度作為最后split的切分點(diǎn)博秫。? ?
? if (prefixLength > 0 && splitPoint != null && splitPoint.length > 0) {
? ? // group split keys by a prefix
? ? return Arrays.copyOf(splitPoint,
? ? ? ? Math.min(prefixLength, splitPoint.length));
? } else {
? ? return splitPoint;
? }
}
//獲取前綴長(zhǎng)度
protected void configureForRegion(HRegion region) {? super.configureForRegion(region);
? prefixLength = 0;
//(1)獲取表的KeyPrefixRegionSplitPolicy.prefix_length屬性為前綴長(zhǎng)度,舊版的為prefix_split_key_policy.prefix_length屬性眶掌。
? String prefixLengthString = region.getTableDesc().getValue(
? ? ? PREFIX_LENGTH_KEY);
? if (prefixLengthString == null) {
? ? prefixLengthString = region.getTableDesc().getValue(PREFIX_LENGTH_KEY_DEPRECATED);
? ? if (prefixLengthString == null) {
? ? ? return;
? ? }
? }
? try {
? ? prefixLength = Integer.parseInt(prefixLengthString);
? } catch (NumberFormatException nfe) {
? ? return;
? }
......
}
DelimitedKeyPrefixRegionSplitPolicy
? ? 根據(jù)分割符獲取前綴進(jìn)行切分台盯,繼承自IncreasingToUpperBoundRegionSplitPolicy。? ??
split觸發(fā)條件
? ? split觸發(fā)條件和IncreasingToUpperBoundRegionSplitPolicy一致畏线。
split觸發(fā)點(diǎn)計(jì)算
? ? 與KeyPrefixRegionSplitPolicy的split觸發(fā)點(diǎn)計(jì)算類(lèi)似也是使用rowkey的前綴作為splitpoint,不同點(diǎn)在于KeyPrefixRegionSplitPolicy使用固定長(zhǎng)度作為前綴良价,而DelimitedKeyPrefixRegionSplitPolicy指定分隔字符進(jìn)行拆分作為前綴寝殴。分隔字符由表的DelimitedKeyPrefixRegionSplitPolicy.delimiter屬性指定蒿叠。
? ? 核心源碼如下:
protected byte[] getSplitPoint() {
//(1)調(diào)用父類(lèi)的方法,獲取該region上最大的store中最大的hfile的中間block的startkey作為splitpoint蚣常。
? byte[] splitPoint = super.getSplitPoint();
//(2)獲取分隔符之前的字符作為splitpoint市咽。
? if (splitPoint != null && delimiter != null) {
? ? int index = com.google.common.primitives.Bytes.indexOf(splitPoint, delimiter);
? ? if (index < 0) {
? ? ? LOG.warn("Delimiter " + Bytes.toString(delimiter) + "? not found for split key "
? ? ? ? ? + Bytes.toString(splitPoint));
? ? ? return splitPoint;
? ? }
? ? return Arrays.copyOf(splitPoint, Math.min(index, splitPoint.length));
? } else {
? ? return splitPoint;
? }
}
//獲取分隔符號(hào)
protected void configureForRegion(HRegion region) {? super.configureForRegion(region);
//(1)獲取表的DelimitedKeyPrefixRegionSplitPolicy.delimiter屬性作為分隔符
? String delimiterString = region.getTableDesc().getValue(DELIMITER_KEY);
? if (delimiterString == null || delimiterString.length() == 0) {
? ? LOG.error(DELIMITER_KEY + " not specified for table " + region.getTableDesc().getTableName() +
? ? ? ". Using default RegionSplitPolicy");
? ? return;
? }
? delimiter = Bytes.toBytes(delimiterString);}?
SteppingSplitPolicy
? ? 分階段進(jìn)行固定大小分割,繼承自IncreasingToUpperBoundRegionSplitPolicy抵蚊。? ?
split觸發(fā)條件
? ? 當(dāng)只有一個(gè)region的時(shí)候施绎,使用initialSize作為觸發(fā)split大小,否則使用desiredMaxFileSize作為觸發(fā)split大小贞绳。initialSize和desiredMaxFileSize都在前面進(jìn)行過(guò)描述谷醉。
? ? 核心代碼如下:
protected long getSizeToCheck(final int tableRegionsCount) {
? return tableRegionsCount == 1? ? this.initialSize : getDesiredMaxFileSize();
}
split觸發(fā)點(diǎn)計(jì)算
? ? 和ConstantSizeRegionSplitPolicy策略的split觸發(fā)點(diǎn)計(jì)算一致。
split流程
????當(dāng)split發(fā)生的時(shí)候冈闭,創(chuàng)建的子region并不會(huì)馬上把所有的數(shù)據(jù)寫(xiě)入新的文件俱尼,而是創(chuàng)建一個(gè)小的鏈接引用文件指向分割點(diǎn)的頭部和尾部。這些引用文件會(huì)在compactions操作逐漸被清除萎攒。只有當(dāng)region沒(méi)有引用文件的時(shí)候才可以進(jìn)行split操作遇八。
????regionserver在split開(kāi)始和結(jié)束都會(huì)通知hmaster去更新.META表,使客戶端可以知道新的子region耍休,重新組織hdfs上的文件路徑刃永。
split操作的流程如下圖所示:
1.在zookeeper的/hbase/region-in-transition/region-name路徑下創(chuàng)建znode并標(biāo)記狀態(tài)為SPLITTING.。
2.hmaster監(jiān)聽(tīng)/hbase/region-in-transition/region-name路徑得知該region正在進(jìn)行split
3.regionserver在hdfs的父region路徑下創(chuàng)建.splits路徑
4.regionserver上關(guān)閉父region羊精,此時(shí)父region為offline斯够,當(dāng)有客戶端訪問(wèn)該父region時(shí)會(huì)報(bào)NotServingRegionException錯(cuò)誤。
5.在hdfs的.splits路徑下創(chuàng)建子region A园匹、B的路徑雳刺,然后split,其實(shí)就是在子region A裸违、B的路徑下創(chuàng)建引用文件指向父region的文件掖桦。
6.創(chuàng)建實(shí)際的子region路徑(上面創(chuàng)建的文件都是在父region路徑下),并把引用文件移動(dòng)到該路徑下供汛。
7.該regionserver向擁有.META表的regionserver發(fā)送一條put請(qǐng)求枪汪,修改該spliting region的狀態(tài)offline,并且添加子region的regionname怔昨。在這個(gè)時(shí)候并沒(méi)有單獨(dú)的子region信息雀久,當(dāng)客戶端scan表.META時(shí)知道到父region在split,但是不知道子region的信息趁舀。當(dāng)put請(qǐng)求成功后父region會(huì)進(jìn)行快速的split赖捌。
8.該regionserver并發(fā)的打開(kāi)兩個(gè)子region。
9.該regionserver將兩個(gè)子region的信息(host)發(fā)送到擁有.META表的regionserver矮烹,添加到.META表中越庇。這時(shí)兩個(gè)子region上線罩锐,客戶端可以知道這兩個(gè)子region并向這兩個(gè)子region發(fā)送請(qǐng)求÷卑Γ客戶端會(huì)緩存.META表中的數(shù)據(jù)涩惑,當(dāng)使用緩存中的數(shù)據(jù)進(jìn)行訪問(wèn)regionserver時(shí)出現(xiàn)問(wèn)題,客戶端會(huì)重新請(qǐng)求.META表中的內(nèi)容進(jìn)行緩存桑驱。
10.將步驟1創(chuàng)建的znode竭恬,將該狀態(tài)轉(zhuǎn)為split,這時(shí)split操作完成熬的,hmaster得知split操作完成痊硕。
11.完成上述步驟后,hdfs仍然包含引用文件指向父region悦析,這些引用文件會(huì)在子region進(jìn)行compactions時(shí)進(jìn)行移除寿桨。hmaster中的gc任務(wù)會(huì)周期的檢查子region是否還有引用父region的文件,沒(méi)有的話會(huì)將父region進(jìn)行移除强戴。
split策略設(shè)置
? ? 分為兩種方式進(jìn)行設(shè)置
1)全局方式亭螟,通過(guò)修改配置文件中的hbase.regionserver.region.split.policy屬性進(jìn)行指定策略,未指定策略的表都使用該配置指定的策略骑歹。
2)表級(jí)別预烙,通過(guò)指定表的屬性進(jìn)行指定split策略,hbase shell中案例如下:
create 'test1', { NAME => 'cf',COMPRESSION => 'GZ'}, {METADATA => {'SPLIT_POLICY' => 'org.apache.hadoop.hbase.regionserver.ConstantSizeRegionSplitPolicy',MAX_FILESIZE=> '10737418240'}}
? ??今天的分享就到這道媚,有看不明白的地方一定是我寫(xiě)的不夠清楚扁掸,所有歡迎提任何問(wèn)題以及改善方法。