MySQL
蚂蚁 ⾦服 Java ⾯试
蚂蚁 ⾦服校招也开奖,也就是我们⽤的⽀付宝的公司。
我整理了 25 届蚂蚁 ⾦服校招薪资情况(数据来源于 offershow ):

ssp offer :28k x 16 + 1.5k x 12 房补,同学 bg 硕⼠ 985 ,base 成都
sp offer :26k x 16 + + 2k x 12 房补,同学 bg 硕⼠双⼀流,base 北京
sp offer :25k x 16 + 2k x 12 房补,同学 bg 硕⼠ 985 ,base 杭州
普通 offer :23k x 16 + 2k x 12 房补,同学 bg 硕⼠ 985 ,base 杭州
普通 offer :23k x 16 + 1.5k x 12 房补,同学 bg 硕⼠ 985 ,base 成都
蚂蚁 ⾦服⼯作地点分布⽐较多,杭州、成都、北京、深圳都有,不同地⽅的薪资基本没什么 区
别,主要是房补稍微有点差距,房补是持续三年,成都是每个⽉ 1.5k ,杭州和北京是 2k 。
从⽬前蚂蚁 ⾦服开的薪资来看,和去年差 不多完全⼀样的⽔平,⽽今年不少互联⽹⼤⼚给的校招
薪资都⽐去年多了⼀些,如果⼿上有多个⼤⼚ offer 的话,蚂蚁 的薪资竞争⼒就相对弱了⼀些。
但是其实已经很不错的,主要是⼤⼚之间为了 抢优秀的⼈才还是太卷的了,把今年校招薪资卷出
了新⾼度。
薪资倒挂这件事还是很正常的,倘然接受就⾏,说不定明年 26 届的⼤⼚校招薪资,⽐ 25 届的还
多呢。
蚂蚁 ⾦服⾯试跟阿⾥巴巴 没什么 区别,⾯试的重点主要是Java+ 框架+后端组件多⼀些,⽽⽹络和
操作系统相对腾讯和字节会问的少⼀些,所以⼤家⾯不同公司的时候,需要针对性的做好复 习的
侧重点。
这次分享的是⼀位同学的** 阿⾥巴巴 Java 后端的校招⾯经,** 感觉问的问题还挺多的,⾯试时⻓有1⼩时+,压⼒不⼩,⾯试官⼀直追问。

考察的知识点,我帮⼤家罗列了⼀下:
MySQL :主键、b+ 树搜索、索引、存储引擎、隔离级别、锁
Redis :应⽤场景、本地缓存、⼤ key 、持久化、zset
MyBatis :SQL 注⼊
Spring :bean ⽣命周 期
MySQL
表中⼗个字段,你主键⽤⾃增ID 还是UUID ,为什么 ?(我回答了⾃增和
UUID 的优缺点)
⽤的是⾃增 id 。
因为 uuid 相对顺序的⾃增 id 来说是毫⽆规律可⾔的,新⾏的值不⼀定要⽐之前的主键的值要⼤,
所以 innodb ⽆法做到总是把新⾏插⼊到索引的最后,⽽是需要为新⾏寻找新的合适的位置从⽽来
分配新的空间。
这个过程需要做很多额外的操作,数据的毫⽆顺序会导致数据分布散乱,将会导致以下的问题:
写⼊的⽬标⻚很可能已经刷新到磁盘上并且从 缓存上移除,或者还没有被加载到缓存中,
innodb 在插⼊之前不得不先找到并从磁盘读取⽬标⻚到内存中,这将导 致⼤量的随机 IO 。
因为写⼊是乱序的,innodb 不得不频繁的做⻚分裂操作,以便 为新的⾏分配空间,⻚分裂导致
移动⼤量的数据,影响性能。
由于频繁的⻚分裂,⻚会变得稀疏并被不规则的填充,最终会导致数据会有碎⽚。
结论:使⽤ InnoDB 应该尽可能的按主键的⾃增顺序插⼊,并且尽可能使⽤单调的增加的聚簇键的
值来插⼊新⾏。
为什么 ⾃增ID 更快⼀些,UUID 不快吗,它在B+ 树⾥⾯存储是有序的吗?
⾃增的主键的值是顺序的,所以 Innodb 把每⼀条记录都存储在⼀条记录的后⾯,所以⾃增 id 更
快的原因:
下⼀条记录就会写⼊新的⻚中,⼀旦数 据按 照这种顺序的⽅式加载,主键⻚就会近乎于 顺序的
记录填满,提升了⻚⾯的最⼤填充率,不会有⻚的浪费
新插⼊的⾏⼀定会在原有的最⼤数据⾏下⼀⾏,mysql 定位和寻址很快 ,不会为计算新⾏的位置
⽽做出额外的消耗减少了⻚分裂和碎⽚的产⽣
但是 UUID 不是递增的,MySQL 中索引的数据结构是 B+Tree ,这种数据结构的特点是索引树上的
节点的数据是有序的,⽽如果使⽤ UUID 作为主 键,那么每次 插⼊数据时,因为⽆法保证每次 产⽣
的 UUID 有序,所以就会出现新的 UUID 需要插⼊到索引树的中间去,这样可能会频繁地导致⻚分
裂,使性能下降。
⽽且,UUID 太占⽤内存。每个 UUID 由 36 个字符组成,在字符串进⾏⽐较时,需要从前往后⽐
较,字符串越⻓,性能越差。另外字符串越⻓,占⽤的内存越⼤,由于⻚的⼤⼩是固定的,这样
⼀个⻚上能存放的关键字数量就会越少,这样最终就会导致索引树的⾼度越⼤,在索引搜索的时
候,发⽣的磁盘 IO 次数越多,性能越差。
查询数据时,到了B+ 树的叶⼦节点,之后的查找数据是如何做?
数据⻚中的记录按照「主键」顺序组成单向链表,单向链表的特点就是插⼊、删除⾮常⽅便,但
是检索效率不⾼,最差的情况下需要遍历链表上的所有节点才能完成检索。因此,数据⻚中有⼀
个⻚⽬录,起到记录的索引作⽤,就像我们书那样,针对书中 内容的每个章节设⽴了⼀个⽬录,
想看某个章节的时候,可以查看⽬录,快速找到对应的章节的⻚数,⽽数据⻚中的⻚⽬录就是为了能快速找到记录。那 InnoDB 是如何给记录创建⻚⽬录的呢?⻚⽬录与记录的关系如下图:

