1、Spring AOP 底层原理
aop 底层是采用动态代理机制实现的:接口+实现类
l 如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代
理对象。
l 没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会
使用 Cglib 生成一个被代理对象的子类来作为代理。
就是由代理创建出一个和 impl 实现类平级的一个对象,但是这个对象不是一个真正的对象,
只是一个代理对象,但它可以实现和 impl 相同的功能,这个就是 aop 的横向机制原理,这
样就不需要修改源代码。
2、HashMap 的底层数据结构是怎样的 ?
JDK1.8 之前
l JDK1.8 之前 HashMap 底层是 数组和链表 结合在一起使用也就是 链表散列。
l HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 (n -
1) & hash 判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在
元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,
直接覆盖,不相同就通过拉链法解决冲突。
l 所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了
防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。
JDK1.8 之后
当链表长度大于阈值(默认为 8)时,会首先调用 treeifyBin()方法。这个方法会根据
HashMap 数组来决定是否转换为红黑树。只有当数组长度大于或者等于 64 的情况下,才会
执行转换红黑树操作,以减少搜索时间。否则,就是只是执行 resize() 方法对数组扩容。
3、HashMap 的扩容机制是怎样的?
一般情况下,当元素数量超过阈值时便会触发扩容。每次扩容的容量都是之前容量的 2 倍。
HashMap 的容量是有上限的,必须小于 1<<30,即 1073741824。如果容量超出了这个
数,则不再增长,且阈值会被设置为 Integer.MAX_VALUE。
JDK7 中的扩容机制
l 空参数的构造函数:以默认容量、默认负载因子、默认阈值初始化数组。内部数组是空数
组。
l 有参构造函数:根据参数确定容量、负载因子、阈值等。
l 第一次 put 时会初始化数组,其容量变为不小于指定容量的 2 的幂数,然后根据负载因
子确定阈值。
l 如果不是第一次扩容,则 新容量=旧容量 x 2 ,新阈值=新容量 x 负载因子 。
JDK8 的扩容机制
l 空参数的构造函数:实例化的 HashMap 默认内部数组是 null,即没有实例化。第一次
调用 put 方法时,则会开始第一次初始化扩容,长度为 16。
l 有参构造函数:用于指定容量。会根据指定的正整数找到不小于指定容量的 2 的幂数,
将这个数设置赋值给阈值(threshold)。第一次调用 put 方法时,会将阈值赋值给容量,
然后让 阈值 = 容量 x 负载因子。
l 如果不是第一次扩容,则容量变为原来的 2 倍,阈值也变为原来的 2 倍。(容量和阈值
都变为原来的 2 倍时,负载因子还是不变)。
此外还有几个细节需要注意:
l 首次 put 时,先会触发扩容(算是初始化),然后存入数据,然后判断是否需要扩容;
l 不是首次 put,则不再初始化,直接存入数据,然后判断是否需要扩容;
4、ConcurrentHashMap 的存储结构是怎样的?
l Java7 中 ConcurrnetHashMap 使用的分段锁,也就是每一个 Segment 上同时只有一
个线程可以操作,每一个 Segment 都是一个类似 HashMap 数组的结构,它可以扩容,
它的冲突会转化为链表。但是 Segment 的个数一但初始化就不能改变,默认 Segment
的个数是 16 个。
l Java8 中的 ConcurrnetHashMap 使用的 Synchronized 锁加 CAS 的机制。结构也由
Java7 中的 Segment 数组 + HashEntry 数组 + 链表 进化成了 Node 数组 + 链表 / 红
黑树,Node 是类似于一个 HashEntry 的结构。它的冲突再达到一定大小时会转化成红
黑树,在冲突小于一定数量时又退回链表。5、线程池大小如何设置?
l CPU 密集型任务(N+1): 这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N
(CPU 核心数)+1,比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,
或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,
而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。
l I/O 密集型任务(2N): 这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线
程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使
用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是 2N。
如何判断是 CPU 密集任务还是 IO 密集任务?
CPU 密集型简单理解就是利用 CPU 计算能力的任务比如你在内存中对大量数据进行排序。
单凡涉及到网络读取,文件读取这类都是 IO 密集型,这类任务的特点是 CPU 计算耗费时间
相比于等待 IO 操作完成的时间来说很少,大部分时间都花在了等待 IO 操作完成上。
6、IO 密集=Ncpu*2 是怎么计算出来?
l I/O 密集型任务任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理
I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。因此在
I/O 密集型任务的应用中,我们可以多配置一些线程。例如:数据库交互,文件上传下载,
网络传输等。IO 密集型,即该任务需要大量的 IO,即大量的阻塞,故需要多配置线程数。
7、G1 收集器有哪些特点?
l G1 的全称是 Garbage-First,意为垃圾优先,哪一块的垃圾最多就优先清理它。
l G1 GC 最主要的设计目标是:将 STW 停顿的时间和分布,变成可预期且可配置的。
被视为 JDK1.7 中 HotSpot 虚拟机的一个重要进化特征。它具备一下特点:
l 并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者
CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线
程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。
l 分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留
了分代的概念。
l 空间整合:与 CMS 的“标记-清理”算法不同,G1 从整体来看是基于“标记-整理”算
法实现的收集器;从局部上来看是基于“标记-复制”算法实现的。
l 可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共
同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明
确指定在一个长度为 M 毫秒的时间片段内。
G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的
Region(这也就是它的名字 Garbage-First 的由来)
2425
8、你有哪些手段来排查 OOM 的问题?
l 增加两个参数 -XX:+HeapDumpOnOutOfMemoryError -
XX:HeapDumpPath=/tmp/heapdump.hprof,当 OOM 发生时自动 dump 堆内存信息
到指定目录。
l 同时 jstat 查看监控 JVM 的内存和 GC 情况,先观察问题大概出在什么区域。
l 使用 MAT 工具载入到 dump 文件,分析大对象的占用情况,比如 HashMap 做缓存未
清理,时间长了就会内存溢出,可以把改为弱引用。
9、请你谈谈 MySQL 事务隔离级别,MySQL 的默认隔离级别是什么?
为了达到事务的四大特性,数据库定义了 4 种不同的事务隔离级别:
l READ-UNCOMMITTED(读取未提交):最低的隔离级别,允许脏读,也就是可能读取
到其他会话中未提交事务修改的数据,可能会导致脏读、幻读或不可重复读。
l READ-COMMITTED(读取已提交): 只能读取到已经提交的数据。Oracle 等多数数
据库默认都是该级别 (不重复读),可以阻止脏读,但是幻读或不可重复读仍有可能发
生。
l REPEATABLE-READ(可重复读):对同一字段的多次读取结果都是一致的,除非数据
是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
l SERIALIZABLE(可串行化):最高的隔离级别,完全服从 ACID 的隔离级别。所有的
事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏
读、不可重复读以及幻读。
l MySQL 默认采用的 REPEATABLE_READ 隔离级别。
10、可重复读解决了哪些问题?
l 可重复读的核心就是一致性读(consistent read);保证多次读取同一个数据时,其值都和事
务开始时候的内容是一致,禁止读取到别的事务未提交的数据,会造成幻读。
l 而事务更新数据的时候,只能用当前读。如果当前的记录的行锁被其他事务占用的话,就
需要进入锁等待。
l 查询只承认在事务启动前就已经提交完成的数据。
l 可重复读解决的是重复读的问题,可重复读在快照读的情况下是不会有幻读,但当前读的
时候会有幻读。
11、对 SQL 慢查询会考虑哪些优化 ?
l 分析语句,是否加载了不必要的字段/数据。
l 分析 SQL 执行计划(explain extended),思考可能的优化点,是否命中索引等。
l 查看 SQL 涉及的表结构和索引信息。l 如果 SQL 很复杂,优化 SQL 结构。
l 按照可能的优化点执行表结构变更、增加索引、SQL 改写等操作。
l 查看优化后的执行时间和执行计划。
l 如果表数据量太大,考虑分表。
l 利用缓存,减少查询次数。
12、谈一谈缓存穿透、缓存击穿和缓存雪崩,以及解决办法?
缓存穿透
l 问题:大量并发查询不存在的 KEY,在缓存和数据库中都不存在,同时给缓存和数据库
带来压力。
l 原因:一般而言,缓存穿透有 2 种可能性:业务数据被误删,导致缓存和数据库中都没
有数据。恶意进行 ddos 攻击。
l 分析:为什么会多次透传呢?不存在 一直为空,需要注意让缓存能够区分 KEY 不存在和
查询到一个空值。
l 解决办法:缓存空值的 KEY,这样第一次不存在也会被加载会记录,下次拿到有这个
KEY。Bloom 过滤或 RoaingBitmap 判断 KEY 是否存在,如果布隆过滤器中没有查到
这个数据,就不去数据库中查。在处理请求前增加恶意请求检查,如果检测到是恶意攻击,
则拒绝进行服务。完全以缓存为准,使用延迟异步加载的策略(异步线程负责维护缓存的
数据,定期或根据条件触发更新),这样就不会触发更新。
缓存击穿
l 问题:某个 KEY 失效的时候,正好有大量并发请求访问这个 KEY。
l 分析:跟穿透其实很像,属于比较偶然的。
l 解决办法:KEY 的更新操作添加全局互斥锁。完全以缓存为准,使用延迟异步加载的策
略(异步线程负责维护缓存的数据,定期或根据条件触发更新),这样就不会触发更新。
缓存雪崩
l 问题:当某一时刻发生大规模的缓存失效的情况,导致大量的请求无法获取数据,从而将
流量压力传导到数据库上,导致数据库压力过大甚至宕机。
l 原因:一般而言,缓存雪崩有 2 种可能性:大量的数据同一个时间失效:比如业务关系
强相关的数据要求同时失效 Redis 宕机
l 分析:一般来说,由于更新策略、或者数据热点、缓存服务宕机等原因,可能会导致缓存
数据同一个时间点大规模不可用,或者都更新。所以,需要我们的更新策略要在时间上合
适,数据要均匀分享,缓存服务器要多台高可用。
l 解决办法:更新策略在时间上做到比较平均。如果数据需要同一时间失效,可以给这批数
据加上一些随机值,使得这批数据不要在同一个时间过期,降低数据库的压力。使用的热
数据尽量分散到不同的机器上。多台机器做主从复制或者多副本,实现高可用。做好主从
的部署,当主节点挂掉后,能快速的使用从结点顶上。实现熔断限流机制,对系统进行负
载能力控制。对于非核心功能的业务,拒绝其请求,只允许核心功能业务访问数据库获取
数据。服务降价:提供默认返回值,或简单的提示信息。
2613、LRU 是什么?如何实现?
最近最少使用策略 LRU(Least Recently Used)是一种缓存淘汰算法,是一种缓存淘汰机
制。
l 使用双向链表实现的队列,队列的最大容量为缓存的大小。在使用过程中,把最近使用的
页面移动到队列头,最近没有使用的页面将被放在队列尾的位置
l 使用一个哈希表,把页号作为键,把缓存在队列中的节点的地址作为值,只需要把这个页
对应的节点移动到队列的前面,如果需要的页面在内存中,此时需要把这个页面加载到内
存中,简单的说,就是将一个新节点添加到队列前面,并在哈希表中跟新相应的节点地址,
如果队列是满的,那么就从队尾移除一个节点,并将新节点添加到队列的前面。
14、什么是堆内存?参数如何设置?
堆内存是指由程序代码自由分配的内存,与栈内存作区分。
在 Java 中,堆内存主要用于分配对象的存储空间,只要拿到对象引用,所有线程都可
以访问堆内存。
l -Xmx, 指定最大堆内存。 如 -Xmx4g. 这只是限制了 Heap 部分的最大值为 4g。这个内
存不包括栈内存,也不包括堆外使用的内存。
l -Xms, 指定堆内存空间的初始大小。 如 -Xms4g。 而且指定的内存大小,并不是操作系
统实际分配的初始值,而是 GC 先规划好,用到才分配。 专用服务器上需要保持 –Xms
和 –Xmx 一致,否则应用刚启动可能就有好几个 FullGC。当两者配置不一致时,堆内存
扩容可能会导致性能抖动。
l -Xmn, 等价于 -XX:NewSize,使用 G1 垃圾收集器 不应该 设置该选项,在其他的某些
业务场景下可以设置。官方建议设置为 -Xmx 的 1/2 ~ 1/4.
l -XX:MaxPermSize=size, 这是 JDK1.7 之前使用的。Java8 默认允许的 Meta 空间无
限大,此参数无效。
l -XX:MaxMetaspaceSize=size, Java8 默认不限制 Meta 空间, 一般不允许设置该选项。
l -XX:MaxDirectMemorySize=size,系统可以使用的最大堆外内存,这个参数跟 -
Dsun.nio.MaxDirectMemorySize 效果相同。
l -Xss, 设置每个线程栈的字节数。 例如 -Xss1m 指定线程栈为 1MB,与-
XX:ThreadStackSize=1m 等价
15、栈和队列,举个使用场景例子?
l 栈(后进先出)可以用于字符匹配,数据反转等场景
l 队列(先进先出)可以用于任务队列,共享打印机等场景
16、MySQL 为什么 InnoDB 是默认引擎?
27聚集索引是指数据库表行中数据的物理顺序与键值的逻辑(索引)顺序相同。一个表只能有一
个聚簇索引,因为一个表的物理顺序只有一种情况,所以,对应的聚簇索引只能有一个。聚簇
索引的叶子节点就是数据节点,既存储索引值,又在叶子节点存储行数据。
Innodb 创建表后生成的文件有:
frm:创建表的语句
idb:表里面的数据+索引文件
17、MySQL 索引底层结构为什么使用 B+树?
l 哈希虽然能够提供 O(1) 的单数据行操作性能,但是对于范围查询和排序却无法很好地支
持,最终导致全表扫描;B 树能够在非叶节子点中存储数据,但是这也导致在查询连续数
据时可能会带来更多的随机 I/O,而 B+树的所有叶节点可以通过指针相互连接,能够减
少顺序遍历时产生的额外随机 I/O;
l 第一,B 树一个节点里存的是数据,而 B+树存储的是索引(地址),所以 B 树里一个节
点存不了很多个数据,但是 B+树一个节点能存很多索引,B+树叶子节点存所有的数据。
l 第二,B+树的叶子节点是数据阶段用了一个链表串联起来,便于范围查找。
18、B+ 树的叶子节点链表是单向还是双向?
双向链表
19、MVCC 是什么?它的底层原理是什么?
MVCC,多版本并发控制,它是通过读取历史版本的数据,来降低并发事务冲突,从而提高并
发性能的一种机制。
l 事务版本号
l 表的隐藏列
l undo log
l read view
20、undo log 具体怎么回滚事务 ?
举个例子:
l 对于 insert 类型的 sql,会在 undo log 中记录下方才你 insert 进来的数据的 ID,当你
想 roll back 时,根据 ID 完成精准的删除。
l 对于 delete 类型的 sql,会在 undo log 中记录方才你删除的数据,当你回滚时会将删
除前的数据 insert 进去。
l 对于 update 类型的 sql,会在 undo log 中记录下修改前的数据,回滚时只需要反向
update 即可。
l 对于 select 类型的 sql,别费心了,select 不需要回滚。
2829
21、如何查询慢 SQL 产生的原因
l 分析 SQL 执行计划(explain extended),思考可能的优化点,是否命中索引等。
l 没有索引或者没有用到索引(这是查询慢最常见的问题,是程序设计的缺陷)。
l 内存不足。
l 网络速度慢。
l 是否查询出的数据量过大(可以采用多次查询,其他的方法降低数据量)。
l 是否返回了不必要的行和列。
l 锁或者死锁。
l I/O 吞吐量小,形成了瓶颈效应。
l sp_lock,sp_who,活动的用户查看,原因是读写竞争资源。
22、索引失效的情况有哪些?
l like 以%开头索引无效,当 like 以&结尾,索引有效。
l or 语句前后没有同事使用索引,当且仅当 or 语句查询条件的前后列均为索引时,索引
生效。
l 组合索引,使用的不是第一列索引时候,索引失效,即最左匹配规则。
l 数据类型出现隐式转换,如 varchar 不加单引号的时候可能会自动转换为 int 类型,这
个时候索引失效。
l 在索引列上使用 IS NULL 或者 IS NOT NULL 时候,索引失效,因为索引是不索引空值
得。
l 在索引字段上使用,NOT、 <>、!= 、时候是不会使用索引的,对于这样的处理只会进
行全表扫描。
l 对索引字段进行计算操作,函数操作时不会使用索引。
l 当全表扫描速度比索引速度快的时候不会使用索引。
23、一个 Redis 实例最多能存放多少的 keys?List、Set、Sorted Set 他们最
多能存放多少元素?
理论上 Redis 可以处理多达 232 的 keys,并且在实际中进行了测试,每个实例至少存放
了 2 亿 5 千万的 keys。我们正在测试一些较大的值。任何 list、set、和 sorted set 都可
以放 232 个元素。换句话说,Redis 的存储极限是系统中的可用内存值。
24、Redis 数据结构 压缩列表和跳跃表的区别
l 压缩列表(ziplist)本质上就是一个字节数组,是 Redis 为了节约内存而设计的一种线性
数据结构,可以包含多个元素,每个元素可以是一个字节数组或一个整数。30
l 跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指
针,从而达到快速访问节点的目的。跳跃表支持平均 O(logN)、最坏 O(N)复杂度 的节点查找,还可以通过顺序性操作来批量处理节点。
25、为什么数据量小的时候用压缩列表 ?
为了省内存。
26、Redis 主从同步是怎么实现的?
全量同步
master 服务器会开启一个后台进程用于将 redis 中的数据生成一个 rdb 文件,与此同时,
服务器会缓存所有接收到的来自客户端的写命令(包含增、删、改),当后台保存进程处理完
毕后,会将该 rdb 文件传递给 slave 服务器,而 slave 服务器会将 rdb 文件保存在磁盘并
通过读取该文件将数据加载到内存,在此之后 master 服务器会将在此期间缓存的
命令通过 redis 传输协议发送给 slave 服务器,然后 slave 服务器将这些命令依次作用于
自己本地的数据集上最终达到数据的一致性。
增量同步
从 redis 2.8 版本以前,并不支持部分同步,当主从服务器之间的连接断掉之后,master 服
务器和 slave 服务器之间都是进行全量数据同步。
从 redis 2.8 开始,即使主从连接中途断掉,也不需要进行全量同步,因为从这个版本开始
融入了部分同步的概念。部分同步的实现依赖于在 master 服务器内存中给每个 slave 服务
器维护了一份同步日志和同步标识,每个 slave 服务器在跟 master 服务器进行同步时都会
携带自己的同步标识和上次同步的最后位置。当主从连接断掉之后,slave 服务器隔断时间
(默认 1s)主动尝试和 master 服务器进行连接,如果从服务器携带的偏移量标识还在
master 服务器上的同步备份日志中,那么就从 slave 发送的偏移量开始继续上次的同步操
作,如果 slave 发送的偏移量已经不再 master 的同步备份日志中(可能由于主从之间断掉
的时间比较长或者在断掉的短暂时间内 master 服务器接收到大量的写操作),则必须进行
一次全量更新。在部分同步过程中,master 会将本地记录的同步备份日志中记录的指令依次
发送给 slave 服务器从而达到数据一致。
Redis 主从同步策略
主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,
slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,
如不成功,要求从机进行全量同步。
27、Redis 持久化 RDB 和 AOF 优缺点
RDBRDB 持久化方式,是将 Redis 某一时刻的数据持久化到磁盘中,是一种快照式的持久化方
法。
RDB 优点:
l RDB 是一个非常紧凑(有压缩)的文件,它保存了某个时间点的数据,非常适用于数据的备
份。
l RDB 作为一个非常紧凑(有压缩)的文件,可以很方便传送到另一个远端数据中心 ,非
常适用于灾难恢复。
l RDB 在保存 RDB 文件时父进程唯一需要做的就是 fork 出一个子进程,接下来的工作全
部由子进程来做,父进程不需要再做其他 IO 操作,所以 RDB 持久化方式可以最大化
redis 的性能。
l 与 AOF 相比,在恢复大的数据集的时候,RDB 方式会更快一些。
RDB 缺点:
l Redis 意外宕机时,会丢失部分数据。
l 当 Redis 数据量比较大时,fork 的过程是非常耗时的,fork 子进程时是会阻塞的,在这
期间 Redis 是不能响应客户端的请求的。
AOF
AOF 方式是将执行过的写指令记录下来,在数据恢复时按照从前到后的顺序再将指令都执行
一遍。
AOF 优点:
l 使用 AOF 会让你的 Redis 更加持久化。
l AOF 文件是一个只进行追加的日志文件,不需要在写入时读取文件。
l Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写 。
l AOF 文件可读性高,分析容易。
AOF 缺点:
l 对于相同的数据来说,AOF 文件大小通常要大于 RDB 文件。
l 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB。
28、谈谈自己对于 Spring AOP 的了解?
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块
所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统
的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
29、 Spring Bean 容器的生命周期是什么样的?
l Bean 容器找到配置文件中 Spring Bean 的定义。
l Bean 容器利用 Java Reflection API 创建一个 Bean 的实例。
l 如果涉及到一些属性值 利用 set()方法设置一些属性值。
31l 如果 Bean 实现了 BeanNameAware 接口,调用 setBeanName()方法,传入 Bean 的
名字。
l 如果 Bean 实现了 BeanClassLoaderAware 接口,调用 setBeanClassLoader()方法,
传入 ClassLoader 对象的实例。
l 如果 Bean 实现了 BeanFactoryAware 接口,调用 setBeanFactory()方法,传入
BeanFactory 对象的实例。
l 与上面的类似,如果实现了其他 *.Aware 接口,就调用相应的方法。
l 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行
postProcessBeforeInitialization() 方法
l 如果 Bean 实现了 InitializingBean 接口,执行 afterPropertiesSet()方法。
l 如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
l 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行
postProcessAfterInitialization() 方法
l 当要销毁 Bean 的时候,如果 Bean 实现了 DisposableBean 接口,执行 destroy() 方
法。
l 当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,
执行指定的方法。
30、RabbitMQ 如何保证消息不丢失 ?
生产者:
方案 1:开启 RabbitMQ 事务(同步,性能差)
方案 2:开启 confirm 模式(异步,性能较好)
MQ:(1)exchange 持久化 (2)queue 持久化 (3)消息持久化
消费者:关闭自动 ACK