瘋狂Spring Cloud連載(9)——RestTemplate的負載均衡原理

本文節選自《瘋狂Spring Cloud微服務架構實戰》html

京東購買地址:https://item.jd.com/12256011.htmljava

噹噹網購買地址:http://product.dangdang.com/25201393.htmlgit

Spring Cloud教學視頻:https://my.oschina.net/JavaLaw/blog/1552993web

Spring Cloud電子書:https://my.oschina.net/JavaLaw/blog/1570383spring

9 RestTemplate負載均衡原理

        本文要點json

             RestTemplate的負載均衡原理瀏覽器

9.1 @LoadBalanced註解概述

        RestTemplate本是spring-web項目中的一個REST客戶端,它遵循REST的設計原則,提供簡單的API讓咱們能夠調用HTTP服務。RestTemplate自己不具備負載均衡的功能,該類也與Spring Cloud沒有關係,但爲什麼加入@LoadBalanced註解後,一個RestTemplate實例就具備負載均衡的功能呢?實際上這要得益於RestTemplate的攔截器功能。架構

        在Spring Cloud中,使用@LoadBalanced修飾的RestTemplate,在Spring容器啓動時,會爲這些被修飾過的RestTemplate添加攔截器,攔截器中使用了LoadBalancerClient來處理請求,LoadBalancerClient原本就是Spring封裝的負載均衡客戶端,經過這樣間接處理,使得RestTemplate就擁有了負載均衡的功能。app

        本小節將模仿攔截器機制,帶領你們實現一個簡單的RestTemplate,以便讓你們更瞭解@LoadBalanced以及RestTemplate的原理。本小節的案例只依賴了spring-boot-starter-web模塊:負載均衡

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>1.5.4.RELEASE</version>
</dependency>

9.2 編寫自定義註解以及攔截器

        先模仿@LoadBalanced註解,編寫一個自定義註解,請見代碼清單4-16。

        代碼清單4-1:

        codes\04\4.5\rest-template-test\src\main\java\org\crazyit\cloud\MyLoadBalanced.java

package org.crazyit.cloud;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.beans.factory.annotation.Qualifier;

@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MyLoadBalanced {

}

        注意MyLoadBalanced註解中使用了@Qualifier限定註解,接下來編寫自定義的攔截器,請見代碼清單4-17。

        代碼清單4-17:

        codes\04\4.5\rest-template-test\src\main\java\org\crazyit\cloud\MyInterceptor.java

package org.crazyit.cloud;

import java.io.IOException;

import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;

/**
 * 自定義攔截器
 * 
 * @author 楊恩雄
 *
 */
public class MyInterceptor implements ClientHttpRequestInterceptor {

	public ClientHttpResponse intercept(HttpRequest request, byte[] body,
			ClientHttpRequestExecution execution) throws IOException {
		System.out.println("=============  這是自定義攔截器實現");
		System.out.println("               原來的URI:" + request.getURI());
		// 換成新的請求對象(更換URI)
		MyHttpRequest newRequest = new MyHttpRequest(request);
		System.out.println("               攔截後新的URI:" + newRequest.getURI());
		return execution.execute(newRequest, body);
	}
}

        在自定義攔截器MyInterceptor中,實現了intercept方法,該方法會將原來的HttpRequest對象,轉換爲咱們自定義的MyHttpRequest,MyHttpRequest是一個自定義的請求類,實現以下請見代碼清單4-18。

        代碼清單4-18:

        codes\04\4.5\rest-template-test\src\main\java\org\crazyit\cloud\MyHttpRequest.java

package org.crazyit.cloud;

import java.net.URI;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;

/**
 * 自定義的請求類,用於轉換URI
 * @author 楊恩雄
 *
 */
public class MyHttpRequest implements HttpRequest {
	
	private HttpRequest sourceRequest;
	
	public MyHttpRequest(HttpRequest sourceRequest) {
		this.sourceRequest = sourceRequest;
	}

	public HttpHeaders getHeaders() {
		return sourceRequest.getHeaders();
	}

	public HttpMethod getMethod() {
		return sourceRequest.getMethod();
	}

	/**
	 * 將URI轉換
	 */
	public URI getURI() {
		try {
			URI newUri = new URI("http://localhost:8080/hello");
			return newUri;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return sourceRequest.getURI();
	}
}

        MyHttpRequest類中,會將原來請求的URI進行改寫,只要使用了這個對象,全部的請求都會被轉發到http://localhost:8080/hello這個地址。Spring Cloud在對RestTemplate進行攔截的時候,也作了一樣的事情,只不過並無像咱們這樣固定了URI,而是對「源請求」進行了更加靈活的處理。接下來使用自定義註解以及攔截器。

9.3 使用自定義攔截器以及註解

