Hỏi đáp về IT
Mã xác nhận Thay đổi một
HNQ Bigdata engineering

Hướng dẫn: Điều chỉnh công việc Apache Spark của bạn (Phần 1)

Duyệt qua: 234

Tìm hiểu các kỹ thuật điều chỉnh công việc Apache Spark của bạn để đạt hiệu quả tối ưu.

 

bản tiếng anh: https://blog.cloudera.com/how-to-tune-your-apache-spark-jobs-part-1/

Khi bạn viết mã Apache Spark và trang thông qua các API công khai, bạn bắt gặp các từ như chuyển đổi , hành động và RDD . Hiểu biết về Spark ở cấp độ này là rất quan trọng để viết các chương trình Spark. Tương tự như vậy, khi mọi thứ bắt đầu không thành công hoặc khi bạn tham gia vào giao diện người dùng web để cố gắng hiểu tại sao ứng dụng của bạn mất nhiều thời gian như vậy, bạn sẽ phải đối mặt với một lượng từ vựng mới về các từ như công việc , giai đoạn và nhiệm vụ . Hiểu biết về Spark ở cấp độ này là rất quan trọng để viết các chương trình Spark tốt và tất nhiên , ý tôi là nhanh. Để viết một chương trình Spark sẽ thực thi hiệu quả, rất rất hữu ích để hiểu mô hình thực thi cơ bản của Spark.

Trong bài đăng này, bạn sẽ tìm hiểu những điều cơ bản về cách các chương trình Spark thực sự được thực thi trên một cụm. Sau đó, bạn sẽ nhận được một số khuyến nghị thực tế về mô hình thực thi của Spark có ý nghĩa như thế nào đối với việc viết các chương trình hiệu quả.

Cách Spark thực hiện chương trình của bạn

Một ứng dụng Spark bao gồm một quy trình trình điều khiển duy nhất và một tập hợp các quy trình thực thi nằm rải rác trên các nút trên cụm.

Trình điều khiển là quá trình phụ trách luồng công việc kiểm soát cấp cao cần phải thực hiện. Các quy trình thực thi chịu trách nhiệm thực hiện công việc này, dưới dạng các nhiệm vụ , cũng như lưu trữ bất kỳ dữ liệu nào mà người dùng chọn để lưu vào bộ nhớ cache. Cả trình điều khiển và trình thực thi thường gắn bó với nhau trong toàn bộ thời gian ứng dụng đang chạy, mặc dù việc phân bổ tài nguyên động sẽ thay đổi điều đó. Một trình thực thi duy nhất có một số vị trí để chạy các tác vụ và sẽ chạy nhiều vị trí đồng thời trong suốt vòng đời của nó. Việc triển khai các quy trình này trên cụm phụ thuộc vào trình quản lý cụm đang sử dụng (YARN, Mesos hoặc Spark Standalone), nhưng bản thân trình điều khiển và trình thực thi tồn tại trong mọi ứng dụng Spark.

Ở trên cùng của hệ thống phân cấp thực thi là các công việc . Việc gọi một hành động bên trong ứng dụng Spark sẽ kích hoạt khởi chạy một công việc Spark để hoàn thành nó. Để quyết định công việc này trông như thế nào, Spark xem xét biểu đồ của RDD mà hành động đó phụ thuộc vào đó và lập một kế hoạch thực hiện. Kế hoạch này bắt đầu với các RDD xa nhất — nghĩa là, những RDD không phụ thuộc vào RDD nào khác hoặc tham chiếu dữ liệu đã được lưu trong bộ nhớ cache – và lên đến đỉnh điểm là RDD cuối cùng cần thiết để tạo ra kết quả của hành động.

Kế hoạch thực hiện bao gồm việc tập hợp các chuyển đổi của công việc thành các giai đoạn . Một giai đoạn tương ứng với một tập hợp các tác vụ mà tất cả đều thực thi cùng một mã, mỗi tác vụ nằm trên một tập con dữ liệu khác nhau. Mỗi giai đoạn chứa một chuỗi các phép biến đổi có thể được hoàn thành mà không cần xáo trộn toàn bộ dữ liệu.

