Làm thế nào để các bản ghi xử lý Hadoop phân chia theo ranh giới khối?


119

Theo Hadoop - The Definitive Guide

Các bản ghi logic mà FileInputFormats xác định thường không nằm gọn trong các khối HDFS. Ví dụ, các bản ghi logic của TextInputFormat là các dòng, sẽ vượt qua ranh giới HDFS thường xuyên hơn không. Điều này không liên quan đến hoạt động của chương trình của bạn — chẳng hạn như các dòng không bị bỏ sót hoặc bị hỏng — nhưng điều đáng biết là nó có nghĩa là bản đồ cục bộ dữ liệu (nghĩa là, bản đồ đang chạy trên cùng một máy chủ lưu trữ dữ liệu đầu vào) sẽ thực hiện một số lần đọc từ xa. Chi phí nhẹ mà nguyên nhân này thường không đáng kể.

Giả sử một dòng bản ghi được chia thành hai khối (b1 và b2). Trình ánh xạ xử lý khối đầu tiên (b1) sẽ nhận thấy rằng dòng cuối cùng không có dấu phân tách EOL và tìm nạp phần còn lại của dòng từ khối dữ liệu tiếp theo (b2).

Làm thế nào để trình ánh xạ xử lý khối thứ hai (b2) xác định rằng bản ghi đầu tiên là không đầy đủ và nên xử lý bắt đầu từ bản ghi thứ hai trong khối (b2)?

Câu trả lời:


160

Câu hỏi thú vị, tôi đã dành thời gian xem mã để biết chi tiết và đây là suy nghĩ của tôi. Các phần tách được xử lý bởi máy khách InputFormat.getSplits, vì vậy hãy xem FileInputFormat sẽ cung cấp thông tin sau:

  • Đối với mỗi tập tin đầu vào, có chiều dài tập tin, kích thước khối và tính toán kích thước phân chia như max(minSize, min(maxSize, blockSize))nơi maxSizetương ứng với mapred.max.split.sizeminSizemapred.min.split.size.
  • Chia tệp thành các FileSplits khác nhau dựa trên kích thước phân chia được tính toán ở trên. Điều quan trọng ở đây là mỗi cái FileSplitđược khởi tạo với một starttham số tương ứng với phần bù trong tệp đầu vào . Vẫn không có xử lý các dòng tại thời điểm đó. Phần có liên quan của mã trông giống như sau:

    while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
      int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
      splits.add(new FileSplit(path, length-bytesRemaining, splitSize, 
                               blkLocations[blkIndex].getHosts()));
      bytesRemaining -= splitSize;
    }
    

Sau đó, nếu bạn nhìn vào LineRecordReadercái được xác định bởi dấu TextInputFormat, đó là nơi các dòng được xử lý:

  • Khi bạn khởi tạo, LineRecordReadernó sẽ cố gắng khởi tạo a LineReaderlà một phần trừu tượng để có thể đọc các dòng FSDataInputStream. Có 2 trường hợp:
  • Nếu có một CompressionCodecđịnh nghĩa, thì codec này chịu trách nhiệm xử lý các ranh giới. Có thể không liên quan đến câu hỏi của bạn.
  • Tuy nhiên, nếu không có codec, đó là nơi mọi thứ thú vị: nếu startcủa bạn InputSplitkhác 0, thì bạn lùi lại 1 ký tự rồi bỏ qua dòng đầu tiên mà bạn gặp phải được xác định bởi \ n hoặc \ r \ n (Windows) ! Đường nền rất quan trọng vì trong trường hợp đường ranh giới của bạn giống với đường ranh giới phân chia, điều này đảm bảo bạn không bỏ qua đường hợp lệ. Đây là mã liên quan:

    if (codec != null) {
       in = new LineReader(codec.createInputStream(fileIn), job);
       end = Long.MAX_VALUE;
    } else {
       if (start != 0) {
         skipFirstLine = true;
         --start;
         fileIn.seek(start);
       }
       in = new LineReader(fileIn, job);
    }
    if (skipFirstLine) {  // skip first line and re-establish "start".
      start += in.readLine(new Text(), 0,
                        (int)Math.min((long)Integer.MAX_VALUE, end - start));
    }
    this.pos = start;
    

Vì vậy, vì các phần tách được tính toán trong máy khách, các trình ánh xạ không cần phải chạy theo trình tự, mọi trình ánh xạ đã biết liệu nó có cần loại bỏ dòng đầu tiên hay không.

