Harbor

Redis

Redis là kho key-value trong RAM với nhiều cấu trúc dữ liệu: dùng làm cache, bộ đếm, leaderboard, hàng đợi, GEO và pub/sub. Bài này đi qua mô hình dữ liệu, persistence, replication, Cluster, Lua và cách tính RAM.

16 phút đọcCông nghệ chủ chốt

Redis là gì và mô hình cốt lõi

Redis là một server lưu dữ liệu trong RAM (Redis in Action gọi nó là in-memory database/data structure server), truy cập qua TCP bằng các lệnh ngắn như GET, INCR, ZADD.1 Mỗi key trỏ tới một giá trị có kiểu riêng: string, list, set, hash, sorted set (năm kiểu cốt lõi trong sách),2 cộng bitmap, HyperLogLog (Grokking nhắc tới bitmap và hyper log)3 và stream, GEO (không có trong sách của thư viện).4 Điểm mạnh của Redis nằm ở chỗ các lệnh thao tác thẳng trên cấu trúc dữ liệu đó, nên bạn không phải đọc cả object về app rồi ghi lại (Grokking: "we don't have to retrieve data structures from it, manipulate them, and then store them back").3

Ba đặc điểm cần nhớ:

  • Dữ liệu trong RAM. Redis in Action §2.3 ghi một lượt đọc Redis mất dưới 1 ms với kết nối cục bộ và dưới 5 ms giữa các máy gần nhau trong cùng data center.5 Đổi lại, dung lượng bị giới hạn bởi RAM và giá RAM.
  • Xử lý lệnh trên một luồng chính. Grokking ghi Redis chạy như một process dùng một core (khác Memcached đa luồng).3 Mỗi lệnh chạy một lần một, nên từng lệnh là atomic, và bạn có thể ghép nhiều bước thành một bằng MULTI/EXEC hoặc Lua script (Redis in Action ch.11: EVAL được coi như một lệnh rất phức tạp nên không có thao tác nào chen vào).6 Mặt trái: một lệnh chậm (ví dụ KEYS * trên 10 triệu key) chặn mọi client khác (suy luận từ mô hình một luồng; sách còn nói SAVE chặn mọi lệnh tới khi snapshot xong).7
  • Persistence là tùy chọn. Redis có thể ghi snapshot hoặc log xuống đĩa,7 nhưng thiết kế mặc định vẫn coi RAM là nơi chứa chính. Nếu dữ liệu không được phép mất, cần cân nhắc kỹ cấu hình hoặc giữ bản gốc ở database khác.

Trong sách, các ứng dụng điển hình là cache trang và dòng dữ liệu (ch.2), bộ đếm và log (ch.5), khóa và hàng đợi (ch.6), timeline mạng xã hội (ch.8).8 Trong phỏng vấn, Redis xuất hiện ở gần như mọi bài (nhận định của khoá học): cache trước database, rate limiter, đếm view, leaderboard, khóa phân tán, lưu session, tìm tài xế gần nhất. Hiểu rõ mỗi cấu trúc dữ liệu giải quyết bài toán gì sẽ giúp bạn chọn đúng lệnh thay vì nói chung chung "dùng Redis để cache".

Kiến trúc: từ một node tới Cluster

Có ba cách triển khai chính, tăng dần về độ phức tạp.

Một node + replica. Một primary nhận ghi, một hoặc vài replica nhận bản sao và phục vụ đọc (Redis in Action §4.2 và §10.1 về scale đọc bằng slave).9 Grokking ghi rõ Redis không đảm bảo strong consistency vì dùng replication bất đồng bộ, nghĩa là khi primary chết, vài lệnh ghi cuối có thể chưa tới replica và bị mất.310

Sentinel. Redis in Action §10.1 mô tả Sentinel là chế độ của server Redis: các tiến trình Sentinel theo dõi master và slave, khi master hỏng thì một Sentinel được chọn để chọn slave mới lên làm master và trỏ các slave còn lại sang đó.11 Sentinel giải quyết high availability, không giải quyết giới hạn dung lượng: toàn bộ dữ liệu vẫn nằm trên một primary (suy luận của khoá học). Con số "thường 3 Sentinel" là khuyến nghị vận hành của khoá học.12

