分布式-Zuul 动态路由
作用:隐藏服务,统一暴露接口,且可以让访问路径统一做验证,如只放行携带Token的路径
本文接上文(分布式-Ribbon 负载均衡)
配置:
新建zuul-server项目,引入依赖
<!-- Zuul -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<!-- feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置文件:
server.port=10010
spring.application.name=zuul-server
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
#健康检查(需要spring-boot-starter-actuator依赖)
eureka.client.healthcheck.enabled=true
# 续约更新时间间隔(默认30秒)
eureka.instance.lease-renewal-interval-in-seconds=10
# 续约到期时间(默认90秒)
eureka.instance.lease-expiration-duration-in-seconds=10
#zuul代理配置 zuul.routes.服务名.path,服务名要与注册的一致
#应用名映射
zuul.routes.service-a.path=/service-a/**
zuul.routes.service-a.service-id=service-a
过滤器:
/**
* Zuul过滤器,实现了路由检查
*/
public class AccessFilter extends ZuulFilter {
/**
* 通过int值来定义过滤器的执行顺序
*/
@Override
public int filterOrder() {
// PreDecoration之前运行
return PRE_DECORATION_FILTER_ORDER - 1;
}
/**
* 过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型:
* public static final String ERROR_TYPE = "error";
* public static final String POST_TYPE = "post";
* public static final String PRE_TYPE = "pre";
* public static final String ROUTE_TYPE = "route";
*/
@Override
public String filterType() {
return PRE_TYPE;
}
/**
* 过滤器的具体逻辑
*/
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
System.out.println(String.format("%s AccessFilter request to %s", request.getMethod(),request.getRequestURL().toString()));
String accessToken = request.getParameter("accessToken");
//有权限令牌
if (!StringUtils.isEmpty(accessToken)) {
ctx.setSendZuulResponse(true);
ctx.setResponseStatusCode(200);
//可以设置一些值
ctx.set("isSuccess", true);
return null;
} else {
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
ctx.setResponseBody("{\"result\":\"accessToken is not correct!\"}");
//可以设置一些值
ctx.set("isSuccess", false);
return null;
}
}
/**
* 返回一个boolean类型来判断该过滤器是否要执行
*/
@Override
public boolean shouldFilter() {
return true;
}
}
启动类:
@EnableZuulProxy
@EnableEurekaClient
@EnableFeignClients
@SpringBootApplication
public class ZuulServerApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulServerApplication.class, args);
}
@Bean
public AccessFilter accessFilter() {
return new AccessFilter();
}
}
测试:
访问当前项目的路径http://127.0.0.1:10010/service-a/ribbon (service-a为配置的服务A的路径,这里不带accessToken)
访问当前项目的路径http://127.0.0.1:10010/service-a/ribbon?accessToken=12306
顺带一提:
过滤器中可以使用令牌桶限流
这里提供一个简单的令牌桶:
/**
* 简单的令牌桶限流
*/
public class RateLimiter {
/**
* 桶的大小
*/
private Integer limit;
/**
* 桶当前的token
*/
private static Integer tokens = 0;
/**
* 构造参数
*/
public RateLimiter(Integer limit, Integer speed){
//初始化桶的大小,且桶一开始是满的
this.limit = limit;
tokens = this.limit;
//任务线程:每秒新增speed个令牌
new Thread(() ->{
while (true){
try {
Thread.sleep(1000L);
int newTokens = tokens + speed;
if(newTokens > limit){
tokens = limit;
}else{
tokens = newTokens;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
/**
* 根据令牌数判断是否允许执行,需要加锁
*/
public synchronized boolean execute() {
if (tokens > 0) {
tokens = tokens - 1;
return true;
}
return false;
}
public static void main(String[] args) {
//令牌桶限流:峰值每秒可以处理10个请求,正常每秒可以处理3个请求
RateLimiter rateLimiter = new RateLimiter(10, 3);
//模拟请求
while (true){
System.out.println("start");
//在控制台输入一个值按回车,相对于发起一次请求
Scanner scanner = new Scanner(System.in);
scanner.next();
//令牌桶返回true或者false
if(rateLimiter.execute()){
System.out.println("允许访问");
}else{
System.err.println("禁止访问");
}
}
}
}