Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

JMeter Distributed Testing — Master/Slave

Performance Testing with JMeter and k6 Bài 23/60

Mở đầu — vì sao bài này quan trọng

Hãy tưởng tượng bạn nhận yêu cầu: "Tối 12/12 sàn thương mại điện tử của công ty phải chịu được 50.000 người dùng đồng thời. Hãy chứng minh nó chịu được." Bạn mở JMeter, cấu hình một Thread Group với 50.000 thread, nhấn Start... và chiếc laptop của bạn đứng hình. CPU bốc lên 100%, RAM cạn kiệt, JMeter báo OutOfMemoryError, và những con số bạn thu về hoàn toàn vô nghĩa — vì thứ đang chậm không phải hệ thống bị test, mà là chính công cụ test.

Đây là bức tường mà mọi kỹ sư performance đều đâm phải sớm hay muộn: một máy JMeter đơn lẻ có giới hạn. Tùy cấu hình RAM/CPU và độ nặng của kịch bản, một máy thường chỉ tạo được khoảng 1.000–3.000 virtual user (VU) một cách ổn định. Muốn vượt qua con số đó để mô phỏng tải thật của một chiến dịch flash sale, bạn buộc phải chia tải ra nhiều máy — đó chính là Distributed Testing (kiểm thử phân tán) theo mô hình Master/Slave của JMeter.

Bài này dạy bạn cách biến nhiều máy JMeter rời rạc thành một "khẩu đội pháo" phối hợp: một máy chỉ huy (Master/Controller) điều khiển nhiều máy bắn tải (Slave/Worker), rồi gom kết quả về một chỗ. Đây là kỹ năng bắt buộc nếu bạn muốn test tải ở quy mô thật, và cũng là phần khiến nhiều người bối rối nhất vì nó dính tới mạng, firewall, RMI và đồng bộ file. Chúng ta sẽ đi qua cả kiến trúc lẫn thao tác thực tế để bạn tự dựng được một cụm JMeter phân tán từ đầu.

Khái niệm cốt lõi

Vì sao một máy không đủ

Mỗi virtual user trong JMeter thực chất là một Java thread. Mỗi thread ngốn bộ nhớ stack, và khi hàng nghìn thread cùng gửi request, cùng parse response, cùng chạy assertion và ghi listener, thì CPU của máy tạo tải trở thành điểm nghẽn. Khi công cụ đo bị nghẽn, nó phản hồi trễ, con số response time bạn ghi lại bị "nhiễm" độ trễ của chính máy test — kết quả là bạn đo nhầm. Nguyên tắc vàng: máy tạo tải luôn phải "khỏe" hơn nhiều so với mức tải nó tạo ra, nếu không số liệu là rác.

Distributed testing giải quyết bằng cách chia nhỏ: thay vì 1 máy gánh 30.000 VU, bạn dùng 10 máy, mỗi máy chỉ gánh 3.000 VU — mức mà mỗi máy xử lý thoải mái.

Kiến trúc Master/Slave

                    ┌─────────────────────┐
                    │   MASTER (Controller)│
                    │  - Chạy GUI/CLI      │
                    │  - Gửi test plan     │
                    │  - Gom kết quả       │
                    └──────────┬──────────┘
                       RMI (điều khiển)
          ┌────────────────────┼────────────────────┐
          ▼                    ▼                    ▼
   ┌─────────────┐      ┌─────────────┐      ┌─────────────┐
   │  SLAVE 1    │      │  SLAVE 2    │      │  SLAVE 3    │
   │ jmeter-server│      │ jmeter-server│      │ jmeter-server│
   │ tạo tải →   │      │ tạo tải →   │      │ tạo tải →   │
   └──────┬──────┘      └──────┬──────┘      └──────┬──────┘
          └────────────────────┼────────────────────┘
                               ▼
                    ┌─────────────────────┐
                    │  HỆ THỐNG BỊ TEST    │
                    │  (Web / API / DB)    │
                    └─────────────────────┘
  • Master (Controller): máy bạn ngồi thao tác. Nó không tự tạo tải (hoặc tạo rất ít). Nhiệm vụ của nó là phân phối test plan (.jmx) tới các slave, ra lệnh start/stop, và gom (aggregate) kết quả sampler từ tất cả slave gửi về qua RMI.
  • Slave (Worker/jmeter-server): mỗi slave chạy tiến trình jmeter-server. Chúng là những "cỗ máy bắn" thực sự — nhận test plan từ master rồi tự tạo tải đập vào hệ thống bị test, đồng thời stream kết quả từng sampler ngược về master.

