使用Spring Cloud Netflix中的Eureka、Ribbon、Feign、Hystrix做了整个微服务的注册、服务发现、交互通信、高可用负载均衡、限流策略、服务降级等功能,保证了我们微服务的健壮性。但是我们是不是缺少了点什么,我们HTTP请求的安全性如何保障呢?可能我自己要对请求加上一些安全性的组件,但是这么做是否就很好呢?
首先,不破坏服务无状态特点。为了保证对外服务的安全性,我们需要实现对服务访问的权限控制,而开放服务的权限控制机制将会贯穿并污染整个开放服务的业务逻辑,这会带来的最直接问题是,破坏了服务集群中REST API无状态的特点。从具体开发和测试的角度来说,在工作中除了要考虑实际的业务逻辑之外,还需要额外可续对接口访问的控制处理。
其次,无法直接复用既有接口。当我们需要对一个即有的集群内访问接口,实现外部服务访问时,我们不得不通过在原有接口上增加校验逻辑,或增加一个代理调用来实现权限控制,无法直接复用原有的接口。
为了解决刚才我们微服务所面临的安全性等一些问题,我们需要将权限控制这样的东西从我们的服务单元中抽离出去,而最适合这些逻辑的地方就是处于对外访问最前端的地方,我们需要一个更强大一些的均衡负载器,它就是本文将来介绍的:服务网关。
网关顾名思义就是网络请求的入口,外部服务通过网关进行访问我们的内部服务,网关可以做的事情很多,进行限流、安全、权限认证、黑白名单过滤、请求拦截、数据校验、api路由转发等等功能。
服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由、均衡负载功能之外,它还具备了权限控制等功能。Spring Cloud Netflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。
•使用spring cloud zuul非常的简单,按照惯例,引入依赖spring-cloud-starter-zuul,并且在主入口加入注解@EnableZuulProxy即可,最后我们对yml进行相关配置。
完成这步相关配置后,我们快速实现这两个service 然后使用zuul的地址进行访问他们,就可以得到进行路由调用的结果,非常的简单。当然zuul有自己的默认路由规则,还可以使用正则表达式进行自定义路由规则。
上图实现的是HA,当一个Zuul出现故障是另一个Zuul启用工作,这不是最优方式;
最优方法:前端使用nginx负载均衡zuul集群来实现;
•通过zuul组件的jar可以发现,他所依赖的组件有ribbon和hystrix,这说明了zuul同样集成了这两个组件,同样能进行负载均衡和断路器功能的实现。
•全局最大化超时配置:在网关上设置全局的ribbon和hystrix超时时间都要尽可能大,保证包含内部的超时/重试/段熔/降级的时间。当然zuul内部本身也有段熔功能。
Zuul的使用必须实现Filter类(com.netflix.zuul.ZuulFilter):
•使用Zuul的过滤器, 可以对请求的一些列信息进行安全认证/权限认证/身份识别等功能。只需要自定义Filter后继承ZuulFilter类即可。重写其filterType、filterOrder、shouldFilter以及run方法。
•filterType表示filter执行的时机:其value值含义如下所示:
•pre:在请求被路由之前调用
•routing: 在请求被路由之中调用
•post: 在请求被路由之后调用
•error: 处理请求发生错误时调用
•filterOrder表示执行的优先级,值越小表示优先级越高
•shouldFilter则表示该filter是否需要执行
代码实现:
eureka-server:略;
oderservice:
pom:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 标示这个工程是一个服务的提供者,需要引入此jar -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 动态刷新的一个模块jar -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>spring-cloud-05-orderservice</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.cc.springcloud.Application</mainClass>
</configuration>
</plugin>
</plugins>
</build>
------------------------------------------------------------------------------------------------------
application.properties:
spring.application.name=order-service
##微服务方式尽量不要加server.context-path,只用名字加方法实现
#server.context-path=/provider
server.context-path=/
server.port=7001
##需要引入eureka注册中心的地址
##下面两条配置代表注册到注册中心后显示自己的IP地址,实际工作中尽量加上
eureka.instance.prefer-ip-address=true
eureka.instance.instance-id=${spring.application.name}:${spring.cloud.client.ipAddress}:${server.port}
##租期到期时间间隔
#eureka.instance.lease-expiration-duration-in-seconds=30
##租期更新时间间隔
#eureka.instance.lease-renewal-interval-in-seconds=10
##开启健康检查,必须要引入spring-boot-starter-actuator动态刷新jar
eureka.client.healthcheck.enabled=true
eureka.client.service-url.defaultZone=http://eureka1:8001/eureka
------------------------------------------------------------------------------------------------------
Application:
package com.cc.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient //标示是一个具体的服务,需要向注册中心注册
@SpringBootApplication //springboot 核心配置
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
------------------------------------------------------------------------------------------------------
OrderController:
package com.cc.springcloud.api;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
@RequestMapping(value="/order")
public String order() throws Exception{
return "------order-----";
}
}
------------------------------------------------------------------------------------------------------
userservice与orderserveice除名称全部相同,略;
zuul-a与zuul-b除名称全部相同:
pom:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 动态刷新的一个模块jar -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- zuul -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>spring-cloud-05-zuul-a</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.cc.springcloud.Application</mainClass>
</configuration>
</plugin>
</plugins>
</build>
------------------------------------------------------------------------------------------------------
application.yml:
spring:
application:
name: zuul-service
##配置使用ribbon-retry,必须在此配置开启
cloud:
loadbalancer:
retry:
enabled: true
server:
context-path: /
port: 5001
eureka:
client:
service-url:
defaultZone: http://eureka1:8001/eureka
##zuul配置
zuul:
routes:
##1.api-order即配置名称可以自定义,尽量遵循见名知意原则
##2.path即访问路径可以自定义,尽量遵循见名知意原则
##3.service-id必须是要访问服务的spring.application.name=order-service
api-order:
path: /oreder-service/**
service-id: oreder-service
api-user:
path: /user-service/**
service-id: user-service
##zuul集成了Ribbon和Hystrix,可以在此处进行全局配置
##全局请求性的配置
##设置全局断路器的超时时间为10秒(yml可以使用此形式进行配置,注意最后的为:号而非=号)
##hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 10000
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 10000
##在ribbon前加上user-service:即配置局部只针对user-service
ribbon:
ConnectTimeout: 5000
##读超时设置相对小一点,否则会有WARN提示
#ReadTimeout: 10000
ReadTimeout: 3000
OkToRetryOnAllOperations: true
MaxAutoRetriesNextServer: 1
MaxAutoRetries: 2
##全局代理配置
feign:
##启用断路器
hystrix:
enabled: true
compression: ##请求压缩配置
request:
min-request-size: 2048
mime-types:
- text/html,application/xml,application.json
response:
enabled:true
------------------------------------------------------------------------------------------------------
Application:
package com.cc.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@EnableZuulProxy //启用网关
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
------------------------------------------------------------------------------------------------------
MyZuulFilter:
package com.cc.springcloud;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
@Component //必须加入此注解,让spring容器可识别
public class MyZuulFilter extends ZuulFilter{
/**
* shouldFilter则表示该filter是否需要执行
* true表示请求走此过滤器,开启
* false表示请求不在此过滤器,关闭
*/
@Override
public boolean shouldFilter() {
// TODO Auto-generated method stub
//return false;
return true;
}
/**
* filterOrder表示执行的优先级,值越小表示优先级越高
* zuul-filer可以写多个,用此方法来标示执行的优先级
*/
@Override
public int filterOrder() {
// TODO Auto-generated method stub
return 0;
}
/**filterType表示filter执行的时机
* pre:在请求被路由之前调用
* routing: 在请求被路由之中调用
* post: 在请求被路由之后调用
* error: 处理请求发生错误时调用
*/
@Override
public String filterType() {
// TODO Auto-generated method stub
return "pre";
}
/**
* 真正执行Filter逻辑的方法
*/
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
//可以通过com.netflix.zuul.context.RequestContext直接获取HttpServletRequest
HttpServletRequest request = ctx.getRequest();
System.out.println("----------uri:------------"+request.getRequestURI());
return ctx;
}
}
------------------------------------------------------------------------------------------------------
启动测试:
注意:控制台输出此警告在此说明:Hystrix断路器设置的超时时间小于Ribbon设置的综合时间用时总和,有可以造成重试机制失效,要尽量将Hystrix断路器的超时时间设置大于Ribbon的综合用时;
WARN 4540 --- [ XNIO-2 task-1] o.s.c.n.z.f.r.s.AbstractRibbonCommand : The Hystrix timeout of 10000ms for the command order-service is set lower than the combination of the Ribbon read and connect timeout, 48000ms.
警告中提示:Ribbon设置的综合用时为48000毫秒,而Hystrix的超时未10000毫秒;
图解Ribbon综合用时: