Khi cần "gửi tin" giữa các thành phần qua Redis, bạn gặp hai lựa chọn tên nghe giống nhau: Pub/Sub và Streams. Dùng nhầm là một lỗi tốn kém — vì chúng khác nhau ở điều quan trọng nhất: message có được lưu lại không. Pub/Sub là một loa phát thanh: phát tới ai đang nghe, không lưu gì — ai không nghe lúc đó thì mất. Streams là một sổ ghi bền (append-only log): lưu lại mọi entry, đọc lại được, phát lại được, và có consumer group để chia việc cho nhiều worker — gần như một Kafka thu nhỏ bên trong Redis. Bài này (phần 6/12) đo thật cả hai và chỉ rõ khi nào dùng cái nào.

Pub/Sub: fire-and-forget, không lưu

redis-cli SUBSCRIBE tin        # đăng ký nghe kênh
redis-cli PUBLISH tin "..."    # phát tới MỌI subscriber đang nghe

Điểm cốt lõi: PUBLISH gửi tới các subscriber đang nghe ngay lúc đó. Nếu không ai đang nghe, message biến mất — Redis không lưu lại để phát sau. Hợp cho: thông báo realtime, chat, tín hiệu invalidate cache — những việc chấp nhận mất nếu người nhận không online.

Streams: append-only log bền

redis-cli XADD orders '*' user alice amount 100   # thêm entry, ID tự tăng
redis-cli XLEN orders                             # số entry
redis-cli XRANGE orders - +                       # đọc LẠI toàn bộ (được lưu)

Streams lưu mọi entry trong một log, mỗi entry có ID tự tăng (dạng timestamp-sequence). Đọc lại, phát lại thoải mái. Và có consumer group: nhiều consumer chia nhau xử lý, mỗi entry giao cho đúng một consumer trong group, XACK đánh dấu đã xử lý, XPENDING theo dõi entry đã giao nhưng chưa ack.

Ảnh chụp đoạn mã nền tối minh hoạ Pub/Sub và Streams hai cách nhắn tin khác nhau ở chỗ giữ hay vứt message, Pub/Sub là loa phát thanh ai nghe thì nghe không lưu Streams là sổ ghi bền lưu lại đọc lại chia việc cho nhiều worker. Pub/Sub fire-and-forget không lưu SUBSCRIBE tin đăng ký nghe kênh PUBLISH tin phát tới mọi subscriber đang nghe PUBLISH khi không ai nghe message mất không lưu lại đâu cả hợp realtime notification chat invalidate cache chấp nhận mất. Streams append-only log bền XADD orders user alice amount 100 thêm entry ID tự tăng XLEN orders số entry XRANGE orders đọc lại toàn bộ message được lưu message giữ trong log đọc lại được phát lại được như Kafka thu nhỏ. Streams consumer group chia việc cộng ack XGROUP CREATE orders g1 0 XREADGROUP GROUP g1 c1 COUNT 1 STREAMS orders c1 nhận 1 entry XACK orders g1 id đánh dấu đã xử lý xong XPENDING orders g1 xem entry đã giao nhưng chưa ack mỗi entry giao cho đúng 1 consumer trong group xử lý song song an toàn

Hình 1: Pub/Sub phát fire-and-forget, không lưu — mất nếu không ai nghe. Streams là append-only log: XADD lưu entry, XRANGE đọc lại, consumer group (XREADGROUP/XACK/XPENDING) chia việc cho nhiều consumer như Kafka thu nhỏ.

Đo thật: mất vs giữ

Ảnh chụp bảng kết quả đo thật Pub/Sub mất message Streams giữ và chia việc output thật redis:7 PUBLISH SUBSCRIBE XADD XRANGE XREADGROUP. Một Pub/Sub không ai nghe thì mất PUBLISH tin xin chao khi chưa có subscriber trả về 0 0 người nhận message mất có subscriber PUBLISH msg-A msg-B subscriber nhận msg-A msg-B message chỉ tới subscriber đang nghe lúc phát phát trước khi ai subscribe là mất luôn không lưu. Hai Streams message được lưu đọc lại được XADD 2 entry XLEN bằng 2 XRANGE orders đọc lại toàn bộ 225-0 user alice amount 100 226-0 user bob amount 250 mỗi entry có ID tự tăng timestamp-seq log giữ lại nên đọc lại phát lại được khác hẳn Pub/Sub. Ba consumer group chia entry cộng ack XREADGROUP g1 c1 alice 100 c1 nhận entry 1 XREADGROUP g1 c2 bob 250 c2 nhận entry khác XPENDING bằng 2 2 entry đã giao chưa ack c1 XACK 1 entry XPENDING bằng 1 còn 1 chưa ack Pub/Sub cho realtime chấp nhận mất notification chat Streams cho log bền cộng consumer group mỗi entry 1 consumer ack theo dõi đã xử lý như một Kafka thu nhỏ trong Redis

