今日教學:RestTemplate 結合 Ribbon 使用
Ribbon 是可以單獨使用的,但是在 Spring Cloud 中使用 Ribbon 會更簡單。因為 Spring Cloud 在 Ribbon 基礎上進行了一層封裝,將很多配置都整合好了。本文將在 Spring Cloud 中使用 Ribbon !
一、使用 RestTemplate 與整合 Ribbon
Spring提供了一種簡單便捷的模板類來進行API的呼叫,那就是
RestTemplate
。
1. 使用 RestTemplate
首先我們看看GET請求的使用方式:在
fsh-house
服務的
HouseController
中增加兩個介面,一個透過
@RequestParam
來傳遞引數,返回一個物件資訊;另一個透過
@PathVarable
來傳遞引數,返回一個字串。儘量透過兩個介面組裝不同的形式,具體如下面程式碼所示。
@GetMapping("/data")public HouseInfo getData( @RequestParam("name") String name) {
return new HouseInfo(1L, "上海","虹口","東體小區");
}
@GetMapping("/data/{name}")
public String getData2(@PathVariable( "name") String name) {
return name;
}
在
fsh-substitution
服務中用
RestTemplate
來呼叫我們剛剛定義的兩個介面,具體如下面程式碼所示。
@GetMapping("/data")
public HouseInfo getData(@RequestParam("name") String name) {
return restTemplate.getForobject(
");
}
@GetMapping("/data/{name}")
public String getData2(@PathVariable("name") String name) {
return restTemplate.getForobject(
"{name}",String.class,name);
}
獲取資料結果透過
RestTemplate
的
getForObject
方法(具體如下面程式碼所示)來實現,此方法有三個過載的實現:
-
url
:請求的 API 地址,有兩種方式,其中一種是字串,另一種是 URL 形式。 -
responseType
:返回值的型別。 -
uriVariables
:PathVariable
引數,有兩種方式, 其中一種是可變引數,另一種是 Map 形式。
public <T> T getForobject(String url, Class<T> responseType,
object... uriVariables);
public <T> T getForobject (String url, Class<T> responseType ,
Map<String, ?> uriVariables) ;
public <T> T getForobject(URI url, Class<T> responseType) ;
除了
getForObject
,我們還可以使用
getForEntity
來獲取資料,程式碼如下面程式碼所示。
@GetMapping(" /data")public HouseInfo getData(@RequestParam("name") String name) {
ResponseEntity<HouseInfo> responseEntity = restTemplate.getForEntity(
"http:/ /localhost: 8081 /house/ data?name= "+name, HouseInfo.class) ;
if (responseEntity.getStatusCodeValue() == 200) {
return responseEntity.getBody();
}
return nu1l ;
}
getForEntity
中可以獲取返回的狀態碼、請求頭等資訊,透過
getBody
獲取響應的內容。其餘的和
getForObject
一樣,也是有3個過載的實現。
接下來看看怎麼使用POST方式呼叫介面。在
HouseController
中增加一個save方法用來接收
HouseInfo
資料,如下面程式碼所示。
@PostMapping("/save")public Long addData(@RequestBody HouseInfo houseInfo) {
System.out.println(houseInfo. getName());
return 1001L;
}
接著寫呼叫程式碼,用
postForObject
來呼叫,如下面程式碼所示。
@GetMapping("/save")public Long add(){
HouseInfo houseInfo = new HouseInfo();
houseInfo.setCity("上海");
houseInfo.setRegion("虹口");
houseInfo.setName( "XXX");
Long id = restTemplate.postFor0bject(
"http: //1ocalhost:8081/ house/save",houseInfo,Long.class);
return id;
}
postForObject
同樣有3個過載的實現。除了
postForObject
還可以使用
postForEntity
方法,用法都一樣,如下面程式碼所示。
public <T> T postForobject(String url, object request,
Class<T> responseType, object... uriVariables);
public <T> T postForobject(String url, object request,
Class<T> responseType, Map<String, ?> urivariables);
public <T> T postForobject(URI url, object request, Class<T> responseType);
除了get和post對應的方法之外,
RestTemplate
還提供了
put
、
delete
等操作方法,還有一個比較實用的就是
exchange
方法。
exchange
可以執行
get
、
post
、
put
、
delete
這4種請求方式。
2. 整合 Ribbon
在 Spring Cloud 專案中整合 Ribbon 只需要在
pom.xml
中加入下面的依賴即可,其實也可以不用配置,因為 Eureka 中已經引用了
Ribbon
, 如下面程式碼所示。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
這個配置我們加在
fangjia-fsh-substitution-service
中。
二、RestTemplate 負載均衡示例
對之前的程式碼進行一些小改造,輸出一些內容,證明我們整合的 Ribbon 是有效的。
改造 hello 介面,在介面中輸出當前服務的埠,用來區分呼叫的服務,如下面程式碼所示。
@RestController@RequestMapping("/house" )public class HouseController {
@Value("${server .port}")
private String serverPort;
@GetMapping("/hel1o")
public String hel1o(){
return "Hello"+serverPort;
}
}
上述程式碼分別以8081和8083埠啟動兩個服務,後面要用到。
接著改造 callHello 介面的程式碼,將呼叫結果輸出到控制檯,如下面程式碼所示。
@RestController@RequestMapping("/substitution")public class Substitut ionController{
@Autowired
private RestTemplate restTemplate;
@GetMapping ("/cal1Hel1o")
public String cal1Hello(){
String result = restTemplate. getFor0bject(
");
System.out.print1n("呼叫結果: " + result);
return result ;
}
}
測試步驟如下:
三、@LoadBalanced 註解原理
相信大家一定有一個疑問:為什麼在
RestTemplate
上加了一個
@LoadBalanced
之後,
RestTemplate
就能夠跟 Eureka 結合了,可以使用服務名稱去呼叫介面,還可以負載均衡?
這功勞應歸於Spring Cloud給我們做了大量的底層工作,因為它將這些都封裝好了,我們用起來才會那麼簡單。框架就是為了簡化程式碼,提高效率而產生的。
主要的邏輯就是給
RestTemplate
增加攔截器,在請求之前對請求的地址進行替換,或者根據具體的負載策略選擇服務地址,然後再去呼叫,這就是
@LoadBalanced
的原理。
下面我們來實現一個簡單的攔截器,看看在呼叫介面之前會不會進入這個攔截器。我們不做任何操作,就輸出一句話,證明能進來就行了。如下面程式碼所示。
public class MyLoadBalancerInterceptor implements ClientHttpRequestInterceptor{
@Override
public ClientHttpResponse intercept (final HttpRequest request ,
final byte[] body, final ClientHttpRequestExecution
execution) throws IOException{
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
System.out.println("進入自定義的請求攔截器中"+serviceName);
return execution.execute(request, body);
}
}
攔截器好了之後,我們再定義一個註解,複製
@LoadBalanced
的程式碼,改個名稱就可以了,如下面程式碼所示。
@Target({ ElementType.FIELD,ElementType.PARAMETER,ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Qualifier public @interface MyLoadBalanced { }
然後定義一個配置類,來給 RestTemplate 注人攔截器,如下面程式碼所示。
@Configurationpublic class MyLoadBalancerAutoConfiguration{
@MyLoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Bean
public MyLoadBalancerInterceptor myLoadBalancerInterceptor() {
return new MyLoadBalancerInterceptor();
}
@Bean
public SmartInitializingSingleton myLoadBalancedRestTemplateInitializer() {
return new SmartInitializingSingleton() {
@Override
public void afterSingletonsInstantiated() {
for (RestTemplate restTemplate :
MyLoadBalancerAutoConfiguration.this.restTemplates){
List<ClientHttpRequestInterceptor> list = new
ArrayList<>(restTemplate.getInterceptors());
list.add(myLoadBalancerInterceptor());
restTemplate.setInterceptors(list);
}
}
};
}
}
維護一個
@MyLoadBalanced
的 RestTemplate 列表,在
SmartlnitializingSingleton
中對 RestTemplate 進行攔截器設定。
然後改造我們之前的 RestTemplate 配置,將
@LoadBalanced
改成我們自定義的
@MyLoadBalanced
,如下面程式碼所示。
@Bean//@LoadBalanced@MyLoadBalancedpublic RestTemplate getRestTemplate(){
return new RestTemplate() ;
}
重啟服務,訪問 就可以看到控制檯的輸出了,這證明在介面呼叫的時候會進人該攔截器,輸出如下:
透過這個小案例我們就能夠清楚地知道
@LoadBalanced
的工作原理。接下來我們來看看原始碼中是怎樣的一個邏輯。
首先看配置類,如何為
RestTemplate
設定攔截器,程式碼在
spring- cloud commonsjar
中的
orgspringframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration
類裡面透過檢視
LoadBalancerAutoConfiguration
的原始碼,可以看到這裡也是維護了一個
@LoadBalanced
的 RestTemplate 列表,如下面程式碼所示。
@LoadBalanced@Autowired(required = false)private List<RestTemplate> restTemplates = Collections.emptyList();@Beanpublic SmartInitializingSingleton loadBalancedRestTemplateInitializer(
final List<RestTemplateCustomizer> customizers) {
return new SmartInitializingSingleton() {
@Override
public void afterSingletonsInstantiated() {
for(RestTemplate restTemplate :
LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer:customizers) {
customizer.customize(restTemplate);
}
}
}
};
}
下面看看攔截器的配置。可以知道,攔截器用的是
LoadBalancerInterceptor
,
RestTemplate Customizer
用來新增攔截器,如下面程式碼所示。
@Configuration @conditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")static class LoadBalancerInterceptorConfig {
@Bean
public LoadBalancerInterceptor ribbonInterceptor (
LoadBalancerClient loadBalancerClient ,
LoadBalancerRequestFactory requestFactory)
return new LoadBalancer Interceptor(loadBalancerClient,requestFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return new RestTemplateCustomizer() {
@Override
public void customize(RestTemplate restTemplate) {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancer Interceptor);
restTemplate. setInterceptors(list);
}
};
}
}
攔截器的程式碼在
org.springframework.cloud.client.loadbalancerLoadBalancerInterceptor
中,如下面程式碼所示。
public class LoadBalancerInterceptor imp1ements
ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer ,
LoadBalancerRequestFactory requestFactory){
this. loadBalancer = loadBalancer;
this. requestFactory = requestFactory;
}
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
}
@Override
public ClientHttpResponse intercept(final HttpRequest request,
final byte[] body, final ClientHttpRequestExecution
execution ) throws IOException {
final URI originaluri = request. getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != nu11, "Request URI does not contain a valid hostname:" + originalUri) ;
return this.loadBalancer.execute (serviceName ,
requestFactory . createRequest(request, body, execution));
}
}
主要的邏輯在
intercept
中,執行交給了
LoadBalancerClient
來處理,透過
LoadBalancer RequestFactory
來構建一個
LoadBalancerRequest
物件,如下面程式碼所示。
public LoadBalancerRequest<ClientHttpResponse> createRequest(final
HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) {
return new LoadBalancerRequest<ClientHttpResponse>() {
@Override
public ClientHttpResponse apply(final ServiceInstance instance)
throws Exception {
HttpRequest serviceRequest = new
ServiceRequestwrapper(request, instance, loadBalancer);
if (transformers != nu11) {
for(LoadBalancerRequestTransformer transformer :
transformers) {
serviceRequest =
transformer . transformRequest(serviceRequest,
instance);
}
}
return execution. execute ( serviceRequest, body) ;
}
};
}
createRequest 中透過
ServiceRequestWrapper
來執行替換URI的邏輯,
ServiceRequest Wrapper
中將 URI 的獲取交給了
org.springframework.cloud.client.loadbalancer.LoadBalancer Client#reconstructURI
方法。
以上就是整個
RestTemplate
結合
@LoadBalanced
的執行流程,至於具體的實現大家可以自己去研究,這裡只是介紹原理及整個流程。
四、Ribbon API 使用
當你有一些特殊的需求,想透過 Ribbon 獲取對應的服務資訊時,可以使用
LoadBalancer Client
來獲取,比如你想獲取一個
fsh-house
服務的服務地址,可以透過
LoadBalancerClient
的 choose 方法來選擇一個:
@Autowiredprivate LoadBalancerClient loadBalancer;@GetMapping("/choose")public object chooseUrl() {
ServiceInstance instance = loadBalancer.choose("fsh-house");
return instance;
}
訪問介面,可以看到返回的資訊如下:
{
serviceId: "fsh-house",
server: {
host: "localhost",
port: 8081,
id: "localhost:8081",
zone: "UNKNOWN",
readyToServe: true,
alive: true,
hostPort: "localhost:8081",
metaInfo: {
serverGroup: null,
serviceIdForDiscovery: null,
instanceId: "localhost:8081",
appName: null
}
},
secure: false,
metadata:
{ },
host: "localhost",
port: 8081,
uri: "
}
五、Ribbon 飢餓載入
筆者從網上看到很多部落格中都提到過的一個情況: 在進行服務呼叫的時候,如果網路情況不好,第一次呼叫會超時。有很多大神對此提出瞭解決方案,比如把超時時間改長一點、禁用超時等。 Spring Cloud 目前正在高速發展中,版本更新很快,我們能發現的問題基本上等新版本出來的時候就都修復了,或者提供了最優的解決方案。
這個超時的問題也是-樣,Ribbon 的客戶端是在第一次請求的時候初始化的,如果超時時間比較短的話,初始化 Client 的時間再加上請求介面的時間,就會導致第一次請求超時。
透過配置
eager-load
來提前初始化客戶端就可以解決這個問題。
ribbon.eager-load.enabled = true ribbon
eager-load.clients = fsh-house
-
ribbon.eager-load.enabled
:開啟 Ribbon 的飢餓載入模式。 -
ribbon.eager-load.clients
:指定需要飢餓載入的服務名,也就是你需要呼叫的服務,若有多個則用逗號隔開。
怎麼進行驗證呢?網路情況確實不太好模擬,我們就透過日誌輸出來判斷吧。先不配置 eager-load,在第一次呼叫的時候會有日誌輸出,內容如下:
在
fsh-house
的 client 初始化資訊輸出的基礎上再加上
eager-load
的日誌重啟服務,就可以發現啟動的時候會輸出上面的日誌,這就證明我們的 client 已經提前初始化好了。
喜歡這篇文章的朋友們可以關注個人簡介中的公眾號
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69964492/viewspace-2767015/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- (18)企業採購招標系統之Ribbon結合RestTemplate實現負載均衡REST負載
- java Spring Cloud企業快速開發架構之Ribbon結合RestTemplate實現負載均衡JavaSpringCloud架構REST負載
- Spring之RestTemplate使用小結SpringREST
- RestTemplate使用REST
- RestTemplate的使用REST
- 微服務之Eureka(二)服務中心互相註冊-Ribbon的結合使用微服務
- SpringCloud Fegin結合Ribbon實現負載均衡SpringGCCloud負載
- 影片教學連結: 收藏
- 180813-Spring之RestTemplate使用小結一SpringREST
- Axure教學案例總結(1)
- 【API知識】RestTemplate的使用APIREST
- RestTemplate和 apache HttpClient 使用方式RESTApacheHTTPclient
- SQLite3 使用教學SQLite
- [轉帖]SQLite使用教學SQLite
- 請教ofbiz與struts,eclipse結合運用Eclipse
- 一起學 Spring 之 RestTemplateSpringREST
- RestTemplate全網最強總結(永久更新)REST
- SpringCloud微服務中使用RestTemplate+Ribbon實現負載均衡(實現方法+實現原理+替換負載均衡策略)SpringGCCloud微服務REST負載
- go-kit結合gRpc的使用和學習GoRPC
- react-hook-form結合antd4使用學習ReactHookORM
- Spring之RestTemplate中級使用篇SpringREST
- 如何使用RestTemplate訪問restful服務REST
- 寓教於樂 11款最適合教兒童程式設計的學習工具程式設計
- 今日總結~
- 今日總結
- AngularJS 入門教學視訊【完結】AngularJS
- C語言指標部分教學總結C語言指標
- 08C++選擇結構(2)——教學C++
- 結合 Laravel 初步學習 GraphQLLaravel
- RestTemplate實踐REST
- 3.29今日總結
- 今日總結1.2
- 今日總結1.3
- 今日總結1.1
- 使用RestTemplate,顯示請求資訊,響應資訊REST
- `GitHub page` 和 `gitbook` 結合使用Github
- ts結合vue使用的感悟Vue
- Docker結合.Net Core初步使用Docker