之前分享很多大厂的面经,这次分享一家上海某小厂的 Java 岗位面试,面试的时间也挺长的,接近 1 个小时,无算法,全程抓着项目+mysql+redis+java这 4 个方向问。
介绍你的项目
这个项目是企业里面做的还是学校的项目?
说说你在这个项目负责了那些,应用了那些技术,你学到了什么?
你说你用到了 redis 分布式锁,为何采用 redis 分布式锁啊,redis 分布锁有啥好处?
你用过线程池吗? 谈谈线程池的好处?
在这个项目中线程池参数怎么配置的?
你这个项目是根据 io密集型 还是 CPU密集型 计算?
读未提交,指一个事务还没提交时,它做的变更就能被其他事务看到;
读提交,指一个事务提交之后,它做的变更才能被其他事务看到;
可重复读,指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,MySQL InnoDB 引擎的默认隔离级别;
串行化;会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行;
按隔离水平高低排序如下:
我们需要了解两个知识:
Read View 中四个字段作用;
聚簇索引记录中两个跟事务有关的隐藏列;
Read View 有四个重要的字段:
m_ids :指的是在创建 Read View 时,当前数据库中「活跃事务」的事务 id 列表,注意是一个列表,“活跃事务”指的就是,启动了但还没提交的事务。
min_trx_id :指的是在创建 Read View 时,当前数据库中「活跃事务」中事务 id 最小的事务,也就是 m_ids 的最小值。
max_trx_id :这个并不是 m_ids 的最大值,而是创建 Read View 时当前数据库中应该给下一个事务的 id 值,也就是全局事务中最大的事务 id 值 + 1;
creator_trx_id :指的是创建该 Read View 的事务的事务 id。
对于使用 InnoDB 存储引擎的数据库表,它的聚簇索引记录中都包含下面两个隐藏列:
trx_id,当一个事务对某条聚簇索引记录进行改动时,就会把该事务的事务 id 记录在 trx_id 隐藏列里;
roll_pointer,每次对某条聚簇索引记录进行改动时,都会把旧版本的记录写入到 undo 日志中,然后这个隐藏列是个指针,指向每一个旧版本记录,于是就可以通过它找到修改前的记录。
在创建 Read View 后,我们可以将记录中的 trx_id 划分这三种情况:
img
一个事务去访问记录的时候,除了自己的更新记录总是可见之外,还有这几种情况:
如果记录的 trx_id 值小于 Read View 中的 min_trx_id
值,表示这个版本的记录是在创建 Read View 前已经提交的事务生成的,所以该版本的记录对当前事务可见。
如果记录的 trx_id 值大于等于 Read View 中的 max_trx_id
值,表示这个版本的记录是在创建 Read View 后才启动的事务生成的,所以该版本的记录对当前事务不可见。
如果记录的 trx_id 值在 Read View 的min_trx_id和max_trx_id之间,需要判断 trx_id 是否在 m_ids 列表中:
如果记录的 trx_id 在 m_ids
列表中,表示生成该版本记录的活跃事务依然活跃着(还没提交事务),所以该版本的记录对当前事务不可见。
如果记录的 trx_id 不在 m_ids
列表中,表示生成该版本记录的活跃事务已经被提交,所以该版本的记录对当前事务可见。
这种通过「版本链」来控制并发事务访问同一个记录时的行为就叫 MVCC(多版本并发控制)。
不会,快照读,
MySQL InnoDB 引擎的默认隔离级别虽然是「可重复读」,但是它很大程度上避免幻读现象(并不是完全解决了),解决的方案有两种:
针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。
针对当前读(select ... for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select ... for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。
这两个解决方案是很大程度上解决了幻读现象,但是还是有个别的情况造成的幻读现象是无法解决的。
比如这个场景:
在可重复读隔离级别下,事务 A 第一次执行普通的 select 语句时生成了一个 ReadView,之后事务 B 向表中新插入了一条 id = 5 的记录并提交。接着,事务 A 对 id = 5 这条记录进行了更新操作,在这个时刻,这条新记录的 trx_id 隐藏列的值就变成了事务 A 的事务 id,之后事务 A 再使用普通 select 语句去查询这条记录时就可以看到这条记录了,于是就发生了幻读。
因为这种特殊现象的存在,所以我们认为 MySQL Innodb 中的 MVCC 并不能完全避免幻读现象。
可重复读隔离级别是启动事务时生成一个 Read View,然后整个事务期间都在用这个 Read View。
读提交隔离级别是在每次读取数据时,都会生成一个新的 Read View。也意味着,事务期间的多次读取同一条数据,前后两次读的数据可能会出现不一致,因为可能这期间另外一个事务修改了该记录,并提交了事务。
索引是数据库中一种用于提高查询性能的数据结构。它类似于书籍的目录,可以帮助数据库系统快速地定位和访问数据。通过在数据库表的一个或多个列上创建索引,可以加快查询速度,减少数据扫描的时间和成本。
索引的作用是通过建立一个索引文件,将索引列的值与对应的数据记录进行映射关联。当进行查询时,数据库系统可以根据索引文件快速定位到符合查询条件的数据记录,而无需逐条扫描整个表。这样可以大大减少查询的时间复杂度,提高查询效率。
索引的创建需要占用额外的存储空间,并且在数据更新时需要维护索引结构,可能会增加写操作的开销。因此,索引的使用需要权衡查询频率和数据更新频率之间的平衡。对于经常进行查询的字段,可以考虑创建索引;而对于更新频率较高的字段,或者区分度较小的字段,创建索引可能效果有限,甚至会影响性能。
通过将多个字段组合成一个索引,该索引就被称为联合索引。
比如,将商品表中的 product_no 和 name 字段组合成联合索引(product_no, name)
,创建联合索引的方式如下:
CREATE INDEX index_product_no_name ON product(product_no, name);
联合索引(product_no, name)
的 B+Tree 示意图如下(图中叶子节点之间我画了单向链表,但是实际上是双向链表,原图我找不到了,修改不了,偷个懒我不重画了,大家脑补成双向链表就行)。
联合索引
可以看到,联合索引的非叶子节点用两个字段的值作为 B+Tree 的 key 值。当在联合索引查询数据时,先按 product_no 字段比较,在 product_no 相同的情况下再按 name 字段比较。
也就是说,联合索引查询的 B+Tree 是先按 product_no 进行排序,然后再 product_no 相同的情况再按 name 字段排序。
因此,使用联合索引时,存在最左匹配原则,也就是按照最左优先的方式进行索引的匹配。在使用联合索引进行查询的时候,如果不遵循「最左匹配原则」,联合索引会失效,这样就无法利用到索引快速查询的特性了。
二叉树会比较数据,从上至下以此分配和排序数据
开启慢查询日志,定位到出现问题的sql的语句
大查询改造为分批查询
数据库分表,降低数据库表的数量
引入redis缓存 ,减少 mysql 的访问
如果查询出来的结果集,存在连续且递增的字段,可以基于有序字段来进行查询,例如 select xxx from book where 有序字段 >= 1 limit 100
舍弃limit关键字,如果查询出来的结果集存在连续且递增的字段,使用between and来进行范围结果集查询,例如 select xxx from book where 有序字段 between 10000000 and 1000100
采用MongoDB、ES搜索引擎优化深分页
当我们使用左或者左右模糊匹配的时候,也就是 like %xx
或者 like %xx%
这两种方式都会造成索引失效;
当我们在查询条件中对索引列使用函数,就会导致索引失效。
当我们在查询条件中对索引列进行表达式计算,也是无法走索引的。
MySQL 在遇到字符串和数字比较的时候,会自动把字符串转为数字,然后再进行比较。如果字符串是索引列,而条件语句中的输入参数是数字的话,那么索引列会发生隐式类型转换,由于隐式类型转换是通过 CAST 函数实现的,等同于对索引列使用了函数,所以就会导致索引失效。
联合索引要能正确使用需要遵循最左匹配原则,也就是按照最左优先的方式进行索引的匹配,否则就会导致索引失效。
在 WHERE 子句中,如果在 OR 前的条件列是索引列,而在 OR 后的条件列不是索引列,那么索引会失效。
不一定,如果 or 左右两个字段都是索引,就能走索引。
如果 a 和b 是联合索引,会发生索引失效,对于联合索引(比如 bc),如果使用了 b =xxx or c=xxx,会走不了索引。
左模糊和全模糊查询
因为索引 B+ 树是按照「索引值」有序排列存储的,只能根据前缀进行比较。
举个例子,下面这张二级索引图(图中叶子节点之间我画了单向链表,但是实际上是双向链表,原图我找不到了,修改不了,偷个懒我不重画了,大家脑补成双向链表就行),是以 name 字段有序排列存储的。
图片
假设我们要查询 name 字段前缀为「林」的数据,也就是 name like '林%'
,扫描索引的过程:
首节点查询比较:林这个字的拼音大小比首节点的第一个索引值中的陈字大,但是比首节点的第二个索引值中的周字小,所以选择去节点2继续查询;
节点 2 查询比较:节点2的第一个索引值中的陈字的拼音大小比林字小,所以继续看下一个索引值,发现节点2有与林字前缀匹配的索引值,于是就往叶子节点查询,即叶子节点4;
节点 4 查询比较:节点4的第一个索引值的前缀符合林字,于是就读取该行数据,接着继续往右匹配,直到匹配不到前缀为林的索引值。
如果使用 name like '%林'
方式来查询,因为查询的结果可能是「陈林、张林、周林」等之类的,所以不知道从哪个索引值开始比较,于是就只能通过全表扫描的方式来查询。
Redis 提供了丰富的数据类型,常见的有五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)、Zset(有序集合)。
img
随着 Redis 版本的更新,后面又支持了四种数据类型:BitMap(2.2 版新增)、HyperLogLog(2.8 版新增)、GEO(3.2 版新增)、Stream(5.0 版新增)。Redis 五种数据类型的应用场景:
String 类型的应用场景:缓存对象、常规计数、分布式锁、共享 session 信息等。
List 类型的应用场景:消息队列(但是有两个问题:1. 生产者需要自行实现全局唯一 ID;2. 不能以消费组形式消费数据)等。
Hash 类型:缓存对象、购物车等。
Set 类型:聚合计算(并集、交集、差集)场景,比如点赞、共同关注、抽奖活动等。
Zset 类型:排序场景,比如排行榜、电话和姓名排序等。
Redis 后续版本又支持四种数据类型,它们的应用场景如下:
BitMap(2.2 版新增):二值状态统计的场景,比如签到、判断用户登陆状态、连续签到用户总数等;
HyperLogLog(2.8 版新增):海量数据基数统计的场景,比如百万级网页 UV 计数等;
GEO(3.2 版新增):存储地理位置信息的场景,比如滴滴叫车;
Stream(5.0 版新增):消息队列,相比于基于 List 类型实现的消息队列,有这两个特有的特性:自动生成全局唯一消息ID,支持以消费组形式消费数据。
Redis 的读写操作都是在内存中,所以 Redis 性能才会高,但是当 Redis 重启后,内存中的数据就会丢失,那为了保证内存中的数据不会丢失,Redis 实现了数据持久化的机制,这个机制会把数据存储到磁盘,这样在 Redis 重启就能够从磁盘中恢复原有的数据。
Redis 共有三种数据持久化的方式:
AOF 日志:每执行一条写操作命令,就把该命令以追加的方式写入到一个文件里;
RDB 快照:将某一时刻的内存数据,以二进制的方式写入磁盘;
混合持久化方式:Redis 4.0 新增的方式,集成了 AOF 和 RBD 的优点;
我们的业务通常会有几个数据会被频繁地访问,比如秒杀活动,这类被频地访问的数据被称为热点数据。
如果缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮,这就是缓存击穿的问题。
图片
可以发现缓存击穿跟缓存雪崩很相似,你可以认为缓存击穿是缓存雪崩的一个子集。
应对缓存击穿可以采取前面说到两种方案:
互斥锁方案,保证同一时间只有一个业务线程更新缓存,未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。
不给热点数据设置过期时间,由后台异步更新缓存,或者在热点数据准备要过期前,提前通知后台线程更新缓存以及重新设置过期时间;
ArrayList是Java中的一个动态数组类,它实现了List接口。它的特点是可以根据需要自动扩展和收缩数组的大小,以便容纳不同数量的元素。
ArrayList可以存储任意类型的对象,包括基本类型的包装类。它提供了一系列方法来操作和访问数组中的元素,比如添加元素、删除元素、获取元素、遍历数组等。
ArrayList的内部实现是基于数组,当添加元素时,如果数组已满,它会自动创建一个更大的数组,并将原数组中的元素复制到新数组中。同样,当删除元素时,如果数组中的元素数量变得较少,它会自动缩小数组的大小,以节省内存空间。
由于ArrayList是基于数组实现的,所以它具有随机访问的特性,可以通过索引直接访问数组中的元素,时间复杂度为O(1)。但在插入和删除元素时,需要移动其他元素以保持数组的连续性,时间复杂度为O(n)。
哈希函数是一种将输入数据映射到固定大小值(哈希值)的函数。它将任意长度的输入数据转换为固定长度的输出,通常是一个较短的数字或字符串。
哈希函数具有以下特性:
输入相同的数据,得到的哈希值必定相同。
即使输入数据只有微小的改动,得到的哈希值也会完全不同。
哈希值的长度固定,不受输入数据的长度影响。
哈希函数常用于密码学、数据完整性校验、数据索引等领域。在密码学中,哈希函数常用于将用户密码进行加密存储,以保护用户的隐私。在数据完整性校验中,哈希函数可以用于验证数据是否被篡改。在数据索引中,哈希函数可以用于快速查找和比较数据。
常见的哈希函数包括MD5、SHA-1、SHA-256等。这些哈希函数具有较低的碰撞概率,保证了数据的唯一性和安全性。然而,随着计算能力的提升,一些传统的哈希函数可能存在安全性问题,因此在实际应用中,需要根据具体需求选择适当的哈希函数。
HashMap 基于 Hash 算法实现的,我们通过 put(key,value)存储,get(key)来获取。当传入 key 时,HashMap 会根据 key. hashCode() 计算出 hash 值,根据 hash 值将 value 保存在 bucket 里。当计算出的 hash 值相同时,我们称之为 hash 冲突,HashMap 的做法是用链表和红黑树存储相同 hash 值的 value。当 hash 冲突的个数比较少时,使用链表否则使用红黑树。
HashMap在多线程环境下会存在线程安全问题。HashMap是一种非线程安全的数据结构,当多个线程同时对HashMap进行修改时,可能会导致数据不一致或产生其他异常。
如果需要在多线程环境下使用HashMap,可以考虑使用ConcurrentHashMap代替。ConcurrentHashMap是Java提供的线程安全的哈希表实现,它采用了分段锁的机制,不同的线程可以同时访问不同的分段,从而提高了并发读写的能力。
ConcurrentHashMap是Java中的一个线程安全的哈希表实现,它可以在多线程环境下并发地进行读写操作,而不需要像传统的HashTable那样在读写时加锁。
ConcurrentHashMap的实现原理主要基于分段锁和CAS操作。它将整个哈希表分成了多个Segment(段),每个Segment都类似于一个小的HashMap,它拥有自己的数组和一个独立的锁。在ConcurrentHashMap中,读操作不需要锁,可以直接对Segment进行读取,而写操作则只需要锁定对应的Segment,而不是整个哈希表,这样可以大大提高并发性能。
Spring MVC和Spring Boot是两个在Java开发中常用的框架,它们有以下几个主要区别:
目标和定位:Spring MVC是一个在Java企业级应用中构建Web应用程序的框架,它提供了一套完整的MVC(模型-视图-控制器)架构,用于处理HTTP请求和响应。而Spring Boot是一个快速构建独立、可运行的、生产级的Spring应用程序的框架,它简化了Spring应用程序的配置和部署过程。
配置方式:Spring MVC需要通过XML文件或Java配置类进行显式的配置,包括配置控制器、视图解析器、拦截器等。而Spring Boot采用约定大于配置的原则,提供了自动配置功能,可以根据应用程序的依赖和配置文件中的设置,自动完成大部分配置工作,简化了开发者的配置过程。
依赖管理:Spring MVC需要开发者手动管理依赖的版本和冲突。而Spring Boot使用了一个叫做"Starter"的概念,它提供了一组预定义的依赖,可以通过简单的引入Starter来管理依赖,减少了开发者的工作量。
Spring Boot的自动装配原理是基于条件注解和Spring的依赖注入机制实现的。
首先,Spring Boot会根据classpath下的依赖以及配置文件中的设置,自动扫描并加载相应的自动配置类。
其次,自动配置类中使用了条件注解,根据条件判断是否要进行自动装配。条件注解可以根据一些特定的条件,如某个类是否在classpath中、某个配置是否存在等,来决定是否要进行自动装配。
最后,当条件满足时,自动配置类会自动注册和配置相应的Bean,将它们添加到Spring容器中。这样,在应用程序启动时,Spring Boot就会自动完成大部分配置工作,开发者无需手动配置。
通过自动装配,Spring Boot可以根据应用程序的依赖和配置文件的设置,智能地选择合适的配置,并将其应用到应用程序中,从而简化了开发者的配置过程,提高了开发效率。
MVC(Model-View-Controller)是一种软件设计模式,用于将应用程序的逻辑分离为三个不同的组件:模型(Model)、视图(View)和控制器(Controller)。
模型(Model)表示应用程序的数据和业务逻辑。它负责处理数据的读取、存储、验证以及与数据库的交互等操作。
视图(View)是用户界面的呈现部分,负责展示模型中的数据给用户。它可以是一个网页、一个图形界面或者其他任何形式。
控制器(Controller)充当模型和视图之间的中介,负责处理用户的请求、更新模型的状态,以及决定要显示哪个视图。它接收用户的输入,调用相应的模型方法来处理请求,并最终将结果返回给视图进行显示。
通过MVC的分层结构,实现了应用程序的解耦和可维护性。模型、视图和控制器各司其职,彼此之间的依赖关系降低,使得应用程序的开发、测试和维护更加灵活和高效。
MVC模式被广泛应用于Web开发、桌面应用程序以及移动应用程序等领域,是一种重要的软件设计模式。
DispatcherServlet:前置控制器,负责接收 HTTP 请求并委托给 HandlerMapping、HandlerAdapter 和 ViewResolver 等组件处理。
Handler:处理器,完成具体的业务逻辑,相当于 Servlet 或 Action。HandlerMapping:负责将请求映射到对应的 Handler 即控制器(Controller)。
HandlerInterceptor:处理器拦截器,是一个接口,如果需要完成一些拦截处理,可以实现该接口。HandlerExecutionChain:处理器执行链,包括两部分内容:Handler 和 HandlerInterceptor(系统会有一个默认的 HandlerInterceptor,如果需要额外设置拦截,可以添加拦截器)。
HandlerAdapter:负责调用处理器方法并封装处理结果,将其传递给 DispatcherServlet。ModelAndView:装载了模型数据和视图信息,作为 Handler 的处理结果,返回给 DispatcherServlet。
ViewResolver:视图解析器,负责根据视图名称解析出对应的 View,最终将渲染结果响应给客户端。
用过gateway做网关,nacos做注册中心和配置中心
面试官问的很多,但是人很好,问的问题基本答出来了,说我理解的可以,就是说让我要多看看底层原理和源码, 聊了50多分钟。
以上,就是这位朋友的面试总结,整体看下来,基本跟现在市场上大部分公司的面试情况一样,就是项目+八股+算法+场景。
只不过年限不同、公司不同的话,各自的占比不太一样而已。
上面这些被问到的技术问题,在我的八股文资料中都有的。
其内容涵盖:计算机基础、Java、JVM、spring、算法、微服务、分布式、大厂面经、技术脑图等等...共1700+页 质量非常高!!!
不管最近要不要去面试,建议大家都保存一份!!学完之后不论是 厂内晋升 还是 跳槽涨薪 都不在话下!
内容如下:
大厂面试题真题解析(38页)
JVM(183页)
多线程(221页)
Mysql(216页)
Spring(338页)
Spring Boot(41页)
经典面试题(35页)
Spring Cloud(50页)
Dubbo(55页)
Mybtis+Redis(27页)
Linux+网络(66页)
MQ+Kafka+Zookeeper(40页)
Netty(21页)
大数据+hadoop(31页)
算法(38页)
设计模式+项目+高并发(41页)
注:篇幅有限,资料已整理成文档,文末查看!
含:红黑树,B+树,贪心算法,哈希分治法,七大查找算法,动态规划,一致性算法,数据结构等...
含:单例模式,工厂模式,抽象工厂模式,建造者模式,原型模式,适配器模式,装饰器模式,代理模式等23种设计模式...
含:Netty常用场景,高性能设计,架构设计,经典面试题等...
含:zookeeper集群,应用场景,分布式锁,Dubbo核心功能,集群配置,负载均衡,常见面试题等...
含:mybtis缓存,运用原理,分页,Redis事务,主从架构,缓存,穿透,穿击,降级面试题等...
含:TCP/IP协议详细笔记,网络层架构,三四次握手,Linux概述,磁盘,目录,文件,安全,经典面试题等...
含:数据库基础,数据类型,引擎,索引,事务,锁,视图,sql语句,优化,mysql锁,面试题等...
含:spring原理,周期,ioc原理,MVC事务,AOP原理,Boot配置,安全,监视器,面试题等...
含:面试必考21问,SpringCloud熔断,cap原理,设计目标优缺点,版本关系等...
含:Java基础,异常,NIO,HashMap,Tomcat,JVM堆栈,内存模型,调优,GC,老年代,新生代,垃圾回收,面试题等...
含:多线程基本概念,线程安全,线程出,volatile,ThreadLocal,使用场景,并发量,阻塞列队,面试题等...
该项目是一款标准且已上线的“网约车”应用。符合我国交通部对网约车监管的技术要求。通过了交通部对网约车线上和线下能力认定。项目原型曾在杭州上线运行。
项目中核心功能包括:账户系统,订单系统,支付系统,地图引擎,派单引擎,消息系统等 网约车核心解决方案。
项目中完全采用微服务架构设计,应用了成熟的接口安全设计方案,采用分布式锁保证了分布式环境中的数据同步,用分布式事务解决了分布式环境中的数据一致性等。
前置技能:Git,Maven,Spring Boot,Spring Cloud,Redis,MySql ,RabbitMQ,ActiveMQ等。
项目架构图:
注:篇幅有限,资料已整理成文档,点击下方小卡片获取!