【美团】平台技术部一面面经|讲解|0511

今天挑选一篇【美团平台技术部一面面经】,给大家做讲解分析~

alt

感谢这位同学的分享,预祝Offer多多~~~ 原贴链接

本文也是 《热门面经讲解》 专栏系列文章之一,大家可以点跳转链接,加个关注和订阅,我会持续更新~

自产《大厂后端Top100面试题讲解》对本篇面经题目覆盖率:9/9 = 100%

自产《大厂后端Top200面试题讲解》对本篇面经题目覆盖率:9/9 = 100%

讲解开始~~~~~

1. 【MySQL】什么是事务的隔离级别?区别是?幻读是什么意思?

解析

属于高频考题:《大厂后端Top100面试题讲解|第8题》

所属专项:MySQL|事务

专项考察占比:

MySQL】面试中考察MySQL的比率:大厂:18%腾讯:13%阿里:17%

MySQL-事务】面试考察MySQL时,问“事务”相关问题的比率:18%

参考口述回答

  1. 读未提交(Read Uncommitted)

    1. 这是最低的隔离级别。在此级别下,一个事务可以读取另一个事务尚未提交的数据。
    2. 这种级别的问题在于,它可能导致“脏读”(Dirty Read)问题,即读取到未经确认的临时数据。
    3. 由于这个原因,这个隔离级别在实际应用中很少使用。
  2. 读已提交(Read Committed)

    1. 在这个级别,每个查询都会在事务执行时获取当前可见的数据。
    2. 它可以避免脏读,因为只能读取已提交的数据。
    3. 但这种级别可能导致“不可重复读”(Non-repeatable Read)问题,即同一个查询在多次执行时可能读取到不同的数据,因为其他事务可能在此期间修改了数据。
  3. 可重复读(Repeatable Read)

    1. 这是MySQL的默认事务隔离级别。
    2. 在这个级别下,事务在开始时“快照”当前状态,因此它看到的是一致的数据视图,在事务执行期间不会改变。
    3. 这可以避免脏读和不可重复读问题。
    4. 但这种级别可能导致“幻读”(Phantom Read)问题,即当事务读取某个范围内的记录时,另一个并发事务插入了一些新的记录,导致前一个事务在再次读取该范围的记录时,会看到一些“幻影”记录
    5. InnoDB通过多版本并发控制(MVCC)和间隙锁来解决这个问题。
  4. 串行化(Serializable)

    1. 这是最高的隔离级别。
    2. 在这个级别下,事务是完全串行执行的,避免了脏读、不可重复读和幻读问题。
    3. 它通过在事务执行期间对数据加锁来实现,这可以防止其他事务对数据进行修改和插入。
    4. 但这种严格的控制可能会导致大量的锁竞争和性能开销。

推荐学习资料

《掘金专栏》|全解MySQL数据库:#MySQL事务的隔离机制

《小林Coding》|图解MySQL:# 事务的隔离级别有哪些?

2. 【MySQL】常用的存储引擎有哪些?区别是?

解析

属于高频考题之一:自产《大厂后端Top100面试题讲解|第1题》

所属专项:MySQL|基础

专项考察占比:

MySQL】面试中考察MySQL的比率:大厂:18%腾讯:13%阿里:17%

MySQL-基础】面试考察MySQL时,问“基础”相关问题的比率:10%

参考口述回答:

在这里,我主要对比两种最常用的存储引擎:InnoDB和MyISAM,同时简要提及其他几种引擎的特点。

1. InnoDB存储引擎:

  • 事务支持:InnoDB是事务型数据库的首选引擎,它提供了提交、回滚和崩溃恢复能力来保护用户数据,确保数据的完整性。它遵循ACID原则,即原子性、一致性、隔离性和持久性。
  • 行级锁定:InnoDB支持行级锁定,这意味着在多个用户并发访问数据库时,它只会锁定被访问的行,而不是整个表。这大大提高了数据库的并发性能。
  • 外键约束:InnoDB还支持外键约束,可以确保数据的参照完整性。
  • 聚簇索引:InnoDB使用聚簇索引,即数据和主键索引存储在一起,这有助于提高某些查询的效率。

