深入浅出深度了解springboot服务gateway网关
ztj100 2024-10-29 18:18 12 浏览 0 评论
什么是服务网关
前文我们已经了解了构建微服务的基础springboot,同时也能使用springboot构建服务。接下来我们就基于springboot聊一下springcloud。这个springcloud并不是一个特定的技术,它指的是微服务中一个生态体系。比如包括网关,注册中心,配置中心等。今天我们就先了解一下微服务网关,微服务网关有很多种我们这次采用现在主流的spring cloud gateway来讲解说明。 在微服务体系中,每个服务都是一个独立的模块都是一个独立运行的组件,一个完整的微服务体系是由若干个独立的服务组成,每个服务完成自己业务模块功能。比如用户服务提供用户信息相关的服务和功能,支付模块提供支付相关的功能。各个服务之间通过REST API或者RPC(以后讲)进行通信,并且一般我们微服务要做到无状态的通信。 我们实现微服务之后在一些方面也会带来不方便的地方,如果网页端或者app端需要请求修改送货地址,还有购物之后要付款在这个场景下:
如上图会出现一些问题:
- 客户端要发起多次请求,请求不同域名对应的服务,增加了通信成本以及对客户端代码的维护增加了复杂性。
- 服务验证会在每个服务里面单独做,如果每个服务验证鉴权逻辑不同就会导致客户端反复验证。
- 另外如果各个服务采用的协议不同那么对于客户端来讲那就是灾难性的。
基于上面所以我们就需要一个中间层,让客户端去请求中间层,至于需要请求那个服务由中间件去请求,最后将结果汇总返回给客户端,这个中间层就是网关。
为什么要使用网关
使用网关有几个作用:
统一鉴权
一般我们在网关上进行鉴权有两种:1,是对于请求的客户端身份的认证。2,访问权限控制就是当确认用户身份之后判断是否有某个资源的访问权限。 曾经我们在单体应用中,客户端请求验证身份和对于资源权限的约束比较简单,通过请求的session就可以获取对应的用户以及权限信息,但是在微服务架构下,所有的服务都被拆成单个微服务而且还是集群部署这种情况就会变得复杂,因为如果还是使用session的话在分布式情况每次请求不一定会落在同一台机器上,这样就会导致session无效。就需要我们进行额外的工作保证集群中的session是一致的。所以我们在网关层进行统一的处理认证:
日志记录
当客户端请求进来之后我们需要记录当前请求的时间依赖来源地址,ip等信息,这样我们就可以统一的在网关层面上进行拦截获取,之后输出到日志文件中通过ELK组件进行输出,记录内容可以多维度多信息统一记录而不需要到具体每个服务中进行分别记录。
请求分发和过滤
对于网关来讲这个请求匹配分发是最重要的功能,我们常见的nginx其实他这个组件就有请求转发和过滤的功能,对于网关来讲可以对请求进行前置和后置的过滤。
- 请求分发:接收客户端的请求,将请求对应到后面的各个微服务上并请求微服务,因为微服务粒度比较细,所以这个网关就可以对各个微服务进行功能性的整合最终给回客户端。
- 过滤:网关会拦截所有请求,相当于spring中AOP一个横向的切面,在这个切面上进行鉴权,限流,认证等操作。
灰度发布
一般公司的互联网产品都是迭代非常快的,基本都是小步快跑。基本是一个周发布一个版本迭代。在这种情况下就会出现风险,比如兼容性,功能完整度,时间比较短会存在bug最终产生事故等问题。这样一般我们发布的时候会将新的功能发布到指定的机器上分过去一小部分流量来观察具体情况。所以网关作为请求的入口就正好可以完成这个功能。
常用网关解决方案
一般我们常用的网关有几种比如:OpenResty、Zuul、Gateway、Kong、Tyk等。我们主要是用spring体系的框架,所以我们本文针对Gateway进行讲解,其它几种网关实现不做重点说明,OpenResty是有nginx+lua集成的web服务器,集成了许多三方库和模块。Zuul其实springcloud前期也是在集成使用,到那时由于他的线程模型策略可能导致的性能问题最终spring选择了自己研发的spring cloud gateway。
环境准备
本文我们使用一个简单的案例来演示一下spring cloud gateway的使用方法,首先我们需要住呢比2个spring boot的应用,具体创建方式请参考我们本专题第二篇文章。
- spring-cloud-gateway-service1 这个是一个微服务
- Spring-cloud-gateway-wangguan 网关微服务
我们根据以前专题创建了2个服务第一个服务我们添加一个controller
@RequestMapping(value = {"/getUser"}, method = RequestMethod.GET)
public String getUser() {
Map<String ,String> user = new HashMap<>();
user.put("name", "张三");
user.put("age", "45");
String s = JSONObject.toJSONString(user);
return s;
}
复制代码
第二个网关服务我们增加pom依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>2.0.4.RELEASE</version>
</dependency>
复制代码
在application.yml中添加gateway路由
spring:
cloud:
gateway:
routes:
- predicates:
- Path =/gateway/** #匹配规则
uri: http://localhost:8099/getUser #服务1的访问地址
filters:
- StripRrefix: 1 #去掉前缀
server:
port: 8077
复制代码
针对上面配置含义说明:
- uri:目标服务地址,可配置uri和lb://应用服务名称
- predicates:匹配条件,根据规则匹配是否请求该路由
- filters: 过滤规则,这个过滤包含前置过滤和后置过滤,
- StripPrefix=1,表示去掉前缀,即在转发目标url的时候去掉’gateway'
这个时候我们启动服务之后发现服务启动日志:Netty started on port(s): 8077.说明我们服务成功了,并且网关依赖的是nettyserver启动几个服务监听。 我们访问:
curl http://localhost:8077/gateway/getUser
在配置正确的情况下将会返回服务返回的结果。
spring cloud gateway原理
上图是gateway官方给出的原理图,可能不太好理解,我们自己画个图辅助理解一下:
如上图有几个概念先说明一下:
- 路由(Route):是网关的组件之一,由id ,uri ,predicate ,filter组成。
- 断言(Predicate):匹配http请求中的内容。如果返回结果是true则就按当前的router进行转发。
- 过滤器(Filter):为请求提供前置和后置的过滤。
当客户端发送请求到网关时,网关会根据一系列的Predicate的匹配结果来决定访问哪个route路由,然后根据过滤器进行请求处理,过滤器可以在请求发送到后端服务之前和之后执行。
路由规则
spring cloud gateway中提供了路由匹配机制,比如我们前文配置的Path=/gateway/** . 意思就是通过Path的属性来匹配URL前缀是/gateway/的请求。其实spring cloud gateway给我们提供了很多规则供我们使用。 每一个Predicate的使用,你可以理解为:当满足这种条件后才会被转发,如果是多个,那就是都满足的情况下被转发。这些Predict的源码在org.springframework.cloud.gateway.handler.predicate包中我们简单看一下:
动态路由
gateway配置路由主要有两种方式,1.用yml配置文件,2.写在代码里。而无论是 yml,还是代码配置,启动网关后将无法修改路由配置,如有新服务要上线,则需要先把网关下线,修改 yml 配置后,再重启网关。这种方式如果在网关上没有优雅停机就会出现服务间断,这无疑是不能被接受的。gateway网关启动时,路由信息默认会加载内存中,路由信息被封装到 RouteDefinition 对象中,配置多个RouteDefinition组成gateway的路由系统,RouteDefinition中的属性与上面代码配置的属性一一对应:
那么就需要我们的动态路由来解决这个问题了。Spring Cloud Gateway 提供了 Endpoint 端点,暴露路由信息,有获取所有路由、刷新路由、查看单个路由、删除路由等方法,具体实现类org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint ,想访问端点中的方法需要添加 spring-boot-starter-actuator 注解,并在配置文件中暴露所有端点。编写动态路由实现类,需实现ApplicationEventPublisherAware接口。
/**
* 动态路由服务
*/
@Service
public class GoRouteServiceImpl implements ApplicationEventPublisherAware {
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
//增加路由
public String add(RouteDefinition definition) {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "success";
}
//更新路由
public String update(RouteDefinition definition) {
try {
delete(definition.getId());
} catch (Exception e) {
return "update fail,not find route routeId: "+definition.getId();
}
try {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "success";
} catch (Exception e) {
return "update route fail";
}
}
//删除路由
public Mono<ResponseEntity<Object>> delete(String id) {
return this.routeDefinitionWriter.delete(Mono.just(id)).then(Mono.defer(() -> {
return Mono.just(ResponseEntity.ok().build());
})).onErrorResume((t) -> {
return t instanceof NotFoundException;
}, (t) -> {
return Mono.just(ResponseEntity.notFound().build());
});
}
}
复制代码
编写 Rest接口,通过这些接口实现动态路由功能.
@RestController
@RequestMapping("/changeRoute")
public class ChangeRouteController {
@Autowired
private GoRouteServiceImpl goRouteServiceImpl;
//增加路由
@PostMapping("/add")
public String add(@RequestBody GatewayRouteDefinition gwdefinition) {
String flag = "fail";
try {
RouteDefinition definition = assembleRouteDefinition(gwdefinition);
flag = this.goRouteService.add(definition);
} catch (Exception e) {
e.printStackTrace();
}
return flag;
}
//删除路由
@DeleteMapping("/routes/{id}")
public Mono<ResponseEntity<Object>> delete(@PathVariable String id) {
try {
return this.goRouteService.delete(id);
}catch (Exception e){
e.printStackTrace();
}
return null;
}
//更新路由
@PostMapping("/update")
public String update(@RequestBody GatewayRouteDefinition gwdefinition) {
RouteDefinition definition = assembleRouteDefinition(gwdefinition);
return this.goRouteService.update(definition);
}
//把传递进来的参数转换成路由对象
private RouteDefinition assembleRouteDefinition(GatewayRouteDefinition gwdefinition) {
RouteDefinition definition = new RouteDefinition();
definition.setId(gwdefinition.getId());
definition.setOrder(gwdefinition.getOrder());
//设置断言
List<PredicateDefinition> pdList=new ArrayList<>();
List<GatewayPredicateDefinition> gatewayPredicateDefinitionList=gwdefinition.getPredicates();
for (GatewayPredicateDefinition gpDefinition: gatewayPredicateDefinitionList) {
PredicateDefinition predicate = new PredicateDefinition();
predicate.setArgs(gpDefinition.getArgs());
predicate.setName(gpDefinition.getName());
pdList.add(predicate);
}
definition.setPredicates(pdList);
//设置过滤器
List<FilterDefinition> filters = new ArrayList();
List<GatewayFilterDefinition> gatewayFilters = gwdefinition.getFilters();
for(GatewayFilterDefinition filterDefinition : gatewayFilters){
FilterDefinition filter = new FilterDefinition();
filter.setName(filterDefinition.getName());
filter.setArgs(filterDefinition.getArgs());
filters.add(filter);
}
definition.setFilters(filters);
URI uri = null;
if(gwdefinition.getUri().startsWith("http")){
uri = UriComponentsBuilder.fromHttpUrl(gwdefinition.getUri()).build().toUri();
}else{
// uri为 lb://consumer-service 时使用下面的方法
uri = URI.create(gwdefinition.getUri());
}
definition.setUri(uri);
return definition;
}
}
复制代码
其实一般我们很少通过API去调用rest服务去增删路由信息,一般我们主流都是通过集成nacos的config功能动态增添路由。与nacos整合我们后面在讲。
过滤器
网关过滤器Filter分为Pre和Post即前置过滤和后置过滤器。分别为在具体请求转发到后端微服务之前执行和将结果返回给客户端之前执行。 内置的GatewayFilter比较多大概有19种,如:
- AddRequestHeader GatewayFilter Factory ,
- AddRequestParameter GatewayFilter Factory ,
- AddResponseHeader GatewayFilter Factory
就不过多举例了,使用起来也比较简单,我们着重看一下如何自定义过滤器:
- 全局过滤器:全局过滤器,对所有的路由都有效,所有不用在配置文件中配置,主要实现了GlobalFilter 和 Ordered接口,并将过滤器注册到spring 容器。
@Service
@Slf4j
public class AllDefineFilter implements GlobalFilter,Ordered{
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("[pre]-Enter AllDefineFilter");
return chain.filter(exchange).then(Mono.fromRunnable(()->{
log.info("[post]-Return Result");
}));
}
@Override
public int getOrder() {
return 0;
}
}
复制代码
- 局部过滤器:需要在配置文件中配置,如果配置,则该过滤器才会生效。主要实现GatewayFilter, Ordered接口,并通过AbstractGatewayFilterFactory的子类注册到spring容器中,当然也可以直接继承AbstractGatewayFilterFactory,在里面写过滤器逻辑,还可以从配置文件中读取外部数据。
@Component
@Slf4j
public class UserDefineGatewayFilter extends AbstractGatewayFilterFactory<UserDefineGatewayFilter.GpConfig>{
public UserDefineGatewayFilter(){
super(GpConfig.class);
}
@Override
public GatewayFilter apply(GpConfig config) {
return ((exchange, chain) -> {
log.info("[Pre] Filter Request,name:"+config.getName());
return chain.filter(exchange).then(Mono.fromRunnable(()->{
log.info("[Post] Response Filter");
}));
});
}
public static class UserConfig{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
复制代码
这块需要有注意的地方:
- 类名必须要统一以GatewayFiterFactory结尾,因为默认情况下过滤器的name会采用该自定义类的前缀。这里的name=UserDefine,也就是在yml中filters中的name值。
- 在apply方法中,同时包含Pre和Post过滤。在then方法中是请求执行结束之后的后置处理。
- UserConfig是一个配置类,该类中只有一个属性name。这个属性可以在ym文件中使用。
- 该类需要装载到Spring IoC容器,此处使用@Component注解实现。
其实整个spring cloud gateway 与spring cloud alibaba整合的很好,可以与nacos整合可以与sentinel整合进行限流,这个后期我们单独进行讲解。
看完三件事??
?如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
- 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
- 关注头条号 『 JAVA后端架构 』,不定期分享原创知识。
- 同时可以期待后续文章ing
- 关注作者后台私信【888】有惊喜相送
相关推荐
- 从IDEA开始,迈进GO语言之门(idea got)
-
前言笔者在学习GO语言编程的时候,GO语言在国内还没有像JAVA/Php/Python那样普及,绕了不少的弯路,要开始入门学习一门编程语言,最好就先从选择一个好的编程语言的开发环境开始,有了这个开发环...
- 基于SpringBoot+MyBatis的私人影院java网上购票jsp源代码Mysql
-
本项目为前几天收费帮学妹做的一个项目,JavaEEJSP项目,在工作环境中基本使用不到,但是很多学校把这个当作编程入门的项目来做,故分享出本项目供初学者参考。一、项目介绍基于SpringBoot...
- 基于springboot的个人服装管理系统java网上商城jsp源代码mysql
-
本项目为前几天收费帮学妹做的一个项目,JavaEEJSP项目,在工作环境中基本使用不到,但是很多学校把这个当作编程入门的项目来做,故分享出本项目供初学者参考。一、项目介绍基于springboot...
- 基于springboot的美食网站Java食品销售jsp源代码Mysql
-
本项目为前几天收费帮学妹做的一个项目,JavaEEJSP项目,在工作环境中基本使用不到,但是很多学校把这个当作编程入门的项目来做,故分享出本项目供初学者参考。一、项目介绍基于springboot...
- 贸易管理进销存springboot云管货管账分析java jsp源代码mysql
-
本项目为前几天收费帮学妹做的一个项目,JavaEEJSP项目,在工作环境中基本使用不到,但是很多学校把这个当作编程入门的项目来做,故分享出本项目供初学者参考。一、项目描述贸易管理进销存spring...
- SpringBoot+VUE员工信息管理系统Java人员管理jsp源代码Mysql
-
本项目为前几天收费帮学妹做的一个项目,JavaEEJSP项目,在工作环境中基本使用不到,但是很多学校把这个当作编程入门的项目来做,故分享出本项目供初学者参考。一、项目介绍SpringBoot+V...
- 目前见过最牛的一个SpringBoot商城项目(附源码)还有人没用过吗
-
帮粉丝找了一个基于SpringBoot的天猫商城项目,快速部署运行,所用技术:MySQL,Druid,Log4j2,Maven,Echarts,Bootstrap...免费给大家分享出来前台演示...
- SpringBoot+Mysql实现的手机商城附带源码演示导入视频
-
今天为大家带来的是基于SpringBoot+JPA+Thymeleaf框架的手机商城管理系统,商城系统分为前台和后台、前台用的是Bootstrap框架后台用的是SpringBoot+JPA都是现在主...
- 全网首发!马士兵内部共享—1658页《Java面试突击核心讲》
-
又是一年一度的“金九银十”秋招大热门,为助力广大程序员朋友“面试造火箭”,小编今天给大家分享的便是这份马士兵内部的面试神技——1658页《Java面试突击核心讲》!...
- SpringBoot数据库操作的应用(springboot与数据库交互)
-
1.JDBC+HikariDataSource...
- SpringBoot 整合 Flink 实时同步 MySQL
-
1、需求在Flink发布SpringBoot打包的jar包能够实时同步MySQL表,做到原表进行新增、修改、删除的时候目标表都能对应同步。...
- SpringBoot + Mybatis + Shiro + mysql + redis智能平台源码分享
-
后端技术栈基于SpringBoot+Mybatis+Shiro+mysql+redis构建的智慧云智能教育平台基于数据驱动视图的理念封装element-ui,即使没有vue的使...
- Springboot+Mysql舞蹈课程在线预约系统源码附带视频运行教程
-
今天发布的是由【猿来入此】的优秀学员独立做的一个基于springboot脚手架的Springboot+Mysql舞蹈课程在线预约系统,系统项目源代码在【猿来入此】获取!https://www.yuan...
- SpringBoot+Mysql在线众筹系统源码+讲解视频+开发文档(参考论文
-
今天发布的是由【猿来入此】的优秀学员独立做的一个基于springboot脚手架的在线众筹管理系统,主要实现了普通用户在线参与众筹基本操作流程的全部功能,系统分普通用户、超级管理员等角色,除基础脚手架外...
- Docker一键部署 SpringBoot 应用的方法,贼快贼好用
-
这两天发现个Gradle插件,支持一键打包、推送Docker镜像。今天我们来讲讲这个插件,希望对大家有所帮助!GradleDockerPlugin简介...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- 从IDEA开始,迈进GO语言之门(idea got)
- 基于SpringBoot+MyBatis的私人影院java网上购票jsp源代码Mysql
- 基于springboot的个人服装管理系统java网上商城jsp源代码mysql
- 基于springboot的美食网站Java食品销售jsp源代码Mysql
- 贸易管理进销存springboot云管货管账分析java jsp源代码mysql
- SpringBoot+VUE员工信息管理系统Java人员管理jsp源代码Mysql
- 目前见过最牛的一个SpringBoot商城项目(附源码)还有人没用过吗
- SpringBoot+Mysql实现的手机商城附带源码演示导入视频
- 全网首发!马士兵内部共享—1658页《Java面试突击核心讲》
- SpringBoot数据库操作的应用(springboot与数据库交互)
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- node卸载 (33)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- exceptionininitializererror (33)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)