⻚⽬录创建的过程如下:
- 将所有的记录划分 成⼏个组,这些记录包括最⼩记录和最⼤记录,但不包括标记为“已删除”的
记录;
- 每个记录组的最后⼀条记录就是组内最⼤的那条记录,并且最后⼀条记录的头信息中会存储该
组⼀共有多少条记录,作为 n_owned 字段(上图中粉红⾊字段)
- ⻚⽬录⽤来存储每组最后⼀条记录的地址 偏移量,这些地址 偏移量会按照先后顺序存储起来,
每组的地址 偏移量也被称之为 槽(slot ),每个槽相当于指针指向了不 同组的最后⼀个记录。
从图可以看到,⻚⽬录就是由多个槽组成的,槽相当于分组记录的索引。然后,因为记录是按照
「主键值」从⼩到⼤排序的,所以我们通过槽查找记录时,可以使 ⽤⼆分法快速定位要查询的记
录在哪个槽(哪个记录分组),定位到槽后,再遍历槽内的所有记录,找到对应的记录,⽆需从最
⼩记录开 始遍历整个⻚中的记录链表。以上⾯那张图举个 例⼦,5 个槽的编号分别 为 0,1,2,
3,4,我想查找主键为 11 的⽤⼾记录:
先⼆分得出槽中间位是 (0+4)/2=2 ,2号槽⾥最⼤的记录为 8。因为 11 > 8 ,所以需要从 2 号槽后继续搜索记录;
再使⽤⼆分搜索出 2 号和 4 槽的中间位是 (2+4)/2= 3 ,3 号槽⾥最⼤的记录为 12 。因为 11 < 12 ,所以主键为 11 的记录在 3 号槽⾥;再从 3 号槽指向的主键值为 9 记录开 始向下搜索 2 次,定位到主键为 11 的记录,取出该条记
录的信息即为我们想要查找的内容。
你说你会 MySQL ,那它有哪些存储引擎(InnoDB 和MyISAM )?
InnoDB :InnoDB 是MySQL 的默认存储引擎,具有ACID 事务⽀持、⾏级锁、外键约束等特性。
它适⽤于⾼并发的读写操作,⽀持较好的数据完整性和并发控制。
MyISAM :MyISAM 是MySQL 的另⼀种常⻅的存储引擎,具有较低的存储空间和内存消耗,适⽤
于⼤量读操作的场景。然⽽,MyISAM 不⽀持事务、⾏级锁和外键约束,因此在并发写⼊和数据
完整性⽅⾯有⼀定的限制。
Memory :Memory 引擎将数据存储在内存中,适⽤于对性能要求较⾼的读操作,但是在服务器
重启或崩溃时数据会丢失。它不⽀持事务、⾏级锁和外键约束。
InnoDB 的四种隔离级别,你平常 做项⽬和实习⽤的什么 隔离级别(默认的)
⽤的是默认的,可重复读隔离级别
可重复读有没有幻读的问题?(举了 例⼦)
有的,可重复读隔离级别下虽然很⼤程度上避免了幻读,但是还是没有能完全解决幻读。我举例
⼀个可重复读隔离级别发⽣幻读现象的场景。以这张表作为例⼦:

