基于Netty的高性能API网关设计

news2024/10/6 10:31:54

0. 本文目的

对于网关设计,业界已有很多成熟的解决方案,开箱即用或者稍作自定义都能满足需求。本文主要是通过网关需求了解底层netty的使用,所以重心在netty的实践使用上。

1. 什么是网关

网关(Gateway)又称网间连接器、协议转换器。网关在网络层以上实现网络互连,通俗来讲就是讲两个不同网络环境连接起来的连接器。

2. 为什么要做网关

从网关的定义上可以看出,其最主要的功能就是解耦。随着解耦扩展开来,网关还具有以下特性:

  • 请求路由:对于调用端来讲,只需关注网关的接口即可,无需关注网关背后的服务集群信息。
  • 负载均衡:如果服务端集群有多台服务器,需要考虑到服务器的资源分配,充分压榨服务器性能;
  • 协议转换:业务客户端可能有多种通信协议,对于服务端来讲做多种协议适配复杂度和健壮性都会有影响,所以可以通过网关消化掉协议的差别,保持网关后服务端的统一性和简单性。
  • 安全校验:SSL 加密及证书管理、Session 验证、授权、数据校验,以及对请求源进行恶意攻击的防范,都可以在网关层拦截掉。
  • 流量控制:可以通过网关区分灰度流量和生成流量,对服务做不同版本控制;还可以限制流量和熔断降级。
  • API编排&聚合:可以在网关层对服务调用做编排和聚合,减少客户端和服务端之前的网络交互,提升网络性能。
  • 弹力设计:客户端请求异步、重试、幂等、监视等都可以交给网关控制,让应用服务只关心自己的业务逻辑而不是控制逻辑。

当然,统一的网关应用在满足上述特性后,各业务系统接入后就不用自行重复造轮子,一方面能省去重复网关搭建的服务器资源和人力运维成本,另一方面统一专业人士维护,提升服务研发运维效能。

3. 业界内的网关介绍

从功能上网关分为流量网关和业务网关。流量网关,顾名思义就是控制流量进入集群的网关,流量网关通常只专注于全局的Api管理策略,比如全局流量监控、日志记录、全局限流、黑白名单控制、接入请求到业务系统的负载均衡等,有点类似防火墙。Kong 就是典型的流量网关。

kong是一款基于OpenRetry(Nginx + Lua) 编写的高可用、易扩展的API Gateway,官网地址:Kong Gateway - v3.0.x | Kong Docs,架构图如下:

 与流量网关相对应的就是业务网关,业务网关更接近业务服务器,一个业务网关的功能包括:

(图片来自亿级流量架构网关设计思路,常用网关对比,写得太好了。。_wadfdhsajd的博客-CSDN博客_网关架构设计)

比较常见的业务网关有zuul/zuul2/springcloud-gateway。

名称OpenRestyKongzuulzuul2springcloud gateway
所属公司OpenRestyKongNetfixNetfixApache
架构nginx+lua基于openRestry基于servlet2.5, 使用阻塞架构基于Netty非阻塞基于Netty非阻塞
特征简单易用,但是需要进行的lua开发很多简单易用,api转发通过管理员接口配置,随着规则复杂仍需要开发维护大量lua脚本已过时支持长连接,可以通过配置文件配置集群限流和单服务器filter限流,支持长连接,可通过IP、用户、集群限流,支持Oauth2和普通鉴权
社区成熟度社区成熟社区成熟过时不再用参考资料相对较少,可维护性弱spring系列可扩展强,易配置 可维护性好

 4. 如何设计一个高性能网关

结合网关的功能作用,所以对网关的设计可以简化分为以下几个方面:

  • 高性能,全链路异步请求。异步化方式有两种:Tomcat/Jetty+NIO+servlet3。这种方式通过成熟的servlet容器处理http请求,在servlet3中开启异步化;另一种是netty+NIO。这种方式需要自行处理http的协议,但是能支撑更大的并发量;
  • 业务隔离。不同业务对网关请求的需求不一致,SLA也不尽相同,所以需要做业务上的隔离。有信号隔离、线程池隔离和集群隔离等方式,这里不做赘述;
  • 限流。分为集群限流和单机限流;
  • 熔断降级。
  • 链式过滤。可参考springcloud和zuul2等处理模式。
  • 泛化调用。泛化调用指的是一些通信协议的转换,比方将HTTP转换成Thrift,rpc和http的转换等。
  • 后端治理平台,主要是对已有网关业务的健康监控配置处理。

