Khi Redis cạn kiệt RAM: Thảm họa chực chờ và Nghệ thuật cấu hình Eviction
Redis luôn được ví như "chiếc F1" của thế giới Backend vì tốc độ bàn thờ của nó. Nhưng anh em phải nhớ: F1 chạy nhanh vì nó rất nhẹ, và Redis chạy nhanh vì nó lưu mọi thứ trên RAM. Mà RAM thì đắt đỏ và có giới hạn.
Nhiều anh em cứ nhắm mắt Redis::set(), đẩy Cache, đẩy Session, đẩy Queue vào Redis một cách vô tội vạ mà quên mất câu hỏi sinh tử: "Chuyện quái gì sẽ xảy ra khi con Server 8GB RAM bị nhét đầy 8GB data?"
Hôm nay, mình sẽ lột trần thảm họa Redis OOM (Out of Memory) và nghệ thuật cấu hình "Đuổi khách" (Eviction Policies) để hệ thống e-commerce của anh em không bị sập ngang lúc có Flash Sale. Trong các hệ thống e-commerce bán lẻ có lượng truy cập khủng, Redis thường phải gánh vác hàng triệu keys: từ Cache sản phẩm, Session đăng nhập, Giỏ hàng tạm, cho đến các Job trong Queue.
Nhưng RAM không phải là hố đen vũ trụ. Khi RAM đầy, nếu anh em không cấu hình đúng, đó không chỉ là lỗi của Redis, mà là hiệu ứng domino đánh sập toàn bộ hệ thống.
1. Thảm họa OOM (Out Of Memory) diễn ra như thế nào?
Có 2 kịch bản tàn khốc sẽ xảy ra khi Redis chạm trần bộ nhớ:
Kịch bản 1: Không set giới hạn (Để Redis bú RAM tự do)
Mặc định trên bản 64-bit, thông số maxmemory của Redis được set là 0 (không giới hạn). Khi data phình to, Redis sẽ nuốt cạn tài nguyên của server. Lúc này hệ điều hành (Linux) thấy nguy kịch, nó sẽ gọi sát thủ máu lạnh mang tên OOM Killer ra. Sát thủ này lùng sục xem tiến trình nào tốn RAM nhất và... kill -9 "bắn bỏ" ngay lập tức mà không báo trước.
👉 Kết quả: Tiến trình Redis chết đứng. Toàn bộ Cache bay màu, Queue đứt gánh. Hàng vạn request đang chọc vào Cache bị đẩy thẳng xuống Database (MySQL/PostgreSQL), gây ra thảm họa Cache Stampede làm sập luôn cả Database.
Kịch bản 2: Có set giới hạn... sai chiến thuật
Anh em thông minh hơn, set maxmemory 4gb trong file redis.conf. Nhưng chạm mốc 4GB, do không cấu hình khoảng cách tiếp theo, Redis ngầm dùng chiến thuật noeviction (Không đuổi ai cả, tao đóng cửa).
Lúc này, các lệnh đọc (GET) vẫn bình thường, nhưng hễ hệ thống có bất kì lệnh ghi nào (SET, LPUSH...), Redis vẫn văng lỗi
OOM command not allowed when used memory > 'maxmemory'.
👉 Kết quả: App Backend văng Exception đỏ lòm hàng loạt. Khách hàng không thể thêm giỏ hàng, không thể đăng nhập. Hệ thống tê liệt chức năng Ghi
2. Nghệ thuật "Đuổi Khách": Và các chiến lược Eviction Policy
Để không bị sập chúng ta phải nói cho Redis biết: "Khi quán đầy khách, mày hãy đuổi bớt những thằng nào ít tiềm năng nhất đi, để cho khách VIP mới vào!".
Trong file redis.conf, thông số quyết đinh vận mệnh này là maxmemory-policy
Có 8 chiến thuật chia làm 3 trường phái chính:
Trường phái 1: Không đuổi ai (Dành cho Queue/Data cứng)
noeviction(Mặc định): Đầy là thông báo lỗiTUYỆT ĐỐI KHÔNGdùng cái này nếu bạn xài Redis làm Cache. Tuy nhiên nếu bạn dùng Redis để làm Message Broker (hàng đợi Queue) hoặc Database chính (lưu data không được phép mất), thì bắt buộc phải dùng cái này thà báo lỗi còn hơn mất dữ liệu
Trường phái 2: Đuổi theo LRU (Least Recently Used - Ít sử dụng gần đây nhất)
Đây là chiến thuật phổ biến nhất. Thằng nào lâu rồi không ai sờ tới thì đuổi nó đi.
allkeys-lru: Lùng sục trong TẤT CẢ các keys, thằng nào lâu không dùng thì xóa. (Cực kì khuyên dùng nếu Redis nhà bạn 100% làm Cache).volatile-lru: Cũng là đuổi thằng lâu không dùng, nhưng CHỈ khoanh vùng trong những keys có đặt thời hạn sống (TTL - Time To Live). Những keys không set giới hạn thời gian (vĩnh viễn) sẽ được an toàn.
Sự thật bẻ bàng của giới kĩ sư: Redis KHÔNG xài thuật toán LRU chuẩn đâu anh em ạ! Thuật toán LRU chuẩn tốn quá nhiều RAM để lưu lại timestamps của từng key. Nên các kĩ sư Redis đã dùng Approximated LRU (LRU xấp xỉ). Nó sẽ lấy ngẫu nhiên 5 keys (cấu hình qua maxmemory-samples), so sánh thời gian của 5 thằng đó và xóa thằng cũ nhất. Hack não nhưng cực kì hiệu quả và tiết kiệm tài nguyên!
Trường phái 3: Đuổi theo LFU (Least Frequently Used - Ít được sử dụng thường xuyên nhất)
Từ Redis 4.0, một thuật toán bá đạo hơn LRU xuất hiện.
- Vấn đề của LRU: Giả sử bạn có 1 bài viết rất hot (triệu view), nhưng vừa nãy không ai xem. Cùng lúc đó, 1 bài viết rác vừa mới được ai đó lỡ tay bấm xem 1 lần. Theo LRU, bài hot sẽ bị xóa (vì lâu không ai xem so với bài rác vừa được xem). Quá vô lý!
- Giải pháp LFU (
allkeys-lfu/volatile-lfu): Redis sẽ theo dõi TẦN SUẤT truy cập. Bài hot có tần suất xem rất cao, dù 5 phút qua không ai xem thì chỉ số tần suất của nó vẫn cao. Bài rác mới được xem 1 lần tần suất quá thấp -> Đuổi bài rác đi!
3. Cấu hình chuẩn Kĩ sư hệ thống (Best Practices)
Để hệ thống vững như bàn thạch, anh em cần áp dụng combo sau:
1. Phân mảnh Redis theo mục đích (Redis Partitioning):
Đừng nhét chung Cache, Session và Queue vào cùng một con Redis.
- Con Redis 1 (Chuyên làm Cache): Set
maxmemoryvà dùng policyallkeys-lfu(hoặc lru). Đầy thì tự xóa, chả sao cả, vì DB vẫn còn data. - Con Redis 2 (Chuyên Session/Queue): Dùng policy
noevictionhoặcvolatile-lru. Cần theo dõi (Monitor) nghiêm ngặt để tăng RAM kịp thời.
2. Đừng bao giờ set maxmemory bằng 100% RAM thực tế:
Nếu Server có 8GB RAM, anh em chỉ nên set maxmemory 6gb.
Tại sao? Vì thi thoảng Redis phải dọn rác, hoặc khi nó thực hiện Snapshot (lưu data từ RAM xuống ổ cứng bằng lệnh BGSAVE), Linux sẽ dùng cơ chế Copy-on-Write. Nếu lúc đó Redis dang chiếm dữ liệu quá lớn, nó cần thêm RAM trống để nhân bản tiến trình. Nếu set 8GB/8GB, tiến trình BGSAVE sẽ thất bại hoặc làm server treo cứng.
3. Tuyệt đối bắt buộc xài TTL cho Cache:
Mọi key dùng làm Cache đều phải đính kèm số giây hết hạn. Ví dụ: Redis::set('home_products', $data, 3600);. Đừng vứt rác vào nhà rồi hi vọng một ngày nào đó máy hút bụi (Eviction) sẽ tự dọn giúp bạn.
Tổng kết
Biết xài Redis thì ai cũng biết, nhưng hiểu cách Redis quản lý bộ nhớ để tránh thảm họa OOM mới là thứ phân định đẳng cấp của một Backend Engineer "thiện chiến".
Lần tới, nếu sếp bảo "RAM server đắt quá, giới hạn Redis lại đi em", hãy chắc chắn rằng bạn đã mở file config lên và set đúng cái policy phù hợp với bussiness của công ty nhé!
Chuỗi bài của chúng ta ngày càng hardcore. Anh em đã nắm trong tay từ DB Relational (SQL) đến DB In-Memory (Redis). Nhưng có một vấn đề "đau đầu" khi hệ thống phình to ra:
Làm sao để biết chính xác một con API đang chạy chậm là do code lởm, do SQL query ngu, hay do gọi API bên thứ 3 bị treo?
Bài viết tới, mình sẽ đập hộp một vũ khí tối tân để giải bài toán này:
👉 APM (Application Performance Monitoring) và nghệ thuật truy vết bằng Distributed Tracing.
Anh em thấy bài viết đi sâu cỡ này có bị ngộp không? Cứ comment chém gió bên dưới nhé, ông tác giả bố đời sẽ rep hết! Thấy hữu ích thì tiếc gì 1 Upvote và Bookmark đúng không nào!
All rights reserved