        編寫一個Spring的配置類,在初始化的bean中爲容器中的RestTemplate實例設置自定義攔截器,本例的Spring自動配置類請見代碼清單4-19。

        代碼清單4-19:

        codes\04\4.5\rest-template-test\src\main\java\org\crazyit\cloud\MyAutoConfiguration.java

package org.crazyit.cloud;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate;


@Configuration
public class MyAutoConfiguration {

	@Autowired(required=false)
	@MyLoadBalanced
	private List<RestTemplate> myTemplates = Collections.emptyList();
	
	@Bean
	public SmartInitializingSingleton myLoadBalancedRestTemplateInitializer() {
		System.out.println("====  這個Bean將在容器初始化時建立    =====");
		return new SmartInitializingSingleton() {
			
			public void afterSingletonsInstantiated() {
				for(RestTemplate tpl : myTemplates) {
					// 建立一個自定義的攔截器實例
					MyInterceptor mi = new MyInterceptor();
					// 獲取RestTemplate原來的攔截器
					List list = new ArrayList(tpl.getInterceptors());
					// 添加到攔截器集合
					list.add(mi);
					// 將新的攔截器集合設置到RestTemplate實例
					tpl.setInterceptors(list);
				}
			}
		};
	}
}

        配置類中,定義了RestTemplate實例的集合,而且使用了@MyLoadBalanced以及@Autowired註解進行修飾,@MyLoadBalanced中含有@Qualifier註解,簡單來講,就是Spring容器中,使用了@MyLoadBalanced修飾的RestTemplate實例,將會被加入到配置類的RestTemplate集合中。

        在容器初始化時,會調用myLoadBalancedRestTemplateInitializer方法來建立Bean,該Bean在初始化完成後,會遍歷RestTemplate集合併爲它們設置「自定義攔截器」,請見代碼清單4-19中的粗體代碼。下面在控制器中使用@MyLoadBalanced來修飾調用者的RestTemplate。

9.4 控制器中使用RestTemplate

        控制器代碼請見代碼清單4-20。

        代碼清單4-20:

        codes\04\4.5\rest-template-test\src\main\java\org\crazyit\cloud\InvokerController.java

package org.crazyit.cloud;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@Configuration
public class InvokerController {
	
	@Bean
	@MyLoadBalanced
	public RestTemplate getMyRestTemplate() {
		return new RestTemplate();
	}
	
	/**
	 * 瀏覽器訪問的請求
	 */
	@RequestMapping(value = "/router", method = RequestMethod.GET, 
			produces = MediaType.APPLICATION_JSON_VALUE)
	public String router() {
		RestTemplate restTpl = getMyRestTemplate();
		// 根據名稱來調用服務,這個URI會被攔截器所置換
		String json = restTpl.getForObject("http://my-server/hello", String.class);
		return json;
	}
	
	/**
	 * 最終的請求都會轉到這個服務
	 */
	@RequestMapping(value = "/hello", method = RequestMethod.GET)
	public String hello() {
		return "Hello World";
	}
}

        注意控制器的hello方法,前面實現的攔截器,會將所有請求都轉到這個服務中。控制器的RestTemplate,使用了@MyLoadBalanced註解進行修飾,熟悉前面使用RestTemplate的讀者可發現,咱們實現的註解,與Spring提供的@LoadBalanced註解使用方法一致。控制器的router方法中,使用這個被攔截過的RestTemplate發送請求。

        打開瀏覽器,訪問http://localhost:8080/router,能夠看到實際上調用了hello服務,在訪問該地址時,控制檯輸出以下:

=============  這是自定義攔截器實現
               原來的URI:http://my-server/hello
               攔截後新的URI:http://localhost:8080/hello

        Spring Cloud對RestTemplate的攔截實現更加複雜,而且在攔截器中,使用LoadBalancerClient來實現請求的負載均衡功能,咱們在實際環境中,並不須要實現自定義註解以及攔截器,用Spring提供的現成API便可,本小節目的是展現RestTemplate的原理。

 

本文節選自《瘋狂Spring Cloud微服務架構實戰》

Spring Cloud教學視頻:https://my.oschina.net/JavaLaw/blog/1552993

Spring Cloud電子書:https://my.oschina.net/JavaLaw/blog/1570383

本書代碼共享地址:https://gitee.com/yangenxiong/SpringCloud

相關文章
相關標籤/搜索