從服務間的一次調用分析整個springcloud的調用過程(二)
- 2022 年 2 月 16 日
- 筆記
- ribbon, spring cloud feign, springboot, SpringCloud, 負載均衡
先看示例程式碼
@RestController @RequestMapping("/students") public class StudentController { @Autowired private TeacherFeignClient teacherFeignClient; @GetMapping("/{id}") public ResponseEntity<Teacher> getTeacher(@PathVariable("id") int id) { return ResponseEntity.ok(teacherFeignClient.findTeacher(5)); } }
@FeignClient(name = "teacher-service",path = "/teachers") public interface TeacherFeignClient { @GetMapping("/{id}") Teacher findTeacher(@PathVariable("id") int id); }
1. 首先我們肯定知道spring會基於TeacherFeignClient生成代理類Proxy,代理類的程式碼如下
public final Teacher findTeacher(int var1) throws { try { return (Teacher)super.h.invoke(this, m3, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } }
代理類中的invoke方法指向的是feign.ReflectiveFeign.invoke方法,之後會走向調用SynchronousMethodHandler invoke方法,其中SynchronousMethodHandler中的target屬性就是包裝了feignclient的相關屬性,比如service,url等,然後會調用executeAndDecode方法,該方法第一步就會根據target構造Request對象,這個方法最終會調用Cilent介面的實現類LoadBalancerFeignClient。


@Override public Response execute(Request request, Request.Options options) throws IOException { try { URI asUri = URI.create(request.url()); String clientName = asUri.getHost(); URI uriWithoutHost = cleanUrl(request.url(), clientName); FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest( this.delegate, request, uriWithoutHost); IClientConfig requestConfig = getClientConfig(options, clientName); return lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse(); } catch (ClientException e) { IOException io = findIOException(e); if (io != null) { throw io; } throw new RuntimeException(e); } }
View Code
2. LoadBalancerFeignClient execute方法先構造RibbonRequest,然後會去獲取IClientConfig,再去調用SpringClientFactory的getInstance方法,走到NamedContextFactory getInstance方法。


@Override public <C> C getInstance(String name, Class<C> type) { C instance = super.getInstance(name, type); if (instance != null) { return instance; } IClientConfig config = getInstance(name, IClientConfig.class); return instantiateWithConfig(getContext(name), type, config); }
View Code
3. 最終會通過AnnotationConfigApplicationContext getBean方法經過一些列流程走到 RibbonClientConfiguration 裝載IClientConfig bean方法,最後就獲得了當前service ribbon的相關配置


@Bean @ConditionalOnMissingBean public IClientConfig ribbonClientConfig() { DefaultClientConfigImpl config = new DefaultClientConfigImpl(); config.loadProperties(this.name); config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT); config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT); config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD); return config; }
View Code
4. 回到LoadBalancerFeignClient execute方法64行獲取到IClientConfig後,此時有個核心步驟就是根據當前clientName獲取到一個FeignLoadBalancer的實現,可以看到其中有一個cache屬性,如果cache有的話就從cache返回,這也就feign第一次調用會慢的原因之一,因為首次需要去載入這個FeignLoadBalancer;首次載入的時候因為這裡我們沒有配置retryFactory,所以會返回一個 FeignLoadBalancer


public FeignLoadBalancer create(String clientName) { FeignLoadBalancer client = this.cache.get(clientName); if(client != null) { return client; } IClientConfig config = this.factory.getClientConfig(clientName); ILoadBalancer lb = this.factory.getLoadBalancer(clientName); ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class); client = loadBalancedRetryFactory != null ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector, loadBalancedRetryFactory) : new FeignLoadBalancer(lb, config, serverIntrospector); this.cache.put(clientName, client); return client; }
View Code
5. 獲取到FeignLoadBalancer會執行它父類AbstractLoadBalancerAwareClient中的executeWithLoadBalancer(該方法中牽扯到負載均衡的邏輯,會在下一篇中說到),該方法最終還是會執行FeignLoadBalancer的execute方法,方法中會獲取request的client,該client默認是feign.Client.Default,實現就是通過構造HttpURLConnection發起http請求


@Override public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride) throws IOException { Request.Options options; if (configOverride != null) { RibbonProperties override = RibbonProperties.from(configOverride); options = new Request.Options( override.connectTimeout(this.connectTimeout), override.readTimeout(this.readTimeout)); } else { options = new Request.Options(this.connectTimeout, this.readTimeout); } Response response = request.client().execute(request.toRequest(), options); return new RibbonResponse(request.getUri(), response); }
View Code