2. MyISAM存储引擎:

  • 表级锁定:MyISAM不支持事务和行级锁定,只支持表级锁。这意味着在读写操作时,会对整个表进行锁定,可能导致并发性能较差。
  • 全文索引:MyISAM支持全文索引,适合进行文本搜索。
  • 高速读取:MyISAM通常用于只读或大量读取的应用场景,如Web站点的归档数据或只读的数据仓库。其查询速度相对较快。
  • 压缩存储:MyISAM还支持压缩表,可以节省存储空间。

3. 其他存储引擎:

  • Memory:将所有数据保存在RAM中,提供极快的访问速度,适用于需要快速查找引用的场景。但服务器关闭时数据会丢失。

总的来说,选择哪种存储引擎取决于具体的应用需求和性能要求。例如,对于需要高并发写入和数据完整性的应用,InnoDB是更好的选择;而对于只读或文本搜索为主的应用,MyISAM可能更合适。在实际应用中,我们需要根据具体情况进行权衡和选择。

推荐学习资料:

《掘金专栏》|全解MySQL数据库:# (十三)MySQL引擎篇:半道出家的InnoDB为何能替换官方的MyISAM?

3. 【MySQL】B 树和 B+树的区别,索引为什么用B+树不用B树?

解析

属于高频考题:自产《大厂后端Top100面试题讲解|第3题》

所属专项:MySQL|索引

专项考察占比:

MySQL】面试中考察MySQL的比率:大厂:18%腾讯:13%阿里:17%

MySQL-索引】面试考察MySQL时,问“索引”相关问题的比率:36%

参考口述回答

  1. 为什么用B+树不用B树

一、树的高度

B+树相较于B树,其树的高度更低。这是因为B+树的非叶子节点不存储数据,只存储索引,因此可以容纳更多的子节点,使得整个树的结构更加扁平化。这种设计减少了查询时需要经过的层级数,最重要是减少了磁盘IO的次数,从而提高了查询效率。相比之下,B树的每个节点都存储数据,导致节点容纳的子节点数量有限,树的高度相对较高。

二、插入删除效率

在插入和删除操作方面,B+树也表现出更高的效率。由于B+树的非叶子节点不存储数据,因此在插入或删除数据时,只需要调整索引结构,而不需要频繁地移动大量数据。这大大简化了插入和删除操作的过程,并提高了效率。相比之下,B树在插入或删除数据时可能需要更复杂的操作来保持树的平衡。

三、范围查询效率

B+树在范围查询方面具有显著优势。由于B+树的叶子节点包含所有数据,并且叶子节点之间通过指针相连,形成了一个有序链表结构。这种结构使得范围查询变得非常简单和高效,因为我们可以直接通过叶子节点的指针顺序访问范围内的数据。而在B树中,范围查询可能需要更复杂的中序遍历操作。

推荐学习资料

《掘金专栏》|全解MySQL数据库:# MySQL索引为何使用B+树结构?

《小林Coding》|图解MySQL:# 为什么 MySQL 采用 B+ 树作为索引?

4. 【MySQL】什么是悲观锁,什么是乐观锁?Mysql死锁的排查?

解析

属于高频考题之一:《大厂后端Top100面试题讲解|第14题》

所属专项:MySQL|锁

专项考察占比:

MySQL】面试中考察MySQL的比率:大厂:18%腾讯:13%阿里:17%

MySQL-锁】面试考察MySQL时,问“锁”相关问题的比率:8%

参考口述回答

1.什么是悲观锁,什么是乐观锁?

