Tình huống thực tế
Lỗi này thường xảy ra vào lúc bạn ít ngờ tới nhất. Microservice Spring Boot của bạn đang hoạt động ổn định, thì bỗng nhiên nhật ký hệ thống (logs) bùng nổ với một loạt dòng chữ đỏ. Thay vì nhận được phản hồi API thành công, bạn lại thấy thông báo này:
Exception in thread "main" java.net.UnknownHostException: api.service-provider.com
at java.base/java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:229)
at java.base/java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
at java.base/java.net.Socket.connect(Socket.java:609)
Lỗi này có nghĩa là Máy ảo Java (JVM) đã bị "lạc đường". Nó có tên miền (hostname), nhưng không thể tìm thấy địa chỉ IP tương ứng để gửi dữ liệu. Nếu không có IP đó, kết nối đơn giản là không thể bắt đầu.
Tại sao việc tra cứu lại thất bại?
Chẩn đoán đúng bệnh là đã thắng được một nửa trận chiến. Hầu hết các lỗi DNS trong Java đều rơi vào ba nhóm sau:
- **Cơ sở hạ tầng (Infrastructure):** Máy chủ DNS không thể truy cập được, hoặc máy chủ đã bị mất kết nối internet.
- **Cấu hình (Configuration):** Lập trình viên viết sai chính tả, hoặc một host nội bộ riêng tư chưa được đăng ký trong các bản ghi DNS công cộng.
- **JVM Caching:** Máy chủ từ xa đã thay đổi địa chỉ IP, nhưng JVM của bạn vẫn "khăng khăng" bám lấy bản ghi cũ không còn hiệu lực.
Bước 1: Kiểm tra cơ bản
Luôn bắt đầu từ những thứ cơ bản nhất. Hãy thực hiện tra cứu thủ công từ terminal của máy tính đang chạy ứng dụng. Sử dụng lệnh nslookup hoặc dig:
nslookup api.service-provider.com
Nếu lệnh này trả về lỗi, vấn đề không nằm ở mã nguồn Java của bạn—đó là sự cố mạng ở cấp hệ thống. Hãy cẩn thận với các lỗi chính tả đơn giản trong tệp application.yml. Một sai sót phổ biến là thừa khoảng trắng ở cuối hoặc viết lộn ký tự, ví dụ như api.servcie-provider.com, điều này sẽ gây ra ngoại lệ mỗi khi chạy.
Bước 2: Sử dụng tệp Hosts để kiểm tra cục bộ
Đôi khi bạn cần kết nối với một máy chủ phát triển chưa có bản ghi DNS công cộng. Trong những trường hợp này, bạn có thể ép buộc ánh xạ địa chỉ trong tệp hosts của hệ điều hành.
Trên Linux hoặc macOS: Mở tệp /etc/hosts với quyền root.
sudo nano /etc/hosts
# Thêm một bản ghi thủ công:
1.2.3.4 api.service-provider.com
Trên Windows: Chỉnh sửa tệp C:\Windows\System32\drivers\etc\hosts với quyền Administrator.
Cách tiếp cận này bỏ qua hoàn toàn các máy chủ DNS. Nó yêu cầu hệ điều hành phân giải tên miền cụ thể đó sang IP cụ thể ngay lập tức.
Bước 3: Khắc phục sự cố trên Docker và Kubernetes
Môi trường container hóa thêm một lớp phức tạp cho mạng lưới. Nếu ứng dụng của bạn nằm trong một container, lỗi UnknownHostException thường bắt nguồn từ các mạng bị cô lập.
Mạng trong Docker
Nếu bạn đang cố gắng kết nối với một container khác trên cùng một host, hãy đảm bảo chúng dùng chung một mạng Docker. Bạn cũng có thể ép buộc container sử dụng một nhà cung cấp DNS đáng tin cậy như Google (8.8.8.8) hoặc Cloudflare (1.1.1.1):
docker run --dns 8.8.8.8 my-java-app
Kubernetes CoreDNS
Bên trong một cluster, các tên ngắn (short names) chỉ hoạt động trong cùng một namespace. Nếu bạn cần truy cập một dịch vụ ở namespace khác, bạn phải sử dụng Tên miền đầy đủ (FQDN):
# Định dạng chuẩn: service-name.namespace.svc.cluster.local
auth-service.production.svc.cluster.local
Bước 4: Kiểm soát bộ nhớ đệm DNS của JVM
Thủ phạm thường bị bỏ qua nhất chính là bộ nhớ đệm (cache) nội bộ của JVM. Theo mặc định, nếu Security Manager đang hoạt động, JVM sẽ lưu kết quả tra cứu DNS thành công mãi mãi (TTL = -1). Nếu nhà cung cấp dịch vụ đám mây của bạn chuyển load balancer sang một IP mới, ứng dụng của bạn sẽ tiếp tục truy cập vào một địa chỉ không còn tồn tại.
Bạn có thể ép buộc JVM làm mới bộ nhớ đệm sau mỗi 60 giây bằng cách thêm tham số khởi động này:
-Dsun.net.inetaddr.ttl=60
Nếu bạn không thể thay đổi script khởi động, hãy thiết lập bằng mã nguồn ngay từ đầu phương thức main:
java.security.Security.setProperty("networkaddress.cache.ttl" , "60");
Xác minh: Kiểm tra việc phân giải tên miền
Chạy đoạn mã nhỏ này để xác nhận JVM có thể nhìn thấy host. Nó giúp loại bỏ sự phức tạp của các framework ứng dụng đầy đủ:
import java.net.InetAddress;
public class DNSCheck {
public static void main(String[] args) {
try {
String host = "api.service-provider.com";
System.out.println("Resolving: " + host);
System.out.println("IP: " + InetAddress.getByName(host).getHostAddress());
} catch (Exception e) {
e.printStackTrace();
}
}
}
Chiến lược phòng ngừa
Độ tin cậy của mạng là một phần cốt lõi của các hệ thống phân tán. Khi lập kế hoạch bố trí mạng, hãy sử dụng một công cụ như Subnet Calculator này để thiết lập các khối CIDR. Điều này giúp ngăn chặn việc chồng lấn các subnet, vốn thường gây ra các vòng lặp định tuyến dẫn đến lỗi DNS.
Cuối cùng, hãy luôn triển khai cơ chế thử lại (retries) với khoảng thời gian chờ tăng dần (exponential backoff). DNS vốn dĩ không ổn định tuyệt đối. Đôi khi chỉ cần một lần thử lại sau 50ms là đủ để vượt qua một sự cố tạm thời và giữ cho ứng dụng của bạn hoạt động trơn tru.

