我们通过Ribbon或者Feign实现微服务之间的调用和负载均衡,那么我们各种微服务之间又应该如何提供给外部应用使用呢?
因为是Rest API接口,所以外部客户端直接调用微服务是没有问题的,但是由于种种原因,这并不是一个很好的的选择。
让客户端直接与各个微服务进行通信,会产生一下几个问题:
- 客户端会多次请求不同的微服务,增加客户端的复杂性。
- 存在跨域请求,在一定场景下处理变得相对比较复杂
- 实现认证复杂,每个微服务需要独立认证
- 难以重构,项目迭代可能会导致微服务重新划分,如果客户端直接与微服务通信,那么重构将很难实现,
- 如果某些微服务使用了防火墙或者浏览器不友好的协议,直接访问存在一定的困难。
那么我们该怎么解决呢?
答案就是服务网关
使用服务网关存在一下几个优点:
- 易于监控, 可以在微服务网关收集监控数据并推送到外部应用进行分析
- 易于认证, 可以在服务网关上进行认证,然后转发到微服务, 无需再每个微服务上进行认证。
- 客户端只跟服务网关打交道,减少了客户端与各个微服务之间的交互次数。
- 多渠道支持, 可以根据不同客户端(Web端, 移动端, 桌面端登)提供不同的API服务网关。

Spring Cloud Zuul

服务网关是微服务架构中不可或缺的部分,在通过服务网关统一向外部系统提供REST API接口的过程中, 除了具备服务路由,负载均衡功能之外, 它还具备权限控制功能。
Spring Cloud Netflix中的Zuul就担任了这样的一个角色, 为微服务的构建提供前门保护的作用, 同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面, 使得服务集群主体能够具备更高的复用性和可测试性。
在Spring Cloud 体系中, Spring Cloud zuul封装了zuul组件, 作为一个API网关, 负责提供负载均衡, 反向代理和权限认证。

Zuul的工作机制

  1. 过滤器机制
    zuul的核心是一系列的filters,其作用类似于Servlet框架中的Filter,Zuul把客户端请求路由到业务处理逻辑的过程中, 这些filter在路由的特定时期参与了一些过滤处理, 比如实现鉴权, 流量转发, 请求统计登功能
  2. 过滤器的生命周期
    FIlter的生命周期有4个,分别是“PRE”,“ROUTING”, “POST”, “ERROR”,
    基于Zuul的这些过滤器可以实现各种丰富的功能, 而这些过滤器类型则对应于请求的典型生命周期:
  • PRE:这种过滤器在请求被路由之前调用, 我们可以使用这种路由器实现身份验证, 在集群中选择请求的微服务名记录调试信息登。
  • ROUTINE:这种过滤器将请求路由到微服务, 这种过滤器用于构建发送给微服务的请求, 并使用Apache HttpClient或者Netflix Ribbon请求微服务。
  • POST: 这种过滤器在路由到微服务以后执行, 这种过滤器可以来为响应添加相应标准的Http Header:统计收集信息和指标, 将响应从微服务发送给客户端。
  • ERROR:在其他阶段错误时执行的过滤器
    3. 禁止指定的Filter
    可以在application.yml中配置需要禁用的Filter,格式为Zuul...disable=true
    另外自定义filter要继承ZuulFilter,并实现ZuulFilter中的抽象方法。

实现案例

1.  新建gateway-server作为服务网关
2. 添加依赖
添加consul,zuul以及相关依赖

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<!--集成注册中心依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!--服务配置中心-->
<!--监控中心-->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-client</artifactId>
<version>${spring-boot-admin.version}</version>
</dependency>
<!--集成健康检查-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  1. 启动类
    为启动类添加@EnableZuulProxy注解, 开启服务网关支持
//开启服务网关支持
@EnableZuulProxy

@SpringBootApplication
public class GatewayServerApplication {

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

}
  1. 配置文件
    配置启动端口为8204,注册服务到注册中心, 配置Zuul转发规则, 这里配置可以访问任何微服务的接口
server:
port: 8204
spring:
application:
name: gateway-server
cloud:
consul:
host: 172.17.0.1 #注册源
port: 8500 #注册端口
discovery:
service-name: ${spring.application.name} #注册到Consul的服务名称
health-check-url: http://172.17.0.1:8204/actuator/health
hostname: 172.17.0.1

boot:
admin:
client:
url: "http://172.17.0.1:8205"

management:
endpoint:
health:
show-details: always
endpoints:
web:
exposure:
include: '*'


zuul:
sensitive-headers: #敏感头部,设置为空,防止截获Authorization
host:
connect-timeout-millis: 20000
socket-timeout-millis: 20000

#遵循默认路由规则:http://ZUUL_HOST:ZUUL_PORT/微服务注册中心serviceId/**
  1. 上传到服务器测试
    DockerFile
FROM java:8
MAINTAINER lidengyin
ARG JAR_FILE
ADD ${JAR_FILE} gateway-server.jar
ADD ./simsun.ttc /usr/share/fonts
EXPOSE 8204
ENTRYPOINT ["java","-jar","gateway-server.jar"]

docker-compose.yml

version: '3'
services:
micro-gateway-server:
image: 172.18.0.1:5000/gateway-server:0.0.1-SNAPSHOT
restart: on-failure
ports:
- 8204:8204
  1. “com.netflix.zuul.exception.ZuulException: Hystrix Readed time out”"
    因为默认的连接超时时间比较小,但是在进行一些大的查询的时候依旧会链接超时,多试几次却可以了
zuul:
sensitive-headers: #敏感头部,设置为空,防止截获Authorization
host:
connect-timeout-millis: 60000
socket-timeout-millis: 60000