Redis Cluster. Grokking mô tả cụm Redis tự động shard, mỗi shard có primary và secondary, có cluster manager phát hiện lỗi và failover.13 Phần chi tiết sau đây là kiến thức của khoá học (sách Redis in Action bản đầu viết khi Redis Cluster chưa phát hành, chỉ dùng sharding phía client ở ch.10):1412 keyspace được chia thành 16.384 hash slot. Slot của một key là CRC16(key) mod 16384. Mỗi primary giữ một dải slot, mỗi primary có replica riêng. Client biết bản đồ slot và gửi lệnh thẳng tới node đúng. Nếu gửi nhầm, node trả lỗi MOVED kèm địa chỉ đúng. Khi thêm node, bạn di chuyển một số slot sang node mới, dữ liệu đi theo slot.

Đang vẽ sơ đồ…

Ràng buộc quan trọng của Cluster: lệnh đụng nhiều key (MGET, SUNION, Lua script, transaction) chỉ chạy được khi tất cả key nằm cùng slot. Cách xử lý là dùng hash tag: phần trong dấu {} mới được băm. Các key cart:{user42}:items và cart:{user42}:total chắc chắn cùng slot. Đổi lại, nếu một tag quá nóng, toàn bộ tải dồn về một node.

Lưu ý: Redis Cluster không dùng consistent hashing theo kiểu vòng tròn. Nó dùng số slot cố định và bảng ánh xạ slot sang node. Ý tưởng giống nhau ở chỗ thêm node chỉ phải di chuyển một phần dữ liệu (xem Grokking ch.9 về rebalance partition và consistent hashing).15

Cấu trúc dữ liệu và bài toán mỗi loại giải quyết

KiểuLệnh tiêu biểuBài toán điển hình
StringGET, SET EX, INCR, SET NXCache object JSON, bộ đếm, khóa đơn giản, idempotency key
HashHSET, HINCRBY, HGETALLLưu object nhiều field (session, profile) và sửa từng field
ListLPUSH, BRPOP, LRANGEHàng đợi đơn giản, danh sách N mục gần nhất
SetSADD, SISMEMBER, SINTERTập user đã like, đã xem, tính giao/hợp
Sorted setZADD, ZINCRBY, ZREVRANGE, ZRANGEBYSCORELeaderboard, top-k, hàng đợi theo thời gian, sliding window rate limit
StreamXADD, XREADGROUP, XACKLog sự kiện có consumer group, ack, đọc lại
GEOGEOADD, GEOSEARCHTìm điểm trong bán kính (tài xế, cửa hàng)
Pub/SubPUBLISH, SUBSCRIBEĐẩy thông báo realtime giữa các server, không lưu lại
HyperLogLogPFADD, PFCOUNTĐếm unique xấp xỉ với ~12 KB mỗi key
BitmapSETBIT, BITCOUNTĐánh dấu user active theo ngày, mỗi user 1 bit

Các kiểu và lệnh trong bảng: string, list, set, hash, sorted set và pub/sub có trong Redis in Action ch.3 (§3.1 tới §3.6);16 bitmap có trong §9.3 (packing bits);17 stream, GEO, HyperLogLog chưa có trong sách.4 Một vài điểm hay bị hỏi kỹ:

  • Sorted set là cấu trúc đáng giá nhất. Mỗi phần tử có một score và Redis giữ thứ tự theo score, nên lấy top 10 rất nhanh (độ phức tạp O(log N) nhờ skip list là kiến thức của khoá học).4 Redis in Action ch.1 xây hệ thống vote bài viết bằng chính ZSET, §6.4 dùng ZSET cho delayed task (score = thời điểm chạy), §7.2 dùng ZSET để sắp xếp kết quả tìm kiếm.18 Leaderboard game và "bài viết hot" là ứng dụng cùng loại.
  • Pub/Sub không lưu tin nhắn: sách nói người nghe chỉ nhận tin trong lúc còn kết nối và đang nghe, nên subscriber mất kết nối là mất tin.19 Nếu cần đọc lại hoặc ack, dùng Stream hoặc Kafka.
  • Stream (chưa có nguồn trong thư viện)4 giống một Kafka thu nhỏ trong một node: có ID tăng dần, consumer group, danh sách tin đang chờ ack. Phù hợp khi khối lượng vừa phải và bạn đã có Redis sẵn.
  • GEO (chưa có nguồn trong thư viện)4 thực chất là sorted set với score là geohash 52 bit. Đủ tốt cho "tìm tài xế trong 3 km", xem thêm tìm kiếm gần.

