源码地址:https://gitee.com/a1234567891/koalas-rpc

企业生产级百亿日PV高可用可拓展的RPC框架。理论上并发数量接近服务器带宽,客户端采用thrift协议,服务端支持netty和thrift的TThreadedSelectorServer半同步半异步线程模型,支持动态扩容,服务上下线,权重动态,可用性配置,页面流量统计,支持trace跟踪等,天然接入cat支持数据大盘展示等,持续为个人以及中小型公司提供可靠的RPC框架技术方案,贴上

   @Override
public void run() {
try {
if (Epoll.isAvailable ()) {
bossGroup = new EpollEventLoopGroup (serverPublisher.bossThreadCount==0?AbstractKoalsServerPublisher.DEFAULT_EVENT_LOOP_THREADS:serverPublisher.bossThreadCount);
workerGroup = new EpollEventLoopGroup ( serverPublisher.workThreadCount==0? AbstractKoalsServerPublisher.DEFAULT_EVENT_LOOP_THREADS*2:serverPublisher.workThreadCount);
} else {
bossGroup = new NioEventLoopGroup (serverPublisher.bossThreadCount==0?AbstractKoalsServerPublisher.DEFAULT_EVENT_LOOP_THREADS:serverPublisher.bossThreadCount);
workerGroup = new NioEventLoopGroup ( serverPublisher.workThreadCount==0? AbstractKoalsServerPublisher.DEFAULT_EVENT_LOOP_THREADS*2:serverPublisher.workThreadCount );
}
executorService = KoalasThreadedSelectorWorkerExcutorUtil.getWorkerExecutorWithQueue (serverPublisher.koalasThreadCount==0?AbstractKoalsServerPublisher.DEFAULT_KOALAS_THREADS:serverPublisher.koalasThreadCount,serverPublisher.koalasThreadCount==0?AbstractKoalsServerPublisher.DEFAULT_KOALAS_THREADS:serverPublisher.koalasThreadCount,serverPublisher.workQueue,new KoalasDefaultThreadFactory (serverPublisher.serviceInterface.getName ())); ServerBootstrap b = new ServerBootstrap ();
b.group ( bossGroup, workerGroup ).channel ( workerGroup instanceof EpollEventLoopGroup ? EpollServerSocketChannel.class : NioServerSocketChannel.class )
.handler ( new LoggingHandler ( LogLevel.INFO ) )
.childHandler ( new NettyServerInitiator (serverPublisher,executorService))
.option ( ChannelOption.SO_BACKLOG, 1024 )
.option ( ChannelOption.SO_REUSEADDR, true )
.option ( ChannelOption.SO_KEEPALIVE, true );
Channel ch = b.bind ( serverPublisher.port ).sync ().channel ();
Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run(){
logger.info ( "Shutdown by Runtime" );
if(zookeeperServer != null){
zookeeperServer.destroy ();
}
logger.info ( "wait for service over 3000ms" );
try {
Thread.sleep ( 3000 );
} catch (Exception e) {
}
if(executorService!=null){
executorService.shutdown ();
}
if(bossGroup != null) bossGroup.shutdownGracefully ();
if(workerGroup != null) workerGroup.shutdownGracefully ();
}
}); if(StringUtils.isNotEmpty ( serverPublisher.zkpath )){
ZookServerConfig zookServerConfig = new ZookServerConfig ( serverPublisher.zkpath,serverPublisher.serviceInterface.getName (),serverPublisher.env,serverPublisher.port,serverPublisher.weight,"netty" );
zookeeperServer = new ZookeeperServer ( zookServerConfig );
zookeeperServer.init ();
}
} catch ( Exception e){
logger.error ( "NettyServer start faid !",e );
if(bossGroup != null) bossGroup.shutdownGracefully ();
if(workerGroup != null) workerGroup.shutdownGracefully ();
} logger.info("netty server init success server={}",serverPublisher); }

首先开启NIO服务,由系统内核来判断是否支持epoll-EpollEventLoopGroup,如果不支持epoll采用IO多路复用的方式EpollEventLoopGroup,然后声明一个用户自定义线程池,这里有不清楚的读者肯定会问,netty本身支持连接线程和IO线程,为什么还要自定义声明自定义线程池,原因是假设在IO线程池中做的业务非常复杂,大量耗时,这样就会阻塞了netty线程的IO处理速度,影响吞吐量,这也就是reactor模型的设计理念,不让业务干扰连接线程和IO读写线程。NettyServerInitiator就是实际处理的业务handle了。

  Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run(){
logger.info ( "Shutdown by Runtime" );
if(zookeeperServer != null){
zookeeperServer.destroy ();
}
logger.info ( "wait for service over 3000ms" );
try {
Thread.sleep ( 3000 );
} catch (Exception e) {
}
if(executorService!=null){
executorService.shutdown ();
}
if(bossGroup != null) bossGroup.shutdownGracefully ();
if(workerGroup != null) workerGroup.shutdownGracefully ();
}
});

手动关闭钩子,服务关闭的时候要主动关闭节点信息。下面来看一下hander拦截器

