Java面试知识点梳理
2021-04-06 18:06:15 12 举报
AI智能生成
自己面试复习过程中总结的常见模块的知识点,包括Java基础,计算机网略,Redis,ES,MQ等,也可作为Java学习扫盲使用。
作者其他创作
大纲/内容
集合
HashMap
1.7
数组+链表
头插法
1.8
数组+链表/红黑树
链表长度超过阈值(默认8)则转换为红黑树,如果链表长度小于6,则转换为链表
尾插
扩容机制
负载因子默认0.75,可调整,但不建议,负载因子越大哈希冲突的可能性越大
当长度大于size*0.75,则触发扩容,扩容大小为原来的2倍
大小总是2次幂,主要是因为HashMap计算元素位置采用位运算,2次幂更方便计算,并且可均匀分布
重写equals必须重写hashcode
线程不安全
想要线程安全需要加synchronized,但是不推荐,一般用ConcurrentHashMap
线程安全还有一个HashTable
HashSet是唯一的,底层实现还是HashMap
LinkedHashMap
数组+链表+双向链表
继承了HashMap
实现了插入有序
ConcurrentHashMap
线程安全
1.7
数组+链表
segment分段锁实现同步
继承reentranLock
volatile修饰节点指针
1.8
数组+链表/红黑树
通过部分加锁和利用CAS来实现同步,get时候不加锁,Node时都用volatile修饰
CAS失败通过自旋保证成功
ArrayList
数组
查询快,增删慢
动态扩容
默认大小10,grow方法每次扩容为1.5倍
扩容后arraycopy
cypyOf方法优化后,CPU对内存可以块操作,增删也不慢
线程不安全
保证线程安全可以用Collections包装
CopyOnWriteArrayList线程安全
文件系统copy-on-write机制:原理类似懒加载,等用到了再分配,cow则是先复制一份再写入,保证数据完整性
add()方法实现:先lock锁,然后复制数组到新数组中,add到新数组中,array指向新数组
耗内存,只能保证数据的最终一致性,不能保证实时一致性
LinkedList
链表
增删快,查询慢
线程不安全
多线程
进程、线程
进程是程序运行的基本单位,拥有独立的堆和方法区
线程是比进程更小的单元,进程内的线程共享堆和方法区,每个线程拥有独立的程序计数器、虚拟机栈、本地方法栈
为什么程序计数器、虚拟机栈、本地方法栈是线程私有的?
程序计数器私有是为了能够准确记录每个线程的字节码执行地址,线程切换时能回到正确的执行位置。
线程创建时会创建一个虚拟机栈,内部栈帧StackFrame保存着线程执行需要的方法
本地方法栈与虚拟机栈同理,保存一些本地方法的调用,保证线程中局部变量不被其他线程访问到
虽然是线程私有的,但是也不一定是线程安全的
优缺点
多核cpu时代,使用多线程可以提高cpu使用率,减小线程上下文切换的开销。
但是多线程的缺点明显,内存泄漏、上下文切换、死锁等问题
线程生命周期和状态
NEW(初始化还未status)、RUNNABLE(运行中)、WAITNG(等待)、TIMEWAITNG(超时等待)、BLOCKED(阻塞)、TERMINATED(终止)
sleep()和wait()方法主要区别就是:sleep不释放锁,wait释放锁
上下文切换
当程序从保存到再加载的过程被称为一次上下文切换
死锁
多个线程被阻塞,一个或多个线程都在等待某个资源的释放,造成无限期的阻塞
产生死锁的四个条件
互斥、请求保持、不剥夺、循环阻塞
synchronized
解决多线程之间资源访问的同步性
前期是重量锁,java1.6优化后减少了锁操作的开销
修饰静态方法,或修改代码块,则都是给类加锁
修饰实例方法,是给对象实例加锁
构造方法不能使用synchronized修饰,因为构造方法本来就是线程安全的
原理:
修饰代码块主要是jvm字节码指令monitorenter 和 monitorexit 来记录开始和结束,结合锁计数器来实现同步
修饰方法主要是ACC_SYNCHRONIZED标识,标识指明该方法是一个同步方法
本质都是对对象监视器monitor的获取。
volatile
防止jvm 指令重排
保证变量的可见性
当共享变量在发生写操作时,会更改主存数据,并将其他缓存强制失效。
不能保证原子性
synchronized与volatile区别
volatile 只能修饰变量,sync能修饰方法和代码块
volatile 是轻量级的,比sync执行效率要快
volatile 只能保证数据的可见性,不能保证原子性,sync都可以保证
volatile 是解决变量在多线程之间的可见性,sync是保证资源在多线程之间的同步性
ThreadLocal
ThreadLocalMap是ThreadLocal的静态内部类,通过给每个线程都分配一个变量副本达到数据隔离的效果,来保证线程的安全
与synchronized相比
ThreadLocal是数据隔离,sync是锁机制保证变量只被一个线程访问,是数据共享
内存泄漏
因为key是弱引用,value是强引用,当没有被引用时,key可能会被垃圾回收机制清理掉,只留value永久保存,造成内存泄漏。
解决办法就是使用完ThreadLocal方法后手动调用remove()
Lock
ReentrantLock可重入锁
可设置为公平锁
提供了比synchronized更多的功能,但是Lock不主动释放锁,容易造成死锁,资源竞争激烈的情况下性能可以维持常态。
ReadWriteLock
ReentrantReadWriteLock
WriteLock写锁
ReadLock读锁
线程池
提高资源利用率
实现Runnable接口和Callable接口区别
Runnable接口不返回结果或抛出异常
Callable可以
执行execute()方法submit()方法区别
execute方法不返回任务是否被线程池执行成功的结果,submit方法返回Future类型对象可判断是否执行成功
ThreadPoolExecutor
new FixedThreadPool
返回一个固定数量的线程池,没有空闲线程使用的任务在队列中等待
new SingleThreadExecutor
返回只有一个线程的线程池,其他在队列中等待
new CachedThreadPool
可根据实际情况调整线程数量的线程池,没有空闲线程执行任务时会创建线程处理任务
参数
corePoolSize核心线程数
最小可以同时运行的线程数
maximumPoolSize最大线程数
当队列容量满时,线程数变为最大线程数
workQueue
任务来时判断运行线程数是否达到核心线程数,达到的话任务进入队列
keepAliveTime等待线程销毁时间
当线程数量大于核心线程数时,等待时间超过keepAliveTime后销毁多余线程
unit
keepAliveTime时间单位
handler饱和策略
饱和策略
默认ThreadPoolExecutor.AbortPolicy,抛出错误拒绝新任务处理
ThreadPoolExecutor.CallerRunsPolicy,伸缩队列,延迟处理任务,不丢弃任务,但是影响性能。
JUC
java.util.concurrent包
包含了多线程并发常用的工具类,比如线程池、异步IO、轻量级任务框架等。
4类原子类型
基本类型(AtomicInteger整形、长整型、布尔型原子类)
数组类型
引用类型
对象属性修改类型
AQS
是一个构建锁和同步器的架构
ReentrantReadWriteLock、SynchronousQueue等都是基于AQS的
原理
多个线程共享一个空闲资源时,其中某个线程获取到后会被锁定,其他线程被放到了CLH队列锁中阻塞等待,CLH是个双向队列,当资源state被更改为0时,队列中其他线程获取资源。
AQS中state的原子性是通过CAS实现的,state又是被volatile修饰的,保证了可见性
对资源的共享方式
Exclusive独占
ReentrantLock为例,独占资源通过state判断,等于0才被释放,重入锁则state+1
Share共享
CountDownLatch为例,共享资源初始state=n为n个线程,当state=0则任务完成
CountDownLatch
允许count个线程阻塞在一个地方,直到所有线程执行完才继续下面的执行逻辑
计算机网络
TCP/IP网略体系结构
物理层
机械、电子等原始比特流传输
数据链路层
将原始比特流转化成逻辑传输线路
网略层
选择合适的网略路由和节点,确保数据的及时传输
IP/IPv6协议
传输层
负责两台主机进程之间的通信传输
TCP、UDP
会话层
不同机器之间用户建立和管理会话
RPC远程调用协议、SSL、TLS安全协议
表示层
对信息进行加密解密、压缩解压缩
应用层
为不同的网略应用提供不同的网略协议
HTTP、FTP、SMTP
TCP、UDP
TCP三次握手
客户端--->发送SYN标志的数据包--->服务端
服务端--->发送SYN/ACK标志的数据包--->客户端
客户端--->发送ACK标志的数据包--->服务端
TCP四次挥手
客户端--->发送FIN,关闭数据传输--->服务端
服务端--->发送ACK,收到序号加1--->客户端
服务端--->发送FIN,关闭与客户端的连接--->客户端
客户端--->发送ACK,收到序号加1--->服务端
TCP,UDP区别
TCP是面向连接的、数据传输可靠、传输形式是字节流、传输效率慢、所需资源较多、常用于文件邮件传输
UDP不面向连接、数据传输不可靠、传输形式是数据报文、传输效率快、所需资源少、常用于语音、视频、直播等
TCP保证数据传输的方法
切割应用数据为合适的数据块
给数据块编号并排序
校验和,确保端对端一致
利用滑动窗口实现流量控制
拥塞控制减少数据的传输
ARQ协议
超时重发
浏览器输入url到页面展示的过程
浏览器通过DNS解析查找url映射的ip地址
浏览器和对应ip的服务端进行TCP连接
浏览器发送HTTP请求,cookies会随请求发送给服务端
服务端接收请求参数等进行处理,返回HTTP报文
浏览器解析报文进行渲染展示
连接结束
HTTP超文本传输协议
各版本连接方式
1.0默认短连接
1.1默认长连接
HTTP2实现了多路复用,头部压缩、服务器推送。
HTTPS
对称加密、非对称加密、数字签名、数字证书
cookie
主要用来跟踪浏览器用户身份的会话
服务端分配了cookie后,以key-value形式存放到浏览器
浏览器存放容易被劫持,不安全
默认随着会话关闭,cookit失效,但是可以手动设置过期时间
session
通过服务端记录用户的状态
存放在服务器,用sessionid存到cookie中来区分客户端身份
较安全,但是用户量大会占用服务器资源
过期销毁,或者invalidate销毁
token
字符串,存放在cookie中
无状态,可扩展,对服务器压力小
无法主动过期
JVM
内存模型
堆
线程共享
是java虚拟机中占用内存最大的一块区域,主要存放对象实例和数组
是GC管理的主要区域,利用分代回收机制分为 新生代、老生代、永生代(1.7以后移除了)
方法区
线程共享
主要存储虚拟机加载的类、常量、静态变量、class文件代码
1.8以前是GC的永生代,1.8以后移除替换为元空间,主要是因为元空间是直接内存,出现内存溢出的几率下降
运行时常量池是方法区的一部分,但是1.7以后字符串常量池放到了堆中
栈
虚拟机栈
线程私有
由栈帧组成,每个栈帧包含:局部变量表、操作数栈、动态链接、方法出口等。
函数调用则压栈,调用结束则弹栈。
函数调用则压栈,调用结束则弹栈。
主要为虚拟机执行java方法,也就是字节码服务
两种Error
StackOverFlowError栈溢出
线程请求栈的深度超过了虚拟机栈的最大深度
OutOfMemoryError内存溢出
本地方法栈
线程私有
主要为虚拟机执行本地Native方法服务。与虚拟机栈相似
程序计数器
线程私有
保证程序代码运行顺序。
多线程情况下,记录当前线程执行位置,这也是线程私有的原因。
多线程情况下,记录当前线程执行位置,这也是线程私有的原因。
创建(new)对象的过程
类加载检查
检查指令的参数是否在常量池中定位到类的符号引用,是否已被加载过、解析或初始化过。
分配内存区域
在堆中分配的两种方式
指针碰撞
适用于堆内存规整,没有内存碎片的情况下。GC收集器算法为“标记--整理”
原理:将用过的内存和没用过的内存分开整理,用指针做分界线,分配给对象的内存大小作为指针移动的大小
空闲列表
适用于堆内存不规整情况下。GC收集算法为“标记--清除”
原理:虚拟机会维护一个列表,记录哪些内存块是可用的,分配给一块足够大的内存块给该对象
多线程下保证线程安全的方式
CAS+失败重试 的方法保证原子性
TLAB 预分配一块内存,当对象所需内存大于TLAB中剩余内存或者用尽时,使用CAS方式分配内存
初始化零值
将内存空间内的对象字段数据类型初始零值
设置对象头
设置基本信息到对象头中,比如 实例的类、对象的哈希值、对象GC分代年龄等
执行init方法
类加载机制
生命周期
加载、验证、准备、解析、初始化、使用、卸载
双亲委派机制
类加载器接收到请求,首先不会自己去加载,而是将请求委派给父加载器完成,每层加载器都这样,直到Bootstrap类(启动类)加载器中,父类反馈自己无法加载请求时,子类才会去加载
好处是java类具备了优先级关系,满足java体系建设
分代回收算法
新生代
Eden 区、From s0区(Survior0)、To s1区
老年代
永久代/元空间
垃圾回收算法
标记-清除
标记不需要清除的对象,然后清除其他对象,会造成大量不连续的内存碎片
效率低,浪费空间
标记-复制
将内存分割成大小相同的两块区域,对一块区域被引用的对象进行标记,将标记对象复制到另一块区域中,然后对元区域对象进行全部清除
解决了效率问题,时间换空间,适用于新生代,也是为什么新生代GC效率比老年代高的原因
标记-整理
对有效的对象进行标记,然后整理挪动到一边,对边界以外的对象进行GC
合理利用了空间,但是效率低,适用于老年代这种不需要频繁GC,但需要空间的内存区域
GC分类
Minor GC
对新生代进行垃圾收集
Major GC
对老年代进行收集,但有的语境下可能是整堆回收
Full GC
整堆回收
垃圾回收器
Serial回收器
单线程回收器,回收过程中,必须暂停其他所有的工作线程
简单高效,但是会影响其他线程
新生代 标记-复制,老年代 标记-整理
ParNew回收器
多线程回收器,是Serial的多线程版本,回收线程执行中也会阻塞用户线程
常用于Server模式下虚拟机,可以与CMS收集器配合使用
新生代 标记-复制,老年代 标记-整理
Parallel Scavenge回收器
1.8的默认回收器,多线程的
高效率的利用了CPU
新生代 标记-复制,老年代 标记-整理
CMS回收器
并发收集器,回收线程和用户线程可并发执行
优点是并发收集,低停顿。缺点是 对CPU资源敏感、会产生大量空间碎片,无法处理浮动垃圾。
标记-清除算法
G1
并发收集器,高概率满足GC停顿要求,还保证了高吞吐量
保留了分代的概念,并且可设置预留停顿时间
标记-整理算法
jvm内存调优
-Xmn 调整新生代堆内存大小
-Xms 设置最小堆空间;-Xmx 设置最大堆空间
-XX:MaxTenuringThreshold 设置对象晋升到老年代年龄的阈值
看是否存在更多的持久对象和临时对象
观察一段时间内老年代的峰值,适当调整新生代
线程默认开启1M的堆栈,设置为500k够用
调优原则围绕减少GC来
堆内存中对象分配的基本策略
优先分配到Eden新生代中
分配担保机制
遇到Eden内存满了,但是s代中也没有内存分配时会提前放到老年代中
遇到大对象比如字符串或数组时会直接放到老年代中,为了避免为大对象分配内存时触发分配担保机制带来的复制降低效率
判断对象死亡的方法
引用计数法
给对象加个计数器,被引用就+1,引用失效就-1,=0则未引用
可达性分析
都从从GC boots 对象根节点向下查找,没有被引用链关联的对象则判断为未引用对象。
性能调优
OOM
内存泄漏
线程死锁
锁征用
Java进程消耗CPU过高
JVM性能检测工具
jvisualvm
Jconsole
可视化工具
Jprofiler
MAT
Spring
设计模式
工厂模式
Spring使用工厂模式可以通过BeanFactory或ApplicationContext创建bean
代理模式
Spring AOP 通过动态代理的方式实现,
单例模式
Spring中Bean都是单例的
通过ConcurrentHashMap单例注册表实现单例
好处:节省创建对象所花费的时间,减小系统开销,减轻GC压力。
适配器模式
解决了不兼容类之间共同工作问题
Spring AOP 的advice通知、Spring MVC对 中 HandlerAdapter 适配器 对 Controller的适配
观察者模式
对象行为类模式,即一个对象依赖另一个对象的变动而变动
Spring 事件驱动模型
事件角色
ApplicationEvent抽象类
监听器角色
ApplicationListener接口
发布者角色
ApplicationEventPublisher接口
Spring 包含的模块
Core
基础类库,包含基本所有功能,提供IOC依赖注入功能
,比如beans、core、context
,比如beans、core、context
AOP
提供面向切面编程
JDBC
数据库连接
JMS
java消息服务
Web
为创建web应用程序提供支持
Spring AOP & IoC
AOP
静态代理
实现类
动态代理
JDK 动态代理
实现接口
java反射机制生成一个代理接口的匿名类
调用具体方法的时候调用invokeHandler
cjlib
asm字节码编辑技术动态创建类,基于classLoad装载
修改字节码生成子类去处理
常用于事务处理、日志管理、权限管理
Spring AOP 与 AspectJ区别
Spring AOP是运行时增强,AspectJ是编译时增强
切面多时,AspectJ性能较好
IoC
控制反转,是一种设计思想,即将原本程序中手动创建对象的控制权交给Spring框架处理
IoC容器是IoC实现的载体,其实就是一个Map,存放各种对象
IoC容器通过实现工厂模式,将需要创建的对象通过注解方式配置好,IoC容器会在程序需要的时候自动创建对象
DI,依赖注入,跟IoC是同一个概念不同的描述,
Bean
生命周期
1,实例化Bean(Instantiation)
加载元数据
Spring启动扫描xml/注解/JavaConfigBean信息,放到BeanDefinition中,通过BeanDefinition描述对象信息,然后以BeanName为key,BeanDefinition为value,放到BeanDefinitonMap中,遍历这个map,执行BeanFacttoryPostProcessor这个Bean工厂后置处理器。
实例化对象
Spring通过反射把对象实例化
2,注入属性(Populate)
3,初始化
3.1,检查Aware的相关接口并设置相关依赖
比如想要获取Spring Bean,可以使用工具类实现ApplicationContextAware接口获取Bean
3.2,BeanPostProcessor中前置处理
before方法
3.3,是否实现InitializingBean接口
@PostConstruct就是initiali实现类
3.4,是否配置自定义的init-method
3.5,BeanPostProcessor后置处理
after方法
4,使用bean
5,销毁(Destruction)
5.1,是否实现DisposableBean接口
5.2,是否配置自定义的destory-method
作用域
singleton
唯一bean实例,默认单例的
prototype
每次请求都会创建一个bean,是多例的
request
每次http请求时,都会创建一个Bean,仅适用与WebApplicationContext环境
session
同一个Http Session共享一个Bean,不同的Session拥有不同的Bean,仅使用与WebApplicationContext坏境
将类声明为Spring 的bean的注解
@Autowired
自动装配bean
@Component
作用与类上
@Bean
作用于方法上
Spring MVC
@Controller 与 @RestController 区别
@Controller
返回一个页面
参数需要引用@ResponseBody才能返回JSON
@RestController
返回JSON或XML形式数据
原理
1,用户通过浏览器发起请求,前端控制器接收到请求,调用HandlerMapping解析Handler
2,解析到Handler后(也就是Controller)交由处理器进行处理,执行相对应的逻辑,返回ModelAndView
3,视图解析器将ModelAndView解析成浏览器可以识别的view。
4,最后再通过前端控制器返回给浏览器
Spring 事务
管理事务的方式
编程式事务
声明式事务
基于xml、基于注解
事务并发可能造成的问题
脏读
读取了上一个事务还未提交的数据
不可重复读
事务多次读取同一个数据,中途另一个事务更新了数据,当时前后读取的数据不一致
幻读
事务多次读取同一个数据集,中途另一个事务新增了数据,导致前后读取的数据集不一致
事务的隔离级别
DEFAULT(默认)
后端数据库默认的级别。Mysql默认是REPEATABLE_READ 可重复读,Oracle 默认READ_COMMITTED
读已提交
读已提交
READ_UNCOMMITTED 读未提交
允许读取并发事务未提交的数据。可能导致 脏读、不可重复读、幻读
READ_COMMITTED 读已提交
允许读取并发事务提交后的数据。可能导致 不可重复读、幻读
REPEATABLE_READ 可重复读
对同一字段多次读取数据都是一致的,除非是被本身事务修改。可能导致幻读
SERIALIZABLE 可串行化
最高隔离级别。所有事务逐个执行。并发事务问题都可防止,但会严重影响性能,不建议使用。
事务传播方式
REQUIRED
支持当前事务
没有事务则创建事务。Spring默认的事务。
SUPPORTS
支持当前事务
当前没有事务,则以非事务的方式运行。
MANDATORY
支持当前事务
当前没有事务则抛出异常。
REQUIRES_NEW
不支持当前事务
当前有事务,则把当前事务挂起。
NOT_SUPPORTS
不支持当前事务
按非事务方式执行,当前有事务则把当前事务挂起
NEVER
不支持当前事务
按非事务方式执行,当前有事务则抛出异常。
NESTED
嵌套事务
当前存在事务,则创建一个事务作为当前事务的嵌套事务运行,当前不存在事务,则创建一个事务。
@Transactional
修饰类或者方法,则该类下所有方法都会服从事务,预到异常按相应配置的事务传播方式进行回滚。
@Transactional(rollbackFor = Exception.class) 不配置rollbackFor属性,则按运行时异常进行回滚,配置了rollbackFor属性,则事务可在遇到非运行时异常时也回滚。
Spring事务原理
本质是对数据库事务的支持,首先获取连接器连接数据库
Spring AOP通过动态代理,实现事务的开启和回滚
对有@Transactional 注解的类或方法的bean进行代理
事务执行线程是同步的
循环依赖
情况
属性依赖可以解决
构造器依赖解决不了
属性依赖解决原理
spring bean对象都需要经历两个阶段,初始化和实例化
getBean()是否存在,不存在则实例化对象,然后实例化对象过程中发现属性依赖,则初始化依赖对象
通过递归循环,拿到三级缓存中的 半成品实现bean的实例化,然后向上递归对依赖对象进行实例。
三级缓存
一级singletonObjects
日常实际获取Bean的地方
二级 earlySingletonObjects
已实例化,但还没进行属性注入,由三级缓存放进来
三级 singletonFactories
value是一个对象工厂
为什么是三级?
Bean是单例的,A对象依赖B对象是有AOP的,三级缓存可以拿到代理对象。如果只有二级,则需要先去做AOP代理。
Redis
单线程模型--文件事件管理器
组件
多个socket
建立请求和redis之间的连接
IO多路复用程序
轮询监听socket,分配到队列中
文件事件分派器
分派队列中的socket 到 对应的事件处理器
事件处理器
连接应答处理器(将AE_READABLE 事件 与命令请求处理器)
命令连接处理器(将从连接应答处理器过来的 socket 中的key_value 读取出来,并在内存中做设置,然后连接命令回复处理器)
命令回复处理器(给客户端回复响应,返回给redis 服务端的 AE_WRITABLE 事件对应响应)
命令连接处理器(将从连接应答处理器过来的 socket 中的key_value 读取出来,并在内存中做设置,然后连接命令回复处理器)
命令回复处理器(给客户端回复响应,返回给redis 服务端的 AE_WRITABLE 事件对应响应)
redis单线程高效率的原因
基于内存
基于非阻塞的IO多路复用程序机制实现,只做轮询监听,不做处理。
单线程避免了多线程频繁上下文切换的损耗,避免了一些数据不一致、死锁等并发问题。
数据类型
基础
String
SDS(简单动态字符串)
二进制安全
记录字符串本身长度
AOF缓存区
k-v结构
常用于计数场景:用户访问次数、热点文章的点赞数量
常用命令:set、get、exists、strlen、del
List
双向链表
常用于发布订阅场景,比如消息队列、慢查询。
常用命令:lpush、lpop、rpush、rpop、lrange
Hash
hash表
常用于存放对象信息,比如用户信息、商品信息等
常用命令:hset、hmset、hget、hkeys、hvals
Set
无序唯一集合
常用于实现交、并集的操作。比如微博之间 共同关注、共同粉丝、共同爱好等
常用命令:sadd、spop、smembers、scard
sorted set
有序唯一集合
常用于权重排序场景。比如直播中 实时直播间人数排行、礼物排行等消息排行
常用命令:zadd、zcard、zrange、zrevrange
高级
BitMaps
针对bit操作的集合,不是数据结构
常用于记录用户是否进行过搜索等
HyperLogLogs
计算唯一事物概率的数据结构。
常用于统计用户搜索次数
GFO
存储用户地理位置
过期策略
惰性删除
查到该key时检查是否过期,过期则删除
对CPU友好
定期删除
到达一定时间时,随机删除一些过期的key
对内存友好
内存淘汰机制
volatile-lru
从已设置过期时间的数据集中删除不常用的数据
allkeys-lru
从键空间所有key中,挑选不常用的key删除。较常用
lru算法
使用HashMap结合双向链表,HashMap值为链表节点,新增时添加Node到队尾,修改时修改Node对应的值并移动到队尾,查询则直接移动Node到队尾。队头则是最不常用的key。
持久化
rdb
快照模式,BGSAVE自动保存当前时间点的redis副本。
文件小,恢复数据快,可以将快照复制到其他从服务器中进行恢复。
容易丢失时间点后更改的数据,运行时宕机容易丢数据
aof
AOF文件。主要用来保存追加redis中执行的写命令到文件中,然后持久化到磁盘。
实时性好,保存的数据较全面,最多丢失一秒数据。持久化到磁盘。
文件大,恢复数据慢。
4.0后支持RDB和AOF混合持久化
高可用
集群
一主多备,读写分离
master复制写操作,slave负责读操作。redis高可用最少一主两备,slave可水平扩容
主从数据一致性
完全重同步
利用master rdb文件进行同步,新增的数据进入缓冲区,slase同步完rdb后同步缓冲区中的写数据。
部分重同步
一般用于slave机器宕机,导致和master机器数据不一致。
master和slave都有复制偏移量offset,如果不一致,从机会通过AOF文件中恢复部分写数据。
有一种情况是slave宕机期间,master机器换了。这种需要比较slave存放的runid记录master的ip是否前后一致。不一致则需要删除数据完全重同步。
哨兵
通过心跳监听master机器状态,若哨兵集群理性判断master宕机后,会选举slave机器为master。
脑裂问题
若因为网略问题,心跳监测不到master,哨兵以为master宕机,并重新进行了选举,这时会造成两个master都接收写命令,会造成数据的不一致。
解决
min-slaves-to-write 2,设置master下slave最小连接为两个
min-slaves-max-lag 10,设置slave同步master的数据的延迟时间最大为10秒,超出则不接收写操作。
常见问题
缓存雪崩
场景
某个时间里,大量缓存过期失效(热点缓存),这时有大量请求进来,就会穿透缓存直接访问数据库,造成数据库压力激增并宕机,
解决
发生前,给key设置随机过期时间
发生时,利用MQ进行限流,避免大量请求走数据库
发生后,重启宕机的redis服务,rdb和aof回复缓存数据
缓存穿透
场景
大量请求访问redis中不存在的值,导致直接穿透缓存访问数据库。
解决
设置过期时间较短的空缓存
布隆过滤器,判断访问的数据是否合法,不合法则直接抛出
原理:将元素放进布隆过滤器时,会计算哈希值,然后将位数组下标置为1。当元素进行访问时,计算哈希值,判断位数组下标是否为1,不是则抛出。
缓存与数据库数据一致性
先删缓存,再更新数据库
高并发下会出现脏数据
延时双删机制:删缓存、更新数据库、一秒后再删缓存。
缓存设置过期时间
先更新数据库,再删缓存(推荐)
避免高并发的下的脏数据,但是原子性不太好
Mysql
事务隔离级别
读未提交
读已提交
可重复读
串行化/序列化
存储引擎
InnoDB
事务性存储引擎
支持行级锁、表级锁
支持外键
支持MVCC
MVCC
多版本并发控制
读不加锁,读写不冲突。读多写少的场景下极大增加了系统并发性能
原理
版本链
利用隐藏字段DB_TRX_ID 记录不同事务Update时的版本,DB_ROLL_PT字段回滚指针将版本以先后顺序连接成Undo log链
DB_ROW_ID字段是为了给未声明主键的表自动生成隐藏主键。
一致性视图ReadView
读已提交
在每次查询前生成一个
可重复读
只在第一个查询时生成一个,后面的事务查询都基于这一个
log
binlog
记录数据库逻辑变更
事务提交后记录,多用于数据恢复
redolog
记录数据库物理变更
事务提交前记录,保证修改后的数据持久化磁盘后的数据一致性。实现了事务持久性
innoDB存储引擎层产生
unnolog
回滚日志文件
用于事务回滚,记录MVCC中数据更新版本。实现事务原子性。
索引
Hash索引
hash表
适用单一查询,范围查询速度慢。
B+树索引
多路平衡二叉树
相同层级节点之间指针互联,天然有序,范围查询不用全表扫描
聚簇索引
B+树叶子节点存储主键索引
非聚簇索引
B+树叶子节点存储非主键索引
索引覆盖
执行的查询语句数据在索引中就能查到
不走索引的情况
like % 模糊查询
索引列参与计算,使用了函数
where对null判断,where不等于
or操作有一个字段没有索引
锁
表锁
开销小,加锁快,不会死锁。
锁冲突概率高,并发低
行锁
开销大,加锁慢,会造成死锁
减少数据库操作冲突,并发高
innoDB默认行锁
Record Lock
对索引项加锁
Gap Lock
对索引项“间隙”加锁
Next-key Lock
锁定索引项和索引范围。
共享锁(读锁)
可以并发读取数据,未释放锁之前不能执行修改操作。
排它锁(写锁)
当前事务对数据加了写锁,该数据只支持当前事务读和写,其他事务不能对该数据进行加锁。
优化
大表查询优化
限定查询范围
查询语句必须带限制数据范围
读写分离
垂直拆分
根据列将表进行拆分成多表
简化表结构,减小列数据,减少IO
水平拆分
sql规范优化
充分利用存在得索引
禁止select *
避免子查询、避免关联太多的表
消息队列
作用
异步
多个业务系统接收到消息,可以异步处理相应逻辑
问题:消息消费成功则返回成功,但是可能其中有系统写库失败,就会造成数据不一致问题。
解耦
消息放到MQ中,其他系统可自由选择是否消费消息。
问题:外部系统对MQ依赖变强,如果MQ宕机可能导致多系统故障。
削峰
MQ在访问高峰时,拉取适量请求到数据库处理,起到削峰作用,挤压在MQ中的消息慢慢处理。
问题:消息挤压可能导致消息重复消费,消息丢失等问题,系统复杂性高。
RocketMQ
队列模型的分布式消息中间件,十万级以上吞吐量。阿里开发。
主题模式/发布订阅
通过使用一个Topic(主题)中配置多个队列并且每个队列维护每个消费者组的消费位置。
基本组成
Broker
主要负责消息的持久化。
高可用
Topic分片分别存放在不同Broker服务上,一个Broker存放多个Topic。多Broker部署可提高并发能力。
NameServer
相当于注册中心,早期是zk,后面改了。
主要用来管理Broker和路由信息。解耦。
消费者和生产者通过NameServer获取Broker地址并通信。
Broker定期发送心跳包(包含自身的Topic)
Producer
消息生产者
支持分布式集群部署
Consumer
消息消费者
支持分布式集群部署。支持push、pull模式对消息进行消费。
完整调用链路
producer 和NameServer节点建立长连接
定期从NameServer获取Topic信息
并向Broker master建立连接,发送心跳
发送消息给Broker Master
cunsumer 从Master 和Slave 一起订阅消息
高可用
支持的集群模式
多master
多master多slave异步复制
多master多slave双写
集群
NameService集群采用去中心化,一个broker发送心跳向所有的NameService。
Broker集群,采用主从,master/slave模式,同步/异步刷盘方式同步消息数据。
producer通过轮询的方式向每个队列中生产消息实现负载均衡。
consumer从broker中pull消息后,采用两种模式消费消息。
广播
一条消息发送给同一个消费组的所有消费者。
集群
一条消息发送给一个消费者。
刷盘机制(单个节点)
同步刷盘
持久化到磁盘的过程中需要等待刷盘成功的一个ACK
可靠性高,性能差,适用于金融等
异步刷盘
开启一个线程异步执行刷盘
降低读写延迟,提高性能,适用于验证码等场景
同步复制/异步复制(Broker主从模式下)
同步复制
消息同步双写到主从节点上才返回写入成功
异步复制
消息写到主节点上就返回写入成功。
RocketMQ不支持主从切换,所以如果主节点宕机,从节点消息会短暂消息不一致
顺序消费
Hash取模确定是同一个业务,放到同一个队列中
重复消费
幂等
对同一个消息的处理结果,执行多少次都不变
实现方式
redis 的key-value结构
通过数据库的唯一键保证不会重复插入
分布式事务
事务消息(half半消息机制)+事务反查
本地事务和存储消息到消息队列才是同一个事务。产生了事务的最终一致性,因为整个过程是异步的,每个系统只要保证它自己那一部分的事务。
消息堆积
增加消费者实例,并增加主题队列数量
Kafka
消息模型
发布、订阅模式
组成
producer、consumer、Broker
Topic
Partition分区
这是与RocketMQ最大的区别,kafka采用分区的概念,与RocketMQ队列的概念基本相同
多副本机制
分区的多个副本之间会有一个leader,其他副本称为follower,leader接收生产者消息,follower同步leader消息。
leader故障,会选举一个同步程度相同的follower当leader
提高容灾能力
ActiveMQ
万级中间件
支持高可用
社区慢慢不活跃了
RabbitMQ
万级中间件
友好的管理界面
erlang语言开发,不能定制化使用
dubbo
Netty
基于NIO的client-server客户端服务器框架
优点
统一API,支持多种传输类型。
简单而强大的线程模型。
自带编解码器包解决TCP粘包/拆包问题。
成熟稳定,提供异步高性能的通信,Dubbo,RocketMQ等都用到了Netty。
TCP粘包/拆包
TCP传输的是一串没有界限的数据,它会根据缓存区的实际情况进行包的划分,所以业务上的一个包可能拆开发送,就是“拆包”,多个小包封装成一个大的数据包发送就是”粘包“。
零拷贝
线程模型
Reactor单线程模型
一个线程独立处理所有IO操作。
高并发场景下,限于cpu压力,有使用瓶颈,而且存在系统隐患。
Reactor多线程模型
在处理链部分增加了线程池,多数场景下可以满足性能要求。
在一些特殊场景下,比如服务器增加安全认证,并且高并发的场景下,也会出现性能问题。
Reactor主从模型
将Reactor分为 mainReactor和 subReactor,mainReactor负责监听server socket,将socket分派给subRecetor处理。
Netty线程模型可以根据参数进行配置。
调用链路
服务暴露过程
服务引用
SPI
容错机制
降级
负载均衡
随机,按权重设置随机概率(默认)
轮询
最少活跃调用数,相同的随机
一致性Hash,相同参数的请求总发给同一个生产者
选举算法
注册中心
Zookeeper
协议
微服务
Spring Cloud
Eureka
注册中心
Eureka Client 将服务器信息注册进 Eureka Server
Eureka Server 通过注册表保存数据
Feign
服务调用
接口或类上使用@FeignClient注解,会调用Feign创建的动态代理对象
Ribbon
负载均衡
轮询算法访问
Hystrix
熔断器
Zuul
服务网关
转发请求,统一做降级、限流、认证等
分布式锁
场景:
Java单机可以通过API实现线程同步,但是分布式系统场景下,需要通过分布式锁保证各系统之间同线程方法调用后的数据一致性。
Zookeeper
通过zk的znode 目录的特性,临时有序节点来实现分布式锁
创建临时有序节点,/lock/目录
创建成功后判断节点是否是序号最小的节点,是则获取锁
不是的话监听上一级目录,上级释放锁时被唤醒,再判断是否是最小
Curator(zk开源的客户端,提供了分布式锁的实现)
acquire()、release()方法实现加锁释放锁
原理与临时有序节点目录实现基本相同
Redis
SET key value NX PX milliseconds 命令
通过key加锁,释放则删除key
PX指定过期时间,不设置过期时间如果Redis宕机,可能导致死锁
释放锁需要注意value是否一致,一致才能删除key
缺点:主从哨兵模式下,主机宕机切换时可能造成锁丢失问题
RedLock算法(Redis自带)
轮询向所有master设置key,多数设置成功则加锁成功
无法保证加锁过程一定正确,不推荐使用
Redisson(开源框架,实际落地时比较常用)
对Redis原生的Set key和 RedLock实现方式都支持
实现简单,通过lock,unlock方法即可实现
针对Set key过期时间结束其他线程获取到锁的问题的解决办法
watchdog(看门狗),在获取锁后狗会每隔10秒帮你重置过期时间,宕机会随着消失。
数据库
创建一个有唯一约束的表,获取锁则新增数据,释放锁则删除数据
悲观锁
乐观锁
缺点是可能会出现锁表的风险
解决库存超卖问题
可以使用悲观锁,分布式锁,乐观锁,队列串行化,异步队列分散,Redis 原子操作等方案
用分布式锁高并发场景下
分布式锁可以解决超卖的问题,但是基于分布式锁串行化的处理,没办法解决多用户对同一商品同一时间的订单请求,高并发场景下处理性能较弱,
优化方案:可以通过分段加锁思路解决,也就是将总库存分为多个小库存单元,高并发进来通过随机算法随机分配库存段的key进行业务处理,如果库存为0则自动释放锁换下一个库存段再加锁后进行业务处理。
缺点:实现较复杂,需要对数据进行分段存储。
需要注意的点比较多,比如随机算法、自动切断分段等
需要注意的点比较多,比如随机算法、自动切断分段等
搜索引擎
Lucene
倒排索引
就是关键字与文档id的映射。类似于关联表,用户输入关键字,找到倒排索引中关键字对应的docid,找到对应的文档返回
倒排索引中一个词项对应一个或多个文档
词项是根据字典顺序升序排列的
ElasticSearch
分布式的文档搜索引擎
支持PB级数据
组成
Node节点
Index
索引,每个索引包含一堆相似结构的文档数据
Type
每个索引可以有一个或多个type,是index的逻辑分类
Document
es中最小的单元,相当于mysql的行,每个索引包含很多document
field
每个document下有多个field,相当于数据字段
shard
数据分片,然后存放在多个机器上,提高吞吐量和性能
primary shard 用于写数据,然后同步到其他 replica shard中,数据可从两个shard中读取。
分布式架构设计
es集群分为多个节点,选举其中一个节点为master节点,负责监听元数据、其他节点状态及shard的分配,如果master宕机重新选举一个master
非master节点宕机,则将节点中primary shard 在其他机器上的 replica shard转化为primary,等机器重启后,将primary缺失的其他shard标记为replica
es写数据原理
1,客户端选择一个节点发送请求过去,该节点就是coordinating node协调节点,协调节点对document进行路由,转发请求到对应的node,由primary shard进行处理请求
协调节点是通过hash计算出primary shard所在位置
2,primary shard处理请求后,将数据同步到其他机器的replica shard上
primary shard写数据原理
主要4个核心:refresh、flush、translog、merge
接收到请求后写入内存buffer,同时写入到os cache中,buffer中搜不到数据,os中才能搜到,同时还同步一份数据到translog中
buffer每隔一秒就会触发refresh操作,也就是将buffer中的数据同步到os cache中,然后清空buffer,将os cache数据写入一个segment file磁盘文件中。refresh 每隔一秒操作也是es为什么被称为准实时的原因。
当translog达到一定长度会触发commit操作,commit后将文件落到磁盘为flush操作,默认30分钟一次,translog主要是为了保证数据不丢失
每秒生成的segment file文件较多时,为节省空间,es会触发merge操作,将segment file合并成一个较大的file
3,同步完数据后,协调节点返回结果给客户
es读数据原理
1,客户端选择一个节点发送读请求,该节点为协调节点,对doc id进行hash,找到对应的node,转发请求到该node
2,node通过随机轮询算法,找到primary shard 或其他任意一个relica shard上获取数据
3,找到数据后node将document 给协调节点,协调节点返回结果给客户端
es删除/更新数据原理
删除操作:cmmit时会生成.del文件,将doc标记为delete状态,在merge时会删除
更新操作:就是将原来的doc标记为delete状态,然后新写入一条记录
数据量亿级时如何提高es查询效率
1,分配给os cache的内存大小是es数据量的一半以上,搜索较快
2,只将索引放到cache中,通过es + hbase架构搜索
3,数据预热,通过缓存预热系统每隔一段时间去访问cache的热点数据,让热点数据提前进入cache中
Solr
与ES区别
实时建立索引时,solr会产生io阻塞,es不会
不断动态写数据时,solr检索效率会下降,es基本没变化
solr是利用zookeeper进行分布式管理,es自身带有分布式管理功能
solr支持多种数据格式(xml,json,csv),es只支持json
0 条评论
下一页