API cơ bản và tính atomic với Lua

Ví dụ cache-aside với TTL trong Node.js (thư viện ioredis):

import Redis from "ioredis";
const redis = new Redis(process.env.REDIS_URL!);

export async function getProduct(id: string) {
  const key = `product:${id}`;
  const hit = await redis.get(key);
  if (hit) return JSON.parse(hit);
  const row = await db.product.findUnique({ where: { id } });
  if (row) await redis.set(key, JSON.stringify(row), "EX", 300 + Math.floor(Math.random() * 60));
  return row;
}

// Leaderboard
await redis.zincrby("lb:2026-11", 50, "user:42");
const top10 = await redis.zrevrange("lb:2026-11", 0, 9, "WITHSCORES");

Mẫu cache-aside này giống cache_request() trong Redis in Action §2.3 (đọc cache, trượt thì sinh nội dung và SETEX 300 giây).5 TTL có thêm vài giây ngẫu nhiên để tránh nhiều key hết hạn cùng lúc rồi cùng đập vào database (kỹ thuật của khoá học).20

Khi cần "đọc rồi ghi" mà không bị client khác chen ngang, dùng Lua. Script chạy trọn vẹn trên server như một lệnh duy nhất (Redis in Action ch.11).6 Ví dụ rate limiter fixed window:

-- KEYS[1] = rl:{user42}:202611021530, ARGV[1] = limit, ARGV[2] = ttl giây
local n = redis.call("INCR", KEYS[1])
if n == 1 then
  redis.call("EXPIRE", KEYS[1], ARGV[2])
end
if n > tonumber(ARGV[1]) then
  return 0
end
return 1

Nếu viết bằng hai lệnh INCR rồi EXPIRE từ app, app có thể crash giữa chừng và key không bao giờ hết hạn. Lua loại bỏ khoảng hở đó. Lưu ý: vì script được coi như một lệnh duy nhất, script dài sẽ chặn cả node, nên giữ script ngắn và tránh vòng lặp lớn.6 Ví dụ rate limiter fixed window ở trên là minh hoạ của khoá học.20

Khóa phân tán đơn giản: SET lock:order:9 <token> NX PX 10000 (Redis in Action §6.2 xây khoá bằng Redis, gồm khoá có timeout, và §11.2 viết lại khoá bằng Lua).21 Khi nhả khóa, dùng Lua kiểm tra token trước khi DEL để không xóa nhầm khóa của tiến trình khác. Khóa này chịu được hầu hết tình huống, nhưng vì replication bất đồng bộ (xem trên) nên khi failover replica chưa nhận khóa thì hai client có thể cùng giữ. Với tiền bạc, kết hợp thêm fencing token hoặc ràng buộc ở database (xem tranh chấp tài nguyên).

Persistence và eviction

RDB và AOF

RDB snapshotAOF (append-only file)
Cách làmfork() rồi ghi toàn bộ dataset ra file theo chu kỳGhi mỗi lệnh ghi vào log, fsync theo cấu hình
Mất dữ liệu khi crashMất mọi thay đổi từ lần snapshot cuối (chu kỳ do cấu hình save)everysec: sync mỗi giây nên mất tối đa khoảng 1 giây; always: gần như không, nhưng chậm
Khởi động lạiLoad một file snapshot (nhanh hơn, suy luận)Phải replay log từ đầu tới cuối (có rewrite để gọn lại)
Chi phíFork tốn RAM copy-on-write khi ghi nhiềuI/O đĩa liên tục

Cấu hình phổ biến: bật cả hai, AOF everysec (khuyến nghị thực hành của khoá học).20 Khi BGSAVE fork, sách giải thích bộ nhớ ban đầu được chia sẻ giữa process cha và con, và trang nào bị ghi sẽ không còn chia sẻ nữa; vì vậy node ghi nhiều cần RAM dư tạm thời (mức cụ thể phải đo).7 Đây là lý do không nên để Redis dùng hết RAM của máy.

