【SpringCloud学习笔记】
- 为什么需要网关
- zuul网关
- 搭建zuul网关服务
- 网关过滤器
为什么需要网关
微服务项目一般有多个服务,每个服务的地址都不同,客户端如果直接访问服务,无疑是增加客户端开发难度,项目小还好,如果项目比较大,有几十上百个地址,怎么办?此时就用到了微服务网关
微服务网关是介于客户端,服务之间,所有请求先经过网关,具体调用什么服务由微服务网关去转发需要调用的服务,这样客户端就只需要知道网关的地址即可,做到了统一入口,方便管理
zuul网关
zuul是Netflix开源的微服务网关,它可以跟eureka、Ribbon、Hystrix等组件配合使用,zuul的核心是过滤器,下面是基于zuul1.0版本
现在有一个用户服务user-service提供了一些查询用户信息的基础能力,该服务注册到eureka服务中心
@RestController
@RequestMapping(value = "/user")
public class UserController {
@GetMapping(value = "/getAllUserInfo")
public List<UserInfo> getAllUserInfo(){
List<UserInfo> list = new ArrayList<>();
list.add(new UserInfo(1,"张三",20,"杭州市滨江区"));
list.add(new UserInfo(2,"李四",21,"杭州市西湖区"));
return list;
}
}
配置文件信息
server:
port: 9001
spring:
application:
name: user-service
eureka:
instance:
hostname: 127.0.0.1
client:
register-with-eureka: true #默认值是true,是否将自己注册到注册中心
fetch-registry: false # 默认值是true,是否从注册中心拉取服务
service-url: #注册中信对外暴露的注册中心地址
defaultZone: http://localhost:8761/eureka/
搭建zuul网关服务
- pom.xml引入网关依赖
<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>
- 启动类上添加zuul注解依赖
@EnableZuulProxy
@SpringBootApplication
public class ZuulServerApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulServerApplication.class, args);
}
}
- 路由配置
#路由规则
zuul:
routes:
user-service: #路由id,自定义
path: /user-service/** #请求url的映射路径
url: http://localhost:9001 #映射路径对应的微服务地址,也就是将path路径拼到url型成一个完整的请求路径
通配符 | 含义 | 例如 |
---|---|---|
/? | 匹配任意单个字符 | /user/a、/user/b |
/* | 匹配任意数量字符,不包括子路径 | /user/abc、/user/def |
/** | 匹配任意数量字符和路径 | /user/abc/def、/user/abc/def/bbb |
上面的路由配置有个不好的地方就是每个路由的映射地址都需要手动去维护,那该如何自动获取服务的地址信息,那就是个网关服务接入到注册中心,下面在网关服务配置文件中添加eurake配置中的配置
eureka:
instance:
hostname: 127.0.0.1
client:
register-with-eureka: true #默认值是true,是否将自己注册到注册中心
fetch-registry: true # 默认值是true,是否从注册中心拉取服务
service-url: #注册中信对外暴露的注册中心地址
defaultZone: http://localhost:8761/eureka/
路由规则配置修改为如下
#路由规则
zuul:
routes:
user-service: #路由id,自定义
path: /service/** #请求url的映射路径
serviceId: user-service #服务名称
serviceId就是服务名称也就是注册中心注册的名称,上面的路由规则也是zuul网关默认配置,该配置你就是不写也是一样的,只要保证网关服务注册到注册中心即可
- 路由排除,请求地址排除,服务名排除
ignored-patterns: /**/user/** #请求地址排除所有包含user路径
ignored-services: user-service #服务名称排除,多个使用逗号隔开,如果配置 * 是排除所有
- 路由前缀,也就是请求的地址必须都要加上改前缀才可以访问
prefix: /api
- 网关限流
网关服务需要引入redis和ratelimit依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.marcosbarbero.cloud</groupId>
<artifactId>spring-cloud-zuul-ratelimit</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
项目的配置文件中添加redis服务的地址,端口等信息,另外添加网关限流配置
ratelimit:
repository: redis #存储类型
enabled: true #启用开关
policy-list:
user-service:
- limit: 5 #在一个单位时间窗口的请求数量
#quota: 1000 #在一个单位时间窗口的请求时间限制
refresh-interval: 60 #单位时间窗口
type:
- user #可指定用户粒度
- origin #可指定客户端地址粒度
- url #可指定url粒度
可以配置不同服务的限流规则,还可以配置全局限流
default-policy-list: #全局配置
- limit: 5
quota: 5
refresh-interval: 60
type:
- url
网关过滤器
zuul网关的核心就是过滤器,当我们需要使用网关来实现一些特定的业务处理,如请求日志打印,请求鉴权等。我们只需要继承zuulFilter抽象类实现其中的抽象方法即可,需要我们实现的方法有四个,filterType(),filterOrder(),shouldFilter(),run()
- filterType(): 过滤器的类型,pre,routing,post,error
- filterOrder(): 过滤器的执行顺序,当有多个过滤器时,通过指定返回值来依次执行,数值越小优先级越高
- shouldFilter(): 判断该过滤器是否需要执行,true 执行,false 不执行
- run(): 过滤器具体执行的逻辑
执行流程:
- 正常流程是 pre -> route -> post
- 如果在 pre 过滤器阶段抛出异常,那么流程是: pre -> error -> post
- 如果在 route 过滤阶段抛出异常,那么流程是: pre -> route -> error -> post
- 如果在post 过滤阶段抛出异常,最终流程是:pre -> route -> post -> error
实例:
@Component
public class PreFilter extends ZuulFilter {
@Override
public String filterType() {
return "pre";
}
@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();
System.out.println("请求的url: " + request.getRequestURL());
return null;
}