My logo
Published on

自研网关组件

一,自研高性能入口网关

  • 网关是干什么的,解决什么问题?
    • 入口网关,统一收口,访问收敛;
    • 方便做安全鉴权控制、统一管理、统计分析、如并发访问量、流控、降级、通用功能适配;
  • 支持特性:
    • 根据业务支持:动态配置、规则灵活、动态路由、灰度发布、各种功能插件化等等;
  • 为什么自己造轮子?
    • 可维护性、扩展性强、自主迭代,高度自由制定化;
    • 摒弃冗余/额外功能代码,专注于自身业务;
    • 高性能,高吞吐量,集各个开源实现于自身;

1.1 主流程分析

网关业务功能点梳理

  1. 作为入口网关,具备哪些核心功能?
  2. 请求解析、效验
  3. 执行各种插件化逻辑
  4. 请求转发下游服务,协议解析
  5. 动态配置化,动态规则

网关架构拓扑图设计

  1. 网关服务端
  2. 客户端
  3. 注册中心端
  4. 控制台端

1.2 高性能设计

高性能之异步化之道

  1. 哪些场景需要做异步化
  2. 请求转发异步化
  3. 请求响应异步化
  4. 过滤器异步化

高性能之吞吐量为王

  1. 如何提升网关的吞吐量?
  2. 高并发下采用,缓存抗压
  3. Disruptor
  4. MPMC

高性能之线程数设定

  1. Netty Reactor模型
  2. CPU亲和性
  3. 减少上下文切换

高性能之用尽缓存

  1. 网关服务加载注册服务信息使用缓存
  2. 网关服务加载配置信息使用缓存
  3. 网关服务加载规则信息使用缓存
  4. 负载均衡策略缓存dubbo请求构建ReferenceConfig缓存

高性能之串行化设计

  1. 过滤器模型组织
  2. 串行化,并行化之间如何选择

1.3 核心模块

核心框架构建

  • 整体项目构建、包结构划分
  • 核心模块领域模型设计
  • Bootstrap启动类构建 & CgiContainer容器
  • LifeCycle组件生命周期定义
  • NettyProcessor核心处理器定义
  • NettyHttpServer核心类
  • NettyHttpClient核心实现
  • 封装自己的并行化组件:ParallelFlusher
  • NettyBatchEventProcessor实现
  • NettyMpmcBlockingProcessor源码品读分析
  • MPMC模式实现

核心上下文模型封装

  • Context上下文核心定义
  • AttributeKey上下文参数
  • BasicContext基础上下文实现
  • CgiRequest实现
  • CgiResponse实现
  • Rule、FilterConfig实现
  • CgiContext实现

解析上下文实现

  • RequestHelper#doContext方法
  • 根据请求获取指定服务信息
  • ServiceDefinition服务定义模型
  • ServiceInvoker模型
  • 动态获取服务与规则,构建上下文
  • 快速失败策略之AntPathMatcher表达式
  • ResponseHelper响应封装

过滤器设计与实现

  • 过滤器的设计
  • 过滤器接口定义 & 过滤器注解定义
  • AbstractLinkedProcessorFilter
  • ProcessorFilterChain
  • ProcessorFilterFactory工厂定义
  • DefaultProcessorFilterFactory过滤器工厂
  • AbstractEntryProcessorFilter

1.4 客户端注册

客户端设计

  • 客户端主要功能说明与设计
  • 客户端规范:注解定义
  • CgiAnnotationScanner客户端注解解析类
  • AbstractClientRegisterManager抽象注册管理器实现
  • 服务注册,接口定义:cgi-discovery-api

注册中心之ETCD

  • ETCD简介
  • 客户端规范:注解定义
  • ETCD环境搭建说明 & 使用介绍
  • ETCD客户端二次封装

注册中心设计与实现

  • 注册中心数据模型设计
  • RegistryServiceEtcdImpl 注册服务实现
  • AbstractClientRegisterManager抽象注册管理器实现续

注册支持多协议

SpringMVC集成注册实现(HTTP)

Dubbo集成注册实现(Dubbo)

1.5 扩展和补充

服务端缓存设置

  1. core模块拉取etcd注册信息、缓存设计
  2. 注册信息更新设计
  3. 网关服务注册

负载均衡实现

  1. 负载均衡服务概述
  2. 抽象类轮询负载实现:权重预热机制
  3. 轮询负载实现
  4. 随机负载实现
  5. http请求基于负载均衡策略的实现
  6. dubbo请求基于负载均衡策略的实现

Route路由机制实现

  1. route过滤器说明
  2. http route filter 实现
  3. dubbo route filter 实现

其他前置路由实现

  1. 其他过滤器服务有哪些?
  2. 前置过滤器实现案例:黑白名单实现
  3. 后置过滤器实现案例:服务QPS统计分析、耗时统计之技术方案
  4. 设计高性能滑窗实现RollingNumber源码深度剖析
  5. 插件化开发与实现插件管理
  6. 整合kafka插件开发实现

高级特性篇

  1. etcd服务不可用时,解决方案设计服务
  2. 灰度发布、动态路由机制;

1.6 控制台

  1. 控制台核心功能说明
  2. 服务实现讲解
  3. 消费kafka数据
  4. 可视化:Grafana/时序数据库

1.7 延伸

  1. Netty Hash 轮实现源码解析
  2. 实现自己的高性能非阻塞队列解析

二,架构设计

入口网关架构图

gateway

执行流程架构图

gateway

2.1 网关主流程分析

• 开发一个中间件产品,考虑这个产品实现的目的,价值是什么,职责?

– 权限认证、流量控制、黑白名单过滤、灰度路由、请求转发(重定向)、跨域处理、流量统计分析...

网关的请求定义?