Sai lầm hay gặp: Dùng Redis làm nơi lưu duy nhất cho dữ liệu tiền tệ hoặc đơn hàng chỉ với RDB mặc định. Crash là mất mọi thay đổi từ lần snapshot cuối.7 Giữ bản gốc ở PostgreSQL, Redis chỉ giữ bản sao hoặc trạng thái tạm.

Eviction policy

Grokking ch.16 nêu các chiến lược evict chung (LRU, MRU, LFU, MFU, FIFO) và nói lựa chọn tuỳ hệ thống.22 Các tên policy cụ thể của Redis dưới đây là kiến thức sản phẩm, chưa có trong sách của thư viện.20 Khi chạm maxmemory, Redis xử lý theo maxmemory-policy:

PolicyHành viDùng khi
noevictionTừ chối lệnh ghi mớiRedis là kho dữ liệu, không được mất key
allkeys-lruXóa key ít dùng gần đây nhất trong toàn bộCache thuần, chọn mặc định hợp lý
allkeys-lfuXóa key ít được truy cập nhấtCache có tập hot ổn định
volatile-lru / volatile-ttlChỉ xóa trong các key có TTLTrộn cache và dữ liệu cần giữ trên cùng node

LRU của Redis là xấp xỉ: nó lấy mẫu vài key rồi xóa key cũ nhất trong mẫu, đủ tốt và rẻ.

Capacity và sizing

Các con số dưới đây là ước lượng để tính nhanh, cần benchmark trên tải thật; thông lượng và độ trễ lấy theo Grokking ch.5 và Redis in Action §2.3, hệ số RAM theo quy tắc ước lượng của khoá học.23524

Chỉ sốƯớc lượng
Thông lượng một node~100.000 ops/s (Grokking: cache server 100.000 tới 1 triệu QPS); pipeline giảm số round trip nên có thể cao hơn nhiều
Độ trễ đọc cùng DCdưới ~5 ms theo Redis in Action §2.3 (ước lượng dưới 1 ms cho kết nối cục bộ)
Overhead mỗi key~100 B ngoài key và value (giả định của khoá học)
RAM cầnitems × (key + value + ~100 B) × 1,3
Kích thước node thoải mái25–50 GB dữ liệu mỗi primary (giả định của khoá học; lớn hơn thì snapshot và đồng bộ slave lâu hơn)

Ví dụ: cache 20 triệu sản phẩm, key ~30 B, value JSON ~1 KB.

20.000.000 × (30 + 1.024 + 100) B ≈ 23 GB
23 GB × 1,3 (fragmentation, buffer) ≈ 30 GB

Một primary 32 GB chạy được nhưng sát. Hợp lý hơn: Cluster 3 primary × ~16 GB, mỗi primary 1 replica, tổng 6 node. Về QPS: nếu peak 150.000 lệnh/s, 3 primary chia đều mỗi node ~50.000 ops/s, còn dư.

Nếu phải nhắc tới con số của Grokking (ch.5, bảng Important Rates): một cache server phục vụ khoảng 100k–1M request/s, sách nói các con số này xấp xỉ và phụ thuộc loại query, cấu hình máy và thiết kế.23 Đó là trần lý thuyết, khi trình bày nên dùng ~100k để an toàn.

Mẹo: Hot key là vấn đề sizing hay bị quên. Một key nhận 200.000 lệnh/s sẽ làm nghẽn một node dù cluster có 10 node. Grokking ch.16 nói thêm replica giảm tải shard nóng, hoặc shard tiếp trong dải key nóng, hoặc replicate động cho key cụ thể.25 Cách chữa của khoá học: cache thêm một tầng trong RAM của app với TTL vài giây, hoặc nhân bản key thành key:1..N và đọc ngẫu nhiên.

Khi nào dùng và khi nào không

Bảng dưới là nhận định của khoá học;20 riêng so sánh Memcached với Redis dựa trên Grokking ch.16.3

