1. Tổng quan
Một bản sao (replica) được coi là đồng bộ nếu nó là leader của partition, hoặc là bản sao follower và đáp ứng các điều kiện sau:
- Duy trì phiên (session) hoạt động với ZooKeeper, tức là trong 6 giây qua (có thể cấu hình), nó đã gửi tín hiệu "heartbeat" đến ZooKeeper.
- Nhận message từ leader trong 10 giây qua (có thể cấu hình).
- Nhận được các message mới nhất từ leader trong 10 giây qua. Điều này có nghĩa là bản sao không chỉ nhận message đều đặn từ leader mà còn phải đảm bảo không có độ trễ ít nhất một lần trong 10 giây (có thể cấu hình).
Nếu bản sao mất kết nối với ZooKeeper, ngừng nhận message mới, hoặc bị trễ quá lâu không thể bắt kịp trong vòng 10 giây, nó sẽ được coi là không đồng bộ. Khi kết nối lại với ZooKeeper và bắt kịp các message mới nhất từ leader, bản sao không đồng bộ sẽ trở lại trạng thái đồng bộ. Thông thường, quá trình này diễn ra nhanh chóng sau khi sự cố mạng được giải quyết, nhưng có thể mất nhiều thời gian hơn nếu broker chứa bản sao đó bị ngừng hoạt động trong thời gian dài.
2. Một số cấu hình broker quan trọng
Hệ số sao chép (Replication Factor)
Hệ số sao chép (Replication Factor) Cấu hình ở mức topic là replication.factor
. Ở cấp độ broker, chúng ta kiểm soát default.replication.factor
cho các topic được tạo tự động.
Giả sử các topic có hệ số sao chép là 3, nghĩa là mỗi partition được sao chép ba lần trên ba broker khác nhau. Đây cũng là cấu hình mặc định của Kafka, nhưng người dùng có thể thay đổi cấu hình này theo ý muốn. Ngay cả sau khi một topic đã tồn tại, chúng ta vẫn có thể thêm hoặc xóa các bản sao và thay đổi hệ số sao chép bằng công cụ gán bản sao của Kafka.
Nếu hệ số sao chép là N cho phép chúng ta mất đi N-1 broker mà vẫn có thể đọc và ghi dữ liệu vào topic. Do đó, hệ số sao chép càng cao, độ sẵn sàng, độ tin cậy càng cao và rủi ro thảm họa càng ít. Tuy nhiên, với hệ số sao chép là N, chúng ta sẽ cần ít nhất N broker và sẽ lưu trữ N bản sao của dữ liệu, nghĩa là chúng ta sẽ cần dung lượng ổ đĩa gấp N lần. Về cơ bản, chúng ta đang đánh đổi giữa độ sẵn sàng và tài nguyên phần cứng.
Vậy làm thế nào để xác định số lượng bản sao (replica) phù hợp cho một topic? Dưới đây là một số yếu tố quan trọng cần xem xét đánh giá:
- Tính khả dụng (Availability): Một partition chỉ có một bản sao sẽ trở nên không khả dụng ngay cả khi chỉ khởi động lại một broker duy nhất. Càng có nhiều bản sao, tính khả dụng càng cao.
- Độ trễ đầu cuối (End-to-end latency): Mỗi bản ghi được tạo ra phải được sao chép, đồng đến tất cả các bản sao trước khi sẵn sàng để consumer tiêu thụ. Về lý thuyết, càng nhiều bản sao, khả năng một trong số đó có thể bị chậm càng cao, và điều này có thể làm chậm consumer. Trong thực tế, nếu một broker bị chậm vì bất kỳ lý do nào, nó sẽ làm chậm tất cả các client cố gắng sử dụng nó, bất kể hệ số sao chép.
- Chi phí (Cost): Đây là lý do phổ biến nhất để sử dụng hệ số sao chép nhỏ hơn 3 cho dữ liệu không quan trọng. Càng nhiều bản sao của dữ liệu, chi phí lưu trữ và mạng càng cao. Vì nhiều hệ thống lưu trữ đã sao chép mỗi khối dữ liệu gấp 3 lần, đôi khi hợp lý để giảm chi phí bằng cách cấu hình Kafka với hệ số sao chép là 2. Tuy nhiên, cần lưu ý rằng điều này sẽ giảm tính khả dụng so với hệ số sao chép là 3, nhưng tính bền vững sẽ được đảm bảo bởi thiết bị lưu trữ.
- Tính bền vững (Durability): Mỗi bản sao là một bản sao của tất cả dữ liệu trong một partition. Nếu một partition chỉ có một bản sao và ổ đĩa không thể sử dụng được vì bất kỳ lý do nào, chúng ta sẽ mất toàn bộ dữ liệu trong partition. Với nhiều bản sao hơn, đặc biệt là trên các thiết bị lưu trữ khác nhau, khả năng mất toàn bộ dữ liệu sẽ giảm.
- Thông lượng (Throughput): Với mỗi bản sao bổ sung, chúng ta có thẻ tính toán lưu lượng giữa các broker. Nếu chúng ta tạo dữ liệu cho một partition với tốc độ 10 MBps, thì một bản sao duy nhất sẽ không tạo ra lưu lượng sao chép nào. Nếu có 2 bản sao, sẽ có 10 MBps lưu lượng sao chép, với 3 bản sao sẽ là 20 MBps, và với 5 bản sao sẽ là 40 MBps. Điều này cần được tính đến khi lên kế hoạch mở rộng cho kích thước và dung lượng cụm.
Việc phân bổ các bản sao (replicas) cũng rất quan trọng. Kafka luôn đảm bảo rằng mỗi bản sao cho một partition được đặt trên các broker riêng biệt. Tuy nhiên, đôi khi điều này vẫn chưa đủ an toàn. Nếu tất cả các bản sao của một phân vùng được đặt trên các broker nằm cùng một rack, và switch của rack đó gặp sự cố, chúng ta sẽ mất khả năng truy cập vào partition đó, bất kể hệ số sao chép lúc này là bao nhiêu. Để bảo vệ khỏi sự cố ở mức rack, mình khuyến nghị đặt các broker trên nhiều rack khác nhau và sử dụng tham số cấu hình broker.rack để định cấu hình tên rack cho mỗi broker. Nếu các tên rack được cấu hình, Kafka sẽ đảm bảo các bản sao của một phân vùng được phân bố trên nhiều rack để đảm bảo tính khả dụng cao hơn. Khi chạy Kafka trong các môi trường cloud, thường coi các vùng khả dụng (availability zones) là các rack riêng biệt.
Bầu chọn leader không an toàn
Cấu hình này chỉ có sẵn ở cấp độ broker (và trên thực tế là cấp độ toàn cụm). Tên của tham số là unclean.leader.election.enable, và mặc định được đặt là false.
Như đã giải thích trước đó, khi leader của partition không còn khả dụng, một trong các bản sao đồng bộ sẽ được chọn làm leader mới. Quy trình bầu chọn này được coi là "an toàn" vì nó đảm bảo không mất mát dữ liệu đã được cam kết — theo định nghĩa, dữ liệu đã cam kết phải tồn tại trên tất cả các bản sao đồng bộ.
Nhưng điều gì sẽ xảy ra khi không còn bản sao đồng bộ nào ngoại trừ leader vừa ngừng hoạt động?
Tình huống này có thể xảy ra trong hai kịch bản:
- Partition có ba bản sao, và hai bản sao follower bị mất kết nối (giả sử hai broker bị sập). Trong tình huống này, khi các producers tiếp tục ghi dữ liệu vào leader, tất cả các message đều được xác nhận (ack) và cam kết (vì leader là bản sao đồng bộ duy nhất). Giờ hãy tưởng tượng rằng leader cũng ngừng hoạt động. Trong trường hợp này, nếu một trong các bản sao không đồng bộ khởi động trước, thì chúng ta sẽ chỉ còn một bản sao không đồng bộ là bản sao duy nhất có sẵn cho partition.
- Partition có ba bản sao, và do sự cố mạng, hai bản sao theo dõi bị tụt lại phía sau, mặc dù chúng vẫn đang hoạt động và sao chép, nhưng không còn đồng bộ nữa. Leader vẫn tiếp tục chấp nhận các message như bản sao đồng bộ duy nhất. Bây giờ, nếu leader ngừng hoạt động, chỉ còn lại các bản sao không đồng bộ sẵn sàng trở thành leader.
Trong cả hai trường hợp này, chúng ta cần phải đưa ra quyết định rất khó khăn:
- Nếu không cho phép bản sao không đồng bộ trở thành leader mới, partition sẽ không thể hoạt động cho đến khi leader cũ (và bản sao đồng bộ cuối cùng) được khôi phục. Điều này có thể mất nhiều giờ trong một số trường hợp, chẳng hạn như cần phải thay thế phần cứng.
- Nếu cho phép bản sao không đồng bộ trở thành leader mới, chúng ta sẽ mất tất cả các message đã được ghi vào leader cũ trong thời gian bản sao đó không đồng bộ, và điều này có thể gây ra sự không nhất quán cho consumer. Ví dụ, trong khi bản sao 0 và 1 không hoạt động, các message có offset từ 100–200 đã được ghi vào bản sao 2 (khi đó là leader). Bây giờ, nếu bản sao 2 ngừng hoạt động và bản sao 0 quay lại, bản sao 0 chỉ có các message từ 0–100, nhưng không có từ 100–200. Nếu bản sao 0 được phép trở thành leader mới, nó sẽ cho phép producer ghi message mới và consumer sẽ đọc chúng. Điều này dẫn đến việc một số consumer có thể đã đọc message cũ từ 100–200, trong khi những người khác đọc message mới từ 100–200, tạo ra sự không nhất quán. Hơn nữa, khi bản sao 2 trở lại và trở thành follower của leader mới, nó sẽ xóa tất cả các message mà nó có nhưng không tồn tại trên leader mới, và những message này sẽ không còn khả dụng cho bất kỳ consumer nào trong tương lai.
Tóm lại, nếu cho phép các bản sao không đồng bộ trở thành leader, chúng ta có nguy cơ mất dữ liệu và gây ra sự không nhất quán. Ngược lại, nếu không cho phép, hệ thống sẽ giảm tính sẵn sàng, vì phải chờ leader gốc khôi phục trước khi phân vùng hoạt động trở lại.
Theo mặc định, tùy chọn unclean.leader.election.enable được đặt thành false, ngăn không cho các bản sao không đồng bộ trở thành leader. Đây là phương án an toàn nhất để ngăn chặn việc mất dữ liệu. Tuy nhiên, trong những tình huống đặc biệt, một số partition sẽ không hoạt động cho đến khi được khôi phục thủ công. Trong trường hợp này, quản trị viên có thể xem xét, quyết định chấp nhận việc mất dữ liệu để phân vùng hoạt động lại bằng cách tạm thời chuyển cấu hình này sang true trước khi khởi động lại cụm Kafka. Sau khi hệ thống được khôi phục, đừng quên chuyển cấu hình này trở lại false.
Số bản sao tối thiểu đang đồng bộ
Cả cấu hình mức topic và mức broker đều sử dụng tham số min.insync.replicas.
Như chúng ta đã thấy, có những trường hợp dù cấu hình một topic có ba bản sao, nhưng chúng ta có thể chỉ còn lại một bản sao đồng bộ. Nếu bản sao này không còn khả dụng, chúng ta có thể phải đối mặt với sự lựa chọn khó khăn giữa tính sẵn sàng và tính nhất quán. Một phần của vấn đề là theo các đảm bảo về độ tin cậy của Kafka, dữ liệu được coi là đã cam kết khi nó được ghi vào tất cả các bản sao đang đồng bộ, ngay cả khi "tất cả" chỉ còn là một bản sao. Điều này dẫn đến nguy cơ mất dữ liệu nếu bản sao đó không khả dụng.
Khi chúng ta muốn đảm bảo rằng dữ liệu đã cam kết được ghi vào nhiều hơn một bản sao, ta cần thiết lập số lượng tối thiểu của các bản sao đồng bộ ở giá trị cao hơn. Ví dụ, nếu một topic có ba bản sao và chúng ta đặt min.insync.replicas
là 2, thì producer chỉ có thể ghi dữ liệu vào partition trong topic nếu ít nhất hai trong ba bản sao đang đồng bộ.
Khi cả ba bản sao đều đồng bộ, mọi thứ diễn ra bình thường. Điều này cũng đúng khi một trong số các bản sao không khả dụng. Tuy nhiên, nếu hai trong số ba bản sao không khả dụng, các broker sẽ không chấp nhận các yêu cầu ghi dữ liệu nữa. Thay vào đó, các producer cố gắng gửi dữ liệu sẽ gặp phải ngoại lệ NotEnoughReplicasException. Các consumer vẫn có thể tiếp tục đọc dữ liệu đã tồn tại. Với cấu hình này, một bản sao đồng bộ duy nhất sẽ trở thành chế độ chỉ đọc (read-only). Điều này giúp tránh được tình trạng dữ liệu được ghi và tiêu thụ nhưng sau đó lại biến mất khi xảy ra sự cố bầu chọn lại leader không đồng bộ. Để khắc phục tình trạng chỉ đọc này, chúng ta cần làm cho một trong hai partition không khả dụng hoạt động trở lại (có thể là khởi động lại broker) và chờ nó bắt kịp và đồng bộ hóa.
Giữ cho các bản sao luôn đồng bộ
Như đã đề cập trước đó, các bản sao không đồng bộ làm giảm độ tin cậy tổng thể hệ thống, vì vậy điều quan trọng là phải tránh tình trạng này càng nhiều càng tốt. Chúng ta cũng đã giải thích rằng một bản sao có thể trở nên không đồng bộ theo hai cách: hoặc nó mất kết nối với ZooKeeper, hoặc không theo kịp leader và dẫn đến việc chậm trễ trong quá trình sao chép. Kafka có hai cấu hình broker để kiểm soát mức độ nhạy cảm của cụm đối với hai tình huống này.
Cấu hình zookeeper.session.timeout.ms
xác định khoảng thời gian mà một broker của Kafka có thể ngừng gửi tín hiệu "heartbeat" đến ZooKeeper mà ZooKeeper vẫn chưa coi broker đó là không hoạt động và loại nó ra khỏi cụm. Trong phiên bản 2.5.0, giá trị này đã được tăng từ 6 giây lên 18 giây để cải thiện tính ổn định của các cụm Kafka trong môi trường cloud, nơi độ trễ mạng có sự biến động lớn hơn. Nói chung, chúng ta muốn thời gian này đủ cao để tránh việc ngắt kết nối ngẫu nhiên do GC hoặc điều kiện mạng, nhưng vẫn đủ thấp để phát hiện kịp thời các broker có sự cố.
Nếu một bản sao không lấy dữ liệu từ leader hoặc không bắt kịp các message mới nhất trên leader trong thời gian dài hơn giá trị của replica.lag.time.max.ms
, bản sao đó sẽ bị coi là không đồng bộ. Giá trị này đã được tăng từ 10 giây lên 30 giây trong phiên bản 2.5.0 để cải thiện khả năng chịu lỗi của cụm và tránh việc ngắt kết nối không cần thiết. Lưu ý rằng giá trị cao hơn này cũng ảnh hưởng đến độ trễ tối đa đối với consumer—với giá trị cao hơn, có thể mất tới 30 giây để một message đến được tất cả các bản sao và consumer mới có thể truy cập nó.
Lưu trữ trên đĩa
Kafka đôi khi sẽ xác nhận các message (ack) ngay cả khi chúng chưa được lưu trữ trên đĩa, chỉ dựa vào số lượng bản sao đã nhận message. Kafka ghi dữ liệu xuống đĩa khi xoay vòng segment (mặc định là 1 GB) và trước khi khởi động lại, nhưng thường thì nó dựa vào bộ nhớ đệm của Linux để ghi dữ liệu khi bộ nhớ đầy. Ý tưởng chính là việc có ba máy chủ đặt ở các rack hoặc vùng khả dụng khác nhau, mỗi máy đều có một bản sao dữ liệu, an toàn hơn so với chỉ ghi dữ liệu xuống đĩa của leader. Điều này là do xác suất sự cố xảy ra đồng thời trên hai rack hoặc vùng là rất nhỏ. Tuy nhiên, Kafka cũng cho phép bạn cấu hình để ghi dữ liệu xuống đĩa thường xuyên hơn. Thông số flush.messages
cho phép kiểm soát số lượng message tối đa chưa được ghi xuống đĩa, và flush.ms
điều chỉnh tần suất ghi dữ liệu. Trước khi sử dụng, chúng ta nên tìm hiểu về cách fsync ảnh hưởng đến hiệu suất của Kafka và làm sao để giảm thiểu các tác động không tốt đến hệ thống.
3. Thông tin kết nối
Nếu anh em muốn trao đổi thêm về bài viết, hãy kết nối với mình qua LinkedIn và Facebook:
- LinkedIn: https://www.linkedin.com/in/nguyentrungnam/
- Facebook: https://www.facebook.com/trungnam.nguyen.395/
Rất mong được kết nối và cùng thảo luận!