悲观锁,顾名思义,它持有一种悲观的态度,总是假设最坏的情况会发生,数据在任何时候都可能被其他事务修改。因此,它会在数据处理前先进行锁定,确保数据在处理过程中不会被其他事务修改或访问,这就好像我们在做某件事情之前,先把相关的资源都占住,以防别人抢走。这种方式虽然安全,但可能会降低系统的并发性能,因为它会阻止其他事务访问被锁定的数据。

乐观锁则相反,它持有一种乐观的态度,认为数据在大部分情况下不会被其他事务修改。因此,它不会一开始就锁定数据,而是在数据提交更新的时候检查数据是否被其他事务修改过。这就像是我们先去做自己的事情,等到最后需要提交成果的时候,再检查是否有其他人已经修改过相关数据。如果数据没有被修改过,那么我们的更新就可以成功提交;如果数据已经被其他事务修改过,那么我们的更新就会失败,需要进行相应的处理。这种方式可以提高系统的并发性能,因为它允许多个事务同时访问和修改数据,但需要在数据提交时进行冲突检测和处理。

2.死锁,Mysql死锁的排查?

  1. 查看错误日志:MySQL的错误日志中可能会记录有关死锁的信息。通过查看这些日志,可以初步判断是否存在死锁以及死锁发生的时间和原因。
  2. 使用SHOW ENGINE INNODB STATUS命令:这个命令可以提供InnoDB存储引擎的详细信息,包括最近的死锁信息。在返回的结果中,可以查找"LATEST DETECTED DEADLOCK"部分,这里会详细描述死锁的情况,包括涉及的事务、锁定的资源等。
  3. 性能监控工具:使用如Percona Monitoring and Management (PMM)、InnoDB Plugin等工具可以帮助监控和分析数据库性能,并在发生死锁时提供警报。
  4. 慢查询日志:如果死锁是由于某些查询执行时间过长导致的,那么慢查询日志可能会记录这些查询。通过分析慢查询日志,可以找到可能导致死锁的长时间运行的查询。

推荐学习资料

《掘金专栏》|全解MySQL数据库:# (十)全解MySQL之死锁问题分析

《掘金专栏》|全解MySQL数据库:# (十八)MySQL排查篇:该如何定位并解决线上突发的Bug与疑难杂症?

5. 【Redis】Redis 是单线程还是多线程?那单线程为什么这么快?

解析

属于高频考题:自产《大厂后端Top100面试题讲解|第63题》

所属专项:Redis|架构

专项考察占比:

Redis】面试中考察操作系统的比率:大厂:12%腾讯:7%阿里:13%

Redis-架构】面试考察Redis时,问“架构模型”相关问题的比率:18%

参考口述回答

  1. redis是单线程还是多线程?

首先,关于Redis是单线程还是多线程的问题,这实际上取决于Redis的版本。在Redis的早期版本,比如3.x版本,它确实是单线程的。这里的单线程主要是指Redis处理客户端请求的主线程是单线程的,即网络IO和键值对读写是由一个线程来完成的。这种设计使得Redis能够避免多线程切换带来的开销,提高了执行效率,同时也避免了线程安全问题。

然而,在后续的版本中,Redis开始逐渐引入多线程的概念。例如,在Redis 4.x版本中,虽然处理客户端请求的主线程仍然是单线程的,但一些辅助功能如异步删除等已经开始使用多线程。到了Redis 6.0版本,多线程的支持得到了进一步的加强,主要体现在处理大量写操作的场景上。这种多线程的设计主要是为了提高Redis的吞吐量,更好地利用现代多核CPU的性能,以及减少在写操作时的延迟。

  1. 单线程为什么还这么快?
  1. 基于内存操作:Redis的所有数据都存储在内存中,这使得数据的读写速度非常快。内存操作的延迟远远低于磁盘操作,因此Redis能够迅速响应客户端的请求。
  2. 简单的数据结构:Redis的数据结构是专门设计的,这些简单的数据结构的查找和操作的时间复杂度大部分都是O(1),这意味着无论数据量多大,操作的时间都是恒定的。
  3. IO多路复用和非阻塞IO:Redis使用IO多路复用功能来监听多个socket连接客户端。这样,它就可以使用一个线程连接处理多个请求,从而减少了线程切换带来的开销,并避免了IO阻塞操作。
  4. 避免上下文切换:由于Redis采用单线程模型(在早期版本中),因此可以避免不必要的上下文切换和多线程竞争。这样可以省去多线程切换带来的时间和性能上的消耗,同时单线程也不会导致死锁问题的发生。