Nhu cầuRedisLựa chọn khác
Cache đọc nhiều, chấp nhận mấtRất hợpMemcached nếu chỉ cần cache đơn giản, đa luồng (Grokking: Memcached hợp hệ đọc nhiều, nhỏ, đơn giản; Redis hợp hệ phức tạp, cả đọc lẫn ghi nhiều)
Leaderboard, top-k realtimeRất hợp với ZSETTính batch bằng Flink nếu dữ liệu khổng lồ
Rate limit, counterRất hợpDynamoDB atomic counter nếu đã ở AWS
Hàng đợi bền, replay nhiều ngàyChỉ hợp ở quy mô nhỏ (Stream)Kafka, SQS
Dữ liệu lớn hơn RAM nhiều lầnKhông hợpCassandra, DynamoDB
Truy vấn phức tạp, join, báo cáoKhông hợpPostgreSQL, data warehouse
Full-text searchKhông hợp (trừ module RediSearch)Elasticsearch

Vận hành ở quy mô nhỏ và lớn

Quy mô nhỏ (startup, dưới vài GB). Dùng dịch vụ managed: AWS ElastiCache, GCP Memorystore, Upstash, Redis Cloud (không có trong sách của thư viện; giá tuỳ nhà cung cấp). Bạn không phải lo failover và patch. Bật TLS và AUTH, đặt Redis trong private network.

Quy mô vừa (10–100 GB). Chuyển sang Cluster mode (hoặc sharding như Redis in Action ch.10, nơi sách cảnh báo các thao tác nhiều key cần nằm trên cùng shard).14 Thiết kế key có hash tag ngay từ đầu cho các nhóm key cần lệnh multi-key. Theo dõi used_memory, evicted_keys, keyspace_hits/misses, instantaneous_ops_per_sec, độ trễ p99, và slowlog.

Quy mô lớn (hàng trăm GB, triệu ops/s). Hàng chục primary (giả định của khoá học20). Tự host trên Kubernetes hoặc VM tiết kiệm chi phí nhưng đòi đội vận hành giỏi: resharding, nâng version, xử lý fork trên máy nhiều RAM. Nhiều công ty tách cụm theo mục đích (cache, rate limit, session) để một cụm gặp sự cố không kéo theo tất cả.

Những lỗi vận hành phổ biến: chạy KEYS * trên production (dùng SCAN), lưu value vài MB trong một key, không đặt maxmemory khiến OS kill tiến trình, và để TTL trống trên cache khiến bộ nhớ tăng mãi.20

Điểm nói trong phỏng vấn

  • Nói rõ Redis đóng vai trò gì: cache (chấp nhận mất), hay kho dữ liệu chính (cần AOF, replica, và giải thích rủi ro).
  • Chọn đúng cấu trúc: ZSET cho leaderboard (Top-K), INCR + TTL hoặc ZSET cho rate limiter, GEO cho gọi xe và Yelp, SET NX PX cho giữ chỗ tạm trong đặt vé.
  • Nhắc tính atomic: một lệnh là atomic, nhiều bước thì dùng Lua hoặc MULTI/EXEC (Redis in Action ch.3 và ch.11).6
  • Khi tính quy mô: đưa công thức RAM, số node, và nói về hot key.
  • Khi bị hỏi failover (Grokking): replication bất đồng bộ nên có thể mất ghi cuối; Sentinel cho HA một shard, Cluster cho cả HA và chia dữ liệu qua 16.384 slot.
  • Liên hệ caching cho chiến lược invalidate và distributed cache nếu đề bài yêu cầu tự thiết kế một hệ như Redis.

Trong phỏng vấn: Câu "dùng Redis cache, TTL 5 phút, allkeys-lru, cluster 3 shard × 16 GB, hot key thì thêm local cache 2 giây" thuyết phục hơn nhiều so với "thêm Redis cho nhanh".

