一个功能引发的思考
今天同事开发了一个文件读写的模块,发现读写性能异常的低,他的做法是单线程纯IO操作,频繁的打开关闭IO流,读写。
于是乎他问我这个应该怎么做,我给他讲解到这种做法的低效,建议他批量的一次性写入,频繁直接操作IO性能当然是无法接受的。
再谈IO操作的演变
BIO:传统的cs端架构,都是一个请求提交,后台一个专门的线程负责接受这个请求,分配给新的线程去处理。这种做法的缺点很明显,当并发量过大的时候,不断的创建新的线程,对于服务器来说,肯定是无法接受的,这样会导致大量的请求阻塞未响应。而且创建线程这个操作java是直接操作 操作系统内核的,光创建线程这个,就会导致系统假死,cpu100等情况。
NIO:直到jdk1.4以后,才推出了非阻塞的IO操作,基于I/O多路复用技术,减小系统开销。即,把多个I/O的阻塞复用到同一个select上阻塞,从而使得单线程情况下,可以同时处理多个请求。系统不需要创建新的额外进程或者线程,也不需要维护这些进程和线程的运行。并且在jdk1.5中,epool替代传统select/poll,极大提升NIO通信新能
jdk nio存在的缺陷
1.开发复杂度非常高,例如:客户端重连,网络闪断,半包读写,失败缓存,网络拥塞,异常码流的处理等。
2.jdk epoll bug会导致selector空轮询,最终导致cpu100
基于Netty实现NIO
Netty算是一个比较成熟的nio框架了,他解决了jdk中epoll的bug,并且开发复杂度也比较低一点。
后面上一个简单的netty nio实现源码
基于Netty开发应用层协议
随着互联网发展,传统垂直架构逐渐被分布式,弹性可伸缩分布式架构替代。
那么问题也随之而出,系统只有分布式,就面临各个节点间通信的问题,尤为强调高可用,扩展性强,高性能。
1.高性能
传统java序列化性能比较低,而且序列化后的字节流太大,现在出的很多新的序列化框架,性能都比传统的jdk要高很多,如:google的protobuf facebook的thrift jboss的marshalling。另外netty对异步非阻塞通信的支持,以及高效reactor线程模型,无锁化串行设计,0拷贝,灵活tcp参数配置等。
2.扩展性
传统java序列化无法跨语言,所以目前的java rpc框架基本也都没用jdk的,比如thirft:可以跨c++,c#,cocoa,erlang,java,perl,php,python,ruby等
3.高可用
传统bio模型,对并发访问支持很差,而新的nio就不同,具体的上面也谈到了
4.可靠性
1>网络通信类
连接超时接口,强制关闭对端连接
2>链路有效性
通过心跳检查
3>reactor线程保护
主要谨慎处理I/O异常,以及规避NIO bug
4>链路控制
5>优雅停机
5.安全性
安全性可以通过netty提供的SSL认证,也可以通过第三方CA认证来保障
6.成功案例
目前也已经有很多成熟应用netty的框架,如alibaba的rocketMQ dubbo ,Apache的avro等等
副一段Netty基础NIO代码
package com.solace.nio;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* Created by Administrator on 2018/4/10.
*
* @author solace
*/
public class TimerServer {
public void bind(int port) throws Exception{
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try{
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,1024)
.childHandler(new ChildChannelHandler());
//绑定端口,同步等待成功
ChannelFuture future = bootstrap.bind(port).sync();
//等待服务端监听端口关闭
future.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new TimerServer().bind(8088);
}
}
package com.solace.nio;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import java.util.logging.Logger;
/**
* Created by Administrator on 2018/4/10.
*
* @author solace
*/
public class TimeClientHandler extends ChannelHandlerAdapter{
public static final Logger logger = Logger.getLogger(TimeClientHandler.class.getName());
private final ByteBuf fisrtMessage;
public TimeClientHandler() {
byte[] req = "QUERY TIME ORDER".getBytes();
fisrtMessage = Unpooled.buffer(req.length);
fisrtMessage.writeBytes(req);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(fisrtMessage);
}
/**
* 解码读取消息
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req,"UTF-8");
System.out.println("Now is :"+body);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
logger.warning("Unexpercted exception from downstream :"+cause.getMessage());
ctx.close();
}
}
package com.solace.nio;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import java.util.Date;
/**
* Created by Administrator on 2018/4/10.
*
* @author solace
*/
public class TimeServerHandler extends ChannelHandlerAdapter{
/**
* 读到消息后回执
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req,"UTF-8");
System.out.println("the time server receive order:"+body);
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body)?new Date(System.currentTimeMillis()).toString():"bad order";
ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
ctx.write(resp);
}
/**
* 消息读完了后flush
* @param ctx
* @throws Exception
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
/**
* 消息读取异常处理
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
package com.solace.nio;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
* Created by Administrator on 2018/4/10.
*
* @author solace
*/
public class TimeClient {
public void connect(int port,String host) throws InterruptedException {
//配置NIO线程租
EventLoopGroup group = new NioEventLoopGroup();
try{
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY,true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new TimeClientHandler());
}
});
//异步发起连接请求
ChannelFuture future = bootstrap.connect(host,port).sync();
//等待客户端链路关闭
future.channel().closeFuture().sync();
}finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
new TimeClient().connect(8088,"127.0.0.1");
}
}
package com.solace.nio;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import java.util.logging.SocketHandler;
/**
* Created by Administrator on 2018/4/10.
*
* @author solace
*/
public class ChildChannelHandler extends ChannelInitializer<SocketChannel>{
/**
* 添加自定义处理类
* @param channel
* @throws Exception
*/
@Override
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline().addLast(new TimeServerHandler());
}
}