5. 手撸一个简单的网关

借鉴zuul2的设计模式,

// 首先定义过滤器
public interface RequestFilter {
    void filter(FullHttpRequest request, ChannelHandlerContext ctx);
}

public interface ResponseFilter {
    void filter(FullHttpResponse response);
}

// 简单的过滤器实现
public class MyRequestFilter implements RequestFilter {
    @Override
    public void filter(FullHttpRequest request, ChannelHandlerContext ctx) {
        request.headers().set("branch", "test");
        System.out.printf("=========" + request.headers().get("branch"));
    }
}

public class MyResponseFilter implements ResponseFilter {
    @Override
    public void filter(FullHttpResponse response) {
        response.headers().set("branch","test123");
    }
}

按照netty server端的开发模式,我们处理逻辑应该放在ChannelPipeline中,所以先定义出我们自由的channel处理pipeline。

public class UrlChannelInitializer extends ChannelInitializer<SocketChannel> {
    // 简单的路由规则表转发
    private Map<String, String> urlMap;

    public UrlChannelInitializer(Map<String, String> urlMap) {
        this.urlMap = urlMap;
    }

    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        pipeline.addLast(new HttpServerCodec());
        pipeline.addLast(new HttpObjectAggregator(1024*1024));
        // 添加自定义拦截器
        pipeline.addLast(new MyChannelHandlerAdapter());
        pipeline.addLast(new UrlChannelHandler(urlMap));
    }
}

// request拦截器生效
public class MyChannelHandlerAdapter extends ChannelInboundHandlerAdapter {
    private MyRequestFilter requestFilter = new MyRequestFilter();
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        FullHttpRequest fullHttpRequest = (FullHttpRequest) msg;
        requestFilter.filter(fullHttpRequest, ctx);
        super.channelRead(ctx, msg);
    }
}

// response拦截器生效
public class UrlChannelHandler extends ChannelInboundHandlerAdapter {
    Map<String, String> urlMap;

    MyResponseFilter myResponseFilter ;

    public UrlChannelHandler(Map<String, String> urlMap) {
        this.urlMap = urlMap;
        this.myResponseFilter = new MyResponseFilter();
    }

    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.fireExceptionCaught(cause);
    }

    // 自定义处理器读取。 这里做对应的简单路由转发
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        FullHttpRequest fullHttpRequest = (FullHttpRequest) msg;
        ByteBuf byteBuf = fullHttpRequest.content();
        processHandleRequest(ctx, readByteBuf(byteBuf), fullHttpRequest);
        ctx.fireChannelRead(msg);
    }

    // 这里做协议转换
    private void processHandleRequest(ChannelHandlerContext ctx, String content, FullHttpRequest fullHttpRequest) {
        String url = urlMap.get(fullHttpRequest.getUri());
        FullHttpResponse response = null;
        try {
            String value = HttpClientUtil.doPost(url, JSONObject.parseObject(content));
            response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(value.getBytes("UTF-8")));
            response.headers().set("Content-Type", "application/json");
            response.headers().set("Content-Length", response.content().readableBytes());
            myResponseFilter.filter(response);
        } catch (Exception e) {
            System.out.printf("处理异常" + e.getMessage());
            response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NO_CONTENT);
        }finally {
            if(fullHttpRequest != null){
                if(!HttpUtil.isKeepAlive(fullHttpRequest)){
                    ctx.write(response).addListener(ChannelFutureListener.CLOSE);
                }else {
                    response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderNames.KEEP_ALIVE);
                    ctx.write(response);
                }
            }
        }
    }

    private static String readByteBuf(ByteBuf byteBuf){
        int size = byteBuf.readableBytes();
        byte[] data = new byte[size];
        byteBuf.readBytes(data);
        return new String(data);
    }
}

最后的server端启动监听端口

public class MyNettyServer {
    private int port;
    private Map<String, String> urlMap;

