目录:
1. nginx网关和zuul网关的区别
2. zuul项目搭建及演示
3. 配置特殊路由规则
四. 动态的路由机制
五. zuul的全局拦截
六. 在zuul里面实现限流
七. 在zuul里面实现熔断器
一. nginx网关和zuul网关的区别
1. 相同点
都是网关. 用户访问, 都是先访问网关. 不能直接访问里面的服务. 他们都能做路由, 负债均衡, 以及限流
2. 异同点
nginx在做路由,负载均衡,限流之前, 都有修改nginx.conf的配置文件. 把需要负载均衡,限流,路由的规则定义在nginx.conf配置文件中
eg: 使用nginx做tomcat负载均衡, 需要修改nginx.conf配置文件
Upsteam www.car.com {
server ip: port,
server ip: port
}
Location / {
proxy_pass http://www.car.com
}
但是zuul不同, zuul是自动负载均衡和路由, zuul和eureka高度集成, 实现自动的路由, 和ribbon结合,实现了负载均衡, zuul也能轻易的实现限流和权限验证
性能相比: nginx的性能比zuul要高. nginx是c写的
二.zuul项目搭建及演示
2.1 新建网关项目--zuul-test-gateway
2.1.1 引入项目依赖
我搭建的是一个全新的项目
在https://start.spring.io/创建一个项目, 项目名称zuul-test-gateway
引入的依赖: web, zuul, nacos-config, nacos-discovery
引入的配置如下:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<!-- 服务之间http调用, 引入feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.1.2 新建bootstrap.yml配置文件
spring:
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
file-extension: yml
namespace: 482c42bd-fba1-4147-a700-5b678d7c0747
group: ZUUL_TEST
2.1.3 修改配置文件application.yml
server:
port: 8080
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
namespace: 482c42bd-fba1-4147-a700-5b678d7c0747
redis:
host: localhost
port: 6379
2.1.4 在启动类引入相关注解
1. 开启zuul网关的注解:@EnableZuulProxy
2. nacos注册发现注解: @EnableDiscoveryClient
3. web框架注解: @RestController
4. 微服务间调用注解feign: @EnableFeignClients
5. 动态更新配置注解: @RefreshScope
package com.lxl.www.gateway;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
@EnableZuulProxy // 开启zuul网关功能
@RefreshScope
public class ZuulTestGatewayApplication {
@Value("${config}")
private String config;
public static void main(String[] args) {
SpringApplication.run(ZuulTestGatewayApplication.class, args);
}
@GetMapping("config")
public String getconfig(){
return this.config;
}
}
2.1.5 启动nacos服务端
我们这里配置中心使用的是nacos, 因此我们需要启动nacos服务.
如何配置nacos,启动nacos, 参考文章:
nacos配置中心
nacos服务中心
启动nacos, 我这里是单机模式启动, 进入到nacos的目录
cd /users/nacos/nacos/bin
./startup.sh -m standalone
2.1.6 启动gateway网关项目
上面的配置文件中已经包含了nacos配置和服务发现的配置内容
2.1.7 在nacos中查看网关项目
1. 网关的配置文件, 如下图所示
2.网关项目启动后, 查看服务列表
2.2 创建另一个服务,用户服务----zuul-test-user
2.2.1 添加依赖
需要添加的依赖有: nacos配置和发现, web,以及feign
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 服务之间http调用, 引入feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2.2.2 添加bootstrap.yml配置文件
spring:
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
file-extension: yml
namespace: 482c42bd-fba1-4147-a700-5b678d7c0747
group: ZUUL_TEST
2.2.3 修改application.yml配置文件
server:
port: ${port:8089}
spring:
application:
name: user
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
namespace: 482c42bd-fba1-4147-a700-5b678d7c0747
2.2.4 启动类引入注解
package com.lxl.www.user;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@EnableDiscoveryClient
@SpringBootApplication
@RefreshScope
public class ZuulTestUserApplication {
@Value("${config}")
private String config;
public static void main(String[] args) {
SpringApplication.run(ZuulTestUserApplication.class, args);
}
@GetMapping("/config")
public String getconfig(){
return config + "2222";
}
}
2.2.5 启动user服务
2.2.6 查看nacos
1. 查看user配置文件
2. 查看项目启动情况
2.3. 启动另一个user服务. 换一个端口
1. 上面我们定义user服务的端口的时候,定义的是动态端口
下面我们构建集群环境,再启动一个user服务.
2. 点击配置管理
3. 点击复制配置
4. 选中要启动的应用, 然后设置端口号
5. 接下来,选中配置,点击启动即可
6. 查看nacos中服务的启动情况
我们看到user的实例数,现在是2台
至此项目搭建就完成了!!!
2.4. 下面来感受一下网关
现在有两个服务, 一个是网关服务,端口是8080; 一个是user服务, 端口分别是8089和8088.
我们通过网关服务区请求user, 看看可不可以
我在postman中输入的是网关的端口localhost:8080, 然后紧跟着服务名+path, 可以正确跳转到user服务上.
其实,我们在配置文件中, 没有做任何配置.
网关是网关服务, 用户是用户服务, 他们都可已单独存在, 单独工作. 但是通过网关的地址却能够跳转到user服务上. 这是zuul自动为我们在服务发现上发现相应的集群中的其他服务.
下面来详细说说网关的应用
三. 配置特殊的路由规则
zuul:
routes:
user-router: # 随便写, 是一个唯一的, 代表一个微服务的路由机制
service-id: user # 该路由机制针对的是哪个微服务
path: /user1/**
我们可以在网关中自定义服务的路由路径. 如上所示
这表示, 所有连接http://localhost:8080/user1/**的连接都会分发到服务名为user的服务上
比如:我们请求路径写成http://localhost:8080/user1/config, 那么也会正确的请求的user服务上
四. 动态的路由机制
五. zuul的全局拦截
zuul是前端访问的唯一入口, 我们可以在zuul实现一个token的拦截验证
1. 定义一个TokenFilter过滤器,这个过滤器extends ZuulFilter
package com.lxl.www.gateway.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
@Component
public class TokenFilter extends ZuulFilter {
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 1;
}
@Override
public boolean shouldFilter() {
return true;
}
/**
* 实现token 拦截验证
* @return
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
// 怎么判断用户的token?
// RequestContext 请求的上下文, 包含所有的请求参数, 他默认和线程绑定
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
// 从请求头中拿出token
String token = request.getHeader("token");
if (!StringUtils.hasText(token)) {
// token 为null
currentContext.setResponseBody("token is null");
currentContext.setResponseStatusCode(401);
// 是否发送路由响应
currentContext.setSendZuulResponse(false);
return null;
}
if (!"123456".equals(token)) {
currentContext.setResponseBody("token is error");
currentContext.setResponseStatusCode(401);
currentContext.setSendZuulResponse(false);
return null;
}
currentContext.setSendZuulResponse(true);
return null;
}
}
这个过滤器extends自ZuulFilter, 后面的案例多了, 我们就发现, 其实zuul网关的本质就是拦截器, zuul的各种功能,也是通过拦截器来实现的
filterType() : 拦截器的类型是前置拦截器.
filterOrder(): 执行顺序是第一个执行.
shouldFilter(): 过滤器执行的条件, 这里是所有的连接都需要过这个拦截器, 所以直接设置为true
run(): 拦截器的核心逻辑. 这里的拦截器逻辑很简单, 就是判断header中是否有一个叫做token的属性, 且其值为123456
2. 启动服务, 查看拦截器效果
当有header, 不启用的时候, 会被拦截, 提示token is null, 并且跳过后面的拦截器, 直接返回
当有token ,但是token的值不是123456的时候, 会说token错误
只有当token符合我们的预期的时候, 才可以放行
这个拦截器是对所有微服务有效的.
六. 在zuul里面实现限流
当有个恶意用户每分钟访问超过一定的次数后, 我们可以对他实行限制, 让他1分钟内只能访问n次
6.1 添加zuul的限流组件
git地址:https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit
进入git查看zuul-ratelimit的使用方法, 上面是源码, 下面是使用方法
6.2 添加限流组件--引入依赖
<!-- 添加限流组件 开始-->
<!-- 1. 添加组件 -->
<dependency>
<groupId>com.marcosbarbero.cloud</groupId>
<artifactId>spring-cloud-zuul-ratelimit</artifactId>
<version>2.4.1.RELEASE</version>
</dependency>
<!-- 2. 添加redis 保存数据-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 添加限流组件 结束-->
我们引入限流组件, 同时引入redis. 用来记录当前用户登录的次数
6.3 在配置文件中增加redis配置.
在本地先启动redis, 端口6379
然后增加redis配置
6.4 增加限流配置
6.4.1 全局限流配置
根据文档, 限流配置有两种,
1. 通用限流配置: 对所有的微服务都生效
2. 特定服务限流配置: 针对某一个微服务设置的限流
文档内容如下:
zuul:
routes:
user-router: # 随便写, 是一个唯一的, 代表一个微服务的路由机制
service-id: user # 该路由机制针对的是哪个微服务
path: /user1/**
ratelimit:
enabled: true
repository: REDIS
behind-proxy: true
add-response-headers: true
default-policy-list: # 全局配置, 改配置对所有的微服务都有效
- limit: 10 #选填--限制的次数
quota: 1000 #选填--时间的显示--刷新窗口时间隔的时间限制
refresh-interval: 60 # 60s
type: #选填--针对哪个服务有效
#- user # 针对用户么
- origin #针对域名么
#- url #针对url么
#- http_method #针对方法么
如上限流的含义是: 对所有微服务都生效的限流策略是: 对某个微服务在60s内,请求次数限制是10次. 或者刷新窗口时间隔实现的限制是1秒. 也就是1秒刷新一次窗口.
配置中的具体含义如上注释
配置好以后, 启动微服务, 在postman中查看 是否起到限流作用
当次数超过10次时候, 就会给出异常提示, too many requests.
6.4.2 特定微服务的限流限制
zuul:
routes:
user-router: # 随便写, 是一个唯一的, 代表一个微服务的路由机制
service-id: user # 该路由机制针对的是哪个微服务
path: /user1/**
ratelimit:
enabled: true
repository: REDIS
behind-proxy: true
add-response-headers: true
default-policy-list: # 全局配置, 改配置对所有的微服务都有效
- limit: 10 #选填--限制的次数
quota: 1000 #选填--时间的显示
refresh-interval: 60 # 60s
type: #选填--针对哪个服务有效
#- user # 针对用户么
- origin #针对域名么
#- url #针对url么
#- http_method #针对方法么
policy-list:
user: #这里设置的是具体的服务id
- limit: 10 #optional 设置限流的次数
quota: 1000 #optional 设置限流的时间--刷新窗口间隔的时间限制
refresh-interval: 60 # 60s为单位限流
type: #optional 针对的限流类型
#- user #针对用户限流么?
- origin #针对域名限流么? 表示的是一个客户端
#- url # 针对url链接限流么?
设置单个微服务的限流策略, 如上所示, 具体含义: 60s内, 限制次数为10次, 窗口刷新间隔时间是1秒.
效果如下, 当每分钟请求次数超过5次的时候, 爆出连接次数过多
6.5 限流的机制
限流器的本质是filter
在限流器通过filter来记录用户的访问次数, 当次数达到一定值, 直接让filter拦截
6.6 限流里面redis的作用
使用redis的原因, 当zuul是一个集群的时候,要在多个redis中共享访问次数
七. 在zuul里面实现熔断器
熔断, 其含义是当路由失败的时候, 执行熔断器.
zuul是自带熔断机制的. 不需要引入任何额外的依赖
我们需要的是, 实现熔断器中的FallbackProvider接口. 定义自己的熔断机制
package com.lxl.www.gateway.fallback;
import org.apache.http.client.methods.HttpHead;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestHeader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* zuul熔断器
*/
@Component
public class ZuulFallbackProvider implements FallbackProvider {
/**
* 要对哪个微服务实现熔断
* @return
*/
@Override
public String getRoute() {
return "user"; // 这里写的是服务的id, *表示任何服务
}
/**
* 在熔断时, 用户执行怎样的响应数据
* @param route
* @param cause
* @return 当熔断被触发以后, 如何响应内容
*/
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
System.out.println("熔断器被触发:" + route);
System.out.println("熔断的原因:" + cause);
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.BAD_REQUEST;
}
@Override
public int getRawStatusCode() throws IOException {
return 401;
}
@Override
public String getStatusText() throws IOException {
return "服务异常!";
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
byte[] body = "server is error, get in the fallback".getBytes();
return new ByteArrayInputStream(body);
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.add("reason", "server is error, get in fallback!");
return headers;
}
};
}
}
getRoute(): 定义对哪个服务启用熔断策略
fallbackResponse(String route, Throwable cause): 当熔断时, 执行怎么样的操作
这里的熔断机制比较简单, 如果熔断了, 那么就打印日志, 并输出server is error, get in fallback!
比如: 我的user微服务挂了, 通过网关请求, 就会进得到如下信息提示.
以上就是zuul在项目中通常使用的场景. demo很简单, 但框架即是如此, 具体可以根据详细的需求增减