网络协议和Netty常见面试题汇总
-
说一下TCP的三次握手过程
-
为什么TCP握手需要三次?
-
解释一下TCP的四次挥手
-
为什么TCP的四次挥手要有TIME_WAIT状态?
-
概述下什么是DDOS攻击和SYN洪水攻击
-
DDOS攻击利用一个合理的服务请求去占用非常多的服务资源,导致正常请求无法成功,也叫拒绝服务攻击。常见的有计算机带宽攻击、连通性攻击
1.带宽攻击是以极大的通讯量去冲击网络,使网络资源消耗殆尽,导致合法的请求无法响应
2.连通性攻击是以大量的连接请求去冲击服务器,有syn洪水攻击,伪造一个客户端请求,服务器必然要应答,但是这个请求是虚假ip地址,无法有客户端收到,服务器等待,永远无法收到客户端发送的第三次握手报文
-
-
哪些应用比较适合用UDP实现?
-
1.tcp是单播,UDP在需要多播的时候
-
2.多媒体、视频流,丢了一两帧基本没有影响的
-
3.DNS、QQ聊天模块
-
4.怎么设计QQ的网络协议?
(1)udp
(2)登陆的时候采用tcp协议,发送登陆信息
(3)好友间发送消息,用udp,但是还需要用某种协议保证消息的可靠传输,因为有可能还要转发
(4)传送文件,内网用户应用层用P2P技术来传输,可以加快进度
-
-
HTTP和HTTPS的区别
-
HTTPS是HTTP加上一个ssl
-
HTTP是明文
-
HTTP端口是80,HTTPS是443
-
HTTPS传输的数据是加密的
-
HTTPS需要第三方证书认证
-
HTTPS通讯过程
1.客户端用https的url访问服务器,要求与服务器建立ssl连接
2.服务器会将网站的认证证书信息,连同服务器公钥,一起发送给客户端
3.客户端收到认证证书,会去第三方机构校验证书是否有效,之后浏览器会生成一个会话密钥,并用服务器公钥加密
4.服务器收到密钥的密文,用自己保持的私钥进行解密,获得会话密钥,之后服务器之间的会话过程都是通过会话密钥进行加密
-
-
Netty的特点?为什么要用Netty?
- 1.netty是高性能、异步事件驱动的nio框架,并且对tcp、udp等协议都支持
- 2.底层使用高性能socket底层,对于epoll空轮训引起的性能飙升进行了处理
- 3.引入了编解码、粘包、半包、序列化等等相关问题支持,各种handler
- 4.使用线程池技术,对重连、心跳检测支持
- 5.提供了网络相关大量参数的配置,io线程数、tcp参数
- 6.使用直接内存替代堆内存,使用线程池循环利用内存,降低gc频率
- 7.源码使用并发编程(volatile、cas、原子性、读写锁使用)实现,提高了多线程使用时的性能
- 8.eventloop单线程线程池串行执行
-
概述下Netty的线程模型
- 反应堆模型有3中,单线程、多线程、主从多线程,neety都支持
- boss线程和work线程,boss处理连接请求,work处理网络的实际读写
-
Netty 中有哪些重要组件?
- eventloop
- 单线程线程池,配合channel处理io事件
- channel
- 基本网络抽象类,绑定、连接、读写
- channelhandler
- 出站和入站的处理器
- channelpipeline
- channelhandlerContext
- channelfuture
- eventloop
-
TCP 粘包/拆包的原因及解决方法?
-
因为tcp是以流的形式处理数据的,一个完整的包有可能拆分成多个,也有可能把小的包合并成一个大的包发送,如果使用nodelay就是第二种
-
怎么解决?
1.消息定长
2.指定分隔符
3.消息头加消息体
-
-
请概要介绍下序列化
- 序列化是把对象序列化成一个二进制形式的字节数组,用于网络数据传输和数据持久化
- 反序列化是把网络、磁盘读取的字节数组,还原成原始数组
- 怎么选择序列化?
- 1.序列化后的字节数组大小,越小越好
- 2.序列化的过程占用的cpu,速度越快越好,因为可能要进行大量数据的序列化
- 3.是不是跨语言
- 常用的序列化
- 1.xml,定义各种标签,附带信息太多
- 2.json,兼容性高,数据格式简单,自解释的,弱点是描述性比xml差,传递是字符串,额外空间开销大,可以通过http形式传,可以跨防火墙
- 3.probuf
- 4.messagepack
-
Netty是如何解决JDK中的Selector BUG的?
-
从jdk的1.6一直跨越到jdk的1.8,jdk认为这个bug应该是linux修改,所以一直没改
-
linux2.6中poll和epoll,对socket的处理上,如果socket突然中断连接,会把poll和epoll支持的事件集发生变化,poll_hup和poll_error,前面写nio会通过select()方法进行阻塞,当有事件发生时,才会返回,如果事件集变化,select会被唤醒,返回的事件数是0个,因为nio监听是通过一个while循环实现的,因为select被唤醒的了,事件数是0,所以没有任何执行,又循环到第二次进行下一轮的select,select里面有事件集,所以又不阻塞,但是检测出来的事件又不是我们关心的那四种,所以又是0 ,又进行下一轮的select,然后不断循环,很快cpu会飙升到100
-
因为我们关注的事件集是增加连接、连接、读、写这四种,但是上面中断后检索出来的事件集没有这四种,是poll_hup和poll_error,并且没有删除这些增加的事件集,所以会不断死循环
-
解决这个空轮训有两种方式?
(1).删除selector上面关注的所有事件key,重新刷新一下selector
jetty小组测试过这种方法,这种不能100%解决,100%降低到10%发生,只是降低几率,发生了仍然无法解决
(2).创建一个全新的selector,把老的selector注册的key全部复制到新的selector,关闭掉老的selector
netty就是这么解决的,会周期性统计select操作,如果在某个周期了,发送了若干次空轮训,认为已经触发了空轮训bug,就会按(2)方法解决
-
-
Netty 高性能表现在哪些方面?
- 高效并发
- io线程池模型,actor模式
-
Netty 发送消息有几种方式?
-
channel.write
所有的出站handler都会走一遍
-
ctx.write
会抄近道,离它最近的handler开始执行
-
-
Netty的内存管理机制是什么?
- netty的内存池,申请一大块内存,Arena(竞技场),划分成很多很多的chunk,chunk进行进行划分,每一个小块称为page页,每个chunk包含2048个page,chunk通过avl树对这些page进行管理,每当有一个page分配出去,avl树就会对这个page打上标记
- 每次网络请求需要的内存很小,会把page进行更小的划分,subpage,通过链表管理这个subpage
- AreaN --> Chunk --> 2048Page(AVL管理) --> subpage
- 基于Jemalloc算法实现
-
ByteBuf的特点
- ByteBuff的特点
- 1.支持自动扩容
- 2.实现零拷贝
- 3.引用使用了原子变量
- 4.支持内存池化技术
- 5.不再需要flip进行读写模式切换
-
如何让单机下基于Netty的应用程序支持几十万乃至百万长连接?
-
tcp连接是虚拟的,不像打电话是真实的物理连接
-
难点?
1.突破操作系统的限制,单一进程能够打开的文件数据,每一个网络请求都会创建一个socket文件句柄,linux里面一切皆文件FD(file description)
(1)MobaXtem软件下终端命令输入ulimit -n,输出1024,说明每个进程最多打开1024个文件,操作系统本身要占用3个,标准输入,标准输出,标准错误,还有其他的,最后真正可以用的只有1014个
ulimit -n 1000000则修改成了100万
(2)vim /etc/security/limits.conf 修改这个文件
修改nofile参数
2.应用程序也要优化,线程池也要调整,看是卡在哪里,是accept,还是handler方法,确定是修改boss线程池还是work线程池
3.对心跳也要优化?
设计比较合适的心跳周期和超时时长,都要短一点,让未连接和死链接尽早剔除
4.发送缓存区和接受缓存区都要调?
socket连接一旦建立,linux操作系统会自动建立发送缓存区和接受缓存区,缺省时默认4k,即使socket不做任何事情,一百万连接就要占8000000K,如果传的数据不是很大,不是传文件,适当调小缓存区,调成1k或者2k
5.尽可能使用内存池技术,使用Pooled,而不是Unpooled,Unpooled使用起来简单
6.io线程和业务线程尽可能进行分离
之前写的都是在handler里面处理业务,应该把这个业务分发到别的线程池进行处理,例如可以写到阻塞队列中
7.流量控制?
为了保持系统稳定性,自己的应用程序要实现流控,第三方流控要做,自己的流控也要做,新增一个FlowCtrolHandler,写一个AtomicInteger计数器,每新增一个连接,就加一,如果超过100万,就关闭channel
8.JVM调优?
如果服务器发生了一次gc,5秒的gc就会导致海量的客户端设备掉线或者消息挤压,一旦gc,海量的客户端数据就会涌过来,就会把服务器冲垮
(1).数据采集
(2).gc日志设置jvm的合理大小
(3).垃圾回收算法选择
(4).代码调整,50%是根据gc信息调整自己的代码
(5)调优原则?
a.尽可能让垃圾回收发生在新生代,老年代越小越好
b.gc内存的最大化原则,每一次垃圾回收的内存越多越好
c.追求两个性能指标,吞吐量和延迟,无法兼顾两个参数,追求吞吐量,延迟就会高,延迟就是让垃圾回收时间越短越好,一般情况是吞吐量优先,具体需要根据业务选择
9.机器内存至少是32g、64g起了
-
-
select、poll、epoll的区别?
-
select,io多路复用,让程序可以监视多个文件描述符,select能够同时打开的最大文件数是2048
-
poll没有最大连接数的限制,因为是链表实现的
-
区别
1.这三个支持的最大连接数是有区别的
2.select和poll是遍历算法,时间复杂度是O(N),打开的文件数越多,select和poll的性能会下降,epoll的时间复杂度是O(1)的,性能更高,是通过回调函数实现的,不会遍历,也就是io效率问题
3.消息的传递方式,当有socket被激活后,select和poll都需要从内核空间,把消息传递到用户空间,出现了一次内核空间到用户空间的转换,epoll是内存空间和用户空间共享一份内存,不需要转换
4.性能并不是epoll一定好,在连接数比较少,并且连接都比较活跃的时候select和poll性能更好,因为epoll的通知机制需要调用大量的回调函数进行相关处理
5.从兼容性来说,select是所有操作系统都支持的,epoll是linux下才支持的
-
-
什么是水平触发(LT)和边缘触发(ET)?
-
当有事件发生了,select会接受通知,进行相关的读写
-
水平触发?
数据放到缓存区写满了4k,现在这一次网络读写读走了2k,水平触发会在下一次select时通知你,如果一直不读这2k,select会一直通知你
-
边缘触发?
就是上面的场景只通知一次,只有当对端又传了新的数据了,select才会又通知你
-
select和poll都是水平触发,epoll两种都支持,默认是水平触发
-
在windows下用jdk是水平触发,是用的select,但是是在linux下用jdk,缺省情况下也是水平触发,使用的是epoll机制,默认是水平触发
-
直接内存深入辨析
直接内存比堆内存快在哪里?
-
TCP缓冲区
- 每个TCP的Socket的内核中都有一个发送缓冲区(SO_SNDBUF)和一个接收缓冲区(SO_RECVBUF)。
- 当建立tcp连接,通讯两端都会有各自的输入缓存区和输出缓存区
- 当使用write方法和read方法,都是从缓存区中操作的
-
当要发送数据时,用户进程把自己应用进程缓冲区的数据,写入到套接字发送缓冲区(SO_SNDBUF)中,这个缓存区是可以tcp参数修改的,可大可小,
-
java的堆上存在垃圾回收特性,当使用write方法写数据时,jdk是写在堆内存里面的,所以write先创建直接内存,把数据从堆内存复制到直接内存中,然后再发送操作系统的发送缓存区,这是jdk内部机制所约束的
-
当我们要把一块内存区域的数据交给发送缓存区的时候,有一个基本要求,内存地址的内容是不能失效的,gc机制里面,不管是哪个地址,都可能存在地址块的移动,gc后,原先地址的内容失效了,所以需要放在一个gc管不到的地方,所以先复制到直接内存上
-
为什么直接内存快?
1.因为堆内存要多复制一次,直接内存直接使用就可以了
2.gc只有在fullgc的时候,老年代回收的时候才会顺便执行直接内存的回收,所以比放在堆上的压力小
直接内存比堆内存快在哪里?
-
1.直接内存相比堆内存,避免了二次拷贝
-
2.减少了垃圾回收的工作,如果在堆上,每次垃圾回收都要处理
-
缺点
- 直接内存不受垃圾回收管理,比较难以控制,发送内存泄漏了不容易排查
- 管理方式不如堆内存完善,所以只存简单数据是可以的,比如字节数组
零拷贝深入辨析
什么是零拷贝?
-
零拷贝(英语: Zero-copy) 技术是指计算机执行操作时,CPU不需要先将数据从某处内存复制到另一个特定区域,不需要cpu干预。这种技术通常用于通过网络传输文件时节省CPU周期和内存带宽。
- 零拷贝技术可以减少数据拷贝和共享总线操作的次数,消除传输数据在存储器之间不必要的中间拷贝次数,从而有效地提高数据传输效率
- 零拷贝技术减少了用户进程地址空间和内核地址空间之间因为上下文切换而带来的开销
-
并不是不拷贝,而是减少拷贝
-
生态圈框架
- Kafka
- Netty
- Rocketmq
- Nginx
- Apache
linux的I/O机制与DMA
-
早期计算机里面,cpu、内存、硬盘,如果要把硬盘中的数据交到内存中,都需要cpu的干预,假设需要读取磁盘中的文件,向cpu发送中断请求,cpu会把当前工作停下来,读取磁盘中数据,拷贝到内存中去,这个过程需要cpu的全程参与,是一种很大的性能浪费
-
DMA-----直接内存读取,direct memory access
- 现在的电脑基本都是这个,不再需要cpu干预
- 当我们要读取磁盘文件,cpu指向硬盘上的一个DMA控制器,告诉它我们要读取哪里到哪里的数据,然后cpu就不管了,接下来由DMA控制器接管,完成读取的操作
-
网卡、磁盘都有DMA
-
使用DMA后,不管是文件io、还是网络io,都分成两个过程,DMA首先把数据拷贝操作系统内核缓冲区,然后操作系统再把数据从内核缓冲区拷贝到用户进程所属的空间中去,这个过程从dma到操作系统内核cpu是不干预的,从内核到用户中去cpu是干预的
传统数据传送
-
buffer = File.read Socket.send(buffer)
-
从磁盘读取一段时间,再通过buffer通过网络发送,在这个过程总共需要几次拷贝?
-
1.先是DMA将磁盘上的数据拷贝到内核的文件读取缓冲区
-
2.然后再由cpu由文件读取缓冲区拷贝到应用进程缓冲区
-
3.然后再由cpu由应用进程缓冲区拷贝到套接字发送缓存区
-
4.再由网卡DMA由套接字发送缓冲区拷贝到网卡上面的网络设备缓存区上
-
中间发生了4次拷贝,4次上下文切换
-
执行File.read方法,用户态开始read,告诉内核态,从用户态切合到内核态,然后read完成,通知用户,又从内核态切换到用户态,前两次拷贝完成,执行Socket.send(buffer)方法,用户态开始send,调用网卡相关工具,又要管理硬件,又从用户态切换到内核态,由操作系统相关进程将数据从应用缓存区拷贝到套接字发送缓存区,并通过网络缓存区发送出去,发送完成后,又从内核态切换到用户态
-
-
从文件读取缓存区到套接字发送缓存区,数据没有发生任何变化,为什么不直接从文件到套接字呢,更加省事,之前cpu干预的第二个和第三个的拷贝,实际上是没有必要的,反而带来了额外的开销,这也就是零拷贝出现的背景和意义
linux常见支持的零拷贝技术有哪几种
1.Linuxz之MMAP内存映射
- 内存映射,在磁盘中的一块内存区域和应用进程的缓存的一块内存对应起来,比如磁盘的1-100行对应应用进程内存的9-109,直接映射过去,
- 所以就减少了第二个的拷贝,因此只有3次拷贝,4次上下文切换
2.Linux之sendfile
- linux 2.1提出的支持零拷贝的技术
- 调用sendfile这个系统调用时,DMA从磁盘拷贝到文件读取缓冲区,然后cpu从文件读取缓冲区拷贝到套接字发送缓冲区,再从DMA拷贝到网络设备缓冲区中
- 比内存映射强在哪里?
- 中间从文件读取缓存区拷贝到套接字发送缓存区时,操作系统并不会真正进行相关复制,只会相关数据记录的长度,起始地址和描述符发送到套接字发送缓存区里面,第四步的DMA拷贝,是直接从文件读取缓冲区,需要硬件设备支持,因为网络设备需要去读取磁盘的文件缓存区
- 3次拷贝,4次上下文切换,因为1次系统调用对应两次上下文切换
- mmap一般是小文件,sendfile一般是大文件
3.Linux之slice
- 用到了linux里面管道通讯机制
- 当数据拷贝到文件读取缓冲区后,会通过管道PIPE自动传递到套接字发送缓冲区中
- 只有2次拷贝,2次上下文切换
- 不需要设备支持,使用的是操作系统内部的一种管道机制
PIPE
- 本质是共享了一块内存区域
零拷贝概念的变化
- 零拷贝是由sendfile中直接引入的
- 零拷贝中cpu完全不干预的并不多,于是对零拷贝概念做了延伸,只要没有不必要的数据拷贝都是零拷贝
Java生态圈中的零拷贝
-
NIO
-
jdk中只支持前两种
1.mmap在jdk中有MappedByteBuffer
2.sendfile在jdk中有FileChannel----transferFrom和transferTo
-
-
Kafka
- 消息中间件broker、生产者、消费者
- 生产者发送到broker后,需要持久化,通过mmap快速写入到本地磁盘,从broker发送到消费者时,是通过sendfile的方式发送的
-
Netty的零拷贝实现
- 网络通讯支持使用直接内存读取网络数据
- netty是支持compositedByteBuf,它不是真正意义上的buffer,它是把多个buffer拼成一个视图,让我们感觉操作的时候是操作一个一个的视图,如果只操作一个视图,需要合并数据,就需要拷贝
- 文件传输时也调用了FileChannel中的transferFrom和transferTo