Hadoop về
Khái niệm Big Data
- Không thể xử lý dữ liệu bằng một máy tính duy nhất
- Trọng tâm của Big Data là mẫu = tổng thể
Đặc tính Big Data
- Lượng lớn (Volume): Trong Big Data, kích thước của một tệp tin riêng lẻ thường từ vài chục đến vài trăm GB trở lên
- Tốc độ (Velocity): Phản ánh tốc độ tạo ra dữ liệu và tần suất thay đổi dữ liệu
- Đa dạng (Variety): Dữ liệu có nhiều loại và nguồn khác nhau, có thể phân loại thành có cấu trúc (structured), bán cấu trúc (semi-structured), và không cấu trúc (unstructured)
- Biến động (Variability): Dữ liệu có đặc tính dao động, không ổn định, có thể xuất hiện các đỉnh cao theo chu kỳ hàng ngày, theo mùa, hoặc theo các sự kiện cụ thể
- Độ chính xác (Veracity): Chất lượng dữ liệu thu thập từ các nguồn khác nhau có thể khác nhau đáng kể, ảnh hưởng đến độ tin cậy của kết quả phân tích
- Phức tạp (Complexity): Khó khăn trong việc quản lý và thao tác dữ liệu, đặc biệt là việc trích xuất, chuyển đổi, tải, kết nối và liên kết để tìm thông tin hữu ích
Công nghệ chính
- Phân phối dữ liệu trên nhiều máy
- Độ tin cậy: Mỗi khối dữ liệu được sao chép đến nhiều nút
- Hiệu suất: Nhiều nút cùng xử lý dữ liệu
- Tính toán đi theo dữ liệu
- Tốc độ IO mạng << Tốc độ IO đĩa cục bộ, hệ thống Big Data sẽ cố gắng phân phối tác vụ đến máy gần dữ liệu nhất (khi chạy chương trình, sao chép chương trình và các gói phụ thuộc đến máy chứa dữ liệu)
- Di chuyển mã đến dữ liệu, tránh di chuyển lượng lớn dữ liệu, đảm bảo tính toán một đoạn dữ liệu xảy ra trên cùng một máy
- IO tuần tự thay thế IO ngẫu nhiên
Thời gian truyền << Thời gian tìm kiếm, dữ liệu thường không được sửa đổi sau khi ghi
Giới thiệu Hadoop
Khái niệm
Hadoop có thể chạy trên các máy tính thương mại, có tính容错 cao, độ tin cậy cao, khả năng mở rộng cao
Đặc biệt phù hợp với kịch bản ghi một lần, đọc nhiều lần
Kịch bản áp dụng
- Dữ liệu quy mô lớn
- Dữ liệu luồng (ghi một lần, đọc nhiều lần)
- Phần cứng thương mại (phần cứng thông thường)
Kịch bản không áp dụng
- Truy cập dữ liệu độ trễ thấp
- Nhiều tệp tin nhỏ
- Thường xuyên sửa đổi tệp tin (cơ bản là ghi 1 lần)
Kiến trúc Hadoop
- HDFS: Lưu trữ tệp tin phân tán
- YARN: Quản lý tài nguyên phân tán
- MapReduce: Tính toán phân tán
- Others: Sử dụng chức năng quản lý tài nguyên của YARN để thực hiện các phương thức xử lý dữ liệu khác
Các nút bên trong chủ yếu sử dụng kiến trúc Master-Worker
Hadoop HDFS
Hadoop Distributed File System, hệ thống tệp tin phân tán
Kiến trúc HDFS
- Khối dữ liệu (Block)
- Đơn vị lưu trữ cơ bản, thường có kích thước 128M, cấu hình khối lớn chủ yếu vì:
- Giảm thời gian tìm kiếm, tốc độ truyền dữ liệu của ổ đĩa thường nhanh hơn thời gian tìm kiếm, khối lớn có thể giảm thời gian tìm kiếm;
- Giảm chi phí quản lý khối dữ liệu, mỗi khối cần có một bản ghi tương ứng trên NameNode;
- Đọc và ghi khối dữ liệu, giảm chi phí kết nối mạng.
- Một tệp tin lớn sẽ được chia thành các khối, sau đó lưu trữ trên các máy khác nhau. Nếu một tệp tin nhỏ hơn kích thước khối, thì không gian thực tế chiếm dụng bằng kích thước tệp tin.
- Đơn vị đọc và ghi cơ bản, tương tự như trang đĩa, mỗi lần đọc và ghi một khối.
- Mỗi khối sẽ được sao chép đến nhiều máy, mặc định sao chép 3 bản.
- NameNode
- Lưu trữ metadata của tệp tin, tất cả dữ liệu đều được lưu trong bộ nhớ khi chạy, số lượng tệp tin có thể lưu trữ trên toàn bộ HDFS bị giới hạn bởi kích thước bộ nhớ của NameNode.
- Một Block trên NameNode tương ứng với một bản ghi (một Block thường chiếm 150 byte), nếu có nhiều tệp tin nhỏ, sẽ tiêu tốn nhiều bộ nhớ. Đồng thời, số lượng tác vụ map được xác định bởi splits, vì vậy khi sử dụng MapReduce để xử lý nhiều tệp tin nhỏ, sẽ tạo ra quá nhiều tác vụ map, chi phí quản lý luồng sẽ tăng thời gian thực hiện công việc. Tốc độ xử lý nhiều tệp tin nhỏ chậm hơn nhiều so với tốc độ xử lý tệp tin lớn có cùng kích thước. Do đó, Hadoop khuyến nghị lưu trữ tệp tin lớn.
- Dữ liệu được lưu định kỳ vào đĩa cục bộ, nhưng không lưu thông tin vị trí của Block, mà được báo cáo bởi DataNode khi đăng ký và được duy trì khi chạy (thông tin liên quan đến DataNode trên NameNode không được lưu vào hệ thống tệp tin của NameNode, mà NameNode tạo động sau mỗi lần khởi động).
- Nếu NameNode bị lỗi, toàn bộ HDFS sẽ bị lỗi, vì vậy cần đảm bảo tính khả dụng của NameNode.
- Secondary NameNode
Đồng bộ hóa định kỳ với NameNode (định kỳ hợp nhất ảnh hệ thống tệp tin và nhật ký chỉnh sửa, sau đó truyền kết quả hợp nhất cho NameNode, thay thế ảnh của nó và xóa nhật ký chỉnh sửa, tương tự như cơ chế CheckPoint), nhưng sau khi NameNode bị lỗi, vẫn cần đặt thủ công nó thành máy chủ.
- DataNode
- Lưu trữ dữ liệu Block cụ thể.
- Chịu trách nhiệm về các hoạt động đọc và ghi dữ liệu và sao chép dữ liệu.
- Khi khởi động, DataNode sẽ báo cáo thông tin về các khối dữ liệu hiện đang lưu trữ cho NameNode, sau đó cũng sẽ báo cáo thông tin sửa đổi định kỳ.
- DataNode sẽ giao tiếp với nhau để sao chép khối dữ liệu, đảm bảo tính dư thừa của dữ liệu.
Ghi tệp tin HDFS
-
Client ghi tệp tin vào tệp tin trên đĩa cục bộ
-
Khi kích thước tệp tin tạm thời đạt đến kích thước một Block, HDFS Client thông báo cho NameNode, yêu cầu ghi tệp tin
-
NameNode tạo một tệp tin trong hệ thống tệp tin HDFS và trả về ID Block và danh sách DataNode cần ghi cho client
-
Sau khi nhận được thông tin này, client sẽ ghi tệp tin tạm thời vào DataNodes
-
Client ghi nội dung tệp tin vào DataNode đầu tiên (thường truyền theo đơn vị 4kb).
-
DataNode đầu tiên nhận được, ghi dữ liệu vào đĩa cục bộ, đồng thời truyền cho DataNode thứ hai.
-
Tương tự cho đến DataNode cuối cùng, dữ liệu được sao chép giữa các DataNode theo phương thức pipeline.
-
Các DataNode sau khi nhận xong dữ liệu sẽ gửi xác nhận cho DataNode trước, cuối cùng DataNode đầu tiên trả về xác nhận cho client.
-
Sau khi client nhận được xác nhận cho toàn bộ Block, sẽ gửi một thông tin xác nhận cuối cùng cho NameNode.
-
Nếu ghi vào một DataNode thất bại, dữ liệu sẽ tiếp tục ghi vào các DataNode khác. Sau đó NameNode sẽ tìm một DataNode khác tốt để tiếp tục sao chép, đảm bảo tính dư thừa.
-
Mỗi Block sẽ có một mã kiểm tra và lưu vào tệp tin riêng để xác minh tính toàn vẹn khi đọc.
-
Sau khi ghi xong tệp tin (client đóng), NameNode xác nhận tệp tin (lúc này tệp tin mới hiển thị, nếu NameNode bị treo trước khi xác nhận, tệp tin sẽ bị mất.
fsync: Chỉ đảm bảo thông tin dữ liệu được ghi vào NameNode, nhưng không đảm bảo dữ liệu đã được ghi vào DataNode)
Rack Aware (Nhận thức Rack)
- Cấu hình tệp tin để chỉ định mối quan hệ giữa tên rack và DNS
- Giả sử tham số sao chép là 3, khi ghi tệp tin, sẽ lưu một bản dữ liệu trên rack cục bộ, sau đó lưu hai bản trong một rack khác (tốc độ truyền trong cùng một rack nhanh hơn, từ đó cải thiện hiệu suất)
- Toàn bộ cụm HDFS, tốt nhất là cân bằng tải, để tận dụng tối đa lợi thế của cụm
Đọc tệp tin HDFS
- Client gửi yêu cầu đọc đến NameNode.
- NameNode trả về tất cả Block của tệp tin và các DataNode chứa chúng (bao gồm các nút sao chép).
- Client đọc dữ liệu trực tiếp từ DataNode, nếu DataNode đọc thất bại (DataNode bị lỗi hoặc mã kiểm tra không đúng), thì đọc từ nút sao chép (nếu dữ liệu được đọc trên máy cục bộ, thì đọc trực tiếp, nếu không thì đọc qua mạng).
Độ tin cậy HDFS
- DataNode có thể bị lỗi
DataNode sẽ gửi tín hiệu heartbeat đến NameNode định kỳ. Nếu NameNode không nhận được tín hiệu heartbeat từ DataNode trong một khoảng thời gian, thì coi là nó bị lỗi. Lúc này, NameNode sẽ sao chép dữ liệu của nút đó (lấy từ các nút sao chép của nút đó) đến các DataNode khác. 2. Dữ liệu có thể bị hỏng
Dù là khi ghi hay do vấn đề của ổ đĩa, miễn là dữ liệu có vấn đề (được phát hiện bằng mã kiểm tra khi đọc), có thể đọc từ các nút sao chép khác, đồng thời sẽ sao chép một bản khác đến nút khỏe mạnh. 3. NameNode không đáng tin cậy
Công cụ lệnh HDFS
- fsck: Kiểm tra tính toàn vẹn của tệp tin
- start-balancer.sh: Cân bằng lại HDFS
- hdfs dfs -copyFromLocal: Sao chép tệp tin từ đĩa cục bộ sang HDFS
Hadoop YARN
Kiến trúc cũ
- JobTracker: Chịu trách nhiệm quản lý tài nguyên, theo dõi tiêu thụ và khả dụng tài nguyên, quản lý vòng đời công việc (lên lịch tác vụ, theo dõi tiến độ, cung cấp khả năng chịu lỗi cho tác vụ)
- TaskTracker: Tải hoặc đóng tác vụ, báo cáo trạng thái tác vụ định kỳ
Vấn đề của kiến trúc:
- JobTracker là điểm xử lý tập trung của MapReduce, có vấn đề điểm đơn.
- JobTracker thực hiện quá nhiều tác vụ, gây tiêu thụ tài nguyên quá nhiều, khi có nhiều công việc MapReduce, sẽ gây ra chi phí bộ nhớ lớn. Đây cũng là lý do tại sao giới hạn của Hadoop MapReduce chỉ có thể hỗ trợ 4000 nút chủ.
- Ở phía TaskTracker, việc sử dụng số lượng tác vụ map/reduce làm đại diện cho tài nguyên quá đơn giản, không xem xét đến việc sử dụng CPU/bộ nhớ. Nếu hai tác vụ tiêu thụ bộ nhớ lớn được lên lịch trên cùng một máy, rất dễ xảy ra OOM.
- Ở phía TaskTracker, chia tài nguyên thành slot tác vụ map và slot tác vụ reduce một cách cứng nhắc, nếu hệ thống chỉ có tác vụ map hoặc chỉ có tác vụ reduce, sẽ gây lãng phí tài nguyên, tức là vấn đề sử dụng tài nguyên của cụm.
Tóm lại, đó là vấn đề điểm đơn và vấn đề sử dụng tài nguyên.
Kiến trúc mới YARN
YARN tách trách nhiệm của JobTracker, tách quản lý tài nguyên và lên lịch tác vụ thành các tiến trình độc lập: một quản lý tài nguyên toàn cục (ResourceManager) và một quản lý công việc riêng lẻ (ApplicationMaster). ResourceManager và NodeManager cung cấp phân bổ và quản lý tài nguyên tính toán, chạy ứng dụng cho ApplicationMaster.
- ResourceManager: Quản lý tài nguyên toàn cục và lên lịch tác vụ
- NodeManager: Quản lý và giám sát tài nguyên của nút riêng lẻ
- ApplicationMaster: Quản lý tài nguyên và giám sát tác vụ của công việc riêng lẻ
- Container: Đơn vị yêu cầu tài nguyên và container chạy tác vụ
So sánh kiến trúc cũ và mới
Kiến trúc YARN tạo thành một nền tảng quản lý tài nguyên chung và một nền tảng tính toán ứng dụng chung, tránh được vấn đề điểm đơn và vấn đề sử dụng tài nguyên của kiến trúc cũ, đồng thời cho phép các ứng dụng chạy trên đó không còn bị giới hạn bởi hình thức MapReduce.
Quy trình cơ bản của YARN
- Gửi công việc
Lấy một Application ID từ ResourceManager, kiểm tra cấu hình đầu ra của công việc, tính toán phân片 đầu vào, sao chép tài nguyên công việc (job jar, tệp tin cấu hình, thông tin phân片) đến HDFS, để thực thi tác vụ sau này. 2. Khởi tạo công việc
- ResourceManager chuyển công việc cho Scheduler (có nhiều thuật toán lên lịch, thường là theo độ ưu tiên).
- Scheduler phân bổ một Container, ResourceManager sẽ tải một tiến trình application master và giao cho NodeManager quản lý.
- ApplicationMaster chủ yếu tạo một loạt các tiến trình giám sát để theo dõi tiến độ công việc, đồng thời lấy phân片 đầu vào, tạo một tác vụ Map và tác vụ Reduce tương ứng cho mỗi phân片.
ApplicationMaster còn quyết định cách chạy công việc, nếu công việc nhỏ (có thể cấu hình), thì chạy trực tiếp trong cùng một JVM. 3. Phân bổ tác vụ
ApplicationMaster yêu cầu tài nguyên từ ResourceManager (các Container riêng lẻ, chỉ định yêu cầu tài nguyên của tác vụ), thường là theo tính cục bộ dữ liệu để phân bổ tài nguyên. 4. Thực thi tác vụ
ApplicationMaster theo phân bổ của ResourceManager, khởi động Container trong NodeManager tương ứng, đọc tài nguyên cần thiết cho tác vụ từ HDFS (job jar, tệp tin cấu hình, v.v.), sau đó thực thi tác vụ đó. 5. Cập nhật tiến độ và trạng thái
Báo cáo định kỳ tiến độ và trạng thái tác vụ cho ApplicationMaster Client, lấy tiến độ và trạng thái toàn bộ công việc từ ApplicationMaster định kỳ. 6. Hoàn thành công việc
Client kiểm tra định kỳ xem công việc đã hoàn thành chưa. Sau khi hoàn thành, sẽ xóa các tệp tin tạm thời, thư mục, v.v.
ResourceManager YARN
Chịu trách nhiệm quản lý tài nguyên toàn cục và lên lịch tác vụ, coi toàn bộ cụm như một pool tài nguyên tính toán, chỉ tập trung vào phân bổ, không quan tâm đến ứng dụng, và không chịu trách nhiệm về khả năng chịu lỗi.
Quản lý tài nguyên
- Trước đây tài nguyên được chia thành các slot Map và slot Reduce trên mỗi nút, bây giờ là các Container, mỗi Container có thể chạy ApplicationMaster, Map, Reduce hoặc bất kỳ chương trình nào theo nhu cầu
- Trước đây phân bổ tài nguyên là tĩnh, bây giờ là động, sử dụng tài nguyên hiệu quả hơn
- Container là đơn vị yêu cầu tài nguyên, định dạng yêu cầu tài nguyên:
<resource-name,priority,resource-requirement,number-of-containers>
resource-name: Tên máy chủ, tên rack hoặc * (máy chủ bất kỳ)resource-requirement: Hiện tại chỉ hỗ trợ CPU và bộ nhớ
- Người dùng gửi công việc đến ResourceManager, sau đó phân bổ một Container trên một NodeManager để chạy ApplicationMaster, ApplicationMaster lại yêu cầu tài nguyên từ ResourceManager theo nhu cầu của chương trình.
- YARN có một cơ chế quản lý vòng đời Container, còn việc quản lý giữa ApplicationMaster và Container của nó là do ứng dụng tự định nghĩa.
Lên lịch tác vụ
- Chỉ tập trung vào việc sử dụng tài nguyên, phân bổ tài nguyên hợp lý theo nhu cầu.
- Scheduler có thể xem xét nhu cầu yêu cầu, yêu cầu tài nguyên cụ thể trên máy cụ thể (ApplicationMaster chịu trách nhiệm về tính cục bộ dữ liệu khi yêu cầu tài nguyên, ResourceManager sẽ cố gắng đáp ứng nhu cầu, phân bổ Container trên máy chỉ định, giảm thiểu di chuyển dữ liệu).
Cấu trúc nội bộ
- Client Service: Gửi ứng dụng, kết thúc, thông tin đầu ra (trạng thái của ứng dụng, hàng đợi, cụm, v.v.).
- Admin Service: Quản lý hàng đợi, nút, quyền của Client.
- ApplicationMasterService: Đăng ký, kết thúc ApplicationMaster, nhận yêu cầu yêu cầu hoặc hủy tài nguyên của ApplicationMaster, và truyền không đồng bộ cho Scheduler, xử lý đơn luồng.
- ApplicationMaster Liveliness Monitor: Nhận tín hiệu heartbeat từ ApplicationMaster, nếu một ApplicationMaster không gửi tín hiệu heartbeat trong một khoảng thời gian, thì coi là tác vụ thất bại, tài nguyên của nó sẽ được thu hồi, sau đó ResourceManager sẽ phân bổ một ApplicationMaster khác để chạy ứng dụng (mặc định thử 2 lần).
- Resource Tracker Service: Đăng ký nút, nhận tín hiệu heartbeat từ các nút đăng ký.
- NodeManagers Liveliness Monitor: Giám sát tín hiệu heartbeat của mỗi nút, nếu không nhận được tín hiệu heartbeat trong thời gian dài, thì coi nút đó là không hợp lệ, đồng thời tất cả Container trên nút đó được đánh dấu là không hợp lệ, không lên lịch tác vụ chạy trên nút đó.
- ApplicationManager: Quản lý ứng dụng, ghi nhận và quản lý các ứng dụng đã hoàn thành.
- ApplicationMaster Launcher: Sau khi gửi một ứng dụng, chịu trách nhiệm tương tác với NodeManager, phân bổ Container và tải ApplicationMaster, cũng chịu trách nhiệm kết thúc hoặc hủy.
- YarnScheduler: Phân bổ tài nguyên lên lịch, có FIFO, Fair, Capacity
- ContainerAllocationExpirer: Quản lý Container đã phân bổ nhưng chưa kích hoạt, nếu quá thời gian sẽ thu hồi.
NodeManager YARN
Quản lý Container
- Khi khởi động, đăng ký với ResourceManager và gửi tín hiệu heartbeat định kỳ, chờ lệnh từ ResourceManager.
- Giám sát hoạt động của Container, duy trì vòng đời Container, giám sát việc sử dụng tài nguyên của Container.
- Khởi động hoặc dừng Container, quản lý các gói phụ thuộc khi chạy tác vụ (theo nhu cầu của ApplicationMaster, trước khi khởi động Container, sao chép các chương trình và gói phụ thuộc, tệp tin cấu hình cần thiết vào máy cục bộ).
Cấu trúc nội bộ
- NodeStatusUpdater: Khi khởi động, đăng ký với ResourceManager, báo cáo tình hình tài nguyên khả dụng của nút, duy trì cổng giao tiếp và trạng thái sau này.
- ContainerManager: Nhận yêu cầu RPC (khởi động, dừng), thay đổi tài nguyên cục bộ (tải tài nguyên ứng dụng cần thiết về máy cục bộ, chia sẻ các tài nguyên này khi cần)
PUBLIC:/filecachePRIVATE:/usercache//filecacheAPPLICATION:/usercache//appcache//(sẽ bị xóa sau khi chương trình hoàn thành)- ContainersLauncher: Tải hoặc kết thúc Container
- ContainerMonitor: Giám sát hoạt động và việc sử dụng tài nguyên của Container
- ContainerExecutor: Tương tác với hệ điều hành底层, tải chương trình cần chạy
ApplicationMaster YARN
Quản lý tài nguyên và giám sát tác vụ của công việc riêng lẻ.
Mô tả chức năng
- Tính toán nhu cầu tài nguyên của ứng dụng, tài nguyên có thể được tính tĩnh hoặc động, tài nguyên tĩnh thường được chỉ định khi Client yêu cầu, tài nguyên động cần ApplicationMaster quyết định theo trạng thái chạy của ứng dụng.
- Yêu cầu tài nguyên theo vị trí dữ liệu (Data Locality)
- Yêu cầu tài nguyên từ ResourceManager, tương tác với NodeManager để chạy và giám sát chương trình, giám sát việc sử dụng tài nguyên đã yêu cầu, giám sát tiến độ công việc.
- Theo dõi trạng thái và tiến độ tác vụ, gửi tín hiệu heartbeat định kỳ đến ResourceManager, báo cáo tình hình sử dụng tài nguyên và tiến độ ứng dụng.
- Chịu trách nhiệm về khả năng chịu lỗi trong công việc này.
ApplicationMaster có thể được viết bằng bất kỳ ngôn ngữ nào, nó tương tác với ResourceManager và NodeManager thông qua ProtocolBuf, trước đây là một JobTracker toàn cục chịu trách nhiệm, bây giờ mỗi công việc có một, khả năng mở rộng tốt hơn, ít nhất không gây ra nút thắt cổ chai JobTracker do quá nhiều công việc. Đồng thời, đặt logic công việc vào một ApplicationMaster độc lập, linh hoạt hơn, mỗi công việc có thể có phương thức xử lý riêng, không bị ràng buộc vào phương thức xử lý MapReduce.
Cách tính toán nhu cầu tài nguyên
Thông thường MapReduce tính toán số lượng Map và Reduce dựa trên số lượng Block, sau đó một Map hoặc Reduce thường chiếm một Container.
Cách phát hiện tính cục bộ dữ liệu
Tính cục bộ dữ liệu được lấy từ thông tin phân片 Block của HDFS.
Container YARN
- Đơn vị tài nguyên cơ bản (CPU, bộ nhớ, v.v.)
- Container có thể tải bất kỳ chương trình nào, không giới hạn bằng Java
- Một Node có thể chứa nhiều Container, hoặc một Container lớn
- ApplicationMaster có thể yêu cầu và giải phóng Container theo nhu cầu
YARN Failover
Loại lỗi
- Vấn đề chương trình
- Tiến trình bị treo
- Vấn đề phần cứng
Xử lý lỗi
Tác vụ thất bại
- Lỗi runtime hoặc JVM thoát sẽ báo cáo cho ApplicationMaster.
- Kiểm tra tác vụ bị treo qua heartbeat (Timeout), sẽ kiểm tra nhiều lần (có thể cấu hình) mới coi tác vụ là thất bại.
- Nếu tỷ lệ thất bại của một công việc vượt quá cấu hình, thì coi công việc đó là thất bại.
- Các tác vụ hoặc công việc thất bại sẽ được ApplicationMaster chạy lại.
ApplicationMaster thất bại
- ApplicationMaster gửi tín hiệu heartbeat định kỳ đến ResourceManager, thường một khi ApplicationMaster thất bại, thì coi là thất bại, nhưng cũng có thể cấu hình nhiều lần mới coi là thất bại.
- Ngay khi thất bại, ResourceManager sẽ khởi động một ApplicationMaster mới.
- ApplicationMaster mới chịu trách nhiệm khôi phục trạng thái của ApplicationMaster lỗi trước đó (
yarn.app.mapreduce.am.job.recovery.enable=true), bước này được thực hiện bằng cách lưu trạng thái chạy ứng dụng vào lưu trữ chia sẻ, ResourceManager không chịu trách nhiệm lưu và khôi phục trạng thái tác vụ. - Client cũng kiểm tra tiến độ và trạng thái ApplicationMaster định kỳ, nếu phát hiện nó thất bại, thì hỏi ResourceManager về ApplicationMaster mới.
NodeManager thất bại
- NodeManager gửi tín hiệu heartbeat đến ResourceManager, nếu không nhận được tín hiệu heartbeat trong một khoảng thời gian, ResourceManager sẽ loại bỏ nó.
- Bất kỳ tác vụ và ApplicationMaster nào đang chạy trên NodeManager sẽ được khôi phục trên các NodeManager khác.
- Nếu một NodeManager thất bại nhiều lần, ApplicationMaster sẽ thêm nó vào danh sách đen (ResourceManager không có), không lên lịch tác vụ chạy trên đó.
ResourceManager thất bại
- Thông qua cơ chế checkpoint, lưu trạng thái định kỳ vào đĩa, sau đó chạy lại khi thất bại.
- Đồng bộ trạng thái và thực hiện HA trong suốt qua Zookeeper
Có thể thấy, thông thường việc xử lý lỗi do module cha hiện tại giám sát (heartbeat) và khôi phục. Còn module ở đầu thì lưu trạng thái định kỳ, đồng bộ và Zookeeper để thực hiện HA.
Hadoop MapReduce
Giới thiệu
Một phương thức tính toán phân tán, chỉ định một hàm Map (ánh xạ) để ánh xạ một cặp khóa-giá trị thành một cặp khóa-giá trị mới, chỉ định hàm Reduce (tổng hợp) để đảm bảo rằng mỗi khóa-giá trị ánh xạ có cùng một nhóm khóa.
Định dạng đầu ra của Map và định dạng đầu vào của Reduce phải giống nhau.
Quy trình cơ bản
- Đọc dữ liệu tệp tin
- Thực hiện Map
- Thực hiện Reduce
- Ghi kết quả xử lý vào tệp tin
Quy trình chi tiết
Quy trình trên nhiều nút
Quá trình chính
Phía Map
- Record Reader
Trình đọc bản ghi sẽ dịch bản ghi được tạo bởi định dạng đầu vào, trình đọc bản ghi được sử dụng để phân tích dữ liệu thành bản ghi, không phân tích bản ghi本身. Mục đích của trình đọc bản ghi là phân tích dữ liệu thành bản ghi, nhưng không phân tích bản ghi本身. Nó truyền dữ liệu dưới dạng cặp khóa-giá trị cho Mapper. Thường khóa là thông tin vị trí, giá trị là khối dữ liệu lưu trữ (không thảo luận về bản ghi tùy chỉnh trong bài viết này).
- Map
Trong Mapper, mã do người dùng cung cấp được gọi là cặp trung gian. Định nghĩa cụ thể cho khóa và giá trị cần cẩn thận, vì định nghĩa này rất quan trọng đối với việc hoàn thành tác vụ phân tán.
- Shuffle and Sort
Tác vụ Reduce bắt đầu bằng bước ngẫu nhiên và sắp xếp, bước này ghi vào tệp tin đầu ra và tải về máy cục bộ. Dữ liệu được sắp xếp theo khóa để nhóm các khóa tương đương lại.
- Reduce
Reduce lấy dữ liệu nhóm làm đầu vào, hàm này truyền khóa và trình lặp giá trị liên quan. Có thể tổng hợp, lọc hoặc hợp nhất dữ liệu theo nhiều cách. Khi hàm Reduce hoàn thành, sẽ gửi 0 hoặc nhiều cặp khóa-giá trị.
- Định dạng đầu ra
Chuyển đổi cặp khóa-giá trị cuối cùng và ghi vào tệp tin. Mặc định, khóa và giá trị được phân tách bằng tab, các bản ghi được phân tách bằng dấu xuống dòng. Do đó có thể tùy chỉnh nhiều định dạng đầu ra hơn, cuối cùng dữ liệu sẽ được ghi vào HDFS. Tương tự như trình đọc bản ghi (không thảo luận về định dạng đầu ra tùy chỉnh trong bài viết này).
Đọc dữ liệu MapReduce
Qua InputFormat quyết định định dạng dữ liệu đọc, sau đó chia thành các InputSplit, mỗi InputSplit tương ứng với một Map xử lý, RecordReader đọc nội dung InputSplit cho Map.
InputFormat
Quyết định định dạng dữ liệu đọc, có thể là tệp tin hoặc cơ sở dữ liệu, v.v.
Chức năng
- Xác thực tính đúng đắn của đầu vào công việc, như định dạng, v.v.
- Chia tệp tin đầu vào thành các phân片 logic (InputSplit), một InputSplit sẽ được phân bổ cho một tác vụ Map độc lập.
- Cung cấp triển khai RecordReader, đọc cặp K-V từ InputSplit để Mapper sử dụng.
Phương thức
List getSplits(): Lấy các phân片 đầu vào được tính toán từ tệp tin đầu vào, giải quyết vấn đề chia dữ liệu hoặc tệp tin thành các phân片.RecordReader <K,V> createRecordReader(): Tạo RecordReader, đọc dữ liệu từ InputSplit, giải quyết vấn đề đọc dữ liệu từ phân片.
Cấu trúc lớp
- TextInputFormat: Mỗi dòng trong tệp tin đầu vào là một bản ghi, Key là byte offset của dòng này, còn Value là nội dung của dòng này.
- KeyValueTextFormat: Mỗi dòng trong tệp tin đầu vào là một bản ghi, ký tự phân cách đầu tiên chia mỗi dòng. Nội dung trước ký tự phân cách là Key, sau là Value. Biến phân cách được đặt qua biến
key.value.separator.in.input.line, mặc định là ký tự\t. - NLineInputFormat: Tương tự TextInputFormat, nhưng mỗi khối dữ liệu phải đảm bảo có đúng N dòng, thuộc tính
mapred.line.input.format.linespermap, mặc định là 1. - SequenceFileInputFormat: Một InputFormat dùng để đọc dữ liệu luồng ký tự, <Key,Value> do người dùng tùy chỉnh. Dữ liệu luồng ký tự là định dạng dữ liệu nhị phân nén tùy chỉnh của Hadoop. Nó được sử dụng để tối ưu hóa quá trình truyền dữ liệu từ đầu ra của một tác vụ MapReduce đến đầu vào của một tác vụ MapReduce khác.
InputSplit
Đại diện cho các phân片 logic, không lưu trữ dữ liệu thực sự, chỉ cung cấp một phương pháp để chia dữ liệu.
Split có thông tin Location, thuận lợi cho tính cục bộ dữ liệu. Một InputSplit được giao cho một Map riêng lẻ.
public abstract class InputSplit {
/**
* Lấy kích thước Split, hỗ trợ sắp xếp InputSplit theo size.
*/
public abstract long getLength() throws IOException, InterruptedException;
/**
* Lấy vị trí nút lưu trữ dữ liệu của phân片.
*/
public abstract String[] getLocations() throws IOException, InterruptedException;
}
RecordReader
Chia InputSplit thành các cặp <Key,Value> cho Map xử lý, cũng là đối tượng đọc và chia tệp tin thực tế.
Vấn đề thường gặp
Xử lý tệp tin lớn
CombineFileInputFormat có thể đóng gói nhiều Split thành một, nhằm tránh quá nhiều tác vụ Map (vì số lượng Split quyết định số lượng Map, tạo và hủy nhiều Mapper Task sẽ tốn kém).
Cách tính split
Thường một split là một Block (FileInputFormat chỉ chia tệp tin lớn hơn Block), làm như vậy giúp Map có thể chạy tác vụ cục bộ trên nút chứa dữ liệu, không cần lên lịch tác vụ giữa các nút qua mạng.
Qua mapred.min.split.size, mapred.max.split.size, block.size để kiểm soát kích thước chia.
Nếu mapred.min.split.size lớn hơn block size, sẽ hợp hai Block thành một split, như vậy một phần Block dữ liệu cần được đọc qua mạng.
Nếu mapred.max.split.size nhỏ hơn block size, sẽ chia một Block thành nhiều split, tăng số lượng tác vụ Map (Map tính toán split và báo cáo kết quả, đóng tác vụ hiện tại mở split mới đều cần tiêu tốn tài nguyên).
Đầu tiên lấy đường dẫn và thông tin Block của tệp tin trên HDFS, sau đó chia tệp tin theo splitSize ( splitSize = computeSplitSize(blockSize,minSize,maxSize) ), mặc định splitSize bằng giá trị mặc định của blockSize (128M).
public List<InputSplit> getSplits(JobContext job) throws IOException {
// Đầu tiên tính toán giá trị tối đa và tối thiểu của phân片. Hai giá trị này sẽ được dùng để tính kích thước phân片
long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job));
long maxSize = getMaxSplitSize(job);
// Chia splits
List<InputSplit> splits = new ArrayList<InputSplit>();
List<FileStatus> files = listStatus(job);
for( FileStatus file : files ){
Path path = file.getPath();
long length = file.getLen();
if( length != 0 ){
FileSystem fs = path.getFileSystem(job.getConfiguration());
// Lấy danh sách thông tin Block của tệp tin này [hostname, offset, length]
BlockLocation[] blkLocations = fs.getFileBlockLocations(file, 0, length);
// Kiểm tra xem tệp tin có thể chia được không, thường là có thể chia, nhưng nếu tệp tin được nén, sẽ không thể chia
if( isSplitable(job, path) ){
long blockSize = file.getBlockSize();
// Tính kích thước phân片, tức Math.max(minSize, Math.min(maxSize, blockSize));
long splitSize = computeSplitSize(blockSize, minSize, maxSize);
long bytesRemaining = length;
// Vòng lặp chia: khi tỷ lệ dữ liệu còn lại và kích thước phân片 lớn hơn Split_Slot, tiếp tục chia;
// Nhỏ hơn hoặc bằng, dừng chia.
while( ((double) bytesRemaining) / splitSize > SPLIT_SLOP ){
int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
splits.add(makeSplit(path, length-bytesRemaining, splitSize, blkLocations[blkIndex].getHosts()));
bytesRemaining -= splitSize;
}
// Xử lý dữ liệu còn lại, không đủ một Block
if(bytesRemaining != 0){
splits.add(makeSplit(path, length-bytesRemaining, bytesRemaining, blkLocations[blkLocations.length-1].getHosts()));
}
} else {
// Không thể chia thành splits, trả về nguyên khối
splits.add(makeSplit(path, 0, length, blkLocation[0].getHosts()));
}
} else {
// Đối với tệp tin có độ dài 0, tạo danh sách Hosts rỗng, trả về
splits.add(makeSplit(path, 0, length, new String[0]));
}
}
// Đặt số lượng tệp tin đầu vào
job.getConfiguration().setLong(NUM_INPUT_FILES, files.size());
LOG.debug("Tổng số Splits:" + splits.size);
return splits;
}
Xử lý dữ liệu giữa các phân片
Split được chia theo kích thước tệp tin, nhưng xử lý thường dựa trên ký tự phân cách, điều này chắc chắn sẽ có một bản ghi trải dài hai split.
Giải pháp: Miễn là không phải split đầu tiên, sẽ đọc một bản ghi từ xa, bỏ qua bản ghi đầu tiên.
public class LineRecordReader extends RecordReader<LongWritable, Text> {
public static final String MAX_LINE_LENGTH = "mapreduce.input.linerecordreader.line.maxlength";
private CompressionCodecFactory compressionCodecs = null;
private long start;
private long pos;
private long end;
private LineReader in;
private int maxLineLength;
private LongWritable key = null;
private Text value = null;
// Hàm initialize của LineRecordReader
// Tính toán vị trí bắt đầu và kết thúc của phân片, mở luồng đầu vào để đọc cặp K-V, xử lý trường hợp phân片 nén, v.v.
public void initialize(InputSplit genericSplit, TaskAttemptContext context) throws IOException {
FileSplit split = (FileSplit) genericSplit;
Configuration job = context.getConfiguration();
this.maxLineLength = job.getInt(MAX_LINE_LENGTH, Integer.MAX_VALUE);
start = split.getStart();
end = start + split.getLength();
final Path file = split.getPath();
compressionCodecs = new CompressionCodecFactory(job);
// Mở tệp tin, và định vị đến vị trí bắt đầu đọc của phân片
FileSystem fs = file.getFileSystem(job);
FSDataInputStream fileIn = fs.open(split.getPath());
boolean skipFirstLine = false;
if(codec != null){
// Nếu tệp tin được nén, mở tệp tin trực tiếp
in = new LineReader(codec.createInputStream(fileIn),job);
end = Long.MAX_VALUE;
} else {
// Nếu không phải split đầu tiên, bỏ qua dòng đầu tiên của split này
if(start != 0){
skipFirstLine = true;
--start;
// Định vị đến vị trí offset, đọc sẽ bắt đầu từ vị trí offset
fileIn.seek(start);
}
in = new LineReader(fileIn, job);
}
if(skipFirstLine) {
// Bỏ qua dòng đầu tiên, định vị lại start
start += in.readLine(new Text(), 0, (int)Math.min((long) Integer.MAX_VALUE, end-start));
}
this.pos = start;
}
public boolean nextKeyValue() throws IOException {
if(key == null) {
key = new LongWritable();
}
// Khóa là offset
key.set(pos);
if(value == null){
value = new Text();
}
int newSize = 0;
while(pos < end){
newSize = in.readLine(value, maxLineLength, Math.max((int) Math.min(Integer.MAX_VALUE, end-pos), maxLineLength));
// Độ dài đọc dữ liệu là 0, tức đã đọc xong
if(newSize == 0){
break;
}
pos += newSize;
// Độ dài dữ liệu đọc nhỏ hơn độ dài dòng tối đa, tức đã đọc xong
if(newSize < maxLineLength){
break;
}
// Thực hiện đến đây, tức dòng dữ liệu chưa đọc xong, tiếp tục đọc vào
}
if(newSize == 0){
key = null;
value = null;
return false;
} else {
return true;
}
}
}
Mapper MapReduce
Đọc từng cặp Key-Value của InputSplit và xử lý.
public class Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT> {
/**
* Chuẩn bị, chỉ chạy một lần khi tác vụ map khởi động
*/
protected void setup(Context context) throws IOException, InterruptedException{
}
/**
* Với mỗi cặp <key, value> trong InputSplit sẽ chạy một lần
*/
@SuppressWarnings("unchecked")
protected void map(KEYIN key, VALUEIN value, Context context) throws IOException, InterruptedException {
context.write((KEYOUT) key, (VALUEOUT) value);
}
/**
* Dọn dẹp, như đóng luồng, v.v.
*/
protected void cleanup(Context context) throws IOException, InterruptedException {
}
/**
* Trình điều khiển tác vụ map
*/
public void run(Context context) throws IOException, InterruptedException {
setup(context);
while (context.nextKeyValue()) {
map(context.getCurrentKey(), context.getCurrentValue(), context);
}
cleanup(context);
}
}
public class MapContext<KEYIN, VALUEIN, KEYOUT, VALUEOUT> extends TaskInputOutputContext<KEYIN, VALUEIN, KEYOUT, VALUEOUT> {
private RecordReader<KEYIN, VALUEIN> reader;
private InputSplit split;
/**
* Lấy InputSplit cho map này.
*/
public InputSplit getInputSplit() {
return split;
}
@Override
public KEYIN getCurrentKey() throws IOException, InterruptedException {
return reader.getCurrentKey();
}
@Override
public VALUEIN getCurrentValue() throws IOException, InterruptedException {
return reader.getCurrentValue();
}
@Override
public boolean nextKeyValue() throws IOException, InterruptedException {
return reader.nextKeyValue();
}
}
Hadoop Shuffle
Sắp xếp và truyền kết quả của Map đến Reduce để xử lý. Kết quả của Map không được ghi trực tiếp vào đĩa, mà sử dụng bộ đệm để thực hiện các thao tác sắp xếp trước. Map sẽ gọi Combiner để nén, phân vùng và sắp xếp theo Key, giảm thiểu kích thước kết quả. Mỗi Map hoàn thành sẽ thông báo cho Task, sau đó Reduce có thể xử lý.
Phía Map
- Khi Map bắt đầu tạo kết quả, không ghi trực tiếp vào tệp tin, mà sử dụng bộ đệm để thực hiện các thao tác sắp xếp trước. Mỗi tác vụ Map có một bộ đệm vòng lặp (mặc định 100M), khi nội dung bộ đệm đạt 80%, luồng nền bắt đầu ghi nội dung vào tệp tin, lúc này Map có thể tiếp tục xuất kết quả; nhưng nếu bộ đệm đầy, Map phải chờ.
- Ghi tệp tin sử dụng phương thức round-robin. Trước khi ghi, chia dữ liệu theo phân vùng Reduce. Đối với mỗi phân vùng, sẽ sắp xếp theo Key trong bộ nhớ, nếu cấu hình Combiner, sau khi sắp xếp sẽ thực hiện Combiner (Combiner có thể giảm kích thước dữ liệu ghi và truyền).
- Mỗi khi kết quả đạt ngưỡng bộ đệm, sẽ tạo một tệp tin, khi Map kết thúc, có thể tạo nhiều tệp tin. Trước khi Map hoàn thành, sẽ hợp nhất và sắp xếp các tệp tin này. Nếu số lượng tệp tin vượt quá 3, sau khi hợp nhất sẽ chạy lại Combiner (1/2 tệp tin thì không cần).
- Nếu cấu hình nén, tệp tin cuối cùng sẽ được nén trước, giảm dữ liệu ghi và truyền. Khi Map hoàn thành, thông báo cho trình quản lý tác vụ, lúc này Reduce có thể bắt đầu sao chép dữ liệu kết quả.
Phía Reduce
- Tệp tin kết quả của Map được lưu vào ổ đĩa cục bộ của máy chạy tác vụ Map. Nếu kết quả của Map ít, sẽ lưu trực tiếp vào bộ nhớ, nếu không sẽ ghi vào tệp tin.
- Đồng thời, luồng nền hợp nhất và sắp xếp các tệp tin này thành một tệp tin lớn hơn (nếu tệp tin được nén, cần giải nén trước)
- Khi tất cả kết quả Map được sao chép và hợp nhất, sẽ gọi hàm Reduce, kết quả Reduce sẽ được ghi vào HDFS
Tối ưu hóa
- Nguyên tắc chung là phân bổ nhiều bộ nhớ nhất có thể cho Shuffle, nhưng phải đảm bảo Map và Reduce có đủ bộ nhớ.
- Đối với Map, chủ yếu là tránh ghi tệp tin vào đĩa, ví dụ sử dụng Combiner, tăng giá trị
io.sort.mb - Đối với Reduce, chủ yếu là lưu kết quả Map vào bộ nhớ càng nhiều càng tốt, cũng tránh kết quả trung gian ghi vào đĩa. Mặc định, tất cả bộ nhớ được phân bổ cho hàm Reduce, nếu hàm Reduce không tiêu tốn nhiều bộ nhớ, có thể đặt
mapred.inmem.merge.thresholdthành 0,mapred.job.reduce.input.buffer.percentthành 1.0. - Trong giám sát tác vụ, có thể theo dõi số lượng ghi vào đĩa qua bộ đếm Spilled Records, nhưng giá trị này bao gồm cả Map và Reduce. Đối với I/O, có thể nén kết quả Map, đồng thời tăng kích thước Buffer (
io.file.buffer.size, mặc định 4kb).
Cấu hình
| Thuộc tính | Giá trị mặc định | Mô tả |
|---|---|---|
| io.sort.mb | 100 | Kích thước bộ đệm được sử dụng khi sắp xếp đầu ra của Map. |
| io.sort.record.percent | 0.05 | Không gian còn lại được sử dụng cho bản ghi đầu ra của Map. Thuộc tính này đã bị loại bỏ sau phiên bản 1.X. Mã ngẫu nhiên được sử dụng để sử dụng toàn bộ bộ nhớ của Map và thông tin bản ghi. |
| io.sort.spill.percent | 0.80 | Tỷ lệ ngưỡng sử dụng cho bộ đệm và chỉ mục bản ghi đầu ra của Map. |
| io.sort.factor | 10 | Số luồng hợp nhất tối đa khi hợp nhất tệp tin. Thuộc tính này cũng được sử dụng cho reduce. Thường đặt giá trị là 100. |
| min.num.spills.for.combine | 3 | Số tệp tin tràn tối thiểu cần thiết để chạy Combiner. |
| mapred.compress.map.output | false | Nén đầu ra của Map. |
| mapred.map.output.compression.codec | DefaultCodec | Bộ giải mã nén cần thiết cho đầu ra của Map. |
| mapred.reduce.parallel.copies | 5 | Số luồng được sử dụng để truyền đầu ra của Map đến reducer. |
| mapred.reduce.copy.backoff | 300 | Số giây tối đa, nếu reducer thất bại sẽ thử lại truyền |
| io.sort.factor | 10 | Số tệp tin tràn tối đa cần thiết để chạy Combiner. |
| mapred.job.shuffle.input.buffer.percent | 0.70 | Tỷ lệ kích thước heap của bộ đệm đầu vào trong giai đoạn sao chép ngẫu nhiên |
| mapred.job.shuffle.merge.percent | 0.66 | Tỷ lệ ngưỡng của bộ đệm đầu ra của Map được sử dụng để khởi động quá trình hợp nhất và truyền dữ liệu đĩa |
| mapred.inmem.merge.threshold | 1000 | Số lượng ngưỡng của đầu ra Map được sử dụng để khởi động quá trình hợp nhất và truyền dữ liệu đĩa. Nhỏ hơn hoặc bằng 0 có nghĩa là không có ngưỡng, và hành vi tràn được quản lý bởi mapred.job.shuffle.merge.percent riêng. |
| mapred.job.reduce.input.buffer.percent | 0.0 | Tỷ lệ kích thước heap của bộ đệm đầu ra của Map, kích thước đầu ra trong bộ nhớ không được vượt quá giá trị này. Nếu reducer cần ít bộ nhớ hơn, có thể tăng giá trị này. |
Lập trình Hadoop
Xử lý
- select: Phân tích trực tiếp dữ liệu đầu vào, lấy các trường dữ liệu cần thiết
- where: Cũng xử lý dữ liệu đầu vào, xác định xem có cần dữ liệu đó không
- aggregation: min, max, sum
- group by: Thực hiện qua Reduce
- sort
- join: Map join, Reduce join
Thư viện bên thứ ba
Loại thứ nhất: Chỉ định phụ thuộc có thể sử dụng Public Cache
export LIBJARS=$MYLIB/commons-lang-2.3.jar, hadoop jar prohadoop-0.0.1-SNAPSHOT.jar org.aspress.prohadoop.c3. WordCountUsingToolRunner -libjars $LIBJARS
Loại thứ hai: Bao gồm phụ thuộc, mỗi lần cần sao chép
hadoop jar prohadoop-0.0.1-SNAPSHOT-jar-with-dependencies.jar org.aspress.prohadoop.c3. WordCountUsingToolRunner Các thư viện phụ thuộc hiện được bao gồm trong tệp tin ứng dụng JAR
Hadoop IO
- Tệp tin đầu vào đọc từ HDFS.
- Tệp tin đầu ra sẽ được lưu vào đĩa cục bộ.
- I/O mạng giữa Reducer và Mapper, lấy tệp tin kết quả từ nút Mapper.
- Sử dụng instance Reducer đọc dữ liệu từ đĩa cục bộ.
- Đầu ra của Reducer - truyền trở lại HDFS.
Tính tuần tự hóa
- Cần thiết cho truyền và lưu trữ
- Giao diện Writable
- Khung Avro: IDL, hỗ trợ phiên bản, đa ngôn ngữ, JSON-like
Nén
- Có thể giảm không gian đĩa và lượng truyền mạng
- Kích thước nén, tốc độ, có thể chia
- gzip, bzip, LZO, LZ4, Snappy
- Cần so sánh tỷ lệ nén và hiệu suất của các thuật toán nén
Điểm chính: Nén và chia thường mâu thuẫn (khối của tệp tin nén không thể chia tốt để chạy độc lập, nhiều khi điểm chia của một tệp tin bị chia thành hai tệp tin nén, Map task không thể xử lý, vì vậy đối với các tệp tin này, Hadoop thường sử dụng một Map task để xử lý toàn bộ tệp tin. Kết quả đầu ra của Map cũng có thể được nén, giảm lượng dữ liệu truyền từ Map đến Reduce.
Tính toàn vẹn
- Đĩa và mạng rất dễ bị lỗi, đảm bảo tính toàn vẹn dữ liệu thường qua phương pháp kiểm tra CRC32
- Mỗi lần ghi dữ liệu vào đĩa sẽ kiểm tra, đồng thời lưu mã kiểm tra
- Mỗi lần đọc dữ liệu cũng kiểm tra mã kiểm tra
- Đồng thời, mỗi DataNode sẽ kiểm tra tính toàn vẹn của mỗi Block định kỳ
- Khi phát hiện Block dữ liệu có vấn đề, không báo lỗi ngay, mà tìm một bản sao hoàn chỉnh của dữ liệu trên NameNode để khôi phục, không thể khôi phục mới báo lỗi
Cài đặt Hadoop
Phương thức cài đặt
- Cài đặt trên một nút
Tất cả dịch vụ chạy trong một JVM, phù hợp để gỡ lỗi, kiểm thử đơn vị
- Cụm giả
Tất cả dịch vụ chạy trên một máy, mỗi dịch vụ chạy trong JVM riêng, phù hợp để kiểm thử đơn giản, lấy mẫu
- Cụm nhiều nút
Dịch vụ chạy trên các máy khác nhau, phù hợp cho môi trường sản xuất
Cấu hình tài khoản chung
Để thuận tiện cho giao tiếp không khóa giữa máy chủ và máy con, chủ yếu sử dụng cơ chế khóa công khai/riêng tư.
- Tài khoản của tất cả các nút giống nhau, trên máy chủ chính thực hiện
ssh-keygen -t rsađể tạo cặp khóa. - Sao chép khóa công khai đến mỗi nút đích.
Cấu hình Hadoop
Tệp tin cấu hình
xxx-default.xml: Chỉ đọc, cấu hình mặc địnhxxx-site.xml: Thay thế cấu hình trong defaultcore-site.xml: Cấu hình thuộc tính chunghdfs-site.xml: Cấu hình HDFSyarn-site.xml: Cấu hình YARNmapred-site.xml: Cấu hình MapReduce
Thứ tự tệp tin cấu hình
- Được chỉ định trong JobConf
- Cấu hình trong
xxx-site.xmltrên máy client - Cấu hình trong
xxx-site.xmltrên nút Slave - Cấu hình trong
xxx-default.xml
Nếu không muốn một thuộc tính bị ghi đè, có thể đặt nó thành final
<property>
<name>{PROPERTY_NAME}</name>
<value>{PROPERTY_VALUE}</value>
<final>true</final>
</property>
Bài viết tổng hợp từ Hướng dẫn Hadoop của W3Cschool (https://www.w3cschool.cn/hadoop/)