Spring Cloud(三):服務提供與調用 Eureka【Finchley 版】

Spring Cloud(三):服務提供與調用 Eureka【Finchley 版】

上一篇文章咱們介紹了 Eureka 服務註冊中心的搭建,這篇文章介紹一下如何使用 Eureka 服務註冊中心,搭建一個簡單的服務端註冊服務,客戶端去調用服務使用的案例。html

案例中有三個角色:服務註冊中心、服務提供者、服務消費者,其中服務註冊中心就是咱們上一篇的 Eureka 單節點啓動既可。
流程以下:java

  1. 啓動註冊中心
  2. 服務提供者生產服務並註冊到服務中心中
  3. 消費者從服務中心中獲取服務並執行

服務提供者

咱們假設服務提供者有一個 hello() 方法,能夠根據傳入的參數,提供輸出 「hello xxx + 當前時間」 的服務。git

POM 包配置

建立一個基本的 Spring Boot 應用,命名爲eureka-producer,在 pom.xml 中添加以下配置:github

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

配置文件

application.yml 配置以下web

1
2
3
4
5
6
7
8
9
spring:
application:
name: eureka-producer
eureka:
client:
service-url:
defaultZone: http://localhost:7000/eureka/
server:
port: 8000

經過spring.application.name屬性,咱們能夠指定微服務的名稱後續在調用的時候只須要使用該名稱就能夠進行服務的訪問。eureka.client.serviceUrl.defaultZone屬性對應服務註冊中心的配置內容,指定服務註冊中心的位置。爲了在本機上測試區分服務提供方和服務註冊中心,使用server.port屬性設置不一樣的端口。spring

啓動類