事务 A 执⾏查询 id = 5 的记录,此时表中是没有该记 录的,所以查询不出来。
事务 A
mysql > begin ;
Query OK , 0 rows affected (0.00 sec )
mysql > select * from t_stu where id = 5;
Empty set (0.01 sec )
然后事务 B 插⼊⼀条 id = 5 的记录,并且提交了事 务。# 事务 B
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into t_stu values(5, '小美', 18);
Query OK, 1 row affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
此时,事务 A 更新 id = 5 这条记录,对没错,事务 A 看不到 id = 5 这条记录,但是他去更新了这
条记录,这场景确实很违和,然后再次查询 id = 5 的记录,事务 A 就能看到事务 B 插⼊的纪录了,幻读就是发⽣在这种违和的场景。
# 事务 A
mysql> update t_stu set name = '小林coding' where id = 5;
Query OK, 1 row affected (0.01 sec)Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from t_stu where id = 5;
+----+--------------+------+| id | name | age |
+----+--------------+------+| 5 | 小林coding | 18 |
+----+--------------+------+
1 row in set (0.00 sec)整个发⽣幻读的时序图如下:在可重复读隔离级别下,事务 A 第⼀次执⾏普通的 select 语句时⽣
成了⼀个 ReadView ,之后事务 B 向表中新插⼊了⼀条 id = 5 的记录并提交。接着,事务 A 对 id = 5 这条记录进⾏了更新操作,在这个时刻,这条新记录的 trx_id 隐藏列的值就变成了事 务 A 的事务 id ,之后事务 A 再使⽤普通 select 语句去 查询这条记录时就可以看到这条记录了,于是就发⽣
了幻读。
因为这种特殊现象的存在,所以我们认为 MySQL Innodb 中的 MVCC 并不能完全避免幻读现象。
MySQL 的锁讲⼀下(按锁的粒度讲了⼀遍)
全局锁:通过flush tables with read lock 语句会将整个数据库就处于只读状态了,这时其他线程
执⾏以下操作,增删改或者表结构修改都会阻塞。全局锁主要应⽤于做全库逻辑备份,这样在
备份数据库期间,不会因为数据或表结构的更新,⽽出现备份⽂件的数据与预期的不⼀样。
表级锁:MySQL ⾥⾯表级别的锁有这⼏种:
表锁:通过lock tables 语句可以对表加表锁,表锁除了会限制别 的线程的读写外,也会限制本
线程接下来的读写操作。
元数据锁:当我们对数据库表进⾏操作时,会⾃动给这个表加上 MDL ,对⼀张表进⾏ CRUD 操
作时,加的是 MDL 读锁;对⼀张表做结构变更操作的时候,加的是 MDL 写锁;MDL 是为了 保
证当⽤⼾对表执⾏ CRUD 操作时,防⽌其他线程对这个表结构做了变更。
意向锁:当执⾏插⼊、更新、删除操作,需要先对表加上「意向独占锁」,然后对该记 录加独占
锁。意向锁的⽬的是为了 快速判断表⾥是否有记录被加锁。
⾏级锁:InnoDB 引擎是⽀持⾏级锁的,⽽ MyISAM 引擎并不⽀持⾏级锁。
记录锁,锁住的是⼀条记录。⽽且记录锁是有 S 锁和 X 锁之分的,满⾜读写互斥,写写 互斥
间隙 锁,只存在于可重复读隔离级别,⽬的是为了 解决可重复读隔离级别下幻读的现象。
Next-Key Lock 称为临 键锁 ,是 Record Lock + Gap Lock 的组合,锁定⼀个范围,并且锁定记
录本⾝。
设计 ⼀个⾏级锁的死锁,举⼀个实际的例⼦(有思路,但是不好描述)
假设这时有两事 务,⼀个事 务要插⼊订单 1007 ,另外⼀个事 务要插⼊订单 1008 ,因为需要对订
单做幂等性校验,所以两个事 务先要查询该订 单是否存在,不存在才插⼊记录,过程如下:

