LightWeightGSet是名字节点NameNode在内存中存储全部数据块信息的类BlocksMap需要的一个重要数据结构,它是一个占用较低内存的集合的实现,它使用一个数组array存储元素,使用linked lists来解决冲突。它没有实现重新哈希分区,所以,内部的array不会改变大小。这个类不支持null元素,并且不是线程安全的。它在BlocksMap中的初始化如下:

  1. this.blocks = new LightWeightGSet<Block, BlockInfo>(capacity)// ...省略部分代码

可见,它类似一个Key、Value集合,Key为BLock对象,Value为BlockInfo对象。

那么,对于LightWeightGSet的上述介绍该如何解释呢?既然看上去像一个Key、Value集合,那么它到底是不是一个Key、Value集合呢?而且,为什么说它占内存比较低,使用数组存储元素,又用linked lists来解决冲突呢?相信当你看完LightWeightGSet的实现,就会一目了然。我们先看下LightWeightGSet中最重要的一个成员变量,如下:

  1. /**
  2. * An internal array of entries, which are the rows of the hash table.
  3. * The size must be a power of two.
  4. * 存储元素条目的内部数组,它是哈希表中的行。
  5. * 数组大小必须是2的幂。
  6. * 数组元素实现了LinkedElement接口。
  7. */
  8. private final LinkedElement[] entries;

entries是一个存储实现了LinkedElement接口对象的数组,实际上存储的是BlockInfo实例。它是LightWeightGSet存储元素条目的内部数组,数组中元素是哈希表中的一行,且数组的大小必须为2的幂。

先了解上述信息就行,我们再往下看,既然是一个集合,就得能够实现存取元素,LightWeightGSet肯定得对外提供了元素存取的方法,先看存,通过put()方法实现,代码如下:

  1. @Override
  2. public E put(final E element) {
  3. //validate element
  4. // 检验元素element
  5. // 不支持null元素
  6. if (element == null) {
  7. throw new NullPointerException("Null element is not supported.");
  8. }
  9. // 元素必须实现LinkedElement接口
  10. if (!(element instanceof LinkedElement)) {
  11. throw new HadoopIllegalArgumentException(
  12. "!(element instanceof LinkedElement), element.getClass()="
  13. + element.getClass());
  14. }
  15. // 将元素element强制转换成LinkedElement类型实例e
  16. final LinkedElement e = (LinkedElement)element;
  17. //find index
  18. // 获取元素对应索引
  19. // 实际上是根据block的hashCode和hash_mask的一种循环取余算法
  20. // blockID是一个递增的序列,它在数组内的index也是在数组长度范围内递增的
  21. final int index = getIndex(element);
  22. //remove if it already exists
  23. // 如果元素已经存在的话,移除
  24. final E existing = remove(index, element);
  25. //insert the element to the head of the linked list
  26. // 将元素element插入到linked列表的头部
  27. // 累加modification、size
  28. modification++;
  29. size++;
  30. // 元素设置
  31. // 将e的next元素设置为数组当前index位置的元素
  32. e.setNext(entries[index]);
  33. // 将e的next元素设置为数组当前index位置的元素
  34. entries[index] = e;
  35. // 返回之前存储的元素existing
  36. return existing;
  37. }

put()方法实现了LightWeightGSet存数据的功能,它接收一个E泛型element作为参数,实现逻辑如下:

1、首先校验参数,即需要存储的元素element,它必须满足不能为null和必须实现LinkedElement接口两个限制条件;

2、然后将元素element强制转换成LinkedElement类型实例e;

3、调用getIndex()方法根据元素element获取它在数组entries中对应的位置索引index;

4、如果元素存在的话,调用remove()方法,根据位置索引index和元素remove进行移除操作,并得到有可能之前存储的元素existing;

5、累加modification、size:

modification代表了数据修改量,无论增加还是删除,均会累加;

size代表了集合元素个数,增加元素时即累加,删除元素时即累减;

6、进行元素设置:

6.1、将e的next元素设置为数组当前index位置的元素,可能为null,也可能为之前存在的不等元素,但肯定不是和需要添加元素相等的元素,因为如果存在,上面就已经删除了;

6.2、将当前元素e设置为数组当前index位置的元素;

