java面试
2021-10-12 16:55:12 3 举报
AI智能生成
登录查看完整内容
Java后台 P7面试知识点大全,持续更新
作者其他创作
大纲/内容
线程安全
StringBuffer
非线程安全
StringBuilder
如果字符串常量池中存在,则返回常量池中的对象,如果不存在则将字符串放入常量池,返回当前对象
字符串有重复的时候,为了避免重复创建,可以指向常量池的同一个
使用场景
String.intern
String
HashTable
数组+链表
put
get
zise
操作
JDK1.7
头结点
null元素
1、对key进行hashcode
2、hash表是否为空,如果为空则初始化hash表
3、计算hashcode所在位置有没有数据,如果没有数据则直接放置
4、如果有数据,则判断key是否相等,如果相等则直接替换
5、如果不同则判断是否为红黑树
6、如果是红黑树,则直接插入树
7、如果是链表,则遍历链表,添加到最后
8、判断链表长度是否大于8,如果大于8则转为红黑树
9、如果元素个数大于阈值,则进行扩容
链表
红黑树
1、将容量扩充为2倍
2、将原始hash表的元素重新hash放置在新的hash表中
resize
数组+链表+红黑树
原理
capitail
loadfactor
参数
JDK1.8
HashMap
lambda
集合
封装
继承
多态
面向对象
子主题
架构图
反射创建对象
反射调用方法
反射获取属性值
JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。
Class clazz1 = Class.forName(\"全限定类名\");
Class clazz2 = User.class;
Class clazz3 = p.getClass();
实现方式
在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。
反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;
反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。
优缺点
反射
保证对象在回收前完成特定资源回收
Java9中以标记为过时
描述
使用不当会造成死锁和效率低的问题
问题
利用幻象引用和引用队列,我们可以保证对象被彻底销毁前做一些类似资源回收的工作,比如关闭文件描述符(操作系统有限的资源),它比 finalize 更加轻量、更加可靠
它有自己的运行线程,所以可以避免意外死锁等问题
Cleaner替换
替代方案
finalize
java基础
New
Runnable
Blocking
watting
Terminated
线程状态
new Thread
implement Runnable
implement Callable
实现
fork
join
wait
notify
notifyall
线程协作
sleep
yield
线程切换
JVM提供
锁升级,性能一般情况下没有问题
不能实现公平锁
不可中断
synchornized
JDK实现
可以对多个Condition加锁
可以实现公平锁
可中断
reentrantLock
线程互斥
线程
原子性
可见性
有序性
三大特性
E(exclusive)、M(modified)、S(shared)、I(invalid)
MESI协议
主存与私有内存的通知与同步
一致性原理
无锁
偏向锁
轻量级锁
重量级锁
锁状态
JDK的实现,比较简单
能保证原子性、可见性、有序性
优点
不能自定义锁的状态
不支持公平锁
缺点
对象
类
静态变量
使用
monitorEnter、monitorExit
对象头
元素数据
对齐填充
对象结构
synchorize
原子操作能保证原子性
不完全原子性
LoadLoad
StoreStore
StoreLoad
LoadStore
内存屏障
as if seriazes
start
监视器规则
传递性
join规则
happens-before语义
特性
原理lock
valitile
禁止重排序,解决可见性问题
final
常见实现
synchronized、ReentrantLock
互斥同步
CAS、AtomicXXX
非阻塞同步
key为null时会出现内存泄漏
set 、remove
ThreadLocalMap
ThreadLoal
线程私有
CountDownLatch
CyclicBarrier
Semaphore
实现案例
可多个Condition条件
基于JDK实现的,操作更灵活
可实现公平锁
ReentrantLock
允许一个或多个线程等待某些操作完成
不可以重置的,所以无法重用
基本操作组合是 countDown/await
关注的是事件
一种辅助性的同步结构,允许多个线程等待到达某个屏障
基本操作组合,则就是 await,当所有的伙伴(parties)都调用了 await,才会继续进行任务,并自动进行重置。
关注的是线程
信号量
,其基本逻辑基于 acquire/release
将基础的同步相关操作抽象在 AbstractQueuedSynchronizer 中
private volatile int state;
一个先入先出(FIFO)的等待线程队列(使用Node的双向链表来实现),以实现多线程间竞争和等待
基于 CAS 的基础操作方法,以及各种期望具体同步结构去实现的 acquire/release 方法。
排他锁与共享锁
两个虚拟队列: 同步队列sync queue 和条件队列condition queue,
AQS
park
unpark
LockSupport
Lock
基于 lock-free(无锁),在常见的多线程访问场景,一般可以提供较高吞吐量
ConcurrentLinkedQueue
基于锁,并提供了 BlockingQueue 的等待性方法
改进了锁操作的粒度,头、尾操作使用不同的锁,所以在通用场景下,它的吞吐量相对要更好一些
LinkedBlockingQueue
源码分析
ArrayBlockingQueue
这是一个非常奇葩的队列实现,每个删除操作都要等待插入操作,反之每个插入操作也都要等待删除动作。那么这个队列的容量是多少呢?是 1 吗?其实不是的,其内部容量是 0。
SynchronousQueue
是无边界的优先队列,虽然严格意义上来讲,其大小总归是要受系统资源影响。
PriorityBlockingQueue
BlockingQueue
分离锁
内部使用volatile的value字段来保证可加性
使用CAS操作,保证无锁并发操作
如果key或者value为null,则抛出空指针异常;如果table为null或者table的长度为0,则初始化table,调用initTable()方法。计算当前键值的索引位置,如果Hash表中当前节点为null,则将元素直接插入。(注意,这里使用的就是前面锁说的CAS操作)如果当前位置的节点元素的hash值为-1,说明这是一个ForwaringNodes节点,即正在进行扩容。那么当前线程加入扩容。当前节点不为null,对当前节点加锁,将元素插入到当前节点。在Java8中,当节点长度大于8时,就将节点转为树的结构。addCount是否需要扩容
如果该位置为 null,那么直接返回 null 就可以了 如果该位置处的节点刚好就是我们需要的,返回该节点的值即可 如果该位置节点的 hash 值小于 0,说明正在扩容,或者是红黑树,后面我们再介绍 find 方法 如果以上 3 条都不满足,那就是链表,进行遍历比对即可
private transient volatile long baseCount;
@sun.misc.Contended static final class CounterCell { volatile long value; CounterCell(long x) { value = x; }}
在计算 size 的时候,会将 baseCount 和 CounterCell 数组中的元素的 value 累加,得到总的大小,但这个数字仍旧可能是不准确的。
size
CurrentHashMap
ConcurrentSkipListMap
任何修改操作,如 add、set、remove,都会拷贝原数组,修改后替换原来的数组,通过这种防御性的方式,实现另类的线程安全。
比较适合读多写少的操作
CopyOnWriteList
Collection
CAS+volatile 依赖于 Unsafe 提供的一些底层能力
实现原理
AtomicBoolean
AtomicInteger
AtomicLong
原子更新基本类型
AtomicIntegerArray
AtomicLongArray
AutomicReferenceArray
原子更新数组
AtomicReference
AtomicStampedReference
AtomicMarkableReferce
原子更新引用类型
AtomicIntegerFieldUpdater
AtomicLongFieldUpdater
AtomicStampedFieldUpdater
AtomicReferenceFieldUpdater
原子更新字段类型
Atomic
存放线程
workSet
存放任务
workQueue
提高执行效率,不用每次创建和销毁
提高系统吞吐量
CPU核数+1
CPU密集型
CPU核数 × 目标CPU利用率 ×(1 + 平均等待时间/平均工作时间)
IO密集型
corePoolSize
maximumPoolSize
keepAliveTime
时分秒
unit
不存储元素的阻塞队列
基于链表的阻塞队列
有优先级的队列
PriorityBlockingQuene
BlockingQueue workQueue
DiscardPolicy
直接丢弃
DiscardOldestPolicy
丢弃老的
CallerRunsPolicy
调用当前线程执行任务
AbortPolicy
异常(默认)
RejectedExecutionHandler
newFixedThreadPool(固定大小)
newSingleThreadExecutor(一个线程)
newCachedThreadPool(Integer最大数的线程)
为任务提供延迟或周期执行
DelayedWorkQueue
ScheduledFutureTask
ScheduleThreadPoolExecutor
ForkJoinPool.defaultForkJoinWorkerThreadFactory
newWorkStealingPool
Executor方式
ThreadPoolExecutor
Executor
JUC
AtomicStampedReference解决ABA问题
ABA问题
只能保证一个变量的原子操作
循环开销时间长
Unsafe
java中的实现
CAS
自旋锁
ReentrantLock、Synchorized
可重复可递归调用的锁
可重入锁、不可重入锁
公平锁、非公平锁
ReentrantReadWriteLock
独占所、共享锁
ReentrantLock、synchorized
乐观锁、悲观锁
ConcurrentHashMap
分段锁
synchorized
偏向锁、轻量级锁、重量级锁
读写锁、互斥锁
种类
锁
并发编程
hash地址
GC分代年龄
锁状态位
运行时元数据(mark word)
指向元空间中对象所属的类型
类型指针
规则:先放父类的成员变量,在放子类的成员变量。相同宽度的变量被放在一起。如果compactFileds参数为true,子类的窄变量可能插入到父类变量的空隙
类中定义的成员变量
实例数据
缓存行是CUP与内存交互的最小工作单元,按块进行处理的,每块为64位处理器每次处理 8Byte(64bit)
对其填充
java对象
结构图
主内存
本地内存
内存模型
重排序
缓存一致性原理
JMM
jre/lib
AppClassLoader
ext
ExtClassLoader
classpath
BootstrapClassLoader
自定义类加载的方式
加载非classpath下的文件
加载网络传输的class
加载需要加解密的class
实现热部署功能
作用
自定义类加载器
类加载器
不会自己先加载,会交给父加载器加载
双亲委派机制
加载后的类会放入缓存,如果缓存中已有则不加载
缓存机制
优先让父类进行加载,只有父类无法加载的时候,自己再加载
父类委托机制
当一个类被加载是,该类依赖的其他类也由该类加载
全盘负责
重写loadClass
如何打破双亲委派
加载机制
加载阶段完成后,虚拟机外部的 二进制字节流就按照虚拟机所需的格式存储在方法区之中,而且在Java堆中也创建一个java.lang.Class类的对象,这样便可以通过该对象访问方法区中的这些数据。
从本地系统中直接加载
通过网络下载.class文件
从zip,jar等归档文件中加载.class文件
加载方式
加载
验证class文件的正确性
验证
为类的静态变量准备内存
准备
在这一步会将常量池中的符号引用(symbolic reference)替换为直接引用。
解析
链接
静态字段赋值的动作,以及执行类定义中的静态初始化块内的逻辑,编译器在编译阶段就会把这部分逻辑整理好,父类型的初始化逻辑优先于当前类型的逻辑。
普通原始类型静态变量和引用类型(即使是常量),是需要额外调用 putstatic 等 JVM 指令的,这些是在显式初始化阶段执行,而不是准备阶段调用;而原始类型常量,则不需要这样的步骤。
初始化
卸载
过程
from
to
Survivor
多线程同时分配内存时,为避免操作同一地址,可能需要使用加锁等机制,进而影响分配速度
top
end
结构
Thread Local Allocation Buffer(TLAB)
Eden
放置长生命周期的对象,通常都是从 Survivor 区域拷贝过来的对象。
我们知道普通的对象会被分配在 TLAB 上;如果对象较大,JVM 会试图直接分配在 Eden 其他位置上;如果对象太大,完全无法在新生代找到足够长的连续空闲空间,JVM 就会直接分配到老年代。
Old
新生代
老年代
用来放置 Java 对象实例,几乎所有创建的 Java 对象实例都是被直接分配在堆上
堆
存储所谓的元(Meta)数据,例如类结构信息,以及对应的运行时常量池、字段、方法代码等。
1.8以前永久代
1.8以后元空间
方法区
线程公用
本地方法的调用
在 Oracle Hotspot JVM 中,本地方法栈和 Java 虚拟机栈是在同一块儿区域,这完全取决于技术实现的决定,并未在规范中强制。
本地方法栈
一次次的 Java 方法调用
局部变量表
操作数(operand)栈
动态链接
方法正常退出或者异常退出的定义
虚拟机栈
存储当前线程正在执行的 Java 方法的 JVM 指令地址
程序计数器
Native Memory Tracking (NMT)分析
堆外内存
元空间
循环引用
可达性分析
不会被回收
强引用
内存不够时才会被回收
软引用
一定会被回收
弱引用
虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。
虚引用
引用类型
加锁(效率低)
Lock Free(复杂)
并发情况下,需要保证同步,性能低
循环引用(导致垃圾泄漏)
对象引用的对象也会回收,导致效率低
引起连锁回收
虚拟机栈和本地方法栈中正在引用的对象、静态属性引用的对象和常量
GC Roots
引用计数器
将存活的对象Copy到另一块内存
无内存碎片
实现简单,通过指针后移即可
新生代效率高
浪费一半内存空间
会有停顿
Forwarding指针
引用指针
CPU空间利用率
BFS和DFS的区别是什么
图的DFS
复制算法
使用链表管理所有的空闲区域,将不存活的对象所占用的内存交还给链表
freelist
内存利用率高
可以用很小的代价实现并发标记和清除
特点
标记-整理算法
有内存碎片
标记-清除算法
标记 - 清除,但为避免内存碎片化,它会在清理过程中将对象移动,以确保移动后的对象占用连续的内存空间。
算法基础
分配效率
回收效率
是否产生内存分片
空间利用率
是否停顿
时间复杂度
实现的复杂度
主要因素
垃圾回收算法
-Xms
-Xmx
-Xss
-XX:NewRatio
-XX:SurvivorRatio
JVM参数
JConsole
JMap
Arthas
工具
调优
STW
一个线程串行
Serial 收集器
多个线程并行,减少STW时间
ParNew 收集器
可控制吞吐量
Parallel Scavenge 收集器
标记整理算法
Serial Old 收集器
目标:优先吞吐量
Parallel Old 收集器
目标:减少停顿时间
栈上的引用
全局变量等
虚拟机的引用
不在堆上的引用
只标记根节点
初始标记
黑引用白,直接将白标灰
会有垃圾
往前进
黑引用白,将黑变灰
往后退
解决办法:三色标记
CMS解决办法
漏标问题
并发标记
把剩余的标记
如果年轻代太大,比较耗时,需要先进行一次yong gc
remark
重新标记
并发清除
实现:标记清除算法
内存碎片,会增多FullGC
多CPU竞争,会与用户线程抢占CPU资源
CMS 收集器
一种兼顾吞吐量和停顿时间
Region 之间是复制算法,但整体上实际可看作是标记 - 整理(Mark-Compact)算法,可以有效地避免内存碎片,尤其是当 Java 堆非常大的时候,G1 的优势更加明显。
G1 的很多开销都是源自 Remembered Set,例如,它通常约占用 Heap 大小的 20% 或更高,这可是非常可观的比例。并且,我们进行对象复制的时候,因为需要扫描和更改 Card Table 的信息,这个速度影响了复制的速度,进而影响暂停时间。
目标
分块GC
Surviver
H
分区类型
粗粒度表(整个region)
多
细粒度表(bitmap)
中
稀疏表(hash表)
少
问题:可能会变得很大
Point In(专属记录集)
Point Out(通用记录集)
用于记录和维护 region 之间对象的引用关系。垃圾回收的主力
Remember Set
记录集
跨区引用
根据停顿时间,计算需要回收的块
优先回收垃圾多的,才能提高吞吐量
合理选择回收区域
同时需要考虑分代
兼顾大对象分配
挑战
JVM将内存划分成了固定大小的Card。这里可以类比物理内存上page的概念。
card table
其他概念
G1 收集器
垃圾收集器
明确需求:是吞吐量还是延迟还是内存占用
jstat
找到具体的垃圾回收器
考虑硬件
重复完成分析、调整、验证这个过程。
JVM调优思路
JVM
面向类,通过字节码增强实现
Cglib动态代理(默认)
面向接口,通过JDK反射实现,性能差
JDK动态代理
AOP
控制反转,是一种思想,不用亲力亲为,用的时候去获取即可。
IoC
单例模式
XMLApplicationContext
ClasspathApplicationContext
ApplicationContext
BeanFactory
工厂模式
***Proxy
代理模式
Observer
监听者模式
JdbcTemplate
模板方法模式
设计模式
Spring
@Configuration
SpringBootConfiguration
@AutoConfigurationPackage
ImportSelect
Registar
Import
spring.factories
Configuration
EnableAutoConfiguration
ComponentScan
SpringBootApplicatin
1、上手容易、开发快捷
2、开箱即用,远离繁琐的配置
3、提供了一系列大型项目通用的非业务性功能
4、避免大量的 Maven 导入和各种版本冲突
EnableAutoConfiguration 自动装配
Starter 启动依赖依赖于自动装配的技术
Springboot
MyBatis
更加优雅的 Reactor 模式实现、灵活的线程模型、利用 EventLoop 等创新性的机制,可以非常高效地管理成百上千的 Channel。
使用池化的 Direct Buffer 等技术,在提高 IO 性能的同时,减少了对象的创建和销毁;利用反射等技术直接操纵 SelectionKey,使用数组而不是 Java 容器等。
充分利用了 Java 的 Zero-Copy 机制
直接利用 JNI 调用 Open SSL 等方式,获得比 Java 内建 SSL 引擎更好的性能。
使用更多本地代码。
在通信协议、序列化等其他角度的优化。
高性能原因
它是一个异步的、基于事件 Client/Server 的网络框架,目标是提供一种简单、快速构建网络应用的方式,同时保证高吞吐量、低延时、高可靠性。
定义
Netty
开发框架
接口层
服务层
存储层
ACID
脏读、幻读、不可重复读
读未提交
脏读、不可重复读
读已提交
不可重复读(innodb没有)
可重复读
无
串行化
事务隔离级别
事务
整体结构
*.idb
支持行级锁
支持事务
主键递增
当事务对数据库进行修改时,InnoDB 会生成对应的 undo log
row transation_id
data_roll_ptr
db_row_id
undo log
A原子性
事务追求的最终目标,一致性的实现既需要数据库层面的保障,也需要应用层面的保障
C一致性
意向锁、排它锁
LBCC
写隔离
read view
MVCC
读隔离
多个事务之间的隔离
I隔离性
InnoDB 作为 MySQL 的存储引擎,数据是存放在磁盘中的,但如果每次读写数据都需要磁盘 IO,效率会很低。为此,InnoDB 提供了缓存(Buffer Pool),Buffer Pool 中包含了磁盘中部分数据页的映射,作为访问数据库的缓冲:当从数据库读取数据时,会首先从 Buffer Pool 中读取,如果 Buffer Pool 中没有,则从磁盘读取后放入 Buffer Pool。当向数据库写入数据时,会首先写入 Buffer Pool,Buffer Pool 中修改的数据会定期刷新到磁盘中(这一过程称为刷脏)。Buffer Pool 的使用大大提高了读写数据的效率,但是也带来了新的问题:如果 MySQL 宕机,而此时 Buffer Pool 中修改的数据还没有刷新到磁盘,就会导致数据的丢失,事务的持久性无法保证。于是,redo log 被引入来解决这个问题:当数据修改时,除了修改 Buffer Pool 中的数据,还会在 redo log 记录这次操作;当事务提交时,会调用 fsync 接口对 redo log 进行刷盘。
产生的原因
利用顺序IO,避免写入数据的随机IO性能低的问题
redo log
D持久性
ACID实现原理
含有数据内容
聚簇索引
不含有数据内容
非聚簇索引
类型
减少层级
效率平衡
B+Tree
最左匹配原则
索引要尽量小,这样可以一次可以加载更多数据
性质
索引
InnoDB
MyISAM
存储引擎
CAT
ShardingJDBC
不支持sharding
第一种:磁盘读IO瓶颈,热点数据太多,数据库缓存放不下,每次查询时会产生大量的IO,降低查询速度 -> 分库和垂直分表
第二种:网络IO瓶颈,请求的数据太多,网络带宽不够 -> 分库
IO瓶颈
第一种:SQL问题,如SQL中包含join,group by,order by,非索引字段条件查询等,增加CPU运算的操作 -> SQL优化,建立合适的索引,在业务Service层进行业务计算
第二种:单表数据量太大,查询时扫描的行太多,SQL效率低,CPU率先出现瓶颈 -> 水平分表
CPU瓶颈
不管是IO瓶颈,还是CPU瓶颈,最终都会导致数据库的活跃连接数增加,进而逼近甚至达到数据库可承载活跃连接数的阈值。在业务Service来看就是,可用数据库连接少甚至无连接可用。接下来就可以想象了吧(并发量、吞吐量、崩溃)
一、数据库瓶颈
以字段为依据,按照一定策略(hash、range等),将一个库中的数据拆分到多个库中
概念
每个库的结构都一样
每个库的数据都不一样,没有交集
所有库的并集是全量数据
结果
系统绝对并发量上来了,分表难以根本上解决问题,并且还没有明显的业务归属来垂直分库
场景
库多了,io和cpu的压力自然可以成倍缓解
分析
1、水平分库
以字段为依据,按照一定策略(hash、range等),将一个表中的数据拆分到多个表中
每个表的结构都一样
每个表的数据都不一样,没有交集
所有表的并集是全量数据
一次SQL查询优化原理分析(900W+数据,从17s到300ms)
系统绝对并发量并没有上来,只是单表的数据量太多,影响了SQL效率,加重了CPU负担,以至于成为瓶颈。推荐:一次SQL查询优化原理分析
表的数据量少了,单次SQL执行效率高,自然减轻了CPU的负担
2、水平分表
以表为依据,按照业务归属不同,将不同的表拆分到不同的库中
每个库的结构都不一样
每个库的数据也不一样,没有交集
系统绝对并发量上来了,可以抽象出单独的业务模块
到这一步,基本上就可以服务化了。例如,随着业务的发展一些公用的配置表、字典表等越来越多,这时可以将这些表拆到单独的库中,甚至可以服务化。再有,随着业务的发展孵化出了一套业务模式,这时可以将相关的表拆到单独的库中,甚至可以服务化
3、垂直分库
以字段为依据,按照字段的活跃性,将表中字段拆到不同的表(主表和扩展表)中
每个表的结构都不一样
每个表的数据也不一样,一般来说,每个表的字段至少有一列交集,一般是主键,用于关联数据
系统绝对并发量并没有上来,表的记录并不多,但是字段多,并且热点数据和非热点数据在一起,单行数据所需的存储空间较大。以至于数据库缓存的数据行减少,查询时会去读磁盘数据产生大量的随机读IO,产生IO瓶颈
可以用列表页和详情页来帮助理解。垂直分表的拆分原则是将热点数据(可能会冗余经常一起查询的数据)放在一起作为主表,非热点数据放在一起作为扩展表。这样更多的热点数据就能被缓存下来,进而减少了随机读IO。拆了之后,要想获得全部数据就需要关联两个表来取数据。但记住,千万别用join,因为join不仅会增加CPU负担并且会讲两个表耦合在一起(必须在一个数据库实例上)。关联数据,应该在业务Service层做文章,分别获取主表和扩展表数据然后用关联字段关联得到全部数据
4、垂直分表
二、分库分表
sharding-sphere:jar,前身是sharding-jdbc
TDDL:jar,Taobao Distribute Data Layer
Mycat:中间件
三、分库分表工具
映射法
基因法
注:写入时,基因法生成user_id,如图。关于xbit基因,例如要分8张表,23=8,故x取3,即3bit基因。根据user_id查询时可直接取模路由到对应的分库或分表。根据user_name查询时,先通过user_name_code生成函数生成user_name_code再对其取模路由到对应的分库或分表。id生成常用snowflake算法
端上除了partition key只有一个非partition key作为条件查询
冗余法
注:按照order_id或buyer_id查询时路由到db_o_buyer库中,按照seller_id查询时路由到db_o_seller库中。感觉有点本末倒置!有其他好的办法吗?改变技术栈呢?
端上除了partition key不止一个非partition key作为条件查询
NoSQL法
后台除了partition key还有各种非partition key组合条件查询
1、非partition key的查询问题基于水平分库分表,拆分策略为常用的hash法
注:用NoSQL法解决(ES等)
2、非partition key跨库跨表分页查询问题基于水平分库分表,拆分策略为常用的hash法
注:扩容是成倍的
水平扩容库(升级从库法)
第一步:(同步双写)修改应用配置和代码,加上双写,部署;
第二步:(同步双写)将老库中的老数据复制到新库中;
第三步:(同步双写)以老库为准校对新库中的老数据;
第四步:(同步双写)修改应用配置和代码,去掉双写,部署;
水平扩容表(双写迁移法)
3、扩容问题基于水平分库分表,拆分策略为常用的hash法
四、分库分表问题
为什么要分库分表
主从、分库分表、添加缓存
架构优化
只搜索需要的字段
优先使用ID查询
条件使用索引
SQL语句优化
联合索引、索引的离散度分析
索引优化
SQL优化
适当冗余
表设计优化
连接数优化
配置优化
数据库优化
数据库优化思路
MySQL
raw
int
embstr
数据结构
适用场景
ziplist
hashtable
对象,更省空间
Hash
quicklist
消息队列
List
intset
签到、点赞
Set
只记录长度不记录内容
实现简单
以空间换时间
高度是随机的
当我们插入或删除节点时,会移动节点进行平衡操作,这导致在并发场景中难以进行合理粒度的同步。
SkipList 结构则要相对简单很多,通过层次结构提高访问速度,虽然不够紧凑,空间使用有一定提高(O(nlogn)),但是在增删元素时线程安全的开销要好很多。
为什么使用红黑树
skiplist
排行榜
ZSet
常用结构
Geo
布隆过滤器
Bitmap
每日访问用户
UV等
基数统计
Hayperloglog
进阶结构
分布式锁
分布式缓存
封装了通用的数据结构
Redisson
命令齐全
单线程,线程安全问题,线程池
Jedis
Lutuce
客户端
bgsave
连接
同步
命令传播
主从数据同步
主从同步
主从
server
Raft算法
服务下线
故障转移
Sentinel
Redis Sentinel
16384个slot,每个节点管理一些slot。
数据分片
虚拟节点,避免单点问题
一致性hash
Redis Cluster
集群
ZeroCopy
事件驱动
单线程
数据结构简单
高性能
丰富的数据类型
没1s记录一次日志
命令聚合
恢复慢
数据压缩
文件大
append到缓冲区以后调用系统fsync操作同步到aof文件中,fsync操作完成后主线程返回;
always
不对aof文件做fsync同步,同步到硬盘操作由操作系统负责,通常同步周期最长30秒
no
表示命令写入aof缓冲区后调用操作系统write操作,write操作完成后主线程返回,由专门的线程每秒去进行fsync同步文件操作
everysec
模式
日志重写
日志压缩
优化
AOF
LZF压缩算法
文件小
恢复快
丢失数据大,不能做到秒级
每次调用bgsave都需要fork子进程,fork子进程属于重量级操作,频繁执行成本较高;
RDB
持久化
执行命令是单线程的,其他过程是多线程的
Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。
处理网络数据的读写和协议的解析,而执行命令依旧是单线程
亮点
MULTI
EXEC
DISCARD
WATCH
链表+HashMap实现
从所有的key中随机选择N个(N可以配置)要么从所有的设置了过期时间的key中选出N个键,然后再从这N个键中选出最久没有使用的一个key进行淘汰。所以准确度不高,但是执行效率高!
设置lru的值,不是获取的当前时间的时间戳,而是设置的为全局的server.lruclock的值。
这里值得注意的是全局时钟只有24位,按秒为单位来表示才能存储194天,所以可能会出现key的时钟大于全局时钟的情况,如果这种情况出现那么就两个相加而不是相减来求最久的key。
redis原理
LRU
LFU
RANDOM
allkeys-random
allkey-lru
将要过期的key
volatile-ttl
volatile-lru
volatile-random
当内存使用超过配置的时候会返回错误,不会驱逐任何键
noeviction
分类
内存淘汰策略
每个设置过期时间的key都会创建一个定时器,到期后立即清除。这个方案的问题是会有太多的定时器,比较消耗CPU。
主动过期
只有当访问一个key的时候,才会判断这个key是否已经过期,过期则删除。这个方案的问题是如果有大量的过期key没有被访问,岂不是一直占用着内存空间。
惰性过期(使用)
每隔一段时间扫描数据库中的expires字典中的key,清除掉已经过期的。可以看到这是一个折中方案,是在CPU和内存中取得了一个平衡。
被动过期(使用)
内存过期
缓存null
加锁,只能一个线程访问
解决办法
缓存穿透
随机过期时间,不要同时过期
预热加载
缓存雪崩
加锁同步
缓存击穿
写操作:删缓存、修改数据、删缓存
缓存污染
缓存风险
其他
采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
Redis
高拓展
顺序写
批量写
异步处理
Broker
partions
Topic
In-Sync Replicas 能够和 leader 保持同步的 follower + leader本身 组成的集合
ISR
Out-Sync Relipcas 不能和 leader 保持同步的 follower 集合
OSR
AR
ack=all
Producer
手动ack
Consumer
消息丢失
写入到同一个Partition,一个Consumer消费
消息顺序
开启ProducerId
幂等支持
消息重复
引入TC,进行2PC提交
消息事务
Kafka
NameServer
queue
MessageQueue
ConsumerQueue
commit.log
放到同一个queue
有序消息
事务消息
1 所有的延迟消息到达broker后,会存放到SCHEDULE_TOPIC_XXX的topic下(这个topic比较特殊,对客户端是不可见的,包括使用rocketmq-console,也查不到这个topic)2 SCHEDULE_TOPIC_XXX这个topic下存在18个队列,每个队列中存放的消息都是同一个延迟级别消息3 broker端启动了一个timer和timerTask的任务,定时从此topic下拉取数据,如果延迟时间到了,就会把此消息发送到指定的topic下,完成延迟消息。
所有的延迟消息由producer发出之后,都会存放到同一个topic(SCHEDULE_TOPIC_XXXX)下,不同的延迟级别会对应不同的队列序号,当延迟时间到之后,由定时线程读取转换为普通的消息存的真实指定的topic下,此时对于consumer端此消息才可见,从而被consumer消费。
延时消息
同步刷盘
异步刷盘
ack
手动回执
减少生产
限流
Consumer与ConsumerQueue一一对应,如果Queue不够多,则扩容也无解
扩容
将一些日志消息丢弃
丢弃
消息积压
RocketMQ
fanout
direct
topic
headers
消息类型
RabbitMQ
MQ
PecificA
一致性算法
正排索引根据ID查找内容,根据内容查找ID
分词
什么是倒排索引
相关概念
倒排索引
ES
无固定的结构、灵活拓展
非关系型数据库
基于文档
支持全文搜索,地理位置索引
水平伸缩性
自动分片部署数据到机器集群的功能
自从MongoDB 3.0推出以后,MongoDB引入了一个高性能的存储引擎WiredTiger,并且它在数据压缩性能上得到了极大的提升,跟之前的MMAP引擎相比,压缩比至少可增加5倍以上,可以极大地改善磁盘空间使用率。
不支持事务
不支持join
MongoDB
真正的面向列的 DBMS
磁盘存储的数据
天然支持分布式
数据不仅按列存储,而且由矢量 - 列的部分进行处理,这使开发者能够实现高 CPU 性能
没有完整的事务支持,
缺少完整的Update/Delete操作,缺少高频率、低延迟的修改或删除已存在数据的能力,仅能用于批量删除或修改数据
Clickhouse
大:一个表可以有上亿行,上百万列。
面向列:面向列表(簇)的存储和权限控制,列(簇)独立检索。
稀疏:对于为空(NULL)的列,并不占用存储空间,因此,表可以设计的非常稀疏。
无模式:每一行都有一个可以排序的主键和任意多的列,列可以根据需要动态增加,同一张表中不同的行可以有截然不同的列。
数据多版本:每个单元中的数据可以有多个版本,默认情况下,版本号自动分配,版本号就是单元格插入时的时间戳。
数据类型单一:HBase中的数据都是字符串,没有类型。
Hbase
promethus
中间件
轻量
安全
稳定
Kong
Zuul2.0
SpringCloudGateway
网关
zookeeper
nacos
服务注册
服务发布
服务发现
FailFast
服务容错
令牌桶策略
漏斗策略
基于Guava实现
单体实现
基于Lua实现
集群实现
sentinel
成熟方案
服务限流
服务降级
服务治理
Promethus
Alter-Manager
告警
度量(Metrics)
Graylog
日志(Logging)
Pinpoint
Skywalking
链路(Trace)
服务监控
分支主题
Regisger
Provider
异步线程,定时存储
Monitor
角色
架构
服务提供、服务调用、连接处理、通信协议、序列化方式、服务发现、服务路由、日志输出
核心功能
失败自动切换到其他服务器(默认)
failover
快速失败,立即报错
failfast
失败安全,直接忽略
failsafe
失败后记录下来,定时重试
failback
并行调用多个,一个成功就成功
forking
容错机制
dubbo
thrift
框架
F5
四层协议
Nginx
LVS
HAProxy
七层协议
计算机协议
随机
轮训
加权轮训
最小连接数
hash
算法
负载均衡
日志规范gelf规范
命名规范
路径规范
滚动策略
日志格式
日志规范
应用日志输出
Filebeat
日志收集
缓冲
加工聚合
存储
查询分析
日志分析
微服务
一致性C
可用性A
分区容错性P
解释
CP
AP
CAP
基本 B
可用 S
软状态 S
最终 E
因果一致性
读己之所读
会话一致性
表现形式
BASE
分布式理论
准备阶段(Prepare)
提交阶段(commit)
协调者单点问题
数据不一致问题
阻塞问题
2PC
尝试阶段(canCommit)
提交阶段(doCommit)
实现复杂
3PC
一致性理论
Basic Paxos
Etcd
Console
使用案例
Raft
Zookeeper
ZAB
Gossip
Multi Paxos
Paxos
分布式一致性算法
资源管理器RM
事务管理器TM
应用
XA
同步阻塞问题
一致性问题
单点问题
效率问题
理论基础
全局事务协调器
SEATA
基于数据补偿替换回滚
SAGA
本地事务发起,发送一个MQ消息,本地事务提交则提交这个MQ消息,消费端可消费
最终一致,可能会出现不一致
依赖中间件
HalfMessage
Try,Commit,Cancel
用户操作灵活
优点:
代码耦合度高
缺点:
TCC
分布式事务
基于MySQL的锁
基于mysql
基于redis的setnx expire
需要考虑超时情况
释放锁没有通知,需要客户端自旋
setnx与expire非原子操作,可能会出现没有过期的情况
基于Redis
基于ZK的临时节点,事件通知
每次要持久化数据,效率低
基于Zookeeper
实现方案
1、有序分配
2、银行家算法
不可剥夺
设置超时
循环等待
互斥
无法获取就释放
请求和保持
条件
死锁的条件
全局唯一
自动递增
高可用
要求
mongodb
mysql
Snowflake
分布式ID
分布式
阻塞
非阻塞
异步
BIO
Channel
Buffer
Selector
Reactor模式
多路复用模型
NIO
AIO
形式
IO模型
用户态
内核态
所谓0Copy是指减少从内核态到用户态的Copy
减少一次copy,但是还有一次上下文切换
适用于小数据量读写
mmap
减少一次上下文切换
适用于大文件传输
可用于DMA方式,减少CPU拷贝
sendfile
select
poll
epoll
(1)select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。(2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。
IO多路复用
HTTP
TCP/IP
网络
需要多次磁道寻址
随机IO
顺序IO
IO
SSD使用电学信号,寻址更快
SSD与机械硬盘
操作系统
计算机基础
O(1)
图的遍历
树的遍历
DFS/BFS
O(n)
二分查找
O(logn)
nO(logn)
O(n2)
O(n3)
O(n!)
O(2^n)
数组的长度
递归的深度
空间复杂度
二叉树
矩阵
O(nlogn)
归并排序
主定理
1、5-10分钟阅读题目
2、写出实现思路
3、尝试去实现
4、阅读官方实践,对比实现方案
5、反复练习5次
练习方法
add
remove
连续内存
固定大小
越界
数组(Array)
数组
next
单向链表
previous
双向链表
循环链表
内存分散,不利于CPU缓存
容易造成内存碎片
链表反转
链表(LinkedList)
public void preOrderTraverse1(TreeNode root) { if (root != null) { System.out.print(root.val + \"->\"); preOrderTraverse1(root.left); preOrderTraverse1(root.right); } }
递归
public void preOrderTraverse2(TreeNode root) { Stack<TreeNode> stack = new Stack<>(); TreeNode node = root; while (node != null || !stack.empty()) { if (node != null) { System.out.print(node.val + \"->\"); stack.push(node); node = node.left; } else { TreeNode tem = stack.pop(); node = tem.right; } } }
非递归
前序
public void inOrderTraverse(TreeNode root) { if (root != null) { inOrderTraverse(root.left); System.out.print(root.val + \"->\"); inOrderTraverse(root.right); } }
public void inOrderTraverse(TreeNode root) { Stack<TreeNode> stack = new Stack<>(); TreeNode node = root; while (node != null || !stack.isEmpty()) { if (node != null) { stack.push(node); node = node.left; } else { TreeNode tem = stack.pop(); System.out.print(tem.val + \"->\"); node = tem.right; } } }
中序
public void postOrderTraverse(TreeNode root) { if (root != null) { postOrderTraverse(root.left); postOrderTraverse(root.right); System.out.print(root.val + \"->\"); } }
后序
遍历
AVL
常用算法
Trie
散列表
贪心算法
分治算法
回溯算法
动态规划
算法思想
快速排序
计数排序
后一个元素和前一个元素进行比较
思想
图解
冒泡排序
public static void shellSort(int[] arr) { int length = arr.length; int temp; for (int step = length / 2; step >= 1; step /= 2) { for (int i = step; i < length; i++) { temp = arr[i]; int j = i - step; while (j >= 0 && arr[j] > temp) { arr[j + step] = arr[j]; j -= step; } arr[j + step] = temp; } } }
希尔排序
优化方式
找到比指定元素小的元素位置进行替换
插入排序
找到最小的元素放在最前面
不稳定
选择排序
堆排序
排序
桶排序
迭代
广度优先
深度优先
数据结构与算法
号码调度
号码绑定
手动解绑
延迟消息
自动解绑
号码解绑
先取消,然后再绑定
AXB续绑
凌晨续绑
ElasticJob任务续绑
XB续绑
续绑
MySQL数据存储
绑定关系
Redis缓存
事件通知
延迟消息解绑
数据状态
话单数据
ElasticJob
技术栈
自如寓接听率底下,且无法知道中间状态,以及接听率底下的原因
需求:提升接听率,且将数据线上化
背景
1、接听率提升60%-90%
2、数据线上化、一键切换供应商,保证业务高可用,绑定不上用原号码兜底,且异常日志输出会第一时间知道具体原因
2、Saas平台化建设多个业务线使用
producer要将一个延迟消息发送到某个Topic中Broker判断这是一个延迟消息后,将其通过临时存储进行暂存。Broker内部通过一个延迟服务(delay service)检查消息是否到期,将到期的消息投递到目标Topic中。这个的延迟服务名字为delay service,不同消息中间件的延迟服务模块名称可能不同。消费者消费目标topic中的延迟投递的消息
延迟消息原理
3、技术平台化建设,延迟消息平台
项目成果
AB号项目
日志格式:gelf
文件名称
收集阶段:日志规范SDK
http上报
采集阶段
处理阶段
一个业务线一个topic
流转阶段:kafka
存储阶段:ES
对整个日志做了管理,包括采集、流转和存储
查询阶段:graylog
日志平台
FlinkSQL平台
数据图表
MapState
ListState
BroadcastState
state
Tumbling Windows(不重复)
Sliding Windows(重复的)
Session Windows(会话窗口)
自定义窗口
window
处理时间 Processing Time
事件时间 Event Time
提取时间 Ingestion Time
实现watermark的机制
time:乱序数据处理
为了保证一致性
checkpoint
Exactly-Once
基本原理
Client
JobManager
TaskManager
Client 用来提交任务给 JobManager,JobManager 分发任务给 TaskManager 去执行,然后 TaskManager 会心跳的汇报任务状态。
基本概念
数据源拓展
自定义函数
功能开发
Flink
完备的DBMS功能
按列存储与按行存储相比,前者可以有效减少查询时所需扫描的数据量
按列存储相比按行存储的另一个优势是对数据压缩的友好性
列式存储与数据压缩
向量化执行,可以简单地看作一项消除程序中循环的优化。
向量化执行引擎
关系模型相比文档和键值对等其他模型,拥有更好的描述能力,也能够更加清晰地表述实体间的关系。
关系模型与SQL查询
多样化的表引擎
多主架构
多线程与分布式
数据分片与分布式查询
clickhouse
日志查询分散,无统一的平台
日志接收有延迟,导致告警数据有延迟
数据实时分析需求
业务大盘项目
项目主要功能
在线客服项目
项目
java面试
0 条评论
回复 删除
下一页