Điểm mấu chốt về cách nhân tải

Đây là chỗ 90% người mới bị sai. Khi bạn chạy phân tán, mỗi slave chạy toàn bộ Thread Group y hệt nhau. Nếu Thread Group cấu hình 1.000 thread và bạn có 5 slave, tổng tải thực tế đập vào server là 1.000 × 5 = 5.000 VU, không phải 1.000 chia đều. Số trong file .jmx là số VU mỗi slave. Ghi nhớ công thức:

> Tổng VU = (số thread trong Thread Group) × (số slave)

Nếu sếp yêu cầu 50.000 VU và bạn có 10 slave, thì mỗi Thread Group để 5.000 thread. Nhầm chỗ này là lý do kinh điển khiến báo cáo sai gấp N lần.

Giao thức RMI và cổng mạng

Master và slave nói chuyện với nhau qua RMI (Java Remote Method Invocation). RMI dùng vài cổng: một cổng registry (mặc định 1099) và một cổng cho luồng dữ liệu server. Trong môi trường có firewall (mà môi trường doanh nghiệp Việt Nam thì gần như luôn có), bạn phải mở đúng cổng và cố định chúng, nếu không master sẽ "gọi mà slave không nghe". Đây là nguyên nhân lỗi số một khi dựng cụm.

Tình huống thực tế

Tình huống 1: Tiki chuẩn bị cho ngày đôi 12/12

Một đội QA của sàn thương mại điện tử (bối cảnh giả định theo mô hình Tiki) cần chứng minh hệ thống checkout chịu được 40.000 người mua đồng thời trong khung giờ vàng 20h–21h. Ban đầu bạn QA thử 1 máy EC2 với 40.000 thread — JMeter chết ngay ở mức 8.000 VU vì hết heap. Con số response time nhảy loạn xạ vì máy test tự nghẽn.

Đội chuyển sang mô hình phân tán: dựng 1 master + 8 slave, mỗi slave là một máy ảo 4 vCPU / 8GB RAM. Mỗi Thread Group để 5.000 thread, nhân với 8 slave = 40.000 VU đúng như yêu cầu. Kết quả: mỗi slave chỉ chạy ~55% CPU, response time đo được sạch. Họ phát hiện hệ thống nghẽn ở mức 34.000 VU (do connection pool database), kịp mở rộng pool trước ngày 12/12.

Bài học: Khi một máy chết trước khi chạm tải mục tiêu, đừng cố "vắt" thêm máy đó — hãy nhân chiều ngang bằng nhiều slave. Và luôn kiểm tra CPU/RAM của chính slave: nếu slave chạy quá 70–80% CPU, con số đo đã bắt đầu nhiễm.

Tình huống 2: Một fintech ví điện tử và bài học firewall

Một startup ví điện tử (bối cảnh kiểu MoMo/ZaloPay) dựng cụm 4 slave trên cùng một VPC nội bộ để test cổng thanh toán. Master nhìn thấy slave khi ping, nhưng khi nhấn "Remote Start" thì báo lỗi Connection refused to host và test không chạy. Cả buổi chiều loay hoay.

Vấn đề: RMI mặc định chọn cổng ngẫu nhiên cho luồng dữ liệu server. Firewall của VPC chỉ mở 1099, nên khi slave cố mở kết nối ngược về master trên một cổng random cao, firewall chặn. Cách khắc phục: cố định cổng bằng cách thêm vào jmeter.properties trên slave: server.rmi.localport=50000 và mở đúng dải cổng đó trên firewall (1099 + 50000 + 4000–4002 cho các luồng). Sau khi cố định, cụm chạy mượt.

Bài học: Distributed testing 80% là bài toán mạng, không phải bài toán JMeter. Trước khi đổ lỗi cho công cụ, hãy telnet <slave_ip> 1099 để xác nhận đường mạng thông. Luôn cố định cổng RMI trong môi trường có firewall.