7、将当前元素e设置为数组当前index位置的元素。
        通过上述添加元素过程的逻辑介绍,你是不是能体会到以下这点呢:

LightWeightGSet在内存中本质上是一个数组entries,用于存储实现了LinkedElement接口的元素。当添加元素element时,我们能够根据待添加元素element计算出它在数组entries中的位置索引index,然后根据位置索引index和元素element删除之前可能存在的相等元素,然后再进行元素设置,将数组entries中当前位置索引index处的元素设置为待添加元素element的next元素,而将待添加元素element放置到数组entries中的位置索引index处。

看到这里,你是不是恍然大悟,是不是能感受到LightWeightGSet中的数组中每个位置存储的好像是一个列表,而不是单一的一个元素?如果你能体会到这点,你就能开始领会到LightWeightGSet的真谛了。而且,我们已经能够开始回答上面我们遗留的使用一个数组array存储元素,使用linked lists来解决冲突这个疑问了。

待会总结,继续往下看。

我们来看看上述put过程中,如何通过getIndex()方法根据元素element获取它在数组entries中对应的位置索引index,代码如下:

  1. private int getIndex(final K key) {
  2. return key.hashCode() & hash_mask;
  3. }

我们知道,LightWeightGSet中存储的元素都是实现了LightWeightGSet.LinkedElement接口的对象,实际上也就是BlockInfo对象(别问我怎么知道的是BlockInfo,你去看看BlocksMap中对LightWeightGSet实例的应用你也能知道),而这个getIndex()方法的入参正式集合元素BlockInfo对象,我们看下BlockInfo的hashCode()方法,代码如下:

  1. @Override
  2. public int hashCode() {
  3. // Super implementation is sufficient
  4. return super.hashCode();
  5. }

直接调用父类的hashCode()方法,也就是Block的hashCode()方法,代码如下:

  1. @Override // Object
  2. public int hashCode() {
  3. //GenerationStamp is IRRELEVANT and should not be used here
  4. return (int)(blockId^(blockId>>>32));
  5. }

很简单,对于其long类型成员变量blockId的位操作而已,那么这个blockId一般都是什么呢?其实就是一个long类型的起始自1024L * 1024 * 1024 + 1的递增数字而已,具体介绍请参考《HDFS源码分析blockId生成分析》一文。

针对上述getIndex()方法,我们做个简单的测试,代码如下:

  1. @Test
  2. public void testGetIndexAndPrint(){
  3. // 起始数据块ID
  4. long LAST_RESERVED_BLOCK_ID = 1024L * 1024 * 1024 + 1;
  5. // 运算因子
  6. // 至于运算因子为什么选用1023,我们后续介绍,这里你只要知道它是数组长度减一就行
  7. int hash_mask = 1023;
  8. // 循环递增生成blockId(供生成1024+100个),并利用getIndex()方法的等价运算逻辑进行运算
  9. for (long blockId = LAST_RESERVED_BLOCK_ID; blockId < LAST_RESERVED_BLOCK_ID + 1024 + 100; blockId++) {
  10. // 计算hashCode
  11. int hashCode = (int) (blockId ^ (blockId >>> 32));
  12. // 计算index
  13. int index = hashCode & hash_mask;
  14. System.out.println("blockId=" + blockId + ";hashCode=" + hashCode
  15. + ";hash_mask=" + hash_mask + ";index=" + index);
  16. }
  17. }