7. 默认路由规则
由于如果后端的微服务特别多, 每一个这样的配置很麻烦, Spring Cloud Zuul做了默认的设置, 默认情况下, Zuul会代理所有注册到注册中心的微服务, 并且Zuul的默认路由规则如下:

http://ZUUL_HOST:ZUUL_PORT/微服务在注册中心的serviceId/**

会被转发到serviceId对应的微服务。如果遵循默认路由规则就没有什么需要配置的了。
8. 路由熔断
 Zuul作为Netfilx组件, 可以与Ribbon, Eureka和Hystrix登组件结合, 实现负载均衡。熔断器的功能。 默认情况下,Zuul与Ribbon登组件相互结合,实现负载均衡。实现负载均衡需要时先FallBackProvider接口, 实现该接口的两个方法:一个是getRoute(), 用于指定熔断器功能需要用到那些路由服务;另一个方法就是fallbackResponse()接口, 为进入熔断器功能时执行的逻辑。
 创建MyFallBackProvider类, getRoute()方法返回"*",用于针对所有路由服务添加熔断器功能。getBody()方法返回发送熔断时的反馈信息, 这里在发送熔断时返回信息“Sorry, the service is unavaliable now”;

public class MyFallbackProvider implements FallbackProvider {
@Override
/**
* 指定熔断器功能对那些路由起作用,*为匹配所有为服务
*/
public String getRoute() {
return "*";
}

/**
* 降级服务
* @param route
* @param cause
* @return
*/
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
System.out.println("route:"+route);
System.out.println("exception:"+cause.getMessage());
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}

@Override
public int getRawStatusCode() throws IOException {
return 200;
}

@Override
public String getStatusText() throws IOException {
return "ok";
}

@Override
public void close() {

}

@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("sorry, the service is unavailable now.".getBytes());
}

@Override
public HttpHeaders getHeaders() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
return httpHeaders;
}
};
}
}

### 路由熔断

{
"timestamp": "2020-03-23T01:42:00.207+0000",
"status": 504,
"error": "Gateway Timeout",
"message": "com.netflix.zuul.exception.ZuulException: Hystrix Readed time out"
}
can't parse JSON.  Raw result:

sorry, the service is unavailable now.
micro-gateway-server_1  | route:hcnet-website-1
micro-gateway-server_1 | exception:null
micro-gateway-server_1 | route:hcnet-website-1
micro-gateway-server_1 | exception:null
micro-gateway-server_1 | route:hcnet-website-1
micro-gateway-server_1 | exception:null
micro-gateway-server_1 | route:hcnet-website-1
micro-gateway-server_1 | exception:null
micro-gateway-server_1 | route:hcnet-website-1
micro-gateway-server_1 | exception:null
"class java.util.Collections$SingletonMap is not a type supported by this

关掉zuul依旧是这个报错
并且生产者没有任何反映,应该是消费者与zuul中的错误。

Configuration
class MultipartSupportConfig {
@Bean
public Encoder feignFormEncoder() {
return new SpringFormEncoder();
}
}

我想到解决图片流传输的时候,设置过这个feign传输文件设置,注释掉试一试
结果,图片流依旧可以,但是依旧包这个错

{
"timestamp": "2020-03-23T03:44:58.534+0000",
"status": 500,
"error": "Internal Server Error",
"message": "Type definition error: [simple type, class java.io.FileDescriptor]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class java.io.FileDescriptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: java.util.Collections$SingletonMap[\"uploadFile\"]->org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile[\"inputStream\"]->java.io.FileInputStream[\"fd\"])",
"path": "/feign/upload/apk"
}

这个错

"message": "class java.util.Collections$SingletonMap is not a type supported by this encoder.",

最后又回到了这个问题

@RequestMapping(value = "/upload",consumes = MediaType.MULTIPART_FORM_DATA_VALUE)

我忘记加consumer

"Incomplete output stream executing POST http://hcnet-website-1/upload/apk

然后就是上面的问题

***************************
micro-micro-consumer_1 | APPLICATION FAILED TO START
micro-micro-consumer_1 | ***************************
micro-micro-consumer_1 |
micro-micro-consumer_1 | Description:
micro-micro-consumer_1 |
micro-micro-consumer_1 | An attempt was made to call a method that does not exist. The attempt was made from the following location:
micro-micro-consumer_1 |
micro-micro-consumer_1 | org.springframework.cloud.openfeign.support.SpringMvcContract.processAnnotationOnClass(SpringMvcContract.java:179)
micro-micro-consumer_1 |
micro-micro-consumer_1 | The following method did not exist:
micro-micro-consumer_1 |
micro-micro-consumer_1 | feign.RequestTemplate.uri(Ljava/lang/String;)Lfeign/RequestTemplate;
micro-micro-consumer_1 |
micro-micro-consumer_1 | The method's class, feign.RequestTemplate, is available from the following locations:
micro-micro-consumer_1 |
micro-micro-consumer_1 | jar:file:/micro-consumer.jar!/BOOT-INF/lib/feign-core-8.17.0.jar!/feign/RequestTemplate.class
micro-micro-consumer_1 | jar:file:/micro-consumer.jar!/BOOT-INF/lib/feign-core-10.4.0.jar!/feign/RequestTemplate.class
micro-micro-consumer_1 |
micro-micro-consumer_1 | It was loaded from the following location:
micro-micro-consumer_1 |
micro-micro-consumer_1 | jar:file:/micro-consumer.jar!/BOOT-INF/lib/feign-core-8.17.0.jar!/
micro-micro-consumer_1 |
micro-micro-consumer_1 |
micro-micro-consumer_1 | Action:
micro-micro-consumer_1 |
micro-micro-consumer_1 | Correct the classpath of your application so that it contains a single, compatible version of feign.RequestTemplate