推荐学习资料

《小林Coding》|图解Redis:Redis 线程模型

6. 【Redis】分布式锁在 Redis 里面怎么做?

解析

属于高频考题:自产《大厂后端Top100面试题讲解|第75题》

所属专项:Redis|应用

专项考察占比:

Redis】面试中考察操作系统的比率:大厂:12%腾讯:7%阿里:13%

Redis-应用】面试考察Redis时,问“应用”相关问题的比率:34%

参考口述回答

  1. redis如何实现分布式锁?
  1. 使用SetNX实现基本的锁机制

Redis的SETNX命令(SET if Not eXists)是实现分布式锁的基础。当一个客户端尝试获取锁时,它会使用SETNX来设置一个键。如果键不存在,SETNX会设置该键并返回1,表示成功获取了锁。如果键已经存在,SETNX不会做任何事情并返回0,表示获取锁失败。

设置过期时间防止死锁

但是,仅仅使用SETNX是不够的。如果持有锁的客户端在处理过程中崩溃或网络断开,那么锁可能永远不会被释放,导致其他客户端永远无法获取锁,这就是所谓的死锁。为了防止这种情况,我们需要为锁设置一个过期时间。在Redis中,这可以通过EXPIRE命令来实现。

使用Lua脚本确保解锁操作的原子性

解锁操作需要谨慎处理。为了确保解锁操作的原子性,我们通常使用Lua脚本来执行。Lua脚本在Redis中是原子执行的,这意味着在脚本执行期间,其他命令不会被插入执行。这确保了即使在并发环境中,解锁操作也是安全的。

看门狗机制自动续期

为了确保锁在客户端处理过程中不会因过期而被意外释放,我们可以实现一个看门狗机制来自动续期锁的过期时间。这可以通过在客户端创建一个定时任务来实现,该任务在锁过期前不断地重新设置锁的过期时间。

  1. 使用Redisson库简化分布式锁的实现

手动实现上述所有功能可能相当复杂且容易出错。幸运的是,有一些库如Redisson已经为我们封装了这些复杂的逻辑。使用Redisson,我们可以非常简单地实现分布式锁,而无需关心底层的细节。Redisson还提供了很多高级功能,如锁的重试机制、公平锁等。

推荐学习资料

《博客》ITPUB:Redis 分布式锁的正确实现原理演化历程与 Redisson 实战总结

7. 【Redis】Redis 有哪些数据结构?

解析

属于高频考题:自产《大厂后端Top100面试题讲解|第65题》

所属专项:Redis|数据类型

专项考察占比:

Redis】面试中考察操作系统的比率:大厂:12%腾讯:7%阿里:13%

Redis-数据类型】面试考察Redis时,问“数据类型”相关问题的比率:20%

参考口述回答

  1. 5中常用数据类型:

首先,字符串 String是Redis最基本的数据类型,它可以存储任意类型的数据,包括文本、数字和二进制数据,且其值最大可以达到512MB。字符串在缓存、计数器、分布式锁等场景下有着广泛的应用。例如,在缓存场景中,我们可以将热点数据存储在Redis的字符串类型中,以提高数据的访问速度;在计数器场景中,我们可以利用Redis的原子性操作来实现高效、准确的计数功能。