这里,首先需要说明一点,至于运算因子为什么选用1023,我们后续介绍,这里你只要知道它是LightWeightGSet中数组entries长度减一就行。我们看下运行结果:

  1. blockId=1073741825;hashCode=1073741825;hash_mask=1023;index=1
  2. blockId=1073741826;hashCode=1073741826;hash_mask=1023;index=2
  3. blockId=1073741827;hashCode=1073741827;hash_mask=1023;index=3
  4. blockId=1073741828;hashCode=1073741828;hash_mask=1023;index=4
  5. blockId=1073741829;hashCode=1073741829;hash_mask=1023;index=5
  6. blockId=1073741830;hashCode=1073741830;hash_mask=1023;index=6
  7. blockId=1073741831;hashCode=1073741831;hash_mask=1023;index=7
  8. blockId=1073741832;hashCode=1073741832;hash_mask=1023;index=8
  9. blockId=1073741833;hashCode=1073741833;hash_mask=1023;index=9
  10. blockId=1073741834;hashCode=1073741834;hash_mask=1023;index=10
  11. blockId=1073741835;hashCode=1073741835;hash_mask=1023;index=11
  12. ...
  13. 省略中间连续输出结果
  14. ...
  15. blockId=1073742844;hashCode=1073742844;hash_mask=1023;index=1020
  16. blockId=1073742845;hashCode=1073742845;hash_mask=1023;index=1021
  17. blockId=1073742846;hashCode=1073742846;hash_mask=1023;index=1022
  18. blockId=1073742847;hashCode=1073742847;hash_mask=1023;index=1023
  19. blockId=1073742848;hashCode=1073742848;hash_mask=1023;index=0
  20. blockId=1073742849;hashCode=1073742849;hash_mask=1023;index=1
  21. blockId=1073742850;hashCode=1073742850;hash_mask=1023;index=2
  22. blockId=1073742851;hashCode=1073742851;hash_mask=1023;index=3
  23. blockId=1073742852;hashCode=1073742852;hash_mask=1023;index=4
  24. blockId=1073742853;hashCode=1073742853;hash_mask=1023;index=5
  25. ...
  26. 省略中间连续输出结果
  27. ...
  28. blockId=1073742942;hashCode=1073742942;hash_mask=1023;index=94
  29. blockId=1073742943;hashCode=1073742943;hash_mask=1023;index=95
  30. blockId=1073742944;hashCode=1073742944;hash_mask=1023;index=96
  31. blockId=1073742945;hashCode=1073742945;hash_mask=1023;index=97
  32. blockId=1073742946;hashCode=1073742946;hash_mask=1023;index=98
  33. blockId=1073742947;hashCode=1073742947;hash_mask=1023;index=99
  34. blockId=1073742948;hashCode=1073742948;hash_mask=1023;index=100

可以看到,如果递增的数据块ID在数组内的位置索引index是从头至尾递增并循环的,这类似于循环取余操作。所以,数组内每个位置的元素肯定不止一个,而且,理论上是完全完整的连续存储的,仅仅是理论上哦,毕竟数据块申请后有可能放弃或者损坏的数据块被检测,导致实际的存储并不完全完整的连续。

到了这里,你应该对LightWeightGSet的存储有了更深一步的了解了吧!

下面,我们再看下添加元素时需要用到的重复元素删除remove()方法,它返回被删除的元素,没有元素可删除则返回null,代码如下:

  1. /**
  2. * Remove the element corresponding to the key,
  3. * given key.hashCode() == index.
  4. *
  5. * @return If such element exists, return it.
  6. *         Otherwise, return null.
  7. */
  8. private E remove(final int index, final K key) {
  9. // 如果entries数组index处的元素为null,直接返回null
  10. if (entries[index] == null) {
  11. return null;
  12. } else if (entries[index].equals(key)) {
  13. // 如果entries数组index处的元素等于key
  14. //remove the head of the linked list
  15. // modification累加
  16. modification++;
  17. // 元素个数减一
  18. size--;
  19. // 取出entries数组index处的元素e
  20. final LinkedElement e = entries[index];
  21. // 将entries数组index处的元素替换为e的next元素
  22. entries[index] = e.getNext();
  23. // e的next元素设置为null
  24. e.setNext(null);
  25. // 将e转换下并返回
  26. return convert(e);
  27. } else {
  28. //head != null and key is not equal to head
  29. //search the element
  30. // 如果列表头部head不为null,且不等于需要删除的key
  31. // 遍历列表元素,直到找到需要删除的key或者遍历完列表全部元素
  32. // 取出列表头部元素prev
  33. LinkedElement prev = entries[index];
  34. // 遍历prev的后续元素curr
  35. for(LinkedElement curr = prev.getNext(); curr != null; ) {
  36. if (curr.equals(key)) {// 如果curr等于key,说明我们已经找到元素,移除它
  37. //found the element, remove it
  38. // 修改数目累加
  39. modification++;
  40. // 元素个数累减
  41. size--;
  42. // 上一个元素prev的next指向当前元素curr的next,即砍掉当前元素
  43. prev.setNext(curr.getNext());
  44. // 当前元素curr的next设置为null
  45. curr.setNext(null);
  46. // 将当前元素curr转换并返回
  47. return convert(curr);
  48. } else {
  49. // 没找到的话,上一个元素prev赋值为当前元素curr,当前元素curr取下一个元素next
  50. prev = curr;
  51. curr = curr.getNext();
  52. }
  53. }
  54. //element not found
  55. // 都没找到的话返回null
  56. return null;
  57. }
  58. }

