前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >深入Java微服务之网关系列2:常见Java网关实现方案对比

深入Java微服务之网关系列2:常见Java网关实现方案对比

作者头像
程序员黄小斜
发布于 2022-02-13 07:36:47
发布于 2022-02-13 07:36:47
1.4K0
举报

 什么是服务网关

前文我们已经了解了构建微服务的基础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

代码语言:txt
复制
@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依赖

代码语言:txt
复制
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            spring-cloud-starter-gateway
            <version>2.0.4.RELEASE</version>
        </dependency>
复制代码

在application.yml中添加gateway路由

代码语言:txt
复制
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原理

[format,png]

上图是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包中我们简单看一下:

[format,png]

动态路由

gateway配置路由主要有两种方式,1.用yml配置文件,2.写在代码里。而无论是 yml,还是代码配置,启动网关后将无法修改路由配置,如有新服务要上线,则需要先把网关下线,修改 yml 配置后,再重启网关。这种方式如果在网关上没有优雅停机就会出现服务间断,这无疑是不能被接受的。gateway网关启动时,路由信息默认会加载内存中,路由信息被封装到 RouteDefinition 对象中,配置多个RouteDefinition组成gateway的路由系统,RouteDefinition中的属性与上面代码配置的属性一一对应:

[format,png]

那么就需要我们的动态路由来解决这个问题了。Spring Cloud Gateway 提供了 Endpoint 端点,暴露路由信息,有获取所有路由、刷新路由、查看单个路由、删除路由等方法,具体实现类org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint ,想访问端点中的方法需要添加 spring-boot-starter-actuator 注解,并在配置文件中暴露所有端点。编写动态路由实现类,需实现ApplicationEventPublisherAware接口。

代码语言:txt
复制
/**
 * 动态路由服务
 */
@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接口,通过这些接口实现动态路由功能.

代码语言:txt
复制
@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

就不过多举例了,使用起来也比较简单,我们着重看一下如何自定义过滤器:

  1. 全局过滤器:全局过滤器,对所有的路由都有效,所有不用在配置文件中配置,主要实现了GlobalFilter 和 Ordered接口,并将过滤器注册到spring 容器
代码语言:txt
复制
@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;
    }
}
复制代码
  1. 局部过滤器:需要在配置文件中配置,如果配置,则该过滤器才会生效。主要实现GatewayFilter, Ordered接口,并通过AbstractGatewayFilterFactory的子类注册到spring容器中,当然也可以直接继承AbstractGatewayFilterFactory,在里面写过滤器逻辑,还可以从配置文件中读取外部数据。
代码语言:txt
复制
@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整合进行限流,这个后期我们单独进行讲解。

作者:我是大明哥

链接:https://juejin.cn/post/6923100060913926157

