Java基础
封装、继承、多态?
封装:封装就是隐藏了细节,在调用Java库中的某个方法时,只需传入正确的参数即可,方法内部进行了什么操作无需关心
继承:是子类继承父类的特征和行为,使得子类对象具有父类的实例与方法,可使子类具有父类相同的行为,通过extends关键字来实现
多态:同一个行为具有多个不同表现形式或形态的能力
- 多态是方法的多态,与属性无关
- 3个必要条件:继承、方法重写、父类引用指向子类对象
- 调用的方法是子类重写后的方法
重写与重载的区别?
重写:
- 发生在父类与子类之间
- 方法名,参数列表,返回值类型必须相同(void),返回类型可以是父类中返回类型的子类
- 子类重写方法的权限修饰符不小于父类被重写方法的权限修饰符(子类不能重写父类中声明为private权限的方法)
- 子类抛出的异常类型不能大于父类的异常类型
重载
- 在同一个类中
- 方法名相同
- 参数列表不同(数量不同,类型不同,顺序不同)
接口和抽象类的区别?
参数 | 抽象类 | 接口 |
---|---|---|
默认方法实现 | 可以有默认方法实现 | 不存在方法实现 |
实现 | 子类使用extends来继承抽象类。如果子类不是抽象类的话,它需要提供所有声明的方法的实现 | 子类使用implements关键字来实现接口。它需要提供接口中所有声明的方法的实现 |
构造器 | 抽象类可以有构造器 | 接口不能有构造器 |
与普通Java类区别 | 除不能实例化抽象类外,其余没有区别 | 接口是完全不同的类型 |
访问修饰符 | 抽象方法可以有public、protected、default修饰符 | 接口方法默认修饰符是public,不可以使用其它修饰符 |
main方法 | 可以有main方法并且可以运行它 | 没有main方法,不可以运行它 |
多继承 | 抽象方法可以继承一个类和实现多个接口 | 接口只可以继承一个或多个其它接口 |
速度 | 比接口速度要快 | 接口速度要慢一些 |
添加新方法 | 往抽象类中添加新的方法,可以给它提供默认的实现。因此不需要改变现有的代码 | 往接口中添加方法,必须改变实现该接口的类 |
equals与==的区别?
- == 既可以比较基本数据类型 也可以比较引用数据类型,对于基本类型就是比较值,对于引用类型就是比较内存中的地址值
- equals用来比较的是两个对象的内容是否相等,前提是已经对equals方法进行了重写,如果没有重写的话还是与 == 的比较作用相同
为什么重写equals方法时一定要重写hashcode方法?
如果只重写equals方法,不重写hashcode时,违反规定:equals相同的对象必须具有相同的哈希码。 类会违反hashCode的通用约定,对于HashSet,HashMap,HashTable等基于hash值的类就会出现问题
HashCode的作用?
就是根据返回对象的内存地址换算出的一个值。当集合位置要添加新的元素时,会先调用该元素的hashCode方法,算出它应该存放的物理位置上。如果该位置上没有元素,就可以直接存储在这个位置上,不用进行其它比较,如果该位置上有元素了,就调用它的equals方法与新元素比较,相同的话就不存了,不相同就散列到其它地址上。实际调用equals方法的次数就大大降低了。
String、StringBuffer、StringBuilder的区别?
- String底层使用final修饰char value[],即String对象是不可变的,但StringBuffer与StringBuilder底层没有使用final修饰,所以这两种对象都是可变的
- String线程安全,StringBuffer对方法或调用的方法加了同步锁,即线程安全,但是效率比较低
- StringBuilder没有对方法加同步锁,是非线程安全的,效率比较高
final关键字的用法?
- final修饰的变量,如果是基本数据类型,数值在初始化之后便不能更改。如果是引用类型的变量,初始化之后便不能再让其指向另外一个对象
- final修饰类时,表示该类不可以被继承
- final修饰方法时,表示该方法不可以被重写
Java集合
ArrayList与LinkedList的区别?
- ArrayList底层是数组的数据结构,且地址连续,有序数据可重复,查询操作效率会比较高,支持快速随机访问,插入和删除操作效率比较低,默认将元素添加到数组的末尾
- LinkedList底层是链表的数据结构,地址是任意的,开辟内存空间时不需要连续的地址,添加和删除操作时只需要改表元素之间节点的指向即可
总结:
ArrayList 查找效率高,增删效率较低 底层数据结构为数组
LinkedList 增删效率较高,查找效率偏低 底层数据结构为双向链表
线程安全的list有哪些?
- Collections.synchronizedList构建安全的list
- 使用synchronized为list.add()方法加锁
- 使用CopyOnWriteArrayList
- 使用ThreadLocal
ArrayList中add()方法实现过程?
- 调用add(E e)方法
- 在第一次添加时,给当前集合指定默认初始容量大小10
- 当元素个数与当前初始容量大小(10)之差大于0时,则进行扩容
- 每次扩容为当前元素的1.5倍(内部进行了右移一位操作)
ArrayList中get()方法实现过程?
- 首先判断当前索引值 是否大于等于 元素数量,如果大于则直接抛异常
- 否则直接根据索引下标取值并返回
ArralList的扩容机制?
扩容长度是原数组的1.5倍
HashMap底层数据结构?
JDK1.7 及之前:数组+链表
JDK1.8:数组+链表+红黑树
HashSet与TreeSet的区别?
- HashSet底层是哈希表实现的。TreeSet底层是二叉树实现的
- 向HashSet添加元素时,实际上是把该元素作为键添加到HashMap中,存储的元素是 无序不可重复的。
- 向TreeSet添加元素时,实际上是把该元素作为键添加到TreeMap中,存储的元素是 有序不可重复的。
HashMap的工作原理?
HashMap基于hashing原理,通过put()和get()方法储存和获取对象。把键值对传递给put()方法时,会调用键对象的hashCode()方法来计算hashcode,然后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。
HashMap中put()方法实现过程?
- 把k,v键值对封装到Node节点中
- 底层会调用k的hashcode方法来得出hash值
- 通过hash算法将hash值转换为数组的下标, 下标的位置上如果没有任何元素, 就会把Node 添加到这个位置上 。 如果下标对应的位置上存在链表的话, 此时就会拿着k和链表上每个节点的k进行equals比较, 如果equals方法比较返回是false, 那么新的节点将会添加到链表的末尾。如果其中有一个equals返回了true, 那么这个节点的value将会被覆盖
HashMap中get()方法实现过程?
- 先调用k的hashcode方法算出hash值,然后通过哈希算法算出数组的下标
- 获取到数组的下标之后,再通过下标快速定位到某个位置上 如果位置上什么都没有, 则返回null
- 如果这个位置上有单向链表, 此时会拿着k和单向链表上的每一个节点的k进行equals, 如果所有的equals方法都返回false的话, get方法则返回null
- 如果其中一个节点的k和参数k进行equals返回true, 那么此时直接返回该节点的value
HashMap的扩容机制?
原table中的元素个数达到了capacity * loadFactor这个上限后, 就需要扩容。 此时调用 resize(), 会new一个两倍长度的新Node数组, 并将容器指针(table) 指向新数组 ,并返回,每次扩容长度为原长度的两倍
HashMap对象的key、value都可以为null吗?
可以
HashTable与HashMap的区别?
- HashTable是线程安全的(Synchronized修饰),HashMap不是线程安全的
- Hashtable 的key不能为 null,value也不能为 null,HashMap 的key和value都可以为 null
HashMap中的key可以使用任何类型作为key吗?
- 如果类重写了 equals 方法,它也应该重写 hashCode 方法
- 类的所有实例需要遵循与 equals 和 hashCode 相关的规则
ConcurrentHashMap 的key,value是否可以为null?
不行 如果key或者value为null会抛出空指针异常
ConcurrentHashMap是如何保证线程安全的?
JDK1.7 使用分段锁对整个桶数组进行了分割分段,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率
JDK1.8 使用node数组,链表,红黑树组成,使用CAS + Synchronized来保证并发安全
红黑树的特征?
- 每个节点是黑色或者红色
- 根节点是黑色
- 每个叶子节点都是黑色(指向空的叶子节点)
- 如果一个叶子节点是红色,那么其子节点必须都是黑色的
- 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点
Java多线程与并发
Java中开启一个线程有哪几种方式?
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
- 线程池方法创建
线程的生命周期(状态)?
- 新建状态:当程序使用 new 关键字创建了一个线程之后,该线程就处于新建状态,此时仅由 JVM 为其分配内存,并初始化其成员变量的值
- 就绪状态:当线程对象调用了 start()方法之后,该线程处于就绪状态。Java 虚拟机会为其创建方法调用栈和程序计数器,等待调度运行
- 运行状态:如果处于就绪状态的线程获得了 CPU,开始执行 run()方法的线程执行体,则该线程处于运行状态
- 阻塞状态:阻塞状态是指线程因为某种原因放弃了cpu使用权,也即让出了cpu时间片,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得 cpu 时间片转到运行(running)状态
- 等待阻塞
- 同步阻塞
- 其他阻塞
- 线程死亡:线程会以下面三种方式结束,结束后就是死亡状态
- 正常结束
- 异常结束
- 调用stop结束
sleep与wait的区别?
- sleep()方法是属于Thread类的,wait()方法是属于Object类的
- sleep()方法可以使程序暂停执行指定的时间,让出cpu给其它线程,但监控状态依然保持着,时间到了会自动恢复运行状态
- 调用sleep()方法过程中,线程不会释放锁。调用wait()方法时,线程会释放锁,使用notify()方法唤醒被wait()方法停止的线程
什么是死锁?
多个进程或线程互相等待对方的资源,在得到新的资源之前不会释放自己的资源,这样就形成了循环等待,这种现象被称为死锁。
产生死锁的四个必要条件?
- 互斥条件:一个资源每次只能被一个线程使用
- 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:进程已经获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系
线程与进程的区别?
进程:是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竞争计算机系统资源的基本单位
线程:是进程的一个执行单元,是进程内部调度实体。比进程更小的独立运行的基本单位
并发与并行的区别?
- 并发,指的是多个事情,在同一时间段内同时发生了
- 并行,指的是多个事情,在同一时间点上同时发生了
- 并发的多个任务之间是互相抢占资源的
- 并行的多个任务之间是不互相抢占资源的
只有在多CPU的情况中,才会发生并行。否则,看似同时发生的事情,其实都是并发执行的
线程池有哪几种?
newSingleThreadExecutor 单线程的线程池
newFixedThreadPool 固定大小的线程池
newCachedThreadPool 可缓存的线程池
newScheduledThreadPool 大小无限的线程池
线程池的原理以及核心参数?
核心参数:
- 最大线程数maximumPoolSize
- 核心线程数corePoolSize
- 活跃时间keepAliveTime
- 阻塞队列workQueue
- 拒绝策略RejectedExecutionHandler
新任务到线程池时,具体执行流程如下:
- 当我们提交任务,线程池会根据corePoolSize(核心线程数)大小创建若干任务数量的线程去执行任务
- 当任务的数量超过corePoolSize数量,后续的任务将会进入阻塞队列阻塞排队
- 当阻塞队列也满了之后,将会继续创建(maximumPoolSize-corePoolSize)个数量的线程来执行任务,如果任务处理完成,maximumPoolSize-corePoolSize额外创建的线程等待keepAliveTime之后被自动销毁
- 如果达到maximumPoolSize,阻塞队列还是满的状态,那么将根据不同的拒绝策略对应处理
线程池的拒绝策略有哪些?
-
AbortPolicy:直接丢弃任务,抛出异常,这是默认策略
-
CallerRunsPolicy:只用调用者所在的线程来处理任务
-
DiscardOldestPolicy:丢弃等待队列中最旧的任务,并执行当前任务
-
DiscardPolicy:直接丢弃任务,也不抛出异常
Java中有哪些锁?锁的概念?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rbzb96Aq-1668005885931)(java%E9%9D%A2%E8%AF%95.assets/image-20221107154746642.png)]
定义:锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源
- **乐观锁:**乐观锁操作数据时不会上锁,在更新的时候会判断一下在此期间是否有其他线程去更新这个数据。
- **悲观锁:**一个共享数据加了悲观锁,那线程每次想操作这个数据前都会假设其他线程也可能会操作这个数据,所以每次操作前都会上锁,这样其他线程想操作这个数据拿不到锁只能阻塞了。
- **独占锁:**是指锁一次只能被一个线程所持有,获得独占锁的线程即能读数据又能修改数据。
- **共享锁:**共享锁是指锁可被多个线程所持有,获得共享锁的线程只能读数据,不能修改数据。
- **互斥锁:**是独占锁的一种常规实现,是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。
- **读写锁:**是共享锁的一种具体实现。读写锁管理一组锁,一个是只读的锁,一个是写锁。
- **公平锁:**是指多个线程按照申请锁的顺序来获取锁,这里类似排队买票,先来的人先买,后来的人在队尾排着,这是公平的。
- **非公平锁:**是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发环境下,有可能造成优先级翻转。
- **可重入锁(递归锁):**是指同一个线程在外层方法获取了锁,在进入内层方法会自动获取锁。 可重入锁的一个好处是可一定程度避免死锁
- **自旋锁:**是指线程在没有获得锁时不是被直接挂起,而是执行一个忙循环,这个忙循环就是所谓的自旋。
Synchronized的锁升级过程?
无锁–> 偏向锁–> 轻量级锁–> 重量级锁
Synchronized与ReentrantLock的区别?
共同点:
- 都是用来协调多线程对共享对象、变量的访问
- 都是可重入锁,同一线程可以多次获得同一个锁
- 都保证了可见性和互斥性
不同点:
- synchronized 可用来修饰普通方法、静态方法和代码块,而 ReentrantLock 只能用在代码块上
- synchronized 会自动加锁和释放锁,但ReentrantLock需要手动加锁和释放锁
- synchronized 属于非公平锁,而 ReentrantLock 既可以是公平锁也可以是非公平锁,默认为非公平锁 也是可重入锁
CAS的原理?
CAS叫做CompareAndSwap 无锁算法,比较并交换,主要是通过处理器的指令来保证操作的原子性,它包含三个操作数:
-
变量内存地址,V表示
-
旧的预期值,A表示
-
准备设置的新值,B表示
当执行CAS指令时,只有当V等于A时,才会用B去更新V的值,否则就不会执行更新操作
CAS的缺点?
- ABA问题:假如Thread1和Thread2先后从内存中拿到了值A,这时,Thread1把内存中的值A改成了值B,突然来了Thread3,它拿到内存中的B值后,然后又将内存中的值B改成了A,这个时候使用CAS进行检查时会发现它的值是没有发生变化的,但是引用却发生了变化
解决方案:JDK中提供AtomicStampedReference来解决这个问题,加入了预期引用和预期标志,首先会检查当前引用与预期引用,当前标志与预期标志是否相等,全部相等的话才用原子方式去更新值,并解决CAS的ABA问题
- 循环时间长开销大:自旋CAS的方式如果长时间不成功,会给CPU带来很大的开销。
volatile的作用?
- 可以保证线程间的可见性(本质是使用CPU的缓存一致性协议 MESI)
- 禁止指令重排序,来保证**有序性 ** 通过内存屏障 LoadLoad LoadStore StoreLoad StoreStore
MySQL
数据库的三范式是什么?
- 第一范式:列不可再分
- 第二范式:行可以唯一区分,主键约束
- 第三范式:表的非主属性不能依赖与其它表的非主属性,外键约束
MySQL有哪些存储引擎?
MyISAM、InnoDB、Memory、MERGE
InnoDB与MyISAM的区别?
MyISAM | InnoDB | |
---|---|---|
索引类型 | 非聚簇索引 | 聚簇索引 |
支持事务 | 否 | 是 |
支持表锁 | 是 | 是 |
支持行锁 | 否 | 是 |
支持外键 | 否 | 是 |
支持全文索引 | 是 | 是(5.6后支持) |
适合操作类型 | 大量select | 大量insert、delete、update |
数据库的事务以及事务的特性是什么?
什么是事务?
简单来说,多条sql语句,要么全部成功,要么全部失败
事务的特性?
- **原子性:**事务中的所有操作可以看做一个原子,事务是应用中不可再分的最小的逻辑执行体 使用事务对数据进行修改的操作序列,要么全部执行,要么全不执行。
- **一致性:**一致性是指事务执行的结果必须使数据库从一个一致性状态,变到另一个一致性状态。当数据库中只包含事务成功提交的结果时,数据库处于一致性状态。一致性是通过原子性来保证的。
- **隔离性:**隔离性是指各个事务的执行互不干扰,任意一个事务的内部操作对其他并发的事务,都是隔离的。
- **持久性:**持久性指事务一旦提交,对数据所做的任何改变,都要记录到永久存储器中,通常是保存进物理数据库,即使数据库出现故障,提交的数据也应该能够恢复。
事务的隔离级别有哪些?MySQL默认隔离级别是什么?
- READ UNCOMMITTED(读未提交)
- READ COMMITTED(读已提交)
- REPEATABLE READ(可重复读) (MySQL默认隔离级别)
- SERIALIZABLE(序列化)
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ UNCOMMITTED(读未提交) | ✔ | ✔ | ✔ |
READ COMMITTED(读已提交) | ❌ | ✔ | ✔ |
REPEATABLE READ(可重复读) | ❌ | ❌ | ✔ |
SERIALIZABLE(序列化) | ❌ | ❌ | ❌ |
事务的并发问题?
- 脏读(Dirty read):当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据
- **不可重复读(Unrepeatableread):**指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样
- **幻读(Phantom read):**幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读
MySQL索引类型有哪些?
- 主键索引: 唯一且非空
- 唯一索引: 唯一,值可以为空
- 普通索引(覆盖索引): 值可以为空,没有唯一限制
- 全文索引(MyISAM支持,InnoDB在5.6后支持): 全文索引的索引类型为FULLTEXT,全文索引可以在varchar、char、text类型上的列创建
- 组合索引: 多列值组成一个索引,专门用于组合搜索(最左匹配原则)
索引在什么时候会失效?
- 不要在where字句中对字段进行null值判断
- 不要在where字句中使用 != 或 <>操作符
- 不要在where字句中对字段进行表达式或函数操作
- 最左前缀法原则:在使用索引字段作为条件时,该索引是复合索引的话,必须使用到该索引的第一个字段作为条件时才能保证系统使用该索引
- 使用like关键字时,%放在前面索引会失效,%不在第一个位置索引才会生效
- 不要在where字句中使用 or 作为连接条件,尽量使用union 或 union all代替**(没有重复数据时,用union all更好)**
- 不要在索引列上做任何操作,否则索引失效,全表扫描
- 减少select *,否则会导致索引失效
SQL优化手段有哪些?
1、查询语句中不要使用select *
2、尽量减少子查询,使用关联查询(left join,right join,inner join)替代
3、减少使用IN或者NOT IN ,使用exists,not exists或者关联查询语句替代
4、or 的查询尽量用 union或者union all 代替(在确认没有重复数据或者不用剔除重复数据时,union all会更好)
5、应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
6、应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如: select id from t where num is null 可以在num上设置默认值0,确保表中num列没有null值,然后这样查询: select id from t where num=0
SQL语句的执行顺序?
from -> on -> join -> where -> group by -> having -> select -> distinct -> order by -> limit
什么是MVCC?
多版本并发控制(MVCC=Multi-Version Concurrency Control),是一种用来解决读 - 写冲突的无锁并发控制。也就是为事务分配单向增长的时间戳,为每个修改保存一个版本。版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照(复制了一份数据)
MVCC 可以为数据库解决什么问题?
在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能。同时还可以解决脏读、幻读、不可重复读等事务隔离问题,但不能解决更新丢失问题
MVCC的实现原理是什么?
MVCC 的目的就是多版本并发控制,在数据库中的实现,就是为了解决读写冲突,它的实现原理主要是依赖记录中的 3 个隐式字段、undo 日志、Read View 来实现的 RedoLog 保证持久性 undolog保证原子性
MySQL数据库有哪些锁?
-
共享锁:不堵塞,多个用户可以同一时刻读取同一个资源,相互之间没有影响。
-
排它锁:一个写操作阻塞其他的读锁和写锁,这样可以只允许一个用户进行写入,防止其他用户读取正在写入的资源。
-
表锁:系统开销最小,会锁定整张表,MyISAM 使用表锁。
-
行锁:容易出现死锁,发生冲突概率低,并发高,InnoDB 支持行锁(必须有索引才能实现,否则会自动锁全表,那么就不是行锁了)。
MySQL的死锁?
多个进程或线程互相等待对方的资源,在得到新的资源之前不会释放自己的资源,这样就形成了循环等待,这种现象被称为死锁。
MySQL如何避免死锁?
-
设置获取锁的超时时间,至少能保证最差情况下,可以退出程序,不至于一直等待导致死锁;
-
设置按照同一顺序访问资源,类似于串行执行;
-
避免事务中的用户交叉;
-
保持事务简短并在一个批处理中;
-
使用低隔离级别;
-
使用绑定链接。
谈谈MySQL的表锁和行锁与页锁?
表级锁:每次操作锁住整张表。开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低
行级锁:每次操作锁住一行数据。开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高;
页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
MySQL中悲观锁与乐观锁的区别?
悲观锁:说的是数据库被外界(包括本系统当前的其他事物以及来自外部系统的事务处理)修改保持着保守态度,因此在整个数据修改过程中,将数据处于锁状态
乐观锁:乐观锁,大多是基于数据版本(Version)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个“version”字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据
Spring
讲一下什么是Spring?
Spring是一个轻量级的IoC和AOP容器框架。是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求
为什么使用Spring?
- 轻量:基本的版本大约2MB
- 控制反转:Spring通过控制反转IOC的技术实现了低耦合
- 面向切面的编程(AOP):Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开
- 方便程序的测试,spring 对 junit4 支持,可以通过注解方便的测试 spring 程序
- 方便集成各种优秀的框架
- 声明式事务的支持(通过配置就完成对事务的支持,不需要手动编程)
依赖注入的方式有几种?
- 构造器注入
- setter方法注入
- 接口注入(不常用)
- 注解注入
Spring IOC的理解?
IOC就是控制反转,是指创建对象的控制权的转移。以前创建对象的主动权和时机是由自己把控的,而现在这种权力转移到Spring容器中,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系。对象与对象之间松散耦合,也利于功能的复用
最直观的表达就是,IOC让对象的创建不用去new了,可以由spring自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的
Spring AOP的理解?
AOP(面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任**(例如事务处理、日志管理、权限控制等)**封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可扩展性和可维护性
Spring AOP是基于动态代理的,如果要代理的对象实现了某个接口,那么Spring AOP就会使用JDK动态代理去创建代理对象;而对于没有实现接口的对象,就无法使用JDK动态代理,转而使用CGlib动态代理生成一个被代理对象的子类来作为代理 切点表达式
五种通知类型?
- before:前置通知,在一个方法执行前被调用。
- after: 在方法执行之后调用的通知,无论方法执行是否成功。
- after-returning: 仅当方法成功完成后执行的通知。
- after-throwing: 在方法抛出异常退出时执行的通知。
- around: 在方法执行之前和之后调用的通知。
Bean生命周期?
- 通过构造器创建bean实例 执行无参构造器
- 为bean属性赋值 执行set方法
- 调用BeanPostProcessor的预初始化方法
- 初始化Bean
- 调用BeanPostProcessor的初始化后方法
- 获取Bean
- 容器关闭销毁Bean
Bean的作用域?
- singleton:单例模式,Spring IoC 容器中只会存在一个共享的 Bean 实例,无论有多少个Bean 引用它,始终指向同一对象。
- prototype:原型模式,每次通过 Spring 容器获取 prototype 定义的 bean 时,容器都将创建一个新的 Bean 实例,每个 Bean 实例都有自己的属性和状态,而 singleton 全局只有一个对象。
- request:在一次 Http 请求中,容器会返回该 Bean 的同一实例。而对不同的 Http 请求则会产生新的 Bean,而且该 bean 仅在当前 Http Request 内有效。
- session:在一次 Http Session 中,容器会返回该 Bean 的同一实例。而对不同的 Session 请求则会创建新的实例,该 bean 实例仅在当前 Session 内有效。
- global Session:在一个全局的 Http Session 中,容器会返回该 Bean 的同一个实例。
Bean是线程安全的吗?
不是线程安全的,容器本身并没有提供Bean的线程安全策略,Bean本身不具备线程安全的特性,但是具体还是要结合具体scope的Bean去研究
原型Bean**(prototype)**:每次创建一个新对象,也就是线程之间并不存在Bean共享,自然是不会有线程安全的问题。
单例Bean:不是线程安全的
Spring中常用注解?
@Component @Repository @Service @Controller @Autowired @Qualifier @Configuration @Bean
BeanFactory与ApplicationContext的区别?
- BeanFactory是Spring中最底层的接口。 面向Spring本身
- ApplicationContext接口是BeanFactory的子接口,具有BeanFactory所有的功能,同时继承了MessageSource,所以提供了更完整的框架功能,支持国际化、资源文件访问、载入多个上下文配置文件 面向框架开发者
- BeanFactory是延时加载,在需要使用bean的时候,才会对该bean进行加载实例化 BeanFactory速度比较快
- ApplicationContext 是在容器启动的时候,一次性创建所有的bean
- BeanFactory是需要手动注册的。
- ApplicationContext是自动注册的
什么是循环依赖?
循环依赖指的是两个类中的属性相互依赖对方:例如 A 类中有 B 属性,B 类中有 A属性,从而形成了一个依赖闭环
如何解决循环依赖?
Spring 通过提前曝光机制,利用三级缓存解决循环依赖
为什么要用三级缓存?二级缓存不能解决循环依赖吗?
二级缓存配合也可以解决循环依赖,但是三级缓存的使用,是为了完成Spring AOP中的后置处理功能而提出的
一级缓存:singletonObjects
二级缓存:earlySingletonObjects
三级缓存:singletonFactories
Autowired与Resource的区别?
- @Autowired注解由Spring提供,按照byType注入
- @Resource注解由J2EE提供,默认按照byName注入
Spring中用到哪些设计模式?
- 代理模式
- 单例模式:在 spring 配置文件中定义的 bean 默认为单例模式
- 模板方法模式:Spring 中 的 jdbcTemplate 、 hibernateTemplate 等 以Template 结尾的对数据库操作的类,这些就使用到了模板模式用来解决代码重复的问题
- 简单/静态工厂模式:spring 中的 BeanFactory 就是简单工厂模式的体现,BeanFactory 用来创建对象的实例
- 工厂模式
- 观察者模式
- 前端控制器模式:Spring 提供了 DispatcherServlet 来对请求进行分发
SpringMVC
什么是SpringMVC?
- SpringMVC是一种基于 Java 的实现MVC设计模型的请求驱动类型的轻量级Web框架,属于Spring框架的一个模块。
- 它通过一套注解,让一个简单的Java类成为处理请求的控制器,而无须实现任何接口。同时它还支持RESTful编程风格的请求
什么是MVC模式?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ud4k1o5b-1668005885933)(java%E9%9D%A2%E8%AF%95.assets/image-20221101134526566.png)]
分析:
M-Model 模型(完成业务逻辑:有javaBean构成service+dao+entity)
V-View 视图(做界面的展示 jsp,html……)
C-Controller 控制器(接收请求—>调用模型—>根据结果派发页面)
SpringMVC的执行流程?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M0AHAyM6-1668005885933)(java%E9%9D%A2%E8%AF%95.assets/image-20221101134741572.png)]
- 用户发送请求至前端控制器DispatcherServlet。
- DispatcherServlet收到请求调用HandlerMapping处理器映射器。
- 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
- DispatcherServlet调用HandlerAdapter处理器适配器。
- HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
- Controller执行完成返回ModelAndView。
- HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
- DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
- ViewReslover解析后返回具体View。
- DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
- DispatcherServlet响应用户
Spring MVC的主要组件?
- 前端控制器
- 处理器映射器
- 处理器适配器
- 处理器
- 视图解析器
- 视图
SpringMVC如何设定重定向和转发?
请求转发与重定向的区别?
- 请求转发在服务器端完成的;重定向是在客户端完成的。
- 请求转发的速度快;重定向速度慢。
- 请求转发的是同一次请求;重定向是两次不同请求。
- 请求转发不会执行转发后的代码;重定向会执行重定向之后的代码。
- 请求转发地址栏没有变化;重定向地址栏有变化。
- 请求转发必须是在同一台服务器下完成;重定向可以在不同的服务器下完成
SpringMVC设定请求转发 在返回值前面加"forward:"
SpringMVC设定重定向 在返回值前面加"redirect:"
SpringMVC常用的注解有哪些?
- @RequestMapping
- @RequestBody
- @ResponseBody
- @PathVariable
- @RestController
- @RequestParam
MyBatis
什么是MyBatis?
Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程
#{}与${}的区别是什么?
- #{}是预编译处理,${}是字符串替换。
- Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;
- Mybatis在处理 时,就是把 {}时,就是把 时,就是把{}替换成变量的值。
- 使用#{}可以有效的防止SQL注入,提高系统安全性。
实体类中的属性名和表中的字段名不一样 ,怎么办 ?
- 在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致
- 通过来映射字段名和实体类属性名的一一对应的关系
用id属性来映射主键字段
用result属性来映射非主键字段,property为实体类属性名,column为数据表中的属性
Mapper接口里,方法可以重载吗?
Mapper接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻找策略
Mybatis是如何进行分页的?
Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页
MyBatis如何批量插入?
循环插入,foreach标签插入,批处理插入
MyBatis的动态标签?
- if:根据条件判断
- where:若子句的开头为“AND”或“OR”,where元素也会将它们去除
- choose when:when条件成立 后面的 when就不再判断了 类似于 case when
- set:功能类似于 sql语句中的set关键字 会忽略第一个 ,号
- trim:prefix 要动态添加的前缀,prefixOverrides 要干掉的前缀,suffix 要补充的后缀,suffixOverrides 要干掉的后缀
- bind:处理模糊查询的模板
- sql:定义公共语句片段,以供标签直接引用
- foreach:collection 遍历的集合或者是数组 list 或 array,separator 多个元素取出的时候,用什么文字分割,open 遍历的元素以什么开头,close 遍历的元素以什么结尾,item 中间变量名(随便取) 与#{}中名称一致
MyBatis中如何获取自动生成的主键?
自增长策略 属性useGenerateKeys=“true”,执行完insert方法后,会将插入到数据库的id自动设置到user对象中。如果不加这个,那么执行完insert方法后,取到的id为null
Mybatis的一对一、一对多查询?
在resultMap标签里配置association节点实现一对一,配置collection节点实现一对多
Mybatis是否支持延迟加载?
Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询
可以配置是否启用延迟加载lazyLoadingEnabled=true|false
MyBatis的一级与二级缓存?
一级缓存是SqlSession上的缓存,默认开启,是内存型缓存,不要求实体类实现Serializable接口
中间发生了增删改或者是调用了SqlSession调用了commit,会自动清空缓存
二级缓存是以namespace为标记的缓存,是由一个SqlSessionFactory创建的SqlSession之间共享缓存数据。默认不开启,实体类必须实现序列化接口
配置开启二级缓存 cacheEnabled 默认设置为true
MyBatis使用的设计模式?
- Builder模式
- 工厂模式
- 单例模式
- 代理模式
- 组合模式
- 模板方法模式
- 适配器模式
- 装饰者模式
- 迭代器模式
SpringBoot
为什么要用SpringBoot?
- 快速构建项目
- 对主流开发框架的无配置集成
- 项目可独立运行,无须外部依赖Servlet容器(Tomcat)
- 极大的提高了开发、部署效率
SpringBoot的核心注解是哪几个?由哪些注解组成?
启动类上面的注解是@SpringBootApplication
- @SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能
- @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项
- @ComponentScan:Spring组件扫描
运行SpringBoot有哪几种方式?
- 打成jar包 使用java -jar运行
- 用Maven插件运行
- 直接执行main方法
如何理解Spring Boot中的Starters?
Starters可以理解为启动器,它包含了一系列可以集成到应用里面的依赖包,你可以一站式集成Spring 及其他技术,而不需要到处找示例代码和依赖包
SpringBoot的自动装配原理是什么?
通过@EnableAutoConfiguration里面的@Import的属性值找到AutoConfigurationImportSelector类中的getCandidateConfigurations()方法中的loadFactoryNames,再在此方法中调用loadSpringFactories方法找到 springboot-autoConfiguration中的META-INF/spring.factories里面的xxxAutoConfiguration配置类,此文件中全是xxxAutoConfiguration的文件路径,利用springboot反射这些类
@EnableConfigurationProperties(xxxProperties.class)获得配置属性需要的值,最后进入这个类通过@ConfigurationProperties(prefix = “xxx”)来把配置文件中的配置导入进来。
SpringBoot配置文件加载顺序?
bootstrap.yml -> bootstrap.properties -> application.yml -> application.properties
SpringBoot如何实现热部署?
使用devtools 开启热部署
SpringCloud
SpringCloud的优缺点?
优点
- 服务拆分粒度更细,有利于资源重复利用,有利于提高开发效率
- 微服务架构采用去中心化思想,服务之间采用Restful等轻量级通讯,比ESB更轻量
- 减轻团队的成本,可以并行开发,不用关注其他人怎么开发,先关注自己的开发
缺点:
- 微服务过多,治理成本高,不利于维护系统
- 性能的监控比较麻烦
- 针对数据的管理比麻烦,因为每个微服务可以使用一个数据库
- 系统集成测试比较麻烦
SpringCloud的组件?
Eureka:首先有两个角色,一个服务端和客户端,服务端就是Eureka本身,客户端就是服务提供者和消费者,当服务提供者启动会将自己的信息注册到Eureka去,消费者启动会去注册中心拉取服务列表缓存到本地,消费者就可以远程调用服务提供者
客户端会与注册中心保持心跳来证明自己存活,每隔30s客户端会发送心跳给注册中心,默认情况下,每隔90s注册中心会检查是否有收到心跳,如果没有收到心跳会将客户端从服务列表剔除。
但是由于服务之间的调用常常会受网络延迟的影响导致心跳没有及时的收到,从而误将客户端剔除,所以Eureka会有自我保护机制来应对这种情况
**自我保护机制:**eureka虽然收不到实例的心跳,但它认为实例还是健康的,eureka会保护这些实例,不会把它们从注册表中删掉
面试题:
Eureka与Nacos的区别?
Nacos既支持AP也支持CP,默认使用AP和Eureka一样 CAP理论:C(一致性)、A(可用性)、P(分区容错性)
Ribbon:是一个Http和Tcp的客户端负载均衡工具,它可以在客户端配置服务端列表,,它使用RestTemplate模拟客户端请求,过程相对繁琐
面试题:
-
负载均衡算法有哪些?
随机、轮询、响应时间权重、重试等,还可以实现IRule接口,自定义负载均衡算法
-
Ribbon和Feign的区别?
- 启动类上加的注解不同,Ribbon用的是@RibbonClients;Feign用的是@EnableFeignClients
- 服务的指定位置不同,Ribbon是在@RibbonClient上指定服务名称;Feign是在接口上的@FeignClient上指定
- 调用方式不同,Ribbon需要自己构建http请求,模拟http请求,然后使用RestTempate进行调用;Feign采用接口的方式调用
HyStrix:Hystix是分布式系统的一个延迟和容错的开源库,它可以进行熔断、降级、限流、监控,可以避免分布式系统的级联故障和雪崩效应
面试题:
-
什么是服务雪崩?
服务雪崩就是服务A调用服务B,服务B调用服务C,服务C挂掉了,导致服务B、C超时受影响,导致服务A也超时,对服务造成级联的影响。即下游服务挂掉或者超时,导致上游调用服务大面积受到影响,阻塞、超时,进而导致雪崩效应
-
Hystix的限流两种方式?
线程池和信号量,信号量没有timeout机制。
-
限流算法有几种?
计数器、滑动窗口计数器、漏桶法、令牌桶
Zuul:作为微服务的网关,对微服务的访问进行控制,它可以进行路由、过滤、鉴权、代理请求
面试题:
Zuul和gateway的区别?
- Zuul1.0是阻塞式的api,不支持长连接,而gateway支持异步
- Zuul没有提供限流、负载均衡等支持,而gateway支持
- 它们都是web网关,处理http请求,底层都是servlet
Config:是微服务中的配置中心,对微服务中多个自服务的配置进行统一的管理,可以对配置的读取、加密、解密等操作
面试题:
Config与Nacos的区别?
- Config大部分集合git使用,配置动态变更需要依赖SpringCloudBus消息总线来通知所有Client变化;并且没有可视化界面
- Nacos采用长连接,一旦配置变更,会迅速通知Client进行变更,速度较快;提供可视化界面
Redis
为什么要用缓存(Redis)?
使用缓存的目的就是提升读写性能。而实际业务场景下,更多的是为了提升读性能,带来更好的性能,带来更高的并发量
什么是Redis?有什么特点?
redis是一个开源的、支持数据持久化和备份的key-value数据库
- Redis的数据是存在内存中的,读写速度非常快
- redis支持数据持久化,可以将内存中的数据存储到磁盘中,重启redis的时候可以再次加载进行使用
- redis不仅支持key-value数据结构,还提供set、list、hash等数据结构的存储
- redis支持数据备份,即master-slave模式的数据备份
Redis有哪些数据类型?
String、Set、List、Hash、Zset、Geospatial、Hyperloglog、Bitmap
为什么 Redis 单线程模型效率也能那么高?
- 纯内存操作
- 非阻塞IO,多路IO复用模型
- 采用单线程,避免了不必要的上下文切换和竞争条件,不存在多线程导致的CPU 切换
- 数据结构简单,对数据操作也简单
Redis持久化方式有哪些?
Redis默认持久化方式是RDB
**RDB:**RDB持久化文件,速度比较快,而且存储的是一个二进制文件,传输起来很方便。RDB无法保证数据的绝对安全,有时候就是1s也会有很大的数据丢失
**AOF:**AOF相对RDB更加安全,一般不会有数据的丢失或者很少。AOF持久化的速度,相对于RDB较慢,存储的是一个文本文件,到了后期文件会比较大,传输困难
Redis如何实现消息队列?
一般使用 list 结构作为队列, rpush 生产消息, lpop 消费消息。当 lpop 没有消息的时候,要适当sleep 一会再重试
缓存穿透、缓存雪崩、缓存击穿?
**缓存穿透:**指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,进而给数据库带来压力
如何避免缓存穿透:
- 如果是非法请求,我们在API入口,对参数进行校验,过滤非法值。
- 使用布隆过滤器快速判断数据是否存在。即一个查询请求过来时,先通过布隆过滤器判断值是否存在,存在才继续往下查
缓存雪崩: 指缓存中数据大批量到过期时间,而查询数据量巨大,请求都直接访问数据库,引起数据库压力过大甚至down机。
如何避免缓存雪崩:
可通过均匀设置过期时间解决,即让过期时间相对离散一点。如采用一个较大固定值+一个较小的随机值(5小时+0到1800秒这样子)
缓存击穿: 指热点key在某个时间点过期的时候,而恰好在这个时间点对这个Key有大量的并发请求过来,从而大量的请求打到db
如何避免缓存击穿:
设置热点数据永远不过期(可以判断当前key快要过期时,通过后台异步线程在重新构建缓存)
redis的过期策略以及内存淘汰机制?
redis采用的是定期删除+惰性删除
定期删除
Redis设定每隔100ms随机抽取设置了过期时间的key,并对其进行检查,如果已经过期则删除。为什么是随机抽取? 因为如果存储了大量数据,全部遍历一遍是非常影响性能的!
惰性删除
每次获取key时会对key进行判断是否还存活,如果已经过期了则删除
Redis内存淘汰机制
- volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
- volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰
- volatile-random:从已设置过期时间的数据集中任意选择数据淘汰
- allkeys-lru:从数据集中挑选最近最少使用的数据淘汰
- allkeys-random:从数据集中任意选择数据淘汰
- no-eviction :当内存不足写入新数据时,写入操作会报错,同时不删除数据
- **volatile-lfu:**从已设置过期时间的数据集中挑选最不经常使用的数据淘汰
- **allkeys-lfu:**当内存不足写入新数据时移除最不经常使用的Key
Redis有哪些集群模式?
- **主从模式:**主数据库可以执行读写功能,而从数据库只能执行读功能。主数据库数据发生变化,会自动同步到从数据库。一个master可以有多个slave,一个slave只能有一个master
- **哨兵模式:**哨兵模式是建立在主从模式的,解决主从master挂掉后不能提供写功能。当master挂掉之后,会自动从slave中选一个作为master。因为哨兵也是一个进程,所以也有挂掉的可能,需要配置多个哨兵互相监督
- **Cluster模式:**高可用,因为每个master都有salve节点,那么如果mater挂掉,redis cluster这套机制,就会自动将某个slave切换成master
Redis和Mysql如何保证数据一致性?
- 先更新数据库,再更新缓存
- 先删除缓存,再更新数据库
RocketMQ可靠性消息通信:
首先更新数据库中的数据,当更新Redis中的数据失败时,把失败的请求写入到MQ事务消息中,然后通过异步重试,确保Redis中消息最终更新成功。
Canal:
首先更新数据库中数据,更新redis中数据失败时,用canal监控Mysql的binlog日志,然后从binlog中加载数据,最后同步到Redis中
以上两种方案,是基于数据的最终一致性实现的
RabbitMQ
为什么要使用MQ?
- **解耦:**使用MQ,A 系统产生一条数据,发送到 MQ 里面去,哪个系统需要数据自己去 MQ 里面消费。如果新系统需要数据,直接从 MQ 里消费即可
- **异步:**没有引入MQ的时候,服务之间都是同步调用,比较耗时,引入MQ之后,全部改成了异步调用,大大提高了接口的性能
- **削峰:**减少高峰时期对服务器的压力
MQ有什么缺点?
-
**系统可用性降低:**系统引入的外部依赖越多,越容易挂掉。万一 MQ 挂了,MQ 一挂,整套系统就会崩溃
-
**系统复杂度提高:**加入MQ之后,如何保证消息不重复消费?怎么处理消息丢失?怎么保证消息传递的顺序性?
-
数据一致性问题: A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是BCD 三个系统那里,BD 两个系统写库成功了,结果 C 系统写库失败了,咋整?你这数据就不一致
了。
MQ如何保证消息不重复消费?
采用第三方存储,来做消费记录,以 redis 为例,给消息分配一个全局 id,只要消费过该消息,将<id,message>以 K-V 形式写入 redis, 那消费者开始消费前,先去 redis 中查询有没有消费记录即可
MQ如何保证消息不丢失?
针对生产者消息丢失:
confirm机制:可以通过Confirm效果保证消息一定送达到Exchange,选择了对于效率影响最低的异步回调的方式
针对MQ消息丢失:
- **消息持久化:**Exchange持久化、Queue持久化、Message持久化(deliveryMode=2),把消息持久化到磁盘中
- 设置镜像集群模式:为什么设置镜像模式集群,因为队列的内容仅仅存在某一个节点上面,不会存在所有节点上面,所有节点仅仅存放消息结构和元数据。缺点:吞吐量会下降
- 消息补偿机制
针对消费者消息丢失:
**ACK确认机制:**使用rabbitmq提供的ack机制,服务端首先关闭rabbitmq的自动ack,然后每次在确保处理完这个消息之后,在代码里手动调用ack。这样就可以避免消息还没有处理完就ack。才把消息从内存删除
MQ如何保证消息传递的顺序性?
就是拆分多个 queue,每个 queue 对应一个 consumer(消费者),就是多一些 queue 而已,确实是麻烦点。
或者就一个 queue 但是对应一个 consumer,然后这个 consumer 内部用内存队列做排队,然后分发给底层不同的 worker 来处理
MQ如何解决消息堆积问题?
- 消费端宕机
- 消费端消费能力不足
- 生产端发送流量过大
方案一:通常的解决方案就是增加消费端实例。说白了就是增加机器
方案二:如果申请机器行不通,毕竟公司的机器是有限的,此时可以增加消费端的消费能力。在MQ的配置中配置"最大消费者数量"与"每次从队列中获取的消息数量"
方案三:先通过一个独立服务把要消费的消息存起来,比如存到数据库,之后再慢慢处理这些消息即可
MQ如何保证高可用?
- 单机模式
- 普通集群模式(不推荐):多台机器上启动多个rabbitMQ实例,但是你创建的queue(元数据+实际数据)只会放在一个rabbitMQ实例 A 上,其他实例都会同步queue的元数据(元数据可以理解为queue的位置、信息,便于其他实例拉取数据的)。如果消费者消费信息的时候,实际连接的是另一个实例 B ,那个B会根据元数据去到A上拉取数据过来 该方案主要是提高吞吐量,让集群中多个节点来服务某个queue的读写操作
- 镜像集群模式(推荐):创建一个queue(元数据+实际数据),无论是queue的元数据还是消息,都存在于多个实例上。每次写消息到queue时,都会自动把消息同步到多个实例的queue里 该方案性能开销大,消息同步所有机器导致网络带宽压力和消耗很重
MQ的死信队列?
变为死信队列的如下情况:
- 消息被消费者拒绝,执行了Basic.Reject或Basic.Nack,并且设置 requeue 参数的值为 false
- 发送消息时设置消息的生存时间,如果时间到了,还没有被消费
- 指定某个队列中所有消息的生存时间,生存时间到了,还没有被消费
- 队列已经到达消息的最大长度后,再路由过来的消息直接变为死信
死信队列的应用:
- 基于死信队列在队列消息已满的情况下,消息也不会丢失
- 实现延迟消费的效果。比如:下订单时,有15分钟的付款时间
RabbitMQ的广播模式?
- fanout:所有bind到此exchange的queue都可以接收到消息
- direct:通过routingKey和exchange决定哪一个queue可以接收到消息
MQ如何解决消息堆积问题?
- 消费端宕机
- 消费端消费能力不足
- 生产端发送流量过大
方案一:通常的解决方案就是增加消费端实例。说白了就是增加机器
方案二:如果申请机器行不通,毕竟公司的机器是有限的,此时可以增加消费端的消费能力。在MQ的配置中配置"最大消费者数量"与"每次从队列中获取的消息数量"
方案三:先通过一个独立服务把要消费的消息存起来,比如存到数据库,之后再慢慢处理这些消息即可
MQ如何保证高可用?
- 单机模式
- 普通集群模式(不推荐):多台机器上启动多个rabbitMQ实例,但是你创建的queue(元数据+实际数据)只会放在一个rabbitMQ实例 A 上,其他实例都会同步queue的元数据(元数据可以理解为queue的位置、信息,便于其他实例拉取数据的)。如果消费者消费信息的时候,实际连接的是另一个实例 B ,那个B会根据元数据去到A上拉取数据过来 该方案主要是提高吞吐量,让集群中多个节点来服务某个queue的读写操作
- 镜像集群模式(推荐):创建一个queue(元数据+实际数据),无论是queue的元数据还是消息,都存在于多个实例上。每次写消息到queue时,都会自动把消息同步到多个实例的queue里 该方案性能开销大,消息同步所有机器导致网络带宽压力和消耗很重
MQ的死信队列?
变为死信队列的如下情况:
- 消息被消费者拒绝,执行了Basic.Reject或Basic.Nack,并且设置 requeue 参数的值为 false
- 发送消息时设置消息的生存时间,如果时间到了,还没有被消费
- 指定某个队列中所有消息的生存时间,生存时间到了,还没有被消费
- 队列已经到达消息的最大长度后,再路由过来的消息直接变为死信
死信队列的应用:
- 基于死信队列在队列消息已满的情况下,消息也不会丢失
- 实现延迟消费的效果。比如:下订单时,有15分钟的付款时间
RabbitMQ的广播模式?
- fanout:所有bind到此exchange的queue都可以接收到消息
- direct:通过routingKey和exchange决定哪一个queue可以接收到消息
- topic:所有符合routingKey规则的消息,所bind的queue都可以接收到消息