Điều gì xác định liệu dữ liệu có cần được xáo trộn hay không? Nhớ lại rằng một RDD bao gồm một số lượng phân vùng cố định, mỗi phân vùng bao gồm một số bản ghi. Đối với các RDD được trả về bởi cái gọi là các phép biến đổi hẹp như bản đồ và bộ lọc, các bản ghi cần thiết để tính toán các bản ghi trong một phân vùng duy nhất nằm trong một phân vùng duy nhất trong RDD mẹ. Mỗi đối tượng chỉ phụ thuộc vào một đối tượng duy nhất trong cha. Các hoạt động như liên kết có thể dẫn đến một tác vụ xử lý nhiều phân vùng đầu vào, nhưng việc chuyển đổi vẫn được coi là hẹp vì các bản ghi đầu vào được sử dụng để tính toán bất kỳ bản ghi đầu ra duy nhất nào vẫn có thể chỉ nằm trong một tập con giới hạn của các phân vùng.

Tuy nhiên, Spark cũng hỗ trợ các phép biến đổi với nhiều phụ thuộc như groupByKey và ReduceByKey. Trong các phần phụ thuộc này, dữ liệu cần thiết để tính các bản ghi trong một phân vùng duy nhất có thể nằm trong nhiều phân vùng của RDD mẹ. Tất cả các bộ giá trị có cùng một khóa phải kết thúc trong cùng một phân vùng, được xử lý bởi cùng một tác vụ. Để đáp ứng các hoạt động này, Spark phải thực hiện xáo trộn, chuyển dữ liệu xung quanh cụm và dẫn đến một giai đoạn mới với một tập hợp các phân vùng mới.

Ví dụ, hãy xem xét đoạn mã sau:

sc . textFile ( "someFile.txt" ). 
  bản đồ ( mapFunc ). 
  Bản đồ phẳng ( flatMapFunc ). 
  bộ lọc ( filterFunc ). 
  count ()

Nó thực hiện một hành động duy nhất, phụ thuộc vào một chuỗi các phép biến đổi trên RDD bắt nguồn từ một tệp văn bản. Mã này sẽ thực thi trong một giai đoạn duy nhất, bởi vì không có đầu ra nào của ba hoạt động này phụ thuộc vào dữ liệu có thể đến từ các phân vùng khác với đầu vào của chúng.

Ngược lại, mã này tìm số lần mỗi ký tự xuất hiện trong tất cả các từ xuất hiện nhiều hơn 1.000 lần trong một tệp văn bản.

val tokenized = sc . textFile ( args ( 0 )). flatMap ( _ . split ( '' )) 
val wordCounts = mã hóa . bản đồ (( _ , 1 )). ReduceByKey ( _ + _ ) 
val filter = wordCounts . filter ( _ . _2 > = 1000 ) 
val charCounts = filter . Bản đồ phẳng  ( _ . _1 . ToCharArray ). bản đồ (( _ , 1 )). 
  reduceByKey ( _ + _ ) 
charCounts . thu thập () 

Quá trình này sẽ chia thành ba giai đoạn. Các reduceByKeyhoạt động dẫn đến ranh giới giai đoạn, bởi vì tính toán đầu ra của chúng yêu cầu phân vùng lại dữ liệu theo các khóa.

Đây là một đồ thị biến đổi phức tạp hơn bao gồm một phép biến đổi nối với nhiều phụ thuộc.

Các hộp màu hồng hiển thị biểu đồ giai đoạn kết quả được sử dụng để thực thi nó.

Tại mỗi ranh giới giai đoạn, dữ liệu được ghi vào đĩa bởi các tác vụ trong các giai đoạn mẹ và sau đó được tìm nạp qua mạng bởi các tác vụ trong giai đoạn con . Vì chúng phải chịu ổ đĩa và I / O mạng nặng, các ranh giới giai đoạn có thể tốn kém và nên tránh khi có thể. Số lượng phân vùng dữ liệu trong giai đoạn mẹ có thể khác với số lượng phân vùng trong giai đoạn con. Các phép biến đổi có thể kích hoạt ranh giới giai đoạn thường chấp nhận một numPartitionsđối số xác định có bao nhiêu phân vùng để chia dữ liệu thành trong giai đoạn con.

