面试大纲_V2023
2023-08-04 01:20:22 0 举报
AI智能生成
2500个节点的Java面试大纲。
作者其他创作
大纲/内容
Java
Java基础
泛型
为什么要使用泛型?
基本使用
泛型类
泛型接口
泛型方法
泛型数组
泛型的上下限
深入理解
1. 如果理解Java中的泛型是伪泛型?
2. 如何证明类型的擦除呢?
基础
new Integer(123) 与 Integer.valueOf(123) 的区别?
String有哪些特性?
String不可变性有哪些好处?
String, StringBuffer and StringBuilder
集合
Colllection
Set
TreeSet
HashSet
LinkedHashSet
List
ArrayList
自动扩容原理?
Fail-Fast机制
LinkedList
Vector
Queue
LinkedList
PrivrityQueue
Map
TreeMap
HashMap
JDK7
JDK8
HashMap如何解决哈希冲突?
HashMap的扩容机制是什么?
HashTable
LinkedHashMap
WeakHashMap
线程
基础
说说线程、进程、协程之间的区别?
java线程阻塞调用wait()和sleep()区别和联系,还有函数yield,notify等的作用?
线程的生命周期?
为什么我们调用start()方法会执行run(),为什么我们不能直接调用run()?
什么是线程中断?如何正确地中断一个线程?
并发
JUC
基础类
原子类、CAS、UnSafe
线程安全的实现方法有哪些?
CAS是什么?
CAS会有哪些问题?
针对这这些问题,Java提供了哪几个解决的?
AtomicInteger底层实现? CAS+volatile
请阐述你对Unsafe类的理解?
AtomicStampedReference是什么?
AtomicStampedReference是怎么解决ABA的? 内部使用Pair来存储元素值及其版本号
java中还有哪些类可以解决ABA的问题?
LockSupport
Thread.sleep()、Object.wait()、Condition.await()、LockSupport.park()的区别?
Thread.sleep()和Object.wait()的区别
LockSupport.park()会释放锁资源吗?
如果在wait()之前执行了notify()会怎样?
如果在park()之前执行了unpark()会怎样?
AQS
什么是AQS? 为什么它是核心?
AQS的核心思想是什么? 它是怎么实现的? 底层数据结构等
AQS有哪些核心的方法?
AQS定义什么样的资源获取方式?
锁相关类
ReentrantReadWriteLock
StampedLock
ReentrantLock
什么是可重入,什么是可重入锁? 它用来解决什么问题?
ReentrantLock的核心是AQS,那么它怎么来实现的,继承吗? 说说其类内部结构关系。
ReentrantLock是如何实现公平锁的?ReentrantLock是如何实现非公平锁的?ReentrantLock默认实现的是公平还是非公平锁?
ReentrantLock和Synchronized的对比?
线程管理类
线程池ThreadPoolExecutor
Java的线程池说一下,各个参数的作用,如何进行的?
当提交新任务时,异常如何处理?
线程池都有哪几种工作队列?
几种常用的线程池
线程池各个状态切换图
如何合理配置线程池?
ForkJoin框架
工具类Executors
并发集合类
Queue
List
Set
Map
为什么 CHM 的读操作不需要加锁?
为什么CHM不允许插入null值?
为什么 CHM是线程安全的?
线程同步类
Semaphore
Semaphore内部原理?
CountDownLatch
实现一个容器,提供两个方法,add,size 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束? 使用CountDownLatch 代替wait notify 好处。
CyclicBarrier
CyclicBarrier底层实现原理?
CountDownLatch和CyclicBarrier对比?
Phaser
Exchanger
CompletableFuture
CompletableFuture原理与实践-外卖商家端API的异步化
Future
Future的缺点?
IO
异常
关键字
说说你对ThreadLocal的理解
什么是ThreadLocal? 用来解决什么问题的?
ThreadLocal是如何实现线程隔离的?
ThreadLocal有什么缺陷?为什么会导致内存泄漏?如何解决?
还有哪些使用ThreadLocal的应用场景?
ThreadLocalMap如何解决value冲突问题?跟HashMap有什么区别?
ThreadLocal能否做到子线程中共享父线程中的数据?InheritableThreadLocal了解过吗?有什么局限性?
FastThreadLocal有了解过吗?
ThreadLocalMap的Key是什么?Entry的key为什么要用弱引用? 四种引用有什么区别说一下?为什么使用弱引用就可以解决内存泄漏问题?
volatile
happens-before规则
as-if-serial规则
面试题
volatile关键字的作用是什么?
volatile能保证原子性吗?
i++为什么不能保证原子性?
共享的long和double变量的为什么要用volatile?
volatile是如何实现可见性的?
volatile是如何实现有序性的?
说下volatile的应用场景?
synchronized
JVM中对锁的优化
锁粗化
锁消除
轻量级锁
偏向锁
自旋锁
锁的类型:无锁、偏向锁、轻量级锁、重量级锁
final
基础使用
修饰类: 不能被继承
修饰方法:private方法是隐式final;final方法可以被重载。
修饰参数: 无法在方法中修改参数引用的对象。这个特性主要用来向匿名内部类传递参数。
修饰变量
重排序规则
final域为基础类型
final域为引用类型
JVM
问题排查工具
jstat
jmap
jstack
jps
排查工具
监控工具
面试题
1. JVM的永久代中会发生垃圾回收么?
2. 为什么要移除永久代?
3. AOT 和JIT 的区别?
待整理
G1
重要概念
CSet(Collection Set)
记录要收集的Region集合
STAB(Snapshot-At-The-Beginning)
GC开始时存活对象的快照
跨代/跨Region引用
RSet(Remembered Set)
记录”谁引用了我的对象“, 用于缩减GC Roots扫描范围
属于point-into
Card Table
记录"我引用了谁的对象"
GC过程
三色标记算法
黑色
灰色
白色
会有什么问题?
Lost Object Problem
JVM内存模型演进
JVM内存模型
运行时区域
线程私有
虚拟机栈
本地变量
本地方法栈
存native方法
程序计数器
线程共有
方法区
字符串常量池、类静态变量、类元信息
堆
数组和实例
直接内存 direct memory
演进过程
JDK1.7:永久代仍存在于JDK1.7中,并没完全移除,譬如符号引用(Symbols)转移到了native(本地) heap;字面量(interned strings)+类的静态变量(class statics)转移到了java heap,类的加载信息还在永久代中。
JDK1.8:JDK8中JVM不再有方法区,方法区存储的信息:1、类信息->元数据区 2 、static变量+常量池->jvm堆中。元数据空间不在jvm中,而是使用本地内存(jvm之外的内存,但也有可能被java使用)
JMM
Java对象创建过程
类加载器
分类
双亲委派机制
类加载过程
Java的对象结构
参考文献
JVM面试题,看这篇就足够了
GC回收算法分类
标记-清除:缺点:效率不高,无法清除垃圾碎片。
复制:缺点:内存使用率不高,只有原来的一半,消耗内存。
标记-整理
分代收集:新生代基本采用复制算法,老年代采用标记整理算法
GC回收器演进(从堆空间角度分)
【综述】JVM 垃圾回收器不可能的三角是什么?(评估GC的性能指标)
1、内存占用
2、吞吐量
3、延迟(停顿时间STW)
一、分代模型
新生代:Series New、 ParNew、Parallel Scavenge
老年代:Series Old、Parallel Old 、CMS
为什么分代?
解决的是:基于内存中的大量对象会在较短的时间内达到不可达.
Parallel Scavenge:吞吐量优先
CMS
GC运行过程
1、初始标记STW
2、并发标记
3、重新标记STW
4、并发清除
优缺点
优点
并发标记、并发清除可以和用户线程一起,低延迟。
缺点
1、处理器资源敏感:并发处理导致应用程序变慢,降低吞吐量
2、内存敏感:并发阶段会产生“浮动垃圾”
3、基于标记-清除算法:会产生内存碎片
二、分代分区模型
G1
设计目标?为了解决什么问题?怎样建立起可靠的停顿预测模型?
1. 从整体看是"标记-整理"的收集器,不会产生过多内存碎片.CMS不能压缩老年代
2. STW更可控,G1在停顿时间上添加了预测机制
内存模型:将堆内存划分多个大小相等的独立区域 Region
E、S、O、H
H:存储>=region一半的对象;如果超过region大小,则存放在N个连续H中。
属于老年代
Region大小可以通过参数设定
-XX:G1HeapRegionSize
如何解决上述问题?
STAB(Snapshot-At-The-Beginning) :GC开始时存活对象的快照
TASM()
G1如何判定Region的“价值”?( 为什么叫G1?)
RSet
GC过程
YoungGC
触发时机:E区不能再分配新对象
回收范围:E、S
用的拷贝算法
运行过程
事后
1. 记录E/S的数据和GC时间
2.根据暂停目标自动调整Region的数量,暂停目标越短,Eden数量越少,会造成吞吐量下降
MixedGC
触发时机
O占用空间>整个heap的45%:可通过参数-XX:InitiatingHeapOccupancyPercent设置
事实上,并不会立刻触发,而且等待下一次Young GC,同步进行初始标记步骤
回收范围
哪些Region? ----被并发标记过的Region,这些Region是G1通过价值测算动态选中的。
选定所有年轻代的Region+1/8老年代
运行过程
1、初始标记STW
2、并发标记
3、重新标记STW
4、清除STW
5、拷贝STW
FullGC(STW)
如果MixedGC跟不上程序分配内存的速度,导致老年代被填满,就会使用serial old GC来进行FullGC回收整个heap
FullGC会暂停整个引用
从全局看:标记-整理;从局部看:标记-复制
优缺点
优点
基于标记-复制算法:避免了空间碎片
缺点
堆利用率不高:原因就是引入的 RSet 占用内存空间较大,一般会达到1%~20%;
暂停时间较长:通常 G1 的 STW 时间要达到几十到几百毫秒,还不够低。
最佳实践
适用场景
软实时、低延迟、可设定目标
-XX:MaxGCPauseMills = 200 默认
JDK9+默认的GC
适用于较大的堆(>4~6G)
用于替代CMS
面试题
1. 既然我们已经有了前面几个强大的GC,为什么还要发布Garbage First (G1) GC?
2. 为什么名字叫做Garbage First (G1)呢?
3. 怎么选择垃圾回收器?
没有最好的算法,只有最合适的算法
参考文献
Java Hotspot G1 GC的一些关键技术
可能是最全面的G1学习笔记
垃圾回收器 G1 详解
从串行收集器到分区收集开创者G1
前沿实践:垃圾回收器是如何演进的?
三、分区模型
Shenandoah(Red Hat研发)
转发指针 -- 用来实现并发收集
连接矩阵 -- 取代RSet
内存屏障模型修改为:引用访问屏障,用于解决读屏障带来的性能开销
没有新生代和老年代之分
设计目标:是将停顿压缩到10ms级别
Shenandoah和G1的区别?Shenandoah相比起G1又有什么改进呢?
Shenandoah GC 是如何实现并发复制的
解决 G1 中产生最长暂停时间的「并行复制」问题,通过与 ZGC 不一样的方式,实现了「并发复制」
四、分层分区模型
ZGC
染色指针(直接把标记信息记在引用对象的指针)-- 用来实现并发收集
染色指针的在GC过程中的作用
多种内存映射
NUMA
https://juejin.cn/post/7226153290051076156#heading-9
堆内存划分: Small、Medium、Large
参考
详解现代垃圾回收器Shenandoah和ZGC
七大经典垃圾回收器篇+部分调优
Spring
SpringBoot
SpringBoot的启动流程
SpringFactoriesLoader
1. 执行静态方法SpringApplication.run
@SpringBootApplication
@SpringBootConfiguration
@Configuration
@EnableAutoConfiguration
借助@Import,所有符合自动创配条件的Bean都会被注入到IOC容器
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
通过SpringFactoriesLoader加载META-INF/spring.factories
springBoot自动装配的原理你能说出来吗?
Bean注入的方式有哪些?
Spring是如何启动的?
Spring是如何加载配置文件到应用程序的?
Spring Bean有没有必要实现Aware接口
@Autowire和@Resource区别
Spring Bean的生命周期?Spring Bean的加载过程?
创建Bean的四种方式?
BeanFactory和FactoryBean的区别
BeanFactory和ApplicationContext的区别
BeanFactoryPostProcessor和BeanPostProcessor有什么区别?作用分别是什么?
BeanFactoryPostProcessor
在Bean实例化之前执行
内置的有:PropertyPlaceholderConfigurer
可以在Bean实例化之前对Bean的元信息进行修改
BeanPostProcessor
在实例化之后,初始化前后执行
Spring循环依赖
三级缓存
第1级:存储完整对象的singletonObjects
第2级:存储半成品对象的earlySingletonObjects
第3级:存储对象的创建工厂的singletonFactories。
为什么要三级?只用2级缓存是否可以?
为什么只支持单例?
为什么只不支持构造函数?
为什么一级缓存是ConcurrentHashMap而其他缓存都是HashMap
Spring中有哪些设计模式?
模板模式
责任链模式
工厂模式
代理模式
适配器模式
JDK的AOP和cglib的AOP有什么区别? Spring默认使用哪个
Spring AOP和AspectJ有什么区别?
Spring的AOP在什么场景下会失效?
Spring事务的传播行为有哪些?
(默认)REQUIRED:如果不存在事务则开启一个事务,如果存在事务则加入之前的事务,总是只有一个事务在执行
REQUIRES_NEW:每次执行新开一个事务。
SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则非事务执行。
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则挂起该事务。
mandatory:强制有事务,没有事务则报异常
NEVER:以非事务方式执行操作,如果当前存在事务,则抛出异常。有事务则报异常
NESTED:如果之前有事务,则创建嵌套事务,嵌套事务回滚不影响父事务,反之父事务影响嵌套事务。
Spring事务在哪几种情况下会不生效?
@Transactional 应用在非 public 修饰的方法上
@Transactional 注解属性 propagation 设置错误
@Transactional 注解属性 rollbackFor 设置错误
同一个类中方法调用,导致@Transactional失效
异常被catch捕获导致@Transactional失效
数据库引擎不支持事务
如何让你的 bean 在其他 bean 之前加载?
Netty
1. Linux5种IO模型
阻塞IO
非阻塞IO
数据拷贝过程仍是阻塞的
轮询数据是否准备好
用户态轮询
IO多路复用
单线程同时监控多个IO文件描述符是否可以执行IO能力
信号驱动IO
异步IO
信号驱动和异步的区别?
信号驱动是内核通知何时可以开始IO操作
异步是内核通知IO操作何时完成
2. IO多路复用器
select模型
工作原理:
1. 遍历一遍socket列表,得到就绪的socket;
2. 进程被唤醒之后,程序不知道哪些socket接受到了数据,就需要再遍历一遍
1. 遍历一遍socket列表,得到就绪的socket;
2. 进程被唤醒之后,程序不知道哪些socket接受到了数据,就需要再遍历一遍
缺陷
1. 最大并发数限制
2. 效率问题
遍历O(N)
3. 内核/用户态拷贝问题
poll模型
解决了最大并发数限制的问题,其他问题没有解决
效率问题:只管“活跃”的连接
epoll内部使用mmap共享内核和用户部分空间,避免数据来回拷贝
epoll模型、底层原理
EPOLL内核原理极简图文解读
https://www.cnblogs.com/silyvin/p/12145700.html
1. 数据结构
就绪列表Rdlist
双向列表
节点复用的就是红黑树的节点
保存socket的数据结构
红黑树,节点是epitem
2. tcp/ip如何通知epoll对应的io就绪
3. epoll_create/epoll_ctl/epoll_wait
1. 调用epoll_create创建一个epitem对象-- 在内核专属于epoll的高速缓冲区,并在该缓冲区建立红黑树和就绪列表
2. 内核针对读写缓冲区 判断是否可读可写 这个动作与epoll无关。
3.1 epoll_ctl 除了通过add将文件句柄添加到红黑树上之外,
3.2 还向内核注册了该文件句柄的回调函数---内核在检测到读写缓冲就绪时会回调该函数,
3.3 回调函数会将文件句柄放到就绪列表
4. epoll_wait只监控 就绪列表 就可以了,如果就绪列表有文件句柄,就表示该文件可读可写,并返回用户态
2. 内核针对读写缓冲区 判断是否可读可写 这个动作与epoll无关。
3.1 epoll_ctl 除了通过add将文件句柄添加到红黑树上之外,
3.2 还向内核注册了该文件句柄的回调函数---内核在检测到读写缓冲就绪时会回调该函数,
3.3 回调函数会将文件句柄放到就绪列表
4. epoll_wait只监控 就绪列表 就可以了,如果就绪列表有文件句柄,就表示该文件可读可写,并返回用户态
4. 水平触发和边缘触发
水平触发LT(Epoll默认)
边缘触发ET
区别?
水平触发是只要读缓冲区有数据,就会一直触发可读信号,而边缘触发仅仅在空变为非空的时候通知一次
问题
1. epoll为何高效?
2. epoll使用应该注意什么?
3. epoll对比select、poll?
select监控的句柄列表在用户态,每次都需要用户态将句柄列表拷贝到内核态
epoll句柄建立在内核态中,这样就减少了内核和用户态的拷贝
4. epoll为什么不使用b+树?不适用hashmap?
思考
1. 内核接收数据全过程?
进程调度
工作队列
等待队列
Socket对象
包含发送缓冲区
接收缓冲区
等待队列
指向所有等待该事件的进程
中断程序
当网卡接收数据写入内存之后,网卡向中CPU发送中断信号,OS得知有新数据到来,会调用中断程序去处理
进程阻塞为什么不占用CPU资源?
阻塞的进程会添加到等待队列,CPU只会执行工作队列中的进程
2.如何同时监控多个Socket的数据?
3. epoll设计思想?
将等待队里和就绪队列拆分
3. 同步与异步、阻塞与非阻塞
同步IO
程序阻塞到IO操作完成
阻塞IO算是同步IO;非阻塞IO也算是同步IO;同理,IO多路复用和信号驱动也是同步IO
强调的是:主动等待消息通知
异步IO
IO操作不会阻塞当前程序运行
强调的是:异步等待消息通知,通过回调、通知等被动获取信息
同步和异步:强调的是消息通知的方式
阻塞和非阻塞:强调的是等消息时线程的状态
Netty是异步非阻塞
为什么Netty使用NIO而不是AIO?
4. BIO、NIO、AIO
BIO同步阻塞
面向流
Stream单向
一个连接一个线程
NIO同步非阻塞
面向缓冲区
Channel是双向的
AIO异步非阻塞
一个连接一个线程,Client的IO请求要等OS完成之后再通知服务器启动线程去处理
5. 半包问题处理
拆包
应用程序写入的字节>缓冲区的大小
粘包
应用程序写入的字节<缓冲区的大小
如何解决?
- 定长解码器FixedLengthFrameDecoder
行分隔符解码器LineBasedFrameDecoder
自定义分隔符解码器DelimiterBasedFrameDecoder
6. Reactor模型(异步非阻塞IO)
单Reactor单线程
所有的IO操作都在同一个NIO线程上完成
缺点
1个NIO线程同时处理成百上千链路,性能无法支撑
NIO线程负载过重之后,处理速度过慢,会导致大量Client连接超时
单Reactor多线程
和单线程模型的最大区别:
有一个专门的Acceptor线程,处理客户端的TCP连接
IO操作由NIO线程池负责
缺点
在高并发下,单一Acceptor线程处理耗时的认证,会出现性能上的不足
主从Reactor多线程
和多线程模型的区别是
接受连接的不再是单独的NIO线程,而是NIO线程池
MainReactor负责客户端连接和认证,并将请求转给SubReactor
SubReactor负责相应通道的IO读写
Netty线程模型是可配置的,支持Reactor三种模式
7. 三大核心API
Buffer
为什么不用JDK的ByteBuffer?
ByteBuffer长度固定,不能扩容
ByteBuffer只有一个position,不易读写操作
Netty4#ByteBuf
当写空间不足时,动态扩容
读写指针readIndex\writeIndex分离
可转换为ByteBuffer
API从内存分配角度分
堆内存字节缓存区 HeapByteBuf
优点 内存分配和回收快,JVM自动回收
缺点 Socket IO读写多一次内存拷贝,将堆缓冲区复制到内核的Channel缓冲区
直接内存字节缓冲区 DirectByteBuf
缺点:分配和回收慢
优点:减少一次内存拷贝
API从内存回收角度分
基于对象池的ByteBuf
PooledDriectByteBuf
PooledHeapByteBuf
普通ByteBuf
UnpooledDriectByteBuf
UnpooledHeapByteBuf
Netty3#ChannelBuffer
Channel
为什么不用JDK的Channel?
JDK的ServerSocketChannel和SocketChannel没有统一的Channel接口
基于JDK#SPI#Channel的二次开发和新开发一个接口工作量差不多
SocketChannel
NioSocketChannel
OioClientSocketChannel
ServerSocketChannel
NioServerSocketChannel
OioServerSocketChannel
NioSelector
NioServerBoss
NioWorker
其他
ChannelPipeline
Netty3#ChannelUpstreamHandler/ChannelDownstreamHandler
Netty4#ChannelInboundHandler/ChannelOutboundHandler
ChannelHandler
8. demo
java nio
1. 创建 ServerSocketChannel 和业务处理线程池。
2. 绑定监听端口,并配置为非阻塞模式。
3. 创建 Selector,将之前创建的 ServerSocketChannel 注册到 Selector 上,监听 SelectionKey.OP_ACCEPT。
4. 循环执行 Selector.select() 方法,轮询就绪的 Channel。
5. 轮询就绪的 Channel 时,如果是处于 OP_ACCEPT 状态,说明是新的客户端接入,调用 ServerSocketChannel.accept 接收新的客户端。
6. 设置新接入的 SocketChannel 为非阻塞模式,并注册到 Selector 上,监听 OP_READ。
7. 如果轮询的 Channel 状态是 OP_READ,说明有新的就绪数据包需要读取,则构造 ByteBuffer 对象,读取数据
2. 绑定监听端口,并配置为非阻塞模式。
3. 创建 Selector,将之前创建的 ServerSocketChannel 注册到 Selector 上,监听 SelectionKey.OP_ACCEPT。
4. 循环执行 Selector.select() 方法,轮询就绪的 Channel。
5. 轮询就绪的 Channel 时,如果是处于 OP_ACCEPT 状态,说明是新的客户端接入,调用 ServerSocketChannel.accept 接收新的客户端。
6. 设置新接入的 SocketChannel 为非阻塞模式,并注册到 Selector 上,监听 OP_READ。
7. 如果轮询的 Channel 状态是 OP_READ,说明有新的就绪数据包需要读取,则构造 ByteBuffer 对象,读取数据
netty nio
1. 创建 NIO 线程组 EventLoopGroup 和 ServerBootstrap。
2. 设置 ServerBootstrap 的属性:线程组、SO_BACKLOG 选项,设置 NioServerSocketChannel 为 Channel,设置业务处理 Handler。
3. 绑定端口,启动服务器程序。
4. 在业务处理 TimeServerHandler 中,读取客户端发送的数据,并给出响应。
2. 设置 ServerBootstrap 的属性:线程组、SO_BACKLOG 选项,设置 NioServerSocketChannel 为 Channel,设置业务处理 Handler。
3. 绑定端口,启动服务器程序。
4. 在业务处理 TimeServerHandler 中,读取客户端发送的数据,并给出响应。
相比
那么相比 Java NIO,使用 Netty 开发程序,都简化了哪些步骤呢?
1. OP_ACCEPT 的处理被简化,因为对于 accept 操作的处理在不同业务上都是一致的。
2. 在 NIO 中需要自己构建 ByteBuffer 从 Channel 中读取数据,而 Netty 中数据是直接读取完成存放在 ByteBuf 中的。相当于省略了用户进程从内核中复制数据的过程。
3. 在 Netty 中,我们看到有使用一个解码器 FixedLengthFrameDecoder,可以用于处理定长消息的问题,能够解决 TCP 粘包读半包问题,十分方便。
9. NettyServer启动流程
NioServerBoss#openSelector-- 启动线程池
NioServerBoss#bind -- 注册绑定task,task为OP_ACCEPT
AbstractNioSelector#run#processTaskQueue -- poll有task 则for(;;)接收OP_ACCEPT请求
如果有accept请求,重新组装NioAcceptedSocketChannel,注册到NioWorker#register,添加到taskQueue
先执行AbstractNioWorker#process#read,如果是OP_READ,封装fireMessageReceived(channel, buffer)传递给ChannelPipeline
10. 臭名昭著的JDK Epoll BUG
BUG表现
正常情况下,selector.select()操作是阻塞的,只有被监听的fd有读写操作时,才被唤醒
但是,在这个bug中,没有任何fd有读写请求,但是select()操作依旧被唤醒
但是,在这个bug中,没有任何fd有读写请求,但是select()操作依旧被唤醒
BUG原因
selector.select()通常是阻塞的,但是在Linux2.6的版本中,对于中断的Socket的连接,select()会被唤醒返回0,selectedKeys返回空数组
Netty如何解决?
对Selector的select进行计数,如果在某个周期(默认500ms)内触发N(512)次select=0,则认为触发bug
重建rebuildSelector,将SocketChannel注册到新selector上,并关闭原selector
11. 其他细节
大小端模式
netty 高低水位
Netty 版本升级血泪史之线程篇
线程模型差异
epoll的ET和LT模式
12. 实战
内存泄漏场景?
发送队列积压
原因
1. 网络阻塞
2. 接收<发送速率
解决?
设置高低水位机制
如何做内存保护?
链路总数控制
单个缓冲区上限控制
1. 缓冲区容量预分配,不够再扩展
2. 根据协议消息长度创建缓冲区
缓冲区内存释放
NIO消息发送队列长度上限控制
高水位控制
ByteBuf使用注意
非法引用问题:如果被netty释放,继续访问或释放为发生异常
线程并发安全
流程整形与流量控制
整形:加入缓冲区
控制:
线程模型
断链重连
13. Netty4 API
ByteBuf
PooledByteBuf
Unpooled
Channel
Handler
ChannelInboundHandler
ChannelInboundHandlerAdapter
SimpleChannelInboundHandler
ChannelOutboundHandler
ChannelDuplexHandler extends ChannelInboundHandlerAdapter implements ChannelOutboundHandler
内置Handler
IdleStateHandler
分布式
分布式理论基础
CAP理论
含义
三个特性
不可能三角
BASE理论
含义
四个特性
一致性协议
Paxos
按公示问题数可分为
Basic Paxos
Muti Paxos
为了提高效率
角色
Proposer
Acceptor
Learner
推导过程
1. 如果只有1个Proposer会有什么问题?
Q: Proposer宕机的话,整个服务不可用。
2. 那如果多Proposer和多Acceptor如何选定一个Value?
P1:一个Acceptor必须接受他收到的第一个提案
Q: 不同的Proposer分别提出不同的value
P1a:一个提案被选定需要半数以上的Acceptor
暗示:一个Acceptor必须能够接受不止一个提案!
提案=value已不满足,需要重新设计,提案=[提案编号+value]
P2:如果某个value为v的提案被chosen,那么每个编号更高的被选定的提案,value也必须是v
P2a:如果某个value为v的提案被chosen,那么每个编号更高的被Acceptor接受的提案,value也必须是v
对Acceptor接收的提案约束
P2b:如果某个value为v的提案被chosen,那么之后任何Proposer提出编号更高的提案value也必须是v
对Proposer提出提案的约束
Q如何保证? A-P2c
P2c:对于任意N和V,如果提案[N,V]被提出,那么存在一个半数以上的Acceptor集合S,满足以下2个条件中的一个:①S中的每个Acceptor没有接受过编号小于N的提案;②S中接受过的最大编号的提案value为V
paxos算法证明过程
如何保证Paxos算法的活性?
通过选取主Proposer,只有主Proposer才能提出提案
算法流程
阶段1. Prepare请求
a. Proposer 选择一个提案编号 N,然后向半数以上的Acceptor 发送编号为 N 的 Prepare 请求。
b.如果一个 Acceptor 收到一个编号为 N 的 Prepare 请求,且 N 大于该 Acceptor 已经响应过的所有 Prepare 请求的编号,那么它就会将它已经接受过的编号最大的提案(如果有的话)作为响 应反馈给 Proposer,同时该Acceptor 承诺不再接受任何编号小于 N 的提案。
阶段2. Acceptor确认
a.如果 Proposer 收到半数以上 Acceptor 对其发出的编号为 N 的 Prepare 请求的响应,那么它就会发送一个针对[N,V]提案的 Accept 请求给半数以上的 Acceptor。注意:V 就是收到的响应中编号最大的提案的 value ,如果响应中不包含任何提案,那么V 就由 Proposer 自己决定。
b.如果 Acceptor 收到一个针对编号为 N 的提案的 Accept 请求,只要该 Acceptor 没有对编号大于 N 的 Prepare 请求做出过响应,它就接受该提案
ZAB协议
在Leader选举时节点状态
LOOKING
Leader选举阶段
OBSERVING
不参与投票
FOLLOWING
参与投票
LEADING
ZAB协议的四个阶段
1. 选举阶段
2. 发现阶段
3. 同步阶段
只有当Quorum都同步完成,准Leader才会成为真正的Leader
2. ZK可以通过增加节点,提升集群性能吗?
4. 广播阶段
基于Java的ZAB实现
1. Fast Leader Election
2. Recovery Phase 崩溃恢复,需要解决一下2个问题
保证示例1. Leader产生了proposal,发给Follower之后,只有自己COMMIT。新Leader必须保证这个P也必须COMMIT
保证示例2. Leader产生了Proposal,在宕机之前没有Follower看到这个Proposal。则该Server恢复之后也必须丢失这个P
3. Broadcast Phase 消息广播
简化的2PC
简单的2PC不能处理Leader失效的情况,因此增加了Recovery模式
选举步骤
投票
接受选票
处理选票
统计
QA
1. Paxos和Zab的区别?Zab相比Paxos有什么优点?
Paxos的一致性达不到Zab要求看,Paxos不关心请求之间的逻辑顺序
3. Raft和ZAB之间的区别?
Leader选举
一般的选举过程
如果出现split vote, 如何处理?
加入一个已完成选举的集群,怎么发现Leader?
加入过程是否阻塞整个请求?
上一任Leader残留数据如何处理?
残留数据如何处理?
上一轮Leader家私
请求处理流程
一般流程
出现异常会怎么样?
如何保证顺序?
出现脑裂分区会怎么样?
文献
ZooKeeper学习第七期--ZooKeeper一致性原理
zab paper
Raft
文献
Raft 一致性算法论文译文
寻找一种易于理解的一致性算法(扩展版)
分布式事务
0. 理论基础
DTP模型
应用程序AP
Web应用,由ApplicationServer提供事务管理器
非Web应用,可以使用三方事务管理器类库,如Atomkios、JOTM
资源管理器RM
关系型数据库Mysql
消息中间件RabbitMQ
DB、MQ都是RM,由TM协调
JTA规范定义了一个XAResource接口(是RM提供给TM调用的接口),MQ、DB都需要实现,如MysqlXAConnection
事务管理器TM
提供事务声明、事务资源管理、事务上下文传播
通讯资源管理器CRM
TM之间通讯通过这个组件完成
通讯协议CP
JTA规范
JTA事务API
JTS事务服务
JTA规范在DTP模型之上,有多了Application Server
如:jboss、weblogic、websphere
并不是所有的容器都实现了JTA规范,如Tomcat并没有实现JTA规范,因此并不能提供事务管理器功能;
TCC事务模型 VS DTP事务模型
TCC两阶段和XA两阶段的区别和优缺点?
XA事务的优缺点
XA是资源层面,强一致性的,在两阶段提交过程中会一直持有锁
XA要求每个RM通过#串行化隔离级别#来保证全局一致性。串行化会导致本来不加锁的select快照读都加上锁
优点(最大作用):普适性,可以横向扩展,提升非热点数据的并发性能
缺点:并不能提升热点数据性能,分布式事务的最高性能 趋近于单机的本地事务
TCC事务的优缺点
TCC是应用层面,最终一致性,不会一直持有锁
多个独立的本地事务,属于补偿事务
优点:1、有效避免XA 2PC占用锁资源过长导致的性能问题
2、方便横向扩展业务资源
2、方便横向扩展业务资源
缺点:业务改造成本高,原本一个接口需要改造成try\confirm\cancel三个接口
2PC和3PC的区别?
理论落地实践
基于XA协议的2PC
理论层面:2PC、所有参与者必须实现prepare、commit、rollback接口
开源实现:Atomikos
http://www.tianshouzhi.com/api/tutorials/distributed_transaction/386
存在问题:
1、#2协调者挂了,事务悬挂;
2、#2参与者挂了,其他参与者是commit还是rollback?
3、2PC的主要实现都在DB(依赖DB是否支持XA协议),而微服务通常是在服务层面实现一致性;
1、#2协调者挂了,事务悬挂;
2、#2参与者挂了,其他参与者是commit还是rollback?
3、2PC的主要实现都在DB(依赖DB是否支持XA协议),而微服务通常是在服务层面实现一致性;
3PC没有彻底解决上面的问题
本地消息表
核心思想:将远程分布式事务拆分成一系列的本地事务
案例
#1. ServiceA 用户a扣款1W,通过本地事务插入消息到本地事务表
#2.通知ServiceB给用户b加上1W
那么问题来了,如何通知对方呢?
采用方式
Producer准备一张消息表,update DB和insert msg存在一个事务里面
定时任务轮询消息表,不断重传发送
Consumer准备一张判重表,实现业务幂等;
问题:消费成功、insert失败了怎么办?
---可以使用手动确认消费;
---可以使用手动确认消费;
适用场景
常见问题
需要解决的问题:先updateDB,后发mq?还是先发mq,后update DB?
结论:这2个操作都不是原子的,无论先操作谁都是有问题的
切记:MQ不能放事务里面,否则会导致长事务
缺点
1. 定时任务需要频繁轮询,占用DB连接,不适合高并发场景
2. 和业务耦合
基于MQ
支持事务MQ
基本流程
#第一阶段:prepared消息,会拿到消息地址
#第二阶段:执行本地事务
#第三阶段:通过第一阶段的消息地址拿到之前的消息,然后修改状态。消费者就可以拿到消息。
MQ消息层面的最终一致性
相比上面方案,其实就是把”扫描消息表“这个事变成消息中间件帮忙做了
优点:1.消息数据独立存储,与业务系统解耦;2. 业务系统接入成本低
缺点:1. 增加消息系统的维护成本
适用场景:1. 对时间敏感度比较低的场景,比如:会员注册服务和邮件发送服务之间
支持非事务MQ
TCC
通用性TCC
从服务是同步调用,会影响主服务决策
适用场景:执行时间确定且比较短的场景,如交易与支付、账务
异步确认型TCC
补偿型TCC
与通用型的区别是:通用型TCC需要提供Try\Confirm\Cancel三个接口;而补偿型只用DO和Compensate
DO:执行正向业务逻辑
Compensate:执行抵消逻辑
缺点:业务在一阶段就执行了完整的逻辑,在回滚时可能存在失败场景,需要人工介入
适用场景:并发较少或者需要与外部交互的场景,如机票预订
案例
蚂蚁金服分布式事务实践解析
https://zhuanlan.zhihu.com/p/112983373
常见问题
TCC如何解决2PC的问题?
Try成功之后,如果Confirm失败、或者Cancel失败,都是不同重试解决,这就要求接收方必须实现幂等;
TCC异常问题
空回滚
Try未执行,Cancel执行了
什么情况会造成空回滚?
Try超时(丢包)
幂等
怎么解决重复执行的幂等问题?
悬挂
什么情况会造成悬挂?
怎么实现防悬挂?
TCC异常控制?
Confirm不允许空回滚
Cancel允许空回滚
Seata
AT/FMT模式
基本思想
二阶段托管由框架执行,
没有隔离性
原理
Undo Log
保存执行前的快照
Redo Log
一阶段SQL执行之后,把新的快照存储到redo log
行锁表
记录表的主键和名称,用于并发控制
常见问题
会不会有脏读?如何解决?
幻读问题
基于冲正模型实现Saga
核心思想:拆分长事务为多个短事务,Soga工作流引擎负责协调
使用前提
对服务的要求:1. 幂等;2.补偿可交换执行
Saga提供ACD保证
原子性:通过Saga协调器完成
一致性:本地事务+saga log
隔离性:saga不保证
持久性:saga log
常见问题
隔离性带来的脏读
业务层加锁
Session层隔离保证串行化操作
业务层用预扣资金隔离
业务层通过select for update当前读加锁保证隔离
案例
华为:Seata 长事务解决方案 Saga 模式
https://www.sofastack.tech/blog/sofa-channel-10-retrospect/
Saga分布式事务解决方案与实践
http://servicecomb.apache.org/cn/docs/distributed-transactions-saga-implementation/
基于服务的分布式事务(下篇)
http://servicecomb.apache.org/cn/docs/distributed-transaction-of-services-2/
文献
分布式事务解决方案与适用场景分析https://zhuanlan.zhihu.com/p/34232350
分布式锁
分布式锁需要考虑的几个点?
可重入or不可重入
公平or非公平
阻塞or非阻塞
独占or非独占
基于Redis单节点
SET lock_key random_value NX PX 5000
缺点:不具备可重入性
Redission
可重入锁
用hash数据结构。key=锁名称,hkey=随机字符串:线程ID,hvalue=自增Int
重入值通过hvalue大小来说明
LUA脚本:1. 用exist判断,如果不存在,则加锁成功;
2. 用hexist判断,如果存在并且是当前线程,说明是重入锁,加锁成功;
3. 如果不是当前线程,说明是其他线程持有说,加锁失败;
2. 用hexist判断,如果存在并且是当前线程,说明是重入锁,加锁成功;
3. 如果不是当前线程,说明是其他线程持有说,加锁失败;
Redission同时支持RedLock
用set数据结构实现
源码解析
基于Redis多节点的RedLock
高可用?分布式环境下会遇见的问题?
1. 崩溃恢复
解决方案:崩溃延迟重启
2. 时钟跳跃
解决方案:
1. 采用小步快跑方式,多次修改,每次更新时间量小
2. 通过阈值来做判断
1. 采用小步快跑方式,多次修改,每次更新时间量小
2. 通过阈值来做判断
3. GC STW、缺页故障、网络延迟
antirze认为redlock做了一些微小的工作,但是没办法完全避免,其他分布式锁方案也没有办法
ZK分布式锁也解决不了这个问题
RedLock具体算法
RedLock是建立在一个Time可信模型上
1. 获取开始时间;
2.去各节点获取锁;
3.再次获取时间;
4. 计算获取锁的时间,检查获取锁的时间是否小于锁的过期时间;
5. 如果小于,持有锁
2.去各节点获取锁;
3.再次获取时间;
4. 计算获取锁的时间,检查获取锁的时间是否小于锁的过期时间;
5. 如果小于,持有锁
1-3步之间发生了阻塞,RedLock可以感知锁已经过期,但是#4之后发生阻塞怎么办?
基于Redis的分布式锁到底安全吗(下)?
基于DB
基于DB排他锁(悲观锁)
1. 会不会阻塞?
其他事务如果select... for update失败,则会一直阻塞
阻塞也会带来线程池撑爆风险
2. 是否可重入?
不可重入
缺点
1. 使用不当会死锁
2. 性能上有额外开销
3. 加锁列如果不加索引会导致表锁
基于DB表记录的唯一性索引
瑜伽案例
缺点
锁没有失效时间,如果解锁失败会一直留在数据库中;可以用定时任务去清理
不可重入,同一个线程在释放之前无法再次获取锁
主从同步延迟?
优点
非阻塞式
基于DB乐观锁
优点
不依赖DB锁
缺点
并发小的时候,有少量请求会失败;大促、秒杀场景下,会有大量请求作用于同一条行锁,对数据库有很大的写压力
适用场景
并发量不高,写不频繁的场景
缺点
1. 在数据量比较少的时候,会进行表锁,而不是行锁
2. 需要占用DB连接
3. 依赖数据库,避免单点,如果有主从同步的话,还会有数据不一直情况
基于ZK
独占锁
容易引发羊群效应
共享锁
1. Client创建create类似/lockpath/{hostname}-读写类型-序号的临时有序节点
2. Client创建完之后,通过getChildren获取节点下所有子节点,并且对所有节点注册Watch监听。
3. 确定本次创建的节点序号在所有节点中的顺序
a. 对于读请求,
ⅰ. 如果没有比自己更小的子节点,后者比自己更小的子节点都是读请求,则获取锁
ⅱ. 如果比自己更小的节点中有写请求,那么等待;
b. 对于写请求,
ⅰ. 如果自己不是最小的节点,则等待;
4. 等待监听通知后,重复步骤1
2. Client创建完之后,通过getChildren获取节点下所有子节点,并且对所有节点注册Watch监听。
3. 确定本次创建的节点序号在所有节点中的顺序
a. 对于读请求,
ⅰ. 如果没有比自己更小的子节点,后者比自己更小的子节点都是读请求,则获取锁
ⅱ. 如果比自己更小的节点中有写请求,那么等待;
b. 对于写请求,
ⅰ. 如果自己不是最小的节点,则等待;
4. 等待监听通知后,重复步骤1
羊群效应?
改进后的分布式锁:
1. Client调用create创建一个/lockpath/{hostname}-读写类型-序号的临时有序节点。
2. Client调用getChildren获取所有子节点列表,这里不注册Watch监听
3. 确定本次创建的节点序号在所有节点中的顺序
a. 如果是读请求,向比自己小的写请求注册Watch监听
b. 如果是写请求,向比自己小的最后一个节点注册Watch监听
4. 等待监听后,继续执行步骤2。
1. Client调用create创建一个/lockpath/{hostname}-读写类型-序号的临时有序节点。
2. Client调用getChildren获取所有子节点列表,这里不注册Watch监听
3. 确定本次创建的节点序号在所有节点中的顺序
a. 如果是读请求,向比自己小的写请求注册Watch监听
b. 如果是写请求,向比自己小的最后一个节点注册Watch监听
4. 等待监听后,继续执行步骤2。
基于curator实现
ZK是如何检测Client已经崩溃?
依靠心跳session来维护锁的持有状态
分布式锁选择?
从性能角度
缓存》ZK》=数据库
从高可用角度
ZK》缓存》数据库
从复杂角度(从低到高)
ZK》缓存》数据库
消息队列
RabbitMQ
延迟队列
优先级队列
如何保证消息的可靠性?消息不被丢失
Publisher
事务机制(同步,不推荐)
缺点:吞吐量会下降
事务机制和Confirm机制的最大区别是
事务机制是同步的,提交事务之后会阻塞在那里
Confirm机制是异步的
Confirm机制(异步,推荐)
MQ
开启消息持久化
1.1 交换器Exchange持久化
1,2 队列Queue持久化
1.3 消息Message持久化
Consumer
关闭MQ自动ACK机制
如何保证高可用?
基于主从(非分布式,Kafka是分布式)
普通集群模式--无高可用性
原理:queue存放在单一实例上
优点:提高吞吐
缺点1:单节点宕机,数据可能丢弃
可开启持久化解决
缺点2:当Consumer连接的节点无数据是,会从其他节点拉取,存在网络消耗
镜像集群模式(高可用)
问题1:所有机器都包含Queue的全部数据,带来带宽和性能的消耗
问题2:不是分布式,单节点负载很重
如何解决消息队列的延时问题和过期失效问题?
延迟消息原理?
RabbitMq是如何实现消息路由的?
Kafka
如何保证高可用?
副本机制
kafka一个Topic的数据分布在多个节点上;RabbitMQ一个Queue的数据存放在一个节点里面;
如何将所有Replica均匀分布到整个集群?
ACK前需要保证有多少个备份?
如何处理所有Replica都不工作?
如何保证消息的可靠性?消息不被丢失
Publisher弄丢了?
MQ弄丢了?(Leader挂了,Follower还没来得及同步数据的情况下)
1.给Topic设置副本数大于1replication.factor
2.设置min.insync.replicas大于1,让Kafka感知到至少有一个follower和自己保持联系
3.在 producer 端设置 acks=all:这个是要求每条数据,必须是写入所有 replica 之后,才能认为是写成功了。
4.在 producer 端设置 retries=MAX(很大很大很大的一个值,无限次重试的意思):这个是要求一旦写入失败,就无限重试,卡在这里了。
Consumer弄丢了?
1. 关闭auto commit
2. 幂等
kafka的三种消息投递语义
exactly once
事务
at least once
at most once
ZK如果挂了,会不会影响Kafka?//ZK在Kafka中的作用?
broker注册
topic注册
生产者负载均衡
消费者负载均衡
分区和消费者的关系
消费者注册
Kafka高性能原因?(Kafka如何实现高性能IO?)
顺序写+页缓存
零拷贝
定时异步批量写
消费模型
Push
由消息代理标记消息处理,无法保证消息处理语义
Poll
consumer可以控制消费速率
Producer发布消息
写入方式
顺序写
消息路由
指定partition,直接使用
未指定partition,指定key,则按key的value进行hash
都未指定,则轮询一个partition
Kafka Consumer分区分配策略有哪些?
一、自动分配策略
1.Range分配
-将每个消费者所负责的分区范围计算出来,分配给消费者
2.Round-robin分配
-将所有分区平均分配给所有消费者
3.Sticky分配
-将同一个消费者分配到同一个分区,只有当该消费者失效时才会重新分配
二、手动分配策略
1.Assign分配
-消费者通过调用assign()方法来手动指定消费哪些分区
2.Subscribe分配
-消费者通过调用subscribe()方法来订阅感兴趣的主题,由Kafka自动分配分区
3.SubscribePattern分配
-消费者通过调用subscribe(Pattern)方法来订阅符合正则表达式的主题,由Kafka自动分配分区
三、自定义分配策略
1.实现org.apache.kafka.clients.consumer.ConsumerRebalanceListener接口
-监听分区的重新分配事件
2.实现org.apache.kafka.clients.consumer.CoordinatorAware接口
-可以在分区分配前后进行一些操作,例如获取当前消费者组的消费者列表
3.调用consumer.subscribe(topic,newCustomPartitionAssignor())
-将自定义的分配策略传入subscribe()方法中
Kafka的Rebalance机制是什么?触发时机是什么?
触发时机:
消费者组成员变化(加入或退出)
消费者订阅的Topic或者分区发生变化(新增或减少)
消费者心跳超时
Rebalance过程-消费者组协调器发起Rebalance请求-消费者组协调器将消费者分配给新的分区-消费者开始消费新分配的分区
Rebalance的影响-消费者停止消费-消费者重新分配分区-消费者重复消费-消费者消息丢失
Rebalance的优化-增加分区数,减少Rebalance次数-均匀分配分区,减少Rebalance时间-启用自动提交,减少重复消费-增加消息的TTL,减少消息丢失
为什么Kafka没办法100%保证消息不丢失?
RocketMQ
消息发送和消费模式有哪些?它们有什么区别?
消息重试机制是怎样的?
消息存储机制是怎样的?
如何实现分布式事务?
消息顺序性如何保证?
如何保证消息不丢失?
负载均衡是怎么做的?
延迟消息如何使用?
集群消费和广播消费有什么区别?
图解 RocketMQ 核心原理
面试题
如果让你写一个消息队列,该如何进行架构设计啊?说一下你的思路。
秒杀时如何处理每秒上万次的的下单请求?
如何降低消息队列中消息的延迟?
如何保证不被重复消费?如何处理重复消息?
消息队列满了之后如何处理?消息积压如何处理?有几百万消息积压几个小时,如何解决?
分析原因
生产者突然大量发布消息
消费者消费失败
消费这出现性能瓶颈
消费者直接挂掉
如何解决?
消费端扩容
服务降级-- 快速失败,不一定适用业务场景
分区和消费者的对应关系?
顺序消费? -- https://www.zhihu.com/question/30195969
RocketMQ与Kafka中如何实现事务?
Dubbo
负载均衡策略
随机(+加权)
轮询(+加权)
最少连接数
源地址hash
泛化调用原理?
线程派发策略
派发策略
默认all
direct
message
execution
connection
业务线程池
fix 固定大小线程池 core200 max 200 队列synchronus 回绝Abort
cache 缓存线程池,空闲一分钟自动删除
limit:池中的线程数只增长不会收缩,目的是为了避免收缩时突然大流量引起的性能问题。
eager:当core满了,当currentThread<max,则创建线程;当currentThread>max,放入阻塞队列,放不下pa
支持协议
hession
protocol
kryo
fst
json
jdk
注册中心
注册中心技术选型?
一致性协议
架构
服务暴露过程
流程图
文献
dubbo剖析:一 服务发布
过程
0. Spring在启动的时候,会通过DefaultNamespaceHandlerResolver解析meta-inf/spring.handlers 1. 通过NamespaceHandlerSupport和DubboBeanDefintionParser实现解析XML标签为ServiceBean
2. Spring容器加载完成,会发布ContextRefreshEvent事件,ServiceBean监听该事件然后调用export()暴露服务,将Bean的属性转成URL参数。
3. ServiceBean通过JavassistProxyFactory创建Invoker;
4. Protocol在export之前会调用拦截器,如果是register会去注册中心注册服务;
5. 最终有ServiceConfig维护所有发布的Exporter与服务URL到本地内存
2. Spring容器加载完成,会发布ContextRefreshEvent事件,ServiceBean监听该事件然后调用export()暴露服务,将Bean的属性转成URL参数。
3. ServiceBean通过JavassistProxyFactory创建Invoker;
4. Protocol在export之前会调用拦截器,如果是register会去注册中心注册服务;
5. 最终有ServiceConfig维护所有发布的Exporter与服务URL到本地内存
服务引用
流程图
文献
Dubbo原理和源码解析之服务引用
dubbo剖析:二 服务引用
https://www.jianshu.com/p/7f3871492c71
过程
1、引用配置及配置初始化
该部分以Spring配置及ReferenceBean为入口,主要在ReferenceConfig中进行。
ReferenceConfig依赖RegistryProtocol完成了 "服务引用者注册"、"服务提供者订阅"和"Invoker创建" 的工作;
ReferenceConfig依赖JavassistProxyFactory完成了 "代理对象生成" 的工作;
2、注册中心订阅 & Invoker生成与获取
该部分主要由RegistryDirectory和FailfastCluster实现。
通过ReferenceConfig调用RegistryDirectory的subscribe方法,触发了对服务提供者url的订阅及监听,在监听过程中RegistryDirectory借助DubboProtocol完成了Invoker的创建工作,并保存了服务引用url和Invoker的关系;
通过ReferenceConfig调用FailfastCluster的join方法,完成了对Invoker对象的获取;
3、生成代理对象
该部分主要由JavassistProxyFactory完成。
以ReferenceConfig调用JavassistProxyFactory的getProxy方法为入口,传入Invoker;
新创建了InvokerInvocationHandler,并使用dubbo自己的动态代理工具Proxy最终生成代理对象T ref;
该部分以Spring配置及ReferenceBean为入口,主要在ReferenceConfig中进行。
ReferenceConfig依赖RegistryProtocol完成了 "服务引用者注册"、"服务提供者订阅"和"Invoker创建" 的工作;
ReferenceConfig依赖JavassistProxyFactory完成了 "代理对象生成" 的工作;
2、注册中心订阅 & Invoker生成与获取
该部分主要由RegistryDirectory和FailfastCluster实现。
通过ReferenceConfig调用RegistryDirectory的subscribe方法,触发了对服务提供者url的订阅及监听,在监听过程中RegistryDirectory借助DubboProtocol完成了Invoker的创建工作,并保存了服务引用url和Invoker的关系;
通过ReferenceConfig调用FailfastCluster的join方法,完成了对Invoker对象的获取;
3、生成代理对象
该部分主要由JavassistProxyFactory完成。
以ReferenceConfig调用JavassistProxyFactory的getProxy方法为入口,传入Invoker;
新创建了InvokerInvocationHandler,并使用dubbo自己的动态代理工具Proxy最终生成代理对象T ref;
1. DubboBeanDefintionParser解析reference;
SPI
和JDK SPI的区别?
微内核+插件
Adaptive和Activate的区别?
Adaptive注解在类上和方法上有什么区别?
Activate:自动激活条件的标记
Adaptive注解在类上和方法上有什么区别?
在类上:该类会直接作为默认实现
在方法上:接口方法要有一个URL入参,根据URL入参选择具体的实现调用
集群容错策略?(5个)
failover
如果出现故障,则切换备份
默认
常用与读模式
failfast
如果出现故障,则立即报错
常用于非幂写等
failsafe
如果出现故障,则可以忽略
常用与不重要的接口,如记录日志
failback
失败了后台自动记录请求,然后定时重发
适用于写消息队列
forking
并行调用多个Provider,只要一个成功就返回
适用于实时性比较高的读操作,但是会浪费更多资源
broadcast
广播所有实例,有一个报错,则抛出异常
available
只调用一个可用的实例,如果没有则抛出异常
mergeable
合并结果集
面试题
Dubbo服务一次调用过程是怎么样的?Consumer直接调用的Provider的?
Provider有变化,如何通知到Consumer? 一定要注册中心吗?
Provider要下线,还有服务在处理怎么办?
Dubbo用的是Spring的showdownHook;Spring使用的JVM的shudownHook;
kill -9是强制下线
kill -15是优雅下线
Elasticsearch
常见面试题
在并发情况下,Elasticsearch 如果保证读写一致?
ES的写入过程是怎么样的?
分布式系统的6大特性
可靠性/持久性
一致性
原子性
隔离性
实时性
性能
分库分表
1. 为什么需要分库分表?
为什么要分库?
磁盘存储
单机并发连接有限制。 to many connectiions
为什么要分表?
索引高度增加,查询变慢。
2. 什么时候考虑分库分表?
阿里巴巴的《Java开发手册》:单表行数超过500万行或者单表容量超过2GB,才推荐进行分库分表。
一般什么类型业务表需要才分库分表?
通用是一些流水表、用户表等才考虑分库分表,如果是一些配置类的表,则完全不用考虑,因为不太可能到达这个量级。
3. 如何选择分表键?
4. 非分表键如何处理?
4.1 同步到ES或者HBase,通过ES查询
4.2 基因法。比如非分表键可以解析出分表键出来,比如常见的,订单号生成时,可以包含客户号进去,通过订单号查询,就可以解析出客户号。但是这个场景除外,手机号似乎不适合冗余userId。
5. 分表策略如何选择?
5.1 range范围
5.2 hash取模
5.3 一致性hash????
5.4 range+hash
6. 如何避免热点数据倾斜?(热点数据)
参照5.4
7. 分库后,事务问题如何解决?
参照分布式事务解决方案。
8. 跨节点join 关联问题
字段冗余
全局表
9. order by\group by等聚合函数问题
10. 分页问题
1. 全局视野法。
2. 业务折中法-禁止跳页
11. 分布式ID
12. 如何评估分库、分表数量?
对于MySQL来说的话,一般单库超过 5千万记录, DB的压力就非常大了。所以分库数量多少,需要看单库处理记录能力。
13. 分表要停服吗?不停服怎么做?
面试题
如何做到永不迁移数据和避免热点?
如何保证接口的幂等性?常见的实现方案有哪些?
计算机网络
一、网络体系结构
OSI的七层协议(概念理论上的分层)
应用层
表示层
会话层
传输层
网络层
数据链路层
物理层
TCP/IP的四层协议(实际使用分层)
应用层(HTTP、FTP、Telnet、DNS、SMTP)
传输单位:报文/消息Message
传输层(TCP或UDP)
传输单位:段Segment
网络层(IP、ICMP)
传输单位:包Packet
网络接口层
传输单位:帧 Frame
总结
应用层是工作在操作系统中的用户态,传输层及以下则工作在内核态。
二、HTTP
1. HTTP技术
HTTP常见状态码有哪些?
1XX:指示信息,表示请求已接收,还需要后续操作。实际使用较少。
2XX 成功
200 成功
204 成功,但response无body数据
206 部分成功过
3XX 重定向
301 永久重定向
302 临时重定向
304 缓存重定向
301和302区别?
4XX 客户端错误
400 bad request 请求报文有误
401 unanthorized 缺失或错误认证
403 forbidden 服务器禁止访问资源
404 not found 请求资源未找到
5XX 服务端错误
501 请求功能不支持
502 bad gateway
503 service unavailable 服务器忙
504 gateway timeout 网关超时
HTTP 常见字段有哪些?
host
accept:客户端请求的时候,可以使用 Accept 字段声明自己可以接受哪些数据格式
Accept: /
accept-encoding:客户端在请求时,用 Accept-Encoding 字段说明自己可以接受哪些压缩方法。
Accept-Encoding: gzip, deflate
user-agent:告诉服务器,client用的os、浏览器版本
connection:最常用于客户端要求服务器使用「HTTP 长连接」机制。
HTTP 长连接的特点是,只要任意一端没有明确提出断开连接,则保持 TCP 连接状态。
content-length
content-type
Content-Type: text/html; Charset=utf-8
content-encoding
HTTP 缓存技术
HTTP缓存有哪些实现方式?
什么是强制缓存?
什么是协商缓存?
GET和POST有什么区别?都是安全和幂等的吗?
2. HTTP演变、各版本特性
HTTP/1.0
无状态
明文
不安全
HTTP/1.1
相比HTTP/1.0提升了什么?
连接方式 : HTTP/1.0 为短连接,HTTP/1.1 支持长连接。
状态响应码 : HTTP/1.1 中新加入了大量的状态码,光是错误响应状态码就新增了 24 种。
缓存机制 : 在 HTTP/1.0 中主要使用 Header 里的 If-Modified-Since,Expires 来做为缓存判断的标准。
带宽:HTTP/1.0 中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP/1.1 则在请求头引入了 range 头域,它允许只请求资源的某个部分,即返回码是 206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。
Host 头(Host Header)处理 :HTTP/1.1 引入了 Host 头字段,允许在同一 IP 地址上托管多个域名,从而支持虚拟主机的功能。而 HTTP/1.0 没有 Host 头字段,无法实现虚拟主机。
HTTP/2.0
做了哪些优化?
IO多路复用。HTTP/2.0 在同一连接上可以同时传输多个请求和响应(可以看作是 HTTP/1.1 中长链接的升级版本)。HTTP/1.1 则使用串行方式,每个请求和响应都需要独立的连接。这使得 HTTP/2.0 在处理多个请求时更加高效,减少了网络延迟和提高了性能
二进制帧。HTTP/2.0 使用二进制帧进行数据传输,而 HTTP/1.1 则使用文本格式的报文。二进制帧更加紧凑和高效,减少了传输的数据量和带宽消耗。
头部压缩。HTTP/1.1 支持Body压缩,Header不支持压缩。HTTP/2.0 支持对Header压缩,减少了网络开销。
服务器推送。HTTP/2.0 支持服务器推送,可以在客户端请求一个资源时,将其他相关资源一并推送给客户端,从而减少了客户端的请求次数和延迟。而 HTTP/1.1 需要客户端自己发送请求来获取相关资源。
HTTP2.0的多路复用和HTTP1.X中的长连接复用有什么区别
HTTP/3.0
做了哪些优化?
传输协议:HTTP/2.0 是基于 TCP 协议实现的,HTTP/3.0 新增了 QUIC(Quick UDP Internet Connections) 协议来实现可靠的传输,提供与 TLS/SSL 相当的安全性,具有较低的连接和传输延迟。你可以将 QUIC 看作是 UDP 的升级版本,在其基础上新增了很多功能比如加密、重传等等。HTTP/3.0 之前名为 HTTP-over-QUIC,从这个名字中我们也可以发现,HTTP/3 最大的改造就是使用了 QUIC。
连接建立。HTTP/2.0 需要经过经典的 TCP 三次握手过程(一般是 3 个 RTT)。由于 QUIC 协议的特性,HTTP/3.0 可以避免 TCP 三次握手的延迟,允许在第一次连接时发送数据(0 个 RTT ,零往返时间)。
队头阻塞。HTTP/2.0 多请求复用一个 TCP 连接,一旦发生丢包,就会阻塞住所有的 HTTP 请求。由于 QUIC 协议的特性,HTTP/3.0 在一定程度上解决了队头阻塞(Head-of-Line blocking, 简写:HOL blocking)问题,一个连接建立多个不同的数据流,这些数据流之间独立互不影响,某个数据流发生丢包了,其数据流不受影响(本质上是多路复用+轮询)
错误恢复。
安全性。
3. HTTPS技术
HTTPS是如何建立连接的?期间交互了什么?
HTTP和HTTPS有哪些区别?HTTPS解决了HTTP的哪些问题?
如何避免被中间人抓取数据?
对称加密与非对称加密
三、TCP、UDP
TCP
1. 连接建立
TCP 是面向连接的、可靠的、基于字节流的传输层通信协议。
TCP三次握手过程是怎么样的?
第三次握手是可以携带数据的,前两次握手是不可以携带数据的,这也是面试常问的题
为什么是3次握手?不是2次、4次?
「两次握手」:无法防止历史连接的建立,会造成双方资源的浪费,也无法可靠的同步双方序列号;
「四次握手」:三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数。
什么是 SYN 攻击?如何避免 SYN 攻击?
SYN 攻击方式最直接的表现就会把 TCP 半连接队列打满,这样当 TCP 半连接队列满了,后续再在收到 SYN 报文就会丢弃,导致客户端无法和服务端建立连接。
如何避免 SYN 攻击
调大 netdev_max_backlog
增大 TCP 半连接队列
开启 net.ipv4.tcp_syncookies
减少 SYN+ACK 重传次数
什么是 TCP 半连接和全连接队列
TCP协议如何保证可靠性?
校验和。目的是检测数据传输过程中的任何变化。
停止等待协议。基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。
无差错情况;
出现差错情况(超时重传);
确认丢失|确认迟到;
自动重传ARQ协议。指超过一段时间仍没有收到确认,就重传之前发送过的分组;每发送完一个分组,就设置一个超时计时器;
连续ARQ协议 (提高信道利用率)。发送方维持一个发送窗口,凡是在这个窗口之内的分组都可以连续发送出去,不需要停止等待对方确认。
流量控制。TCP使用滑动窗口机制实现流量控制;为了控制发送方速率,保证接收方来得及接收;接收方可以通过发送ACK报文中的窗口字段,可以控制发送方窗口大小,从而影响发送方速率;
拥塞控制
流量控制和拥塞控制的区别
拥塞控制是防止过多的数据注入到网络中,可以使网络中的路由器或链路不致过载,是一个全局性的过程。
流量控制是点对点通信量的控制,是一个端到端的问题,主要就是抑制发送端发送数据的速率,以便接收端来得及接收。
经典的拥塞算法有哪几个部分?
慢开始、拥塞避免、快重传、快恢复;
拥塞避免的实现技术?
2. 连接断开
TCP四次挥手过程是怎么样的?
主动关闭连接的,才有 TIME_WAIT 状态。
2次握手会有什么问题?
三次握手主要目的是:防止出现请求超时而导致脏连接;
如果两次握手:如果有超时的请求,而Server返回ACK确认报文,但是Client因为是超时的请求而没有处理,就会导致Server白白建立连接;
TCP四次握手可以变成3次吗?
为什么挥手要4次?
关于TIME_WAIT的问题
为什么 TIME_WAIT 等待的时间是 2MSL?为什么不是1MSL?
为什么需要 TIME_WAIT 状态?
防止历史连接中的数据,被后面相同四元组的连接错误的接收
保证「被动关闭连接」的一方,能被正确的关闭
服务器出现大量 TIME_WAIT 状态的原因有哪些?(TIME_WAIT 过多有什么危害?)
如何优化 TIME_WAIT?
能不能在发送完ACK之后TIME_WAIT就直接进入CLOSE状态?
不能。这是为了TCP协议的可靠性。
由于网络的不可靠性,Client的ACK可能会发送失败,那么Server就会重新发送一次FIN,如果Client还在TIME_WAIT里面的话,就还会再重新发送一次ACK,从而保证可靠性。
关于CLOSE_WAIT的问题
CLOSE_WAIT会导致什么问题?
CLOSE_WAIT的危害在于,在一个进程上打开的文件描述符超过一定数量,(在linux上默认是1024,可修改),新来的socket连接就无法建立了,因为每个socket连接也算是一个文件描述符
服务器出现大量 CLOSE_WAIT 状态的原因有哪些?
如果一直保持在CLOSE_WAIT状态,那么只有一种情况,就是在对方关闭连接之后服务器程序自己没有进一步发出FIN信号,一般原因都是TCP连接没有调用关闭方法
如何解决?
通过keepalived让OS自动回收;
排查代码;
如果已经建立了连接,但是客户端突然出现故障了怎么办?
3. 面试题
TCP重传、滑动窗口、流量控制、拥塞控制
如何优化TCP
如何理解TCP是面向字节流的协议?
TCP协议有什么缺陷?
TCP协议如何保证可靠性?
UDP
如何基于UDP协议实现可靠传输?
建立应用层协议,通过重传机制来实现数据的可靠传输。例如,TFTP(Trivial File Transfer Protocol)和DNS(Domain Name System)协议都是基于UDP协议的应用层协议,它们使用重传机制来确保数据的可靠传输。
使用ACK机制。发送端发送数据时,接收端需要返回一个确认(ACK)消息,表示已经收到了数据。如果发送端在一定时间内没有收到ACK消息,就会认为数据丢失,触发重传机制。这种方法可以确保数据的可靠传输,但会增加额外的网络流量。
使用序号机制。发送端给每个数据包分配一个唯一的序号,接收端在接收到数据包后需要返回一个确认消息,并将序号作为回应。发送端在一定时间内没有收到确认消息,就会触发重传机制。这种方法可以确保数据的可靠传输,并可以处理乱序到达的数据包。
使用超时机制。发送端在发送数据包后会设定一个超时时间,如果在超时时间内没有收到ACK或确认消息,就会触发重传机制。这种方法可以应对网络延迟较大的情况,但会增加额外的延迟。
需要注意的是,UDP协议本身并没有提供可靠传输的机制,因此在实现可靠传输时需要注意应用层协议的设计和实现。
四、IP
常见面试题
键入网址到网页显示,期间发生了什么?
DNS查找过程
Linux的5种网络模型是什么?
BIO
当调用一次 channel.read 或 stream.read 后,会由用户态切换至操作系统内核态来完成真正数据读取,而读取又分为两个阶段,分别为:
等待数据阶段
复制数据阶段
用户线程进行 read 操作时,需要等待操作系统执行实际的 read 操作,此期间用户线程是被阻塞的,无法执行其他操作
NIO
IO复用模型
信号驱动IO模型
AIO
同步IO、异步IO、阻塞IO、非阻塞IO之间的区别和联系?
回答1
同步I/O和异步I/O描述的是一个程序如何等待一个I/O操作完成。同步I/O是指程序会一直等待I/O操作完成后才会继续执行下一步操作,而异步I/O则是在I/O操作完成之前,程序可以继续执行其他操作。
阻塞I/O和非阻塞I/O则是描述操作系统内核如何处理I/O请求的方式。阻塞I/O是指应用程序会一直被阻塞,直到I/O操作完成,而非阻塞I/O是指应用程序不会被阻塞,而是立即返回一个错误码或者空值,告诉应用程序I/O操作未完成,应用程序可以立即执行其他操作。
通常情况下,同步I/O和阻塞I/O是一起使用的,而异步I/O和非阻塞I/O则是一起使用的。例如,同步I/O通常使用阻塞I/O方式,因为应用程序需要等待I/O操作完成。而异步I/O通常使用非阻塞I/O方式,因为应用程序不需要等待I/O操作完成。
回答2
同步IO和阻塞IO是一回事吗?
同步I/O和阻塞I/O并不是完全一回事,尽管它们经常被混淆和一起讨论。
操作系统
一、计算机结构
冯诺依曼模型
计算机五大核心组成部分:控制器、运算器、存储器、输入、输出
内存、CPU、总线、输入、输出设备
存储器
存储器的层次结构
存储器
CPU Cache: L1 Cache 通常分为 dCache(数据缓存) 和 iCache(指令缓存),L3 Cache 则是多个核心共享的
L1 Cache:指令缓存、数据缓存。几十kb到几百kb。随机访问延时是 1 纳秒
cat /sys/devices/system/cpu/cpu0/cache/index0/size
L2 Cache:几百 KB 到几 MB
cat /sys/devices/system/cpu/cpu0/cache/index2/size
L3 Cache:大小在几 MB 到几十 MB
cat /sys/devices/system/cpu/cpu0/cache/index3/size
内存:随机访问延时 100 纳秒
硬盘 SSD HDD
CPU 缓存一致性
缓存一致性问题
缓存一致性的问题具体是怎么发生的呢?
多核CPU对同时写一个共享资源,操作双核中的L1 Cache缓存不一致情况。
如何解决?
1. 写传播。最常见的实现方式:总线嗅探。
缺点: CPU 需要每时每刻监听总线上的一切活动,但是不管别的核心的 Cache 是否缓存相同的数据,都需要发出一个广播事件,这无疑会加重总线的负载。
2. 事务串行化。实现方式:升级版本 基于总线嗅探的MESI协议。
CPU是如何执行任务的?
CPU如何读写数据?
CPU架构
CPU读写单位
CPU Cache Line(缓存块):CPU不再是按字节访问内存,而是以64字节为单位的块(chunk)拿取,称为一个缓存行(cache line)。
CPU伪共享问题
概念:多个线程同时读写同一个 Cache Line 的不同变量时,而导致 CPU Cache 失效的现象
避免伪共享的方法?
1. 填充Padding。通过在对象的字段之间添加填充,使不同的字段位于不同的缓存行,避免多个线程同时访问同一缓存行。
Java中可以使用sun.misc.Contended注解来实现填充。
2. 对齐(Alignment):通过将对象的字段调整为CPU缓存行的整数倍,确保每个字段位于不同的缓存行,避免多个线程同时访问同一缓存行。
Java中可以使用Unsafe类来手动进行字段对齐。
需要注意的是,填充和对齐都需要针对具体的硬件和操作系统进行优化,因此并不是所有情况下都能够有效解决伪共享问题。
CPU如何选择线程?
中断
中断是什么?
中断是系统用来响应硬件设备请求的一种机制。
如何处理中断处理程序时间过长问题?
为了避免由于中断处理程序执行时间过长,而影响正常进程的调度,Linux 将中断处理程序分为上半部和下半部:
上半部,对应硬中断,由硬件触发中断,用来快速处理中断;
下半部,对应软中断,由内核触发中断,用来异步处理上半部未完成的工作;软中断是以内核线程的方式执行。
系统调用
异常处理
什么是软中断?什么是硬中断?系统里面有哪些软中断?
内核是怎么工作的?
什么是内核态和用户态?
为什么要用户态和内核态?只有一个内核态不行吗
用户态和内核态是如何切换的?
系统调用:用户态进程 主动 要求切换到内核态的一种方式。
中断
异常
中断和异常类似,都是通过中断向量表来找到相应的处理程序进行处理。区别在于,中断来自处理器外部,不是由任何一条专门的指令造成,而异常是执行当前指令的结果。
什么是系统调用?
二、进程&线程
进程
概念:是资源(CPU、内存)分配的最小单位
分类
用户态进程:应用程序
内核态进程:内核本身的进程
进程状态
创建
就绪
运行
阻塞
终止
进程间通讯IPC方式
管道:半双工,只能单向。
信号
消息队列
共享内存
信号量
套接字Socket
上下文切换
特殊进程
守护进程
孤儿进程:一个进程的父进程已经终止或者不存在,但是该进程仍在运行。
僵尸进程:子进程已经终止,但是其父进程仍在运行,且父进程没有调用 wait()或 waitpid()等系统调用来获取子进程的状态信息,释放子进程占用的资源,导致子进程的 PCB 依然存在于系统中,但无法被进一步使用。这种情况下,子进程被称为“僵尸进程”。避免僵尸进程的产生,父进程需要及时调用 wait()或 waitpid()系统调用来回收子进程。
线程
用户态线程&内核态线程
线程间的同步方式有哪些?
互斥锁:Java 中的 synchronized 关键词和各种 Lock 都是这种机制。
信号量
屏障
事件Event:Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作。
线程状态有几种?
创建NEW
就绪READY
运行RUNNING
阻塞WAITING
结束TERMINATED
协程
比线程更轻量级。
特点
1. 效率高,不需要上下文切换。
2. 不需要多线程的锁机制。因为只有一个线程,不存在同时写变量冲突。
死锁
产生原因:多个进程/线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于进程/线程被无限期地阻塞,因此程序不可能正常终止。
发生死锁的四个必要条件(只要系统发生死锁,这些条件必然成立)
互斥:资源只能被一个进程占用,不能被共享。
请求和保持:一个进程申请资源时,保持已占有的资源不放。
不剥夺:已分配的资源不能被强制性地从进程中剥夺。
循环等待:若干个进程之间形成一种循环等待资源的关系。
处理方法?
预防。通过破坏死锁产生的四个必要条件之一,来预防死锁的产生。比如,资源一次性分配、资源有序分配、资源类型抽象、进程抢占等。
避免。通过对进程的资源请求进行安全性检测,只要检测到当前请求可能引起死锁,就不予分配。比如,银行家算法、资源分配图算法等。
检测。及时发现死锁的发生,并采取相应的措施,比如终止某些进程、回收某些资源等。
解除。当死锁发生时,通过恰当地回收一些资源,使得死锁的进程可以继续执行。比如,采用撤销进程、回滚事务等方法。
三、内存管理
虚拟内存
什么是虚拟内存?有什么用?
概念:虚拟内存是一种计算机内存管理技术,它将计算机硬盘空间作为扩展内存使用,使得应用程序能够访问比物理内存更大的地址空间。
作用&优缺点
优点
进程隔离
提升物理内存使用率
提高内存使用安全性:控制进程对物理内存的访问,隔离不同进程的访问权限,提高系统的安全性。
提供更大的可使用内存空间
缺点
虚拟内存的实现需要消耗一定的计算机资源,包括硬盘空间、CPU时间和内存带宽等,可能会影响系统的性能
没有虚拟内存有什么问题?(参考虚拟内存提供的能力回答这个问题)
用户程序可以访问任意物理内存,可能会不小心操作到系统运行必需的内存,进而造成操作系统崩溃,严重影响系统的安全。
程序运行过程中使用的所有数据或指令都要载入物理内存,根据局部性原理,其中很大一部分可能都不会用到,白白占用了宝贵的物理内存资源。
什么是虚拟地址和物理地址?
物理地址:内存地址寄存器中的地址
虚拟地址:程序所使用的内存地址
虚拟地址与物理内存地址是如何映射的?(操作系统是如何管理虚拟地址与物理地址之间的关系?)
内存管理单元MMU(Memory Management Unit,内存管理单元)
MMU 将虚拟地址翻译为物理地址的主要机制有 3 种:
1. 分段机制
2. 分页机制
3. 段页机制
常见的内存管理方式有哪些?(虚拟内存实现方式?)
1. 连续内存管理
块式管理:存在严重内存碎片问题。
在Linux中使用Buddy System伙伴系统实现。
对于内部内存碎片的问题,Linux 采用 SLAB 进行解决
2. 非连续内存管理
段式管理
页式管理
段页式管理
分段机制
应用程序的虚拟地址空间被分为大小不等的段,段是有实际意义的,每个段定义了一组逻辑信息,例如有主程序段 MAIN、子程序段 X、数据段 D 及栈段 S 等。
段表有什么用?地址翻译过程是怎样的?
分段管理通过 段表(Segment Table) 映射虚拟地址和物理地址。
分段机制下的虚拟地址由两部分组成:段号+段内偏移量
翻译过程
通过段号一定要找到对应的段表项吗?得到最终的物理地址后对应的物理内存一定存在吗?
不一定。段表项可能并不存在:
段表项被删除:软件错误、软件恶意行为等情况可能会导致段表项被删除。
段表项还未创建:如果系统内存不足或者无法分配到连续的物理内存块就会导致段表项无法被创建。
分段机制为什么会导致内存外部碎片?
分页机制
把主存(物理内存)分为连续等长的物理页,应用程序的虚拟地址空间划也被分为连续等长的虚拟页。
页表有什么用?地址翻译过程是怎样的?
分页管理通过 页表(Page Table) 映射虚拟地址和物理地址。
分页机制下的虚拟地址由两部分组成:页号+页内偏移量
翻译过程
通过虚拟页号一定要找到对应的物理页号吗?找到了物理页号得到最终的物理地址后对应的物理页一定存在吗?
不一定。存在页缺失。
单级页表有什么问题?为什么需要多级页表?
多级页表属于时间换空间的典型场景,利用增加页表查询的次数减少页表占用的空间。
TLB 有什么用?使用 TLB 之后的地址翻译流程是怎样的?
快表:为了提高虚拟地址到物理地址的转换速度,操作系统在 页表方案 基础之上引入了 转址旁路缓存(Translation Lookasjde Buffer,TLB,也被称为快表) 。
TLB 属于 (Memory Management Unit,内存管理单元) 内部的单元,本质上就是一块高速缓存(Cache),缓存了虚拟页号到物理页号的映射关系,你可以将其简单看作是存储着键(虚拟页号)值(物理页号)对的哈希表。
换页机制有什么用?
什么是页面置换算法?页面置换算法有哪些?
当物理内存不足时,操作系统需要选择一些页面从物理内存中移除,以便为新的页面腾出空间。页面置换算法的目标是最小化页面置换的次数,从而提高系统的性能。
页面置换算法
哪一种页面置换算法实际用的比较多?
LRU 算法是实际使用中应用的比较多
页面置换算法的评价指标是什么?
缺页率:在一段时间内发生缺页的次数与总访问次数的比率,缺页率越低,表示页面置换算法的性能越好
页面置换次数:指在一段时间内发生页面置换的次数,页面置换次数越低,表示页面置换算法的性能越好
页面访问时间:指访问一个页面所需的时间,页面访问时间越短,表示页面置换算法的性能越好。
段页机制
结合了段式管理和页式管理的一种内存管理机制,把物理内存先分成若干段,每个段又继续分成若干大小相等的页。
在段页式机制下,地址翻译的过程分为两个步骤:
1. 段式地址映射。
2. 页式地址映射。
四、文件系统
存储管理
常见的磁盘调度算法有哪些?
文件管理
硬链接和软链接有什么区别?
硬链接为什么不能跨文件系统?
提高文件系统性能的方式有哪些?
目录管理
文件访问控制
五、常见面试题
一个进程可以创建多少线程。
外中断和异常有什么区别
解决Hash冲突四种方法
开放定址法
链地址法
再hash法
建立公共溢出区
分页机制和分段机制有哪些共同点和区别?
共同点
都是非连续内存管理的方式。
都采用了地址映射的方法,将虚拟地址映射到物理地址,以实现对内存的管理和保护。
区别:
分页机制以页面为单位进行内存管理,而分段机制以段为单位进行内存管理。页的大小是固定的,由操作系统决定,通常为 2 的幂次方。而段的大小不固定,取决于我们当前运行的程序。
页是物理单位,即操作系统将物理内存划分成固定大小的页面,每个页面的大小通常是 2 的幂次方,例如 4KB、8KB 等等。而段则是逻辑单位,是为了满足程序对内存空间的逻辑需求而设计的,通常根据程序中数据和代码的逻辑结构来划分。
分段机制容易出现外部内存碎片,即在段与段之间留下碎片空间(不足以映射给虚拟地址空间中的段)。分页机制解决了外部内存碎片的问题,但仍然可能会出现内部内存碎片。
分页机制采用了页表来完成虚拟地址到物理地址的映射,页表通过一级页表和二级页表来实现多级映射;而分段机制则采用了段表来完成虚拟地址到物理地址的映射,每个段表项中记录了该段的起始地址和长度信息。
分页机制对程序没有任何要求,程序只需要按照虚拟地址进行访问即可;而分段机制需要程序员将程序分为多个段,并且显式地使用段寄存器来访问不同的段。
异常和中断的区别
【专项】调度算法
进程调度算法
先来先服务
短作业优先
时间片轮转
最短剩余时间优先
多级反馈队列调度
优先级调度
磁盘调度算法
1. 先来先服务 FCFS
由于没有考虑磁头移动的路径和方向,平均寻道时间较长。同时,该算法容易出现饥饿问题,即一些后到的磁盘请求可能需要等待很长时间才能得到服务。
2. 最短寻道时间优先算法 SSTF
易出现饥饿问题,即磁头附近的请求不断被服务,远离磁头的请求长时间得不到响应。
3. 扫描算法 SCAN
能够保证所有的请求得到服务,解决了饥饿问题。但是,如果磁头从一个方向刚扫描完,请求才到的话。这个请求就需要等到磁头从相反方向过来之后才能得到处理。
4. 循环扫描 C-SCAN
SCAN 算法的变体
5. 边扫描边观察 LOOK
LOOK 算法对 SCAN 算法进行了改进,如果磁头移动方向上已经没有别的请求,就可以立即改变磁头移动方向,依此往复。也就是边扫描边观察指定方向上还有无请求,因此叫 LOOK。
6. 均衡循环扫描 C-LOOK
C-SCAN 只有到达磁盘边界时才能改变磁头移动方向,并且磁头返回时也需要返回到磁盘起点,这样可能会做很多无用功。C-LOOK 算法对 C-SCAN 算法进行了改进,如果磁头移动的方向上已经没有磁道访问请求了,就可以立即让磁头返回,并且磁头只需要返回到有磁道访问请求的位置即可。
页面置换算法
最佳页面置换算法OPT -- 而该算法无法实现,只是理论最优的页面置换算法
1. FIFO置换算法:一般只需要通过一个 FIFO 队列即可需求。不过,它的性能并不是很好
FIFO 页面置换算法性能为何不好?
2. 时钟页面置换算法(CLOCK)
3. FIFO
4. LRU最近最少使用算法
5. LFU最不经常使用算法
数据库
Redis
一、数据结构
外部数据结构
string
内部实现:内部编码有3种数据结构,原理类似arraylist,最大值不能超过512M
int:8个字节的long。用于存储整数。如果一个字符串对象保存的是整数值,并且这个整数值可以用long类型来表示,那么字符串对象会将整数值保存在字符串对象结构的ptr属性里面(将void*转换成 long),并将字符串对象的编码设置为int。
embstr(SDS):如果字符串对象保存的是一个字符串,并且这个字符申的长度小于等于 32 字节(redis 2.+版本),那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串,并将对象的编码设置为embstr, embstr编码是专门用于保存短字符串的一种优化编码方式。
raw(SDS):如果字符串对象保存的是一个字符串,并且这个字符串的长度大于 32 字节(redis 2.+版本),那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串,并将对象的编码设置为raw:
常见命令:incrby\set\get\getrange\strlen\ttl\del\expire
应用场景:缓存、分布式锁、分布式session、计数器。
面试题
SDS相比C语言的字符串有什么好处?
embstr和raw有什么区别?
好处:embstr会通过一次内存分配函数来分配一块连续的内存空间来保存redisObject和SDS,而raw编码会通过调用两次内存分配函数来分别分配两块空间来保存redisObject和SDS。
缺点:如果字符串的长度增加需要重新分配内存时,整个redisObject和sds都需要重新分配空间,所以embstr编码的字符串对象实际上是只读的,redis没有为embstr编码的字符串对象编写任何相应的修改程序。当我们对embstr编码的字符串对象执行任何修改命令(例如append)时,程序会先将对象的编码从embstr转换成raw,然后再执行修改命令
list
内部实现:类似双向链表linkedlist
当元素较少的时候,使用ziplist(压缩列表)。
当元素较多的时候,使用quicklist。
常见命令:lpush\lpop\rpush\rpop\llen\lrange\lindex\lset\lrem
应用场景:粉丝列表、文章评论、阻塞队列
消息队列:
1、如何满足消息保序需求?-- LPUSH + RPOP。RPOP是非阻塞的,需要不断轮询。为解决这个问题,Redis提供RBPOP(阻塞读取)。
2、如何处理重复的消息? -- 自己生成唯一性ID
3、如何保证消息可靠性?
4、List 作为消息队列有什么缺陷? --List 不支持多个消费者消费同一条消息,因为一旦消费者拉取一条消息后,这条消息就从 List 中删除了,无法被其它消费者再次消费。
hash
内部实现:类似HashMap
field、value比较少时,使用ziplist
field、value比较多时,使用dict(哈希表)
什么时候从ziplist转dict?
配置项:hash-max-ziplist-entries 512
hash-max-ziplist-value 64,满足以上一个条件,就会转dict
hash-max-ziplist-value 64,满足以上一个条件,就会转dict
为什么这么设计?
1. 插入和修改引发的realloc,大概率会引起内存拷贝
2. ziplist的查询需要O(N)的遍历
常见命令:hset\hmset\hget\hgetall\hkeys\hvals\hdel\hexist
应用场景:购物车
面试题
Hash数据结构可以给HKey设置过期时间吗?
如果要实现此功能怎么办?
set
内部实现:无序唯一kv
intset:如果集合中的元素都是整数且元素个数小于 512 (默认值,set-maxintset-entries配置)个,Redis 会使用整数集合作为 Set 类型的底层数据结构;
dict(哈希)
常用命令
潜在的风险:Set 的差集、并集和交集的计算复杂度较高,在数据量较大的情况下,如果直接执行这些计算,会导致 Redis 实例阻塞。
应用场景
点赞
共同关注
sorted set(zset)
内部实现
当数据量比较少时,用ziplist
当数据量比较大时,内部使用2个数据结构
dict:hash存储value和score的唯一映射关系
跳表skiplist用于排序
实现排序功能
省市区例子
查找的时间复杂度O(N)
常用命令:zadd\zrem\zrank\zrange\zrangbyscore
使用场景
排行榜
带权重的队列
其他数据结构
Bitmap(V2.2新增)
实现原理:位图,是一串连续的二进制数组(0和1),可以通过偏移量(offset)定位元素。
常用命令:setbit key offset value\getbit key offset
应用场景
统计活跃用户
用户在线状态
布隆过滤器
用户签到
HyperLogLog(V2.8新增)
内部原理:省略
特性
基数统计,提供不精确的去重计数。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数,和元素越多就越耗费内存的 Set 和 Hash 类型相比,HyperLogLog 就非常节省空间
常用命令
应用场景
百万级网页 UV 计数
Geo(V3.2新增)
实现原理:GEO 本身并没有设计新的底层数据结构,而是直接使用了 Sorted Set 集合类型。
常用命令
应用场景
滴滴叫车
Stream(V5.0新增)
常用命令
应用场景
消息队列
1、Redis Stream 消息会丢失吗?(基于 Stream 实现的消息队列,如何保证消费者在发生故障或宕机再次重启后,仍然可以读取未处理完的消息?)
2、Redis Stream 消息可堆积吗?
3、Redis 发布/订阅机制为什么不推荐作为消息队列?
不具备数据持久化能力
没有ACK机制
发布订阅机制只适合即时通讯的场景。
内部数据结构
dict
为了解决算法中的查找问题
增量Hash
dict数据结构
dictEntry
dictType
dictht
redis中的key和value组成一个全局字典,zset中的value和score锁组成的映射关系也是通过dict实现。
sds
特点
可动态扩展内存
可存储任何二进制
与C语言兼容
sds数据结构
一个header结构
和C语言字符串相似,但是有长度字段
长度=最大容量+1
len
表示字符串真正长度(不含NULL字符串)
alloc
字符串最大容量
flags
一个字符数组
浅谈sds和string的关系
当string存储数值时,内部存储还是sds吗?
quicklist
quicklist的每个节点都是ziplist
为什么这么设计?
空间和时间的折中
ziplist
内存结构:<zlbytes><zltail><zllen><entry>...<entry><zlend>
每一项之间连续顺序存储,追求存储效率。能以O(1)的效率在两端存储
可以存储:字符串和整数,其中整数以二进制方式存储
skiplist
面试题
为什么设计内外部数据结构?
1. 可以改进内部编码,对外部数据结构没有影响
2. 多种内部编码在不同场景下发挥各自优势
基础数据结构robj(RedisObject)
一个database内的映射关系用dict维护。dict#key固定为sds,value比较复杂
例如:value是String,则存的是sds;如果value是list,则存的是quicklist
robj是联接内外部数据结构的桥梁,通过不同的encoding来区分。
二、持久化机制
AOF追加
三种写回磁盘策略?
always 同步写回 -- 在主进程进行。高可靠性
every sec 每秒写回 -- 在子进程进行。如果允许数据丢失一点,但又想性能高
no 由操作系统决定何时写回磁盘 -- 高性能
优点?
实时性更好
缺点?
因为日志文件记录的是命令,不是实际数据,所以回放恢复很缓慢;
OS无法保存过大的文件,再者大文件追加命令效率也会变低;
面试题
AOF机制是如何实现的?
AOF日志文件太大怎么办?
为什么重写就可以减小日志文件呢?
为什么重写 AOF 的时候,不直接复用现有的 AOF 文件,而是先写到新的 AOF 文件再覆盖过去?
为什么用子进程不用子线程?
子进程是怎么拥有主进程一样的数据副本的呢?
AOF重写过程是怎么样的?
AOF重写会阻塞主线程吗?
写前日志和写后日志有什么区别?redis和mysql分别采用什么策略?
redis为什么采用写前日志? 会有什么问题?
RDB快照
默认;二进制数据快照;适用于备份;对全量数据快照
对哪些数据做快照?
redis提供2个命令生成rdb文件
优点?
在恢复大数据时,速度比AOF快;
bgsave和写时复制可以减少对主线程的影响。
缺点?
实时性不高。因为是全量快照,执行不能太频繁。
面试题
快照时数据能修改吗?如果可以,又是如何做到的呢?
Redis4.0 支持RDB和AOF混合持久化
配置项:aof-use-rdb-preamble yes
RDB以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作
CAOCAO采用的方案
三、过期策略&淘汰策略
过期key的删除策略:删除已过期的 key,减少过期kv积压
如果设置过期时间?
设置 expire、pexpire、exipireat、pexpireat; set key value ex n;
查询:ttl
取消 persist
如何判定key已过期?
过期字典 expires dict
过期删除策略有哪些?
1. 定时删除
优点:对内存是最友好的。
缺点:占用部分CPU。
2. 惰性删除(不主动删除过期键,每次从数据库访问 key 时,都检测 key 是否过期,如果过期则删除该 key)
优点:对CPU友好。
缺点:占用资源。
3. 定期删除:每隔一段时间「随机」从数据库中取出一定数量的 key 进行检查,并删除其中的过期key。
Redis过期策略是什么?
定期删除+惰性删除
内存淘汰策略:删除符合条件的 key
为什么需要内存淘汰策略?
1. 内存资源有限
2. 数据访问频率不同
3. 避免数据过期
4. 数据淘汰与数据持久化
Redis中默认启用了maxmemory配置项来限制内存使用量,当Redis占用的内存达到maxmemory限制时,就会触发内存淘汰机制,按照淘汰策略删除不必要的键值对以释放空间。如果没有设置maxmemory,则Redis可能会因为内存耗尽而崩溃。
内存淘汰策略有哪些?内存淘汰策略是如何工作的?
1. 不进行数据淘汰的策略
noevicition:Redis3.0之后默认淘汰策略。它表示当运行内存超过最大设置内存时,不淘汰任何数据,这时如果有新的数据写入,则会触发 OOM,但是如果没用数据写入的话,只是单纯的查询或者删除操作的话,还是可以正常工作。
2. 进行数据淘汰的策略
2.1 在设计了过期时间的数据中进行淘汰
volatile-random
volatile-ttl
volatile-lru(Redis3.0之前默认淘汰策略)
volatile-lfu
2.2 在所有数据范围进行淘汰
allkeys-random
allkeys-lru
allkeys-lfu
如何选择内存淘汰策略?
LRU和LFU的区别?
内存用完了会怎么样?
Redis内存用完后会导致写操作失败、读操作变慢、OOM错误以及数据淘汰等问题,因此需要采取合理的内存管理策略和淘汰策略,以便充分利用内存资源,避免因为内存不足而影响系统的正常运行
四、事务机制
特性
原子性:事务中的所有命令要么全部执行,要么全部回滚。
一致性【不具备】:Redis 的事务机制并不会自动保证数据的一致性。如果在事务执行过程中有其他客户端对事务中涉及的键进行修改,事务会继续执行而不会回滚。因此,一致性需要在应用层面进行处理。
缺陷
不具备一致性(不支持回滚)
隔离性【不具备】:Redis 的事务机制默认情况下是没有隔离级别的概念的,多个事务之间可以并发执行,彼此之间不会产生相互干扰。但是,在事务执行期间,如果有其他客户端修改了事务中的键,事务可能会受到影响。
持久性:Redis 的事务机制在 EXEC 命令执行后,事务中的修改会立即生效,不需要像普通命令一样显式地进行持久化操作。Redis 使用写时复制(copy-on-write)技术,将事务中的修改写入磁盘,保证了持久性。
redis事务是怎么实现的 ?基本流程是怎么样的?
五、集群高可用
主从:有且只有一个master
优点:
读写策略:主节点可读可写,从节点只读。从节点可以扩展主库节点的读能力,有效应对大并发量的读操作。
高可靠性:一方面,采用双机主备架构,能够在主库出现故障时自动进行主备切换,从库提升为主库提供服务,保证服务平稳运行;另一方面,开启数据持久化功能和配置合理的备份策略,能有效的解决数据误操作和数据异常丢失的问题;
缺点:
故障恢复复杂:需要手动。主节点宕机后整个集群不可用。
主库的写能力受到单机的限制。
面试题
Redis主从节点时长连接还是短连接?
长连接
怎么判断 Redis 某个节点是否正常工作?
ping-pong 心态检测机制
Redis 是同步复制还是异步复制?
Redis 主节点每次收到写命令之后,先写到内部的缓冲区,然后异步发送给从节点。
如何应对主从数据不一致?
为什么会出现主从数据不一致?
主从如何做到故障自动切换?
哨兵sentinel
基于主从复制实现主从节点故障转移。社区版本推出的原生高可用解决方案,其部署架构主要包括两部分:Redis Sentinel集群和Redis数据集群。
特点:1、哨兵的任务是:监控、提醒、自动故障转移;2、过半机制
优点:能实现自动故障转移
缺点
读写策略:仍然只有一个master节点,不能缓解写压力。
哨兵进程的工作原理?(自动故障转移机制?)
1. 哨兵判断节点是否正常的依据?
每隔1s发送一次心跳。如果节点没有在规定时间内响应,哨兵就会标记主观下线。
主观下线:单个sentinel认为节点下线
客观下线(只适用于主节点):过半sentinel认为节点下线-- 为了降低误判
2. 由哨兵集群中的哪个节点进行主从故障转移呢?
3.主从故障转移的过程是怎样的?
哨兵如何选定新主库?
0. ”筛选+打分“
1. 筛选:剔除主从网络不好的节点,判断依据是down-after-milliseconds*10
2.1 优先级高的从库得分高,判断依据是salve-prority配置项
2.2 和旧master同步最接近的从库得分高,判断依据是master_repl_offset和salve_repl_offset
2.3 ID号小的从库得分高
面试题
如果有哨兵实例出现故障,主从库还能正常切换吗?
哨兵Leader选主流程?
主从切换过程中,client是否正常进行请求操作?
为什么哨兵节点至少要有 3 个?
哨兵集群是如何组成的?
哨兵集群会对「从节点」的运行状态进行监控,那哨兵集群如何知道「从节点」的信息?
集群redis-cluster
Master可读可写、Slave只写,最少6个节点(三主三从)
优点:
读写策略:多个master提供读写,去中心化。主节点提供读写操作,从节点作为备用节点,不提供请求,只作为故障转移使用。
可扩展性:
高可用性:
缺点:
资源浪费:Redis数据节点中slave节点作为备份节点不提供服务;
数据一致性:异步复制,不保证数据的强一致。
部分命令不支持。
工作原理是什么?如何处理数据分片和故障转移?
codis
特点:集成了哨兵。
优点
可展现:可以通过添加proxy做横向扩展
缺点:1.不支持事务 2.不支持部分指令
面试题
为什么codis很多命令不支持?例如keys
Codis之间的槽位同步?
ZK存储路由表,过半机制保证高可靠
为什么Codis支持MGET和MSET,却不支持MSETNX命令呢?
Codis扩容:新增节点之后,原来的key如何迁移和分配?
集群的扩容和数据迁移
怎么保证高可用?
主从+哨兵机制
实践
为什么大厂都喜欢用 Codis 来管理分布式集群?
六、面试题
Redis为什么能那么快?Redis真的只有单线程吗?为什么用单线程?Redis有哪些潜在的性能瓶颈?Redis6.0为什么引入多线程(换而言之: 6.0之前为什么不用多线程)?会引入并发控制问题吗?
IO多路复用机制
单线程。只是IO是单线程。
Redis是AP还是CP?
Redis是单线程的,如何提高多核CPU的利用率?
你知道有哪些Redis分区实现方案?
Redis为什么那么快?
基于内存实现
高效的数据结构
合理的数据编码
合理的线程模型
虚拟内存映射
BigKey问题:什么是BigKey?BigKey有哪些危害?如何解决BigKey问题?
什么是BigKey?
字符串:value值过大。一般认为超过10kb就是bigKey。
非字符串:hash\set\list\zeset元素过多。
危害?
内存空间不均匀。
超时阻塞。由于redis单线程,操作bigkey比较耗时,容易阻塞。
网络拥塞。获取bigkey产生较大网络流量。
对持久化有什么影响?
如果写入的是bigkey,且配置的AOF回写策略是Always时,主线程在执行fsync()函数的时候,阻塞时间比较久。
如何避免?
在设计阶段,把大key拆成若干小key。
如果发现大key是可以删除的,不要使用del删除,因为会阻塞主线程。而是用unlink(Redis4.0+)删除bigKey,改命令式异步的,不会阻塞主线程。
什么是热Key? 如何解决热Key问题?
为什么Lua脚本的执行可以保证原子性?Lua脚本如何某个命令失败了咋办?
Lua脚本和事务有什么区别?
如何实现一个LRU算法?
Redis的哨兵机制和集群有什么区别?
DB和Cache一致性问题?
在Cache Aside模式中,为什么使用删除缓存而不是更新缓存?
在Cache Aside模式中,为什么先更新DB,而不是先删除Cache?
在CacheAside模式中,如果选择先删除缓存,再更新数据库,那如何解决一致性问题呢?
那么 Cache-Aside 存在数据不一致的可能吗?
双写的情况下,先操作数据库还是先操作缓存?
延迟双删
4中模式流程详解
Cache-Aside
Write-Thought
Read-Thought
Write-Bebind
如何使用Redis解决并发竞争Key问题?
热Key问题
多级缓存(本地缓存)
缓存数据不失效
布隆过滤器
读写分离
BigKey问题?
缓存异常有哪些情况?
缓存雪崩
缓存穿透
缓存击穿
作为分布式ID,如何保障可用性?
设计一个缓存系统,需要考虑哪些问题?
Redis除了做缓存,还能做什么?
Redis如何实现延迟队列?
Redis如何实现发布订阅?
什么是RedLock? 他解决了什么问题?
基于Redis设计限流算法
Redis事务如何保证原子性? 持久性?
参考文献
MySQL
0. 基础
SQL查询是如何执行的?
连接器->分析器->优化器->执行器->存储引擎
SQL更新是如何执行的?
WAL:Write-Ahead Logging先写日志,再写磁盘
逻辑日志redo log : 保证crash-safe, innodb特有
物理日志bin log
如何保证2个日志文件的一致性?
二阶段提交
SQL语句执行顺序?
from->join->on->where->group by-> having-> select -> distinct-> order -> limit
InnoDB存储引擎是怎么设计的?
InnoDB架构图
Buffer Pool
本质:一个以页为元素的链表
Buffer Pool 采用基于 LRU(least recently used) 的算法来管理内存:
为什么要使用改进的LRU?
保证缓存命中率最大化。
Log Buffer
innodb_log_buffer_size默认16MB
大的Log Buffer让较大事务能够运行,而无需在事务提交之前将redo log中的数据写入磁盘
Change Buffer
原理
当需要更新一个记录时,
- 如果数据页在buffer pool就直接更新;
- 如果这个数据页不在buffer pool中,不需要将数据读取内存,innoDB会将更新操作记录到change buffer中,等下次查询时,再执行change buffer中的相关操作,然后返回查询结果。
- 如果数据页在buffer pool就直接更新;
- 如果这个数据页不在buffer pool中,不需要将数据读取内存,innoDB会将更新操作记录到change buffer中,等下次查询时,再执行change buffer中的相关操作,然后返回查询结果。
merge:Change Buffer -> Buffer Pool
purge:Buffer Pool -> Disk
purge:Buffer Pool -> Disk
为什么需要ChangeBuffer?
Changebuffer是InnoDB中的一个缓存区域,用于存储对索引和数据的修改。它可以将多个随机的、小的修改合并成一个更大的修改,从而减少了对磁盘的访问次数,提高了写入性能。
change buffer主要节省的是随机读磁盘的IO消耗。 -- 随机写。
redo buffer主要节省随机写磁盘的IO消耗。redo log将数据变更操作记录到日志中,不用写入磁盘即可返回结果;
redo buffer主要节省随机写磁盘的IO消耗。redo log将数据变更操作记录到日志中,不用写入磁盘即可返回结果;
将二级索引的数据操作缓存下来,减少二级索引的随机IO,合并操作
Change Buffer什么时候真正更新?
merge: 将change buffer的操作应用到磁盘的过程;除了访问数据页会触发merge之外,系统后台也会有线程定期merge;
一个数据页在merge之前,changebuffer变更的记录越多,效益越大;反之,如果读多写少,每次读取都会触发merge,反而增加ChangeBuffer的成本。
Change Buffer的使用场景?
写多读少。适用于日志类、账单类
对查询基本没差别,主要考虑的是对更新性能的影响
唯一性索引就不能使用chang buffer
二级索引、非唯一
文章
MySQL 的 InnoDB 存储引擎是怎么设计的?
MySQL 之 InnoDB引擎 Change Buffer
通过参数innodb_change_buffer_max_size指定占用buffer pool的大小
Doublewrite Buffer 双写缓冲区
IO的最小单位
数据库IO的最小单位:16K
文件系统IO的最小单位:4K
磁盘IO的最小单位:512B
作用:提高数据文件的可靠性。
工作原理
面试题:因为写1页的数据需要多次磁盘IO,那么如果断电,部分写什么了怎么办?
写前日志(Write Ahead Log,WAL)机制。
当InnoDB将数据写入Doublewrite Buffer时,实际上是将数据写入一个预分配的磁盘区域中,而不是直接写入到磁盘。如果在写入磁盘时发生故障,Doublewrite Buffer中的数据可以被用来恢复数据文件,从而确保数据的一致性。
缺点
double write buffer是file,双写有性能上的影响
1. 索引
索引的数据结构是怎么样的?
B+树
表空间组成
数据行
页
区
段
问题:通常一颗B+树可以存放多少行数据?B+树一层有多少分支?(假设一行记录1kb,一颗层高为3的B+树,大约能存放多少行记录)
问题2:InnoDB为什么选择B+树而不是B树?(为什么不是二叉树,为什么不是平衡二叉树,为什么不是B树,而偏偏是B+树呢?)
1. B+树,子节点连接方便遍历
2. B树非叶子节点也保存数据,保存更多的数据需要更高的层数据,增加IO成本
索引类型
全文索引
哈希索引
等值查询
B+树索引
聚簇索引和非聚簇索引
聚簇索引一般是主键(1个表中只有1个),好处是索引行和数据行放同一空间
explain的时候type是const
如果定义了主键,那PK就是聚簇索引
如果没有定义主键,那么第一个非空唯一索引就是聚簇索引
否则,innodb会创建row_id作为聚簇索引
非聚簇索引,多扫描一次(二级索引、辅助索引)
普通索引
组合索引
概念
二级索引叶子节点只有主键和索引值
差异?
存储结构
唯一性
访问速度
更新开销
SQL优化的思路:参数、硬件、索引
1. 考虑如何创建有效的索引
1.1 只用给搜索、排序、分组的列创建索引,尽量选择区分度高的列作为索引
1.2 普通索引or 唯一性索引?
Change Buffer
(最初叫inser buffer)
(最初叫inser buffer)
区别
唯一性
查询时,两者差别不大
修改时,普通索引优于唯一索引。
1. 因为普通索引可以用Change Buffer
2. 唯一性索引需要校验
1.3 覆盖索引 or 普通索引
减少回表次数
1. 覆盖索引
Extra:Using Index
2. 5.6之后 索引下推ICP(Index condition Pushdown) 减少回表次数
当Extra值为:Using index condition.表示使用索引下推。
控制开关SET optimizer_switch = 'index_condition_pushdown=off';
通过建组合索引,实现索引覆盖,减少回表
场景1:全表count查询优化
场景2:列查询回表优
1.4 减少重复索引
1.5 order by优化
方法1:利用索引;排序算法分2种:全字段排序和rowId排序
rowId需要设置SET max_length_for_sort_data = XX; 如果单行长度超过XX,则只取排序字段和id进行排序,最后回表一次;
方法2:利用联合索引有序的特点;
1.6 join优化:
1. 使用join性能比强拆成多个单表的SQL要好;
2. 使用join的话,要让小表做驱动表;-- 驱动表不走索引,被驱动表走索引;
3. 如果被驱动表上没有索引呢?
explain选出的索引一定是最优的吗?
explain统计不一定准确。看下行数对不对 不对可以用analyze table t 矫正
Explain
type :访问方法
const: 主键或者唯一二级索引与常量进行等值匹配
ref: 普通二级索引与常量进行等值匹配
rang:范围匹配
index
all:全表扫描
key
经过优化器最终选择成本最低的索引
rows
extra
using filesort: 索引用文件排序,走的比较差
用例
有没有遇到走的比较差的索引?
大数据量下分页查询
2. 【高频】索引失效场景?
or。如何解决?-- 可以拆分成多个select 然后union all替换/ 或者使用in
is null -- 记录包含null就不会建索引
like
函数/表达式/数据类型转换
!=/ <>
in/ not in
用between/exist替换 -- exist外表循环内表索引、in内表循环外表索引
组合索引最左前缀匹配
3. 索引统计信息
优化器选错索引
1. 什么情况会选错?
统计信息不准确
索引类型不匹配
数据分布不均匀
查询条件复杂
2. 选错的怎么处理?
面试题
什么是回表?怎么减少回表次数?
什么是索引下推?什么是覆盖索引?
聚簇索引和非聚簇索引有什么区别?
如何进行SQL调优?
慢SQL如何排查?
InnoDB为什么推荐使用自增ID作为主键?
索引失效如何排查?
区分度不高的字段建索引一定没用吗?
SQL执行计划分析的时候,要关注哪些信息?
用了索引还是很慢,可能是什么原因?
order by 是如何实现的?
设计索引的时候有哪些原则?
如何解决热点数据更新?
MySQL是如何保证唯一性索引的唯一性的?
唯一索引允许NULL值吗?
唯一性索引查询更快吗?
唯一性索引有什么缺点吗?
2. 锁
按锁的粒度分
全局锁Global Lock
表级别的全局锁(Table-level Global Lock)
读写锁(Read/Write Global Lock)
表级锁
意向锁
意向锁共享锁(IS锁)
意向锁排他锁(IX锁)
特殊的表级锁:自增锁
针对自增列的一个特殊的表级锁
在insert的时候加一个AUTO-INC锁
在有主从复制的时候不安全
行锁
行锁:是通过索引上的索引项加锁来实现的
行锁的算法(重点)
临键锁
间隙锁
记录锁
按加锁方式
自动锁
显示锁
按锁的级别
共享锁(S锁)
只读不能修改
排他锁(X锁)
加锁方式?
其他事务读取的数据来自快照
不能与其他锁(共享锁、排他锁)共存
按使用方式
乐观锁
悲观锁
面试题
如何利用锁进行并发控制?
利用锁怎么解决脏读?
使用X锁,select for update
利用锁怎么解决幻读
使用Next Key Lock
利用锁怎么解决不可重读?
使用S锁,其他事务只能读不能写,select lock in shared mod
没有事务会产生锁吗?事务和锁之间有什么关系?
什么是共享锁和排他锁?它们有什么区别?
行锁锁的到底是什么?如果innodb表中没有主键和唯一性索引,会产生行锁吗
InnoDB加索引会锁表吗?
为什么 InnoDB 选择临键锁作为行锁的默认算法?
在RR隔离级别下,普通的select,没有使用事务会加锁吗?
什么是意向锁?
如何避免死锁?(怎么解决热点行更新导致的性能问题?)
发生了死锁如何处理?
1. 设置超时时间
2. 回滚其中一个事务
如何提高MySQL的并发性能和避免锁竞争?
MySQL只操作同一条记录,也会发生死锁吗?
3. 事务
SQL标准中的事务隔离级别
读未提交
读已提交
可重复读
序列化
事务并发会有哪些问题
脏读
幻读
不可重复读
事务特性?
原子性
一致性
隔离性
持久性
如果没有redo log,可能会出现什么问题?
如何解决buffer log缓存中数据同步到磁盘过程中,因宕机导致数据丢失的问题?
MVCC
事务基础
重做日志 redo log
回滚日志 undo log
bin log
WAL 先写日志
基本概念
当前读
快照读
通过Undo Log和版本链实现数据多版本。
InnoDB隐藏列
DB_ROW_ID
DB_TRX_ID
DB_ROLL_PTR
版本链
所有版本的DB_ROLL_PTR组成版本链;版本链的头结点为最新记录
通过锁实现并发控制
利用锁怎么解决脏读?
利用锁怎么解决幻读?
利用锁怎么解决不可重复读?
ReadView(读视图)
数据结构
访问规则?
ReadView生成时机?
面试题
MVCC是如何实现的?如何理解MVCC? MVCC如何处理并发读取和写入?
MVCC为什么只在RC、RR级别生效?
什么是脏读、幻读、不可重复读?InnoDB如何解决脏读、不可重复读和幻读的?
MySQL中的事务隔离级别?
Innodb的RR到底有没有解决幻读?
当前读和快照读有什么区别?
MySQL执行大事务会存在什么问题?
Mysql的主键一定是自增的吗?
MySQL事务两阶段提交过程是怎么样的?为什么要两阶段提交?是先接binlog还是先写redolog?
4. 高可用篇
基于分布式协议
基于Gossip的Mysql Cluster
官方集群方案
特点
官方组件
可以实现数据的强一致性
缺点
1. 需要使用NDB引擎
2. 至少3个节点
基于AP的Galera
特点
多主写入,保证数据强一致
同步复制、主备无延迟
行级别并行复制
热插拔
对应用透明
缺点
1. 至少三个节点
2. 不支持XA Transaction
3. 需要为原生MySQL节点打wsrep补丁
原理
文献
Galera Cluster for MySQL 详解(一)——基本原理
Galera replication原理详解
开源实现方案
PXC(Percona XtraDB Cluster)
MariaDB Cluster
基于Paxos的MGR(MySql Group Replica)组复制
主从复制
主从复制
原理
如何理解半同步复制?
异步复制(默认)
主库执行完之后立刻返回,并不关心从库是否成功接收
全同步复制
所有从库执行完之后返回
全同步复制有什么缺点?
半同步复制
至少一个从库同步完成之后才返回
主备延迟来源
备库硬件差
备库压力大
大事务:不要一次性delete太多数据
大表DDL
备库的并行复制能力
由于主备延时存在,所以在主备切换的时候,如何应对?
可靠性优先策略
MMM(Master-Master Replication for MySQL)双主多从
原理
特点:互为主从(双主)
优点
1. 多个slave读负载均衡
2. 自动主主故障切换
缺点
1. 无法完全保证数据一致性
db1宕机,一旦db2落后于db1,这时发生切换,db2变成可写状态,会导致数据不一致
2. monitor单点,可用keepalived解决
3. 无论何时,只有一个db可写
MHA(Master High Avaliable)多主多从
原理
由MHA Node和MHA Manager组成
MHA Manger主要负责
MHA是MMM的升级版,但是缺少vip,所以一般采用keepalived的补完vip功能
最大特点:在主从复制之上,修复多个slave之间的日志差异,最终使所有slave保持一致
优点
1. 自动监控Master故障转移 ,故障之后节点数据同步
2. 具备数据自动补偿能力,最大程度保证数据一致
缺点
缺少读负载均衡
可以用F5、LVS解决
读写分离需要改动代码,和开发耦合度高
案例
MySQL高可用集群方案
mysql5.7集群方案对比
MySQL集群之五大常见的MySQL高可用方案(转)
综合面试题
分库分表的方案有哪些?
客户端方案
TDDL
Sharding-JDBC
代理方案
MyCAT
Mysql Proxy
MySQL为什么那么慢?什么影响了MySQL性能?
MyISAM和InnoDB区别?
索引:聚簇和非聚簇,myiasm有3个文件:frm\myd\myi innodb有2个文件:frm\idb
锁
事务
外键
Mysql 5.5之后默认innodb
为什么要分库分表?单节点MySQL的瓶颈在哪?为什么慢?
IO瓶颈
CPU瓶颈
参考文章
敖丙思维导图-Mysql数据库
架构设计
DDD
基础概念
领域、子域
通用域
核心域
支撑域
通用语言、限界上下文
实体、值对象、聚合
领域事件
其他架构
洋葱模型
六边形模型
DDD的核心是什么?
DDD的弊端是什么?
DDD、微服务、中台和平台之间的关系?
数据驱动和领域驱动对比有什么差别?
请详细描述DDD的实现流程?
什么是聚合,什么是聚合根?
什么是实体,什么是值对象?
什么是充血模型和贫血模型?
DDD的分层架构是怎么样的?
系统设计面试题
Feed流推荐系统如何设计?
1. 询问功能需求点
2. 询问系统需要支持的数据量
请求QPS
存储量
3. API设计&数据模型设计
4.
设计一个类 Twitter 的服务?
LeetCode 设计推特
设计一个短网址系统 TinyURL?
使用场景
多少长度合适?
目前互联网网页总数大概45亿。
长度:7位。如果是数字+大小写字母,62^7远大于45亿。
短链接和长链接是一对一or一对多mapping?
正确答案:一对多
如果一个长网址与一个短网址一一对应,那么在数据库中,仅有一行数据,无法区分不同的来源,就无法做数据分析了
设定了短网址是一个长度为7的字符串,如何计算得到这个短网址呢?
正确答案:分布式ID生成器
最容易想到的办法是哈希,先hash得到一个64位整数,将它转化为62进制整,截取低7位即可。但是哈希算法会有冲突,如何处理冲突呢,又是一个麻烦。这个方法只是转移了矛盾,没有解决矛盾,抛弃。
如果存储短网址和长网址的对应关系?
以短网址为 primary key, 长网址为value, 可以用传统的关系数据库存起来,例如MySQL, PostgreSQL,也可以用任意一个分布式KV数据库,例如Redis, LevelDB。
301还是302重定向?
正确答案是302重定向
短时间内向TinyURL服务器发送大量的请求,会迅速耗光ID,怎么办呢?
限制IP的单日请求总数,超过阈值则直接拒绝服务。
光限制IP的请求数还不够,因为黑客一般手里有上百万台肉鸡的,IP地址大大的有,所以光限制IP作用不大。
可以用一台Redis作为缓存服务器,存储的不是 ID->长网址,而是 长网址->ID,仅存储一天以内的数据,用LRU机制进行淘汰。这样,如果黑客大量发同一个长网址过来,直接从缓存服务器里返回短网址即可,他就无法耗光我们的ID了。
如何设计限流器Rate Limter
四大限流算法
令牌桶
主要调节的是每秒的QPS
漏斗
主要调节的是请求的时间间隔
滑动窗口
基于Redis实现
如何设计实时数据系统 Data Dog
如何设计一个聊天系统?
如何设计一个爬虫系统?
如何设计一个评论系统/留言板?
设计一个高可用,高伸缩的缓存系统?设计一个Key-Value存储引擎? 设计一个分布式键值存储系统
设计拼车服务?
设计秒杀系统?
定时任务调度器
设计一个优惠券系统?
设计一个停车场系统?
设计一个注册中心?
海量数据处理
设计模式
工厂模式
策略模式
模板模式
责任链模式
单例模式
面向对象编程的六原则一法则
开闭原则
里氏替换原则
依赖倒置原则
合成复用原则
接口隔离原则
单一职责原则
最少知道原则/迪米特法则
项目管理
金三角
时间、范围、成本-> 质量
开发模型
瀑布模型
MVP模型
大瀑布拆小瀑布
敏捷开发
产研团队中较关心的问题
项目管理经验
大局观
管理人和事
指定计划
大数据库
flink
hive
基本概念和特点
Hive 是什么?它与传统的关系型数据库有何不同?
Hive 的体系结构和工作原理是怎样的?
说说你对列存储数据库的理解?
Hive 的分区、桶和索引
分区是什么?如何在 Hive 中创建和使用分区表?
桶是什么?与分区的区别是什么?
Hive 支持哪些类型的索引?如何在 Hive 中创建和使用索引?
Hive 查询语言(HiveQL)
Hive 支持用户自定义函数(UDF)吗?如何创建和使用 UDF?
HiveQL 是什么?与 SQL 有何异同?
Hive 的性能优化和调优
如何通过设置参数来优化 Hive 的性能?
如何设计优化 Hive 查询的数据存储格式?
Hive 的并行执行机制是怎样的?如何进行并行处理以提高性能?
Hive 的容错和故障恢复
Hive 如何处理节点故障和数据丢失的情况?
Hive 元数据是如何存储和管理的?如何备份和恢复元数据?
0 条评论
下一页