其次,哈希类型 Hash是一种键值对的集合,它适合存储对象的多个属性。在哈希类型中,每个键都与一个值相关联,这使得我们可以方便地获取或修改对象的某个属性。哈希类型的值最多可以包含2^32-1个键值对,这使得它能够存储大量的数据。哈希类型在存储用户信息、配置信息等场景下非常有用。例如,我们可以将用户的个人信息存储在一个哈希值中,通过键值对的方式来访问和更新这些信息。

第三,列表类型 List是一种有序的字符串集合,它可以存储多个字符串值。列表类型支持在头部或尾部插入元素,以及获取指定范围内的元素等操作。这使得列表类型非常适合用于实现队列、栈等数据结构。例如,在消息队列场景中,我们可以利用列表类型来存储待处理的消息,并通过LPUSH和RPOP等命令来实现消息的入队和出队操作。

第四,集合类型 Set是一种无序、唯一的字符串集合。它支持添加元素、删除元素以及判断元素是否存在等操作。此外,集合类型还支持求交集、并集和差集等操作。这使得集合类型非常适合用于存储不重复的元素,并进行快速的集合运算。例如,在标签系统中,我们可以利用集合类型来存储用户的标签信息,并通过集合运算来查找具有相同标签的用户或者计算用户之间的相似度等。

最后,有序集合类型 ZSet是一种有序的字符串集合,每个元素都关联着一个分数用于排序。有序集合支持添加元素、删除元素以及获取指定范围内的元素等操作,并且还支持根据分数进行范围查找和计算元素的排名等操作。这使得有序集合非常适合用于排行榜、优先级队列等场景。例如,在排行榜场景中,我们可以利用有序集合来存储用户的得分信息,并通过ZRANK等命令来获取用户的排名情况。

  1. 后面新增4种

首先是Bitmap数据类型,它是一种基于String类型实现的特殊数据结构,用于存储大量二进制位(0或1)的数据。这些位可以代表不同的状态或标志。Bitmap非常适合用于签到打卡等场景,因为这类场景通常只需要记录用户是否进行了某个操作,例如签到(1)或未签到(0)。使用Bitmap可以极大地节省存储空间,并提高处理速度。

接下来是HyperLogLog数据类型,它是Redis 2.8.9版本引入的,用于做数据基数计算。HyperLogLog的优势在于它占用的内存非常少,并且不保存原始数据,而是通过一定的算法估计数据的基数。尽管它有一定的误差(标准误差为0.81%),但在对数据准确度要求不是特别高的场景下,如统计网站注册IP数、每日访问IP数或页面实时UV/PV数等,HyperLogLog是一个非常好的选择。

再来说说GEO数据类型,这是Redis 3.2版本新增的特性,用于存储经纬度格式的地理坐标,并对这些坐标执行距离计算、范围查找等操作。GEO数据类型非常适合用于地理位置相关的业务场景,如各类社交软件中的“附近的人”功能、外卖配送服务中的距离计算和最优配送员分配等。

最后是Stream数据类型,它类似于消息队列或日志系统,可以记录一系列的事件或消息,并按照时间顺序进行存储。Stream数据类型非常适合用于实时监控、大数据处理、数据流转、实时数据分析和视频流处理等场景。例如,在实时监控中,可以使用Stream来记录服务器的日志或网络流量数据;在大数据处理中,可以使用Stream来对一批数据进行筛选、排序和统计等操作;在实时数据分析中,可以使用Stream来处理网站访问记录并了解用户行为。

推荐学习资料

《小林Coding》|图解Redis:Redis 常见数据类型和应用场景

8. 【Java】volatile 关键字有用过吗?

解析

属于高频考题:自产《大厂后端Top100面试题讲解|第27题》

所属专项:Java|Java并发

专项考察占比:

Java】面试中考察Java的比率:大厂:17%腾讯:6%阿里:32%

Java-并发】面试考察Java时,问Java并发问题的比率:37%

