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. 【CodeChef】PARADE(费用流,最短路)

    题意: 思路: #include<cstdio> #include<iostream> #include<algorithm> #include<cstrin ...

  2. food(洛谷P4040 [AHOI2014/JSOI2014]宅男计划)

    题目在这里 题目描述 外卖店一共有N种食物,分别有1到N编号.第i种食物有固定的价钱Pi和保质期Si.第i种食物会在Si天后过期.JYY是不会吃过期食物的. 比如JYY如果今天点了一份保质期为1天的食 ...

  3. mysql查询练习题

    建库建表 a.建立一个公司数据库(gongsi)    CREATE DATABASE gongsi b.建立一张部门表(部门编号b_id,部门名称b_name) ,             其中b_ ...

  4. HDU 6251 Inkopolis(2017 CCPC-Final,I题,环套树 + 结论)

    题目链接 HDU 6251 题意 给出一个$N$个点$N$条边的无向图.然后给出$M$个操作,每个操作为$(x, y, z)$,表示把连接 $x$和$y$的边的颜色改成$z$. 求这张无向图中所有边的 ...

  5. luogu P1941 飞扬的小鸟

    题目描述 Flappy Bird 是一款风靡一时的休闲手机游戏.玩家需要不断控制点击手机屏幕的频率来调节小鸟的飞行高度,让小鸟顺利通过画面右方的管道缝隙.如果小鸟一不小心撞到了水管或者掉在地上的话,便 ...

  6. Android Studio 中.android 文件夹移动默认位置

    转自 开发工具打造: .android 文件夹移动默认位置 .android 文件夹是用来存放 avd 模拟器文件的文件夹. 因为默认是 C盘 的. 占用空间比较大.很不爽 将它移动到其它盘其实很简单 ...

  7. Android Spinner In Toolbar

    As the title of the post suggest in this tutorial we will see how to have spinner widget inside the ...

  8. 如何部署和运行Scut服务器及游戏:Windows篇

    概述 Scut游戏引擎是一个永久免费的全脚本游戏服务器框架,采用MVC框架设计,简化数据库设计和编码工作:降低对开发人员的开发难度:同时提供了丰富的类库和API接口. 一.    安装环境 必须安装的 ...

  9. win7安装mysql解压缩版

    http://blog.csdn.net/u013067166/article/details/49951577

  10. 使用ssh从外网访问内网

    一.场景如下: 各个角色的对应关系如下: 角色 描述 APP 个人笔记本,属于内网IP sshd server 公网 VPS ( 映射端口: port 2222 ),拥有公网IP ssh client ...