SpringCloud服務間調用

本篇簡介

在上一篇咱們介紹了SpringCloud中的註冊中心組件Eureka。Eureka的做用是作服務註冊與發現的,目的是讓不一樣的服務與服務之間均可以經過註冊中心進行間接關聯,而且能夠經過註冊中心有效的管理不一樣服務與服務的運行狀態。但在微服務的架構中,服務與服務只知道對方的服務地址是沒有用的,它們的本質仍是須要彼此進行通訊的,這也是微服務最核心的功能之一。java


既然提到了服務與服務之間的通訊,那咱們天然而然會想到大名鼎鼎的HttpClient。由於在其它的項目架構中咱們基本均可以經過它來進行不一樣服務與服務之間的調用。在SpringCloud中咱們依然可使用HttpClient進行服務與服務調用,只不過若是採用HttpClient調用的話,會有一些弊端。例如: 若是同一個服務有多個負載的話,採用HttpClient調用時,沒有辦法處理負載均衡的問題。還有另外一個問題就是HttpClient只是提供了核心調用的方法並無對調用進行封裝,因此在使用上不太方便,須要本身對HttpClient進行簡單的封裝。git


調用方式

在SpringCloud中爲了解決服務與服務調用的問題,因而提供了兩種方式來進行調用。也就是RestTemplate和Feign。雖然從名字上看這兩種調用的方式不一樣,但在底層仍是和HttpClient同樣,採用http的方式進行調用的。只不過是對HttpClient進行的封裝。下面咱們來詳細的介紹一下這兩種方式的區別,咱們首先看一下RestTemplate的方式。github


RestTemplate方式調用

  • RestTemplate

爲了方便掩飾咱們服務間的調用,因此咱們須要建立三個項目。它們分別爲eureka(註冊中心)、server(服務提供方)、client(服務調用方)。由於上一篇中咱們已經介紹了eureka的相關內容。因此在這一篇中咱們將不在作過多的介紹了。下面咱們看一下server端的配置。由於實際上Server端和Client端是相互的。不必定client端必定要調用server端。server端同樣能夠調用client端。但對於eureka來講,它們都是client端。由於上一篇中咱們已經介紹了eureka是分爲server端和client端的,而且已經介紹client端相關內容。因此咱們下面咱們直接看一下server端的配置內容:web

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka/
spring:
  application:
    name: jilinwula-springcloud-feign-server
server:
  port: 8082

爲了掩飾咱們服務間的調用,因此咱們須要建立一個Controller,並編寫一個簡單的接口來供client調用。下面爲server的源碼。spring

package com.jilinwula.feign.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/server")
public class Controller {

    @GetMapping("/get")
    public Object get() {
        Map<String, String> map = new HashMap<String, String>();
        map.put("code", "0");
        map.put("msg", "success");
        map.put("data", "吉林烏拉");
        return map;
    }
}

下面咱們訪問一下這個接口看看,是否能正確返回數據。(備註:注意別忘記了在啓動類上添加@EnableEurekaClient註解。)下面咱們仍是使用.http文件的方式發起接口請求。json

GET http://127.0.0.1:8082/server/get

返回結果:架構

GET http://127.0.0.1:8082/server/get

HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 15 Mar 2019 08:20:33 GMT

{
  "msg": "success",
  "code": "0",
  "data": "吉林烏拉"
}

Response code: 200; Time: 65ms; Content length: 42 bytes

咱們看已經成功的返回了接口的數據了。下面咱們看一下eureka。看看是否成功的檢測到了server端的服務。下面爲eureka管理界面地址:app

http://127.0.0.1:8761

  title

咱們看eureka已經成功的檢測到了server端註冊成功了。下面咱們看一下client端的代碼,咱們仍是向server端同樣,建立一個Controller,並編寫一個接口。下面爲具體配置及代碼。負載均衡

application.yml:dom

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka/
spring:
  application:
    name: jilinwula-springcloud-feign-client
