java架构师面试题
menu
1 数据库 2 jvm 3 redis 4 Mq 5 springcloud 6 系统解决方案 7 多线程 8 网络
为什么说B+比B树更适合实际应用中操作系统的文件索引和数据库索引?
1、B+的磁盘读写代价更低。
B+的内部结点并没有指向关键字具体信息的指针,因此其内部结点相对B树更小。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。
2、B+-tree的查询效率更加稳定。
所有的记录都存储在叶子节点上面。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
3、叶子节点都是有序链表存储的,更利于范围存储
MySQL InnoDB、Mysaim的特点?
Innodb是聚集索引,支持事务,支持外键 ,支持行锁,必须有唯一索引,适合修改删除多的,回表查询指的是innodb里面非聚集索引的查询。
Myisam是非聚集索引,叶子节点保存指向数据的指针。不支持事务。
Mysql默认隔离级别
可重复读(RR),由于之前mysql的binlog同步模式为statement,RC会产生数据不一致。当前binlog已经支持row的格式,可以选择RC+ROW,作为mysql的隔离级别和日记格式
数据库隔离级别是什么?
A原子性(undo log)、C一致性(AID来保证)、I隔离性(加锁、mvcc)、D持久性(redo log)
读已提交(脏读)、可重复读(读未提交)、序列化(幻读)
MySQL主备同步的基本原理。 当slave连接到master的时候,master机器会为slave开启binlog dump线程。当master 的 binlog发生变化的时候,binlog dump线程会通知slave,并将相应的binlog内容发送给slave
如何优化数据库性能(索引、分库分表、批量操作、分页算法、升级硬盘SSD、业务优化、主从部署)
SQL什么情况下不会使用索引( select *、索引列上有函数、字段类型错误、like%、or、not in、not exist、order by 不加where,加了limit)
exists和in 如果两个表中一个较小,一个是大表,则子查询表大的用exists,子查询表小的用in
MVCC
InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读
通过readview 机制和 undo log 实现了读不被阻塞,而且在一定程度上实现RC和RR,并且RR级别在大部分时候可以解决幻读
快照读(select ...)的幻读是用MVCC解决的,当前读(select ... for update; update; delete)的幻读是用间隙锁解决的。
数据库事务的几种粒度
表锁、行锁(通过索引来加锁)只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁
如何解决高并发减库存问题
如何从一张表中查出name字段不包含“XYZ”的所有行?
not in where name LIKE "%XYZ%"
mysql主从同步延时分析 1.业务的持久化层的实现采用分库架构,mysql服务可平行扩展,分散压力。
2.单个库读写分离,一主多从,主写从读,分散压力。这样从库压力比主库高,保护主库。
3.服务的基础架构在业务和mysql之间加入memcache或者redis的cache层。降低mysql的读压力。
4.使用比主库更好的硬件设备作为slave,mysql压力小,延迟自然会变小。
加锁原则
拿MySql的InnoDB引擎来说,对于insert、update、delete等操作。会自动给涉及的数据加排他锁;
对于一般的select语句,InnoDB不会加任何锁,事务可以通过以下语句给显示加共享锁或排他锁。
共享锁:SELECT ... LOCK IN SHARE MODE;
排他锁:SELECT ... FOR UPDATE;
索引的优缺点:优点,提高查询速度;缺点,更新数据时效率低,因为要同时更新索引
Eureka的源码分析服务注册和服务发现以及心跳机制和保护机制,对比eureka与zookeeper,什么是CAP原则?
服务注册,客户端启动后向服务端发送一个心跳,如果返回404,证明未注册,发起注册
服务发现 客户端定时拉去服务列表,保存在本地,使用时调用本地
心跳机制 客户端每30s会向服务端发送一个心跳,如果90s没有收到心跳,服务端会剔除客户端
自我保护机制 服务端在发现一定时间内剔除的节点过多,就会触发自我保护,不再剔除节点
Eureka支持AP 1、自我保护机制 2、server端低位平等,只要一个server存在就可以正常工作
Zookeeper支持CP 哨兵模式,需要选主,选举过程停止服务,中心化思想
Nacos
Ribbon源码分析和客服端负载均衡,客户端负载均衡?服务端负载均衡? Ribbon核心组件IRule以及重写IRule
客户端负载均衡,Ribbon实现IRule接口来自定义负载均衡策略
Fegin源码分析和声明式服务调用,Fegin负载均衡,Fegin如何与Hystrix结合使用? 有什么问题?
给feigin类写一个实现类fallback,里面定义一些方法,在feigin调用的时候,在类上引入fallback
Hystrix实现服务限流、降级,大型分布式项目服务雪崩如何解决? 服务熔断到底是什么?一线公司的解决方案
服务降级:返回一个友好提示,场景:程序运行异常、超时、服务熔断、服务限流
服务熔断:当失败达到一定阈值之后,停止服务调用,直接降级。等待一个时间窗口之后,继续重试
服务限流:采用线程池和信号量的方式对服务调用进行限流
Zuul统一网关详解、服务路由、过滤器使用等,从源头来拦截掉一些不良请求
路由 : 网关的基本模块,有ID,目标URI,一组断言和一组过滤器组成
断言:就是访问该路由的访问规则,可以用来匹配来自http请求的任何内容,例如headers或者参数
过滤器:这个就是我们平时说的过滤器,用来过滤一些请求的,gateway有自己默认的过滤器,我们也可以自定义过滤器,但是要实现两个接口,ordered和globalfilter
分布式链路跟踪详解,串联调用链,,让Bug无处可藏,如何厘清微服务之间的依赖关系?如何跟踪业务流的处理顺序?
2、 持久化:RDB、AOF
3、 缓存击穿:1、查询之后将数据刷新到redis中 2、避免一些热点数据在同一时间失效、
缓存雪崩:同时失效或者redis挂掉。1、分散过期时间,2、通过hystrix进行访问降级或者限流 3、redis进行分布式部署
缓存穿透:1、增加查询限制条件 2、在特殊值加载到缓存里面 3布隆过滤器
5、 使用场景:1、会话缓存 2、热点数据缓存 3、搜索历史记录 4、分布式锁
6、单线程,I/O多路复用, 通过数组的方式同时处理多个I/O
7、高可用方案:主从、哨兵、cluster(hash槽)https://blog.csdn.net/u014209205/article/details/82113258
8、redis如何实现多写入,通过事务中添加watch来检测,要修改的key是否已经被其他客户端修改
9、redis脑裂:出现了两个master节点,clientA继续往旧的master写入,旧master降级成为slave节点,会进行全量同步,会把clientA写入的数据清空。解决方案:参数配置,min-slave-to-write 2、min-slaves-max-lag 5:至少2个slave对master的同步复制延迟不能超过5秒,如果达不到要求,master停止接受请求
10、redis主从节点是长连接
11、redis如何判断某个节点是否正常工作?通过心跳检测机制,主节点10秒发送一次心跳检测从节点 ,从节点1秒发送一次心跳给主节点同步自己的复制偏移量
12、主从如何判断全量同步还是增量同步?从节点slave到主节点之后,如果是首次直接全量同步,如果存在偏移量,比较偏移量和主节点的缓冲区大小,如果比缓存区小,那么可以进行增量同步,不然进行全量同步
13、redis不同数据类型的作用
- String:缓存,限流,计数器,分布式锁,分布式Session
- Hash:储存用户信息,用户主页访问量,组合查询
- List:微博关注人时间轴列表,简单队列
- Set:赞,踩,标签,好友关系
- Zset:排行榜
14.Redis为什么这么快?官方使用的基准测试结果表明,单线程的Redis可以达到10W/S的吞吐量。
a.基于内存操作:Redis的所有数据都存在内存中,因此所有的运算都是内存级别的,所以它的性能比较高。
b.数据结构简单:Redis的数据结构比较简单,是为Redis专门设计的,而这些简单的数据结构的查找和操作的时间复杂度都是O(1)。
c.多路复用和非阻塞IO:Redis使用IO多路复用功能来监听多个socket连接的客户端,这样就可以使用一个线程来处理多个情况,从而减少线程切换带来的开销,同时也避免了IO阻塞操作,从而大大提高了Redis的性能。
d.避免上下文切换:因为是单线程模型,因此就避免了不必要的上下文切换和多线程竞争,这就省去了多线程切换带来的时间和性能上的开销,而且单线程不会导致死锁的问题发生。
Redis在4.0之前单线程依然快的原因:基于内存操作、数据结构简单、IO多路复用和非阻塞IO、避免了不必要的线程上下文切换。并且在Redis4.0开始支持多线程,主要体现在大数据的异步删除方面,例如:unlink key、flushdb async、flushall async等。而Redis6.0的多线程则增加了对IO读写的并发能力,用于更好的提升Redis的性能
1、notify唤醒哪一个线程?
notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。
notifyAll会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒,推荐使用notifyAll方法。
2、如何停止一个线程的运行
stop()、interrupt() 设置一个标志位,在run方法内部进行判断。
https://www.nowcoder.com/discuss/740580?source_id=discuss_experience_nctrack&channel=-1
3、线程如何同、进程如何同步
1、Mq如何保证消息不丢失 : 1、生产者:采用confirm机制(同步、异步) 2、mq本身:消息持久化,可以结合confirm机制 3、消费者:关闭自动的ack
2、消息队列机制 https://blog.csdn.net/h2604396739/article/details/81136527
3、消息堆积:会阻塞生产者,必须提高消费端能力,还可以增加队列容量,
tcp如何保证传输可靠性,tcp的报文头包含什么
https: 1、非对称+对称:先用非对称浏览器将对称秘钥传给服务器,之后服务器和浏览器通过对称加密传输 2、防止公钥被篡改,采用证书
java对象内存分布
1,对象头 2,实例数据 3,对齐填充字节
对象头:1,Mark Word 2,指向类的指针 3,数组长度(只有数组对象才有)
mark word :锁、分代年龄、gc标记
JVM一般是这样使用锁和Mark Word的:
1,当没有被当成锁时,这就是一个普通的对象,Mark Word记录对象的HashCode,锁标志位是01,是否偏向锁那一位是0。
2,当对象被当做同步锁并有一个线程A抢到了锁时,锁标志位还是01,但是否偏向锁那一位改成1,前23bit记录抢到锁的线程id,表示进入偏向锁状态。
3,当线程A再次试图来获得锁时,JVM发现同步锁对象的标志位是01,是否偏向锁是1,也就是偏向状态,Mark Word中记录的线程id就是线程A自己的id,表示线程A已经获得了这个偏向锁,可以执行同步锁的代码。
4,当线程B试图获得这个锁时,JVM发现同步锁处于偏向状态,但是Mark Word中的线程id记录的不是B,那么线程B会先用CAS操作试图获得锁,这里的获得锁操作是有可能成功的,因为线程A一般不会自动释放偏向锁。如果抢锁成功,就把Mark Word里的线程id改为线程B的id,代表线程B获得了这个偏向锁,可以执行同步锁代码。如果抢锁失败,则继续执行步骤5。
5,偏向锁状态抢锁失败,代表当前锁有一定的竞争,偏向锁将升级为轻量级锁。JVM会在当前线程的线程栈中开辟一块单独的空间,里面保存指向对象锁Mark Word的指针,同时在对象锁Mark Word中保存指向这片空间的指针。上述两个保存操作都是CAS操作,如果保存成功,代表线程抢到了同步锁,就把Mark Word中的锁标志位改成00,可以执行同步锁代码。如果保存失败,表示抢锁失败,竞争太激烈,继续执行步骤6。
6,轻量级锁抢锁失败,JVM会使用自旋锁,自旋锁不是一个锁状态,只是代表不断的重试,尝试抢锁。从JDK1.7开始,自旋锁默认启用,自旋次数由JVM决定。如果抢锁成功则执行同步锁代码,如果失败则继续执行步骤7。
7,自旋锁重试之后如果抢锁依然失败,同步锁会升级至重量级锁,锁标志位改为10。在这个状态下,未抢到锁的线程都会被阻塞。
2,指向类的指针
该指针在32位JVM中的长度是32bit,在64位JVM中长度是64bit。Java对象的类数据保存在方法区。
3,数组长度
只有数组对象保存了这部分数据。该数据在32位和64位JVM中长度都是32bit。
GCRoots的对象包括下面几种:
(1). 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。
(2). 方法区中的类静态属性引用的对象。
(3). 方法区中常量引用的对象。
(4). 本地方法栈中JNI(Native方法)引用的对象。
sychronized底层实现逻辑、
volatile 内存屏障
防重放:1、增加时间戳,请求超时后端不处理 2、请求携带uuid
大文件下载
token机制、spring注解原理、、cpu彪高处理、数据库慢排查方式、索引失效、CDN刷新不一致