保持默認生成的便可, Finchley.RC1 這個版本的 Spring Cloud 已經無需添加@EnableDiscoveryClient註解了。(那麼若是我引入了相關的 jar 包又想禁用服務註冊與發現怎麼辦?設置eureka.client.enabled=false架構

@EnableDiscoveryClient is no longer required. You can put a DiscoveryClient implementation on the classpath to cause the Spring Boot application to register with the service discovery server.
Spring Cloud - @EnableDiscoveryClientapp

1
2
3
4
5
6
7
8
@SpringBootApplication
public class EurekaProducerApplication {

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

}

Controller

提供 hello 服務負載均衡

1
2
3
4
5
6
7
8
9
10
@RestController
@RequestMapping("/hello")
public class HelloController {

@GetMapping("/")
public String hello(@RequestParam String name) {
return "Hello, " + name + " " + new Date();
}

}

啓動工程後,就能夠在註冊中心 Eureka 的頁面看到 EUERKA-PRODUCER 服務。
maven

咱們模擬一個請求試一下 Producer 可否正常工做
http://localhost:8000/hello/?name=windmt

1
Hello, windmt Fri Apr 13 18:36:36 CST 2018

 

OK, 直接訪問時沒有問題的,到此服務提供者配置就完成了。

服務消費者

建立服務消費者根據使用 API 的不一樣,大體分爲三種方式。雖然你們在實際使用中用的應該都是 Feign,可是這裏仍是把這三種都介紹一下吧,若是你只關心 Feign,能夠直接跳到最後。

三種方式均使用同一配置文件,再也不單獨說明了

1
2
3
4
5
6
7
8
9
spring:
application:
name: eureka-consumer
eureka:
client:
service-url:
defaultZone: http://localhost:7000/eureka/ # 指定 Eureka 註冊中心的地址
server:
port: 9000 # 分別爲 9000、900一、9002

使用 LoadBalancerClient

LoadBalancerClient接口的命名中,咱們就知道這是一個負載均衡客戶端的抽象定義,下面咱們就看看如何使用 Spring Cloud 提供的負載均衡器客戶端接口來實現服務的消費。

POM 包配置

咱們先來建立一個服務消費者工程,命名爲:eureka-consumer。pom.xml 同 Producer 的,再也不贅述。

啓動類

初始化RestTemplate,用來發起 REST 請求。

1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootApplication
public class EurekaConsumerApplication {

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

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

Controller

建立一個接口用來消費 eureka-producer 提供的接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RequestMapping("/hello")
@RestController
public class HelloController {

@Autowired
private LoadBalancerClient client;

@Autowired
private RestTemplate restTemplate;

@GetMapping("/")
public String hello(@RequestParam String name) {
name += "!";
ServiceInstance instance = client.choose("eureka-producer");
String url = "http://" + instance.getHost() + ":" + instance.getPort() + "/hello/?name=" + name;
return restTemplate.getForObject(url, String.class);
}

}

 

能夠看到這裏,咱們注入了LoadBalancerClientRestTemplate,並在hello方法中,先經過loadBalancerClientchoose方法來負載均衡的選出一個eureka-producer的服務實例,這個服務實例的基本信息存儲在ServiceInstance中,而後經過這些對象中的信息拼接出訪問服務調用者的/hello/接口的詳細地址,最後再利用RestTemplate對象實現對服務提供者接口的調用。

另外,爲了在調用時能從返回結果上與服務提供者有個區分,在這裏我簡單處理了一下,name+="!",即服務調用者的 response 中會比服務提供者的多一個感嘆號(!)。

訪問 http://localhost:9000/hello/?name=windmt 以驗證是否調用成功

1
Hello, windmt! Fri Apr 13 18:44:55 CST 2018

 

Spring Cloud Ribbon

以前已經介紹過 Ribbon 了,它是一個基於 HTTP 和 TCP 的客戶端負載均衡器。它能夠經過在客戶端中配置 ribbonServerList 來設置服務端列表去輪詢訪問以達到均衡負載的做用。

當 Ribbon 與 Eureka 聯合使用時,ribbonServerList 會被 DiscoveryEnabledNIWSServerList 重寫,擴展成從 Eureka 註冊中心中獲取服務實例列表。同時它也會用 NIWSDiscoveryPing 來取代 IPing,它將職責委託給 Eureka 來肯定服務端是否已經啓動。

POM 包配置

將以前的 eureka-consumer 工程複製一份,並命名爲 eureka-consumer-ribbon。

pom.xml 文件還用以前的就行。至於 spring-cloud-starter-ribbon,由於我使用的 Spring Cloud 版本是 Finchley.RC1,spring-cloud-starter-netflix-eureka-client 裏邊已經包含了 spring-cloud-starter-netflix-ribbon 了。

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

 

啓動類

修改應用主類,爲RestTemplate添加@LoadBalanced註解

1
2
3
4
5
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}

 

Controller

修改 controller,去掉LoadBalancerClient,並修改相應的方法,直接用 RestTemplate發起請求

1
2
3
4
5
6
@GetMapping("/")
public String hello(@RequestParam String name) {
name += "!";
String url = "http://eureka-producer/hello/?name=" + name;
return restTemplate.getForObject(url, String.class);
}

 

可能你已經注意到了,這裏直接用服務名eureka-producer取代了以前的具體的host:port。那麼這樣的請求爲何能夠調用成功呢?由於 Spring Cloud Ribbon 有一個攔截器,它可以在這裏進行實際調用的時候,自動的去選取服務實例,並將這裏的服務名替換成實際要請求的 IP 地址和端口,從而完成服務接口的調用。

訪問 http://localhost:9001/hello/?name=windmt 以驗證是否調用成功

1
Hello, windmt! Fri Apr 13 22:24:14 CST 2018

 

也能夠經過啓動多個 eureka-producer 服務來觀察其負載均衡的效果。

Spring Cloud Feign

在實際工做中,咱們基本上都是使用 Feign 來完成調用的。咱們經過一個例子來展示 Feign 如何方便的聲明對 eureka-producer 服務的定義和調用。

POM 包配置

建立一個基本的 Spring Boot 應用,命名爲eureka-producer-feign,在 pom.xml 中添加以下配置:

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

 

啓動類

在啓動類上加上@EnableFeignClients

1
2
3
4
5
6
7
8
@EnableFeignClients
@SpringBootApplication
public class EurekaConsumerFeignApplication {

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

 

Feign 調用實現

建立一個 Feign 的客戶端接口定義。使用@FeignClient註解來指定這個接口所要調用的服務名稱,接口中定義的各個函數使用 Spring MVC 的註解就能夠來綁定服務提供方的 REST 接口,好比下面就是綁定 eureka-producer 服務的/hello/接口的例子:

1
2
3
4
5
6
7
@FeignClient(name = "eureka-producer")
public interface HelloRemote {

@GetMapping("/hello/")
String hello(@RequestParam(value = "name") String name);

}

 

此類中的方法和遠程服務中 Contoller 中的方法名和參數需保持一致。

這裏有幾個坑,後邊有詳細說明。

Controller

修改 Controller,將 HelloRemote 注入到 controller 層,像普通方法同樣去調用便可

1
2
3
4
5
6
7
8
9
10
11
12
13
@RequestMapping("/hello")
@RestController
public class HelloController {

@Autowired
HelloRemote helloRemote;

@GetMapping("/{name}")
public String index(@PathVariable("name") String name) {
return helloRemote.hello(name + "!");
}

}

 

經過 Spring Cloud Feign 來實現服務調用的方式很是簡單,經過@FeignClient定義的接口來統一的聲明咱們須要依賴的微服務接口。而在具體使用的時候就跟調用本地方法一點的進行調用便可。因爲 Feign 是基於 Ribbon 實現的,因此它自帶了客戶端負載均衡功能,也能夠經過 Ribbon 的 IRule 進行策略擴展。另外,Feign 還整合的 Hystrix 來實現服務的容錯保護,這個在後邊會詳細講。(在 Finchley.RC1 版本中,Feign 的 Hystrix 默認是關閉的。參考 Spring Cloud OpenFeign 和 Disable HystrixCommands For FeignClients By Default)。

在個人 IDEA 裏,這裏會有錯誤提示,以下

這個其實不用管,運行的時候會被正確注入。若是嫌這個提示煩,能夠在HelloRemote這個接口上邊加@Component註解。

訪問 http://localhost:9002/hello/windmt 以驗證是否調用成功

1
Hello, windmt! Sat Apr 14 01:03:56 CST 2018

 

踩坑記錄

問題一:not have available server

1
com.netflix.client.ClientException: Load balancer does not have available server for client: eureka-producer

這個問題剛開始困擾了我好長時間,最後發現原來是由於我沒加入 eureka-client 這個依賴,只加了 spring-boot-starter-web 和 spring-cloud-starter-openfeign。只有後二者的話,啓動的時候實際上是不會有任何異常被拋出的,
可是若是細心地查看了啓動 log 的話,其中有這麼一條能夠看出實際上確實是沒有獲取到任何服務的

1
c.netflix.loadbalancer.BaseLoadBalancer  : Client: eureka-producer instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=eureka-producer,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null

 

因此,要想使用 Feign,至少須要如下三個依賴

  • spring-boot-starter-web
  • spring-cloud-starter-openfeign
  • spring-cloud-starter-netflix-eureka-client

問題二:Request method ‘POST’ not supported

1
2
feign.FeignException: status 405 reading HelloRemote#hello(String); content:
{"timestamp":"2018-04-13T16:29:12.453+0000","status":405,"error":"Method Not Allowed","message":"Request method 'POST' not supported","path":"/hello/"}

HelloRemote中的代碼是這樣的,出現上邊的異常

1
2
@GetMapping("/hello/")
String hello(String name);

 

改爲這樣,仍是一樣的異常

1
2
@RequestMapping(value = "/hello/", method = RequestMethod.GET)
String hello(String name);

 

再改,此次 OK 了

1
2
@GetMapping("/hello/")
String hello(@RequestParam(value = "name") String name);

 

這個問題挺奇葩的的,不加@RequestParam就變成了 POST 請求,不論我是用@GetMapping仍是method = RequestMethod.GET,也是無語了。
至於怎麼想到的加了個@RequestParam(value = "name"),說來話長。一開始我沒加 eureka-client 依賴,也沒加@RequestParam註解,一啓動就報異常:

1
Caused by: java.lang.IllegalStateException: PathVariable annotation was empty on param 0.

 

因此就加了@RequestParam,算是歪打正着吧。

至於爲何出現這個 GET 變 POST 的狀況,我的猜想應該是當參數沒有被@RequestParam註解修飾時,會自動被當作 request body 來處理。只要有 body,就會被 Feign 認爲是 POST 請求,因此整個服務是被看成帶有 request parameter 和 body 的 POST 請求發送出去的。

負載均衡

以上三種方式都能實現負載均衡,都是以輪詢訪問的方式實現的。這個以你們經常使用的 Feign 的方式作一個測試。

以上面 eureka-producer 爲例子修改,將其中的 controller 改動以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
@RequestMapping("/hello")
public class HelloController {

@Value("${config.producer.instance:0}")
private int instance;

@GetMapping("/")
public String hello(@RequestParam String name) {
return "[" + instance + "]" + "Hello, " + name + " " + new Date();
}

}

 

打包啓動

1
2
3
4
5
6
7
8
9
// 打包
mvn clean package -Dmaven.test.skip=true

// 分別以 8000 和 8001 啓動實例 1 和 實例 2
java -jar target/eureka-producer-0.0.1-SNAPSHOT.jar --config.producer.instance=1 --server.port=8000
java -jar target/eureka-producer-0.0.1-SNAPSHOT.jar --config.producer.instance=2 --server.port=8001

// 在端口 9002 上啓動 eureka-consumer-feign
java -jar target/eureka-eureka-consumer-feign-0.0.1-SNAPSHOT.jar --server.port=9002

 

訪問 http://localhost:9002/hello/windmt 進行測試。在不斷的測試下去會發現兩種結果交替出現

1
2
3
4
5
[1]Hello, , windmt Sun Apr 15 19:37:15 CST 2018
[2]Hello, , windmt Sun Apr 15 19:37:17 CST 2018
[1]Hello, , windmt Sun Apr 15 19:37:19 CST 2018
[2]Hello, , windmt Sun Apr 15 19:37:24 CST 2018
[1]Hello, , windmt Sun Apr 15 19:37:27 CST 2018

這說明兩個服務中心自動提供了服務均衡負載的功能。若是咱們將服務提供者的數量在提升爲 N 個,測試結果同樣,請求會自動輪詢到每一個服務端來處理。

相關閱讀

Spring Cloud(一):服務治理技術概覽
Spring Cloud(二):服務註冊與發現 Eureka
Spring Cloud(三):服務提供與調用 Eureka
Spring Cloud(四):服務容錯保護 Hystrix
Spring Cloud(五):Hystrix 監控面板
Spring Cloud(六):Hystrix 監控數據聚合 Turbine
Spring Cloud(七):配置中心(Git 版與動態刷新)
Spring Cloud(八):配置中心(服務化與高可用)
Spring Cloud(九):配置中心(消息總線)
Spring Cloud(十):服務網關 Zuul(路由)
Spring Cloud(十一):服務網關 Zuul(過濾器)
Spring Cloud(十二):分佈式鏈路跟蹤(Sleuth 與 Zipkin)

示例代碼:GitHub

參考

Spring Cloud 構建微服務架構:服務消費(基礎)【Dalston 版】
Spring Cloud 構建微服務架構:服務消費(Ribbon)【Dalston 版】
Spring Cloud 構建微服務架構:服務消費(Feign)【Dalston 版】
springcloud(三):服務提供與調用
Spring Cloud OpenFeign
Disable HystrixCommands For FeignClients By Default
Feign 使用 Hystrix 無效緣由及解決方法
spring cloud-Feign 使用中遇到的問題總結

相關文章
相關標籤/搜索