Tình huống 3: Nhầm số VU khiến báo cáo phóng đại 5 lần

Một bạn QA junior tại công ty gia công phần mềm ở Đà Nẵng chạy cụm 5 slave, mỗi slave 2.000 thread, và tự tin báo cáo: "Hệ thống chịu được 2.000 user, response time 300ms, đạt yêu cầu." Sếp duyệt, deploy production. Đến khi có 2.000 user thật, hệ thống sập.

Nguyên nhân: bạn ấy tưởng 2.000 thread là tổng, nhưng thực tế cụm đã tạo 2.000 × 5 = 10.000 VU. Con số 300ms đẹp đẽ đó là ở mức 10.000 VU giả — nhưng bạn ấy tưởng là 2.000. Ở mức 2.000 VU thật thì mọi thứ ổn, nhưng đó không phải điều được kiểm chứng; ngược lại nếu hiểu đúng thì hệ thống thực ra đã pass ở 10.000 — chỉ là báo cáo sai nhãn khiến niềm tin bị đặt sai chỗ. Rắc rối thật nằm ở chỗ nhãn sai làm mọi quyết định capacity phía sau đều lệch.

Bài học: Luôn ghi rõ trong báo cáo: "X thread/slave × Y slave = Z tổng VU". Nhãn số liệu sai còn nguy hiểm hơn không có số liệu, vì nó tạo niềm tin giả.

Hướng dẫn từng bước

Giả sử bạn có 1 máy master (IP 10.0.0.1) và 2 máy slave (10.0.0.2, 10.0.0.3), tất cả cài cùng phiên bản JMeter và cùng phiên bản Java (điều kiện bắt buộc — lệch version là hỏng).

Bước 1 — Cấu hình cổng RMI trên slave. Trên mỗi slave, mở bin/jmeter.properties, đặt cổng cố định:

server.rmi.localport=50000
server.rmi.ssl.disable=true
Tắt SSL cho môi trường lab nội bộ để tránh phiền phức chứng chỉ (production thì cân nhắc bật lại).

Bước 2 — Khởi động jmeter-server trên từng slave. Trên mỗi máy slave, chạy:

Linux/Mac

./bin/jmeter-server -Djava.rmi.server.hostname=10.0.0.2

Windows

jmeter-server.bat
Tham số -Djava.rmi.server.hostname phải là IP mà master gọi tới được. Khi thấy log Created remote object là slave đã sẵn sàng nghe.

Bước 3 — Khai báo danh sách slave trên master. Trên máy master, mở bin/jmeter.properties, sửa dòng:

remote_hosts=10.0.0.2,10.0.0.3
Đây là danh sách các slave master sẽ điều khiển.

Bước 4 — Tính lại số thread. Mở test plan .jmx, đặt số thread trong Thread Group bằng (tổng VU mong muốn ÷ số slave). Ví dụ muốn 6.000 VU với 2 slave → mỗi Thread Group để 3.000.

Bước 5 — Chạy phân tán bằng Non-GUI (khuyến nghị). Không bao giờ chạy tải lớn bằng GUI. Trên master:

./bin/jmeter -n -t testplan.jmx -R 10.0.0.2,10.0.0.3 -l result.jtl -e -o report/
  • -n: non-GUI mode
  • -t: test plan
  • -R: danh sách remote host (ghi đè remote_hosts)
  • -l: file kết quả tổng hợp .jtl
  • -e -o: sinh báo cáo HTML dashboard sau khi xong
Bước 6 — Đảm bảo file phụ trợ có trên slave. Nếu test plan dùng CSV Data Set, mỗi slave phải có bản copy của file CSV ở đúng đường dẫn tương đối. Master không tự gửi file dữ liệu cho slave — nó chỉ gửi file .jmx. (Có thể bật mode=StrippedBatch để giảm lưu lượng kết quả trả về.)

Bước 7 — Theo dõi và gom kết quả. Kết quả từ tất cả slave stream về master và ghi vào result.jtl. Sau khi test xong, mở report/index.html để xem dashboard tổng hợp toàn cụm.

