Zuul作为网关,在Spring Cloud中 常可以作为以下的作用使用
1. 和eureka-client,Ribbon,Feign结合可以实现智能路由和负载均衡的功能
2. 将所有服务的API统一聚合,外界调用API时由网关统一对外暴露,能起到保护API接口的作用
3. 网关可以做统一的身份和权限验证
下面我们将对网关Zuul进行学习,主要有路由,负载均衡,熔断器,过滤器等,设计到的模块有
eureka-server 7001
eureka-client 7002 7003
eureka-ribbon-client 7004
eureka-zuul-client 8000
其中 eureka-server, eureka-client, eureka-ribbion-client 和我们前几章的module一样,为了加强记忆,我们再来做一次完整的配置
1. 新建maven项目eurekazuul
删掉src目录,在pom中添加
<packaging>pom</packaging>
2. 配置eureka-server
2.1 pom.xml
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-sleuth</artifactId> </dependency>
2.2 启动类
package com.devin.eurekaserver; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @EnableEurekaServer @SpringBootApplication public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } }
2.3 权限配置类
package com.devin.eurekaserver.config; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { //关闭csrf super.configure(http); //开启认证 http.csrf().disable(); } }
2.4 application.yml 配置文件
eureka-server的启动端口 7001
server:
port: 7001 #启动端口
spring:
#应用名称
application:
name: eureka-server
#安全配置
security:
basic:
enabled: true
user:
name: dev
password: 123456
#eureka配置
eureka:
server:
#设置扫描失效服务的间隔时间
eviction-interval-timer-in-ms: 20000
enable-self-preservation: true
instance:
hostname: localhost
leaseRenewalIntervalInSeconds: 10
health-check-url-path: /actuator/health
client:
register-with-eureka: false #false:不作为一个客户端注册到注册中心
fetch-registry: false #为true时,可以启动,但报异常:Cannot execute request on any known server
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
# health endpoint是否必须显示全部细节。默认情况下, /actuator/health 是公开的,并且不显示细节。
# 设置actuator开关
management:
security:
enabled: false
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: ALWAYS
3. eureka-client配置
3.1 pom.xml
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.1.8.RELEASE</version> </dependency>
3.2 启动类
package com.devin.eurekaclient; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @EnableEurekaClient @SpringBootApplication public class EurekaClientApplication { public static void main(String[] args) { SpringApplication.run(EurekaClientApplication.class, args); } }
3.3 controller类
package com.devin.eurekaclient.controller; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * @author Devin Zhang * @className HelloController * @description TODO * @date 2020/3/31 10:26 */ @RestController public class HelloController { @Value("${server.port}") private String port; @GetMapping("/sayHello") public String sayHello(String name) { return "hello " + name + ", Warmly welcome from port:" + port; } }
3.4 application.yml 配置类
后续eureka-client将启动2个实例,7002 , 7003
eureka:
auth:
user: dev
password: 123456
client:
serviceUrl:
defaultZone: http://${eureka.auth.user}:${eureka.auth.password}@localhost:7001/eureka/
instance:
#使用IP进行注册
prefer-ip-address: true
#配置实例的注册ID
instance-id: ${spring.cloud.client.ip-address}:${server.port}
#心跳时间,即服务续约间隔时间(缺省为30s)
lease-renewal-interval-in-seconds: 5
#发呆时间,即服务续约到期时间(缺省为90s)
lease-expiration-duration-in-seconds: 10
health-check-url-path: /actuator/health
server:
port: 7002
spring:
application:
name: eureka-client
4. eureka-ribbon-client
新建该项目主要用于测试zuul网关路由到eureka-ribbon-client
4.1 pom.xml
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.1.8.RELEASE</version> </dependency>
4.2 启动类
package com.devin.eurekaribbonclient; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @EnableEurekaClient @SpringBootApplication public class EurekaRibbonClientApplication { public static void main(String[] args) { SpringApplication.run(EurekaRibbonClientApplication.class, args); } }
4.3 RibbonConfig
package com.devin.eurekaribbonclient.config; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; /** * @author Devin Zhang * @className RibbonConfig * @description TODO * @date 2020/3/31 10:48 */ @Configuration public class RibbonConfig { @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } }
4.4 service 通过RestTemplate去负载均衡调用eureka-client
package com.devin.eurekaribbonclient.service; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; /** * @author Devin Zhang * @className HelloService * @description TODO * @date 2020/3/31 10:50 */ @Service public class HelloService { @Resource private RestTemplate restTemplate; public String sayHello(String name) { return restTemplate.getForObject("http://eureka-client/sayHello?name=" + name, String.class); } }
4.5 controller
package com.devin.eurekaribbonclient.controller; import com.devin.eurekaribbonclient.service.HelloService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; /** * @author Devin Zhang * @className RibbonHelloController * @description TODO * @date 2020/3/31 10:50 */ @RestController public class RibbonHelloController { @Resource private HelloService helloService; @GetMapping("/sayHelloFromRibbon") public String sayHelloFromRibbon(String name) { return helloService.sayHello(name); } }
4.6 application.yml配置类
eureka-ribbon-client的穷端口为7004
eureka:
auth:
user: dev
password: 123456
client:
serviceUrl:
defaultZone: http://${eureka.auth.user}:${eureka.auth.password}@localhost:7001/eureka/
instance:
#使用IP进行注册
prefer-ip-address: true
#配置实例的注册ID
instance-id: ${spring.cloud.client.ip-address}:${server.port}
#心跳时间,即服务续约间隔时间(缺省为30s)
lease-renewal-interval-in-seconds: 5
#发呆时间,即服务续约到期时间(缺省为90s)
lease-expiration-duration-in-seconds: 10
health-check-url-path: /actuator/health
server:
port: 7004
spring:
application:
name: eureka-ribbon-client
5. eureka-zuul-client
在eureka-zuul-client中定义了 熔断器,过滤器,以及路由的配置
5.1 pom.yml
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.1.8.RELEASE</version> </dependency>
5.2 启动类
package com.devin.eurekazuulclient; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; @EnableEurekaClient @EnableZuulProxy @SpringBootApplication public class EurekaZuulClientApplication { public static void main(String[] args) { SpringApplication.run(EurekaZuulClientApplication.class, args); } }
5.3 定义熔断器处理类,当通过zuul访问的服务不可用时,就进入熔断器方法
package com.devin.eurekazuulclient.service; import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.client.ClientHttpResponse; import org.springframework.stereotype.Component; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; /** * @author Devin Zhang * @className BaseZuulFallbackProvider * @description TODO * @date 2020/3/31 11:05 */ @Component public class BaseZuulFallbackProvider implements FallbackProvider { @Override public String getRoute() { //返回的为服务的名称,如果所有client都走这个熔断器的话则返回* return "*"; } @Override public ClientHttpResponse fallbackResponse(String route, Throwable cause) { 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 system is busy now ,pls try later ,response by zuul hystrix".getBytes()); } @Override public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); return headers; } }; } }
5.4 定义filter,用于统一的权限校验等
比如我们做简单的token不能为空校验
package com.devin.eurekazuulclient.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE; /** * @author Devin Zhang * @className ZuulBaseFilter * @description TODO * @date 2020/3/31 11:18 */ @Component public class ZuulBaseFilter extends ZuulFilter { @Override public String filterType() { return PRE_TYPE; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { RequestContext currentContext = RequestContext.getCurrentContext(); HttpServletRequest request = currentContext.getRequest(); String token = request.getParameter("token"); if (token == null) { try { currentContext.setSendZuulResponse(false); currentContext.setResponseStatusCode(301); currentContext.getResponse().getWriter().write("token is empty!"); } catch (Exception e) { e.printStackTrace(); } } return null; } }
5.5 application.yml配置路由映射
/zuulapi/ 的请求将被路由到eureka-client 服务
/ribbonapi/ 的请求将被路由到 eureka-ribbon-client 服务
eureka:
auth:
user: dev
password: 123456
client:
serviceUrl:
defaultZone: http://${eureka.auth.user}:${eureka.auth.password}@localhost:7001/eureka/
instance:
#使用IP进行注册
prefer-ip-address: true
#配置实例的注册ID
instance-id: ${spring.cloud.client.ip-address}:${server.port}
#心跳时间,即服务续约间隔时间(缺省为30s)
lease-renewal-interval-in-seconds: 5
#发呆时间,即服务续约到期时间(缺省为90s)
lease-expiration-duration-in-seconds: 10
health-check-url-path: /actuator/health
server:
port: 8000
spring:
application:
name: zuul-client
zuul:
routes:
zuulapi:
path: /zuulapi/**
serviceId: eureka-client
ribbonapi:
path: /ribbonapi/**
serviceId: eureka-ribbon-client
6. 测试
启动eureka-server 7001
启动eureka-client 7002 7003
java -jar eureka-client-0.0.1-SNAPSHOT.jar --server.port=7002
java -jar eureka-client-0.0.1-SNAPSHOT.jar --server.port=7003
启动eureka-ribbon-client 7004
启动eureka-zuul-client 8000
启动后的注册信息如下 http://localhost:7001/
6.1 路由转发校验
分别访问
http://localhost:8000/zuulapi/sayHello?name=devin&token=123
http://localhost:8000/ribbonapi/sayHelloFromRibbon?name=devin&token=123
可以看到已经路由到eureka-client 和 eureka-ribbon-client的接口
6.2 token验证
访问 http://localhost:8000/zuulapi/sayHello?name=devin 可以看到会提示token为空
访问 http://localhost:8000/zuulapi/sayHello?name=devin&token=123 可以看到访问正常,
说明请求已经被转发到eureka-client同时过滤器校验也已经通过,并且在7002和7003之间进行切换
从访问结果可以看出,zuul自动在eureka-client之间进行了负载均衡
同样访问 http://localhost:8000/ribbonapi/sayHelloFromRibbon?name=devin&token=123 也能得到同样的结果
6.3 zuul熔断器验证
分别停掉两台eureka-client,然后分别访问如下地址,可以看到zuul的熔断器已经生效
http://localhost:8000/zuulapi/sayHello?name=devin&token=123
http://localhost:8000/ribbonapi/sayHelloFromRibbon?name=devin&token=123
至此,Zuul网关搭建并验证完毕