架构解密从分布式到微服务:深入理解网络,NIO

NIO

我们知道,分布式系统的基础是网络。因此,网络编程是分布式软件工程师和架构师的必备技能之一,而且随着当前大数据和实时计算技术的兴起,高性能RPC架构与网络编程技术再次成为焦点。不管是RPC领域的ZeroC Ice、Thrift,还是经典分布式框架Actor模型中的Akka,或者实时流领域的Storm、Spark、 Flink, 又或者开源分布式数据库中的Mycat、VoltDB, 这些高大上产品的底层通信技术都采用了NIO (非阻塞通信)通信技术。而Java领域里大名鼎鼎的NIO框架一Netty, 则被众多的开源项目或商业软件所采用。

相对于它的老前辈BIO (阻塞通信)来说,NIO 模型非常复杂,以至于我们难以精通它,难以编写出一个没有缺陷、高效且适应各种意外情况的稳定的NIO通信模块。之所以会出现这样的问题,是因为NIO编程不是单纯的一个技术点,而是涵盖了一系列相关技术、专业知识、编程经验和编程技巧的复杂工程。

架构解密从分布式到微服务:深入理解网络,NIO

 

难懂的ByteBuffer

Java NIO抛弃了我们所熟悉的Stream、byte[]等数据结构, 设计了一个全新的数据结构——ByteBuffer, ByteBuffer 的主要使用场景是保存从Socket中读取的输入字节流并循环利用,以减少GC的压力。Java NIO功能强大,但难以掌握。以经典的Echo服务器为例,其核心是读入客户端发来的数据,并且回写给客户端,这段代码用ByteBuffer来实现,大致就是下面的逻辑:

1 byteBuffer = ByteBuffer .allocate (N) ;
2 / /读取数据,写入byteBuffer
3 readableByteChannel. read (byteBuffer) ;
6 / /读取byteBuffer, 写入Channel
7 writableByteChannel .write (byteBuffer) ;

如果我们能马上发现在上述代码中存在一个严重缺陷且无法正常工作,那么说明我们的确精通了ByteBuffer 的用法。这段代码的缺陷是在第6行之前少了一个byeBuffreflip()调用。

之所以ByteBuffer 会设计这样一个名称奇怪的Method,是因为它与我们所熟悉的InputStream &OutStream分别操作输入输出流的传统I/O设计方式不同,是“二合一”的设计方式。我们可以把ByteBuffer设想成内部拥有-一个固定长度的Byte数组的对象,属性capacity 为数组的长度(不可变),position 变量保存当前读(或写)的位置,limit 变量为当前可读或可写的位置上限。当Byte被写入ByteBuffer中时,position++, 而0到position之间的字符就是已经写入的字符。如果后面要读取之前写入的这些字符,则需要将position重置为0,limit 则被设置为之前position的值,这个操作恰好就是flip 要做的事情,这样一来, position 到limit之间的字符刚好是要读的全部数据。

ByeBuffer有三种实现方式:第一种 是堆内存储数据的HeapByteBuffer;第二种是堆外存储数据的DirectByteBuffer; 第三种是文件映射( 数据存储到文件中)的MappedByteBuffer。

HeapByteBuffer将数据保存在JVM堆内存中,我们知道64位JVM的堆内存在最大为32GB时内存利用率最高,一旦堆超过了32GB,就进入64位的世界里了,应用程序的可用堆空间就会减小。另外,过大的JVM堆内存也容易导致复杂的GC问题,因此最好的办法是采用堆外内存,堆外内存的管理由程序员自己控制,类似于C语言的直接内存管理。DirectByteBuffer 是采用堆外内存来存放数据的,因此在访问性能提升的同时带来了复杂的动态内存管理问题。而动态内存管理是一项高端编程技术,涵盖了内存分配性能、内存回收、内存碎片化、内存利用率等一系列复杂问题。

在内存分配性能方面,我们通常会在Java里采用ThreadLocal 对象来实现多线程本地化分配的思路,即每个线程都拥有一个ThreadL ocal类型的ByteBufferPool,然后每个线程都管理各自的内存分配和回收问题,避免共享资源导致的竞争问题。Grizzy NIO 框架中的ByteBufferThreadL ocalPool,就采用了ThreadLocal 结合ByteBuffer 视图的动态内存管理技术:

架构解密从分布式到微服务:深入理解网络,NIO

 

架构解密从分布式到微服务:深入理解网络,NIO

 

上面的代码很简单也很经典,可以分配任意大小的内存块,但存在一个问题:它只能从Pool的当前位置持续往下分配空间,而中间被回收的内存块是无法立即被分配的,因此内存利用率不高。另外,当后面分配的内存没有被及时释放时,会发生内存溢出,即使前面分配的内存早已释放大半。其实上述问题可以通过一-

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值