Cũng giống như số lượng bộ giảm là một tham số quan trọng trong việc điều chỉnh công việc MapReduce, việc điều chỉnh số lượng phân vùng ở ranh giới vùng thường có thể làm giảm hoặc phá vỡ hiệu suất của ứng dụng. Chúng tôi sẽ nghiên cứu sâu hơn về cách điều chỉnh số này trong phần sau.

Chọn đúng nhà điều hành

Khi cố gắng hoàn thành một điều gì đó với Spark, một nhà phát triển thường có thể chọn từ nhiều cách sắp xếp hành động và chuyển đổi sẽ tạo ra kết quả giống nhau. Tuy nhiên, không phải tất cả những cách sắp xếp này sẽ dẫn đến hiệu suất như nhau: tránh những cạm bẫy phổ biến và chọn cách sắp xếp phù hợp có thể tạo ra sự khác biệt trong hiệu suất của ứng dụng. Một vài quy tắc và hiểu biết sâu sắc sẽ giúp bạn định hướng cho mình khi có những lựa chọn này.

Công việc gần đây trong SPARK-5097 đã bắt đầu ổn định SchemaRDD, sẽ mở trình tối ưu hóa Catalyst của Spark cho các lập trình viên sử dụng các API cốt lõi của Spark, cho phép Spark đưa ra một số lựa chọn cấp cao hơn về việc sử dụng toán tử nào. Khi SchemaRDD trở thành một thành phần ổn định, người dùng sẽ không cần phải đưa ra một số quyết định này.

Mục tiêu chính khi chọn cách sắp xếp các toán tử là giảm số lần xáo trộn và lượng dữ liệu bị xáo trộn. Điều này là do xáo trộn là hoạt động khá tốn kém; tất cả dữ liệu xáo trộn phải được ghi vào đĩa và sau đó được chuyển qua mạng. repartitionjoincogroup, Và bất kỳ hoặc biến đổi có thể dẫn đến shuffle. Tuy nhiên, không phải tất cả các thao tác này đều như nhau và một số lỗi hiệu suất phổ biến nhất đối với các nhà phát triển Spark mới bắt đầu phát sinh do chọn sai:*By*ByKey

  • Tránh groupByKeykhi thực hiện một hoạt động giảm thiểu kết hợp. Ví dụ, sẽ tạo ra kết quả tương tự như . Tuy nhiên, cái trước sẽ chuyển toàn bộ tập dữ liệu qua mạng, trong khi cái sau sẽ tính tổng cục bộ cho mỗi khóa trong mỗi phân vùng và kết hợp các tổng cục bộ đó thành các tổng lớn hơn sau khi xáo trộn.rdd.groupByKey().mapValues(_.sum) rdd.reduceByKey(+ _)
  • Tránh reduceByKeyKhi loại giá trị đầu vào và đầu ra khác nhau. Ví dụ, hãy xem xét việc viết một phép biến đổi tìm tất cả các chuỗi duy nhất tương ứng với mỗi khóa. Một cách sẽ là sử dụng bản đồ để chuyển đổi từng phần tử thành a Setvà sau đó kết hợp các Sets với reduceByKey:
    rdd . map ( kv => ( kv . _1 , new Set [ String ] () + kv . _2 )) . ReduceByKey ( _ ++ _ )    
        

    Đoạn mã này dẫn đến vô số việc tạo đối tượng không cần thiết vì một tập hợp mới phải được cấp phát cho mỗi bản ghi. Tốt hơn là sử dụng aggregateByKey, điều này thực hiện tổng hợp phía bản đồ hiệu quả hơn:

    val zero = bộ sưu tập mới . có thể thay đổi . Đặt [ Chuỗi ] () 
    rdd . tổng hợpByKey ( không ) ( ( set , v ) => set + = v , ( set1 , set2 ) => set1 ++ = set2 ) 
           
         
  • Tránh khuôn mẫu. flatMap-join-groupByKhi hai tập dữ liệu đã được nhóm theo khóa và bạn muốn nối chúng và giữ chúng được nhóm lại, bạn chỉ có thể sử dụng cogroup. Điều đó tránh tất cả các chi phí liên quan đến việc giải nén và đóng gói lại các nhóm.