Vì vậy, về cơ bản nếu bạn có 2 dòng, mỗi dòng 100Mb trong cùng một tệp và để đơn giản hóa, hãy giả sử kích thước phân chia là 64Mb. Sau đó, khi các phân chia đầu vào được tính toán, chúng ta sẽ có tình huống sau:

  • Tách 1 chứa đường dẫn và các máy chủ đến khối này. Khởi tạo lúc đầu 200-200 = 0Mb, chiều dài 64Mb.
  • Chia 2 khởi tạo lúc đầu 200-200 + 64 = 64Mb, độ dài 64Mb.
  • Chia 3 khởi tạo lúc đầu 200-200 + 128 = 128Mb, độ dài 64Mb.
  • Chia 4 được khởi tạo lúc đầu 200-200 + 192 = 192Mb, độ dài 8Mb.
  • Người lập bản đồ A sẽ xử lý phân tách 1, bắt đầu là 0 vì vậy đừng bỏ qua dòng đầu tiên và đọc toàn bộ dòng vượt quá giới hạn 64Mb nên cần đọc từ xa.
  • Người lập bản đồ B sẽ xử lý phần tách 2, bắt đầu là! = 0 vì vậy hãy bỏ qua dòng đầu tiên sau 64Mb-1byte, tương ứng với phần cuối của dòng 1 là 100Mb vẫn ở phần chia 2, chúng ta có 28Mb dòng trong phần chia 2, vì vậy từ xa đọc 72Mb còn lại.
  • Mapper C sẽ xử lý phân tách 3, bắt đầu là! = 0 vì vậy hãy bỏ qua dòng đầu tiên sau 128Mb-1byte, tương ứng với cuối dòng 2 là 200Mb, là phần cuối của tệp nên đừng làm gì cả.
  • Người lập bản đồ D cũng giống như người lập bản đồ C ngoại trừ nó tìm kiếm một dòng mới sau 192Mb-1byte.

Ngoài ra @PraveenSripati, điều đáng nói là các trường hợp cạnh mà ranh giới sẽ ở \ r trong \ r \ n trả về được xử lý trong LineReader.readLinehàm, tôi không nghĩ nó có liên quan đến câu hỏi của bạn nhưng có thể thêm các chi tiết khác nếu cần.
Charles Menguy

Giả sử rằng có hai dòng với 64MB chính xác trong đầu vào và do đó, các InputSplits xảy ra chính xác ở ranh giới dòng. Vì vậy, sẽ mapper luôn bỏ qua các dòng trong khối thứ hai vì start = 0.!
Praveen Sripati

6
@PraveenSripati Trong trường hợp đó, trình ánh xạ thứ hai sẽ thấy start! = 0, vì vậy hãy quay lại 1 ký tự, đưa bạn trở lại ngay trước \ n của dòng đầu tiên và sau đó bỏ qua đến phần sau \ n. Vì vậy, nó sẽ bỏ qua dòng đầu tiên nhưng xử lý dòng thứ hai như mong đợi.
Charles Menguy

@CharlesMenguy có thể dòng đầu tiên của tệp bị bỏ qua bằng cách nào đó không? Cụ thể, tôi có dòng đầu tiên với khóa = 1 và giá trị a, sau đó có hai dòng nữa có cùng khóa ở đâu đó trong tệp, khóa = 1, val = b và khóa = 1, val = c. Vấn đề là, bộ giảm tốc của tôi nhận được {1, [b, c]} và {1, [a]}, thay vì {1, [a, b, c]}. Điều này không xảy ra nếu tôi thêm dòng mới vào đầu tệp của mình. Lý do có thể là gì, thưa ông?
Kobe-Wan Kenobi

@CharlesMenguy Điều gì sẽ xảy ra nếu tệp trên HDFS là tệp nhị phân (trái ngược với tệp văn bản, trong đó \r\n, \nbiểu thị việc cắt bớt bản ghi)?
CᴴᴀZ

17

Thuật toán Map Reduce không hoạt động trên các khối vật lý của tệp. Nó hoạt động trên các phân chia đầu vào hợp lý. Sự phân chia đầu vào phụ thuộc vào nơi ghi bản ghi. Một bản ghi có thể kéo dài hai Người lập bản đồ.

Cách HDFS đã được thiết lập, nó chia nhỏ các tệp rất lớn thành các khối lớn (ví dụ: kích thước 128MB) và lưu trữ ba bản sao của các khối này trên các nút khác nhau trong cụm.

HDFS không có nhận thức về nội dung của các tệp này. Một bản ghi có thể đã được bắt đầu trong Block-a nhưng phần cuối của bản ghi đó có thể có trong Block-b .

Để giải quyết vấn đề này, Hadoop sử dụng biểu diễn logic của dữ liệu được lưu trữ trong các khối tệp, được gọi là phân tách đầu vào. Khi một ứng dụng khách MapReduce tính toán các phân chia đầu vào , nó sẽ tìm ra nơi bắt đầu của toàn bộ bản ghi đầu tiên trong một khối và nơi kết thúc của bản ghi cuối cùng trong khối .

Điểm then chốt :

Trong trường hợp bản ghi cuối cùng trong một khối không đầy đủ, phần tách đầu vào bao gồm thông tin vị trí cho khối tiếp theo và độ lệch byte của dữ liệu cần thiết để hoàn thành bản ghi.

Hãy xem sơ đồ dưới đây.

nhập mô tả hình ảnh ở đây

