- Published on
自研网关组件
一,自研高性能入口网关
- 网关是干什么的,解决什么问题?
- 入口网关,统一收口,访问收敛;
- 方便做安全鉴权控制、统一管理、统计分析、如并发访问量、流控、降级、通用功能适配;
- 支持特性:
- 根据业务支持:动态配置、规则灵活、动态路由、灰度发布、各种功能插件化等等;
- 为什么自己造轮子?
- 可维护性、扩 展性强、自主迭代,高度自由制定化;
- 摒弃冗余/额外功能代码,专注于自身业务;
- 高性能,高吞吐量,集各个开源实现于自身;
1.1 主流程分析
网关业务功能点梳理
- 作为入口网关,具备哪些核心功能?
- 请求解析、效验
- 执行各种插件化逻辑
- 请求转发下游服务,协议解析
- 动态配置化,动态规则
网关架构拓扑图设计
- 网关服务端
- 客户端
- 注册中心端
- 控制台端
1.2 高性能设计
高性能之异步化之道
- 哪些场景需要做异步化
- 请求转发异步化
- 请求响应异步化
- 过滤器异步化
高性能之吞吐量为王
- 如何提升网关的吞吐量?
- 高并发下采用,缓存抗压
- Disruptor
- MPMC
高性能之线程数设定
- Netty Reactor模型
- CPU亲和性
- 减少上下文切换
高性能之用尽缓存
- 网关服务加载注册服务信息使用缓存
- 网关服务加载配置信息使用缓存
- 网关服务加载规则信 息使用缓存
- 负载均衡策略缓存dubbo请求构建ReferenceConfig缓存
高性能之串行化设计
- 过滤器模型组织
- 串行化,并行化之间如何选择
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 扩展和补充
服务端缓存设置
- core模块拉取etcd注册信息、缓存设计
- 注册信息更新设计
- 网关服务注册
负载均衡实现
- 负载均衡服务概述
- 抽象类轮询负载实现:权重预热机制
- 轮询负载实现
- 随机负载实现
- http请求基于负载均衡策略的实现
- dubbo请求基于负载均衡策略的实现
Route路由机制实现
- route过滤器说明
- http route filter 实现
- dubbo route filter 实现
其他前置路由实现
- 其他过滤器服务有哪些?
- 前置过滤器实现案例:黑白名单实现
- 后置过滤器实现案例:服务QPS统计分析、耗时统计之技术方案
- 设计高性能滑窗实现RollingNumber源码深度剖析
- 插件化开发与实现插件管理
- 整合kafka插件开发实现
高级特性篇
- etcd服务不可用时,解决方案设计服务
- 灰度发布、动态路由机制;
1.6 控制台
- 控制台核心功能说明
- 服务实现讲解
- 消费kafka数据
- 可视化:Grafana/时序数据库
1.7 延伸
- Netty Hash 轮实现源码解析
- 实现自己的高性能非阻塞队列解析
二,架构设计
入口网关架构图
执行流程架构图
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 组件实践
四,实践问题汇总
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