首先我们先要了解,一个高性能的IO它必须是能提供快速读写的能力。那要提供这个能力,我们需要解决什么问题?
提高互联网上的数据交换速度:
- 通过优化数据传输协议,
- 提供定制的序列化和反序列化方式
- 利用NIO技术
提高服务读写速度:
- 通过批量处理的方式提高系统的吞吐量,
- 利用操作系统的高性能顺序读写能力,
- 利用操作系统的PageCache来缓存数据,减少IO并提升读性同时给写磁盘让空间;
- 利用操作系统的零拷贝技术加快消息的消费速度;
下面我们给仔细说下每个知识点:
异步模型:
像消息中间件这种场景,对吞吐量要求很高,我们可以采用异步模型去设计。
使用异步编程模型,虽然并不能加快程序本身的速度,但可以减少或者避免线程等待,只用很少的线程就可以达到超高的吞吐能力。同时要再次强调,异步虽好,但它毕竟引入了复杂度,我们的业务本来已经很复杂了,对吞吐量要求不高的情况下,尽量还是同步省事。
如何实现高性能的异步网络通信:NIO是你的选择,比如Netty就是基于NIO的高性能网络通信框架,我们能通过它解决线程控制、缓存管理、连接管理等在NIO上需要手动解决的事情。
简单介绍一下,NIO是怎么解决高并发问题的。
如图所示,首先我们需要创建两个线程组,IO线程和工作线程,IO线程负责监听通道内的数据是否接收完毕(selctor是一个阻塞的回调机制),接收完毕就通知回调业务工作线程可以开始读取并进行自己的业务处理。这样做的好处是,与同步IO相比,一个线程对应一个通道,它节省了很多io线程,这样可以很大程度的提高吞吐量。
如何通过网络传输结构化数据
我们常用的方案是序列化(把应用数据转换成字节在网络传输)与反序列化(接收到字节数据后反序列化为应用程序能识别的数据格式)。
如用得非常广泛的json和xml格式,就是其中代表,那选择哪种数据格式有没有选择的标准呢,这里我们列举了几个关键要素:
- 序列化后的数据最好是易于人类阅读的;
- 实现的复杂度是否足够低;
- 序列化和反序列化的速度越快越好;
- 序列化后的信息密度越大越好,也就是说,同样的一个结构化数据,序列化之后占用的存储空间越小越好;
当然,如你所见,不可能有一种数据格式能满足所有的要素,所以我们要根据自己的使用业务场景选择合适的数据格式,当然像中间件这种对速度要求非常高的,它也可以定制化数据格式,降低他的可读性,提高信息密度。
传输协议,应用程序之间对话的语言
接上一部分说的,你定好了数据格式,那数据的格式都有哪些要素呢?
在设计传输协议的时候,需要解决如何断句的问题,我们给大家提供了“分隔符”和“前置长度”两种断句的方法,你可以选择使用。
最后双工通信也是一个非常重要的技术,“使用 ID 来标识请求与响应对应关系” (比如我们在tcpi三次握手里面看到的ack,syn)来实现双工通信的方法,可以有效提升数据传输的吞吐量。
解决了断句问题,实现了双工通信,配合专用的序列化方法,你就可以实现一套高性能的网络通信协议,实现高性能的进程间通信。很多的消息队列、RPC 框架都是用这种方式来实现它们自己的私有应用层传输协议。
内存管理:如何避免内存溢出和频繁的垃圾回收?
如上我们已经准备好了服务可以大干一场,谁知在超大并发下每个请求报文有10KB大小,我们服务器直接进入假死状态(GC)。
对于开发者来说,垃圾回收是不可控的,而且是无法避免的。但是,我们还是可以通过一些方法来降低垃圾回收的频率,减少进程暂停的时长。
- 不要频繁的创建新对象,要复用;
- 然后对于大对象,我们可以通过向一个自己定义的变量池取一个用,使用完放回去。这样第二次使用的时候就可以覆盖旧数据减少了垃圾回收的次数。
- 如果可能的话,使用更大内存的服务器,也可以非常有效地缓解这个问题。
- 如何避免这种情况呢,这里我们可以利用操作系统的能力,顺序读写,PageCache,零拷贝技术使得数据可以从socket直接存放到磁盘上,减少应用频繁的创建和销毁内存的垃圾回收操作。