– 承接所有外部的HTTP服务,把他转换成内部的具体服务协议;(dubbo、springMVC、thrift、Grpc)

一次HTTP请求会经历哪些环节?

– HttpServer:接收外部请求的服务器

– 解析HTTP请求

– 资源定位:根据一个请求的路径(Path)携带的参数(Header),定位当前所访问的资源是什么?(商品、用户服务)

– 拉取实例列表:(网关启动的时候,加载一些资源信息)

– 负载均衡:LoadBalance:RR、随机、加权重...

– 发送请求:(HttpClient)

2.2 功能点梳理

• 业务功能点梳理:一次HTTP请求过来,我们要实现哪些功能;

– HTTP(FullHttpRequest对象)转换成我们内部的HTTP对象(自己定义的请求对象:RapidRequest)

– 对HTTP请求进行校验(合法性、正确性)

– 执行各种插件化逻辑:在基础的流程之上我们要做各种功能化的组件

– 不是所有的请求都要执行下面这些插件:/login(登录,要进行认证和授权)

– 要针对不同的请求,执行不同的插件化逻辑(/pathA:3 ,4,5, 8)(/pathB:1 ,4,5, 8)

– 规则的概念:Rule:规则 和 路径是一个多对一的关系

• Filter(某一个插件化逻辑)

– 1 认证授权插件:Auth

– 2 流控插件:FlowCtl

– 3 黑白名单插件:WB

– 4 负载均衡插件:LB

– 5 协议解析插件:ProtocolR

– 6 转发的插件:Dispatch

– 7 超时插件:5000ms

– 8 后置插件,统计分析插件:总的访问次数,成功(下游服务能够显示捕获异常并返回的)、失败(网关服务自身出现exception,超时、连接断开)

网络拓扑功能点梳理:

– 网关服务器:

– 客户端角色:spring、dubbo、tcp、thrift、Grpc...

– 注册中心:注册客户端数据,网关服务启动时从客户端拉取注册的信息;

– 控制台端:

• 监控客户端服务的状态,实例的上下线;

• 监控网关服务自身的信息:网关自身实例(服务本身的负载情况),总请求数、成功率失败率;

配置化:各种各样的规则:插件的动态配置

2.3 技术选型

高性能网关整体技术选型:

• 技术选型:

– 网关服务端:java系Netty为网关核心服务;

• 不依赖任何第三方框架(Spring/SpringBoot),轻量级、高性能;

– 注册中心:选择Etcd,选择理由如下:

• 主流,K8S使用;

• 高性能:golang语言实现;

• 一致性保障好:raft协议;

• 存储结构:kv键值对存储,区别于zookeeper目录树存储方式;

– 附加黑科技:

• 高性能组件(后续介绍)

2.4 网关高性能之道

• 异步化的设计

– 路由转发请求异步化:

• 在我们网关内部转向下游服务的时候,用于提升吞吐量;

– 接收服务响应异步化

• 双异步模式好处:比较适合于下游服务性能不是很高的场景(500-2000ms),非常合适的;

• 双异步模式缺点:下游服务性能很好(1ms - 3ms),频繁的上下文切换;

– 插件(Fliter)异步化

• 比如在插件中处理很耗时的操作(认证授权服务,调用第三方的PRC服务),用于提升吞吐量

吞吐量为王:

– 在某些特定的业务场景下:会有一些流量的洪峰突然瞬时打到我们的入口网关;

– 我们需要再boss-work之后加一个缓冲区:

• disruptor

• mpmc

线程数设定:

– 网关服务完全是一个CPU密集型的服务类型:假设我们服务器是8C/16G

• CPU密集型:core + 1 (N) = 8 ~ 8 + N

• IO密集型: core / (1 - 阻塞系数[0.7-0.9]) = 80

– CPU亲和性:用操作系统的CPU核,与一个线程做强绑定

用尽缓存:

– 在尽可能能用到缓存的地方,都使用缓存(内存:map、list、queue)

串行化设计:

– 在耗时很小,性能要求非常高的场景下:往往串行执行逻辑,会比并行执行效率更高;

比较合适并行设计的场合:业务逻辑处理的时候,比如有远程RPC调用,很耗时的操作(任务没有依赖关系)

三,Disruptor 组件实践

gateway

四,实践问题汇总

4.1 项目启动时报错UnsupportedOperationException

参考:

https://blog.csdn.net/Vincent_Field/article/details/105951797

https://github.com/msgpack/msgpack-java/issues/600

cannot access class jdk.internal.misc.Unsafe问题是因为java9新增了模块系统特性参见。

可以将java版本降低到1.8来解决该问题,也可以引入该模块,或者JVM启动参数中增加导入模块的声明


--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED
--add-exports=java.base/jdk.internal.misc.Unsafe=ALL-UNNAMED

Reflective setAccessible(true) disabled问题,是netty对java9及以上版本做了io.netty.tryReflectionSetAccessible是否为true的检查。将java版本降低到1.8可以解决该问题,也可以在JVM启动参数中增加该值为true的配置。

-Dio.netty.tryReflectionSetAccessible=true

To use DirectByteBuffer in JDK17, two JVM options must be set:

--add-opens=java.base/java.nio=ALL-UNNAMED
--add-opens=java.base/sun.nio.ch=ALL-UNNAMED

If DirectByteBuffer is not used, msgpack-java 0.9.3 will work even if these two options are not set.

我的解决方案

ieda 启动加上下面的启动参数:

-Dio.netty.tryReflectionSetAccessible=true
--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED
--add-exports=java.base/jdk.internal.misc.Unsafe=ALL-UNNAMED
--add-opens=java.base/java.nio=ALL-UNNAMED
--add-opens=java.base/sun.nio.ch=ALL-UNNAMED