Bối cảnh và Cấu hình
Trong các phiên bản Spring Boot trước 2.4.x, hệ sinh thái Spring Cloud thường tích hợp sẵn thành phần cân bằng tải Ribbon. Từ phiên bản 2.4.x trở đi, cấu trúc mặc định đã chuyển sang sử dụng Spring Cloud LoadBalancer, tuy nhiên việc hiểu rõ cơ chế hoạt động của Ribbon kết hợp với Feign Client vẫn rất quan trọng khi bảo trì các dự án legacy hoặc tùy chỉnh hành vi cụ thể.
Cấu hình thời gian chờ (timeout) cho Feign client thường được thiết lập như sau để đảm bảo dịch vụ không bị treo khi gọi chậm:
<code><span class="language-yaml">feign:
client:
config:
default:
connectTimeout: 500
readTimeout: 500</span></code>
Mô phỏng phía Service Provider
Xét một endpoint cung cấp thông tin người dùng có độ trễ giả lập cao, dễ dẫn đến tình trạng vượt quá ngưỡng thời gian chờ đã cấu hình:
<code><span class="language-java">@RestController
@RequestMapping("/member")
public class MemberServiceController implements Serializable {
@GetMapping(path = "/view/{identifier}")
public UserProfile getProfileDetails(@PathVariable("identifier") String identifier) {
// Giả lập xử lý mất thời gian
try {
Thread.sleep(4000);
} catch (InterruptedException interruptedException) {
Thread.currentThread().interrupt();
throw new RuntimeException("Dịch vụ bị gián đoạn", interruptedException);
}
UserProfile profile = new UserProfile();
profile.setUniqueCode(identifier);
profile.setUsername("user_" + identifier);
profile.setLevel("active");
return profile;
}
}</span></code>
Khi client Feign gọi vào đường dẫn này với readTimeout là 500ms, nó sẽ kích hoạt cơ chế xử lý lỗi về phía client.
Luồng thực thi và Theo dõi Stack Trace
Khi xảy ra sự cố kết nối hoặc vượt quá thời gian chờ, stack trace sẽ phản ánh lộ trình truy cập từ lớp Socket xuống tới lớp trừu tượng của Load Balancer. Dưới đây là các điểm mấu chốt trong chuỗi gọi:
SocketInputStream.socketRead0: Chờ dữ liệu từ mạng.HttpClient.parseHTTPHeader: Phân tích phản hồi HTTP.Feign.Client$Default.execute: Thực hiện yêu cầu HTTP gốc.LoadBalancerFeignClient.execute: Chuyển đổi yêu cầu qua lớp cân bằng tải.AbstractLoadBalancerAwareClient.executeWithLoadBalancer: Chọn instance đích.
Lỗi cuối cùng bắt nguồn từ tầng TCP/IP của JVM và được bọc lại tại lớp SynchronousMethodHandler.
Cơ chế của SynchronousMethodHandler
Lớp xử lý đồng bộ này quản lý vòng lặp gửi yêu cầu và cơ chế thử lại (retry):
- Khởi tạo biến số: Đặt lại bộ đếm lần thử và tính toán khoảng cách giữa các lần retry dựa trên công thức tăng trưởng theo hàm mũ (thường là exponential backoff).
- Thực thi yêu cầu: Nếu nhận được kết quả hợp lệ, thoát khỏi vòng lặp và trả về đối tượng.
- Xử lý ngoại lệ: Nếu phát hiện lỗi I/O, đóng gói lại thành ngoại lệ Feign cụ thể. Nếu số lần thử chưa đạt giới hạn tối đa, áp dụng độ trễ và thực hiện lại. Khi vượt quá ngưỡng, ném ngoại lệ cuối cùng.
Thời gian nghỉ giữa các lần retry thường tuân theo quy tắc:period * multiplier ^ attempt, giúp giảm tải đột biến lên server khi đang gặp sự cố.
Vai trò của LoadBalancerFeignClient
Thành phần này đóng vai trò cầu nối, biến đổi lời gọi Feign thuần túy thành một yêu cầu đi qua bộ cân bằng tải Ribbon. Nó chịu trách nhiệm chọn instance phù hợp từ danh sách dịch vụ đăng ký và bao bọc các lỗi từ phía ribbon (ClientException) thành IOException để Feign Client hiểu được.
Chuyển đổi Ngoại lệ Cuối cùng
Kết quả cuối cùng là mọi vấn đề liên quan đến kết nối mạng đều xuất phát dưới dạng IOException. Trong kiến trúc Feign, SocketTimeoutException là một loại con của IOException. Lớp handler chính thức sẽ bắt lấy các ngoại lệ này và đóng gói chúng vào FeignException tương ứng, giúp tầng nghiệp vụ phía trên dễ dàng nhận diện nguyên nhân lỗi mà không cần phân biệt sâu về lỗi nền tảng mạng.