(六)Spring Cloud 网关Zuul
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网关搭建并验证完毕

浙公网安备 33010602011771号