了解了上面的put过程,及LightWeightGSet的存储原理,相信你应该能看懂remove()方法的逻辑。为了加深理解,我们这里再简单概括下,它分123三种情况,大体逻辑如下:

1、如果entries数组index处的元素为null,直接返回null;

2、如果entries数组index处的元素等于key,即待添加或者其他的指定元素,则:

2.1、修改量modification累加,元素个数size累减;

2.2、取出entries数组index处的元素e;

2.3、将entries数组index处的元素替换为e的next元素;

2.4、e的next元素设置为null;

2.5、将e转换下并返回;

3、如果entries数组index处的元素不等于key,即待添加或者其他的指定元素,则遍历列表元素,直到找到需要删除的key或者遍历完列表全部元素:

3.1、取出列表头部元素prev;

3.2、遍历prev的后续元素curr:

3.2.1、如果curr等于key,说明我们已经找到元素,移除它:

3.2.1.1、修改量modification累加,元素个数size累减;

3.2.1.2、上一个元素prev的next指向当前元素curr的next,即砍掉当前元素;

3.2.1.3、当前元素curr的next设置为null;

3.2.1.4、将当前元素curr转换并返回;

3.2.2、没找到的话,上一个元素prev赋值为当前元素curr,当前元素curr取下一个元素next;

4、都没找到的话返回null。
        上面,元素的添加、移除都讲到了,下面我们看下元素的获取get()方法,代码如下:

  1. @Override
  2. public E get(final K key) {
  3. //validate key
  4. / 校验key:key不能为null
  5. if (key == null) {
  6. throw new NullPointerException("key == null");
  7. }
  8. //find element
  9. // 寻找元素
  10. // 根据key,获取索引index
  11. final int index = getIndex(key);
  12. // 取出数组entries中index位置的元素,当e不为null时,判断e是否等于key,如果相等,convert转换下,如果不相等,通过循环e的getNext()遍历后续元素,重复上述判断
  13. for(LinkedElement e = entries[index]; e != null; e = e.getNext()) {
  14. if (e.equals(key)) {
  15. return convert(e);
  16. }
  17. }
  18. //element not found
  19. // 没有找到元素的话,返回null
  20. return null;
  21. }

十分简单,具体如下:

1、先校验key:key不能为null;

2、根据key,获取索引index;

3、取出数组entries中index位置的元素,当e不为null时,判断e是否等于key,如果相等,convert转换下,如果不相等,通过循环e的getNext()遍历后续元素,重复上述判断;

4、没有找到元素的话,返回null。

既然LightWeightGSet本质上是一个数组,那么数组在内存中应该是固定大小的,这个固定的大小是如何确定的呢?我们先看下LightWeightGSet的构造方法,如下:

  1. /**
  2. * @param recommended_length Recommended size of the internal array.
  3. */
  4. public LightWeightGSet(final int recommended_length) {
  5. // 根据推荐数组长度recommended_length计算实际数组长度actual
  6. final int actual = actualArrayLength(recommended_length);
  7. if (LOG.isDebugEnabled()) {
  8. LOG.debug("recommended=" + recommended_length + ", actual=" + actual);
  9. }
  10. // 初始化entries为指定大小actual的LinkedElement数组
  11. entries = new LinkedElement[actual];
  12. // hash_mask默认为entries数组大小减1
  13. hash_mask = entries.length - 1;
  14. }

构造方法需要一个参数recommended_length,即推荐的数组长度,并且,我们需要根据根据推荐数组长度recommended_length计算实际数组长度actual,然后初始化entries为指定大小actual的LinkedElement数组,而hash_mask默认为entries数组大小减1,至于为什么这么做,相信看过上面的介绍你应该能找到答案吧!

