Dự đoán, tham gia và nhận thức phân vùng


Đặng Văn Tuyển
2 năm trước
Hữu ích 11 Chia sẻ Viết bình luận 0
Đã xem 7993

Ví dụ này cho thấy một số tính năng mới hơn của truy vấn (một cách có thể tham gia) và cho thấy những ưu và nhược điểm của định tuyến nhận biết phân vùng.

Mặc dù chủ yếu là một ví dụ IMDG của Hazelcast, Hazelcast Jet có sự xuất hiện của khách để thực hiện việc tham gia.

Bối cảnh: Phân vùng

Cấu trúc dữ liệu được sử dụng phổ biến nhất trong Hazelcast là com.hazelcast.core.IMap.

An  IMap chủ yếu có thể được sử dụng thay thế cho nhau java.util.Map như một  _key-value_store.

Đằng sau hậu trường, có những khác biệt. Điều quan trọng nhất cho ví dụ này IMaplà phân vùng. Điều chúng tôi muốn nói là điều này được IMapchia thành các phần được gọi là các phân vùng và các phân vùng này được trải rộng trên các JVM của máy chủ có sẵn.

Trong sơ đồ này, có một lời IMapkêu gọi  person lưu trữ chi tiết về con người, được chia thành 26 phần và trải rộng trên bốn JVM.

Thu nhỏ

Phân vùng này mang lại lợi ích của việc nhân rộng.

Không một JVM nào nắm giữ tất cả IMapnội dung, do đó, nhiều dữ liệu có thể được đưa vào person bản đồ hơn bất kỳ một JVM nào có thể đối phó - một lợi thế đáng kể so với java.util.Map.

Hơn nữa, nếu các máy chủ được thêm vào cụm, Hazelcast sẽ tự động sắp xếp lại các phân vùng trên các máy chủ hiện có và được thêm vào để cân bằng lại tải. Vì vậy, công suất có thể tăng hoặc giảm trong khi cụm chạy.

Mạng

Truyền bá dữ liệu theo cách như vậy không có vấn đề gì đối với các hoạt động CRUD. Thông thường, người gọi duy trì kết nối đến mọi máy chủ, vì vậy nếu cần một bản ghi dữ liệu từ máy chủ đầu tiên, thì đó là một bước nhảy duy nhất trên mạng. Và nếu bản ghi dữ liệu tiếp theo là cần thiết từ máy chủ thứ hai, thì cũng có một kết nối mở tới đó, và một lần nữa, đó là một mạng duy nhất hy vọng có được nó.

Truyền bá dữ liệu làm cho truy vấn nhanh hơn. Nếu chúng ta cần tìm kiếm thông qua các bản ghi dữ liệu và có năm JVM, việc tìm kiếm có thể chạy song song trên tất cả năm máy chủ cùng một lúc và hoàn thành trong một phần năm thời gian.

Thông thường, việc xử lý cần truy cập vào một số bản ghi dữ liệu cùng một lúc và điều này bị cản trở nếu các bản ghi dữ liệu nằm trên các JVM khác nhau. Mặc dù chuyển mạng có thể nhanh, nhưng nó vẫn góp phần chạy thời gian. Sử dụng PartitionAwarecó thể giúp đỡ.

Phần mềm phân vùng

Thuật toán mặc định để xác định phân vùng nào lưu trữ một bản ghi dữ liệu cụ thể dựa trên toàn bộ khóa của bản ghi dữ liệu. Nói một cách đơn giản, mục nhập có khóa 1  có thể đi đến phân vùng 1 , khóa  2  đến phân vùng 2, khóa 3  đến phân vùng 3, v.v.

Điều này có nghĩa trong thực tế là việc xử lý chạy trên máy chủ JVM và cần truy cập vào các mục 1  và  3  sẽ có hiệu năng không nhất quán. Nếu các phân vùng chứa các mục nhập đó được lưu trữ bởi máy chủ JVM mà quá trình xử lý chạy, sẽ không có truyền dữ liệu mạng. Nếu các phân vùng sau đó được sắp xếp lại cho các JVM máy chủ khác nhau vì các JVM máy chủ được thêm vào, quá trình xử lý tương tự sẽ phải truy xuất dữ liệu trên mạng, sẽ cho hiệu suất khác nhau.

Giải pháp ở đây là sử dụng com.hazelcast.core.PartitionAwaređể ghi đè thuật toán mặc định cho vị trí dữ liệu.

Một khóa thực hiện PartitionAwarephải cung cấp một phương thức getPartitionKey()cung cấp định tuyến tới Hazelcast. Trong ví dụ này, chúng tôi có thể trả về  số lẻ  cho các khóa 1  và 3thậm chí  cho khóa 2 .