Hãy xem bài viết này và câu hỏi liên quan đến SE: Giới thiệu về tách tệp Hadoop / HDFS

Có thể đọc thêm chi tiết từ tài liệu

Khung bản đồ-Reduce dựa trên Định dạng đầu vào của công việc để:

  1. Xác thực đặc tả đầu vào của công việc.
  2. Tách (các) tệp đầu vào thành các InputSplits logic, mỗi tệp trong số đó được gán cho một Mapper riêng lẻ.
  3. Mỗi InputSplit sau đó được gán cho một Mapper riêng lẻ để xử lý. Sự phân chia có thể là nhiều lần . InputSplit[] getSplits(JobConf job,int numSplits) là API để xử lý những việc này.

FileInputFormat , mở rộng phương thức InputFormatimplement getSplits(). Hãy xem nội dung của phương pháp này tại grepcode


7

Tôi thấy nó như sau: InputFormat chịu trách nhiệm chia dữ liệu thành các phân chia hợp lý có tính đến bản chất của dữ liệu.
Không có gì ngăn cản nó làm như vậy, mặc dù nó có thể thêm độ trễ đáng kể cho công việc - tất cả logic và việc đọc xung quanh ranh giới kích thước phân chia mong muốn sẽ xảy ra trong trình theo dõi công việc.
Định dạng đầu vào nhận biết bản ghi đơn giản nhất là TextInputFormat. Nó đang hoạt động như sau (theo như tôi hiểu từ mã) - định dạng đầu vào tạo ra các phần chia theo kích thước, bất kể dòng nào, nhưng LineRecordReader luôn luôn:
a) Bỏ qua dòng đầu tiên trong phần tách (hoặc một phần của nó), nếu nó không lần tách đầu tiên
b) Đọc một dòng sau ranh giới của lần tách cuối cùng (nếu dữ liệu có sẵn, vì vậy nó không phải là lần chia cuối cùng).


Skip first line in the split (or part of it), if it is not the first split- nếu bản ghi đầu tiên trong khối không phải khối đầu tiên là hoàn thành, thì không chắc chắn logic này sẽ hoạt động như thế nào.
Praveen Sripati

Theo như tôi thấy mã - mỗi phần tách đọc những gì nó có + dòng tiếp theo. Vì vậy, nếu ngắt dòng không nằm trên ranh giới khối - thì không sao. Làm thế nào chính xác xử lý trường hợp khi ngắt dòng là chính xác về khối ràng buộc - phải được hiểu - tôi sẽ đọc mã hơn một chút
David Gruzman

3

Từ những gì tôi đã hiểu, khi FileSplitđược khởi tạo cho khối đầu tiên, hàm tạo mặc định được gọi. Do đó, các giá trị cho bắt đầu và độ dài ban đầu bằng 0. Khi kết thúc quá trình xử lý khối nắm tay, nếu dòng cuối cùng chưa hoàn thành, thì giá trị độ dài sẽ lớn hơn độ dài của phần tách và nó cũng sẽ đọc dòng đầu tiên của khối tiếp theo. Do đó, giá trị bắt đầu của khối đầu tiên sẽ lớn hơn 0 và với điều kiện này, giá trị LineRecordReadersẽ bỏ qua dòng đầu tiên của khối thứ hai. (Xem nguồn )

Trong trường hợp dòng cuối cùng của khối đầu tiên hoàn thành, thì giá trị độ dài sẽ bằng độ dài của khối đầu tiên và giá trị bắt đầu của khối thứ hai sẽ bằng không. Trong trường hợp đó, LineRecordReadersẽ không bỏ qua dòng đầu tiên và đọc khối thứ hai từ đầu.

Có ý nghĩa?


2
Trong trường hợp này, những người lập bản đồ phải giao tiếp với nhau và xử lý các khối theo trình tự khi dòng cuối cùng trong một khối cụ thể không hoàn thành. Không chắc chắn nếu đây là cách nó hoạt động.
Praveen Sripati

1

Từ mã nguồn hadoop của LineRecordReader.java, hàm tạo: Tôi tìm thấy một số nhận xét:

// If this is not the first split, we always throw away first record
// because we always (except the last split) read one extra line in
// next() method.
if (start != 0) {
  start += in.readLine(new Text(), 0, maxBytesToConsume(start));
}
this.pos = start;

từ điều này, tôi tin rằng hadoop sẽ đọc thêm một dòng cho mỗi lần chia (ở cuối lần tách hiện tại, đọc dòng tiếp theo trong lần tách tiếp theo), và nếu không phải lần chia đầu tiên, dòng đầu tiên sẽ bị vứt bỏ. để không có bản ghi dòng nào bị mất và không đầy đủ


0

Người lập bản đồ không cần phải giao tiếp. Các khối tệp nằm trong HDFS và trình ánh xạ hiện tại (RecordReader) có thể đọc khối có phần còn lại của dòng. Điều này xảy ra ở hậu trường.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.