可以看到,两个事 务都陷⼊了等待状态,也就是发⽣了死锁,因为都在相互等待对⽅释放锁。
在执⾏下⾯这条语句的时候:
select id from t_order where order_no = 1008 for update;
因为 order_no 不是唯⼀索引,所以⾏锁的类型是间隙 锁,于是间隙 锁的范围是 (1006, +∞ ) 。
那么,当事务 B 往间隙 锁⾥插⼊ id = 1008 的记录就会被锁住。因为当我们执⾏以下插⼊语句时,会在插⼊间隙 上再次获取插⼊意向锁。
insert into t_order (order_no, create_date) values (1008, now());插⼊意向锁与间隙 锁是冲突的,所以当其它事务持有该间隙 的间隙 锁时,需要等待其它事务释放
间隙 锁之后,才能获取到插⼊意向锁。⽽间隙 锁与间隙 锁之间是兼容的,所以所以两个事 务中
select ... for update 语句并不会相互影响。
案例中的事务 A 和事务 B 在执⾏完后 select ... for update 语句后都持有范围为 (1006,+∞ )的间隙 锁,⽽接下来的插⼊操作为了 获取到插⼊意向锁,都在等待对⽅事务的间隙 锁释放,于是
就造成了循环等待,导致死锁。
MyBatis
## 我看你写到了MyBatis ,#和$有什么 区别(主要是SQL 注⼊的问题)
Mybatis 在处理 #{} 时,会创建预编译的 SQL 语句,将 SQL 中的 #{} 替换为 ? 号,在执⾏ SQL时会为预编译 SQL 中的占位符(?)赋值,调⽤ PreparedStatement 的 set ⽅法来赋值,预编译
的 SQL 语句执⾏效率⾼,并且可以防⽌SQL 注⼊,提供更⾼的安全性,适合传递参数值。
Mybatis 在处理 ${} 时,只是创建普通的 SQL 语句,然后在执⾏ SQL 语句时 MyBatis 将参数直接拼 ⼊到 SQL ⾥,不能防⽌ SQL 注⼊,因为参数直接拼接 到 SQL 语句中,如果参数未经过验
证、过滤,可能会导致安全问题。
你说到了SQL 注⼊,那你给我设计 出⼀个SQL 注⼊,具体说表中的字段,然
后SQL 语句是怎样的(有印象,但是⾃⼰说不来)
每次 ⽤⼾登录时,都会执⾏⼀个相应的 SQL 语句。这时,⿊客会通过构造⼀些恶意的输⼊参数,
在应⽤拼接 SQL 语句的时候,去篡改正常的 SQL 语意,从⽽执⾏⿊客所控制的 SQL 查询功能。这
个过程,就相当于⿊客“注⼊”了⼀段 SQL 代码到应⽤中。这就是我们常说的 SQL 注⼊。
我们先来看⼀个例⼦。现在有⼀个简单的登 录⻚⾯,需要⽤⼾输⼊ Username 和 Password 这两个
变量来完成登录。具体的 Web 后台 代码如下所⽰:
uName = getRequestString("username");
uPass = getRequestString("password");
sql='SELECT * FROM Users WHERE Username ="'+ uName +'" AND Password ="'+ uPass +'"'当⽤⼾提交⼀个表单(假设 Username 为 admin ,Password 为 123456 )时,Web 将执⾏下⾯这
⾏代码:
SELECT*FROM Users WHERE Username ="admin" AND Password ="123456"⽤⼾名密码如果正确的话,这句 SQL 就能够返回对应的⽤⼾信息;如果错误的话,不会返回任何 信息。因此,只要返回的⾏数≥1 ,就说明验证通过,⽤⼾可以成功登录。
所以,当⽤⼾正常地输⼊⾃⼰的⽤⼾名和 密码时,⾃然就可以成功登录应⽤。那⿊客想要在不知
道密码的情况下登录应⽤,他⼜会输⼊什么 呢?他会输⼊ " or ""=" 。这时,应⽤的数据库就会执⾏下⾯这⾏代码:
SELECT*FROM Users WHERE Username ="" AND Password ="" or ""=""
我们可以看到,WHERE 语句后⾯的判断是通过 or 进⾏拼接 的,其中""="" 的结果是 true 。那么,当有⼀个 or 是 true 的时候,最终结 果就⼀定是 true 了。因此,这个 WHERE 语句是恒为真的,所
以,数据库将返回全部的数据。
这样⼀来,我们就能解答⽂章开头的问题了,也就是说,⿊客只需要在登录⻚⾯中输⼊ " or ""=" ,就可以在不知道密码的情况下,成功登录后台 了。⽽这,也就是所谓的“万能密码”。⽽这个“万能密码”,其实就是通过修改 WHERE 语句,改变数据库的返回结果,实现⽆密码登录。
Redis
你⽤Redis 做了什么
主要⽤作缓存,Redis 除了⽤于缓存,还能实现分布式锁、消息队列等等 。
本地缓存和Redis 缓存的区别(没了解过)
本地缓存是指将数据存储在本地应⽤程序或服务器上,通常⽤于加速数据访问和提⾼响应速度。
本地缓存通常使⽤内存作为存储介质,利⽤内存的⾼速读写特性来提⾼数据访问速度。
本地缓存的优势:
访问速度快:由于本地缓存存 储在本地内存中,因此访问速度⾮常快,能够满⾜频繁访问和即
时响应的需求。
减轻⽹络压⼒:本地缓存能够降低对远程服务器的访问次数,从⽽减轻⽹络压⼒,提⾼系统的
可⽤性和稳定性。
低延迟:由于本地缓存位于本地设备上,因此能够提供低 延迟的访问速度,适⽤于对实时性要
求较⾼的应⽤场景。
本地缓存的不⾜:
可扩展性有限:本地缓存的可扩展性受到硬件资源的限制,⽆法⽀持⼤规模的数据存储和访
问。
** 分布式缓存(Redis )** 是指将数据存储在多个分布式节点上,通过协同⼯作来提供⾼性能的数
据访问服务。分布式缓存通常使⽤集群⽅式进⾏部署,利⽤多台服务器来分担数据存储和访问的
压⼒。
分布式缓存的优势:
可扩展性强:分布式缓存的节点可以动态扩展,能够⽀持⼤规模的数据存储和访问需求。
数据⼀致性⾼:通过分布式⼀致性协议,分布式缓存能够保证数据在多个节点之间的⼀致性,
减少数据不⼀致的问题。
易于维护:分布式缓存通常采⽤⾃动化管理⽅式,能够降低维护成 本和管理的复杂性。
分布式缓存的不⾜:
访问速度相对较慢:相对于本地缓存,分布式缓存的访问速度相对较慢,因为数据需要从多个
节点进⾏访问和协同。
⽹络开销⼤:由于分布式缓存需要通过⽹络进⾏数据传输和协同操作,因此相对于本地缓存来
说,⽹络开销较⼤。
在选择使⽤本地缓存还是分布式缓存时,我们需要根据具体的应⽤场景和需求进⾏权衡。以下是
⼀些考虑因素:
数据⼤⼩:如果数据量较⼩,且对实时性要求较⾼,本地缓存更适合;如果数据量较⼤,且需
要⽀持⼤规模的并发访问,分布式缓存更具优势。
⽹络状况:如果⽹络状况良好且稳定,分布式缓存能够更好地发挥其优势;如果⽹络状况较差
或不稳定,本地缓存的访问速度和稳定性可能更有 优势。
业务特点:对于实时性要求较
Redis 的Key 过期了是⽴⻢删除吗(回答了定期删除和惰性删除两种策略)
不会,Redis 的过期删除策略是选择「惰性删除+定期删除」这两种策略配和使⽤。
惰性删除策略的做法是,不主 动删 除过期键,每次 从数据库访问 key 时,都检测 key 是否过
期,如果过期则删 除该 key 。
定期删除策略的做法是,每隔⼀段时间「随机」从数据库中取出⼀定数量的 key 进⾏检查,并
删除其中的过期key 。
Redis 的⼤Key 问题是什么 ?(答出来了)
Redis ⼤key 问题指的是某个key 对应的value 值所占的内存空间⽐较⼤,导致Redis 的性能下降、内
存不⾜、数据不均衡以及主从 同步延迟等问题。
到底多⼤的数据量才算是⼤key ?
没有固定的判别 标准,通常认为字符串类型的key 对应的value 值占⽤空间⼤于1M ,或者集合类型
的k元素数量超过1万个 ,就算是⼤key 。
Redis ⼤key 问题的定义及评判准则并⾮⼀成不变,⽽应根据Redis 的实际运⽤以及业务需求来综合
评估。
例如,在⾼并发且低延迟的场景中,仅10kb 可能就已构成⼤key ;然⽽在低并发、⾼容量的环境
下,⼤key 的界限可能在100kb 。因此,在设计 与运⽤Redis 时,要依据业务需求与性能指标来确⽴
合理的⼤key 阈值。
⼤Key 问题的缺点?(答出来了)
内存占⽤过⾼。⼤Key 占⽤过多的内存空间,可能导致可⽤内存不⾜,从⽽触发内存淘汰策略。
在极端情况下,可能导致内存耗尽,Redis 实例崩溃,影响系统的稳定性。
性能下降。⼤Key 会占⽤⼤量内存空间,导致内存碎⽚增加,进⽽影响Redis 的性能。对于⼤Key
的操作,如读取、写⼊、删除等,都会消耗更多的CPU 时间和内存资源,进⼀步降低系统性
能。
阻塞其他操作。某些对⼤Key 的操作可能会导致Redis 实例阻塞。例如,使⽤DEL 命令删除⼀个⼤
Key 时,可能会导致Redis 实例在⼀段时间内⽆法响应其他客⼾端请求,从⽽影响系统的响应时
间和吞吐 量。
⽹络拥塞。每次 获取⼤key 产⽣的⽹络流量较⼤,可能造成机器或局域⽹的带宽被打满,同时波
及其他服务。例如:⼀个⼤key 占⽤空间是1MB ,每秒访问1000 次,就有1000MB 的流量。
主从 同步延迟。当Redis 实例配置了主从 同步时,⼤Key 可能导致主从 同步延迟。由于⼤Key 占⽤
较多内存,同步过程中需要传输⼤量数据,这会导致主从之 间的⽹络传输延迟增加,进⽽影响
数据⼀致性。
数据倾斜。在Redis 集群模式中,某个数据分⽚的内存使⽤率远超其他数据分⽚,⽆法使数据分
⽚的内存资源达到均衡。另外也可能造成Redis 内存达到maxmemory 参数定义的上限导致重要
的key 被逐出,甚⾄引发内存溢出。
ZSet 的底层数据结构,查询的时间复杂度是多少?
Zset 类型的底层数据结构是由压缩列表或跳表实现的:如果有 序集合的元素个数⼩于 128 个,并
且每个元素的值⼩于 64 字节时,Redis 会使 ⽤压缩列表作为 Zset 类型的底层数据结构,压缩列表
的查找操作是顺序查找,时间复杂度为O(n)如果有 序集合的元素不满⾜上⾯的条件,Redis 会使 ⽤跳表作为 Zset 类型的底层数据结构,跳
表查询任意数据的时间复杂度就是 O(logn)在 Redis 7.0 中,压缩列表数据结构已经废弃了,交由 listpack 数据结构来 实现了。
Redis 的持久化(AOF 和RDB )?
Redis 的持久化机制有两种,⼀种是快照(RDB ),另⼀种是 AOF ⽇志。
RDB 是⼀次全量备份,AOF ⽇志是连续的增量备份。快照是内存数据的⼆进制序列化形式 ,在存
储上⾮常紧凑,⽽ AOF ⽇志记录的是内存数据修改的指令记录⽂本。
RDB 是怎样做的?(答出来了)
所谓的快照,就是记录某⼀个瞬间东西,⽐如当我们给⻛景拍照时,那⼀个瞬间的画⾯和信息就
记录到了⼀张照⽚。
所以,RDB 快照就是记录某⼀个瞬间的内存数据,记录的是实际数据,⽽ AOF ⽂件记录的是命令
操作的⽇志,⽽不是实际的数据。因此在 Redis 恢复数据时, RDB 恢复数据的效率会⽐ AOF ⾼
些,因为直接将 RDB ⽂件读⼊内存就可以,不需要像 AOF 那样还需要额外执⾏操作命令的步骤才
能恢复数据。
Redis 提供了两个 命令来⽣成 RDB ⽂件,分别 是 save 和 bgsave ,他们的区别就在于是否在「主线程」⾥执⾏:
执⾏了 save 命令,就会在主线程⽣成 RDB ⽂件,由于和执⾏操作命令在同⼀个线程,所以如
果写⼊ RDB ⽂件的时间太⻓,会阻塞主线程;
执⾏了 bgsave 命令,会创建⼀个⼦进程来⽣成 RDB ⽂件,这样可以避免主线程的阻塞,
basave 命令可以在主进程的基础上,fork ⼀个⼦进程,⼦进程会共享主 进程的代码段和数据
段,相当于是在后台 ⽣成快照。
aof 的写⼊策略,按时间写⼊和每次 都写⼊的区别,优缺点(答出来了)
Redis 提供了 3 种写回硬盘的 策略, 在 Redis.conf 配置⽂件中的 appendfsync 配置项可以有以下
3 种参数可填:
Always ,这个单词的意思是「总是」,所以它的意思是每次 写操作命令执⾏完后,同步将 AOF
⽇志数据写回硬盘;
Everysec ,这个单词的意思是「每秒」,所以它的意思是每次 写操作命令执⾏完后,先将命令写
⼊到 AOF ⽂件的内核缓冲区,然后每隔⼀秒将缓冲区⾥的内容写回到硬盘;
No ,意味着不由 Redis 控制写回硬盘的 时机,转交给操作系统控制写回的时机,也就是每次 写
操作命令执⾏完后,先将命令写⼊到 AOF ⽂件的内核缓冲区,再由操作系统决定何时将缓冲区
内容写回硬盘。
我也把这 3 个写回策略的优缺点总结成了⼀张表格:

你平常 是怎么使⽤RDB 和AOF 的?

数据安全性:如果要求数据不丢 失,推荐AOF
如果使⽤每秒同步⼀次策略,则最多丢失⼀秒的数据
如果使⽤每次 写操作都同步策略,安全性达到了极致,但这会影响性能
AOF 可以采取每秒同步⼀次数据或每次 写操作都同步⽤来保证数据安全性
RDB 是⼀个全量的⼆进制⽂件,恢复时只需要加载到内存即可,但是可能会丢失最近⼏分钟的
数据(取决于RDB 持久化策略)
数据恢复速度:如果要求快速恢复数据,推荐RDB
AOF 需要重新执⾏所有的写命令,恢复时间会更⻓
RDB 是⼀个全量的⼆进制⽂件,恢复时只需要加载到内存即可
数据备份和迁移:如果要求⽅便地进⾏数据备份和迁移,推荐RDB
AOF ⽂件可能会很⼤,传输速度慢
RDB ⽂件是⼀个紧凑的⼆进制⽂件,占⽤空间⼩,传输速度快
数据可读性:如果要求能够⽅便地查看和修改数 据,推荐AOF
AOF 是⼀个可读的⽂本⽂件,记录了所有的写命令,可以⽤于灾难恢复或者数据分析
RDB 是⼀个⼆进制⽂件,不易查看和修改
Spring
Bean 的⽣命周 期(答出来了,主要分⼏个过程,细致介绍了⼀遍)