我们看下actualArrayLength()方法,代码如下:

  1. //compute actual length
  2. // 根据推荐长度recommended和最大最小阈值计算实际长度actual
  3. private static int actualArrayLength(int recommended) {
  4. f (recommended > MAX_ARRAY_LENGTH) {// 如果推荐长度recommended超过最大长度,则实际长度actual取值最大长度
  5. return MAX_ARRAY_LENGTH;
  6. } else if (recommended < MIN_ARRAY_LENGTH) {// 如果推荐长度recommended低于最小长度,则实际长度actual取值最小长度
  7. return MIN_ARRAY_LENGTH;
  8. } else {
  9. // 推荐长度在最大最小阈值范围内的话,返回大于等于recommended的最近的2的n次幂
  10. // 确保数组长度为2的n次幂
  11. final int a = Integer.highestOneBit(recommended);
  12. return a == recommended? a: a << 1;
  13. }
  14. }

实际上很简单,确保数组真实长度在阈值上限MAX_ARRAY_LENGTH和下限MIN_ARRAY_LENGTH之前,并且推荐长度在最大最小阈值范围内的话,返回大于等于recommended的最近的2的n次幂,确保数组长度为2的n次幂。

这个数组长度阈值上下限的定义如下:

  1. // array最大大小为2的30次方,即1073741824
  2. static final int MAX_ARRAY_LENGTH = 1 << 30; //prevent int overflow problem
  3. // array最小大小为1
  4. static final int MIN_ARRAY_LENGTH = 1;

那么,构造LightWeightGSet时,这个推荐长度如何定义呢?这个需要看下BlockManager中对blocksMap的初始化,如下:

  1. // Compute the map capacity by allocating 2% of total memory
  2. blocksMap = new BlocksMap(
  3. LightWeightGSet.computeCapacity(2.0, "BlocksMap"));

它是通过总内存大小的2%来分配的,调用了LightWeightGSet的computeCapacity()方法来计算,代码如下:

  1. /**
  2. * Let t = percentage of max memory.
  3. * Let e = round(log_2 t).
  4. * Then, we choose capacity = 2^e/(size of reference),
  5. * unless it is outside the close interval [1, 2^30].
  6. */
  7. public static int computeCapacity(double percentage, String mapName) {
  8. return computeCapacity(Runtime.getRuntime().maxMemory(), percentage,
  9. mapName);
  10. }

通过Runtime.getRuntime().maxMemory()获取总内存大小,然后传入百分比percentage和使用这些内存的map名称mapName,调用三个参数的computeCapacity()方法,如下:

  1. @VisibleForTesting
  2. static int computeCapacity(long maxMemory, double percentage,
  3. String mapName) {
  4. / 校验内存百分比percentage的合法性,必须在[0-1]之间
  5. if (percentage > 100.0 || percentage < 0.0) {
  6. throw new HadoopIllegalArgumentException("Percentage " + percentage
  7. + " must be greater than or equal to 0 "
  8. + " and less than or equal to 100");
  9. }
  10. // 校验总内存大小maxMemory的合法性,必须大于等于0
  11. if (maxMemory < 0) {
  12. throw new HadoopIllegalArgumentException("Memory " + maxMemory
  13. + " must be greater than or equal to 0");
  14. }
  15. // 如果内存百分比percentage、总内存大小maxMemory其中任一为0,直接返回0
  16. if (percentage == 0.0 || maxMemory == 0) {
  17. return 0;
  18. }
  19. //VM detection
  20. //See http://java.sun.com/docs/hotspot/HotSpotFAQ.html#64bit_detection
  21. // 机器是否为32位
  22. final String vmBit = System.getProperty("sun.arch.data.model");
  23. //Percentage of max memory
  24. // 百分比因子percentDivisor
  25. final double percentDivisor = 100.0/percentage;
  26. // 需要使用的内存percentMemory,实际上就是maxMemory*percentage/100
  27. final double percentMemory = maxMemory/percentDivisor;
  28. //compute capacity
  29. // 计算容量
  30. final int e1 = (int)(Math.log(percentMemory)/Math.log(2.0) + 0.5);
  31. final int e2 = e1 - ("32".equals(vmBit)? 2: 3);
  32. final int exponent = e2 < 0? 0: e2 > 30? 30: e2;
  33. final int c = 1 << exponent;
  34. LOG.info("Computing capacity for map " + mapName);
  35. LOG.info("VM type       = " + vmBit + "-bit");
  36. LOG.info(percentage + "% max memory "
  37. + StringUtils.TraditionalBinaryPrefix.long2String(maxMemory, "B", 1)
  38. + " = "
  39. + StringUtils.TraditionalBinaryPrefix.long2String((long) percentMemory,
  40. "B", 1));
  41. LOG.info("capacity      = 2^" + exponent + " = " + c + " entries");
  42. return c;
  43. }