来源:稀土掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
openpyxl:Python的Excel操作库
luckpunk
2023/09/27
7430
openpyxl:Python的Excel操作库
Python自动化办公-处理Excel文档
Python自动化办公-处理word文档,这次分享python处理excel的方法
用户9925864
2022/07/27
4880
Python自动化办公-处理Excel文档
Python3 读取和写入excel xlsx文件 使用openpyxl
•win32com:不仅仅是excel,可以处理office;不过它相当于是 windows COM 的封装,新手使用起来略有些痛苦。
用户7886150
2020/12/23
3.2K0
openpyxl的简单入门
openpyxl模块只支持xlsx/xlsm/xltx/xltm格式,不支持xls格式。
JQ实验室
2022/07/03
1.1K0
Python之Excel 优雅操作手法 精选
一直想写这一篇,却又一直被事情拖着。 我带的一个项目团队正好进行到这一块儿了,正好,将这事儿办了,以后要用的时候也不用到处找。 半点不求人。
看、未来
2021/09/18
5710
Python Linux自动化运维 -- Openpyxl Excel处理
python模块openpyxl pip install openpyxl 使用load_workbook函数读取一个已有的excel文件。 使用Workbook的get_sheet_by_name方法获取Worksheet对象。 使用Worksheet的cell方法获取Cell对象。 类 Workbook Workbook对应Excel工作簿 Workbook对象的部分属性: active:获取活跃的Worksheet; read_only:是否以read_only模式打开excel文件; encod
用户3013098
2022/06/01
2K0
Python Linux自动化运维 -- Openpyxl Excel处理
Python自动化:Python操作Excel的多种方式Pandas+openpyxl+xlrd
使用pandas操作Excel文件主要涉及读取(read_excel)和写入(to_excel)两个主要操作。
小白的大数据之旅
2024/11/20
5550
openpyxl
openpyxl用来操作xlsx文件(不支持xls文件) 以下用wb代表WorkBook类,ws代表WorkSheet类,cell代表单元格。
TomatoCool
2023/07/30
3420
Excel自动化办公
安装 pip install openpyxl==3.0.7 基本操作 import openpyxl print(openpyxl.__version__) # 用openpyxl读取excel表格 wb = openpyxl.load_workbook('信息表.xlsx') print(wb) # 获取工作蒲sheet表名称 sheet1 = wb.sheetnames print("sheet表名称:\n", sheet1) # 获取指定sheet对象 sheet = wb['基本信息'] pr
shaoshaossm
2022/12/26
4460
'python之excel读写报表统计入门'
随着python版本升级, 版本在2.7以上的,在安装Python的时候,已经自动安装好了pip.pip是下载相关依赖的引擎,每个人的理解不一样.相当于java中的mvn,也相当于node中的npm,可以使用pip install 模块名 的方式下载所需的依赖.
java攻城狮
2021/02/26
9990
'python之excel读写报表统计入门'
python3 openpyxl操作excel
在日常工作中,避免不了需要操作excel文件的情况,如果还带有需要对excel的内容进行格式设定、合并单元格等需求,那么可以使用openxl来解决处理。
Devops海洋的渔夫
2019/10/10
2.8K0
python3 openpyxl操作excel
python3操作Excel (一)
excel表中有图像,需要安装pillow库。 pip install Pillow
py3study
2020/01/06
9590
超超长篇 - 手把手带你用python玩转Excel
gitee:https://gitee.com/xiaozai-van-liu/mwj_utils
梦无矶小仔
2024/06/18
9360
超超长篇 - 手把手带你用python玩转Excel
python3编程基础:操作excel(
python中操作excel的模块有很多,比如xlrd,xlwt,openpyxl,xlutils等。前两个是一套,一个读一个写。注意:xlwt模块,只能支持到excel2003,也就是扩展名为.xls的excel;xlrd模块可以支持读取07版本,也就是.xlsx扩展名的excel。每个模块都有一些优缺点,本文以openpyxl模块为例来进行介绍。
py3study
2020/01/13
8070
Python操作Excel文件(2)
文章背景:Excel是Window环境下流行的、强大的电子表格应用。openpyxl模块让Python程序能够读取和修改Excel电子表格文件。下面介绍如何通过Python操作Excel文件。
Exploring
2022/09/20
3450
Python操作Excel文件(2)
Python 自动化指南(繁琐工作自动化)第二版:十三、使用 EXCEL 电子表格
Excel 是一个流行且功能强大的 Windows 电子表格应用。openpyxl模块允许您的 Python 程序读取和修改 Excel 电子表格文件。例如,您可能有从一个电子表格中复制某些数据并粘贴到另一个电子表格中的枯燥任务。或者,您可能必须遍历数千行,然后只挑选出其中的一小部分,根据某些标准进行小的编辑。或者你可能不得不查看数百份部门预算的电子表格,寻找任何赤字。这些正是 Python 可以为您完成的那种枯燥、无需动脑的电子表格任务。
ApacheCN_飞龙
2023/04/04
18.4K0
Python 自动化指南(繁琐工作自动化)第二版:十三、使用 EXCEL 电子表格
python openpyxl 常用功能
1. 安装 pip install openpyxl 1 2. 打开文件 ① 创建 from openpyxl import Workbook # 实例化 wb = Workbook() # 激活 worksheet ws = wb.active 12345 ② 打开已有 >>> from openpyxl import load_workbook >>> wb2 = load_workbook('文件名称.xlsx') 12 3. 储存数据 # 方式一:数据可以直接分配到单元格中(可以输入公式)
用户5760343
2022/05/13
7520
Python | 使用Python操作Excel文档(一)
openpyxl操作Excel的第三方库,作者是Eric Gazoni, Charlie Clark。您也可以访问openpyxl的官方网站通过官方手册进行学习。同时附上官方网站的地址:
LogicPanda
2019/07/30
2.4K0
常用模块 - openpyxl模块
首先介绍下Excel的一些基本概念,Workbook相当于是一个文件,WorkSheet就是文件里面的每个具体的表,比如新建Excel文件里面的“Sheet1”,一个Workbook里面有一个或多个WorkSheet。
py3study
2020/01/17
1.1K0
python openpyxl
from openpyxl import Workbook wb = Workbook() #创建文件对象
用户5760343
2022/05/13
8230
相关推荐
openpyxl:Python的Excel操作库
更多 >
目录
  •  什么是服务网关
  • 为什么要使用网关
    • 统一鉴权
    • 日志记录
    • 请求分发和过滤
    • 灰度发布
  • 常用网关解决方案
  • 环境准备
  • spring cloud gateway原理
  • 路由规则
  • 动态路由
  • 过滤器
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文