Điều này sẽ đảm bảo rằng các phím 1  và 3  được đặt trong cùng một phân vùng. Bất kể phân vùng đó được lưu trữ ở đâu, mọi thao tác xử lý truy cập các khóa 1  và  3  sẽ luôn tìm thấy chúng trong cùng một hiệu năng nhất quán JVM và soget mà không bị nhiễu mạng.

Điều này không ảnh hưởng đến sự bình đẳng. Phím 1  và 3  là khác nhau, các mục bản đồ riêng biệt. Chúng chỉ có cùng cơ sở để định tuyến -  lẻ  thay vì toàn bộ khóa - vì vậy có một đích chung.

Cũng lưu ý rằng bạn không chỉ định phân vùng nào họ đi vào cũng như phân vùng đó được lưu trữ ở đâu. Nó chỉ là một cơ chế ái lực.

Chi tiết phân vùng

Thuật toán mặc định để tính toán phân vùng nơi một mục nhập sẽ cư trú là lấy hàm băm của khóa ở dạng nhị phân và mô đun này theo số lượng phân vùng được chọn.

Nếu điều đó có vẻ như quá nhiều công việc khó khăn, chỉ cần hỏi Hazelcast cho câu trả lời:

System.out.println(this.hazelcastInstance.getPartitionService().getPartition("1"));
System.out.println(this.hazelcastInstance.getPartitionService().getPartition("2"));
System.out.println(this.hazelcastInstance.getPartitionService().getPartition("3"));

Cung cấp:

Partition [41]
Partition [257]
Partition [33]

Khi sử dụng thiết lập mặc định của 271 phân vùng.

Nguy hiểm phân vùng

Việc sử dụng lẻ  và thậm chí  là một ví dụ có chủ ý xấu để minh họa những cạm bẫy tiềm ẩn của việc ghi đè định tuyến phân vùng bằng sơ đồ của riêng bạn.

Nếu tất cả các khóa dẫn đến lẻ  hoặc chẵn , thì chỉ có hai phân vùng sẽ chứa bất kỳ dữ liệu nào. Điều này dẫn đến sự mất cân đối đáng kể trong dữ liệu.

Thêm nhiều máy chủ để tăng dung lượng sẽ không hiệu quả. Thêm nhiều phân vùng sẽ không hiệu quả.

Dự kiến

Các truy vấn Hazelcast bình thường trả về toàn bộ các đối tượng: khóa, giá trị hoặc cả hai (mục nhập).

Như một tối ưu hóa, dự kiến ​​có sẵn. Điều này cho phép bạn chọn các trường bạn muốn từ các đối tượng được truy vấn cho tập kết quả.

Nếu tập kết quả nhỏ hơn vì nó không chứa các trường không mong muốn, vì vậy nó sẽ truyền nhanh hơn.

Truy vấn trên các phím

Đây IMaplà một cửa hàng khóa-giá trị. Các truy vấn thường được biểu thị dưới dạng các vị từ để khớp với giá trị.

Ngoài ra, vị ngữ cũng có thể được mã hóa để khớp với khóa.

Kiểm tra bình đẳng sẽ được mã hóa là __key = 1. Điều này là vô nghĩa. Nếu bạn có khóa đầy đủ, thì  IMap.get(key) sẽ luôn nhanh hơn, vì nó không phải tìm kiếm.

Tìm kiếm chính rất hữu ích trong các tìm kiếm so sánh ( __key > 1) và tìm kiếm ký tự đại diện ( __key LIKE "John%").

Tham gia

Tham gia có nhiều vấn đề hơn.

Hãy nhớ rằng trường hợp sử dụng chính IMaplà để chia tỷ lệ, trong đó dữ liệu quá lớn để phù hợp với một JVM và được chia thành các phần để phân tán tải của lưu trữ.

Gần như là tiên đề rằng nếu dữ liệu đã bị tách ra vì nó lớn, nó không thể được nối vì nó lớn.

Điều này nói chung là đúng, vì vậy các tham gia không được Hazelcast IMDG hỗ trợ. Nhưng có thể trong một số trường hợp cụ thể, như chúng ta sẽ thấy trong ví dụ sử dụng Hazelcast Jet.

Ví dụ

Ví dụ sử dụng hai IMaps chính và xuất phát một phần ba.

Đầu tiên IMapđược gọi person và chứa thông tin về mọi người. Chìa khóa là một hợp chất của tên và họ. Giá trị là ngày sinh của người đó.

Thứ hai IMapđược gọi là " deaths và ghi lại ngày chết đối với một số người. Ngày chết được giữ trong một hồ sơ riêng biệt kể từ ngày sinh; một phần để làm cho ví dụ trở nên khó khăn hơn và cũng không phải vì tất cả mọi người sẽ chết. , đây là một mô hình dữ liệu không chuẩn hóa.

