這是一個從零開始的springcloud的系列教程,如果你從中間開始看,可能會看不明白.請進(jìn)入我的系列教程開始從頭開始學(xué)習(xí).spring-cloud教程
啟動服務(wù)治理服務(wù)
在之前我們學(xué)會了怎么啟動一個eureka服務(wù)治理注冊搭建,啟動eureka-server服務(wù).eureka服務(wù)治理和注冊教程
Feign
在之前我們知道ribbo+restTemplate來訪問微服務(wù),不過似乎還不夠簡單,現(xiàn)在我們需要用更簡單的方式來完成微服務(wù)之間的調(diào)用.
Feign簡介
Feign是一個聲明式的偽Http客戶端,它使得寫Http客戶端變得更簡單椭住。使用Feign字逗,只需要創(chuàng)建一個接口并注解葫掉。它具有可插拔的注解特性,可使用Feign 注解和JAX-RS注解俭厚。Feign支持可插拔的編碼器和解碼器。Feign默認(rèn)集成了Ribbon叼丑,并和Eureka結(jié)合鸠信,默認(rèn)實現(xiàn)了負(fù)載均衡的效果。簡而言之:
Feign 采用的是基于接口的注解
Feign 整合了ribbon店雅,具有負(fù)載均衡的能力
整合了Hystrix贞铣,具有熔斷的能力
打開我們之前創(chuàng)建的工程spring-cloud-learn.沿用我們之前創(chuàng)建的多maven工程.啟動eureka-server,eureka-client
創(chuàng)建一個工程.eureka-client-feign.
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-learn</artifactId>
<groupId>com.jack</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>eureka-client-feign</artifactId>
<dependencies>
<!--
一定要寫稱spring-cloud-starter-netflix-eureka-client
如果寫錯成spring-cloud-netflix-eureka-client,將無法注冊
-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--引入feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
創(chuàng)建EurekaClientFeignApplication文件
package com.jack.feign;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class EurekaClientFeignApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientFeignApplication.class, args );
}
}
application.yml
server:
port: 7773
eureka:
client:
serviceUrl:
defaultZone: http://localhost:7770/eureka/
spring:
application:
name: eureka-client-feign
創(chuàng)建FeignService,提供三個方法,訪問的服務(wù)名字為eureka-client.
- sayStore方法: 請求參數(shù)name,要求的返回值為string類型
- getStores方法: 請求參數(shù)length,要求的返回值為store類型.如果我們沒給feign配置默認(rèn)解碼器,默認(rèn)的解碼器為SpringDecoder.該解碼器如果發(fā)現(xiàn)返回的內(nèi)容是application/json類型,會解析成json對象
-
updateStore方法:
- put方法,請求體為store,是一個對象
- 如果沒有配置默認(rèn)的編碼器,則為SpringEncoder
- 如果@PutMapping中consumes參數(shù)有值,則按照consumes參數(shù)格式(比如application/json)來解析,如果沒有則默認(rèn)按application/json.
- 如果請求體是String,則按照text/plain編碼
package com.jack.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@FeignClient(name = "eureka-client")
// 可以選擇不加@Component, 不過編譯器會出現(xiàn)錯誤警告.因為FeignService是動態(tài)注入的,會出現(xiàn)FeignService無法找到的警告.
// 加了@Component,向spring聲明下我有一個FeignService的bean
@Component
public interface FeignService {
@GetMapping(value = "/hello_store")
String sayStore(@RequestParam(value = "name") String name);
@GetMapping(value = "/stores")
List<Store> getStores(@RequestParam(value = "length", required = false) Integer length);
/**
* 默認(rèn)沒有注解的參數(shù)為請求體,如果再加一個參數(shù)酱畅,則會報錯Method has too many Body parameters
* consumes = "application/json", 表示發(fā)送的body內(nèi)容為json
*/
@PutMapping(value = "/stores/{storeId}")
Store updateStore(@PathVariable("storeId") String id, Store store);
}
接著給eureka-client-feign工程創(chuàng)建一個StoreController, 為了方便瀏覽器訪問,三個方法皆用get請求
package com.jack.feign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
public class StoreController {
private FeignService feignService;
@Autowired
public void setFeignService(FeignService feignService) {
this.feignService = feignService;
}
@GetMapping("/hello_store")
public Object hello(@RequestParam String name) {
return feignService.sayStore(name);
}
@GetMapping("/stores")
public Object getStores() {
return feignService.getStores(2);
}
@PutMapping("/stores/{storeId}")
public Object updateStore(@PathVariable String storeId) {
List<Store> stores = feignService.getStores(2);
Store store = feignService.updateStore(storeId, stores.get(0));
return store;
}
}
eureka-client-feign三個方法建立完畢,開始給eureka-client創(chuàng)建相應(yīng)的api
eureka-client服務(wù)創(chuàng)建api
給eureka-client創(chuàng)建StoreController.寫上對應(yīng)的三個http請求.請求體為實體類型,默認(rèn)的解析類型都為application/json.如果有需要變更@PutMapping(value = "/stores/{storeId}", "consumes"="xxxx")
package com.jack.eureka_client;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
public class StoreController {
@GetMapping(value = "/hello_store")
public Object stores() {
return "hello store!!!";
}
@GetMapping(value = "/stores")
public Object stores(@RequestParam(defaultValue = "5") Integer length) {
List<Map<String, Object>> stores = new ArrayList<>();
for (int i = 0; i < length; i++) {
Map<String, Object> store = new HashMap<>();
store.put("name", "jack" + i);
store.put("date", new Date());
stores.add(store);
}
return stores;
}
@PutMapping(value = "/stores/{storeId}")
public Object updateStore(@PathVariable String storeId,
@RequestBody Map<String, Object> store) {
System.out.println(storeId);
System.out.println(store);
return store;
}
}
到了這一步,請求eureka-client-feign服務(wù)的api,eureka-client-feign收到請求后通過feign發(fā)送給eureka-client
我們可以給feign配置自定的encoder,decoder等
feign:
client:
config:
# 這里是服務(wù)名字,如果我們給eureka-client配置,要寫eureka-client.如果想給全局配就寫default.
feignName:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: full
errorDecoder: com.example.SimpleErrorDecoder
retryer: com.example.SimpleRetryer
requestInterceptors:
- com.example.FooRequestInterceptor
- com.example.BarRequestInterceptor
decode404: false
# 編碼器
encoder: com.example.SimpleEncoder
# 解碼器
decoder: com.example.SimpleDecoder
# 協(xié)議
contract: com.example.SimpleContract
feignName2:
....
Spring-Cloud-Feign提供了默認(rèn)的配置
Decoder feignDecoder: ResponseEntityDecoder (which wraps a SpringDecoder)
Encoder feignEncoder: SpringEncoder
Logger feignLogger: Slf4jLogger
Contract feignContract: SpringMvcContract
Feign.Builder feignBuilder: HystrixFeign.Builder
Client feignClient: if Ribbon is enabled it is a LoadBalancerFeignClient, otherwise the default feign client is used.
沒有默認(rèn)配置的有
- Logger.Level
- Retryer: 超時重試
- ErrorDecoder
- Request.Options
- Collection<RequestInterceptor>
- SetterFactory
熔斷器
想象一下這種情況,如果一個微服務(wù)無法提供內(nèi)容,這時向這個微服務(wù)發(fā)送請求的線程就會被阻塞.
假設(shè)連接的超時時長為5秒,有大量請求在這五秒內(nèi)涌入,因為對微服務(wù)請求沒有結(jié)束,無法釋放線程資源,這表示著很快機(jī)器的線程資源將會被消耗殆盡,一旦消耗殆盡后,又引起本服務(wù)請求延遲,其他依賴本服務(wù)的服務(wù)也會開始發(fā)生這樣的情況,最終如果多米諾骨牌一般,導(dǎo)致所有微服務(wù)崩潰.這成為雪崩效應(yīng).
為了解決這個問題餐蔬,業(yè)界提出了斷路器模型。
http請求->服務(wù)A->服務(wù)B(不可用)->服務(wù)A發(fā)現(xiàn)服務(wù)B不可用(Hystric 是5秒20次請求失敗)->服務(wù)A使用熔斷器
請求變?yōu)閔ttp請求->服務(wù)A->服務(wù)A熔斷方法
服務(wù)A實現(xiàn)了服務(wù)降級
Feign中已包含了熔斷器,需要啟動熔斷器
feign:
hystrix:
enabled: true
hystrix:
command:
default:
execution:
isolation:
# 隔離策略 THREAD|SEMAPHORE
# THREAD —— 在固定大小線程池中仗考,以單獨線程執(zhí)行词爬,并發(fā)請求數(shù)受限于線程池大小。
# SEMAPHORE —— 在調(diào)用線程中執(zhí)行锅锨,通過信號量來限制并發(fā)量恋沃。
strategy: SEMAPHORE
在eureka-client-feign創(chuàng)建FeignServiceFallback類,這個類作用就是當(dāng)服務(wù)B發(fā)生異常后,將會會使用fallback里的方法進(jìn)行代替.該類需要注冊到spring中且需要實現(xiàn)FeignService
package com.jack.feign;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
@Component
public class FeignServiceFallback implements FeignService {
@Override
public String sayStore(String name) {
return "sorry i am down! " + name;
}
@Override
public List<Store> getStores(Integer length) {
return Collections.emptyList();
}
@Override
public Store updateStore(String id, Store store) {
store.setName("sorry.I am down!");
return store;
}
}
將FeignServiceFallback添加到FeignService中
package com.jack.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@FeignClient(name = "eureka-client", fallback = FeignServiceFallback.class)
// 不加@Component, 不過編譯器會出現(xiàn)錯誤警告芽唇,說找不到FeignService,因為FeignService是動態(tài)注入的
@Component
public interface FeignService {
....
關(guān)閉eureka-client服務(wù),調(diào)用/hello_store api,熔斷器起了作用,直接返回我們定義的內(nèi)容
如果想要捕捉到錯誤異常,可以使用FallbackFactory.創(chuàng)建文件FeignServiceFallbackFactory
package com.jack.feign;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
@Component
public class FeignServiceFallbackFactory implements FallbackFactory<FeignService> {
@Override
public FeignService create(Throwable throwable) {
return new FeignService() {
@Override
public String sayStore(String name) {
return "sorry: " + throwable.getMessage();
}
@Override
public List<Store> getStores(Integer length) {
System.out.println(throwable);
return Collections.emptyList();
}
@Override
public Store updateStore(String id, Store store) {
store.setName("sorry: " + throwable.getMessage());
return store;
}
};
}
}
修改FeignService文件
package com.jack.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;
import java.util.List;
// 改為fallbackFactory,之前是fallback參數(shù)
@FeignClient(name = "eureka-client", fallbackFactory = FeignServiceFallbackFactory.class)
// 不加@Component, 不過編譯器會出現(xiàn)錯誤警告匆笤,說找不到FeignService,因為FeignService是動態(tài)注入的
@Component
public interface FeignService {
....
調(diào)用/hello_stores,打印出錯誤異常.
總結(jié)
我們學(xué)會如何使用feign來執(zhí)行服務(wù)之間rpc的調(diào)用,并且還學(xué)會了使用hystrix來防止服務(wù)器的雪崩效應(yīng)