Mở đầu — vì sao bài này quan trọng
Trong suốt khóa học, bạn đã dành rất nhiều thời gian với JMeter (GUI kéo-thả, XML nặng nề) và k6 (script JavaScript hiện đại). Cả hai đều xuất sắc, nhưng chúng đặt ra một câu hỏi mà rất nhiều đội QA ở Việt Nam gặp phải: "Team của tôi toàn dân Python — backend viết Django/FastAPI, data engineer viết pandas — vậy tại sao lại bắt họ học một ngôn ngữ mới chỉ để viết test tải?"
Đó chính là chỗ Locust bước vào. Locust là công cụ load testing mã nguồn mở, trong đó kịch bản test được viết hoàn toàn bằng Python thuần. Không có XML, không có DSL riêng, không có GUI bắt buộc. Bạn định nghĩa hành vi người dùng bằng một class Python bình thường — và vì nó là Python thật, bạn có thể dùng bất kỳ thư viện nào trong hệ sinh thái khổng lồ của Python: requests, faker, boto3, kết nối database, gọi API nội bộ, đọc file Excel... Tất cả đều nằm trong tầm tay.
Bài này quan trọng vì trong thực tế, việc chọn công cụ load testing không chỉ là bài toán kỹ thuật mà còn là bài toán con người và khả năng bảo trì. Một bộ test mà chỉ một chuyên gia performance viết được và không ai khác đọc hiểu sẽ chết yểu sau khi người đó nghỉ việc. Locust giải quyết vấn đề này bằng cách hạ thấp rào cản: nếu developer của bạn biết viết Python, họ đã biết viết Locust. Hiểu rõ Locust cho bạn thêm một lựa chọn mạnh trong "hộp đồ nghề" — và biết khi nào nên rút nó ra thay vì JMeter hay k6.
Khái niệm cốt lõi
Mô hình "user class" — kịch bản là hành vi, không phải request
Triết lý cốt lõi của Locust khác biệt so với JMeter. Trong JMeter bạn nghĩ theo hướng "một chuỗi HTTP request". Trong Locust bạn nghĩ theo hướng "một người dùng ảo (virtual user) sẽ hành xử như thế nào". Mỗi virtual user là một instance của một class Python kế thừa từ HttpUser, và mỗi hành động người dùng có thể làm là một method được đánh dấu @task.
from locust import HttpUser, task, betweenclass WebsiteUser(HttpUser):
wait_time = between(1, 5) # nghỉ 1-5 giây giữa mỗi task
@task
def xem_trang_chu(self):
self.client.get("/")
@task(3) # trọng số 3 — được chọn thường xuyên gấp 3 lần
def xem_san_pham(self):
self.client.get("/products/123")
Khi Locust chạy, nó sinh ra nhiều instance WebsiteUser. Mỗi instance lặp đi lặp lại: chọn ngẫu nhiên một @task (theo trọng số), thực thi, rồi wait_time một khoảng, rồi lặp lại. Đây là mô hình mô phỏng người dùng thực rất tự nhiên — người dùng không bắn request liên tục mà có "think time" giữa các thao tác.
self.client — HTTP client thông minh
self.client là một wrapper quanh thư viện requests của Python, nhưng có thêm siêu năng lực: tự động thu thập metrics. Mỗi lần bạn gọi self.client.get() hay .post(), Locust ghi lại thời gian phản hồi, mã trạng thái, số byte, và tổng hợp thống kê real-time. Cú pháp gần như giống hệt requests, nên developer Python thấy quen thuộc ngay.
Một chi tiết quan trọng: dùng tham số name để gộp các URL động lại. Nếu không, /products/1, /products/2, /products/3 sẽ hiện ra 3 dòng thống kê riêng, làm báo cáo rối tung:
@task
def xem_san_pham(self):
product_id = random.randint(1, 10000)
self.client.get(f"/products/{product_id}", name="/products/[id]")
wait_time — mô phỏng think time
wait_time quyết định khoảng nghỉ giữa các task. Locust cung cấp between(min, max) (ngẫu nhiên), constant(x) (cố định), và constant_throughput(x) (đảm bảo mỗi user thực hiện x task/giây — cực hữu ích khi bạn muốn kiểm soát throughput chính xác).
Kiến trúc phân tán (distributed) và event-driven
Locust dùng mô hình event-driven dựa trên gevent (greenlet — coroutine nhẹ), nghĩa là một process có thể mô phỏng hàng nghìn user đồng thời mà không tốn hàng nghìn thread thật. Khi cần tải cực lớn, Locust chạy phân tán: một master điều phối và nhiều worker sinh tải, giao tiếp qua giao thức của riêng nó. Điều này giống mô hình Master/Slave của JMeter (Bài 23), nhưng cấu hình nhẹ nhàng hơn nhiều — chỉ cần chỉ định --master hoặc --worker --master-host=....
Web UI real-time
Khi chạy, Locust mở một web UI tại http://localhost:8089. Bạn nhập số user, tốc độ tăng user (spawn rate), rồi bấm Start. Đồ thị response time, requests/giây (RPS), tỉ lệ lỗi cập nhật ngay trước mắt. Điểm khác biệt so với k6/JMeter non-GUI: bạn có thể điều chỉnh tải trực tiếp trong lúc test đang chạy — tăng từ 100 lên 500 user chỉ bằng vài cú click, rất tiện khi khám phá ngưỡng chịu tải.
Tình huống thực tế
Ví dụ 1 — Startup fintech ở TP.HCM: tận dụng đội ngũ Python có sẵn
Một startup fintech (giả định tên "PayNhanh") ở Quận 1 có backend viết bằng FastAPI, đội 6 kỹ sư đều thành thạo Python nhưng chưa ai biết JavaScript sâu. Trước ngày ra mắt tính năng chuyển tiền QR, họ cần kiểm tra hệ thống chịu được 2.000 giao dịch đồng thời.
Team lead ban đầu định dùng k6, nhưng nhận ra sẽ mất 1-2 tuần để cả đội làm quen với JS và event-loop của k6. Họ chuyển sang Locust. Chỉ trong một buổi chiều, một backend engineer viết được kịch bản mô phỏng đầy đủ luồng: đăng nhập lấy token, tạo giao dịch QR, kiểm tra trạng thái. Vì là Python, anh tái sử dụng luôn hàm sinh dữ liệu giả từ codebase test đơn vị (unit test) sẵn có, dùng thư viện faker để tạo số điện thoại VN hợp lệ.
Kết quả: họ phát hiện ở mức 1.400 user, thời gian phản hồi của API tạo giao dịch nhảy từ 200ms lên 3 giây do connection pool tới PostgreSQL bị cạn. Họ chỉnh pool_size và test lại, đạt mốc 2.000 user với p95 dưới 800ms.
Bài học: Khi đội ngũ đã có sẵn năng lực Python, Locust biến việc viết test tải thành công việc "trong nhà" thay vì phải thuê chuyên gia ngoài hay học công cụ mới. Chi phí học tập gần như bằng không là lợi thế cạnh tranh thực sự.
Ví dụ 2 — Sàn thương mại điện tử: test luồng phức tạp cần logic nghiệp vụ
Một sàn TMĐT khu vực Đông Nam Á cần test luồng "giỏ hàng đến thanh toán" trước đợt sale. Vấn đề: luồng này có điều kiện phức tạp — nếu tồn kho hết thì phải chọn sản phẩm khác, nếu mã giảm giá hết hạn thì bỏ qua, và giá tiền tính toán phụ thuộc nhiều biến.
Với JMeter, logic này phải nhồi vào các JSR223 sampler viết Groovy (Bài 41) — dài dòng và khó debug. Với Locust, nhóm QA viết luồng bằng Python thuần trong một class SequentialTaskSet (đảm bảo task chạy theo thứ tự):
from locust import HttpUser, task, between, SequentialTaskSetclass MuaHang(SequentialTaskSet):
@task
def dang_nhap(self):
res = self.client.post("/login", json={"user": "test", "pass": "123"})
self.token = res.json()["token"]
@task
def them_gio_hang(self):
headers = {"Authorization": f"Bearer {self.token}"}
res = self.client.post("/cart/add", json={"sku": "AO-THUN-01"}, headers=headers)
if res.status_code == 409: # hết hàng
res.success() # đánh dấu là thành công vì đây là hành vi mong đợi
@task
def thanh_toan(self):
self.client.post("/checkout", headers={"Authorization": f"Bearer {self.token}"})
class Customer(HttpUser):
tasks = [MuaHang]
wait_time = between(2, 6)
Họ dùng logic if/else Python bình thường để xử lý các nhánh nghiệp vụ, dễ đọc như code ứng dụng thật.
Bài học: Khi kịch bản test có nhiều logic nghiệp vụ điều kiện, sức mạnh của một ngôn ngữ lập trình đầy đủ như Python vượt trội so với việc chắp vá script trong công cụ. Locust tỏa sáng ở đúng những luồng phức tạp mà JMeter gặp khó.
Ví dụ 3 — Bài học ngược: khi Locust không phải lựa chọn tốt nhất
Một công ty game mobile cần test một hệ thống matchmaking với 50.000 kết nối đồng thời, giao thức tùy chỉnh trên UDP và yêu cầu CPU cực thấp mỗi virtual user. Đội thử Locust nhưng phát hiện vấn đề: dù gevent nhẹ, việc mô phỏng 50.000 user vẫn cần nhiều worker (mỗi process Python bị giới hạn bởi GIL, thường xử lý tốt vài nghìn user/core). Họ phải dựng cụm 12 máy worker mới đạt tải mong muốn — trong khi k6 (viết bằng Go, đa luồng thật) làm được với 2 máy.
Team chuyển phần test tải cực lớn sang k6, nhưng vẫn giữ Locust cho các test luồng nghiệp vụ phức tạp cần logic Python.
Bài học: Locust rất mạnh nhưng không phải "búa vàng". Với tải cực lớn (hàng chục nghìn user trở lên) và yêu cầu tiết kiệm tài nguyên tối đa, k6 hoặc Gatling thường hiệu quả hơn về mặt tài nguyên phần cứng. Chọn công cụ theo bài toán, không theo sở thích.
Hướng dẫn từng bước
Hãy dựng một bài test Locust hoàn chỉnh từ đầu.
Bước 1 — Cài đặt. Locust yêu cầu Python 3.9+. Nên dùng môi trường ảo để tránh xung đột thư viện:
python3 -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install locust
locust --version # kiểm tra cài đặt thành công
Bước 2 — Viết file locustfile.py. Đây là kịch bản Hello World có ý nghĩa:
from locust import HttpUser, task, between
import randomclass ApiUser(HttpUser):
host = "https://api.myshop.vn" # có thể ghi đè bằng cờ --host
wait_time = between(1, 3)
def on_start(self):
# chạy MỘT LẦN khi user ảo bắt đầu — thường dùng để đăng nhập
res = self.client.post("/login", json={"email": "load@test.vn", "password": "secret"})
self.token = res.json().get("token", "")
@task(3)
def xem_danh_muc(self):
self.client.get("/categories", name="/categories")
@task(1)
def xem_chi_tiet(self):
pid = random.randint(1, 500)
self.client.get(f"/products/{pid}",
name="/products/[id]",
headers={"Authorization": f"Bearer {self.token}"})
def on_stop(self):
# chạy khi user ảo dừng — dùng để đăng xuất, dọn dẹp
self.client.post("/logout", headers={"Authorization": f"Bearer {self.token}"})
on_start và on_stop là "vòng đời" của mỗi virtual user — tương tự concept setup/teardown ở cấp độ từng user.
Bước 3 — Chạy chế độ Web UI. Trong thư mục chứa file:
locust
Mở trình duyệt tại http://localhost:8089, nhập số user (ví dụ 100), spawn rate (ví dụ 10 user/giây), host, rồi bấm Start. Quan sát các tab Statistics, Charts, Failures cập nhật real-time.
Bước 4 — Chạy chế độ headless (không GUI, cho CI/CD). Đây là chế độ bạn sẽ dùng trong pipeline (kết nối với Bài 27):
locust --headless \
--users 500 \
--spawn-rate 50 \
--run-time 5m \
--host https://api.myshop.vn \
--html bao_cao.html \
--csv ket_qua
Cờ --html xuất báo cáo HTML đẹp, --csv xuất dữ liệu thô để phân tích sau. Locust trả về exit code khác 0 nếu vượt ngưỡng lỗi (khi kết hợp với tùy chọn kiểm tra), giúp fail pipeline tự động.
Bước 5 — Chạy phân tán khi cần tải lớn. Trên máy điều phối:
locust --master --host https://api.myshop.vn
Trên mỗi máy worker (có thể nhiều máy):
locust --worker --master-host=10.0.0.5
Master tự động phân bổ user cho các worker. Quy tắc kinh nghiệm: mỗi worker nên chạy tối đa vài nghìn user, và nên có một worker process cho mỗi CPU core.
Lỗi thường gặp & mẹo
Lỗi 1 — Máy sinh tải (load generator) trở thành nút cổ chai. Đây là sai lầm kinh điển: bạn test thấy response time cao và kết luận server yếu, nhưng thực ra chính máy chạy Locust đã hết CPU. Vì GIL của Python, một process Locust chỉ dùng được một core hiệu quả. Mẹo: luôn theo dõi CPU của máy load generator; nếu vượt 80-90%, chuyển sang chạy phân tán với nhiều worker.
Lỗi 2 — Quên tham số name, báo cáo ngập trong URL động. Như đã nói, /order/1001, /order/1002... sẽ tạo hàng nghìn dòng thống kê. Luôn gộp bằng name="/order/[id]".
Lỗi 3 — Không xử lý lỗi HTTP đúng cách. Mặc định Locust coi status 2xx/3xx là thành công. Nhưng đôi khi bạn cần logic riêng — ví dụ 404 khi tìm sản phẩm không tồn tại là hành vi mong đợi. Dùng catch_response=True để tự quyết định:
with self.client.get("/products/99999", catch_response=True) as res:
if res.status_code == 404:
res.success() # đây là kết quả mong đợi, không tính là lỗi
elif res.elapsed.total_seconds() > 2:
res.failure("Quá chậm dù trả về 200")
Lỗi 4 — Không dùng think time thực tế. Nếu bỏ wait_time, mỗi user bắn request liên tục không nghỉ — tạo tải phi thực tế và làm sai lệch số liệu throughput so với hành vi người dùng thật.
Mẹo — Tái sử dụng dữ liệu test. Vì là Python, bạn có thể đọc CSV bằng csv module, sinh dữ liệu bằng faker (ví dụ faker.Faker("vi_VN") cho tên và địa chỉ Việt Nam), hoặc kéo dữ liệu từ database. Đây là lợi thế lớn so với CSV Data Set của JMeter (Bài 9) khi cần logic sinh dữ liệu phức tạp.
Mẹo — Dùng nhiều loại user cùng lúc. Bạn có thể định nghĩa nhiều class HttpUser với weight khác nhau để mô phỏng đúng tỉ lệ người dùng thực (ví dụ 80% khách xem hàng, 15% thêm giỏ, 5% thanh toán).
Mẹo — Custom metrics qua event hooks. Locust cho phép gắn listener vào các sự kiện (request thành công/thất bại) để đẩy metrics sang hệ thống ngoài như Prometheus hay InfluxDB — kết nối được với dashboard Grafana bạn học ở Bài 22 và 55.
Bài tập thực hành
- Cài đặt và chạy đầu tiên: Tạo môi trường ảo, cài Locust, viết
locustfile.pymô phỏng một user gồm 2 task (GET /vàGET /about) vớiwait_time = between(1, 4). Chạy Web UI với 50 user, spawn rate 5, và quan sát đồ thị. Dùng một site test công khai nhưhttps://httpbin.orglàm host.
- Mô phỏng luồng có đăng nhập: Thêm
on_startđể gọiPOST /post(httpbin trả lại body), lưu một giá trị từ response vàoself, rồi dùng lại giá trị đó trong một task khác qua header. Xác nhận bằng tab Statistics rằng cả hai endpoint đều được ghi nhận.
- So sánh tỉ trọng task: Tạo 2 task với
@task(4)và@task(1). Chạy 100 user trong 2 phút và kiểm chứng trong báo cáo CSV rằng số request của task thứ nhất xấp xỉ gấp 4 lần task thứ hai.
- Headless + báo cáo: Chạy lại kịch bản ở chế độ
--headlessvới--run-time 3m, xuất--htmlvà--csv. Mở file HTML và viết 3 câu nhận xét về p95 response time và RPS đạt được.
- Suy ngẫm chọn công cụ: Viết một đoạn ngắn (5-7 câu) so sánh: với dự án hiện tại của bạn, nếu phải chọn giữa Locust, k6 và JMeter, bạn chọn cái nào và vì sao — dựa trên kỹ năng đội ngũ, quy mô tải cần thiết, và độ phức tạp logic nghiệp vụ.
Tóm tắt
Locust là công cụ load testing mã nguồn mở với triết lý cốt lõi: kịch bản test là code Python thuần, mô tả hành vi người dùng qua các class và @task. Điểm mạnh lớn nhất của nó là hạ rào cản gần bằng không cho các đội đã biết Python, cho phép tái sử dụng toàn bộ hệ sinh thái thư viện Python, và diễn đạt logic nghiệp vụ phức tạp một cách tự nhiên bằng if/else thay vì chắp vá script trong công cụ.
Kiến trúc event-driven trên gevent giúp một process mô phỏng hàng nghìn user, và mô hình master/worker mở rộng ra nhiều máy khi cần tải lớn. Web UI real-time cho phép điều chỉnh tải ngay khi test đang chạy, còn chế độ headless với xuất HTML/CSV phục vụ tốt cho CI/CD.
Nhưng hãy nhớ bài học từ ví dụ thứ ba: Locust không phải "búa vàng". Vì giới hạn GIL của Python, ở tải cực lớn nó ngốn nhiều tài nguyên hơn k6 (viết bằng Go). Nguyên tắc chọn công cụ vẫn không đổi trong suốt khóa học này: cân nhắc kỹ năng đội ngũ, quy mô tải, và độ phức tạp logic — rồi chọn công cụ phù hợp với bài toán, không phải theo sở thích cá nhân. Locust xứng đáng có một chỗ trong hộp đồ nghề của mọi Performance Engineer, đặc biệt trong những môi trường lấy Python làm trung tâm.