Với sự xuất hiện của Microservice và thiết kế dịch vụ phân tán, Kafka trở nên phổ biến trong kiến trúc của nhiều sản phẩm. Trong bài viết này, tác giả sẽ giải thích cách Kafka lưu trữ dữ liệu như thế nào.
Đầu tiên, Kafka thường được nhắc đến với các khái niệm: Distributed, Replicated message queue. Mặc dù về kỹ thuật thì khái niệm Replicated message queue này đúng, nhưng nó có thể gây ra một số hiểu nhầm khi mà định nghĩa Message queue không được rõ ràng cho lắm. Thay cho từ này, bài viết sẽ đi vào định nghĩa Distributed và Replicated Commit Log. Theo nhận định của tác giả, khái niệm này (replicated commit log) mô tả chính xác hơn về Kafka vì nó giống cách chúng ta lưu trữ các đoạn nhật ký (Log) vào ổ đĩa. Và trong trường hợp này “nhật ký” là các message được Kafka lưu vào ổ.
Liên quan đến lưu trữ trong Kafka thì Partition và Topic là hai thuật ngữ sẽ luôn được nhắc đến. Partition là đơn vị lưu trữ message của Kafka. Và Topic có thể coi như là nơi chứa các partition đó.
Hãy bắt tay vào sử dụng Kafka để hiểu hơn về những khái niệm cơ bản này.
Tác giả sẽ tạo ra 1 Topic trong Kafka với 3 Partition.
1
|
kafka–topics.bat—create—topic freblogg—partitions3—replication–factor1—zookeeper localhost:2181
|
Khi truy cập thư mục chứa nhật ký (log) của Kafka, tác giả phát hiện ra có 3 thư mục đã được tạo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
$tree freblogg*
freblogg–0
|—00000000000000000000.index
|—00000000000000000000.log
|—00000000000000000000.timeindex
`—leader–epoch–checkpoint
freblogg–1
|—00000000000000000000.index
|—00000000000000000000.log
|—00000000000000000000.timeindex
`—leader–epoch–checkpoint
freblogg–2
|—00000000000000000000.index
|—00000000000000000000.log
|—00000000000000000000.timeindex
`—leader–epoch–checkpoint
|
Chúng ta có 3 thư mục bởi vì ta khởi tạo topic với 3 partition, điều này có nghĩa là mỗi partition là 1 thư mục trên tệp hệ thống. Bạn cũng sẽ thấy một số các file như index, log,… và chúng ta sẽ đề cập đến chúng sau.
Bạn có thể nhận ra một điều là Topic chỉ là một nhóm mang tính logic chứ không có gì đặc biệt khác, Partition mới là đơn vị lưu trữ trong Kafka và đó là thành phần vật lý thực sự được ghi trên ổ đĩa.
Partition
Về lý thuyết, Partition có để được mô tả như là một tập hợp (hoặc chuỗi) bất biến của các message. Tiến trình chỉ có thể nối thêm message vào partition chứ không thể xóa khỏi nó. Và “tiến trình” ở đây là ám chỉ đến Producer. Một Producer không thể xóa message khỏi topic.
Tác giả sẽ gửi một số message vào topic:
1
2
3
4
5
6
|
$ls–lh freblogg–0
total20M
–freblogg19712110MAug508:2600000000000000000000.index
–freblogg1971210Aug508:2600000000000000000000.log
–freblogg19712110MAug508:2600000000000000000000.timeindex
–freblogg1971210Aug508:26leader–epoch–checkpoint
|
Ban đầu có thể thấy index file có dung lượng tổng cộng là 20MB, trong khi file log trống rỗng. Tương tự với 2 thư mục còn lại: freblogg-1 và freblogg-2.
Bây giờ hãy thử chuyển message vào xem điều gì xảy ra. Để gửi được message, ta sử dụng producer như sau:
1
|
kafka–console–producer.bat—topic freblogg—broker–list localhost:9092
|
2 message với nội dung “hello world” đã được gửi. Bây giờ ta sẽ truy vấn dung lượng folder một lần nữa:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
$ls–lh freblogg*
freblogg–0:
total20M
–freblogg19712110MAug508:2600000000000000000000.index
–freblogg1971210Aug508:2600000000000000000000.log
–freblogg19712110MAug508:2600000000000000000000.timeindex
–freblogg1971210Aug508:26leader–epoch–checkpoint
freblogg–1:
total21M
–freblogg19712110MAug508:2600000000000000000000.index
–freblogg19712168Aug510:1500000000000000000000.log
–freblogg19712110MAug508:2600000000000000000000.timeindex
–freblogg19712111Aug510:15leader–epoch–checkpoint
freblogg–2:
total21M
–freblogg19712110MAug508:2600000000000000000000.index
–freblogg19712179Aug509:5900000000000000000000.log
–freblogg19712110MAug508:2600000000000000000000.timeindex
–freblogg19712111Aug509:59leader–epoch–checkpoint
|
2 message đã đi vào 2 partition và bạn có thể thấy dung lượng file log của chúng thay đổi. Đó là bởi vì message trong partition được lưu vào file “xxx.log”. Để xác minh điều này, bạn có thể kiểm tra nội dung của các file log này:
1
2
|
$cat freblogg–2/*.log
@^@^B°£æÃ^@^K^Xÿÿÿÿÿÿ^@^@^@^A“^@^@^A^VHello World^@
|
Bạn có thể thấy từ “hello world” xuất hiện ở phía cuối, và nó cho ta biết rằng file đã được cập nhật thành công.
Chú ý một điều là message thứ nhất được chuyển vào partition thứ 3 (freblogg-2) và message còn lại được chuyển vào partition thứ 2 (freblogg-1). Đó là vì Kafka tùy ý chọn partition cho message đầu tiên và phân phối các message tiếp theo vào các partition khác theo một vòng tròn. Nếu có một message thứ 3 xuất hiện, nó sẽ được chuyển tới partition đầu tiên (freblogg-0) và thứ tự chọn này sẽ tiếp tục với các message tiếp theo sau. Chúng ta có thể ép Kafka chọn cùng 1 partition cho message bằng cách thêm key cho message. Kafka sẽ lưu tất cả các message có cùng key vào cùng 1 partition.
Mỗi một tin nhắn mới trong partition sẽ nhận được một Id lớn hơn Id của message trước nó. Id này được gọi là Offset. Vậy nên, message đầu tiên sẽ có offset là 0, message thứ 2 có offset là 1 và tiếp tục như vậy.
Những id offset này luôn luôn tăng dần lên.
Segment
Chúng ra đã xem xét file index và file log trong thư mục partition. Partition không phải là tầng lưu trữ thấp nhất. Mỗi một partition được chia nhỏ thành các segment. Một segment đơn giản là một tập hợp message trong một partition. Thay vì lưu trữ message của một partition trong 1 file, Kafka chia chúng thành các phần, gọi là Segment.
Một trong những ưu điểm quan trọng nhất của nó là giúp cho việc xóa dữ liệu dễ dàng. Như đã đề cập, từ góc độ dữ liệu thì partition là bất biến. Nhưng Kafka vẫn xóa bỏ message của topic theo cơ chế gọi là “Retention policy”. Xóa một segment đơn giản hơn nhiều việc xóa hẳn dữ liệu khỏi file, đặc biệt khi Producer muốn tiếp tục ghi dữ liệu vào file đó.
1
2
3
4
5
6
|
$ls–lh freblogg–0
total20M
–freblogg19712110MAug508:2600000000000000000000.index
–freblogg1971210Aug508:2600000000000000000000.log
–freblogg19712110MAug508:2600000000000000000000.timeindex
–freblogg1971210Aug508:26leader–epoch–checkpoint
|
Đoạn 00000000000000000000 đầu tiên ở tên các file index và log trong thư mục partition là tên của segment.
Mỗi một segment có các file segment.log, segment.index và segment.timeindex.
Kafka lưu trữ message trong các file segment này. Luôn luôn có một segment ở trạng thái “active” để Kafka ghi message vào. Một khi kích thước segment đạt tới giới hạn, một file segment mới sẽ được tạo ra và được đánh dấu là “active”.
Mỗi một file segment được tạo ra sẽ lấy số offset của message đầu tiên để tạo thành tên file.
1
2
3
4
5
6
7
8
9
10
|
Segment——Offset
—–0————–0
—–0————–1
—–0————–2
—–3————–3
—–3————–4
—–3————–5
—–6————–6
—–6————–7
—–6————–...
|
Trong ví dụ trên, segement 0 chứa message từ offset 0 đến 2, segment 3 chứa message từ offset 3 đến 5… Segment 6 là segment cuối cùng và là segment đang ở trạng thái active.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
$ls–lh freblogg*
freblogg–0:
total20M
–freblogg19712110MAug508:2600000000000000000000.index
–freblogg1971210Aug508:2600000000000000000000.log
–freblogg19712110MAug508:2600000000000000000000.timeindex
–freblogg1971210Aug508:26leader–epoch–checkpoint
freblogg–1:
total21M
–freblogg19712110MAug508:2600000000000000000000.index
–freblogg19712168Aug510:1500000000000000000000.log
–freblogg19712110MAug508:2600000000000000000000.timeindex
–freblogg19712111Aug510:15leader–epoch–checkpoint
freblogg–2:
total21M
–freblogg19712110MAug508:2600000000000000000000.index
–freblogg19712179Aug509:5900000000000000000000.log
–freblogg19712110MAug508:2600000000000000000000.timeindex
–freblogg19712111Aug509:59leader–epoch–checkpoint
|
Trong trường hợp của bài viết, chúng ta chỉ có 1 segment ứng với mỗi một partition và có tên là 00000000000000000000. Vì không thấy có file segment nào khác nên 00000000000000000000 là segment active của mỗi partition đó. Kích thước segment mặc định là hẳn 1GB, nhưng dù vậy trong trường hợp này tác giả đã chỉnh cấu hình Kafka để mỗi segment chỉ có thể chứa 3 message.
Giả sử trạng thái hiện tại của partition freblogg-2 đang có 3 message:
1
2
|
Partition:freblogg–2
Segment0:(“hello world”–offset0)(“amazon”–offset1)(“kafka”–offset2)
|
Vì giới hạn 1 segment có 3 message nên nếu có 1 message mới xuất hiện trong partition này thì Kafka sẽ tự động đóng segment này lại, tạo 1 segment mới, đánh dấu active cho nó và lưu message mới này trong file log của segment mới.
1
2
3
4
5
6
7
8
|
freblogg–2
|—00.index
|—00.log
|—00.timeindex
|—03.index
|—03.log
|—03.timeindex
`—
|
Bạn chú ý là tên của segment mới không phải là 01. Thay vào đó là 03.index, 03.log như giải thích phía trên (tên của segment sẽ được đặt theo số id offset của message đầu tiên trong segment đó).
1
2
3
|
Partition:freblogg–2
Segment0:(“hello world”–offset0)(“amazon”–offset1)(“kafka”–offset2)
Segment3:(“newmessage”–offset3)
|
Một trong những thao tác thường xuyên của Kafka là đọc message ở 1 vị trí offset cụ thể. Đối với thao tác này, chi phí thực thi sẽ rất lớn nếu truy cập thẳng vào file log để tìm offset vì kích thước của nó có thể rất lớn (mặc định 1GB). Trong trường hợp này, file index rất hữu dụng. File index này chứa thông tin offset và vị trí vật lý của message trong file log. File index cho file log ví dụ như sau:
Log file:
1
2
3
|
Offset—Position—————Time—————————Message——
——0————0————–1533443377944———–hello world—
——1————79————1533443388994—————amazon——
|
Index file:
1
2
3
|
Offset—–Position
—–0————0——
—–1————79—–
|
Nếu bạn cần đọc message ở offset 1, đầu tiên bạn cần tìm offset đó trong file index và lấy được vị trí của message là 79, sau đó truy xuất trực tiếp đến vị trí 79 trong file log và bắt đầu đọc message ra. Cách này rất hiệu quả vì có thể sử dụng tìm kiếm nhị phân để lấy chính xác offset cần tìm trong file index, và file này đã được sắp xếp theo id offset.
Topic
Điều quan trọng nhất cần nhớ là topic chỉ đơn thuần là nhóm logic của một vài partition.
Một topic có thể được phân phối trên nhiều broker, bằng việc sử dụng nhiều partition. Và 1 partition vẫn chỉ nằm trên 1 broker duy nhất. Mỗi một topic có một định danh riêng và partition cũng sẽ được đặt theo các định danh này.
Replication
Khi tạo một topic trong Kafka, chúng ta cần xác định rõ hệ số nhân rộng (replication factor) cho topic đó. Giả sử chúng ta có 2 broker và thiết lập replication-factor bằng 2. Kafka sẽ đảm bảo mỗi partition của topic này có phần sao lưu và bản sao (backup/replica). Cách phân phối partition này khá giống với cách HDFS phân phối các khối dữ liệu giữa các node.
Giả sử ta có topic freblogg, replication-factor được đặt bằng 2. Kết quả là sẽ có 3 partition được chia ra như sau:
1
2
3
4
5
6
7
8
9
10
|
Broker0:(leader)
Freblogg–1
Freblogg–0
Freblogg–2
Broker1:(replica/follower)
Freblogg–0
Freblogg–2
Freblogg–1
|
Kể cả khi bạn có partition được nhân rộng (replicated parition) trên một broker khác thì Kafka cũng không cho phép bạn đọc từ đó, bởi vì trong mỗi partition được nhân bản, có một Leader và những phần còn lại chỉ là Follower, là phần sao lưu của nó. Khi Leader mất, một trong những partition được đồng bộ đó sẽ được chọn làm Leader mới và bạn có thể đọc message từ partition đó.
Rõ ràng vì thế mà Leader và Follower của một partition không bao giờ nên ở trong cùng một broker.
Tóm tắt
- Dữ liệu trong Kafka được lưu theo topic.
- Các topic được phân vùng với nhau vào các partition.
- Mỗi partition được chia nhỏ thành các segment.
- Mỗi segment có một file log chứa nội dung message và một file index chứa vị trí của message trong file log.
- Các partition khác nhau của một topic có thể nằm trên các broker khác nhau, nhưng mỗi một partition chỉ thuộc về 1 broker duy nhất.
- Các partition được tự động nhân bản, bạn chỉ có thể đọc message từ phần đó khi nó trở thành Leader mới.
Phạm Duy Anh (Dịch giả)
Theo Medium