Thứ ba IMapđược gọi là  life. Nó giữ tuổi của người dân khi họ chết, và được bắt nguồn từ hai bản đồ trước đó, giống như một quan điểm cụ thể hóa trong cơ sở dữ liệu quan hệ.

Thành phần chính

Hai phần của ví dụ có giá trị xem xét kỹ hơn.

PersonKey.java

Các PersonKeylớp học định nghĩa quan trọng đối với các   person bản đồ. Nó là hợp chất của tên và họ.

Điều này được định nghĩa là PartitionAwarevà định nghĩa một chức năng định tuyến getPartitionKey(), trả về chữ cái đầu tiên của tên cuối cùng.

Vì vậy, đối với hai người John Doe  và Jane Doe , lựa chọn phân vùng sẽ dựa trên "D" và cả hai sẽ đi đến cùng một phân vùng.

Đây là (cố tình) một thuật toán xấu để chọn.

Để bắt đầu, chỉ có 26 chữ cái trong bảng chữ cái phương tây sẽ được sử dụng cho tên cuối cùng. Vì vậy, với 271 phân vùng mặc định, 26 có thể được sử dụng và 245 sẽ luôn trống.

Và hơn nữa, không phải tất cả các tên xảy ra với cùng tần số. Vì vậy, phân vùng chứa những người có họ bắt đầu bằng "M" có thể sẽ có số lượng khác với phân vùng giữ những người có họ bắt đầu bằng "P".

Trở về:

this.lastName;

Thay vì:

this.lastName.charAt(0);

Sẽ là một cải tiến đáng kể để cân bằng dữ liệu.

LifeAgeValueExtractor.java

Đối với life bản đồ, mục nhập dữ liệu là LifeValue, giữ ngày sinh và ngày chết.

Điều gì LifeAgeValueExtractorlàm cho một trường thứ ba ảo ( age) trong thời gian chạy bằng cách trừ sinh ra khỏi cái chết.

Trường mới  age này sau đó có thể sử dụng trong các truy vấn giống như bất kỳ trường hợp nào khác.

Chạy ví dụ

Ví dụ sử dụng Spring Boot, vì vậy bạn cần chạy mvn installđể tạo tệp JAR thực thi.

Sau đó, chạy lệnh này để bắt đầu một phiên bản Hazelcast:

java -jar target/project-key-0.1-SNAPSHOT.jar

Lý tưởng nhất, chạy này hai lần hoặc nhiều hơn cùng một lúc. Các bước sau của ví dụ hiển thị phân phối dữ liệu giữa các thành viên, vì vậy một người sẽ không đủ.

Bước 1: Tải dữ liệu kiểm tra

Từ bất kỳ trường hợp đang chạy nào, hãy nhập  load dưới dạng một lệnh trên dòng lệnh.

Mã trong TestDataLoader.load()sẽ tải sáu bản ghi vào person bản đồ và bốn deaths bản đồ vào  bản đồ.

Chạy list lệnh để hiển thị dữ liệu này.

Bước 2: Truy vấn chính # 1

Chạy lệnh howard để truy vấn tất cả những người có họ "Howard".

Mã nằm trong MyCommands.howard()và dòng quan trọng là đây:

new SqlPredicate("__key.lastName = 'Howard'")

Điều này trả về tất cả các giá trị trong person bản đồ khớp với vị từ trên.

Vì các giá trị chỉ chứa ngày sinh, điều này không hữu ích lắm. Đầu ra cho thấy ngày sinh nhưng không phải tên.

Bước 3: Truy vấn chính # 2

Bây giờ, chạy lệnh howard2.

Đây là một sàng lọc cho phần trước, thêm phần chiếu này:

Projections.multiAttribute("dateOfBirth", "__key.firstName");

Vì vậy, truy vấn đang tìm kiếm kết quả dựa trên tên cuối cùng nhưng trả về tên và dữ liệu sinh.

Điều này hiệu quả hơn cho việc chuyển mạng hơn là trả lại toàn bộ mục nhập. Truyền dữ liệu ít hơn trên mạng so với trả về mục nhập cho người gọi chỉ để một số trường không được hiển thị (ví dụ: tên cuối cùng không được in).

Bước 4: Vị trí dữ liệu

Chạy locationlệnh. Đối với mỗi bản ghi trong  personbản đồ, phân vùng và lưu trữ JVM được in.

Giả sử bạn đang chạy hai hoặc nhiều JVM tại thời điểm này, bạn sẽ thấy một số bản ghi nằm trên các JVM khác nhau.

