Nội dung bài viết
Video học lập trình mỗi ngày
Tôi nghe nói đặt tiêu đề vậy có nhiều người vào xem, liệu có đúng không kakakaka.
Ở phía backend thì ngoài tối ưu hóa việc triển khai code là ưu tiên hàng đầu, tiếp theo là về SQL, và sau đó chính là bộ nhớ đệm hay gọi là cache. Cache cũng có hai loại đó là Bộ nhớ cache cục bộ và Dịch vụ bộ nhớ đệm.
Bộ nhớ cache cục bộ bao gồm như HashMap
, ConcurrentHashMap
, Ehcache
, RocksDB
... Và dịch vụ cache như Redis
, Tair
, Memcache
. Bài viết này ngắn gọn nói về Redis vì sau khi tôi xuất bản bài viết ở số trước về phân tích 2 sự cố còn lại khi sử dụng Reddis thì một cố câu hỏi được gửi về cho Tips Javascript. Và tôi nhận được, nhưng vì dự án chạy nhiều nên cũng chưa có thời gian ngồi nghiêm túc để viết và giải thích thêm. Vui lòng bỏ qua phần 1 nếu bạn đã biết.
Cách sử dụng cache
Nói đến cách sử dụng cache
ở đâu?, khi nào? và ưu điểm? thì đương nhiên các bạn mới vào nghề cũng biết chứ đừng nói đến anh em lâu năm. Thậm chí nói ra là hiểu, đơn giản là sử dụng cache trước mũi của database (DB), khi người dùng truy vấn ở các dữ liệu có tỷ lệ đọc nhiều và ghi ít. Ưu điểm thì khỏi phải bàn, vì bài viết trước đã nói tất cả. Nhưng, nhưng gì??
Nhưng ở đây tôi viết bạn chỉ đang hiểu đến đó mà thôi, thâm chí đồng nghiệp tôi cũng vậy, chỉ biết một sử dụng kiểu phổ biến nhất đó là [Cache Aside]. Các bước sau đây sẽ tổng quát truy trình trên như thế này, người dùng query dữ liệu vào cache, nếu có thì lấy dữ liệu lên. Nếu không, xuống DB rồi lấy lên đưa vào cache đồng thời hiển thị cho người dùng.
Tạm các bước là vậy, nhưng khi sử dụng xuất hiện 3 sự cố khi sử dụng redis mà tôi đã nói ở bài trước. Và đa số thắc mắc là ở 2 sự cố còn lại đó là Cache penetration
và cache breakdown
, nhìn chung những câu hỏi thì đa số các bạn đều hiểu sai, đó là có sự giống nhau. Và bài viết này tôi sẽ nói rõ lại từng chi tiết và hoàn cảnh một lần nữa. Hy vọng có gì đó để lại cho bạn theo cách hiểu của tôi.
Redis cache penetration
Nói tiếng anh vậy thôi chứ dịch ra tiếng việt là Thâm nhập bộ nhớ cache
. Giải thích và tôi tô đậm lên để các bạn hình dung, Thâm nhập bộ nhớ cache đề cập đến dữ liệu không có trong bộ nhớ cache và trong cơ sở dữ liệu
, chẳng hạn như kẻ tấn công sẽ nhập sản phẩm có id=-1
, or id=maxnum
là một id rất lớn không có thật. Lúc này, kẻ tấn công sẽ chạy liên tục số lượng request/second gây áp lực cho cơ sở dữ liệu. Vì tìm không có ở cache thì nó sẽ lục lọi DB, và cứ như vậy DB chịu tải một áp lực lớn từ đó CPU lên cao.. Vậy bạn sẽ làm gì trong trường hợp này.
Fix cache penetration
Ở lớp trên cùng hay nói chính xác hơn là lớp giao diện. thì bạn chỉ cần check thêm điều kiện cơ bản nếu:
const {id, token} = body;
if(id <= 0){
return; // chặn ngay ở đây khỏi xuống cache và db luôn
}
Tuy nhiên đó là xác minh điều kiện cơ bản thôi. Điều cuối cùng ở đây là phía backend phải đảm bảo rằng không được gây áp lực đến DB. Tại thời điểm này nếu như, một request đầu tiên không có trong cache và DB thì bạn set luôn = null
.
let value = redis.getKey('key'); //check xem cache co data khong?
if(!value){ // neu khong
value = await db.getKey('key'); // check xem db co data khoon?
}
if(!value){// neu khoong
redis.setKey('key', null, 30000); //set = null
}
return value;
Như vậy request tiếp theo sẽ có value = null
, và bạn cũng nên đặt 30 giây thôi, set lâu quá lỡ khi có những trường hợp đúng như vậy mà có data thì bỏ mịa. Điều này có thể ngăn việc tấn công người dùng liên tục bằng cách sử dụng cùng một id để tấn công gây áp lực lên hệ thống của bạn.
Redis cache breakdown
Redis cache breakdown hiểu theo tiếng việt là Sự cố phá vỡ trong bộ nhớ cache
. Giải thích và tôi tô đậm lên để các bạn hình dung so với cache penetration
nhé, Sự cố bộ nhớ cache đề cập đến dữ liệu không có trong bộ nhớ cache mà nằm trong cơ sở dữ liệu
thường xảy ra khi hết thời gian bộ nhớ cache. Tại thời điểm này, do có quá nhiều người dùng đồng thời nên dữ liệu không có ở cache nên dữ liệu được truy xuất từ cơ sở dữ liệu cùng một lúc. Áp suất cơ sở dữ liệu tăng ngay lập tức, gây ra áp lực quá mức lại CPU lên cao và chậm. IO đọc liên tục là không ổn.
Fix cache breakdown
Rất đơn giản cho trường hợp này là không settime
cho cache, nếu mà cảm thấy dự liệu này được đọc nhiều thì không cần thiết lập thời gian cho nó nữa. Thứ hai nâng cao hơn một chút là sử dụng chiến lược mutex mà tôi có đề cập ở bài viết về process và thread. Nói một cách đơn giản, khi bộ nhớ đệm không hợp lệ (giá trị xem là bằng null
), thay vì tải db ngay lập tức, trước tiên hãy sử dụng các thao tác nhất định với giá trị trả về của hoạt động thành công của công cụ bộ nhớ đệm (chẳng hạn như Redis SETNX hoặc Memcache ADD) để đặt khóa mutex. Khi thao tác trả về thành công, hãy thực hiện thao tác tải db và đặt lại bộ đệm; nếu không, hãy thử lại toàn bộ phương pháp lấy bộ đệm. Tương tự như đoạn mã sau đây:
const getData = async get(key) => {
String value = redis.get(key);
if (value == null) { // neu cache = null
// setnx -> insert neeu chua co key ton tai, nen dat 3 phut thoi
if (redis.setnx(key_mutex, 1, 3 * 60) == 1) { // Nó có nghĩa là set thanh cong
value = db.get(key); // lay db
redis.set(key, value, expiretime); // set cache
redis.del(key_mutex); // xoa mutex di
} else { // Lúc này có nghĩa là các luồng khác cùng lúc đã tải db và đặt lại vào bộ nhớ đệm, lúc này bạn hãy thử lấy lại giá trị trong cache
sleep(50);
get(key); // data
}
} else {
return value;
}
}
Tóm lại
Bài viết không dài nhưng cũng cần tóm lại cho các bạn đọc dễ hiểu, chỉ cần phân biệt được hai sự cố Cache penetration
và cache breakdown
.
- Cache penetration là
Thâm nhập bộ nhớ cache đề cập đến dữ liệu không có trong bộ nhớ cache và trong cơ sở dữ liệu
. Nhớ là không có. - Cache penetration là,
Sự cố bộ nhớ cache đề cập đến dữ liệu không có trong bộ nhớ cache mà nằm trong cơ sở dữ liệu
. Nhớ là không có trong cache mà có trong DB.
Điều quan trọng hãy đọc lại những bài viết mà tôi có để link trong bài này. Điều đó rất quan trọng.