3.19 腾讯CSIG面试
整体上比上次好多了,已经走到了最后部分,但是还是基础不算牢固。本次面试没有问道项目,而是实习和八股部分的询问,也错失了很多部分,需要进一步总结学习;
-
自我介绍,大方、自信、熟练一点
-
为什么不打算走本专业?(学历、选择的赛道问题)
-
项目经历:你这个项目是学校内部的项目吗?有部署到云端之类的吗?有没有人用呢?
- 一点思考:为什么会这样问?可能是觉得见的太多了,都已经知根知底,显然不会太受欢迎;部署的问题,可能是微服务架构部分,希望可以看到一些关注的架构处理方面的逻辑;有没有人用?说明服务项目的欢迎程度,如果可以上线部署(小程序等)进行搜索利用会好很多。
-
实习期间做了什么项目东西?有没有遇到过什么比较难的问题?自己参与的比较深,可以解决工作中的问题?
- 因为我部门属于网络组,负责的是网络交互、数据缓存、上传之类;我印象最深刻的是发送新闻稿,保存到本地来,对于比较热点的文章、数据进行缓存;
-
缓存用到的是什么数据结构?
-
由于我实习的组负责网页端新闻稿的存储和编写上传,对于新发布的热点文章,比如应用的场景是公司新产品的发布。
- 首先需要在数据库中进行表的创建,一般都是以文章自带的序列号为主,此时根据文章的标题、部门、发布的时间、发布存储的内网路径等创建对应的属性列;
- 对于热点的文章,利用redis进行缓存操作;
- 缓存redis利用的是Hash结构,其中的key为文章创建的编号和时间戳;将其中的field字段名设置为标题、部门、发布时间、内网存储路径,进行结构化的操作;
- 之所以要用时间戳,是因为可能存在重复的情况,比如插入更新数据的时候出现阻塞重复,利用时间戳可以更好地确保唯一标识性和快速检索
- 如果还是存在问题,那么会根据redis自带分布式锁和事务的原子性操作,进行唯一性创建文章;
- 除此之外,还可以利用有序集合进行操作,比如对多个需要在首页显示的热点文章,这时我们需要按照具体的业务需求进行score分数的排序缓存。比如hot1,hot2,hot3等,使用ZADD指令存储,设置优先级等;
- 有序集合的数据结构等等…
- 性能如何??(QPS/TPS等)
- 2000左右并发量等,我负责的部分是新闻稿的一个部门分支,在进行负载均衡的基础上,通过我添加的redis缓存,还是承载了部分的访问请求。在对热点文章进行访问请求的时候,效果比较显著的;
-
服务端负载均衡: 在服务端部署负载均衡器,例如使用 Nginx 或 HAProxy 等软件,它们可以根据一定的负载均衡算法(如轮询、最小连接数等)将用户请求分发给后端的多台服务器。这样可以避免单一服务器承担过大的压力,提高整个系统的处理能力。
-
DNS 负载均衡: 通过 DNS 负载均衡可以将用户请求分发到不同的服务器 IP 地址,实现基于域名解析的负载均衡。这种方式相对简单,但无法动态调整负载分配。
-
应用层负载均衡: 在应用层实现负载均衡,比如利用反向代理服务器(如 Nginx)将请求分发到不同的应用服务器上。这种方式可以根据具体的业务需求进行更灵活的负载均衡配置。
-
-
-
Java中的锁有哪几种?分别是什么实现方式??❌
在 Java 中,主要有以下几种类型的锁:
- synchronized 锁: synchronized 是 Java 中最基本的锁机制,可以修饰方法或代码块,实现对对象的互斥访问。在实现上,synchronized 使用的是悲观锁机制,通过对象头中的 Mark Word 来实现锁的获取和释放。
- ReentrantLock: ReentrantLock 是 java.util.concurrent.locks 包下提供的锁实现,它是显示锁,并且支持可重入性。ReentrantLock 提供了比 synchronized 更灵活的锁操作,比如可以设置公平性、超时等待、可中断等特性。
- ReadWriteLock(读写锁): ReadWriteLock 接口支持读-写分离的锁机制,允许多个线程同时读取共享数据,但在写操作时需要独占锁。ReentrantReadWriteLock 是 ReadWriteLock 的默认实现。
- StampedLock: StampedLock 是 Java 8 新增的锁机制,它包含三种访问模式:写锁、悲观读锁和乐观读。StampedLock 支持乐观读锁的同时,写入操作是独占的。
- LockSupport: LockSupport 是一种基于线程的阻塞原语,可以在没有使用传统锁的情况下实现线程的阻塞和唤醒。它通常和 CAS 操作一起使用,用于实现自定义的锁机制。
- 这些锁机制在 Java 中都有不同的实现方式,通过它们可以实现对共享资源的安全访问和控制。在选择锁的类型时,需要根据具体的场景需求来决定使用哪种锁机制,以确保线程之间的安全并发访问。
利用锁进行线程同步和资源共享;
-
选择合适的锁: 根据需求选择合适的锁机制,可以是 synchronized 关键字、ReentrantLock、ReadWriteLock、StampedLock 或其他锁类型。不同的锁有不同的特性和适用场景,需要根据具体情况选择适合的锁。
-
创建锁对象: 根据选择的锁类型创建相应的锁对象,通常是在类的成员变量中声明一个锁对象,保证多个线程可以共享这个锁对象。
-
加锁和解锁: 在访问共享资源之前,通过调用锁对象的加锁方法(如 lock())获取锁,以确保只有一个线程能够访问共享资源。在访问结束后,需要调用解锁方法(如 unlock())释放锁,让其他线程可以继续访问共享资源。
-
使用锁保护共享资源: 将需要保护的共享资源的访问部分用锁结构包裹起来,确保在访问共享资源时只有一个线程能够进入临界区,避免出现数据竞争和不一致性。
-
处理异常情况: 在使用锁的过程中,需要注意处理可能出现的异常情况,如加锁失败、解锁失败等,可以使用 try-finally 块确保锁的释放。
-
Java IO中的阻塞式IO和非阻塞式IO的区别和联系??❌
-
可以简单理解为需要做一件事能不能立即得到返回应答,如果不能立即获得返回,需要等待,那就阻塞了(进程或线程就阻塞在那了,不能做其它事情),否则就可以理解为非阻塞(在等待的过程中可以做其它事情)。
-
阻塞式IO:
- 特点:
- 当应用程序调用阻塞式 I/O 进行读写操作时,如果数据没有准备好或无法立即处理,则线程会被阻塞,直到数据准备好或操作完成。
- 阻塞式 I/O 在读取或写入数据时会一直等待,直到操作完成才返回结果给应用程序。
- 优点:
- 简单易用,编程模型直观。
- 缺点:
- 阻塞式 I/O 会导致线程阻塞,降低系统并发性能。
- 当大量连接需要处理时,会出现线程数量急剧增加,可能导致资源消耗过大。
- 特点:
-
非阻塞式IO:
- 特点:
- 非阻塞式 I/O 在进行读写操作时,如果数据没有准备好或无法立即处理,会立即返回而不会阻塞线程,让线程可以继续执行其他任务。
- 优点:
- 提高系统并发性能,一个线程可以处理多个 I/O 操作。
- 可以避免线程阻塞,提高系统的响应速度。
- 缺点:
- 编程模型相对复杂,需要不断轮询 I/O 的状态来确定是否可读取或写入数据。
- 非阻塞式 I/O 需要配合事件驱动等机制来实现高效的 I/O 处理。
区别和联系:
-
区别:
- 阻塞式 I/O 在读写操作时会阻塞线程,而非阻塞式 I/O 不会阻塞线程,可以继续执行其他任务。
- 非阻塞式 I/O 需要通过轮询或事件驱动等方式来检查 I/O 的状态,而阻塞式 I/O 则是等待数据准备好后再返回结果。
-
联系:
-
非阻塞式 I/O 可以基于阻塞式 I/O 进行实现,在底层仍然使用阻塞式 I/O 的系统调用,但通过设置非阻塞标志位来实现非阻塞操作。
-
在实际应用中,通常会将非阻塞式 I/O 和多路复用(如 Selector)结合起来使用,以实现高效的 I/O 处理和更好的系统性能。
-
- 特点:
- 聊聊Java中的反射机制?并且在Spring中是如何进行反射相关操作的?✔️
-
Java的反射机制是指运行时候动态获取类的信息并操作类或者对象的属性、方法、构造方法等。通过反射,我们可以在运行时获取类的信息、调用类的方法、访问类的属性,以及创建对象实例;
-
Spring框架中,反射被大量引入到依赖注入、AOP等功能的实现中。
-
获取类的信息:在Spring中,可以使用Class.forName()方法或者对象.getClass()方法来获取类的Class对象,然后通过Class对象可以获取类的构造方法、字段。
-
创建对象实例:通过反射机制动态创建对象,Class.new Instance()方法或者通过构造方法来实例化对象。
-
调用方法:动态调用,使用Method.invoke()实现方法的调用,可以传入对象实例和参数信息。
-
设置/获取属性:使用Field.set()和Field.get()方法实现对属性的设置和获取操作。
-
-
Java集合ArrayList和LinkedList的区别联系?哪个查找元素的速度快一点?✔️
ArrayList:
基于数组实现,内部以数组存储元素。
通过索引可以快速访问元素,即按照索引查找元素的速度很快。
添加或删除元素时,可能会涉及到数组的扩容和数据的移动,因此在插入和删除操作时可能会比较慢。
LinkedList:
基于链表实现,内部以链表存储元素,每个元素都有指向前一个和后一个元素的引用。
在任意位置添加或删除元素的速度较快,因为只需要改变相邻元素的引用即可,不需要像 ArrayList 那样涉及数组的扩容和数据的移动。
通过索引查找元素的速度较慢,因为需要从链表的头部或尾部开始遍历链表,直到找到对应位置的元素。
综上所述,在查找元素的速度方面,ArrayList 由于基于数组实现,可以通过索引快速定位到元素,因此查找元素的速度更快。而 LinkedList 在插入和删除元素时速度较快,但在查找元素时速度相对较慢。
-
JMM,java内存模型(这个相当庞杂)❌
-
实际上,对于 Java 来说,你可以把 JMM 看作是 Java 定义的并发编程相关的一组规范,除了抽象了线程和主内存之间的关系之外,其还规定了从 Java 源代码到 CPU 可执行指令的这个转化过程要遵守哪些和并发相关的原则和规范,其主要目的是为了简化多线程编程,增强程序可移植性的。
-
为此,JMM 抽象了 happens-before 原则来解决这个指令重排序问题。
-
Java 内存模型(JMM) 抽象了线程和主内存之间的关系,就比如说线程之间的共享变量必须存储在主内存中。
-
MySQL索引失效问题以及解决方案?✔️
-
没有遵循最左匹配的原则
-
模糊查询%,左匹配
-
添加索引字段上进行了运算操作或者类型转换
-
多个字段的复合索引中间使用范围查询,右边的条件索引也会失效
-
查询条件中使用到OR
-
IN的取值范围较大时候会导致索引失效,走的是全表查询
-
减少select * 进行全表查询,也有可能是先走二级索引、后走聚集索引获取全部的列,需要减少回表产生性能方面的问题;
-
创建组合索引,但是需要避免全表扫描;
-
尽可能简历联合索引而不是单列索引;每个索引都会对应着一颗B+树,如果一个表字段过多,索引占用的空间很大,修改成本也较大。
-
字符串类型的字段使用前缀索引代替普通索引;
-
-
MySQL索引用的是什么数据结构?✔️
-
在innodb索引结构就是改良版的B+树,在B+树的基础上叶子节点增加了双向链表;叶子节点存储键值和数据,非叶子节点只有这个索引字段的值。
-
对于数据的查找需要看是聚集索引还是二级索引,聚集索引则是全部的数据,二级索引则是索引字段的数据+id;
-
叶子节点是页为单位,而不是行,MySQL中的最小管理单元是页;
-
B+树索引结构,查询数据都从叶子节点进行查找,搜索效率比较稳定;同时由于是双向链表,可以支持范围查询和排序。
- 相较于B-tree,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页(固定内存大小16K)中存储的键值减少,指针也会跟着减少,为了同样保存大量数据,只能增加树的高度,从而导致性能会降低;
- 相对于Hash索引,B+Tree支持范围匹配以及排序操作,Hash索引并不支持,而是等值查找匹配。
-
-
MySQL数据库服务器飙升,会是什么原因造成的?✔️
- 大量并发连接: 当有大量并发连接到 MySQL 服务器时,服务器可能会出现负载过高、内存占用过多等问题,导致服务器性能下降甚至飙升。
- 复杂查询和索引失效: 复杂的查询或者未优化的查询语句可能会导致数据库服务器负载飙升。同时,如果索引设计不合理或者索引失效,会导致查询性能下降,从而影响服务器的稳定性。
- 缓冲区溢出: 如果配置的缓冲区大小不足以处理当前的工作负载,可能会导致缓冲区溢出,进而引起服务器飙升。
- 慢查询: 大量的慢查询可能会导致服务器性能下降,特别是在高负载环境下,会导致服务器飙升。
- 大量临时表或排序操作: 如果查询需要大量的临时表或者排序操作,会消耗大量的内存和 CPU 资源,导致服务器性能下降。
- 未及时释放资源: 如果应用程序或者数据库操作没有及时释放连接、内存或者其他资源,可能会导致内存泄漏,最终导致服务器飙升。
- 硬件故障: 服务器硬件故障或者资源不足也可能导致服务器性能下降,包括磁盘故障、内存故障等。
需要利用日志log分析等
-
缓存中的生产问题(缓存击穿、穿透、雪崩问题,原因、解决方式)✔️
- 总结来说,缓存雪崩是因为缓存中大量数据同时失效导致的问题;缓存击穿是因为某个热点数据未命中缓存导致的问题;而缓存穿透是因为恶意请求查询不存在数据导致的问题。针对这些问题,可以通过设置合理的缓存策略、使用互斥锁、采用预加载数据等方法来进行缓解和解决。
- 具体细节烂熟于心~
-
编程题目✔️
-
冒泡、快速(时间、空间复杂度)
-
反转链表
-
总的来说,面试整体感受还可以,就是在多线程、并发、内存、JVM这一类知识点不够完整,后续要抓紧时间补充完整了!
实习中的工作部分还要细分,完整解释其中承担的任务等;
八股深入理解,最好结合具体的案例进行解释回答,做好全方位被拷打的准备;
继续努力吧,后续还有美团的面试,加油💪