先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新大数据全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip204888 (备注大数据)
正文
3、项目在面试中可能遇到的问题
3.1 在RPC项目中使用短链接有什么问题?如何实现长连接
由于采用TCP作为传输协议,所以若采用短连接,则每次传输都要建立连接和断开连接,有很多网络通信消耗,这和开发预期的高性能RPC不符合。
利用Zookeeper分布式服务器,将服务端注册入Zookeeper服务器中,而客户端每次都从zookeeper服务器中获取服务端地址,连接后将返回的ChannelFuture存进一个set里,每次发送请求都从set从取出ChannelFuture发送信息,并且发送消息完毕后并不关闭连接,这就保证了客户端和服务端的长连接。
3.2 项目是基于TCP还是HTTP来做的,为什么用TCP实现呢?
要做长连接的话,是不能用http协议来做的,因为http协议已经是应用层协议了,并且http协议是无状态的,而我们要做长连接,肯定是需要在应用层封装自己的业务,所以就需要基于TCP协议来做。
同时TCP协议可以保证到达应用层的数据是正确无误的,是可靠的网络传输协议,这对于RPC通信框架来说是不可缺少的。
3.3 同步阻塞调用性能瓶颈:有什么瓶颈?你是怎么解决的,怎么实现异步调用的?
同步阻塞的缺点是调用时线程会阻塞在请求中,cpu此时并不会切换到其他线程,若服务器不返回消息,则客户端会一直被阻塞,无法处理其他请求。
Netty是基于NIO开发的,采用了异步非阻塞通信方式,本项目采用了netty服务框架实现了客户端服务端开发。
3.4 为什么需要心跳检测,客户端发送请求服务端没回应会造成什么?心跳检测机制你是怎么实现的?
在系统空闲时,例如凌晨,往往没有业务消息。如果此时链路被防火墙Hang住,或者遭遇网络闪断、网络单通等,通信双方无法识别出这类链路异常。等到第二天业务高峰期到来时,瞬间的海量业务冲击会导致消息积压无法发送给对方,由于链路的重建需要时间,这期间业务会大量失败(集群或者分布式组网情况会好一些)。
为了解决这个问题,需要周期性的心跳对链路进行有效性检测,一旦发生问题,可以及时关闭链路,重建TCP连接。
当有业务消息时,无须心跳检测,可以由业务消息进行链路可用性检测。所以心跳消息往往是在链路空闲时发送的。
使用IdleStateHandler实现心跳检测机制,IdleStateHandler提供了3种心跳检测方法:
● 读空闲超时机制:当连续周期T没有消息可读时,触发超时Handler, 用户可以基于读空闲超时发送心跳消息,进行链路检测;如果连续N个周期仍然没有读取到心跳消息,可以主动关闭链路。
● 写空闲超时机制: 当连续周期T没有消息要发送时,触发超时Handler,用户可以基于写空闲超时发送心跳消息,进行链路检测;如果连续N个周期仍然没有接收到对方的心跳消息,可以主动关闭链路。
● 读写空闲超时机制:以上两者的结合
重写userEventTriggered方法,根据用于业务需要,判断event触发事件,当产生读空闲、写空闲、读写空闲时所需要进行的操作,如在发生读写空闲时,服务端发送“pong”,客户端发送“ping”做回应。
3.5 什么叫做序列化?Java原生序列化有什么问题?为什么用JSON,怎么实现的JSON序列化,和JAVA序列化速度的差异?
序列化就是将类对象编码成可在网络中传输的二进制数据,jdk原生序列化有下面三个问题:
-
Java序列化机制是Java内部的一种对象编解码技术,无法跨语言使用。例如对于异构系统之间的对接,Java序列化后的码流需要能够通过其他语言反序列化成原始对象(副本),目前很难支持。
-
相比于其他开源的序列化框架,Java序列化后的码流太大,无论是网络传输还是持久化到磁盘,都会导致额外的资源占用。
-
序列化性能差,资源占用率高(主要是CPU资源占用高)。
使用fastjson的原因是这种序列化方式简单明了,且相比jdk序列化编解码速度快,序列化后码流较小。
3.6 为什么选择使用JSON,应该有其他更快的序列化方式吧?
JSON简单通用,使用广泛,有较好的扩展性和可读性。与其他的二进制序列化方式对比,JSON序列化的可读性会好很多,而且JSON可以跨多语言进行使用,通用性好。
3.7 你还知道哪些序列化的方式?
以上几个问题
速度方面:
序列化后大小方面:
其他序列化方式主要有:protobuf和protostuff,hessian等
Hessian比JSON性能要高,但对java一些常见的对象类型不支持,如Linked系列、Locale类等等
Protobuf是由谷歌公司开发的数据语言,支持java、C++、python等多平台,序列化后体积较小,转化性能较高。但需要预编译IDL。
Protostuff 不需要依赖 IDL 文件,可以直接对 Java 领域对象进行反/序列化操作,在效率上跟 Protobuf 差不多,生成的二进制格式和 Protobuf 是完全相同的,可以说是一个 Java版本的 Protobuf 序列化框架。但不支持null,也不支持单纯的 Map、List 集合对象,需要包在对象里面。
3.8 你是怎么基于动态代理进行的请求处理?用的哪一种?为什么使用这种?
使用SpringCGLIB进行动态代理,在intercept方法里对方法进行横切扩展,主要是将请求参数包装成ClientRequest类,并使用Netty客户端将请求发送出去
使用SpringCGLIB的优点就是可以对类进行代理,而jdk的动态代理只能针对接口。
3.9 你的项目哪里遇到进程通信问题了?
当大量客户端同时向服务器发送请求时,服务器响应可能会产生并发问题,导致出现进程通信问题。
3.10 怎么解决RPC项目中的进程通信问题?
通过引入wait和notify机制,使用ReentryLock和Condition进行控制。
① 创建锁
② 在主线程获取数据前,先等待结果
③ 在获取结果时,上锁,防止多线程同时获得结果
④ 如果没有获得结果,则进入Condition进行等待,并在finally释放锁
⑤ 获得结果,则调用signal唤醒锁。
3.11 还有哪些解决进程通信问题的方式?
Synchronized:重量级锁;volatile乐观锁;CAS机制
3.12 TCP/IP 拆包粘包能简单介绍下么?你怎么解决的呢?有没有更好的解决方式?
由于TCP协议是基于流的协议,没有消息边界,客户端发送数据时,由于TCP的neagle算法,为了减少通信消耗,会将多次发送的数据组合在一起发送,而在发送过程中有可能会对数据包进行分片,而服务端收到消息时,由于没有指定消息边界,因此多条消息的数据有可能连在一起,这就是粘包现象,还有一种可能是一条消息过长,因此被分片后服务器得到的数据只有消息的部分,这就是半包。
这种现象发生的原因有以下几种:应用程序write写入的字节大于套接字发送缓冲区;TCP进行MSS分片;以太网帧payload大于MTU。
我采用分隔符的方法解决TCP的粘包半包问题,使用DelimiterBasedFrameDecoder这一handler,指定分隔符为”/r/n”,每次发送消息时都会在消息后面加上这一分隔符,这样服务器就可以在应用层分辨出消息边界。
更好的解决方法可以采用“消息头+消息体”的方法,每条请求的消息头表明该消息的长度,可以使用LengthFieldBasedFrameDecoder这一handler标明数据长度。其他方法还有固定长度:FixedLengthFrameDecoder,或者短连接方式(但这不符合开发目的)。
3.13 你是怎么基于BeanPostProcessor 机制和 ApplicationListener 机制实现客户端的自启动与基于注解的服务调用?
这个问题比较复杂,
首先客户端类TcpClient的静态代码块会初始化一个netty客户端,因此在BeanPostProcessor中完成请求的包装后,调用TcpClient.send()方法就实现了客户端的初始化自启动。
而服务端则将初始化代码放在一个start()方法里,同时服务端实现了ApplicationListener,并在onApplicationEvent方法里调用服务端的start方法,这样当Spring启动完成后,监听器监听到ContextRefreshedEvent,就会启动netty server。
基于注解的服务调用方面,我将service的实现类标注了@Remote注解,这是一个自定义注解,同时在Spring启动过程中会有一个BeanPostProcessor,对@Remote注解标注的类进行处理,主要是将service接口的各种方法与接口进行映射;而客户端则对service的接口标注了@RemoteInvoke,在BeanPostProcessor的实现类中对被@RemoteInvoke标注的接口完成方法与接口的映射,这样服务器的map和客户端的map里关于方法和类名都是一致的,因此服务端可以知道该执行什么处理。
3.14 注册中心你是怎么实现的?用的什么?
对每个服务器设置一个序列号,每次启动一个服务器都会将该服务器注册同一路径下,保存该服务器地址和端口号。
用的是基于zookeeper的服务注册。
3.15 为什么RPC项目用Zookeeper作为注册中心?
zookeeper中curator中的watcher机制,可以实现客户端连接的动态管理、监听和发现功能,并实现了服务器注册功能,扩展项目功能。
3.16 你的服务结点结构是怎样的?
路径/+IP地址+“#”+端口号+“#”(+权重+“#”)
3.17 那么当有新的服务上线或者旧服务下线的时候,你怎么保证得到最新结果?客户端怎么发现服务?怎么动态监听连接?给我讲一讲机制?
使用CuratorWatcher,构建一个实现Curator接口的ServerWatcher实现类,当客户端启动时,将watcher注册到自身的CuratorFramework上,并让其监视zookeeper的/netty目录节点,当该节点有变动——增加或减少时,产生的event就会触发CuratorWatcher的process方法,在这个方法里清除先前保存的服务器信息,重新查询所有服务端节点并连接,将返回的channelFuture保存到一个set里。
由于一个watcher注册一次只会监听一次,因此在process方法里需要再次注册watcher到CuratorFramework。
3.18 给我讲一讲你项目中你认为最难的点以及你的解决方式?
基于Spring注解和动态代理实现对客户端和服务端请求和响应的调用。
3.19 你的项目有哪些改进的地方?你想改进哪里?
- 序列化方式,可以采用速度更快的protobuf或者protostuff
- 针对客户端连接,改进负载均衡算法
- 给服务端增加流量控制功能
- 就注册中心而言,采用eureka代替zookeeper可以获得更高的服务可用性,当然这会导致数据一致性方面不是很好,客户端获取到的节点是过时的,不过由于有wacther的存在,请求失败一两次是可以接受的
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)
[外链图片转存中…(img-akAGTsLB-1713188519104)]
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!