server:
  port: 8081

  Controller:

package com.jilinwula.feign.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/client")
public class Controller {

    @GetMapping("/get")
    public Object get() {
        Map<String, String> map = new HashMap<String, String>();
        map.put("code", "0");
        map.put("msg", "success");
        map.put("data", "吉林烏拉");
        return map;
    }
}

下面爲訪問的接口地址:

GET http://127.0.0.1:8081/client/get

返回結果:

GET http://127.0.0.1:8081/client/get

HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 15 Mar 2019 08:56:42 GMT

{
  "msg": "success",
  "code": "0",
  "data": "吉林烏拉"
}

Response code: 200; Time: 273ms; Content length: 42 bytes

如今咱們在訪問一下Eureka地址看一下Client服務註冊的是否成功。

http://127.0.0.1:8761

  title

RestTemplate實例化

咱們發現server和client端都已經成功的在註冊中心註冊成功了。這也就是咱們接下來要介紹的服務間調用的前提條件。在開發Spring項目時咱們知道若是咱們想要使有哪一個類或者哪一個對象,那就須要在xml中或者用註解的方式實例化對象。因此既然咱們打算使用RestTemplate類進行調用,那咱們必需要先實例化RestTemplate類。下面咱們就看一下怎麼在實例化RestTemplate類。由於不論採用的是RestTemplate方式調用仍是採用Feign方式,均是在服務的client端進行開發的,在服務的server是無需作任何更改的。因此下面咱們看一下client端的改動。下面爲項目源碼:

package com.jilinwula.feign;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableEurekaClient
public class JilinwulaSpringcloudFeignClientApplication {

    public static void main(String[] args) {
        SpringApplication.run(JilinwulaSpringcloudFeignClientApplication.class, args);
    }

    @Bean
    public RestTemplate initRestTemplate() {
        return new RestTemplate();
    }

}

RestTemplate調用方式一

爲了掩飾方便咱們直接在啓動類上添加了一個@Bean註解。而後手動實例化了一個對象,而且要特別注意,在使用RestTemplate時,必需要先實例化,不然會拋出空指針異常。下面咱們演示一下怎麼使用RestTemplate來調用server端的接口。下面爲Controller中的代碼的改動。

package com.jilinwula.feign.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@RequestMapping("/client")
public class Controller {

    @Autowired
    private RestTemplate template;

    @GetMapping("/get")
    public Object get() {
        String result = template.getForObject("http://127.0.0.1:8082/server/get", String.class);
        return result;
    }
}

上面的代碼比較簡單,咱們就不詳細的介紹了,主要是RestTemplate中提供了getForObject方法(實際上RestTemplate提供了不少種調用的方法,主要分爲Get或者Post),能夠指定要調用接口的地址,指定返回的值的類型。而後就會直接返回要調用接口的結果。下面咱們測試一下,仍是調用client接口,看看可否正確的返回server端的數據。

http://127.0.0.1:8081/client/get

返回結果:

GET http://127.0.0.1:8081/client/get

HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 50
Date: Fri, 15 Mar 2019 09:42:02 GMT

{"msg":"success","code":"0","data":"吉林烏拉"}

Response code: 200; Time: 362ms; Content length: 42 bytes

RestTemplate調用方式二

咱們看結果,已經成功的返回的server端的數據了,雖然返回的數據沒有格式化,但返回的結果數據確實是server端的數據。這也就是RestTemplate的簡單使用。但上述的代碼是有弊端的,由於咱們直接將調用的server端的接口地址直接寫死了,這樣當服務接口變動時,是須要更改客戶端代碼的,這顯示是不合理的。那怎麼辦呢?這時就知道註冊中心的好處了。由於註冊中心知道全部服務的地址,這樣咱們經過註冊中心就能夠知道server端的接口地址,這樣就避免了server端服務更改時,要同步更改client代碼了。下面咱們在優化一下代碼,看看怎麼經過註冊中心來獲取server端的地址。