方法很简单,读者可自行分析。

我们回到最初关于LightWeightGSet的一些介绍,它是一个占用较低内存的集合的实现,使用一个数组array存储元素,使用linked lists来解决冲突。它没有实现重新哈希分区,所以,内部的array不会改变大小。这个类不支持null元素,并且不是线程安全的。

现在再来看上面这段话,怎么解释它们,相信你心中应该有些答案了吧!这里,我们还是一起来分析下:

首先,我们要知道数组和链表的异同及各自的优缺点,如下:

从逻辑结构来看

1、数组必须事先确定固定长度,它不能适应数据动态增减情况的变化,即不能存储超过固定长度的元素,如果存储的元素没有达到固定长度,又会造成资源的浪费,但是数组可以根据下标直接存取;

2、链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。它必须通过next指针找到下一个元素。

从内存存储来看

1. (静态)数组从栈中分配空间, 对于程序员方便快速,但是自由度小;

2、链表从堆中分配空间, 自由度大但是申请管理比较麻烦。

从上面的比较可以看出,如果需要快速访问数据,很少或不插入和删除元素,就应该用数组;相反, 如果需要经常插入和删除元素就需要用链表数据结构了。

那么LightWeightGSet使用它这种数据加链表的存储结构,有什么好处呢?

首先,使用数组,可以很方便的申请内存,且占用内存比较低,考虑了初始内存使用的感受,检索比较快;

其次,使用链表,可以适应数据动态增减的变化,但是检索性能肯定不如数组;

然后,将二者融合,即照顾了内存申请等的物理需要,又考虑到了数据动态增减的逻辑业务需要;

最后,先定位数组索引,再遍历链表元素,可以大大改善只使用链表数据检索的性能;

综上,LightWeightGSet是一种将数组、链表融合的非常好的折中方案,很值得我们以后在自己的系统内学习借鉴。

总结:

LightWeightGSet是名字节点NameNode在内存中存储全部数据块信息的类BlocksMap需要的一个重要数据结构,它是一个占用较低内存的集合的实现,它使用一个数组存储元素,数组中存储的元素实际上是一个链表,这样,综合利用了数组、链表各自在内存申请、动态扩展、检索等方面的优势,取长补短、相互促进。它利用long类型的blockId,采用一定的高效的哈希映射算法来定位元素在数组中的位置,并将其添加到列表头部,删除与查询亦是类似定位过程,先确定数组位置,然后遍历列表,做查询或删除操作。