Hình 2: Đo thật. (1) Pub/Sub: PUBLISH khi chưa có subscriber → 0 nhận (mất); có subscriber → nhận msg-A, msg-B. (2) Streams: XADD 2 entry, XLEN=2, XRANGE đọc lại cả hai (alice 100, bob 250). (3) Consumer group: c1 nhận alice, c2 nhận bob; XPENDING=2, sau c1 XACK → XPENDING=1.

Kết quả thật:

  • ① Pub/Sub mất khi không ai nghe: PUBLISH tin "xin chao" khi chưa có subscriber trả về 0 (số subscriber nhận = 0 → message mất). Sau khi có subscriber, PUBLISH msg-A, msg-B → subscriber nhận đủ msg-A, msg-B. Vậy: message chỉ tới người đang nghe lúc phát; phát trước khi ai subscribe là mất luôn.
  • ② Streams giữ và đọc lại được: XADD 2 entry → XLEN=2, XRANGE orders - + đọc lại cả hai (alice 100, bob 250), mỗi cái có ID tự tăng. Message được lưu trong log — đọc lại, phát lại thoải mái, khác hẳn Pub/Sub.
  • ③ Consumer group chia việc: XREADGROUP g1 c1 nhận entry alice, XREADGROUP g1 c2 nhận entry khác (bob) — mỗi entry giao đúng một consumer. XPENDING=2 (hai entry đã giao, chưa ack); sau khi c1 XACK một entry → XPENDING=1. Đây là mô hình "chia tải + theo dõi đã xử lý" y như consumer group của Kafka (sê-ri trước), thu nhỏ trong Redis.

Khác biệt quyết định lựa chọn: Pub/Sub cho realtime chấp nhận mất (thông báo, chat, tín hiệu); Streams cho khi cần giữ message, cần phát lại, hoặc cần nhiều worker chia việc đáng tin cậy.

Đánh đổi cần cân nhắc

Pub/Sub không có bảo đảm giao nhận — đừng dùng cho việc quan trọng. Ngoài chuyện mất khi không ai nghe, Pub/Sub còn mất message nếu subscriber đang xử lý chậm và buffer đầy, hoặc nếu kết nối chớp tắt. Không có ack, không retry, không lưu. Dùng nó chỉ cho dữ liệu mà mất một vài message không sao (ví dụ "cache key X vừa đổi, ai quan tâm thì làm mới"). Việc không được mất (đơn hàng, thanh toán) phải dùng Streams hoặc một message broker thật.

Streams lưu log — phải quản lý độ dài kẻo phình RAM. Vì Streams giữ mọi entry, log sẽ lớn mãi nếu không cắt. Dùng XADD ... MAXLEN ~ 10000 (giữ khoảng 10 nghìn entry gần nhất) hoặc XTRIM định kỳ để giới hạn. Quên điều này là một stream ngốn hết RAM — nối với bài eviction/big key. Streams tiện nhưng không miễn phí về bộ nhớ như Pub/Sub (vốn không lưu gì).

Streams không thay thế Kafka ở quy mô rất lớn. Streams là "Kafka thu nhỏ" — tuyệt cho khối lượng vừa, khi bạn đã có Redis và không muốn dựng cả Kafka. Nhưng nó sống trong RAM của Redis (giới hạn bởi bộ nhớ), và không có hệ sinh thái/độ bền phân tán của Kafka thật (replication, retention theo ngày/tuần trên đĩa — sê-ri Kafka). Khi throughput và độ bền lên rất cao, Kafka vẫn là lựa chọn; Streams hợp cho quy mô nhỏ-vừa và đơn giản hoá hạ tầng.

Ba ý mang về

  1. Pub/Sub và Streams khác nhau ở chỗ giữ hay vứt message. Đo thật: PUBLISH khi không subscriber → 0 nhận (mất); XADD rồi XRANGE đọc lại được cả hai entry. Pub/Sub là loa phát thanh fire-and-forget; Streams là log bền.
  2. Streams có consumer group như Kafka thu nhỏ. Đo thật: c1 nhận alice, c2 nhận bob (mỗi entry một consumer), XPENDING theo dõi chưa ack (2 → 1 sau XACK). Cho phép chia tải và xử lý đáng tin cậy — thứ Pub/Sub không có.
  3. Chọn theo nhu cầu bền vững. Pub/Sub cho realtime chấp nhận mất (không ack, không lưu, nhẹ); Streams cho khi cần giữ/phát lại/chia việc (nhưng phải MAXLEN kẻo phình RAM). Quy mô rất lớn + độ bền cao vẫn nên dùng Kafka thật.

Nguồn

Phần sau ta vào pattern thực chiến quan trọng nhất: cache-aside, và cách chống cache stampede — khi một key nóng hết hạn và hàng nghìn request cùng lúc đập thẳng vào database.