package com.jilinwula.feign.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@RequestMapping("/client")
public class Controller {

    @Autowired
    private RestTemplate template;

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @GetMapping("/get")
    public Object get() {
        ServiceInstance serviceInstance = loadBalancerClient.choose("jilinwula-springcloud-feign-server");
        String url = String.format("http://%s:%s/server/get", serviceInstance.getHost(), serviceInstance.getPort());
        String result = template.getForObject(url, String.class);
        return result;
    }
}

在SpringClourd中提供了LoadBalancerClient接口。經過這個接口咱們能夠經過用戶中心的Application的名字來獲取該服務的地址和端口。也就是下圖中紅色標紅的名字(注意名字大小寫)。

  title

經過這些咱們就能夠獲取到完整的服務接口地址了,這樣就能夠直接經過RestTemplate進行接口調用了。下面咱們在看一下調用的結果。接口地址:

GET http://127.0.0.1:8081/client/get

返回結果:

GET http://127.0.0.1:8081/client/get

HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 50
Date: Sat, 16 Mar 2019 09:08:32 GMT

{"msg":"success","code":"0","data":"吉林烏拉"}

Response code: 200; Time: 53ms; Content length: 42 bytes

RestTemplate調用方式三

這樣咱們就解決了第一次服務接口地址寫死的問題了。但上述的接口還有一個弊端就是咱們每次調用服務時都要先經過Application的名字來獲取ServiceInstance對象,而後才能夠發起接口調用。實際上在SpringCloud中爲咱們提供了@LoadBalanced註解,只要將該註解添加到RestTemplate中的獲取的地方就能夠了。下面爲具體修改:

啓動類:

package com.jilinwula.feign;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableEurekaClient
public class JilinwulaSpringcloudFeignClientApplication {

    public static void main(String[] args) {
        SpringApplication.run(JilinwulaSpringcloudFeignClientApplication.class, args);
    }

    @Bean
    @LoadBalanced
    public RestTemplate initRestTemplate() {
        return new RestTemplate();
    }

}

咱們在RestTemplate實例化的地方添加了@LoadBalanced註解,這樣在咱們使用RestTemplate時就該註解就會自動將調用接口的地址替換成真正的服務地址。下面咱們看一下Controller中的改動:

package com.jilinwula.feign.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@RequestMapping("/client")
public class Controller {

    @Autowired
    private RestTemplate template;

    @GetMapping("/get")
    public Object get() {
        String url = String.format("http://%s/server/get", "jilinwula-springcloud-feign-server");
        String result = template.getForObject(url, String.class);
        return result;
    }
}

代碼和第一次的代碼基本同樣,惟一的區別就是獲取服務地址和端口的地方替換成了註冊中心中的Application的名字,而且咱們的RestTemplate在使用上和第一次沒有任何區別,只是在url中不一樣。下面咱們看一下返回的結果。

GET http://127.0.0.1:8081/client/get

HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 50
Date: Sat, 16 Mar 2019 09:55:46 GMT

{"msg":"success","code":"0","data":"吉林烏拉"}

Response code: 200; Time: 635ms; Content length: 42 bytes

默認負載均衡策略

上述內容就是使用RestTemplate來進行服務間調用的方式。而且採用這樣的方式能夠很方便的解決負載均衡的問題。由於@LoadBalanced註解會自動採用默信的負載策略。下面咱們看驗證一下SpringCloud默認的負載策略是什麼。爲了掩飾負載策略,因此咱們在新增一個server服務,而且爲了掩飾這兩個server返回結果的不一樣,咱們故意讓接口返回的數據不一致,來方便咱們測試。下面爲新增的server服務端的配置信息及controller源碼。

application.yml:

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka/
spring:
  application:
    name: jilinwula-springcloud-feign-server
server:
  port: 8083

  Controller:

package com.jilinwula.feign.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/server")
public class Controller {