Nguồn trích dẫn

  1. Redis in Action · ch.1 Getting to know Redis · §1.1 What is Redis? (tr. 4–7). ↩

  2. Redis in Action · ch.1 · §1.2 What Redis data structures look like (tr. 7–15: strings, lists, sets, hashes, sorted sets). ↩

  3. Grokking Modern System Design · ch.16 Distributed Cache · bài "Memcached versus Redis" · mục "Redis" và "Memcached versus Redis" (data structure store, một core, replication bất đồng bộ, persistence AOF/RDB, kiểu dữ liệu). ↩ ↩2 ↩3 ↩4 ↩5

  4. Chưa có nguồn trong thư viện: Redis Stream, GEO, HyperLogLog (chi tiết) và độ phức tạp sorted set không được mô tả trong Redis in Action bản trong thư viện; chỉ nêu khái niệm cơ bản. ↩ ↩2 ↩3 ↩4 ↩5

  5. Redis in Action · ch.2 · §2.3 Web page caching (tr. 30–31: cache_request, độ trễ dưới 1 ms cục bộ, dưới 5 ms cùng data center). ↩ ↩2 ↩3

  6. Redis in Action · ch.11 Scripting Redis with Lua · §11.1 Adding functionality without writing C (ghi chú "Lua scripts as atomic as single commands or MULTI/EXEC", tr. 252–253). ↩ ↩2 ↩3 ↩4

  7. Redis in Action · ch.4 Keeping data safe and ensuring performance · §4.1 Persistence options (tr. 64–70: snapshot BGSAVE/SAVE, fork, append-only file, appendfsync). ↩ ↩2 ↩3 ↩4

  8. Redis in Action · ch.2 (cache), ch.5 Using Redis for application support (counters, logging), ch.6 Application components in Redis (locks, task queues), ch.8 Building a simple social network. ↩

  9. Redis in Action · ch.4 · §4.2 Replication (tr. 70–75) và ch.10 · §10.1 Scaling reads (tr. 228–232). ↩

  10. Grokking Modern System Design · ch.9 Databases · bài "Data Replication" · mục "Synchronous versus asynchronous replication". ↩

  11. Redis in Action · ch.10 Scaling Redis · §10.1 Scaling reads · phần "Redis Sentinel" (tr. 231). ↩

  12. Chưa có nguồn trong thư viện: Redis Cluster (16.384 slot, MOVED, hash tag) và số lượng Sentinel không có trong sách của thư viện (Redis in Action viết trước khi Cluster phát hành); phần này chỉ nêu khái niệm cơ bản. ↩ ↩2

  13. Grokking Modern System Design · ch.16 Distributed Cache · bài "Memcached versus Redis" · mục "Redis cluster". ↩

  14. Redis in Action · ch.10 · §10.2 Scaling writes and memory capacity (tr. 232–236) và ch.11 · §11.1 (keys phải cùng server khi chạy script). ↩ ↩2

  15. Grokking Modern System Design · ch.9 Databases · bài "Data Partitioning" · mục "Consistent hashing" và "Rebalance the partitions". ↩

  16. Redis in Action · ch.3 Commands in Redis · §3.1 Strings tới §3.6 Publish/subscribe (tr. 39–57). ↩

  17. Redis in Action · ch.9 Reducing memory use · §9.3 Packing bits and bytes (tr. 222–227). ↩

  18. Redis in Action · ch.1 §1.3 Hello Redis (voting on articles), ch.6 §6.4 Task queues (delayed tasks) và ch.7 §7.2 Sorted indexes. ↩

  19. Redis in Action · ch.3 · §3.6 Publish/subscribe (tr. 54–57). ↩

  20. Chưa có nguồn trong thư viện: jitter TTL, ví dụ rate limiter, khuyến nghị cấu hình RDB+AOF, tên các eviction policy, lỗi vận hành và quy mô cụm là thực hành của khoá học. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7

  21. Redis in Action · ch.6 · §6.2 Distributed locking (tr. 116–126) và ch.11 · §11.2 Rewriting locks and semaphores with Lua (tr. 255–258). ↩

  22. Grokking Modern System Design · ch.16 Distributed Cache · bài "Background of Distributed Cache" · mục "Eviction policies". ↩

  23. Grokking Modern System Design · ch.5 Back-of-the-envelope Calculations · mục "Standard numbers to remember" (bảng "Important Rates"). ↩ ↩2

  24. Quy tắc ước lượng của khoá học · /hoc/chon-server (RAM Redis ×1,3). ↩

  25. Grokking Modern System Design · ch.16 Distributed Cache · bài "Evaluation of a Distributed Cache Design" · mục "Scalability" (hot key). ↩