Khi xáo trộn không xảy ra

Nó cũng hữu ích để biết các trường hợp mà các phép biến đổi trên sẽ không dẫn đến xáo trộn. Spark biết cách tránh xáo trộn khi một chuyển đổi trước đó đã phân vùng dữ liệu theo cùng một trình phân vùng. Hãy xem xét luồng sau:

rdd1 = someRdd . ReduceByKey (...) 
rdd2 = someOtherRdd . ReduceByKey (...) 
rdd3 = rdd1 . tham gia ( rdd2 )

Bởi vì không có trình phân vùng nào được chuyển đến reduceByKey, trình phân vùng mặc định sẽ được sử dụng, dẫn đến rdd1 và rdd2 đều được băm-phân vùng. Hai reduceByKeys này sẽ dẫn đến hai lần xáo trộn. Nếu các RDD có cùng số lượng phân vùng, phép nối sẽ không yêu cầu xáo trộn bổ sung. Bởi vì các RDD được phân vùng giống nhau, bộ khóa trong bất kỳ phân vùng đơn nào của rdd1 chỉ có thể hiển thị trong một phân vùng duy nhất của rdd2. Do đó, nội dung của bất kỳ phân vùng đầu ra đơn lẻ nào của rdd3 sẽ chỉ phụ thuộc vào nội dung của một phân vùng duy nhất trong rdd1 và một phân vùng duy nhất trong rdd2 và không cần xáo trộn thứ ba.

Ví dụ: nếu someRddcó bốn phân vùng, someOtherRddcó hai phân vùng và cả hai phân vùng đều reduceByKeysử dụng ba phân vùng, thì tập hợp các tác vụ thực thi sẽ giống như sau:

Điều gì sẽ xảy ra nếu rdd1 và rdd2 sử dụng các trình phân vùng khác nhau hoặc sử dụng trình phân vùng (băm) mặc định với các phân vùng số khác nhau? Trong trường hợp đó, chỉ một trong các rd (một trong các rd có số lượng phân vùng ít hơn) sẽ cần phải được cấu hình lại cho phép nối.

Các phép biến đổi giống nhau, đầu vào giống nhau, số lượng phân vùng khác nhau:

Một cách để tránh xáo trộn khi kết hợp hai tập dữ liệu là tận dụng các biến quảng bá . Khi một trong các tập dữ liệu đủ nhỏ để vừa trong bộ nhớ của một trình thực thi duy nhất, nó có thể được tải vào bảng băm trên trình điều khiển và sau đó phát cho mọi trình thực thi. Sau đó, một phép biến đổi bản đồ có thể tham chiếu đến bảng băm để thực hiện tra cứu.

Khi có nhiều xáo trộn tốt hơn

Đôi khi có một ngoại lệ đối với quy tắc giảm thiểu số lần xáo trộn. Thêm một lần xáo trộn có thể có lợi cho hiệu suất khi nó tăng tính song song. Ví dụ: nếu dữ liệu của bạn đến trong một vài tệp lớn không thể xếp được, việc phân vùng được chỉ định bởi tệp InputFormatcó thể đặt số lượng lớn các bản ghi trong mỗi phân vùng, trong khi không tạo đủ phân vùng để tận dụng tất cả các lõi có sẵn. Trong trường hợp này, việc gọi ra phân vùng lại với số lượng phân vùng cao (sẽ kích hoạt xáo trộn) sau khi tải dữ liệu sẽ cho phép các hoạt động sau nó tận dụng nhiều CPU của cụm hơn.