    @GetMapping("/get")
    public Object get() {
        Map<String, String> map = new HashMap<String, String>();
        map.put("code", "0");
        map.put("msg", "success");
        map.put("data", "jilinwula");
        return map;
    }
}

調用如下接口:

GET http://127.0.0.1:8083/server/get

返回結果:

GET http://127.0.0.1:8083/server/get

HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sat, 16 Mar 2019 10:49:07 GMT

{
  "msg": "success",
  "code": "0",
  "data": "jilinwula"
}

Response code: 200; Time: 100ms; Content length: 47 bytes

如今咱們訪問一下注冊中心看一下如今註冊中心的變化。註冊中心地址:

http://127.0.0.1:8761

  title

咱們看上圖註冊中心已經顯示Application名字爲JILINWULA-SPRINGCLOUD-FEIGN-SERVER的有兩個服務已經註冊成功了。下面咱們直接調用client中的接口,看一下client默認會返回哪一個server端的信息。client接口地址:

GET http://127.0.0.1:8081/client/get

返回結果:

GET http://127.0.0.1:8081/client/get

HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 47
Date: Sat, 16 Mar 2019 10:58:39 GMT

{"msg":"success","code":"0","data":"jilinwula"}

Response code: 200; Time: 24ms; Content length: 47 bytes

看上面返回的結果是server2的接口數據。咱們在請求一下接口在看一下返回的結果:

GET http://127.0.0.1:8081/client/get

HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 50
Date: Sat, 16 Mar 2019 11:01:01 GMT

{"msg":"success","code":"0","data":"吉林烏拉"}

Response code: 200; Time: 15ms; Content length: 42 bytes

更改默認負載均衡策略一

咱們看這回返回的接口數據就是第一個server端的信息了。而且咱們能夠頻繁的調用client中的接口,並觀察發現它們會交替返回的。因此咱們基本能夠肯定SpringCloud默認的負載策略爲輪詢方式。也就是會依次調用。在SpringCloud中提供了不少種負載策略。比較常見的爲:隨機、輪詢、哈希、權重等。下面咱們介紹一下怎麼修改默認的負載策略。SpringCloud底層採用的是Ribbon來實現的負載均衡。Ribbon是一個負載均衡器,Ribbon的核心組件爲IRule,它也就是全部負載策略的父類。下面爲IRule接口的源碼:

package com.netflix.loadbalancer;

public interface IRule {
    Server choose(Object var1);

    void setLoadBalancer(ILoadBalancer var1);

    ILoadBalancer getLoadBalancer();
}

該類只提供了3個方法,它們的做用分別是選擇一個服務名字、設置ILoadBalancer和返回ILoadBalancer。下面咱們看一下IRule接口的常見策略子類。常見的有RandomRule、RoundRobinRule、WeightedResponseTimeRule等。分別對應着隨機、輪詢、和權重。下面咱們看一下怎麼更改默認的策略方式。更改默認策略也是在client端中操做的,因此咱們看一下client端的代碼更改:

package com.jilinwula.feign;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableEurekaClient
public class JilinwulaSpringcloudFeignClientApplication {

    public static void main(String[] args) {
        SpringApplication.run(JilinwulaSpringcloudFeignClientApplication.class, args);
    }

    @Bean
    @LoadBalanced
    public RestTemplate initRestTemplate() {
        return new RestTemplate();
    }

    @Bean
    public IRule initIRule() {
        return new RandomRule();
    }

}

咱們在啓動類上新實例化了一個IRule對象,而且指定該對象實例化的子類爲RandomRule,也就是隨機的方式。因此當咱們Client端啓動服務調用服務時,就會採用隨機的方式進行調用,由於咱們已經將IRule對象默認的實例化方式更改了。下面咱們測試一下,繼續訪問Client端接口:

GET http://127.0.0.1:8081/client/get

返回結果:

GET http://127.0.0.1:8081/client/get

HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 50
Date: Sat, 16 Mar 2019 11:36:01 GMT

{"msg":"success","code":"0","data":"吉林烏拉"}