HDFS源码分析之LightWeightGSet的更多相关文章

  1. HDFS源码分析之UnderReplicatedBlocks(一)

    http://blog.csdn.net/lipeng_bigdata/article/details/51160359 UnderReplicatedBlocks是HDFS中关于块复制的一个重要数据 ...

  2. HDFS源码分析数据块校验之DataBlockScanner

    DataBlockScanner是运行在数据节点DataNode上的一个后台线程.它为所有的块池管理块扫描.针对每个块池,一个BlockPoolSliceScanner对象将会被创建,其运行在一个单独 ...

  3. HDFS源码分析数据块复制监控线程ReplicationMonitor(二)

    HDFS源码分析数据块复制监控线程ReplicationMonitor(二)

  4. HDFS源码分析数据块复制监控线程ReplicationMonitor(一)

    ReplicationMonitor是HDFS中关于数据块复制的监控线程,它的主要作用就是计算DataNode工作,并将复制请求超时的块重新加入到待调度队列.其定义及作为线程核心的run()方法如下: ...

  5. HDFS源码分析之UnderReplicatedBlocks(二)

    UnderReplicatedBlocks还提供了一个数据块迭代器BlockIterator,用于遍历其中的数据块.它是UnderReplicatedBlocks的内部类,有三个成员变量,如下: // ...

  6. HDFS源码分析数据块汇报之损坏数据块检测checkReplicaCorrupt()

    无论是第一次,还是之后的每次数据块汇报,名字名字节点都会对汇报上来的数据块进行检测,看看其是否为损坏的数据块.那么,损坏数据块是如何被检测的呢?本文,我们将研究下损坏数据块检测的checkReplic ...

  7. HDFS源码分析之数据块及副本状态BlockUCState、ReplicaState

    关于数据块.副本的介绍,请参考文章<HDFS源码分析之数据块Block.副本Replica>. 一.数据块状态BlockUCState 数据块状态用枚举类BlockUCState来表示,代 ...

  8. HDFS源码分析EditLog之获取编辑日志输入流

    在<HDFS源码分析之EditLogTailer>一文中,我们详细了解了编辑日志跟踪器EditLogTailer的实现,介绍了其内部编辑日志追踪线程EditLogTailerThread的 ...

  9. HDFS源码分析EditLog之读取操作符

    在<HDFS源码分析EditLog之获取编辑日志输入流>一文中,我们详细了解了如何获取编辑日志输入流EditLogInputStream.在我们得到编辑日志输入流后,是不是就该从输入流中获 ...

随机推荐

  1. html执行.NET函数 html操作数据库 html与ashx结合

    原文发布时间为:2009-09-30 -- 来源于本人的百度文章 [由搬家工具导入] html页面执行.NET函数 html与ashx的结合 1、添加一般应用程序Handler.ashx <%@ ...

  2. 微信公众平台开发(71)OAuth2.0网页授权-摘抄

      微信公众平台开发 OAuth2.0网页授权认证 网页授权获取用户基本信息 作者:方倍工作室 微信公众平台最近新推出微信认证,认证后可以获得高级接口权限,其中一个是OAuth2.0网页授权,很多朋友 ...

  3. 用Python和Pygame写游戏-从入门到精通(py2exe篇)

    这次不是直接讲解下去,而是谈一下如何把我们写的游戏做成一个exe文件,这样一来,用户不需要安装python就可以玩了.扫清了游戏发布一大障碍啊! perl,python,java等编程语言,非常好用, ...

  4. LeetCode OJ--Multiply Strings **

    https://oj.leetcode.com/problems/multiply-strings/ 用字符串实现大数乘法,细节题,细节很多 class Solution { public: stri ...

  5. ECNU 3480 没用的函数 (ST表预处理 + GCD性质)

    题目链接  ECNU 2018 JAN Problem E 这题卡了双$log$的做法 令$gcd(a_{i}, a_{i+1}, a_{i+2}, ..., a_{j}) = calc(i, j)$ ...

  6. 洛谷——P2040 打开所有的灯

    P2040 打开所有的灯 题目背景 pmshz在玩一个益(ruo)智(zhi)的小游戏,目的是打开九盏灯所有的灯,这样的游戏难倒了pmshz... 题目描述 这个灯很奇(fan)怪(ren),点一下就 ...

  7. Sharing Cookies --AtCoder

    题目描述 Snuke is giving cookies to his three goats.He has two cookie tins. One contains A cookies, and ...

  8. Linked List Cycle - LeetCode

    Given a linked list, determine if it has a cycle in it. Follow up:Can you solve it without using ext ...

  9. require_once(): Failed opening required '/var/www/config/config.php' (include_path='.:') in /var/www/vendor/forkiss/pharest/src/Pharest/Register/Register.php on line 10

    环境 docker环境 错误 [Tue Jun 18 18:43:26 2019] 127.0.0.1:53980 [500]: /index.php - require_once(): Failed ...

  10. 基于WPF系统框架设计(1)-为什么要仿Office2010 Ribbon?

    为什么系统框架设计使用Ribbon导航模式? 这得从Office软件的演变说起.微软为什么最后选择使用Ribbon,也许就是很多系统设计要使用Ribbon做功能导航的原因. 你是否还记得曾经使用过的M ...