この号で共有するトピック: 「Alluxio ブロック割り当て戦略の詳細な説明」
は、主に [戦略の詳細な概要]、[ブロック割り当て戦略の紹介]、[コード レベルの解釈] の 3 つの部分で紹介されています
。乾物↓
戦略詳細概要
Alluxio のワーカーは、ユーザーのデータ リソースを格納する責任があり、データはワーカーのストレージ ディレクトリ (階層型ストレージ) にブロックの形で格納されます. ストレージ ディレクトリは、MEM/SSD/HDD などの複数のレベルを持つことができます。複数のディレクトリで構成されているため、ユーザーが Alluxio を介してデータを読み書きする場合、Alluxio はどのディレクトリにブロックを配置するかをどのように決定しますか? この記事では、ブロック ストレージ ディレクトリの選択プロセスをコードの観点から分析します。
ブロック割り当て戦略の概要
Alluxio は、ブロック割り当てポリシーを使用して、複数のストレージ ディレクトリ (同じ層または異なる層) にブロックを割り当てる方法を定義します。
現在、Alluxio のブロック割り当て戦略には主に 3 つのタイプがあります。
- alluxio.worker.block.allocator.GreedyAllocator
は、ブロックを上から下に格納できる最初の格納ディレクトリにブロックを割り当てます。 - alluxio.worker.block.allocator.MaxFreeAllocator
は、残りの空き容量が最大のストレージ ディレクトリにブロックを割り当てます。 - alluxio.worker.block.allocator.RoundRobinAllocator
は、ブロックを各ストレージ ディレクトリに上から下にループします。
使用されるデフォルトのポリシーは MaxFreeAllocator で、プロパティ alluxio.worker.allocator.class で変更できます。
コードレベルでの解釈
ブロックへのストレージ ディレクトリの割り当てを担当するコード レベルのallocateSpace関数は、 allocateSpace です。
データの読み取りおよび書き込み時に、ワーカーにデータを保存する必要がある場合、ブロック割り当てポリシーに従ってデータを保存するために、ブロックのストレージ ディレクトリが要求されます。 options (デフォルトは level0) 、指定された場所に適切なスペースが見つからない場合、すべてのストレージ ディレクトリでそれを見つけようとします。
private StorageDirView allocateSpace(long sessionId, AllocateOptions options)
throws WorkerOutOfSpaceException, IOException {
while (true) {
if (options.isForceLocation()) {
//...
} else {
//...
dirView = mAllocator.allocateBlockWithView(sessionId, options.getSize(),
options.getLocation(), allocatorView, false);
if (dirView != null) {
return dirView;
}
dirView = mAllocator.allocateBlockWithView(sessionId, options.getSize(),
BlockStoreLocation.anyTier(), allocatorView, false);
if (dirView != null) {
return dirView;
}
}
//...
}
上記の allocateSpace によって選択された格納ディレクトリは動的計算によって取得されることがわかりますが、ある時点で、指定された格納ディレクトリまたはレベルにデータを書き込むこともできるため、allocateSpace も evict Block をサポートして、格納ディレクトリを解放できるようにします。新しいデータを収容するのに十分なスペース (これについては後で詳しく説明します)。
private StorageDirView allocateSpace(long sessionId, AllocateOptions options) {
StorageDirView dirView;
//...
while (true) {
if (options.isForceLocation()) {
dirView = mAllocator.allocateBlockWithView(sessionId, options.getSize(),
options.getLocation(), allocatorView, true);
if (dirView != null) {
return dirView;
}
freeSpace(sessionId, options.getSize(), options.getSize(), options.getLocation());
dirView = mAllocator.allocateBlockWithView(sessionId, options.getSize(),
options.getLocation(), allocatorView.refreshView(), true);
//...
}
}
//...
} else {
//...
}
return dirView;
}
}
allocateSpace から適切なストレージ ディレクトリを具体的に見つけるクラスは、前の章で提案されたいくつかのブロック割り当て戦略である Allocator インターフェイスのいくつかの実装クラスです: GreedyAllocator/MaxFreeAllocator/RoundRobinAllocator
アロケーター
public interface Allocator {
//...
StorageDirView allocateBlockWithView(long sessionId, long blockSize, BlockStoreLocation location,
BlockMetadataView view, boolean skipReview);
}
Allocator インターフェイスの allocateBlockWithView は、主に適切なストレージ ディレクトリを見つける役割を果たします。主に 3 つのパラメータに注意する必要があります:
✓ blockSize: 割り当てたい領域の量 ✓ location:
領域を割り当てたい場所
✓ skipReview: 割り当てるかどうかレビューをスキップして
、デフォルトの alluxio.worker.block.allocator.MaxFreeAllocator#MaxFreeAllocator を例として使用します。すべての場所でストレージ ディレクトリを検索する場合、すべてのストレージ ディレクトリを 1 つずつチェックし、最大のものを返すことがわかります。 1:
private StorageDirView allocateBlock(long sessionId, long blockSize,
BlockStoreLocation location, boolean skipReview) {
if (location.equals(BlockStoreLocation.anyTier())) {
for (StorageTierView tierView : mMetadataView.getTierViews()) {
candidateDirView = getCandidateDirInTier(tierView, blockSize, BlockStoreLocation.ANY_MEDIUM);
if (candidateDirView != null) { // Review
if (skipReview || mReviewer.acceptAllocation(candidateDirView)) {
break;
}
}
}
}
//...
return candidateDirView;
}
private StorageDirView getCandidateDirInTier(StorageTierView tierView,
long blockSize, String mediumType) {
StorageDirView candidateDirView = null;
long maxFreeBytes = blockSize - 1;
for (StorageDirView dirView : tierView.getDirViews()) {
if ((mediumType.equals(BlockStoreLocation.ANY_MEDIUM)
|| dirView.getMediumType().equals(mediumType))
&& dirView.getAvailableBytes() > maxFreeBytes) {
maxFreeBytes = dirView.getAvailableBytes();
candidateDirView = dirView;
}
}
return candidateDirView;
}
コードからわかるように、candidateDirView が見つかった場合、最終的にどのストレージ ディレクトリを返すかを決定するため、レビュー プロセスを実行する必要があります。
ブロック割り当てレビュー ポリシー
レビューは、ブロック割り当て戦略を補足するものであり、ブロック割り当て戦略にいくつかの追加の制限 (SoftLimit/HardLimit など) とランダム性をもたらします。現在、Alluxio には 2 つの Review 戦略があります。
- alluxio.worker.block.reviewer.AcceptingReviewer
は、すべてのレビューを直接通過します。これは、レビューなしと同等です。 - alluxio.worker.block.reviewer.ProbabilisticBufferReviewer
は、現在使用可能なスペースの残りのサイズに従って、Reviewer の以前の割り当て結果を確認します。
デフォルトの ProbabilisticBufferReviewer を見てください。
public class ProbabilisticBufferReviewer implements Reviewer {
//...
double getProbability(StorageDirView dirView) {
//...
if (availableBytes > mSoftLimitBytes) {
return 1.0;
}
if (availableBytes <= mHardLimitBytes) {
return 0.0;
}
double x = capacityBytes - availableBytes;
double k = 1.0 / (mHardLimitBytes - mSoftLimitBytes); // If HardLimit = SoftLimit, then we would have returned in the previous if-else
double b = (capacityBytes - mHardLimitBytes + 0.0) / (mSoftLimitBytes - mHardLimitBytes);
double y = k * x + b;
return y;
}
}
ProbabilisticBufferReviewer
- 現在のストレージ ディレクトリの残りのスペースが mHardLimitBytes 未満の場合は、直接 0 を返し、審査に合格していないことを示します。
- 現在のストレージ ディレクトリの残りのスペースが mSoftLimitBytes よりも大きい場合は、審査に合格したことを示す 1 を直接返します。
- 残りの容量が mHardLimitBytes と mSoftLimitBytes の間の場合、(0, 1) の間の値が返されます。
上記のFreeSpace
は、allocateSpace が evict Block をサポートして、ストレージ ディレクトリに新しいデータを収容するのに十分なスペースを作成させることをサポートしています。では、エビクトが実行されるとき、どのブロックをエビクトするかをどのように決定するのでしょうか?
Evict Block は freeSpace を介して行われ、十分なスペースがない場合、十分なスペースが空くまで freeSpace でブロックが 1 つずつ削除されます。
public synchronized void freeSpace(long sessionId, long minContiguousBytes,
long minAvailableBytes, BlockStoreLocation location) {
Iterator<Long> evictionCandidates = mBlockIterator.getIterator(location, BlockOrder.NATURAL);
while (true) {
//...
if (contiguousSpaceFound && availableBytesFound) {
break;
}
if (!evictionCandidates.hasNext()) {
break;
}
long blockToDelete = evictionCandidates.next();
if (evictorView.isBlockEvictable(blockToDelete)) { // 有一些 block 是不会被 evict 的
try {
BlockMeta blockMeta = mMetaManager.getBlockMeta(blockToDelete);
removeBlockFileAndMeta(blockMeta);
//...
}
//...
}
もちろん、削除されないブロックがいくつかあります。
public boolean isBlockEvictable(long blockId) {
boolean pinned = isBlockPinned(blockId);
boolean locked = isBlockLocked(blockId);
boolean marked = isBlockMarked(blockId);
boolean isEvictable = !pinned && !locked && !marked;
if (!isEvictable) {
LOG.debug("Block not evictable: {}. Pinned: {}, Locked: {}, Marked: {}", blockId, pinned,
locked, marked);
}
return isEvictable;
}
現在、2 種類のエビクト ルールがあります。
- alluxio.worker.block.annotator.LRUAnnotator
LRU ルール、つまり、最初に削除されるのは、最も長い間アクセスされていないものです - alluxio.worker.block.annotator.LRFUAnnotator
は LRFU および LRU ルールと組み合わせることができ、パラメーターを渡してルールを LRFU または LRU に近づけることもできます
使用されるデフォルトの戦略は LRUAnnotator です。これはプロパティ alluxio.worker.block.annotator.class で変更できます。
デフォルトの LRUAnnotator を見てみましょう:
public class LRUAnnotator implements BlockAnnotator<LRUAnnotator.LRUSortedField> {
private static final Logger LOG = LoggerFactory.getLogger(LRUAnnotator.class);
private AtomicLong mLRUClock;
@Override
public BlockSortedField updateSortedField(long blockId, LRUSortedField oldValue) {
long clockValue = mLRUClock.incrementAndGet();
return new LRUSortedField(clockValue);
}
/**
* Sorted-field for LRU.
*/
protected class LRUSortedField implements BlockSortedField {
private Long mClockValue;
private LRUSortedField(long clockValue) {
mClockValue = clockValue;
}
@Override
public int compareTo(BlockSortedField o) {
Preconditions.checkState(o instanceof LRUSortedField);
return mClockValue.compareTo(((LRUSortedField) o).mClockValue);
}
//...
}
LRUAnnotator は、単調に増加する AtomicLong を使用して各ブロックのアクセス順序を識別していることがわかります。AtomicLong が大きいほど、早くアクセスされます。