Do PartitionAwaremã hóa, bạn sẽ thấy tất cả các bản ghi cho tên cuối cùng "Howard" nằm trong cùng một phân vùng.

Nếu bạn có thể, hãy thêm hoặc xóa một số JVM máy chủ cho cụm và chạy lại lệnh này sau khi tự động cân bằng lại. Các phân vùng có thể đã di chuyển và nếu hồ sơ "Howard" đã di chuyển, chúng vẫn được giữ cùng nhau.

Bước 5: Tham gia

Các join lệnh chạy một đường ống dẫn Jet để kéo dữ liệu từ hai IMaps và đầu ra kết quả một phần ba IMap.

Trong Hazelcast IMDG, các phép nối không được hỗ trợ. Các truy vấn chạy trên một bản đồ tại một thời điểm và nếu dữ liệu quá lớn, nó cần được phân phối trên nhiều nút, thì nói chung, nó lớn đến mức kết hợp hai hoặc nhiều bản đồ sẽ không phù hợp với bộ nhớ.

Đây là nơi Jet cho mượn một bàn tay. Một đường ống phản lực tám bước được xác định.

  • Bước 1 và 2 đọc từ IMaptên được đặt person và giảm nó xuống chỉ các trường cần thiết,  firstName và dateOfBirth.
  • Bước 3 và 4 làm tương tự cho IMap deaths, dẫn đến các cặp firstName và  dateOfDeath.
  • Bước 5 có hai luồng đầu vào:  ("firstName", "dateOfBirth") và ("firstName", "dateOfDeath), vì vậy việc nối dễ dàng và tạo ra  (("firstName", "dateOfBirth"), "dateOfDeath").
  • Bước 6 lọc ra kết quả trong đó  dateOfDeath là null. Bước 5 cố gắng làm phong phú luồng Bước 2 bằng luồng Bước 4 nhưng không phải lúc nào cũng có thể khớp.
  • Bước 7 và 8 chuyển đổi đầu ra từ Bước 6 thành mục nhập bản đồ và lưu nó vào IMaptên  life.

Có sáu hàng dữ liệu thử nghiệm trên person bản đồ và bốn hàng  deaths.

Vì vậy, điều này mang lại tiềm năng cho 24 kết hợp, sẽ không tràn bộ nhớ. Trên thực tế, chỉ có bốn trận đấu, vì vậy nó thậm chí còn ít hơn.

Tuy nhiên, số lượng trận đấu phù hợp với bộ nhớ bởi may mắn, không phải thiết kế. Nếu nhiều hàng dữ liệu thử nghiệm được thêm vào, tại một số điểm, số lượng kết quả có thể quá lớn.

Sự tham gia này xảy ra để hoạt động, nhưng không đảm bảo rằng nó sẽ không dẫn đến OutOfMemoryErrortương lai nếu có thêm dữ liệu.

Bước 6: Liệt kê

Vì phép nối được mã hóa để gửi đầu ra tới một IMap, kết quả không thể nhìn thấy ngay lập tức.

Chạy lệnh   list để xuất nội dung của ba bản đồ. Bây giờ tham gia đã chạy,  life bản đồ đã được điền.

Bước 7: Cũ nhất

Bước cuối cùng là tìm ra mục nào trong bốn mục trong life bản đồ tồn tại lâu nhất. Chạy lệnh  longevity.

Mã đầu tiên không:

int max = lifeMap.aggregate(Aggregators.integerMax("age"));

Để tìm tuổi già nhất. Hãy nhớ rằng  age lĩnh vực này là ảo; nó không thực sự là một trường trong LifeValueđối tượng nhưng có nguồn gốc.

Sau đó:

Set<String> keySet = lifeMap.keySet(new SqlPredicate("age = " + max));

Được chạy để tìm tất cả những người sống lâu đó.

Tóm lược

Sử dụng __key tiền tố cho phép bạn sử dụng một khóa hoặc một phần của khóa trong vị ngữ tìm kiếm.

Sử dụng  PartitionAwaretrên một phím ghi đè các quy tắc vị trí dữ liệu của Hazelcast. Điều này có thể dẫn đến tăng hiệu suất tuyệt vời bằng cách sử dụng kiến ​​thức cụ thể của tên miền hoặc có thể sai lầm nghiêm trọng.

Các phép chiếu làm giảm kích thước của tập kết quả thành các phần bạn cần.

Các tập hợp thực hiện tính toán phía máy chủ, tiếp tục giảm tập kết quả được gửi đến người gọi.

Tham gia không được hỗ trợ, nhưng có thể được xây dựng cho các tình huống cụ thể bằng Hazelcast Jet.

Hữu ích 11 Chia sẻ Viết bình luận 0
Đã xem 7993