Nội dung bài viết
Video học lập trình mỗi ngày
Bài viết này được đưa vào Series: Phỏng vấn kỹ sư backend
Mùa nhảy việc lại đến rồi anh em. CV gửi đi, qua được vòng lọc của HR, và giờ là lúc đối mặt với vòng phỏng vấn kỹ thuật. Nếu anh em đang nhắm vào vị trí Backend Java cho Banking hay Fintech, sớm muộn gì cũng sẽ gặp một câu hỏi na ná thế này:
"Hệ thống của em thiết kế thế nào để xử lý được 1000–2000 transaction/giây (TPS)?"
Đây không phải là câu hỏi để doạ ma. Đây là câu hỏi để phân loại. Nó tách biệt một Senior thực thụ là người đã từng "toát mồ hôi" với hệ thống product với một developer chỉ quen code CRUD trên máy cá nhân. Vì vậy ở đây khi viết blog thì có thể nó không có sự hấp dẫn hoặc nó có thể hiểu nhầm là quá dễ so với câu hỏi.
Và tất nhiên có nhiều khía cạnh khi giải quyết vấn đề về Transactions trong hệ thống banking or fintech. Nhưng đây cũng là một cách mà các lập trình viên họ có thể bình tĩnh và xử lý trong tình huống khẩn cấp.
Thật ra các đại ca đứng bên phía tuyển dụng không chỉ muốn nghe bạn nói về scale-out, về load balancing hay gì đó... Cái họ muốn thấy là chiều sâu tư duy về sự ổn định của hệ thống.
Và một trong những kẻ thù thầm lặng, nguy hiểm nhất khi xử lý đa luồng multithreading với throughput cao chính là Deadlock mà rất nhiều section trong java anh em mình đã nói tới.
Cái này AI không cứu được vì nó đang xảy ra trong Production. Chỉ có kinh nghiệm và sự hiểu biết bản chất mới giúp anh em sống sót. Còn ngoài Local thì khả năng vẫn được. Khà khà
"Deadlock" — Nó Là Cái Quái Gì?
Nói cho dễ hiểu, deadlock là tình trạng "khóa chết". Nhìn vào đoạn code "ngây thơ" dưới đây, có thể xem video thực hành thì càng tốt.
public class DeadlockDemo {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
// Luồng 1: Khóa lock1 trước, rồi tìm cách khóa lock2
Thread thread1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread 1: Đã giữ lock 1...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println("Thread 1: Đang chờ lock 2...");
synchronized (lock2) {
System.out.println("Thread 1: Đã giữ lock 1 & 2.");
}
}
});
// Luồng 2: Khóa lock2 trước, rồi tìm cách khóa lock1
Thread thread2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("Thread 2: Đã giữ lock 2...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println("Thread 2: Đang chờ lock 1...");
synchronized (lock1) {
System.out.println("Thread 2: Đã giữ lock 1 & 2.");
}
}
});
thread1.start();
thread2.start();
}
}
Luồng 1 giữ lock1 và chờ lock2. Luồng 2 thì giữ lock2 và chờ lock1. Kết quả? Hai thằng cứ đứng nhìn nhau đến vô tận. Chương trình sẽ bị treo cứng.
Ví dụ này thì đơn giản, nhưng để thấy tận mắt hai luồng code nó "nhìn nhau" thì có lẽ không bao giờ... Đúng không?
Kinh nghiệm phát hiện khi Deadlock xảy ra trong môi trường Produciton.
Trên máy dev, chạy code trên thì thấy treo ngay vì các IDEA hiện nay đều hỗ trợ như Dump Threads mà tôi đã chỉ cho các anh chị.
Nhưng trên môi trường product, 2 giờ sáng hàng ngàn request đổ vào và quan trọng là có nhiều task đang chạy, không IDEA thì làm thế nào? Đây chính là một trong những điều mà nhà tuyển dụng họ cần biết về chúng ta.
Kinh nghiệm của tôi thì có 2 dấu hiệu kinh điển cho thấy deadlock có thể đã xảy ra:
| Triệu chứng | Giải thích |
|---|---|
| CPU Usage tụt xuống gần 0% | Các luồng đang ở trạng thái BLOCKED — chờ đợi chứ không làm gì. CPU rảnh nhưng không có việc. |
| Throughput = 0, Response Time = ∞ | Request vẫn vào nhưng không có response nào được trả ra. |
Cách fix nhanh gấp thế nào?
Lúc này cần sự bình tĩnh. Anh em không thể dí debugger vào server product được. Chúng ta cần dùng "đồ nghề" chuyên dụng được JDK cung cấp sẵn. Tất nhiên sẽ bị lost một vài threads, đó là việc của 'tính nhất quán' sẽ làm việc sau khi fix lỗi khẩn cấp này.
Bước 1 — SSH vào server đang chạy ứng dụng Java.
Bước 2 — Tìm Process ID (PID) của ứng dụng:
jps -l
# Output sẽ ra danh sách các tiến trình Java đang chạy, ví dụ:
# 94518 com.xxx.DeadlockDemo
Bước 3 — Dùng jstack để "dump" trạng thái của tất cả các luồng:
jstack 94518
Kéo xuống cuối cùng, nếu có deadlock, bạn sẽ thấy một thông báo không thể rõ ràng hơn:
Found 1 deadlock.
"Thread-1":
waiting to lock monitor 0x0000... (a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x0000... (a java.lang.Object),
which is held by "Thread-1"
Nó chỉ thẳng mặt vấn đề! Chỉ 4 dòng output, nhưng lúc này tôi biết chính xác thằng nào đang "cắn" thằng nào. Dựa vào những lỗi này và line nào thì chúng ta sẽ xác định khoanh vùng lại...
Các Cấp Độ "Fix" Deadlock Từ Dễ Đến Khó
Khi đã xác định được các vùng gây ra sự bế tắc này thì xem như đã biết bệnh, lúc này sẽ dễ chữa trị hơn. Để ngăn chặn deadlock, chỉ cần phá vỡ một trong các điều kiện gây ra nó. Tôi đã có đề cập những điều kiện có thể gây deadlock trong Phỏng vấn ở Banking (Backend): Cốt lõi là phải xử lý được nhiều giao dịch cùng một lúc? Tôi đã vượt qua thế nào?
Các bạn có thể xem tại đó... Thanks!

