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.

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ữ

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:
XADD2 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 c1nhận entry alice,XREADGROUP g1 c2nhận entry khác (bob) — mỗi entry giao đúng một consumer.XPENDING=2(hai entry đã giao, chưa ack); sau khi c1XACKmộ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ề
- 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.
- 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ó.
- 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
- Redis — Pub/Sub: https://redis.io/docs/latest/develop/interact/pubsub/
- Redis — Streams: https://redis.io/docs/latest/develop/data-types/streams/
- Redis — Streams consumer groups: https://redis.io/docs/latest/develop/data-types/streams/#consumer-groups
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.