Spring 启动,查找并加载需要被Spring 管理的bean ,进⾏Bean 的实例化
Bean 实例化后对将 Bean 的引⼊和值注⼊到Bean 的属性中
3. 如果Bean 实现了BeanNameAware 接⼝的话,Spring 将Bean 的Id 传递给setBeanName() ⽅法
4. 如果Bean 实现了BeanFactoryAware 接⼝的话,Spring 将调⽤setBeanFactory() ⽅法,将BeanFactory 容器实例传 ⼊
- 如果Bean 实现了ApplicationContextAware 接⼝的话,Spring 将调⽤Bean 的
setApplicationContext() ⽅法,将bean 所在应⽤上下 ⽂引⽤传⼊进来。- 如果Bean 实现了BeanPostProcessor 接⼝,Spring 就将 调⽤他们的
postProcessBeforeInitialization() ⽅法。
7. 如果Bean 实现了InitializingBean 接⼝,Spring 将调⽤他们的afterPropertiesSet() ⽅法。类似的,如果bean 使⽤init-method 声明了初始化⽅法,该⽅法也会被调⽤
- 如果Bean 实现了BeanPostProcessor 接⼝,Spring 就将 调⽤他们的
postProcessAfterInitialization() ⽅法。- 此时,Bean 已经准备就绪,可以被应⽤程序使⽤了。他们将⼀直驻留在应⽤上下 ⽂中,直到应
⽤上下 ⽂被销毁。
10. 如果bean 实现了DisposableBean 接⼝,Spring 将调⽤它的destory() 接⼝⽅法,同样,如果bean使⽤了destory-method 声明销毁⽅法,该⽅法也会被调⽤。
Bean 是否单例?
Spring 中的 Bean 默认都是单例的。
就是说,每个Bean 的实例只会被创建⼀次,并且会被存储在Spring 容器的缓存中,以便 在后续的
请求中重复使⽤。这种单例模式可以提⾼应⽤程序的性能和内存效率。
但是,Spring 也⽀持将Bean 设置为多例模式,即每次 请求都会创建⼀个新的Bean 实例。要将Bean
设置为多例模式,可以在Bean 定义中 通过设置scope 属性为"prototype" 来实现。
需要注意的是,虽然Spring 的默认⾏为是将Bean 设置为单例模式,但在⼀些情况下,使⽤多例模
式是更为合适的,例如在创建状态不可变的Bean 或有状态Bean 时。此外,需要注意的是,如果
Bean 单例是有状态的,那么在使⽤时需要考虑线程安全性问题。
Bean 的单例和⾮单例,⽣命周 期是否⼀样
不⼀样的,Spring Bean 的⽣命周 期完全由 IoC 容器控制。Spring 只帮我们管理单例模式 Bean 的
完整⽣命周 期,对于 prototype 的 Bean ,Spring 在创建好交给使⽤者之后,则不会再管理后续
的⽣命周 期。
你刚才说的Bean ⽣命周 期,是单例的还是⾮单例的
单例 Bean
Spring 容器⾥存的是什么 ?
在Spring 容器中,存储的主要是Bean 对象。
Bean 是Spring 框架中的基本组件,⽤于表⽰应⽤程序中的各种对象。当应⽤程序启动时,Spring 容
器会根据配置⽂件或注解的⽅式创建和管理这些Bean 对象。Spring 容器会负责 创建、初始化、注
⼊依赖以及销毁Bean 对象。
Bean 注⼊和xml 注⼊最终得到了相同的效果,它们在底层是怎样做的
XML 注⼊
使⽤ XML ⽂件进⾏ Bean 注⼊时,Spring 在启动时会读取 XML 配置⽂件,以下是其底层步骤:
Bean 定义解析:Spring 容器通过 XmlBeanDefinitionReader 类解析 XML 配置⽂件,读取其中
的 <bean> 标签以获取 Bean 的定义信息。注册 Bean 定义:解析后的 Bean 信息被注册到 BeanDefinitionRegistry (如
DefaultListableBeanFactory )中,包括 Bean 的类、作⽤域、依赖关系、初始化和销毁⽅法
等。
实例化和依赖注⼊:当应⽤程序请求某个 Bean 时,Spring 容器会根据已经注册的 Bean 定义:
⾸先,使⽤反射机制创 建该 Bean 的实例。
然后,根据 Bean 定义中 的配置,通过 setter ⽅法、构造函数或⽅法注 ⼊所需的依赖 Bean 。
注解注⼊
使⽤注解进⾏ Bean 注⼊时,Spring 的处理过程如下:
类路径扫描:当 Spring 容器启动时,它⾸先会进⾏类路径扫描,查找带有特定注解(如
@Component 、 @Service 、 @Repository 和 @Controller )的类。注册 Bean 定义:找到的类会被注册到 BeanDefinitionRegistry 中,Spring 容器将为其⽣成
Bean 定义信息。这通常通过 AnnotatedBeanDefinitionReader 类来实现。
依赖注⼊:与 XML 注⼊类似,Spring 在实例化 Bean 时,也会检查字段上是否有
@Autowired 、 @Inject 或 @Resource 注解。如果有 ,Spring 会根据注解的信息进⾏依赖注⼊。
尽管使⽤的⽅式不同,但 XML 注⼊和注解注⼊在底层的实现机制是相似的,主要体现在以下⼏个
⽅⾯:
- BeanDefinition :⽆论是 XML 还是注解,最终都会⽣成 BeanDefinition 对象,并存储在同⼀
个 BeanDefinitionRegistry 中。
- 后处理器:
Spring 提供了多个 Bean 后处理器( 如 AutowiredAnnotationBeanPostProcessor ),⽤于处理
注解(如 @Autowired )的依赖注⼊。
对于 XML ,Spring 也有相应的后处理器来处理 XML 配置的依赖注⼊。
- 依赖查找:在依赖注⼊时,Spring 容器会通过 ApplicationContext 中的 BeanFactory ⽅法来查找和注⼊依赖,⽆论是通过 XML 还是注解,都会调⽤类似的查找⽅法。