package netty.initializer;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import netty.hanlder.KoalasDecoder;
import netty.hanlder.KoalasEncoder;
import netty.hanlder.KoalasHandler;
import org.apache.thrift.TProcessor;
import server.config.AbstractKoalsServerPublisher; import java.util.concurrent.ExecutorService;
/**
* Copyright (C) 2018
* All rights reserved
* User: yulong.zhang
* Date:2018年11月23日11:13:33
*/
public class NettyServerInitiator extends ChannelInitializer<SocketChannel> { private ExecutorService executorService; private AbstractKoalsServerPublisher serverPublisher; public NettyServerInitiator(AbstractKoalsServerPublisher serverPublisher,ExecutorService executorService){
this.serverPublisher = serverPublisher;
this.executorService = executorService;
} @Override
protected void initChannel(SocketChannel ch) {
ch.pipeline ().addLast ( "decoder",new KoalasDecoder () );
ch.pipeline ().addLast ( "encoder",new KoalasEncoder ());
ch.pipeline ().addLast ( "handler",new KoalasHandler (serverPublisher,executorService) );
} }
decode负责拆包。encoder负责装包,handler是真正业务处理的逻辑,所有的业务处理都在这里的线程池中运行,结果通过ChannelHandlerContext 异步的返回给client端,通过这种方式真正的实现了reactor
下面我们看看拆包处理
 package netty.hanlder;

 import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import server.KoalasServerPublisher; import java.util.List;
/**
* Copyright (C) 2018
* All rights reserved
* User: yulong.zhang
* Date:2018年11月23日11:13:33
*/
public class KoalasDecoder extends ByteToMessageDecoder { private final static Logger logger = LoggerFactory.getLogger ( KoalasDecoder.class ); @Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) { try {
if (in.readableBytes () < 4) {
return;
} in.markReaderIndex ();
byte[] b = new byte[4];
in.readBytes ( b ); int length = decodeFrameSize ( b ); if (in.readableBytes () < length) {
//reset the readerIndex
in.resetReaderIndex ();
return;
} in.resetReaderIndex ();
ByteBuf fream = in.readRetainedSlice ( 4 + length );
in.resetReaderIndex (); in.skipBytes ( 4 + length );
out.add ( fream );
} catch (Exception e) {
logger.error ( "decode error",e );
} } public static final int decodeFrameSize(byte[] buf) {
return (buf[0] & 255) << 24 | (buf[1] & 255) << 16 | (buf[2] & 255) << 8 | buf[3] & 255;
}
}

通过读取四个字节的长度来决定消息体长度,然后根据消息体长度来读取所有的字节流数据。decodeFrameSize方法将四个字节流转成int类型。KoalasHandler处理器逻辑比较复杂,我们只看核心的内容,首先通过thrift解析字节流来获取transport

            ByteArrayInputStream inputStream = new ByteArrayInputStream ( b );
ByteArrayOutputStream outputStream = new ByteArrayOutputStream ( ); TIOStreamTransport tioStreamTransportInput = new TIOStreamTransport ( inputStream);
TIOStreamTransport tioStreamTransportOutput = new TIOStreamTransport ( outputStream); TKoalasFramedTransport inTransport = new TKoalasFramedTransport ( tioStreamTransportInput,2048000 );
inTransport.setReadMaxLength_ ( maxLength );
TKoalasFramedTransport outTransport = new TKoalasFramedTransport ( tioStreamTransportOutput,2048000,ifUserProtocol );

最终扔到线程池里去执行,将当前IO线程释放给下一个任务。

           try {
executorService.execute ( new NettyRunable ( ctx,in,out,outputStream,localTprocessor,b,privateKey,publicKey,className,methodName,koalasTrace,cat));
} catch (RejectedExecutionException e){
logger.error ( e.getMessage ()+ErrorType.THREAD+",className:" +className,e );
handlerException(b,ctx,e,ErrorType.THREAD,privateKey,publicKey,thriftNative);
}
RejectedExecutionException来负责当线程池不够用的时候返回给client端异常,因为server端的业务处理能力有限,所以这里适当的做了一下服务端保护防止雪崩的问题。当发现server端有大量的
RejectedExecutionException抛出,说明单机已经无法满足业务请求了,需要横向拓展机器来进行负载均衡。用户实际的业务执行是在Runable里,我们看看他到底做了什么
            try {
tprocessor.process ( in,out );
ctx.writeAndFlush (outputStream);
if(transaction!=null && cat)
transaction.setStatus ( Transaction.SUCCESS );
} catch (Exception e) {
if(transaction!=null && cat)
transaction.setStatus ( e );
logger.error ( e.getMessage () + ErrorType.APPLICATION+",className:"+className,e );
handlerException(this.b,ctx,e,ErrorType.APPLICATION,privateKey,publicKey,thriftNative);
}

通过thrift的process来执行业务逻辑,将结果通过ctx.writeAndFlush (outputStream),返回给client端。在catch里处理当出现异常之后返回给client端异常结果。这样netty server的实现就全部结束了,thrift服务端解析相关内容我们下一篇来说,里面当中有很多细节需要读者是跟着源码阅读,如果有问题欢迎加群825199617来交流,更多spring,spring mvc,aop,jdk等源码交流等你来!