Response code: 200; Time: 15ms; Content length: 42 bytes

更改默認負載均衡策略二

在這裏咱們就不依依演示了,但若是咱們屢次調用接口就會發現,Client接口返回的結果不在是輪詢的方式了,而是變成了隨機了,這就說明咱們已經成功的將SpringCloud默認的負載策略更改了。下面咱們換一種方式來更改默認的負載策略。這種方式和上面的有所不一樣,而是在配置文件中配置的,下面爲具體的配置。(備註:爲了避免影響測試效果,咱們須要將剛剛在啓動類中的實例化的IRule註釋掉)

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka/
spring:
  application:
    name: jilinwula-springcloud-feign-client
server:
  port: 8081
jilinwula-springcloud-feign-server:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

咱們在配置文件中指定了註冊中心中的server端的Application名字,而後指定了默認的負載策略類。下面咱們測試一下。訪問如下接口:

GET http://127.0.0.1:8081/client/get

返回結果:

GET http://127.0.0.1:8081/client/get

HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 50
Date: Sat, 16 Mar 2019 11:54:42 GMT

{"msg":"success","code":"0","data":"吉林烏拉"}

Response code: 200; Time: 13ms; Content length: 42 bytes

Feign方式調用

咱們在實際的開發中,可使用上述兩種方式來更改SpringCloud中默認的負載策略。下面咱們看一下SpringCloud中另外一種服務間調用方式也就是Feign方式。使用Feign方式和RestTemplate不一樣,咱們須要先添加Feign的依賴,具體依賴以下(備註:該依賴一樣是在client端添加的):

pom.xml:

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
  <version>2.1.1.RELEASE</version>
</dependency>

其次咱們還須要在啓動類中添加@EnableFeignClients註解。具體代碼以下:

package com.jilinwula.feign;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class JilinwulaSpringcloudFeignClientApplication {

    public static void main(String[] args) {
        SpringApplication.run(JilinwulaSpringcloudFeignClientApplication.class, args);
    }

}

接下來咱們須要在Client端建立一個新的接口並定義Client端須要調用的服務方法。具體代碼以下:

package com.jilinwula.feign.server;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

@FeignClient(name = "jilinwula-springcloud-feign-server")
public interface ServerApi {

    @GetMapping("/server/get")
    String get();
}

上述接口基本上和server端的Controller一致,惟一的不一樣就是咱們指定了@FeignClient註解,該註解的須要指定一個名字,也就是註冊中心中Applicaiton的名字,也就是要調用的服務名字。下面咱們看一下Controller中的代碼更改:

package com.jilinwula.feign.controller;

import com.jilinwula.feign.server.ServerApi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/client")
public class Controller {

    @Autowired
    private ServerApi serverApi;

    @GetMapping("/get")
    public Object get() {
        String result = serverApi.get();
        return result;
    }
}

咱們在Controller中直接使用了咱們自定義的接口,並直接調用咱們接口中定義的方法,下面咱們調用一下Client接口看看這樣的方式是否能夠調用成功。接口地址:

GET http://127.0.0.1:8081/client/get

返回結果:

GET http://127.0.0.1:8081/client/get

HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 50
Date: Sat, 16 Mar 2019 12:54:50 GMT

{"msg":"success","code":"0","data":"吉林烏拉"}

Response code: 200; Time: 14ms; Content length: 42 bytes

咱們看這樣的方式也是能夠成功的調用server端的接口的,只不過這樣的方式可能會讓覺的不太方便,由於這樣的方式是須要Client端定義和Server端同樣的接口的。


上述內容就是本篇的所有內容,在實際的項目開發中,這兩種方式都可實現服務與服務間的調用,而且這兩種方式都有彼此的弊端,因此並無特別推薦的方式。在下一篇中,咱們將介紹配置中心相關內容,謝謝。


項目源碼

https://github.com/jilinwula/jilinwula-springcloud-feign


原文連接

http://jilinwula.com/article/...

相關文章
相關標籤/搜索