Bài viết mới nhất

Tổng 2300 bài
Lập trình 22/09/2026 6 phút

Transaction và Lua trong Redis: ba cách làm nhiều lệnh nguyên tử, và vì sao Lua mạnh hơn MULTI

Khi nhiều client cùng trừ tồn kho hay cùng trừ tiền, race condition làm hỏng dữ liệu. Redis có ba công cụ đảm bảo nguyên tử. Bài này đo thật trong redis-lab: MULTI/EXEC chạy cả khối lệnh liền nhau; WATCH huỷ transaction khi key bị client khác đổi (bal=999 chứ không phải 949, chứng minh DECRBY bị huỷ); và script Lua trừ tồn kho CHỈ KHI đủ — đọc-kiểm-ghi chạy nguyên tử trong một EVAL, thứ MULTI không làm được vì không rẽ nhánh.

Lập trình 22/09/2026 8 phút

Chống stampede: singleflight và khoá Redis, đo thật 500 lượt chạm database rút xuống còn 1

Phần trước đo được 500 request cùng miss một key kéo theo đúng 500 truy vấn database và 1128,4ms cho request xấu nhất. Bài này chặn nó bằng hai cách và đo lại cùng kịch bản: singleflight gom lời gọi trùng trong một tiến trình — còn 1 truy vấn, trễ xấu nhất 7,2ms (nhanh hơn 157 lần); khoá Redis SET NX cũng còn 1 truy vấn nhưng chậm hơn chút (13,9ms). Rồi chạy 4 instance để lộ ra khác biệt thật sự: singleflight cho 4 truy vấn, khoá Redis vẫn 1.

Lập trình 22/09/2026 7 phút

Kiểm thử mã đồng thời đo thật: -race bắt đua dữ liệu 20/20 nhưng im lặng với đua logic; đua logic và deadlock lộ 0/1000 lần trên 10 CPU, lộ 100/100 khi chèn một điểm nhường CPU; runtime Go không báo deadlock trong go test

Ba lỗi đồng thời khác bản chất trong một gói Go: bộ đếm không mutex, rút tiền kiểm rồi trừ với hai lần khoá riêng, chuyển khoản khoá theo thứ tự tham số. Bộ dò đua bắt lỗi đầu 20/20 và không nhắc một chữ tới Withdraw dù test đó đỏ 5/20 dưới -race. Đua logic đỏ 0/100 và 0/1000 lượt chạy thường, 0/100 trên 1 CPU, 100/100 khi chèn runtime.Gosched vào giữa kiểm và trừ. Với p bằng 26% cần 10 lượt để 95% bắt được; với p bằng 0 không N nào đủ. Deadlock kẹt 0/20 lượt thường, 20/20 với điểm nhường, và runtime Go không báo deadlock trong go test, chỉ -timeout và goroutine dump chỉ ra chỗ kẹt.

Lập trình 22/09/2026 6 phút

Load balancing phía client trong gRPC: rải request qua nhiều backend không cần proxy

gRPC giữ kết nối lâu dài, nên load balancer tầng kết nối không rải được từng request — gRPC tự cân bằng ở phía client. Bài này chạy thật với 3 server: chính sách mặc định pick_first dồn cả 300 request vào một server (2 server ngồi không); đổi sang round_robin thì 300 request rải đều 100/101/99. Đo bằng counter thật mỗi server. Client biết danh sách backend qua resolver và tự chọn — không cần proxy ở giữa.

Lập trình 22/09/2026 9 phút

Idempotency-Key: đo thật client timeout rồi retry — không khoá tạo 6 đơn cho một lần mua, có khoá tạo đúng 1

Client gọi POST /orders, server đã tạo đơn nhưng phản hồi không về kịp, client retry. Mình dựng server Go với ba bản: không khoá, khoá idempotency trong Redis bằng SET NX, và khoá trong PostgreSQL bằng ON CONFLICT nằm cùng giao dịch với đơn hàng. Đo thật: client timeout 10 ms retry 5 lần — bản không khoá tạo 6 đơn, hai bản có khoá tạo đúng 1. 50 request đồng thời cùng key: 50 đơn so với 1. Hai bản có khoá khác nhau ở chỗ xử lý request trùng đang chạy: Redis từ chối ngay bằng 409, PostgreSQL bắt chờ rồi trả lại phản hồi cũ. Chi phí: khoảng 0,6 ms mỗi request, và request lặp lại chỉ còn 0,22 ms.

Lập trình 22/09/2026 8 phút

Cache phân tầng: đo thật L1 trong tiến trình cắt 97,6% lưu lượng Redis và nhanh hơn 800 lần

Mười phần trước đều coi Redis là tầng cache duy nhất. Bài này thêm một tầng L1 ngay trong bộ nhớ tiến trình và đo trên 300.000 request Zipf qua 10.000 key: L1 với TTL 1 giây kéo lưu lượng tới Redis từ 300.000 xuống 7.438 — cắt 97,6% — và độ trễ mỗi request từ 39,2µs xuống 2,95µs. Đo riêng từng tầng còn lộ ra chuyện bất ngờ: Redis 36,9µs chỉ nhanh hơn PostgreSQL 1,26 lần cho query điểm có index, còn L1 thì nhanh hơn Redis 800 lần. Cái giá đo được: mỗi instance giữ bản riêng và trả dữ liệu cũ đúng bằng TTL của L1.