初識Spring Cloud Eureka(三)(Eureka客戶端之間 服務的相互呼叫)

餘孤寒發表於2019-03-31

接著上一篇部落格,客戶端的建立 我們知道了服務的註冊與發現,那麼,服務之間是怎麼進行互相呼叫的呢?

我們先看一下服務列表,然後通過例項,來看一下怎麼進行服務之間的呼叫,再稍微看一下原始碼,看看呼叫是怎麼實現的。

首先,我們按照上一篇部落格的方法,建立了三個服務,一個server端,兩個client,通過訪問server,如下所示:

 使用eureka_client-2呼叫eureka_client-1的服務,首先在eureka_client-1中定義controller,返回傳送過來的值,如下所示:

@RestController
@RequestMapping("test")
public class TestController {

    @GetMapping("getSendStr")
    public String getSendStr(String string) {
        return "收到的訊息是:" + string;
    }
}

然後,在 eureka_client-2中定義一個controller,用來獲取客戶端的請求,然後呼叫eureka_client-1服務中剛剛定義的方法。

我們先使用RestTemplate的api去進行呼叫。

使用方法很簡單,在啟動類配置RestTemplate的Bean,把他交由spring進行管理,然後引入Ribbon的jar包,用來進行負載均衡。首先引入jar,如下所示:

<dependency>
       <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

 然後,新增bean:

@Bean
//實現負載均衡的註解
@LoadBalanced
public RestTemplate getRestTemplate(){
    return new RestTemplate();
}

接著,就可以實現controller了,如下所示:

@RestController
@RequestMapping("test")
public class GetController {

    //注入剛才交由spring託管的bean
    @Autowired
    RestTemplate restTemplate;

    @GetMapping("sendStr")
    public String sendStr(String string) {
        Map map = new HashMap();
        map.put("string", string);

        //這裡的url不用直接寫成ip,使用服務名稱代替即可,切記,RestTemplate的bean實現是必須新增@LoadBalanced註解,後續會解釋為什麼
        return restTemplate.getForEntity("http://eureka-client-1/test/getSendStr?string="+string, String.class).getBody();
    }
}

 好了,一切就緒了,為了除錯方便,這裡直接使用的get方法,在瀏覽器上訪問eureka_client-2,然後呼叫eureka_client-1,接著返回資訊到瀏覽器,如下:

這樣就完了一次服務之間的呼叫了,是不是很簡單呢。

那麼,這一切究竟是怎麼實現的呢?

我們追蹤restTemplate.getForEntity,如下圖,最終呼叫了execute方法,此時的url值為:

http://eureka-client-1/test/getSendStr?string=我是訊息 ,顯然,這樣是無法訪問到相關服務的,接著向下看。

	@Override
	public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables)
			throws RestClientException {

		RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
		ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
		return nonNull(execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables));
	}

後續呼叫了DefaultUriBuilderFactory的expand()方法,得到一個UriBuilder,此時我們看,UriBuilder實際上就是把請求連線給拆分開了,其中host部分,就是我們想要呼叫的服務的名稱。如下圖:

然後,我們回到RestTemplate,來跟蹤執行doExecute方法,先看一下結果,IDEA,alt+滑鼠左鍵即可,可以發現,返回了服務呼叫的結果:

那裡邊究竟發生了什麼呢?接著,我們追蹤到了InterceptingClientHttpRequest的executeInternal()方法,直到此時,我們的url還沒有發生變化,那麼,祕密肯定就在其中,我們接著追蹤:看我們發現了什麼?

 LoadBalanceInterceptor攔截器!祕密肯定在他身上,我們先去看他一眼,他在spring-cloud-commons包下,實現了ClientHttpRequestInterceptor介面。知道他之後,我們接著往後走,執行他的intercept方法,拿到了前邊準備的host,即此處的serviceName,如下圖:

看來到了這裡,也是時候施展變換魔法了,首先是執行loadBalancer的execute方法,這個loadBalancer也是大有來頭,我們可以看到,它來自ribbon,繼承了LoadBalancerClient,作為bean註冊到了這裡:

 在RibbonLoadBalancerClient中, 執行的execute方法,

 

	public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
			throws IOException {
		ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
		Server server = getServer(loadBalancer, hint);
		if (server == null) {
			throw new IllegalStateException("No instances available for " + serviceId);
		}
		RibbonServer ribbonServer = new RibbonServer(serviceId, server,
				isSecure(server, serviceId),
				serverIntrospector(serviceId).getMetadata(server));

		return execute(serviceId, ribbonServer, request);
	}

 獲得一個IloadBalancer ,這是通過ribbon的負載均衡策略,再根據傳入的serviceid,從spring的context裡拿到的,具體的拿法現在先不細說,當然裡邊的請求的url,已經被變換為真是的請求路徑。

後續因為工作上需要使用Nacos,也就是springcloud-alibaba來作為微服務基礎,所有後續的內容,會重點在說一下Nacos的使用。

相關文章