Một trường hợp khác của ngoại lệ này có thể phát sinh khi sử dụng hành động giảm hoặc tổng hợp để tổng hợp dữ liệu vào trình điều khiển. Khi tổng hợp trên nhiều phân vùng, việc tính toán có thể nhanh chóng bị tắc nghẽn trên một luồng duy nhất trong trình điều khiển hợp nhất tất cả các kết quả lại với nhau. Để nới lỏng tải cho trình điều khiển, trước tiên người ta có thể sử dụng reduceByKeyhoặc aggregateByKeythực hiện một vòng tổng hợp phân tán chia tập dữ liệu thành một số lượng phân vùng nhỏ hơn. Các giá trị trong mỗi phân vùng được hợp nhất với nhau song song, trước khi gửi kết quả của chúng đến trình điều khiển để tổng hợp lần cuối. Hãy xem treeReducevà treeAggregateví dụ về cách làm điều đó. (Lưu ý rằng trong 1.2, phiên bản mới nhất tại thời điểm viết bài này, chúng được đánh dấu là API dành cho nhà phát triển, nhưng SPARK-5430 tìm cách thêm các phiên bản ổn định của chúng vào lõi.)

Thủ thuật này đặc biệt hữu ích khi tổng hợp đã được nhóm bằng một khóa. Ví dụ: hãy xem xét một ứng dụng muốn đếm số lần xuất hiện của từng từ trong kho ngữ liệu và kéo kết quả vào trình điều khiển dưới dạng bản đồ. Một cách tiếp cận, có thể được thực hiện với hành động tổng hợp, là tính toán một bản đồ cục bộ tại mỗi phân vùng và sau đó hợp nhất các bản đồ tại trình điều khiển. Cách tiếp cận thay thế, có thể được thực hiện aggregateByKey, là thực hiện đếm theo cách được phân phối đầy đủ, và sau đó chỉ đơn giản collectAsMaplà kết quả cho trình điều khiển.

Sắp xếp thứ cấp

Một khả năng quan trọng khác cần nhận biết là sự repartitionAndSortWithinPartitionsbiến đổi. Đó là một sự biến đổi nghe có vẻ phức tạp, nhưng dường như sẽ xuất hiện trong đủ loại tình huống kỳ lạ. Sự chuyển đổi này đẩy việc phân loại xuống bộ máy xáo trộn, nơi có thể đổ một lượng lớn dữ liệu một cách hiệu quả và việc phân loại có thể được kết hợp với các hoạt động khác.

Ví dụ: Apache Hive trên Spark sử dụng chuyển đổi này bên trong việc triển khai kết hợp của nó. Nó cũng hoạt động như một khối xây dựng quan trọng trong kiểu sắp xếp thứ cấp , trong đó bạn muốn nhóm các bản ghi theo khóa và sau đó, khi lặp lại các giá trị tương ứng với một khóa, hãy để chúng hiển thị theo một thứ tự cụ thể. Vấn đề này xuất hiện trong các thuật toán cần nhóm các sự kiện theo người dùng và sau đó phân tích các sự kiện cho từng người dùng dựa trên thứ tự chúng xảy ra trong thời gian. Việc tận dụng repartitionAndSortWithinPartitionsđể thực hiện phân loại thứ cấp hiện đòi hỏi một chút công sức từ phía người dùng, nhưng SPARK-3655 sẽ đơn giản hóa mọi thứ một cách đáng kể.

Phần kết luận

Bây giờ bạn đã hiểu rõ về các yếu tố cơ bản liên quan đến việc tạo ra một chương trình Spark hiệu quả về hiệu suất! Trong Phần 2 , chúng tôi sẽ đề cập đến việc điều chỉnh các yêu cầu tài nguyên, tính song song và cấu trúc dữ liệu.

Sandy Ryza là Nhà khoa học dữ liệu tại Cloudera, người cam kết Apache Spark và là thành viên Apache Hadoop PMC. Anh ấy là đồng tác giả của cuốn sách O'Reilly Media, Phân tích nâng cao với Spark .

bigdata 2020/11/23 9:49

Để lại dấu chân

Bước trên một chân

Bình luận

copyright © bigdata 2010-2020
Processed in 0 seconds, 0 queries