参考口述回答

volatile是Java中的一个关键字,主要用于确保多线程环境中变量的可见性和有序性。当一个变量被声明为volatile时,它会告诉编译器和处理器,不要对这个变量的读写做优化处理,以确保每次对该变量的读写都是直接从主内存中进行的,而不是从CPU缓存中进行。这样可以确保每个线程都能立即看到其他线程对该变量的最新修改。

具体来说,volatile关键字的作用主要体现在以下两个方面:

一、可见性:当一个线程修改了一个volatile变量的值,其他线程能够立即看到这个修改。这是因为volatile关键字会禁止CPU缓存和编译器优化,确保每次读写都是直接从主内存中进行的。这样,当一个线程修改了volatile变量的值后,其他线程在读取这个变量时,会直接从主内存中读取最新的值,而不是从CPU缓存中读取可能过时的值。

二、有序性:Java内存模型允许编译器和处理器对指令进行重排序,以提高执行效率。但是,在某些情况下,重排序可能会导致多线程环境中的问题。volatile关键字通过插入内存屏障来禁止指令重排序,从而确保代码的执行顺序与程序的顺序一致。这可以防止因重排序而导致的并发问题。

需要注意的是,虽然volatile关键字可以确保变量的可见性和有序性,但它并不能确保原子性。也就是说,volatile不能替代锁来确保复杂操作的原子性。在处理需要多个步骤的复合操作时,仍然需要使用锁或其他同步机制来确保操作的原子性。

推荐学习资料

《JavaGuide》| 并发面试题:##volatile 关键字

9. 【Java】讲讲内存泄露和溢出?ThreadLocal内存溢出?

解析

属于高频考题:自产《大厂后端Top100面试题讲解|第25题》

所属专项:Java|Java并发

专项考察占比:

Java】面试中考察Java的比率:大厂:17%腾讯:6%阿里:32%

Java-并发】面试考察Java时,问Java并发问题的比率:37%

参考回答

  1. 什么是内存泄漏?

内存泄漏是指在程序运行过程中,动态分配的内存没有得到及时释放,导致系统无法再对该部分内存进行回收再利用,即使这部分内存已经不再被程序所使用。简单来说,就是程序申请了内存空间后,未能适时地将其释放回操作系统,从而造成内存资源的浪费。

  1. ThreadLocal内存泄漏问题?

ThreadLocal如果使用不当,可能会导致内存泄漏。这主要是因为ThreadLocalMap中的Entry对ThreadLocal实例是弱引用(WeakReference),而对应的值value则是强引用。如果ThreadLocal实例被回收了,但是对应的value还没有被回收,那么这部分数据就可能成为内存泄漏的源头。

为了避免这种情况,有几种方法:

  1. 使用完ThreadLocal后,及时调用其remove()方法清除对应的Entry,避免因为线程长时间存活而导致的内存泄漏。
  2. 将ThreadLocal变量设置为static和final,以延长其生命周期,减少被垃圾回收的可能性。但这并不是根本的解决办法,因为即使ThreadLocal实例不被回收,其对应的value还是可能被长时间持有而无法释放。

因此,最佳实践是在使用完ThreadLocal后,及时通过remove()方法清除数据,以避免潜在的内存泄漏问题。

推荐学习资料

《JavaGuide》| 并发面试题:# ThreadLocal 面试题

《JavaGuide》| 详解:# ThreadLocal 详解

最后

alt

本文也是 《热门面经讲解》 专栏系列文章之一,大家可以点跳转链接,加个关注和订阅,我会持续更新~

#面经##美团##实习##牛客在线求职答疑中心##24届软开秋招面试经验大赏#
热门面经讲解 文章被收录于专栏

挑选近期热门真实后端面经进行讲解分析,给出:个人分析+参考回答+学习资料指引。

全部评论

相关推荐

9 16 评论
分享
牛客网
牛客企业服务