    public MyNettyServer(int port, Map<String, String> urlMap) {
        this.port = port;
        this.urlMap = urlMap;
    }
    // 初始化构造
    public void init(){
        EventLoopGroup bossGroup = new NioEventLoopGroup(2);
        EventLoopGroup workerGroup = new NioEventLoopGroup(16);

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.TCP_NODELAY, true)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .childOption(ChannelOption.SO_REUSEADDR, true)
                    .childOption(ChannelOption.SO_RCVBUF, 21*1024)
                    .childOption(ChannelOption.SO_SNDBUF, 32*1024)
                    .childOption(EpollChannelOption.SO_REUSEPORT, true)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);

            serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.DEBUG))
                    .handler(new UrlChannelInitializer(urlMap));

            Channel channel = serverBootstrap.bind(port).channel();
            System.out.println("开启netty http服务器,监听地址和端口为 http://127.0.0.1:" + port + '/');
            channel.closeFuture().sync();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/28324.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于球向量的粒子群优化(SPSO)算法在无人机路径规划中的实现(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

PyTorch for Audio + Music Processing(2/3/4/5/6/7) :构建数据集和提取音频特征

基于Torchaudio构建数据集 文章目录基于Torchaudio构建数据集前言02 Training a feed forward network03 Making predictions04 Creating a custom dataset05 Extracting Mel spectrograms06 Padding audio files07 Preprocessing data on GPU一、下载数据集文件目录标注格式二…

19 【RTK Query】

19 【RTK Query】 1.目前前端常见的发起 ajax 请求的方式 1、使用原生的ajax请求2、使用jquery封装好的ajax请求3、使用fetch发起请求4、第三方的比如axios请求5、angular中自带的HttpClient 就目前前端框架开发中来说我们在开发vue、react的时候一般都是使用fetch或axios自…

web前端期末大作业【 大学生抗疫感动专题网页设计】HTML+CSS

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

Prometheus 使用cadvisor采集docker容器监控数据

Prometheus采集主机监控参考部署下载&#xff0c;图形生成 系统安装Grafana downloadWindows参考图形生成参考win_exporterLinux参考node_exporterMysql参考Mysql_exporterSQL Server参考SQL exporterRedis 参考Redis_exportercadvisor参考cadvisor snmp_exporter 参考图形展示…

Redis详细教程

【尚硅谷】2021最新版Redis详细教程&#xff08;基于Redis 6.2.x版本&#xff09; 文章目录 一、前言二、NoSQL数据库简介 1.技术发展 1.1 Web1.0时代1.2 Web2.0时代1.3 解决CPU及内存压力1.4 解决IO压力 2.NoSQL数据库 2.1 NoSQL数据库概述2.2 NoSQL适用场景2.3 NoSQL不适用…

小啊呜产品读书笔记001:《邱岳的产品手记-09》第17讲 产品经理如何获得非权力性的影响力 第18讲 产品案例分析:WWFTogether的情怀设计

小啊呜产品读书笔记001&#xff1a;《邱岳的产品手记-09》第17讲 产品经理如何获得非权力性的影响力 & 第18讲 产品案例分析&#xff1a;WWFTogether的情怀设计一、今日阅读计划二、泛读&知识摘录1、第17讲 产品经理如何获得非权力性的影响力&#xff1f;2、第18讲 产品…

Unity 打印安卓apk报错的日志

文章目录环境连接安卓手机附录环境 1.华为手机打开调试模式&#xff1a; 【设置】|【关于手机】|【版本号】点三次。 2.unity3d 环境设置&#xff1a; 【File】|【Building Settings】开启必备选项 &#xff08;其他非必选&#xff0c;最好勾上&#xff09;&#xff1a; D…

Sparse Merkle Tree

1. 引言 前序博客有&#xff1a; Merkle tree及其在区块链等领域的应用Merkle tree proof 2. Merkle tree Merkle tree可看成是对一组数据的密码学承诺&#xff0c;类似&#xff1a; 2.1 Merkle tree包含证明 如需证明A包含在上述树中&#xff0c;仅需要发送A, H(B), H(…

编译原理实验--实验一 词法分析--Python实现

目录 一、实验目的 二、实验内容 三、实验环境 四、实验步骤 五、测试要求 六、实验步骤 1、单词表<列出所识别语言的所有单词及其种别编码>&#xff1b; 2、识别单词的DFA图<可选择1-2类单词&#xff0c;给出识别该单词的DFA图> 3、关键代码 七、实验结果…

【C++实现】线程池的设计与实现

文章目录前言正文线程池提供的两个重要方法Any类的设计SemaphoreResult的实现Cache模式解释会遇到死锁问题第二个死锁问题&#xff0c;移植到Linux发生项目重构大致流程总结前言 开发环境&#xff1a; Linux&#xff0c;要求g版本能够支持C17以上&#xff1b;vs2019下开发&…

实验四 数码管实验【Verilog】

实验四 数码管实验【Verilog】前言推荐实验四 数码管实验【Verilog】一、实验目的&#xff1a;二、实验设备&#xff1a;三、实验任务:四、实验原理:五、实验步骤&#xff1a;六、实验结果&#xff1a;七、心得体会&#xff1a;最后前言 以下内容源自Verilog实验 仅供学习交流…

[附源码]java毕业设计校园期刊网络投稿系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Git的基础操作及使用

目录 1.git工作原理示意 2.git拉取服务器上的代码 3.git往服务器上提交新code 4.git 查看提交记录&#xff0c; 5.git删除旧代码仓库&#xff0c;提交新的代码仓库 6.如何修改自己提交代码的用户名和邮箱 6.1.查看现用邮箱和用户名 6.2.使用命令修改git的用户名和提交的…

将一段文本映射到低纬向量空间

文本表示学习就是将一段文本映射到低纬向量空间&#xff0c;获取句子的语义表示&#xff0c;大致经历过四个阶段&#xff1a; 阶段 1&#xff1a;统计类型&#xff0c;此阶段比较典型的是利用 TD-IDF 抽取关键词&#xff0c;用关键词表示表征整个句子。 阶段 2&#xff1a;深度…

MySQL数据库增删改查进阶 — 聚合查询、分组查询、联合查询

文章目录1.聚合查询1.1 count 函数1.2 sum 函数1.3 avg 函数1.4 max 和 min 函数2.分组查询2.1 group by 子句2.2 分组查询可以指定条件2.2.1 分组之前&#xff0c;指定条件2.2.2 分组之后&#xff0c;指定条件2.2.3 分组前后都指定条件3.联合查询3.1 笛卡尔积3.1.1 笛卡尔积中…

YourKit Java Profiler 2022.9.X Crack

YourKit Java Profiler 2022.9.X Crack 从 CPU 和内存利用率的角度分析您的程序非常重要。它允许您最大限度地提高自身性能并限制其对服务器系统工具的影响&#xff0c;这将始终受到最终用户的重视。 在 YourKit Java Profiler 的支持下&#xff0c;可以很容易地运行基于 Java…

传奇单机架设教程及游戏GM设置方法

传奇技术教学 第二课:传奇单机架设教程及游戏GM设置方法 架设前关杀毒 确保自己的热血传奇客户端是13周年以后的 最好用最新的. 不要使用已经淘汰的10周年客户端和微端客户端 否则会出现显示不全情况. 注意HERO引擎版本在登录器方面不支持WIN8及WIN10系统的. 若你是以上系统…

Linux资源限制命令—ulimit

ulimit功能简述 假设有这样一种情况&#xff0c;当一台 Linux 主机上同时登陆了 10 个人&#xff0c;在系统资源无限制的情况下&#xff0c;这 10 个用户同时打开了 500 个文档&#xff0c;而假设每个文档的大小有 10M&#xff0c;这时系统的内存资源就会受到巨大的挑战。而实…

市面上主流源表软件全面对比,总有一款适合你!

在电测行业中&#xff0c;在对高精度的电压、电流或电流电压源进行测量扫描时就要请出我们的小伙伴“源表“。 它精确的采集能力以及为各种低电平测量应用提供额外的灵活性让它成为了电测行业中不和或缺的一员。而作为和它配合的搭档源表软件也在电测行业中有着重要的作用。 …