I. 前言
本文档是Janus Reference Documentation
总之,Janus是一个基于Netty自主研发的高性能网关
Release Note
关于Janus的Release Note可以访问[Janus-Release-Note]
II. Janus 概述
1. Janus网关介绍
Janus(汉译作“杰纳斯”或“雅努斯”)是天门神,早晨打开天门,让阳光普照人间,晚上又把天门关上,使黑暗降临大地。他的头部前后各有一副面孔,同时看着两个不同方向,一副看着过去,一副看着未来,因此也称两面神
Janus掌握所有对外接口的进出.
服务网关Janus是Halo体系的一个组成模块,为各业务开发部门开发的业务服务(RESTful、RPC)提供对外统一的,高性能的HTTP网关。
Janus致力于优化API入口的七层协议接入,主要从下面几个方面进行优化:
-
协议支持:外网HTTPS、HTTP2、HTTP、自定义协议等,自动适配到内网RPC协议和HTTP协议。
-
接入优化:SSL session复用、SSL 加解密异步化、异地多活、流量管控
-
安全: 认证验签、验签加固、设备指纹、WAF、防刷、限流、风控
-
其他: 单元化、轻量数据聚合、A/B test, mock api, 多租户等
3. Janus 项目
3.1. Janus Team
Janus Team开发团队信息如下,感谢所有对Janus有贡献的所有人!
github id | 角色 | github地址 | 备注 |
---|---|---|---|
SoftwareKing |
PPMC |
||
wolfgump |
PPMC |
||
tbkk |
PPMC |
3.2. Janus工程
Janus工程主要为3个项目,如下所示:
工程名 | 说明 | github地址 |
---|---|---|
janus |
janus网关Server |
|
janus-ui |
janus管控平台前端 |
|
janus-admin |
Janus网关的管控平台后端 |
暂无 |
III. janus设计
4. janus filter设计
4.1. Filter设计
责任链模式是实现了类似「流水线」结构的逐级处理,通常是一条链式结构,将「抽象处理者」的不同实现串联起来: 如果当前节点能够处理任务则直接处理掉,如果无法处理则委托给责任链的下一个节点,如此往复直到有节点可以处理这个任务。
Filter链的设计正式采用了这种责任链的设计方式
5. Janus 路由设计
5.2. 示例路由配置
routes:
- id: 1 #演示Spring Cloud应用模糊匹配并走LB # http://localhost:8081/janus-admin/admin/menu/manage/allMenu
filters:
- name: StripPrefix #去前缀
args:
parts: 1
- name: AddRequestHeader #添加Header
args:
X-Request-Blue: Blue
X-Request-Red: red
protocol: lb://sc
serviceName: janus-admin
loadBalancerName: round-robin #random、hash; default is round-robin
predicates: # and ; if need or add a new route
- name: PathRegex
args:
pattern: ^/janus-admin/.*
- id: 2 #演示 URL1->URL2 并走LB #http://localhost:8081/api/getAllMenu
protocol: lb://sc
serviceName: janus-admin
filters:
- name: PathMapping
args:
/api/getAllMenu: "/admin/menu/manage/allMenu"
predicates: # and ; if need or add a new route
- name: PathPrecise
args:
pattern: /api/getAllMenu
- id: 3 #用于演示精准匹配,不走LB http://localhost:8081/admin/menu/manage/allMenu
protocol: http
hosts:
- 127.0.0.1:8084
predicates: # and ; if need or add a new route
- name: PathPrecise
args:
paths: /admin/menu/manage/allMenu
- id: 4 #用于模糊匹配,不走LB
protocol: http
hosts:
- 127.0.0.1:8084
predicates: # and ; if need or add a new route
- name: PathRegex
args:
pattern: ^/janus-admin-test/.*
- id: 5 #用于演示精准匹配,不走LB http://localhost:8081/admin1/menu/manage/allMenu3
filters:
- name: MockResponse
args:
/admin1/menu/manage/allMenu3: "hello word!"
protocol: http
hosts:
- 127.0.0.1:8084
predicates: # and ; if need or add a new route
- name: PathPrecise
args:
paths: /admin1/menu/manage/allMenu3
- id: 6 #用于演示精准匹配,不走LB,直接转发到指定的IP和端口 http://localhost:8081/admin2/menu/manage/allMenu2
filters:
- name: PathMapping
args:
/admin2/menu/manage/allMenu2: "/admin/menu/manage/allMenu"
protocol: http
hosts:
- 127.0.0.1:8084
predicates: # and ; if need or add a new route
- name: PathPrecise
args:
paths: /admin2/menu/manage/allMenu2
- id: 7 #根据配置的多个Hosts不通过注册中心进行LB #http://localhost:8081/admin7/menu/manage/allMenu2
filters:
- name: PathMapping
args:
/admin7/menu/manage/allMenu2: "/admin/menu/manage/allMenu"
protocol: lb://hosts
hosts:
- 127.0.0.1:8084
- admin.qingcloud.net:80
predicates: # and ; if need or add a new route
- name: PathPrecise
args:
paths: /admin7/menu/manage/allMenu2
7. Janus Netty中的设计
org.xujin.janus.core.netty.client.NettyConnectClient
org.xujin.janus.core.netty.server.NettyServer-→NettyServerHandler
7.1. Janus Server的设计
7.1.1. 网关接受请求的Netty Server
org.xujin.janus.core.netty.NettyServer.java
/**
* 初始化EventPool 参数
*/
private void initEventPool() {
bossGroup = new NioEventLoopGroup(serverConfig.getBossThread(), new ThreadFactory() {
private AtomicInteger index = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "Janus_Boss_" + index.incrementAndGet());
}
});
workGroup = new NioEventLoopGroup(serverConfig.getWorkThread(), new ThreadFactory() {
private AtomicInteger index = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "Janus_Work_" + index.incrementAndGet());
}
});
}
/**
* 服务开启
*/
public void start(int port) {
//TODO 上生产时去掉
ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.ADVANCED);
initEventPool();
ServerBootstrap bootstrap = new ServerBootstrap();
//绑定线程池
bootstrap.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class) //指定使用的channel
.option(ChannelOption.SO_REUSEADDR, serverConfig.isReUseAddr())
.option(ChannelOption.SO_BACKLOG, serverConfig.getBackLog())
.childOption(ChannelOption.SO_RCVBUF, serverConfig.getRevBuf())
.childOption(ChannelOption.SO_SNDBUF, serverConfig.getSndBuf())
.childOption(ChannelOption.TCP_NODELAY, serverConfig.isTcpNoDelay())
.childOption(ChannelOption.SO_KEEPALIVE, serverConfig.isKeepalive())
.childHandler(initChildHandler());
bootstrap.bind(port).addListener((ChannelFutureListener) channelFuture -> {
if (channelFuture.isSuccess()) {
changeOnLine(true);
log.info("服务端启动成功【" + IpUtils.getHost() + ":" + port + "】");
} else {
log.error("服务端启动失败【" + IpUtils.getHost() + ":" + port + "】,cause:"
+ channelFuture.cause().getMessage());
shutdown(port);
}
});
}
-
Netty的Server端,不管是Netty Server端和Client端都有NioEventLoopGroup
-
因为是Netty Server端,所以Channel类型是NioServerSocketChannel.
-
childHandler(initChildHandler())是基于Pipeline的自定义Handler机制。
-
EventLoopGroup:无论是服务端还是客户端,都必须指定EventLoopGroup。 在上面的代码中, 指定了NioEventLoopGroup,表示一个NIO的EventLoopGroup,不过服务端需要指定两个EventLoopGroup,一个是bossGroup, 用于处理客户端的连接请求;另一个是workerGroup,用于处理与各个客户端连接的I/O操作。
(2)指定Channel的类型。这里是服务端,所以使用了NioServerSocketChannel。
(3)配置自定义的业务处理器Handler。
@SuppressWarnings("rawtypes")
private ChannelInitializer initChildHandler() {
return new ChannelInitializer<SocketChannel>() {
final ThreadPoolExecutor serverHandlerPool = ThreadPoolUtils.makeServerThreadPool(NettyServer.class.getSimpleName());
//网关Server的Http请求入口Handler
final NettyServerHandler nettyServerHandler = new NettyServerHandler(serverHandlerPool);
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//initHandler(ch.pipeline(), serverConfig);
ch.pipeline().addLast(new IdleStateHandler(180, 0, 0));
ch.pipeline().addLast("logging", new LoggingHandler(LogLevel.DEBUG));
// We want to allow longer request lines, headers, and chunks
// respectively.
ch.pipeline().addLast("decoder", new HttpRequestDecoder(
serverConfig.getMaxInitialLineLength(),
serverConfig.getMaxHeaderSize(),
serverConfig.getMaxChunkSize()));
//ch.pipeline().addLast("inflater", new HttpContentDecompressor());
ch.pipeline().addLast("aggregator", new HttpObjectAggregator(serverConfig.getMaxHttpLength()));
//响应解码器
ch.pipeline().addLast("http-encoder", new HttpResponseEncoder());
//目的是支持异步大文件传输()
ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler());
//网关Server的Netty HTTP连接处理类
ch.pipeline().addLast("process", nettyServerHandler);
}
};
}
7.1.2. 网关转发调用内部源服务的Client
-
网关内部转发的客户端Java类 org.xujin.janus.core.netty.client.NettyConnectClient.java
@Slf4j
public class NettyConnectClient extends ConnectClient {
public NioEventLoopGroup clientGroup = new NioEventLoopGroup(0,
new DefaultThreadFactory("Janus-client-Worker", true));
private Channel channel;
@Override
public void init(String address) throws Exception {
Object[] array = IpUtils.parseIpPort(address);
String host = (String) array[0];
int port = (int) array[1];
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(clientGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
.addLast(new IdleStateHandler(0, 0, 60, TimeUnit.SECONDS))
// .addLast(new HttpClientCodec())
// .addLast(new HttpObjectAggregator(65536))
//解压
//.addLast(new HttpContentDecompressor())
.addLast(new HttpClientCodec(NettyConstants.MAX_INITIAL_LINE_LENGTH,NettyConstants.MAX_HEADER_SIZE, NettyConstants.MAX_CHUNK_SIZE),
new HttpObjectAggregator(NettyConstants.MAX_RESPONSE_SIZE))
//Netty Client把请求转发给内网源服务的Handler
.addLast("handler", new NettyClientHandler());
}
})
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 60*1000);
this.channel = bootstrap.connect(host, port).sync().channel();
// valid
if (!isValidate()) {
close();
return;
}
log.debug("netty client proxy, connect to server success at host:{}, port:{}", host, port);
}
}
-
Netty客户端必须有NioEventLoopGroup
-
ChannelType:指定Channel的类型。因为是客户端,所以使用了NioSocketChannel。
-
new NioEventLoopGroup()核心线程数是CPU*2,如果我们传入的线程数nThreads是0,那么Netty会设置默认的线程数DEFAULT_EVENT_LOOP_THREADS,而这个默认的
Netty的客户端如何发起TCP长连接
bootstrap.connect(host, port).sync().channel()
客户端通过调用Bootstrap的connect()方法进行连接。在connect()方法中进行一些参数检查,并调用doConnect()方法,
static {
DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
"io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
if (logger.isDebugEnabled()) {
logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
}
}
/**
* @see MultithreadEventExecutorGroup#MultithreadEventExecutorGroup(int, ThreadFactory, Object...)
*/
protected MultithreadEventLoopGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, threadFactory, args);
}
7.1.3. 网关转发调用源服务的连接池
网关Server内置的Netty客户端不是每次转发都立即创建一个连接,而是从连接池中获取一个连接。
10. Janus监控告警系统设计
10.1. 背景
-
需要统一的流量统计、监控告警指标,以便对Janus系统性能、运行状态进行分析。
-
Janus网关需要对进来的业务流量进行监控、统计和实时告警,以让业务方了解他们的服务经过Janus的实时状态。
-
对Janus本身运行情况进行监控和告警,以便发现Janus系统的问题,对产品持续做改进。
-
通过监控告警,直接定位到相应的处理人员,提高故障处理效率。
10.2. 应用场景
-
业务方人员。了解服务调用统计信息、调用耗时、出错率、异常等信息,方便排查问题。
-
Janus开发人员。评估系统性能、流量负载情况,发现系统运行问题。
-
系统运维人员。了解Janus机器和进程运行状态,机器负载,以发现集群运行问题。
12. Janus限流方案设计
12.1. 需求
-
保证Janus服务器在 瞬间 极限流量的冲击下,稳定提供服务。
IV. janus Admin设计
13. 数据库设计
13.2. API的模型设计
- id: 7 #根据配置的多个Hosts不通过注册中心进行LB #http://localhost:8081/admin7/menu/manage/allMenu2
filters:
- name: PathMapping
args:
/admin7/menu/manage/allMenu2: "/admin/menu/manage/allMenu"
protocol: lb://hosts
hosts:
- 127.0.0.1:8084
- admin.qingcloud.net:80
predicates: # and ; if need or add a new route
- name: PathPrecise
args:
paths: /admin7/menu/manage/allMenu2
14. Janus Admin使用
14.1. API管理
14.1.1. 新建API
-
新建路由进行负载均衡
- id: 7 #根据配置的多个Hosts不通过注册中心进行LB #http://localhost:8081/admin7/menu/manage/allMenu2
filters:
- name: PathMapping
args:
/admin7/menu/manage/allMenu2: "/admin/menu/manage/allMenu"
protocol: lb://hosts
hosts:
- 127.0.0.1:8084
- admin.qingcloud.net:80
predicates: # and ; if need or add a new route
- name: PathPrecise
args:
paths: /admin7/menu/manage/allMenu2
Janus Admin的UI操作
15. 开发者手册
15.1. 启动Janus Server
在IDEA中运行JanusServerApplication.java,设置启动参数即可。 eg:-Dcluster=Janus_Test -Dlocal=true,如下图所示:
Note
|
-Dcluster=Janus_Test是网关集群编码Janus_Test, -Dlocal=true表示启动的时候读取本地配置文件,而不是远端配置文件 |