JAVA RPC (九) netty服务端解析的更多相关文章

  1. JAVA RPC (十) nio服务端解析

    源码地址:https://gitee.com/a1234567891/koalas-rpc 企业生产级百亿日PV高可用可拓展的RPC框架.理论上并发数量接近服务器带宽,客户端采用thrift协议,服务 ...

  2. netty服务端启动--ServerBootstrap源码解析

    netty服务端启动--ServerBootstrap源码解析 前面的第一篇文章中,我以spark中的netty客户端的创建为切入点,分析了netty的客户端引导类Bootstrap的参数设置以及启动 ...

  3. Netty之旅三:Netty服务端启动源码分析,一梭子带走!

    Netty服务端启动流程源码分析 前记 哈喽,自从上篇<Netty之旅二:口口相传的高性能Netty到底是什么?>后,迟迟两周才开启今天的Netty源码系列.源码分析的第一篇文章,下一篇我 ...

  4. Netty 服务端启动过程

    在 Netty 中创建 1 个 NioServerSocketChannel 在指定的端口监听客户端连接,这个过程主要有以下  个步骤: 创建 NioServerSocketChannel 初始化并注 ...

  5. Netty 服务端创建

    参考:http://blog.csdn.net/suifeng3051/article/details/28861883?utm_source=tuicool&utm_medium=refer ...

  6. Netty服务端NioEventLoop启动及新连接接入处理

    一 Netty服务端NioEventLoop的启动 Netty服务端创建.初始化完成后,再向Selector上注册时,会将服务端Channel与NioEventLoop绑定,绑定之后,一方面会将服务端 ...

  7. Netty服务端的启动源码分析

    ServerBootstrap的构造: public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, Serve ...

  8. ASP.NET Core中间件(Middleware)实现WCF SOAP服务端解析

    ASP.NET Core中间件(Middleware)进阶学习实现SOAP 解析. 本篇将介绍实现ASP.NET Core SOAP服务端解析,而不是ASP.NET Core整个WCF host. 因 ...

  9. java开源即时通讯软件服务端openfire源码构建

    java开源即时通讯软件服务端openfire源码构建 本文使用最新的openfire主干代码为例,讲解了如何搭建一个openfire开源开发环境,正在实现自己写java聊天软件: 编译环境搭建 调试 ...

随机推荐

  1. 网络知识(1)TCP/IP五层结构

    图1 数据流向图 1,网络基础 1.1 发展 古代:①烽火狼烟最为原始的0-1单bit信息传递:②飞鸽传书.驰道快马通信,多字节通信: 近代:①轮船信号灯:②无线电报[摩尔斯码]: 现代:①有线模拟通 ...

  2. 【SQL Server DBA】日常巡检语句3:特定监控(阻塞、top语句、索引、作业)

    原文:[SQL Server DBA]日常巡检语句3:特定监控(阻塞.top语句.索引.作业) 1.查询阻塞信息.锁定了哪些资源 --1.查看阻塞信息 select spid,loginame,wai ...

  3. 获取类的描述信息 DescriptionAttribute

    static void Main(string[] args) { var attrs = typeof(TestClass).GetCustomAttributes(typeof(System.Co ...

  4. Docker 安装mysql5.6

    1.首先进入命令行现在mysql5.6镜像 E:\>docker pull mysql:5.6 2.把mysql的配置文件放入到本地,供后期做修改用 文件分别为:mysql.cnf 和 mysq ...

  5. Trie树(字典树)-题解 P2580 【于是他错误的点名开始了】

    此题可以用STL中的map做,但是了解一下Trie树这个数据结构也是必须的. Trie树(又称字典树)有以下特点: 根节点不包含字符,除它之外的每一个节点都包含一个字符. 从根节点到某一节点,路径上经 ...

  6. 货币转换B

    描述 人民币和美元是世界上通用的两种货币之一,写一个程序进行货币间币值转换,其中:‪‬‪‬‪‬‪‬‪‬‮‬‪‬‭‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬‪‬‪‬‪‬‪‬‪‬‮ ...

  7. SVN 问题解决之 The XML response contains invalid XML

    公司几个同事的SVN更新时出现了The XML response contains invalid XML报错 经Google得到一个线索,可能和Http请求有关. 想起之前项目改过一次网络请求方式, ...

  8. 5.创建执行线程的方式之三 :实现Callable 接口

    Callable 接口 一.Java 5.0 在 java.util.concurrent 提供了 一个新的创建执行线程的方式(之前有继承Thread 和 实现Runnable):Callable 接 ...

  9. crunch离线密码生成

     Crunch是一种创建密码字典工具,按照指定的规则生成密码字典,可以灵活的制定自己的字典文件. 一.Crunch为kali自带工具之一在kali环境下进行,默认基于26个小写英文字母. 语法:cru ...

  10. django中解决跨域问题

    -跨域问题 -浏览器的:同源策略,浏览器拒绝不是当前域域返回的数据 -ip地址和端口号都相同才是同一个域 -如何解决: -CORS:跨域资源共享 -简单请求:发一次请求 -非简单请求:非简单请求是发送 ...