面试宝典
2023-01-28 18:08:16 25 举报
AI智能生成
登录查看完整内容
为你推荐
查看更多
java面试整理
作者其他创作
大纲/内容
不重复,基于Map集合的单列Key值去重
无序
基于HashMap的key,通过Hash值比较,如果一致,再通过eques方法比较达到去重效果
LinkedHashSet
HashSet
有序
基于TreeMap的key,通过实现compare接口的compareTo方法来保证不一致
TreeSet
NavigableSet
SortedSet
Set
有序集合,可重复
基于数组,查询速度快,添加元素慢
默认长度是10,在进行扩容的时候会进行 1.5 倍的增长 不存在自动减容机制
ArrayList
基于双向链表,索引是通过从头开始遍历计算的
查找修改速度慢,添加删除速度快
LinkedList
线程安全(所有方法都加了锁,同步锁)
Vector
List
Collection
默认值16负载因子0.75(扩容大小,默认值*负载因子)当数据到达12位会自动扩容(两倍的增长)当链表节点长度为9,并且数组长度大于等于64时,链表变成红黑树当同一个索引位置的节点移除后达到6个的时候,并且当位置节点是红黑树结构,会自动转换成链表
链表:通过hash算法来计算存储的位置,如果计算的位置不为空,那么就创建一个链表,链表节点的最大值是8,如果长度大于64的时候自动转换成红黑树
红黑树:使用红黑树查询效率高于普通链表,因为用的是二分查找效率要高于顺序查找并且会保持效率。如果当数量小于6的时候会自动转换成链表
详情
jdk1.8之前是数组+链表,之后是数组+链表+红黑树
按照键值对的方式存储,key不能重复,value可以为null也可以重复,线程不安全
提高查询效率
一开始数据存储的时候发生hash冲突,这个时候需要把冲突的数据放到后面的链表中(链地址法,拉链法),如果hash冲突的数据过多,就会让链表过长,查询效率会变低,所以jdk1.8就换成了链表长度大于8的时候转换成红黑树
为什么使用 数组+链表+红黑树 结构?
就是为了解决hash冲突问题
jdk1.7数据结构为什么是数组加链表
解决方案:加锁,加sync或lock锁,或者使用concurrentHashMap
hash是线程不安全的,1.7的时候会造成死锁问题,1.8解决了死锁,但是会出现值覆盖问题,在put的过程中同时put两个值,然后覆盖
jdk1.7使用的是头插法,在多线程情况下,链表会产生环形,因此1.8修改成了尾插法。多线程推荐使用ConcurrentHashMap
继承自AbstractMap类
key不重复 是先通过hash值比较的,如果一致再进行eques方法进行一致性比较来保证唯一key
HashMap
线程安全的hashmap
jdk1.8之后是用node里面加sync锁升级和cas配合使用,相当于在原有基础上加上红黑树
采用分段锁
ConcurrentHashMap
不允许key和value为null
HashTable的初始值是11,扩容2*n+1
elements() 方法用于返回此Hashtable中的value的枚举
contains()方法判断该Hashtable是否包含传入的value。它的作用与containsValue()一致。事实上,contansValue() 就只是调用了一下contains() 方法
继承自Dictionnary,比HashMap多提供了elments() 和contains() 两个方法。
HashTable
有序的key 的散列表
数据结构是红黑树
key是通过实现compare接口的compareTo方法来保证不重复的
TreeMap
实现类
再hash
把具有相同散列地址的关键字放在同一个单链表里面(每个数组里面的链表就相当于拉链)
拉链法/链地址法
遇到哈希冲突时,去寻找一个新的空闲的哈希地址。
地址法/开放定址法
hash冲突解决
Map
集合
1.继承Thread类,重写run方法
2.实现runable接口,无返回值
3.实现callable接口,有返回值
4.通过线程池创建
创建线程的方式
1.新建状态 new
执行了start方法,但是还没有获取cpu的资源,等待cpu指针找到这个线程,并且去执行
2.准备就绪状态 start()
也是执行了start方法,只不过cpu现在已经获取到了这个线程,该线程也拿到了cpu的资源
3.运行时状态 runtime
Thread的方法,时间到会自动唤醒,会占用cpu和锁资源,唤醒之后继续执行
sleep睡眠
notify和notifyall的区别:notify会随机唤醒一个线程,是虚拟机控制的;notifyAll会将所有的线程唤醒,放到锁池,参与锁的竞争,竞争成功就执行,失败就继续留到锁池
object的方法,一定需要notify方法进行唤醒释放锁资源,睡眠的时候会进入等待区域(性能不好,因为会有守护线程来唤醒)
wait等待
4.阻塞状态(被同步锁阻塞blocked)
1.线程执行完成自动结束
2.使用stop方法,强行终止,不会释放ReentrantLock锁,不过在jdk1.5之后就被弃用了
3.使用interrupt方法终止线程,终止会抛出InterruptedException并且会自动退出线程,比较温和,可以将休眠状态的线程转换到runnable状态
5.结束
线程的状态
join方法:是Thread的方法,作用是调用线程需等待该join线程执行完成后,才可以继续运行
wait方法:是object的方法,作用是让当前线程进入等待状态,同时,wait也会让当前线程释放它所持有的锁。直到其他线程调用次对象
如何保证线程执行顺序
特点:当设置休眠的时间,又没有空闲线程时,会创建新的线程去执行任务;当没有设置休眠时间时,有空闲线程时,会重复使用线程去执行任务
没有核心线程,线程池可容量最大线程为max,休眠时间,线程队列(用在段时间处理大量任务)
创建可缓存的线程池。如果线程池的长度超过需要处理的长度,请灵活地重用空闲线程,如果不能重用就创建新的线程。
Executors.newCachedThreadPool()
特点:当有任务执行时,核心线程先运行,再有新的任务时,会放入到线程队列里面,再有新的任务就会创建新的线程去执行
提交优先级:核心线程 --> 线程队列 --> 新创建的线程
执行优先级:核心线程 --> 新创建的线程 --> 线程队列
传的参数为最大线程数(最大线程数包括核心线程数,还有线程队列)
创建固定长度的线程池,用于控制线程的最大并发行数,超出线程将在队列中等待。
Executors.newFixedThreadPool(int nThreads)
特点:只有一个核心线程在执行任务
传的参数为最大线程数;只有一个核心线程,线程池最大的线程数是核心线程数,线程队列
创建单线程池,只在唯一的工作线程上执行任务,以确保所有任务都在指定顺序(FIFO、LIFO、优先级)执行。
Executors.newSingleThreadExecutor()
创建固定长度的线程池,以支持计划和定期任务执行
Executors.newScheduledThreadPool()
自定义线程池,最常用的,因为可以自己设置线程池的参数,jdk提供的最大线程数是integer的最大值
newThreadPoolExecutor()
创建方式 7种
1.核心线程数(corePoolSize)不会销毁
2.最大线程数(maximumPoolSize)为核心线程数和创建线程数总和,空闲一段实际则会销毁创建的线程
3.活跃时间(keepAliveTime)非核心线程的最大空闲时间,没有被使用就会销毁
4.时间单位(unit)
5.队列(workQueueSize)
6.线程工厂(threadFactory)
关键的三个参数:corePoolSize-(最小)核心线程数workQueue - 阻塞队列maximumPoolSize - 最大线程数
线程池的拒绝策略:当任务超过队列的最大时,线程池会拒绝后面的任务不在存放到队列中
AbortPolicy 终止策略这是ThreadPoolExecutor线程池默认的拒绝策略,程序将会抛出RejectedExecution异常。当线程队列满了就会抛弃后续的任务
CallerRunsPolicy 调用者运行策略线程池中没办法运行,那么就由提交任务的这个线程运行
DiscardOldesPolicy丢弃最早未处理请求策略,丢弃最先进入阻塞队列的任务以腾出空间让新的任务入队列
7.线程的执行策略(handle)四种
线程池的7个参数
1.降低能源消耗:通过重复利用创建的线程降低创建和销毁造成的损耗
2.提高响应速度:当任务达到的时候可以不用等待线程创建,直接执行就可以
3.提高线程可管理性:线程的资源是很稀缺的,如果没有限制的创建,不仅消耗系统资源,还会降低系统的稳定性,使得线程可以进行统一的调优分配和监控
为什么要用线程池
execute():只能执行Runnable类型的任务
submit():可以执行Runnable和Callable类型的任务 并通过返回的Future对象获取结果
线程池提交任务的两种方式
线程池
1.使用安全类,比如java.util.concurrent下的类
2.使用自动锁synchronized
3.使用手动Lock锁
保证线程安全的三种方式
没有线程线程参与锁的竞争时
无锁
如果在很长一段时间只有同一个线程在竞争锁就会发生偏向锁
通过锁在内存中的mark word来确定是否为同一线程。在mark word里面记录ThreadId,在锁竞争时比较,如果一致则是同一线程
偏向锁
如果有多个线程同时竞争锁,那么就会升级成轻量级锁
内存地址V
旧的预期值A
要修改的新值B
基本操作数
1.读取内存地址V
2.在原子操作中比较内存地址V的值是否与A相同
3.相同时,修改内存地址V的值为B,原子操作成功
4.不相同时,循环执行第一至第三步(自旋),直到成功
交换值步骤
cas(Compare And Swap)比较并交换
当mark word中的锁指针指向该栈,则栈中持有轻量级锁。没有获取到锁的线程会进行cas自旋
优点:不会引起调用者休眠,阻塞的时间很短,提高响应的速度
缺点:如果一直得不到锁竞争的线程使用自旋会消耗cpu的资源
优缺点
轻量级锁/自旋锁
通过操作系统的互斥锁完成
因为锁对象处于偏向或者轻量级锁的状态下,是没有管程对象和等待队列的,所以无法保存线程节点
持有锁的线程调用锁对象的wait方法会升级成重量级锁
CPU可以访问任何的数据,包括外围设备,比如网卡、硬盘、处于内核态的CPU可以从一个程序切换到另外一个程序,并且占用CPU不会发生枪占情况,一般处于特权级0的状态我们称之为内核态
内核态
CPU受限的访问内存,并且不允许访问外围设备,用户态下的CPU不允许独占,这个时候的CPU是能够被其他程序获取的
用户态
如果多个线程自旋失败,则升级成重量级锁,操作系统将线程挂起,将线程从用户态转换成该内容和态
优点:线程竞争不使用自旋,不会消耗cpu
缺点:线程会阻塞,响应的速度比较慢
重量级锁
升级顺序: 无锁-->偏向锁-->轻量级锁-->重量级锁升级目的:锁升级是为了减少锁带来的性能消耗
1.确保线程互斥的访问同步代码
2.保持共享变量能及时看见
3.有效解决重排序问题
作用
1.修饰普通方法
2.修饰静态方法
3.修饰代码块
用法
synchronized 锁升级原理
synchronized 中有一个monitor 对象,通过判断当前线程是否被占用来设置状态
通过monitorentry和monitorexit在执行monitorenter,线程试图获取monitor(存在于每个java对象的对象头中,也是java中任意对象可以作为锁的原因)对象的持有权,当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行monitorexit指令后,将锁计数器设为0,表明被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止
synchronized 底层实现原理
1.synchronized可以修饰类,方法,代码块;Lock只给代码块加
2.synchronized为自动锁,会自动释放锁,Lock为手动锁,必须使用unlock方法手动释放
synchronized和lock的区别
可以保证代码的可见性,但不保证原子性
禁止进行指令重排序
volatile关键字
atomic主要利用的是cas和volatile和native方法来保证原子操作,从而避免synchronzied的高开销,执行效率大为提升
atomic原理
1.使用tryLock方法设置超时时间,超时可以退出
2.使用安全类来代替锁
3.降低锁的使用粒度(减少使用锁)
4.减少在方法上加锁
怎么防止死锁
什么是死锁:两个线程同时占用了自己的锁,并且释放的时候需要获取到对方的锁
ThreadLocal可能出现的问题:jmm(java线程内存模型)的值存在堆中,方法再栈中执行就涉及到可见性的问题,当共享值改变时,其他线程可以实时更新本地值
1软引用:相对强引用弱一点的引用,需要用类来实现,可以让对象豁免一些垃圾回收机制,当内存不足时,会报内存溢出异常用来描述一些还有用但非必要的对象,当系统内存不足时才会回收类2弱引用:比弱引用弱一点的引用,被弱引用关联的对象在垃圾回收机制工作,不管内存是否足够,都会被回收3强引用:将一个对象赋给另一个引用变量,这个引用变量就是一个强引用当对象被强引用变量引用时,处于可达状态,不能被垃圾回收机制回收,所以有时会造成内存泄漏4虚引用:用来描述跟踪对象垃圾回收的状态,当对象被回收时收到一个系统通知或者后续添加进一步处理
每个线程都维护一个ThreadLocalMap,key为ThreadLocal,value为要存的值,同时key时做为弱引用使用,弱引用对象会在垃圾机制时被回收,ThreadLocalMap和Thread生命周期一致,当ThreadLocal对象被回收时,线程还未结束,key为null,value访问不到情况就会导致内存泄漏主要原因:ThreadLocalMap的生命周期和Thread一样,没有及时remove时,线程还未结束,导致内存泄漏当使用线程池和ThreadLocal时要注意线程是不断重复的,不手动删除会导致value的积累
ThreadLocal内存泄漏问题:内存不可达的情况
cas:负责将某处内存地址的值与一个期望值地址进行比较,如果不相等,则将该内存地址处的值替换为新值aba:cas需要检查带操作的值是否发生改变,没发生改变则跟新(但是存在一种状况,一个值原来为a,变成了b,然后又变回了a,那么cas检查时发现没有该改变,但实际发生了改变)
cas和aba
ThreadLocal:可以使每个线程都有自己独立的工作副本,每个线程都只改变自己的副本,不会影响其他副本
线程安全
线程
做集合的迭代
foreach
将一个集合转换成一个新的集合
map
filter
用来做集合的排序
可以通过重写Comparable的compareTo
sorted
collect
lambda表达式
函数式接口
optional
接口的default方法
java8新特性
java基础
User-Agent:告诉网站服务器,访问者是通过什么工具来请求的,如果是爬虫请求,一般会拒绝,如果是用户浏览器,就会应答
请求头
请求体
响应体
Http
使用的是对称加密+非对称加密
Https
协议
www
域名
锚
参数
url划分
1.浏览器需要解析url地址
4.拿到最终服务器的ip+端口号后找到对应的服务
5.服务器通过交换机找到对应的DHCP服务器
6.dhcp会映射给网卡(MAC地址是每台电脑唯一的地址)
7.完成网络传输
浏览器输入一个地址后做了哪些操作
应用层
表示层
会话层
传输层
网络层
数据链路层
物理层
七层协议
网络接口层
简化成四层
网络组成
4.第一次挥手是客户端a首先想服务端b发送一个断开连接的请求
6.A收到B的确认后,进入FIN-WAIT-2(终止等待2)状态,等待B发出的连接释放报文段。
8.a客户端会改变状态为关闭状态完成断开连接的操作
超链接
协议可靠是因为三次握手四次挥手
Tcp
Udp
底层的实现
post
get
post&get
Session
cookie
Session&cookie
servlet
javaweb
通过字节码的方式去动态写入响应的位置,在编译阶段就生成,性能更快
静态代理
实现proxy代理类里面的InvocationHandler,里面的invoke方法
jdk自带的动态代理
通过字节码形式来实现的动态代理
cglib动态代理
不是被public修饰的类
不被spring容器管理的
自己捕获了异常或者手动try catch
动态代理失效的原因
动态代理
AOP:面向切面编程
创建对象的控制权的转移,ioc让对象的创建不用去new了,可以由spring来管理,根据反射动态的创建bean对象
构造器注入
setter方法注入
注解注入
三种注入方式
IOC:控制反转
DI:依赖注入
实例化bean,通过BeanDefinition对象的信息,通过反射获取构造方法进行创建bean
1.实例化bean
spring根据BeanDefinition中的信息通过BeanWrapper提供的设置属性接口完成依赖注入
2.设置对象属性
使用XXXAware可以让Bean获得Spring容器的服务,从而获取SpringBoot启动时的一些信息
3.处理aware接口
4.postProcessBeforeInitialization
设置属性,执行afterPropertiesSet方法
5.InitializingBean
初始化之后执行,一般在这个阶段进行aop生成proxy代理类
6.postProcessAfterInitialization
清理阶段,如果Bean实现了DisposableBean这个接口,会调用destroy方法
7.DisposableBean
如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法
8.destroy-method
spring bean的生命周期
一级缓存存放实例化对象
二级缓存存放已经在内存空间创建好但是还没有给属性赋值的对象
三级缓存的作用,「尽可能」的让代理对象的创建要在目标对象完全初始化后进行
二级缓存完全可以解决循环依赖的 bean 是代理对象的情况,只不过需要 spring 实例化一个 bean 后,需要立即判断是否要代理此 bean,若需代理, 则先生成代理 bean 放到二级缓存中而后进行初始化。但是这样做显然会不合规,所以 spring 引入了三级缓存
三级缓存存放对象工厂,用来创建提前暴露到bean的对象
三级缓存
spring
核心控制器DispatcherServlet
视图解析器:ViewResolver
处理器映射器:HandlerMapping
处理器适配器:HandlerAdapter
2.执行流程
自动帮我们匹配当前请求的请求方式
.requestmapping
只能解析get请求
getmapping
只能解析post请求
1.去做url映射的注解
2.参数解析的
3.参数反解析
3.springmvc常用注解
spring mvc执行流程 详细
详细流程
spring mvc
1.内嵌mvc核心包
2.内嵌tomcat
默认使用jackson
常用的是fastjson
3.json解析的依赖
1.快速搭建web项目
dependencyManagement 只做jar包的版本管理,不会下载依赖
dependencies 这个会帮我们下载jar包,以及管理jar包
2.整合了市面上大多数框架的版本,设置到springboot-parent中,帮助我们解决依赖版本不同导致冲突的问题
3.简化了整合进的框架配置,spring容器提供了大量的默认配置
1.概念:基于spring 的一个简化配置,约定大于配置的框架
Springboot根据应用声明的依赖来对spring框架进行自动配置,他会去帮我找一个spring.factories,这个文件定义了所有spring整合的XXXAutoConfiguration
@EnableAutoConfiguration
@SpringBootConfiguration
组件扫描,可自动发现和装配Bean,默认扫描Springapplication的run方法里面的Booter.class所在的包路径下的文件,所以最好将改启动类放到根包路径下
@ComponentScan
springboot的执行流程
2.启动流程-执行流程@springbootapplication
spring boot
springcloud
框架部分
类加载子系统负责从文件系统或者网络中加载Class文件,class文件在文件开头有特定的文件标识
加载
验证
准备
将常量池中的符号引用替换为直接引用的过程.直接引用为直接指向目标的指针或者相对偏移量等
解析
初始化
类加载过程
类装载子系统
解释器
JIT
字节码执行引擎
eden区
survivor存活区,分为s0和s1
新生代
老年代
1.7常量池
1.7静态变量
1.8永久代
堆
类信息(.class)
1.7方法区/1.8元空间
线程共享
存储C/C++相关的变量和方法
本地方法栈
操作数运算时一块临时的空间来存放操作数
局部变量表
操作数栈
将代码的符号引用转换为在方法区(运行时常量池)中的直接引用
动态链接
存储了栈帧中的方法执完之后回到上一层方法的位置
方法出口
虚拟机栈/线程栈
记录线程运行指令的步骤,可以看成是指令执行的行号,在切换线程时cpu根据行号来执行
程序计数器
线程私有
运行时数据区
jvm内存模型
标记复制
标记整理
标记清除
三种垃圾回收算法
单线程
Serial新生代串行垃圾回收器
单线程,标记整理
Serial Old老年代串行垃圾回收器
多线程
Parallel Scavenge新生代并行垃圾回收器
多线程,标记整理
Parallel Old老年代并行垃圾回收器
ParNew新生代并发清除回收器
仅仅只标记GC Roots能直接关联的对象,速度很快,仍然需要暂停所有工作线程
初始标记
根据刚刚标记的对象向下找,需要时间,不会暂停所有工作线程,但会占用cpu资源,导致应用程序线程变慢,吞吐量降低
并发标记
为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有用户线程
重新标记
清除GC Roots不可达对象,和用户线程一起工作,不需要暂停工作线程,基于标记结果,直接清除对象,采用的标记清除算法,会产生碎片空间,碎片空间不够的话容易触发FullGC
并发清除
CMS(Concurrent Mark Sweep)老年代并发标记清除回收器 清理不够彻底,会产生浮动垃圾,但速度快,stw时间短
分代回收
G1可以选择部分区域进行垃圾回收,这样缩小了回收的范围,因此可以减少stw的时间
G1会跟踪regin里面的垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的regin。保证在有限时间内尽可能的获取到最高的收集率
G1会占用额外空间,G1可以采用应用程序的线程来进行GC,不过会降低吞吐量
YoungGC年轻代GC
YongGC + concurrent mark并发标记过程
MixedGC混合回收
FullGC(在特定条件下发生)
G1垃圾回收过程
G1
ZGC
分区回收regin
常见的垃圾收集器
GC垃圾回收机制
永远不会回收
强引用
发现就回收
弱引用
内存不足时回收
软引用
不会影响对象的生命周期,也无法通过虚引用获得对象实例。虚引用的作用就是对象被回收时收到一个系统通知(回收跟踪)
虚引用
java对象引用
jstat,JVM statistics Monitoring是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据
jmap,JVM Memory Map命令用于生成heap dump文件
jhat,JVM Heap Analysis Tool命令是与jmap搭配使用,用来分析jmap生成的dump,jhat内置了一个微型的HTTP/HTML服务器,生成dump的分析结果后,可以在浏览器中查看
jstack,用于生成java虚拟机当前时刻的线程快照
jinfo,JVM Configuration info 这个命令作用是实时查看和调整虚拟机运行参数
调优命令
jconsole,Java Monitoring and Management Console是从java5开始,在JDK中自带的java监控和管理控制台,用于对JVM中内存,线程和类等的监控
jvisualvm,jdk自带全能工具,可以分析内存快照、线程快照;监控内存变化、GC变化等
MAT,Memory Analyzer Tool,一个基于Eclipse的内存分析工具,是一个快速、功能丰富的Javaheap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗
GChisto,一款专业分析gc日志的工具
调优工具
设定新生代大小。 新生代不宜太小,否则会有大量对象涌入老年代
-Xmx:堆内存最大限制。
-XX:NewSize:新生代大小
-XX:NewRatio 新生代和老生代占比
设定垃圾回收器 年轻代用 -XX:+UseParNewGC 年老代用-XX:+UseConcMarkSweepGC
-XX:SurvivorRatio:伊甸园空间和幸存者空间的占比
调优参数
JVM调优
JVM虚拟机
已启动就帮你创建对象
饿汉式
调用的时候才创建对象
减少竞争锁的资源开销
懒汉式创建时需要加双重锁判断
懒汉式
1.单例模式
其中复杂对象指的是类的构造函数参数过多等对类的构造有影响的情况下,我们将创建对象的过程抽离出来,减少代码中的耦合以及后期我们的维护成本
spring的ioc
jdbc的连接池
静态工厂
抽象工厂
简单工厂
2.工厂模式
aop
3.代理模式
springmvc就是适配器模式
4.适配器模式
缓冲IO流
5.观察者模式
所有集合都有迭代器
6.迭代器模式
7.装饰者模式
线程池的策略参数
8.策略模式
自动拆装箱
9.享元模式
JDbc
10.桥接模式
11.责任链模式
设计模式
读11万,写8万
redis的读写效率
6379
redis端口号
1.hash
2.string
3.set
4.zset/sored set
5.list
redis的数据类型
save 900 1
save 600 5
save 100 100
存储的是数据快照,效率很高,存储的文件小,不占用空间,有可能会有数据丢失
rdb(默认开启)
存储的是所有写的操作命令,每秒执行一次,性能比较低,存储文件大,不会有数据丢失
aof(手动开启)
rdb+aof
公司一般用那种?
持久化策略
redis所谓的事务是一个假事务,只是将命令打包成一个批处理执行
redis事务
设置一个定时器,监听所有设置过期时间的key,但凡过期,就删除
定时删除
每隔一段时间去删除过期的key,是随机挑选key进行删除
定期删除
使用key的时候,再去判断key是否过期,如果过期,就删除
惰性删除
为什么不用定时删除?定时删除,会用一个定时器来负责监视key,过期自动删除。虽然及时的释放内存,但是十分消耗cpu的资源,如果并发很大的话,cpu要处理很多请求,就非常占用cpu的资源
redis默认使用的是 定期 + 惰性
redis的删除策略
noeviction: 不删除,直接返回报错信息
allkeys-lru:移除最久未使用(使用频率最少)使用的key。推荐使用这种
volatile-lru:在设置了过期时间的key中,移除最久未使用的key
allkeys-random:随机移除某个key
volatile-random:在设置了过期时间的key中,随机移除某个key
volatile-ttl: 在设置了过期时间的key中,移除准备过期的key
allkeys-lfu:移除最近最少使用的key
volatile-lfu:在设置了过期时间的key中,移除最近最少使用的key
redis的淘汰策略
原有的缓存失效,新的缓存未到期间在这个时间段有大量请求,本来是走缓存的,但去访问数据库了,就可能发生数据库宕机
解决方案:加锁或者用队列来保证不会有大量的线程对数据库进行一次性的读写
redis的缓存雪崩
用户查询数据,在数据库里面不存在,那么在缓存里面也不可能有,那么每次查询就会访问数据库,进行io操作,非常消耗资源
1.使用布隆过滤器,将所有可能查到的数据进行hash算法(因为hash算法一般是O(n)级别的,性能会特别的高)计算放到足够大的bitmap里面,不存在的数据过过滤掉(推荐使用)
2.如果一个查询返回的结果为空,那么直接把这个查询结果缓存,设置过期时间,一般不会超过5分钟,下次再有这个查询直接返回
解决方案
redis穿透
1.直接写个缓存刷新页面,上线时手工操作
2.数据量不大,可以在项目启动的时候进行加载
3.定时刷新缓存
就是系统上线,直接将相关的缓存数据加载到缓存系统。这样用户请求就可以直接拿缓存,不去访问数据库,降低数据库的压力
redis缓存预热
非核心服务影响到核心流程的性能时,仍然要保证服务可用,这个时候可以根据一些关键数据进行降级,也可以配置开关实现人工降级。最终目的是要保证即使有损,核心服务也是可用的。
redis缓存降级
1.纯内存操作
2.单线程操作,避免了频繁的切换上下文
3.采用了非阻塞I/O多路复用机制
redis为什么这么快
定时去清理过期缓存
当用户请求过来判断缓存有没有过期,有过期就去数据库重新读取数据更新
redis的缓存如何更新
主从复制
哨兵模式
集群模式
redis集群的三种模式
redis的延迟双删是指在并发的情况下,修改了数据库的数据,然后缓存还没来得及改,就已经被线程获取到了,所以这个时候的数据是脏数据
什么是redis的延迟双删呢?
先把redis缓存的数据删掉,然后在修改数据库数据,这时候sleep一下(即延迟),然后在把redis缓存的数据删掉。防止有其他线程在你修改数据库的时候,更新缓存导致数据不一致
阿里提供的解决方案,通过mysql的binglog日志解决,直接给redis推送信息完成数据更新
redis的延迟双删问题
redis
秒杀削峰 处理高并发
异步响应
并发排队 滴滴打车
天府通扣费
使用场景
1.安装erlang,配置环境变量
2.安装RabbitMQ,开启管理插件
安装
1.创建连接工厂
2.创建连接
3.创建信道
4.创建队列
5.发送消息
生产者
4.消息处理回调
5.消息监听
消费者
1.HelloWorld
1.自动签收
手动ack确认消息是否发送成功,以及消息是否消费成功
2.手动签收
2.签收模式
和helloworld一样,只是搞多个消费者
设置消息的消费并发数(能者多劳)
3.workqueue
4.创建交换机(fanout)
5.准备消息
使用上面创建的交换机
不用指定的routing key
6.发送消息
5.把队列绑定到交换机:指定一个routing key
6.监听队列
7.回调:处理消息
4.fanout:发布订阅模型
4.创建交换机(direct)
要用指定routing key
5.direct:路由模型
4.创建交换机(topic)
要使用指定routing key
5.把队列绑定到交换机:指定一个routing key(可以通配符)
6.topic:主题模型
1.交换机持久化
2.队列持久化
4.消息持久化
7.持久化
消息模型
单任务按顺序执行
同步阻塞io
多任务,定时查看任务执行状态
同步非阻塞nio
单任务,自动提交任务执行状态
异步阻塞aio
多任务,自动提交任务执行状态,合理分配,最大化利用资源
异步非阻塞bio
多路复用模型
rebbitMQ
中间件
可能导致脏读、幻读、不可重复读
读未提交
在事务提交之前创建一个视图
可能会出现幻读和不可重复度
读已提交
在事务启动的时候就会创建一个视图,避免了不可重复度
可能会出现幻读。mysql5.6之后不会出现幻读
可重复读
不会出现脏读、幻读不可重复读,相当于单线程
串行化
A事务未提交,B事务读取到A事务的内容
脏读
A事务执行中,B事务修改了A事务的数据,导致A事务前后读取的数据内容不一致
不可重复度
A事务执行中,B事务进行了添加或删除操作,导致A事务读取的数据条数不一致
幻读
不同隔离级别的问题
事务隔离级别
记录数据修改之后的值,无论事务是否提交。可以实现事务的持久性
redo log
回滚日志,记录数据修改之前的值,实现事务的原子性(用于回滚)
undo log
归档日志,属于逻辑日志,是以二进制的形式存储整个数据库的执行语句
bin log
mysql三大日志
原子性,列不可再分
唯一性,保证每一行的数据是唯一的
独立性,消除数据冗余,每一列的数据都不可以再分
数据库三范式
mysql5.5之后的默认引擎,默认索引类型是B+数的聚簇索引,支持行锁、事务和外键约束,支持数据同步,默认的事务隔离级别是可重复读
InnoDB
默认索引类型是B+树,叶子节点保存的是数据对应的指针。只支持表锁,不支持事务,保存了表的行数,所以读写操作效率高
MyISAM
数据库引擎
将所有的表拆分到不同的数据库中
垂直拆分
复制多个相同的数据库做集群(单库的数量建议控制在5000万以内)
水平拆分
分库
将同一张表中的字段拆分到多个表中,并使用一对一关联,适用于多字段的大表
复制相同结构的表,将数据分散。建议200万以上的数据就进行分表
分表
数据库优化
就是主键索引,mysql默认给表主键索引,没有主键就找唯一键为索引,没有唯一键就会生成一个6位数的row_id作为索引
聚簇索引
索引列的值都只能出现一次,值可以为空
唯一索引
非唯一索引,索引列值可以重复,可以为空
普通索引
全文索引类型为fulltext,可以在varchar,char,text类型上创建
全文索引
多列值组成的一个索引,准备用于组合搜索
联合索引
特点:叶子节点只存储该索引键和对应的主键,不存储整条数据
非聚簇索引
索引分类
先通过聚簇索引查询到所需记录的主键,然后通过主键索引去找到主键对应的值
回表
当需要查询的数据列可以从索引中直接获取,不需要进行回表的操作称之为索引覆盖
索引覆盖
在联合索引中,数据库会依据最左边的字段来构建B+数,这个原则就是最左匹配原则
最左匹配原则
mysql5.6之前是先通过最左索引列过滤后的数据返回到数据库server层,在进行右边索引列数据过滤,此操作增加回表次数
建立联合索引的时候,数据库会遵循最左匹配原则,此时如果查询条件中存在多个索引列,数据库服务器会将这部分条件下推到存储引擎中进行过滤
索引下推
索引相关名词
查询条件包含or并且不是所有条件列都建了索引
数据类型不匹配(隐式转换导致)
不满足最左匹配原则
对索引进行了算数运算
对索引列使用了<>、not in、in、!=、is null、is not null
mysql自己的优化,认为全表扫描更快,所以不走索引
索引失效的情况
数据索引
expain执行计划
概念:多版本并发控制,是一种数据库并发读写的解决方法
创建这条记录或者最后一次修改该记录的事务id
db_trx_id
回滚指针,指向上一个历史版本数据(undo log)
db_roll_ptr
db_rowid
表的隐藏字段
待整理
MVCC机制
MySql
原子性
事务改变的前后数据总数不变
一致性
事务与事务之间相互不干扰
隔离性
保存在磁盘中
持久性
1.单机事务ACID
2.mysql的二阶段提交
关于悲观锁乐观锁
2.相同的服务不同的数据库出现事务问题
3.不同的微服务相同的数据库出现事务问题
出现的场景
什么是分布式事务
A:高可用性
C:一致性
P:分区容错性
CAP理论
Base理论
强一致性
弱一致性
最终一致性
分布式一致性理论
关于acid的概念
TX
分布式原子性的概念
XA
TX(单机事务) XA(分布式事务)
理论知识
2.首先所有参与该事务的服务需要请求事务协调器完成注册
3.事务协调器需要对每一个参与该事务的服务进行一个发起执行请求
4.所有受邀请的服务都需要给事务协调器返回一个执行的结果(本地执行事务)
2pc阶段提交
在2pc提交流程上
3pc阶段提交
tcc
1.先生成一个本地的事务
2.直接提交这个事务
3.生成一个本地的消息表(是需要给调用服务发消息的数据)
mq完成最终一致性
1.pom.xml导入依赖
2.yml添加关于seata的服务事务分组配置
3.在资源目录下添加关于file.conf 以及registry.conf两个配置文件
4.去配置手动的datasource数据源
5.启动类上排除@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
搭建seata的过程
解决分布式的方案
分布式事务
字符串,数组
单链表
双链表
链表
stack栈
Queue队列
二叉树
平衡二叉树
红黑树
线段树
B树
B+树
tree树
有向图
无向图
graph图
数据结构
冒泡排序
选择排序
插入排序
希尔排序
归并排序
快速排序
计数排序
桶排序
基数排序
排序算法
深度优先搜索
广度优先搜索
搜索算法
背包问题
最长公共子序列
动态规划
二分查找
贪心算法
分治算法
加密算法
算法
数据结构与算法
面试宝典
0 条评论
回复 删除
下一页