微服务
1. Springboot加载配置文件顺序?
2. SpringBoot启动时都做了那些事
3. Mybatis的sql执行过程
4.SpringMVC的执行流程
5. Spring容器启动的执行过程
6. SpringIOC的过程
7. 微服务整体架构图
8. 如何拆分微服务
DDD领域驱动
9. 几个注册中心的比较?Eureka、Zookeeper、Nacos、Consul
10. 链路追踪和监控(如何实现全链路的业务监控?)
11. 说说springBean的一个加载流程吧,如何解决循环依赖问题
JVM
1. jvm内存结构
-
线程共享
-
方法区 MetaSpace
- 运行时常量池
- 类的信息,包括类名、参数、方法等
- 类的常量
- 类的静态变量
-
堆 Heap
- 老年代 Old Space
-
新生代 Young Space
- Eden -8
- S1-1
- S2-1
-
方法区 MetaSpace
-
线程私有的
-
虚拟机栈
- 本地变量表
- 操作数栈
- 动态链接 一些动态生成的链接地址等,类似于符号引用变直接引用
- 返回地址(包括正常的和异常的)
- 本地方法栈
- 程序计数器
-
虚拟机栈
2. JVM垃圾回收算法,垃圾回收器,G1,CMS回收过程,虚拟机怎么保证分配内存时的线程安全
- 标记清除
- 标记整理
- 标记复制
3. JVM类加载
类加载是指把.class 文件加载到jvm虚拟机的过程,主要包括以下几个步骤
-
加载 Loading 根据类的全限定名从磁盘或者网络上加载二进制流(十六进制编码)
此处主要是使用Java的ClassLoader 即类加载器进行加载,常用的类加载器分为以下几个部分
- BootStrap ClassLoader 即根加载器,主要加载rt.jar的类
- Extention ClassLoader 即扩展类加载器, 主要加载.ext下的jar
- Application ClassLoader 即应用类加载器,加载当前classpath下的类
- Custom ClassLoader 即自定义类加载器
-
链接 Linking
- 验证 Verification 验证类文件的格式,比如开头是否是cafebabe等 包括有对应的词法语法解析器
- 准备 Preparing 给类的静态变量初始化空间并赋值初始值
- 解析 Resovling 把一些符号引用转化为内存中的直接引用
-
初始化 Initialization
给类的静态变量赋值
MethodNotFoundException、ClassNotFoundException、ClassDefNotFoundException分别发生在哪个步骤?
4. 垃圾收集器
-
Serial 新生代 使用标记复制 单线程
-
Serial Old 老年代 使用标记整理 单线程
-
ParNew 新生代 使用标记复制,基于Serial 多线程收集
-
Parallel Scavenge 新生代 使用标记复制,更关注吞吐量
-
Parallel Old 老年代 标记整理,关注吞吐量
-
CMS收集器 老年代 使用标记清楚算法 关注停顿时间,整个过程stw 两次
-
先初始标记 stw (每一步具体做了什么???)
-
然后并发标记
-
重新标记 stw
-
并发清除
并发:用户线程和垃圾回收线程可以并行执行 成为并发
-
-
G1 并发收集器 1.9默认 可以设置具体的停顿时间,会更改jvm的内存区域,划分成大小相等的独立区域
相比cms 是在cms 并发清楚的时候这一步进行筛选回收,主要是因为用户设置了停顿时间,所以只能选择性的回收。
- 堆内存的存活率超过了50%
- 对象的分配和晋升速度变化大
- 垃圾回收时间长
5. JVM调优(你是怎么去做jvm调优的)
-
GC收集器:停顿时间和吞吐量
-
停顿时间: 垃圾回收所使用的的时间
- CMS G1 停顿时间较小 适用于web应用 并发类的收集器
-
吞吐量: 吞吐量 = 运行用户代码的时间/(运行用户代码的时间+垃圾回收时间)
- Parallel Scavenge Parallel Old 并行类的收集器
-
停顿时间: 垃圾回收所使用的的时间
-
内存的使用维度
6.如何设置JVM堆的大小
正常来说默认JVM堆的大小=老年代的大小+新生代的大小,默认比例为2:1, 老年代的大小可以设置成老年代FULL GC后存活对象总大小的3到4倍,元数据区域的大小可以设置为老年代存活对象总大小的1.2倍到1.5倍左右,可以通过JVM启动参数 加入GC日志来观察每次FULL GC后的对象总大小,或者通过JMAP -dump命令下载内存快照强制触发full gc 并通过快照分析。
7. 如果jsp的CPU占用过高如何去分析?
8. Jvm内存溢出怎么分析?
9. Happens Before
- Volatile 规则
- a happens before b , b happens before c 则 a happens before c
- 单线程的程序顺序规则
- 线程的start规则,线程start前面的程序一定对线程内部是可见的
- 线程的join规则,
- 锁规则,synchronize规则
10. 对象在堆中的组成结构
![截屏2020-10-10 10.56.08](/Users/wangqian/Desktop/截屏2020-10-10 10.56.08.png)
Java基础和JUC
1. valotile关键字
原因:之所以出现是因为cpu级别的缓存和总线锁导致的数据可见性问题以及指令重排序问题
作用:valotile的作用是可以禁止指令重排序以及实现可见性,但并不是线程安全的,因为不具有原子性
可见性是如何实现的了?
操作系统层面提供了三种内存屏障,即读屏障 写屏障 和全屏障,JVM基于操作系统层面的内存屏障实现了4种屏障类型,分别是读读屏障,读写屏障、写写屏障和写读屏障。volitile正是在代码执行之后加了一个storeLoad屏障,保证了此写入对后续读是可见的。
为什么存在指令重排序?举例说明
2. 线程池的使用,各参数的作用,拒绝策略的执行
- corePoolSize 核心线程数
- maximumPoolSize 最大线程数
- keepAliveTime 最大线程数持续时间
- unit 持续单位
- workQueue 线程队列
- threadFacory 线程工厂
- refectedHandler 拒绝策略(默认有几种拒绝策略?)
有时候会从源码级别考察,一个线程执行的过程
3. ThreadLocal原理,会出现什么问题?
主要说明实现原理,可以顺带说他的哈希碰撞解决方案用了线性寻址法,以及内存泄露的原因和应该怎么去处理
4. 锁升级的过程
对象在内存中包括 对象头 实例数据 填充数据
对象头包括mark word 对象指针 数组长度
markword 32位或者64位 分别包括线程Id hashcode值 分代年龄 是否偏向锁标识 锁标识等epoch
偏向锁 cas 把当前线程的ThreadId存储在对象头中
轻量级锁 自旋 把锁对象的指针指向当前栈帧中的lockRecord,
重量级锁 锁 monitor monitorenter monitorexit
5. 线程的一些方法wait,notify,condition await() signal()
wait, notify 是属于Object类的方法 基于monitor 监视器锁实现等待和唤醒,后台维护了一个等待队列和一个阻塞队列
await signal是condition接口的方法,是基于ReentrantLock锁实现的 基于AQS等待和唤醒
6.讲讲为什么ConcurrentHashMap是并发安全的吧,既然有锁怎么去统计size呢
7. ArrayList和LinkedList的区别
- arrayList是数组
- LinkedList是链表
8. HashMap的底层数据结构
数组加链表+红黑树
9. hashmap容量为什么是2的幂次?
因为计算数组下标的时候是hashcode & (n-1) 如果n是2的幂次方 转换成二进制后 后面的全是1,做&运算的时候能保证散落的更加均匀
10. 你重写过hashcode和equals么,要注意什么?
11. ConcurrentHashMap怎么解决HashMap的并发问题(源码)
主要是通过unsafe类的cas操作
12. CAS缺点和解决方案
cas 第一个缺点是大多数使用在自选中,但最后只有一个线程可以运行成功,效率较低
第二个是ABA问题,即第一个线程要修改的值是A,然后第二个线程先把值修改成B 然后在修改成A, 这时候cas依然可以成功。
13. Synchronize锁和ReetranLock锁的区别?
把你所知道的可以有逻辑的讲出来,mic的课程很清晰
14. CountDownLatch和CycleBarrier使用场景?
15. JDK动态代理和CGLIB动态代理
jdk动态代理基于接口,因为代理类本身已经继承了Proxy类, 实现Invocationhandler
Proxy.newInstance(ClassLoader, Class<?>[], Invocationhandler)
CGLIB动态代理是基于可继承的父类,通过Enhancer类加强器创建子类实现动态代理 实现MethodIntecetpor
16. 哈希冲突的四种解决办法
-
开放寻址法 如果可以预知哈希的大小,可以完美散列 ThreadLocalMap
- 线性探测
- 二次探测
- 随机数探测
- 拉链法
- 再哈希法
- 建立公共溢出区
17. 一致性Hash算法
- 常规哈希取模
- 不带虚拟节点的哈希 通过服务器的信息(比如 host、port、name等)通过hash算法形成一个圈,每个服务器负责一段范围的请求,如果超过了这个返回就重新回到第一个服务器。这中算法的缺点是其中一个服务挂了之后,所有的请求会转发到下一个服务,容易导致雪崩
- 带虚拟节点的哈希 跟上一个不同点是,把每一个服务器分布在环的不同位置,从而避免雪崩。
中间件
redis如何主从同步?
- 全量复制 通过rdb文件当slave机器连接的时候 master服务器 fork一个子线程 子线程生成一个快照,同步给slave去同步数据
- 增量复制 通过心跳命令去增量同步数据,服务器会维护一个log的内存文件和已经同步的偏移量信息
- 无磁盘复制 通过内存生成文件去复制,不通过磁盘
redis分布式锁注意事项?
redis的哨兵和集群模式?
redis的基本数据结构和底层实现
redis的持久化方案
分布式事务的解决方案?
分布式下redis如何保证线程安全?
单点登录怎么实现?
秒杀系统怎么来实现?
多路复用 IO NIO BIO
布隆过滤器 Bitmap
kafka的架构,如何用kafka保证消息的有序性
kafka和redis的区别
MQ
-
交换机类型
-
消息怎么删除
-
消息队列的使用场景
-
削峰
- 解耦
- 异步
- 广播
-
削峰
-
消息队列什么时候变成死信
-
消息消费的时候被拒绝,没有设置重新进入队列
- 消息过期
- 超过队列长度的时候 第一条消息会变成死信
-
消息消费的时候被拒绝,没有设置重新进入队列
-
多个消费者监听同一个队列,消息怎么分发
-
轮询
- 公平分发
- 比如有的消费时间比较久,占用时长高,这时候可以设置参数 大于多少个的时候不在分发消息
-
轮询
-
无法路由的消息去了哪里?一般被丢弃
-
延迟队列如何实现
-
3.7+ 有一个延迟队列插件
-
TTL + 死信队列 可以通过消息的存活时间和死信队列来实现,指定死信交换机
-
-
-
消息的顺序执行
-
一个队列一个消费者
-
一个队列,然后使用内存队列排队,根据订单id哈希去把同一个订单的发送到同一个队列,然后使用多个线程去消费不同的队列
-
MYSQL
1. 事务的四大特性 ACID
-
原子性 Atomicity
我们对数据库的一系列操作要么都成功 要么都失败,如果转账场景,一个账户增加 另一个失败,要么都成功或者都失败
-
一致性 Consistent
一种是指数据库的完整性月数没有被破坏,比如主键唯一,事务的合法等,另一种是业务的一致性,比如转账场景,A账户余额减少1000,B账户余额增加500,这时候虽然两个操作都成功了,符合了原子性协议,但没有符合一致性
-
隔离性 Isolation
数据库中有很多事务同时去访问同一个表或者同一行数据,必然会产生一些并发的影响,这时候就要定义不同的事务让他们之间互不干扰,也是通过这种方式保证数据的一致性
-
持久性 Durable
对数据库的任意操作,只要成功了就应该是持久的
数据库的持久性是如何实现的?
通过redo log 和 double write双写缓冲来实现的,我们操作数据的时候,会先写道内存的buffer pool中,同时记录redo log,如果在刷盘之前出现异常,再重启后就可以读取redo log的内容,写入磁盘,保证数据的持久性,当然恢复成功的前提是数据页本身没有被破坏,是完整的 这个是通过双写缓冲来保证的
2. 性能优化
-
通过开启slow query log 可以把慢查询日志记录下来,并用mysqlDumpSlow分析并导出对应需要的格式
mysqlDumpSlow -s t -t 20 -g ‘select’ /usr/local/mysql/data/slow-query.log
-
通过开启select @@profile 开关 可以查看sql执行所用时间 cpu占用信息等情况,
select * from information_schema.profiling 表中存储了所有信息
-
通过show processlist 或者select * from information_schema.processlist 查看所有正在执行的sql线程,看看是否有死锁线程或者等待队列已满等情况
3. EXPLAIN
-
id 笛卡尔积,2 * 3 * 4 = 6 * 4 会优于 3 * 4 * 2 = 12 * 2 因为中间产生的笛卡尔积临时表空间更少
如果id大小不一样 查询顺序是从大到小进行,如果id大小是一样的,查询顺序是自上而下
-
select_type
- PRIMARY
- SUBQUERY
- DERIVED
- UNNION ALL
-
table
-
type
- System 系统只有一行数据
- const 只查询到一行数据
- eq_ref 唯一性索引 是对于多表join查询,并且对于前表的每一条结果刚好能匹配到后表的每一条结果
- ref 非唯一性索引,
- range 范围查找
- index full index scan 查询的字段是在索引上的字段
- all
- null
-
possible_keys 可能使用到的索引
-
key 实际使用到的索引,如果查询的字段存在于某个索引上,虽然这个索引不符合最左匹配原则,但实际还是会用到索引,此时 possible_keys为null key不为null,
-
rows 扫描到的大概行数
-
filtered 过滤的百分比 越接近100越好
-
extra 附加条件
-
using where 如果存储引擎层返回的数据不是我们最终需要的数据,需要经过server端过滤的时候
需要索引查询到的数据回表查询,
-
using index 只是用了索引数据,不需要回表去查询数据
-
using index condition 索引下推 查询的条件语句经过索引进一步过滤之后 再返回给server端
-
using filesort 没有使用到对应的索引排序, 用了额外的排序规则
-
using temporary 临时表
-
4. 一个SQL语句的执行过程
连接–》分析器(词法分析器(把一个sql语句拆分成单个的单词去校验)和语法分析器(按照语法树去分析))–》预处理器–》优化器(query optimizer,会生成所有可能的执行计划,然后基于开销时间获取一个最佳的执行计划) –》执行计划(可以通过explain查看)-》执行引擎
5. mysql 存储文件
-
innodb:
- .frm文件 表结构文件
- ibd文件索引文件
-
myisam
- frm文件 表结构文件
- myi 索引文件
- myd 数据文件
6. 存储引擎
-
MYISAM
-
支持表级别的锁,不支持事务 ,所以插入和更显会锁表
-
拥有较高的插入和查询速度,适合读多写少的情况
-
存储了表的count行数,所以count速度更快
怎么快速向数据库插入100万条数据了,可以先用MYISAM引擎插入数据,然后在修改为INNODB
-
-
INNODB
- 支持事务,支持外键,
- 支持航级别的锁和表级别的锁
- 支持读写并发,写不阻塞读(MVCC)
- 特殊的索引存放 可以减少IO
7. INNODB的内存结构和磁盘结构
1. 内存结构(Buffer Pool) 默认128M 可以缓存索引页和数据页
-
Buffer Pool 内存的缓冲池数据写满了怎么办?Innodb使用LRU算法,有一个Buffer Pool List分成Old (3/8)和young(5/8)代,如果查询的数据在缓冲池中已存在,则提升到连表头,如果不存在则放到中间,后面的数据进行末尾淘汰
-
Change Buffer 写缓冲,如果要写入的数据不是唯一索引,也就是说不存在数据重复的情况下,就不需要先从磁盘加载判断唯一性,直接先在缓冲池中更改,从而提升增删改的执行速度,5.5之前的版本叫Insert Buffer 现在可以支持更新和删除,所以更改为Change Buffer
然后把change Buffer的数据merge,在访问这个数据页的时候merge,通过后台线程、或者数据库发生 shutdown的时候,以及 redo log 写满时触发
如果数据库的大部分索引都是非唯一索引,并且业务是写多读少,不会在写后立刻读取数据,就可以调大change buffer的值,默认占用 Buffer Pool的25%空间
-
Adaptive Hash index
-
(redo)log buffer 为防止Change Buffer里的数据未同步到磁盘发生宕机,innodb 把所有的写操作都记录在 redo log 中,redo log在表空间中 默认有2个文件,每个文件默认48M,默认都是先写日志 在写磁盘,因为写入磁盘是随机IO,写入日志文件是顺序IO,所以速度更快。默认是每次事务提交的时候 都会把log buffer 写入到磁盘。
redo log的文件大小是固定的,默认16M,里边有两个指针,一个写指针,一个check指针,如果两个指针重叠,说明文件满了,这个时候需要同步数据到磁盘中
2. 磁盘结构
- 系统表空间 system tablespace
-
独占表空间
- segment 段 数据段 索引段
- extent 区(簇) 大小是1M 包括64个页
- page 页 每个页大小是16K
- 通用表空间
8. 日志文件
- redo log 记录修改后的数据,用来更新数据日志 是在存储引擎层完成的
- undo log 记录之前的数据,用来恢复数据
- bin log 记录逻辑日志,只记录操作,不记录数据,主要用来做主从复制和数据恢复 是在server层完成的
一个更新语句的执行流程是:先经过server层,然后交给存储引擎层执行更新,将更新结果写入到Buffer Pool,记录redo log的记录并且设置状态为prepare的,然后返回给server层,server层写入bin log日志,此时再通知存储引擎层更改redo log的状态为commit,保持数据一致性
Innodb 辅助索引只存储索引和主键值 ,主键索引即聚簇索引是和数据放在一起的
Myisam 索引和数据是分开存储的
mysql innodb如果没有主键:第一个不为空唯一索引 –》row_id
9. 事务的几种数据特性 / 事务并发带来的几个问题
-
脏读
A事务读到了B事务更改后但未提交的值,导致两次读取到的数据不一样
-
不可重复读
A事务读到了B事务更改(特指修改或者删除)后提交的值,导致两次读取的数据不一样,
-
幻读
A事务读到了B事务更改(特指新增)后提交的值,导致两次读取的数据不一样
10. 事务的4个隔离级别
-
RU (Read Uncommitted)未提交读
-
RC (Read Committed) 已提交读
通过MVCC实现读一致性 通过Record lock 实现写一致性,存在幻读问题
-
RR (Repeatable Read)可重复读 同一个事务里多次读取同样的数据结果是一样的,解决了不可重复读问题,并在INNODB中解决了幻读的问题,通过临键锁,
通过MVCC实现读一致性,通过Record Lock,Gap Lock, Next-Key Lock实现写一致性
-
Serializable 串行化 所有的事务都是串行执行的,解决了所有问题,但效率降低
11. Mysql默认添加的几个字段
- DB_TRX_ID 事务id
- DB_ROLL_PTR 回滚id 指向undo log
- DB_ROW_ID 行id
12. mysql锁
包括有排它锁 即Exclusive Lock 即X锁 写锁 和 共享锁 Share Mode Lock 即S锁 读锁
-
record lock 记录锁
如果查询的数据正好是唯一索引或者主键索引的数据,就是记录锁,只锁当前这一行数据
-
gap lock 间隙锁
间隙所是记录锁的n+1个,是指记录不错的区间锁,左开右开的,如果查询的记录不存在,则用间隙锁
-
Next-key Lock 临键锁 默认
临键锁是INNODB默认的行锁,并通过此解决了在RR事务级别下的数据幻读问题,如果查询的是区间数据并且命中了某一个索引的值,则用间隙锁,锁住当前记录的前一个间隙锁和后一个间隙锁。
13. mysql几种日志文件格式及作用
- redo log 重做日志 。是存储引擎层记录的,在数据更新到Buffer Pool之后记录在redo log中,用于记录数据的更新值
- undo log 回滚日志,也是存储引擎层记录的,用于记录数据更新前的值,
- bin log server层记录的,只记录增删改的这些sql日志,并不记录实际的值,用于主从同步和数据恢复的
14. MySQL多版本控制MVCC
Multi Version Concurrency, 基于快照实现, 通过数据库隐藏的字段 事务id和 回滚指针来完成,回滚指针指向undo log里的数据
15. 如何解决数据库读一致性的问题
-
LBCC (Lock Based Conconrrency Control)
在读取数据前,对其加锁,阻止其他事务对数据进行修改
-
MVCC( Muti Version Concurrency Control )
生成一个数据请求时间点的快照,并用这个快照来提供一定级别的一致性读取
16. 二叉搜索树 二叉平衡树(AVL)B树 B+树
-
二叉搜索树:左子树小于根节点 右子树大于根节点 按照中序排列的话是一个有序的队列,缺点是树的深度太大
-
二叉平衡树:基于二叉搜索树的一个平衡树 树的深度绝对值差不超过1
-
B树 :多路平衡树,度是关键字n的n+1个,叶子节点都存储数据和索引,度数多
-
B+树:非叶子节点只存储关键字,不存储索引和值,所以可以存储更多关键字,叶子节点中有一个指向下一个节点的指针,排序和范围查询更快,而且树的深度是固定的,所以IO次数是固定的
17. 死锁的四大必要条件(举例说明,如何发生一个死锁)
- 互斥条件
- 持有且等待
- 不可剥夺
- 循环依赖
18. 哪些情况不走索引
1、查询条件中使用了计算 比如 age+=20
2、查询条件中使用了函数
3、第三是like %
4、or操作
其他
1. tcp为什么断开连接时是四次挥手而建立连接时是三次握手
2. 设计模式7大原则
-
开闭原则
-
单一职责原则
-
接口隔离原则 接口不要太大 尽可能暴露给客户端的都是需要的
-
里氏替换原则 子类可以继承父类,但不能更改父类原有的功能
-
依赖倒置原则 应该是下层依赖上层 细节依赖抽象
-
迪米特法则 也即最少知道原则
-
合成复用原则 尽量多远组合而不是继承
3. 常见的设计模式(举例说明你在项目中的实际使用场景)
必问话题
个人提前准备好对应的措辞,反复自问自答,多做练习
- 自我介绍
- 找一个你做过最复杂的项目说说
- 找一个你解决过项目中最困难的问题说说
- 找一个你解决的记忆最深刻的线上问题说说
- 在上家公司个人成长
- 为什么离开上家公司
- 个人未来的职业规划
- 对我们公司,你还有什么想要了解的吗(可以多问问岗位工作内容、技术栈方向)
- 你期望的薪资、你目前的薪资
- 你对我们公司的职级了解么?你期望达到什么职级
开发性话题
-
设计一个系统你会考虑哪些方面?
-
了解CAP、Base、Tcc吗?
-
如何拆分微服务?DDD了解过吗?
-
怎么去设计一个单点登录系统?
-
如何去设计一个秒杀系统?
-
如何去设计一个短连接处理系统?
中间件主要以MQ、REDIS、ZOOKEPER和KAFKA为主