Lỗi thường gặp & mẹo

  • Lệch phiên bản JMeter/Java giữa master và slave → RMI serialize lỗi, kết nối gãy giữa chừng. Mẹo: đóng gói cùng một bản JMeter, copy y hệt sang mọi máy; kiểm tra java -version khớp.
  • Quên nhân số VU với số slave → báo cáo sai gấp N lần (như tình huống 3). Mẹo: ghi công thức tổng VU ngay trên đầu báo cáo.
  • Firewall chặn cổng RMIConnection refused / test treo ở "Starting". Mẹo: cố định server.rmi.localport, mở cổng 1099 + cổng đó trên cả hai chiều; test đường mạng bằng telnet/nc trước.
  • Slave không có file CSV → lỗi "File ... not found" trên slave, hoặc dữ liệu rỗng. Mẹo: copy thủ công mọi file phụ trợ sang từng slave, giữ đúng cấu trúc thư mục.
  • Bật listener nặng (View Results Tree, Graph) khi chạy phân tán → master ngộp vì phải render kết quả của toàn cụm. Mẹo: chạy non-GUI, chỉ ghi .jtl, phân tích sau.
  • Slave bị quá tải chính nó (CPU > 80%) → số liệu nhiễm. Mẹo: theo dõi CPU/RAM từng slave; nếu cao, thêm slave chứ đừng ép thêm thread.
  • -Djava.rmi.server.hostname sai → master gọi vào IP nội bộ không tới được (đặc biệt trên cloud có IP public/private). Mẹo: đặt hostname bằng đúng IP mà master thấy được.
  • Đặt master cùng làm slave để "tận dụng" → master vừa điều phối vừa tạo tải sẽ nghẽn. Mẹo: giữ master sạch, chỉ làm nhiệm vụ điều khiển và gom.

Bài tập thực hành

  • Dựng cụm tối thiểu: Dùng máy của bạn làm master và một máy ảo (hoặc một máy đồng nghiệp cùng mạng LAN) làm slave. Cấu hình remote_hosts, khởi động jmeter-server, và chạy thành công một test plan đơn giản (GET một trang bất kỳ) qua cờ -R. Xác nhận trong log master thấy "Starting the test on host 10.0.0.2".
  • Bài toán tính VU: Sếp yêu cầu mô phỏng 24.000 VU. Bạn có sẵn 6 máy slave đồng cấu hình. Hãy tính số thread cần đặt trong Thread Group và viết đúng câu lệnh jmeter -n -t ... -R ... cho tình huống này. (Đáp án: 4.000 thread/slave.)
  • Gỡ lỗi firewall: Cố ý bật firewall chặn cổng 50000 trên slave, chạy test và quan sát thông báo lỗi. Sau đó mở cổng, cố định server.rmi.localport, chạy lại và ghi lại sự khác biệt trong log. Mục tiêu: nhận diện được "chữ ký" của lỗi mạng RMI.
  • So sánh sạch/bẩn: Chạy 6.000 VU trên 1 máy (nếu máy chịu nổi), rồi chạy 6.000 VU chia cho 2 slave. So sánh CPU máy tạo tải và response time trung bình giữa hai lần. Viết 3 câu giải thích vì sao (nếu) số liệu khác nhau.

Tóm tắt

Distributed Testing theo mô hình Master/Slave là cách JMeter vượt qua giới hạn ~1.000–3.000 VU của một máy đơn để mô phỏng tải quy mô thật. Master điều phối và gom kết quả; các Slave (jmeter-server) mới thực sự tạo tải. Ba điều bạn phải khắc cốt ghi tâm: (1) Tổng VU = số thread × số slave — nhầm chỗ này là báo cáo sai gấp bội; (2) distributed testing phần lớn là bài toán mạng/RMI/firewall — hãy cố định cổng và kiểm tra đường truyền trước khi đổ lỗi cho công cụ; (3) master, slave phải cùng phiên bản JMeter/Java, và mọi file phụ trợ (CSV) phải có mặt trên từng slave. Luôn chạy tải lớn ở non-GUI mode, giữ master sạch, và theo dõi sức khỏe của chính các slave để số liệu không bị nhiễm. Nắm được những nguyên tắc này, bạn đã sẵn sàng dựng một cụm bắn tải đủ sức mô phỏng một đêm 12/12 thực thụ.