这是一个从零开始的springcloud的系列教程,如果你从中间开始看,可能会看不明白.请进入我的系列教程开始从头开始学习.spring-cloud教程
启动服务治理服务
在之前我们学会了怎么启动一个eureka服务治理注册搭建,启动eureka-server服务.eureka服务治理和注册教程
Feign
在之前我们知道ribbo+restTemplate来访问微服务,不过似乎还不够简单,现在我们需要用更简单的方式来完成微服务之间的调用.
Feign简介
Feign是一个声明式的伪Http客户端,它使得写Http客户端变得更简单。使用Feign,只需要创建一个接口并注解。它具有可插拔的注解特性,可使用Feign 注解和JAX-RS注解。Feign支持可插拔的编码器和解码器。Feign默认集成了Ribbon,并和Eureka结合,默认实现了负载均衡的效果。简而言之:
Feign 采用的是基于接口的注解
Feign 整合了ribbon,具有负载均衡的能力
整合了Hystrix,具有熔断的能力
打开我们之前创建的工程spring-cloud-learn.沿用我们之前创建的多maven工程.启动eureka-server,eureka-client
创建一个工程.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>
创建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
创建FeignService,提供三个方法,访问的服务名字为eureka-client.
- sayStore方法: 请求参数name,要求的返回值为string类型
- getStores方法: 请求参数length,要求的返回值为store类型.如果我们没给feign配置默认解码器,默认的解码器为SpringDecoder.该解码器如果发现返回的内容是application/json类型,会解析成json对象
-
updateStore方法:
- put方法,请求体为store,是一个对象
- 如果没有配置默认的编码器,则为SpringEncoder
- 如果@PutMapping中consumes参数有值,则按照consumes参数格式(比如application/json)来解析,如果没有则默认按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, 不过编译器会出现错误警告.因为FeignService是动态注入的,会出现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);
/**
* 默认没有注解的参数为请求体,如果再加一个参数,则会报错Method has too many Body parameters
* consumes = "application/json", 表示发送的body内容为json
*/
@PutMapping(value = "/stores/{storeId}")
Store updateStore(@PathVariable("storeId") String id, Store store);
}
接着给eureka-client-feign工程创建一个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创建相应的api
eureka-client服务创建api
给eureka-client创建StoreController.写上对应的三个http请求.请求体为实体类型,默认的解析类型都为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服务的api,eureka-client-feign收到请求后通过feign发送给eureka-client
我们可以给feign配置自定的encoder,decoder等
feign:
client:
config:
# 这里是服务名字,如果我们给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
# 协议
contract: com.example.SimpleContract
feignName2:
....
Spring-Cloud-Feign提供了默认的配置
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.
没有默认配置的有
- Logger.Level
- Retryer: 超时重试
- ErrorDecoder
- Request.Options
- Collection<RequestInterceptor>
- SetterFactory
熔断器
想象一下这种情况,如果一个微服务无法提供内容,这时向这个微服务发送请求的线程就会被阻塞.
假设连接的超时时长为5秒,有大量请求在这五秒内涌入,因为对微服务请求没有结束,无法释放线程资源,这表示着很快机器的线程资源将会被消耗殆尽,一旦消耗殆尽后,又引起本服务请求延迟,其他依赖本服务的服务也会开始发生这样的情况,最终如果多米诺骨牌一般,导致所有微服务崩溃.这成为雪崩效应.
为了解决这个问题,业界提出了断路器模型。
http请求->服务A->服务B(不可用)->服务A发现服务B不可用(Hystric 是5秒20次请求失败)->服务A使用熔断器
请求变为http请求->服务A->服务A熔断方法
服务A实现了服务降级
Feign中已包含了熔断器,需要启动熔断器
feign:
hystrix:
enabled: true
hystrix:
command:
default:
execution:
isolation:
# 隔离策略 THREAD|SEMAPHORE
# THREAD —— 在固定大小线程池中,以单独线程执行,并发请求数受限于线程池大小。
# SEMAPHORE —— 在调用线程中执行,通过信号量来限制并发量。
strategy: SEMAPHORE
在eureka-client-feign创建FeignServiceFallback类,这个类作用就是当服务B发生异常后,将会会使用fallback里的方法进行代替.该类需要注册到spring中且需要实现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, 不过编译器会出现错误警告,说找不到FeignService,因为FeignService是动态注入的
@Component
public interface FeignService {
....
关闭eureka-client服务,调用/hello_store api,熔断器起了作用,直接返回我们定义的内容
如果想要捕捉到错误异常,可以使用FallbackFactory.创建文件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参数
@FeignClient(name = "eureka-client", fallbackFactory = FeignServiceFallbackFactory.class)
// 不加@Component, 不过编译器会出现错误警告,说找不到FeignService,因为FeignService是动态注入的
@Component
public interface FeignService {
....
调用/hello_stores,打印出错误异常.
总结
我们学会如何使用feign来执行服务之间rpc的调用,并且还学会了使用hystrix来防止服务器的雪崩效应