redis:
缓存击穿:对于访问过期的key查询数据时,加锁,保证只有一个线程去底层获取数据,并返回结果缓存
缓存穿透:对于访问不存在的key时给出空结果并缓存,或引入布隆过滤器将数据提前缓存在布隆过滤器中
缓存雪崩:对于key采用随机过期时间,避免大量的key在同一时间失效导致数据库承载过高
持久化机制:
AOF:以追加 命令行记录的方式记录完整的日志 优点:能够完整恢复数据 缺点:恢复备份效率低
RDB:以二进制的方式定时本分数据 优点:恢复备份效率高 缺点:存在备份时间临界区有丢失数据的可能
AOF + RDB:定时备份数据以二进制的方式写入文件,增量以命令行的记录方式追加到文件当中
数据类型:
string : 字符串(操作命令:set get append setrange getrange strlen)
数值(操作命令:incr) 理解待加深
bitmap
list: 理解待加深
hash: 理解待加深
set: 理解待加深
sorted_set 理解待加深
并发编程:
synchronized (悲观锁,非公平锁 ) 与 lock(reetrantLock,readwriteLock)(非公平锁、乐观锁)
synchronized:1.6之前为重型锁:线程获取不到锁会直接进入(锁池)队列等待直到上一个线程释放锁之后才可以争取锁
1.6之后引入了锁升级概念:JAVA对象头部(MarkWord)包含锁的信息,锁升级状态由无锁到第一次加锁升级为偏向锁,第二次加锁发生锁竞争则升级为轻量级锁,轻量级锁通过CAS自旋尝试获取锁,如仍然获取不到锁则升级为重量级锁进入锁池等待即1.6之前的方式
reetrantLock:底层采用CAS+AQS方式实现的锁机制,当一个线程想要获取锁先通过CAS自旋方式尝试获取锁;如获取不到则将当前线程封装为Node对象并存入AQS双向队列中,
1:AQS双向队列中已有线程在等待则将当前线程挂接在已有线程后面并将指针互相连接上,且挂接成功后会将上一个Node节点的状态(waitStatus)更新为-1表示上一个Node节点后面挂接的Node节点可被唤醒,处于AQS队列的头部Node节点会通过CAS方式尝试获取锁资源
2:AQS双向队列中没有线程在等待,则会创建一个虚拟的头部节点(head)并将当前Node节点挂接在虚拟节点之后,且将虚拟头部节点(head)的状态(waitStatus)设置为-1,以便更高效率的唤醒后面的线程节点
readwriteLock:读读不互斥,读写互斥,写写互斥;与reetranLock的区别在于更在细化的对锁的颗粒度做了区别,以适用读、写的不同场景下的效率提升
concurrentHashMap:多线程并发的线程安全集合类;采用数组+单向链接+红黑树的数据结构 ,当数组长度大于64(实则为了优化效率,数组的查询时间复杂度为O(1))且单向链表长度大于8(优化效率,单向链接的查询时间复杂度为O(n))时单向链接将转化成红黑树结构(时间复杂度Ologn)
CAS:比较并交换;程序运行时会先从内存中获取数据到CPU进行计算,再将计算完的结果写入到内存
1:如果多个线程同一时间从内存中获取同一份的数据进行计算则会引发数据不一致性的问题(其他线程不能及时拿到某一线程计算完写入的内存中的数据)
2:因此CAS的方式就是数据在CPU计算完之后写入到内存时,会比较此时内存中的值与当前CPU中获取的值是否一致,如一致则认为没有其它的线程修改过值正常写入,反之就认为值被其它线程篡改过则写入失败,再次通过CAS自旋方式直到写入成功为止
Executors创建线程池的几种方式:
newFixedThreadPool:固定线程数的线程池,当添加的线程数超过定义的线程数时则进入阻塞队列(LinkedBlockingQueue),遵循FIFO(先进先出)原则等待线程池中有空闲的线程数时再 进行工作
newScheduledThreadPool:固定线程数的定时任务线程池,底层采用延迟队列(DelayedWorkQueue),可按固定的周期或者延迟多久的时间执行任务
newCachedThreadPool:创建存活时间为60秒的线程池,当前第一次有任务进来会直接创建新线程,如60秒内再次有任务进入会复用已创建的存活线程执行任务,如60秒内无任务进入则会结束当前线程池。如60秒外有任务进入则会创建新的线程执行;因为任务只要提交线程池中,就必然会有线程处理
newSingleThreadExecutor:单例线程池,线程池中只有一个线程在工作,后续进来的任务会进入到阻塞对列中等待,遵循FIFO(先进先出)原则,因此单例线程池适合按顺序执行的一系列任务
newWorkStealingPool:并行计算底层使用ForkJoinPool;核心思想分而治之,线程窃取;使用场景,将一个大的任务按照一定的规则拆分(需手动编写拆分逻辑) 并放到当前线程的阻塞队列当中,其它的空闲线程可以去处理有任务的线程的阻塞列队中的任务 ,优点:线程池中空闲线程可以被充分利用
线程数计算:
核心线程数=cpu核数 * cpu利用率 * (1 + w/c)=cpu核数 * (1-阻塞系数)
分布式
CAP:Consistency(一致性) Availability(可用性) Partition Tolerance(分区容错性)
微服务规定原则:CP / AP 三选二挑一
C:保证微服务之间的数据强一致性,如zookeeper 发送消息必须等待其它节点都有反馈结果才可成功
A:保证微服务之间的可用,如某个节点掉线(需返回错误响应结果)但其他节点可正常使用则不影响整体的服务调用流程
P:保证由于网络故障原因(网络不可靠运行)引起的服务波动不影响整体的系统运行,分布式系统的基石
nacos:阿里开源的微服务注册中心
服务注册:微服务调用注册中心客服端服务发送微服务实例信息完成注册
服务心跳:客户端会以默认5秒的频率向服务端发送心跳,告知注册中心当前服务的健康状态
服务健康检查:服务端启用定时任务默认每15秒检查一次注册服务列表的健康状态如超过25秒未响应则将服务置不健康状态并下线
服务发现:注册中心的服务能被其它服务检测到且可调用
服务同步:CP
Mysql
b+tree:矮胖形的树状结构;
1:非子叶节点存储子叶节点的分区指针信息不存储数据
2:子叶节点存储主键信息(如有主键存储主键列信息,否则存储唯一索引列信息,两者都未生成则会存储生成隐式的rowid)
联合索引:多列组合创建的索引;
1:最左前缀原则:where 条件中必须包第一列的字段才会生效且需正确命中索引
2:原理:多列联合索引先按第一列构建b+tree结构,第二列基于第一列再构建b+tree索引以此类推,因此第一列未命中索引,第二例自然不会命中索引
3:回表查询:对于普通索引(二级索引,非聚簇索引),构建b+tree不会存储该列数据,而是存储主键对应的指针引用,所以普通索引查询时先从主键索引中找到对应的主键再根据主键查询返回完整数据 待加深理解
InnoDB行级锁:只有当检索条件中包含索引才使用行锁,否则使用表锁
ACID:Atomicity(原子性),Consistency(一致性),Isolation(隔离性),持久性(durability)
原子性:事务具有不可分割的性,一个事务被视为不可分割最小单元,在事务中的操作要么全部成功,要么全部失败。意思就是不能只执行事务中的一部分。
一致性: 事务将数据由一种一致性的状态转换到另一种一致性的状态,保证了事务开始之前和结束后的数据完整性
隔离性:一个事务的执行不能被其它的干扰,即一个事务内部中操作及使用的数据对其它事务是隔离的,并发执行的各个事务之间不能互相干扰
持久性:一旦事务提交,事务中操作修改的数据会永久的保存到数据库中,此时即使系统崩溃已提交的数据已不会丢失
事务隔离级别:脏读,幻读,不可重复读;理论上事务的隔离性可以保证事务之前的操作互不干扰,多个事务操作同一份数据必须等待上一个事务结束才可,这样就成了串行,但效率极低。因此需要舍弃事务原本的隔离性,由此出现了事务隔离级别的概念
脏读:一个事务读到了另一个事务的已修改但未提交的数据
幻读:一个事务执行中,另一个事务将新纪录添加到了正在读取的事务中
不可重复度:当事务内相同的记录被检索两次,且两次的检索结果不一样
JVM
内存结构(运行时数据区):堆(Heap),方法区(Method area),栈(本地方法栈(Native Method Stacks),虚拟机方法栈(Java Virtual Machine Stacks)),程序计数器(The pc Register)
堆:java虚拟机所管理的内存中最大的一块;在虚拟机启动时创建,被所有线程共享,java对象实例及数组都在堆上分配
方法区:Java虚拟机规范把方法区描述为堆的一个逻辑部分,别名Non-Heap,目的是与java堆区分开来,在java虚拟机启动时创建,被所有线程共享;用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译后的代码等数据;当方法区无法满足内存要求时抛出OutOfMemoryError异常
虚拟机栈:是一个线程执行的区域,保存着一个线程中方法的调用状态;
1:一个JAVA线程的运行状态由一个虚拟机栈保存,所以虚拟机栈是线程私有的,随着线程的创建而创建
2:每一个被线程执行的方法为该栈中的栈帧,即每个方法对应一个栈帧;调用一个方法就会向栈中压入一个栈帧,一个方法调用完成,就会把该栈帧从栈中弹出(FILO先压入的栈帧后弹出)
程序计数器:线程的执行权由CPU时间分片轮换调度,当一个线程执行过程中失去了执行权时由程序计数器记录当前线程执行的位置,待线程再次获得执行权时由程序计数器记录的位置处开始执行
1:如果线程正在执行的是JAVA方法,则计数器记录的是正在执行的虚拟机字节码指令的地址
2:如果线程正在执行的是native方法,则这个计数器为空
本地方法栈:如果线程执行的是native方法,这些方法就会在本地方法栈中执行;如果在JAVA方法中调用native方法,则以动态链接的方式执行调用
内存结构(概念):老年代(old),新生代(Eden,survive0,survice1)
垃圾回收(GC):
Eden:1:在JAVA对象的头部(markWord)中会存储当前的对象GC次数,当一个对象GC次数大于18时会将此对象从新生代转移到老年代
2:当有较大的对象进入Eden区时,首先会将Eden存在的不连续空间的对象转移到survive0区;当survive0区中的对象总内存超过survive0区的一半时,将survive0区中对象复制到survive1区中以便在survive0区中空出连续的完整空间
3:当占有超过Eden区内存一半的对象时,会直接存放到老年代
什么时候会进行垃圾回收:
1:当Eden区或S区内存不够用了
2:老年代空间不够用了
3:方法区空间不够用了
4:System.gc()
垃圾回收算法:标记-清除,标记复制
可达性分析算法:通过GC Root对象(能作为GC Root:类加载器,Thread,虚拟机栈的本地变量表,static成员,常量引用,本地方法栈的变量等)开始向下寻找,看某个对象是否可达,不可达的对象标记回收
标记-清除(Mark Sweep)
标记:找出内存中需要回收的对象,并且把它们标记出来。此时堆中的所有对象都要被扫描一遍,才能确定需要被回收的对象,比较耗时。
清除: 清除掉被标记需要回收的对象,释放出对应的内存空间。
缺点: 标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
标记-复制(Mark Copying)
将内存划分为两块相等的区域,每次只使用其中一块,当其中一块使用完了,就将还存活的对象复制到另外一块内存上,然后把已使用过的内存空间一次清除掉。
缺点:空间利用率降低
标记-整理(Mark Compact)
标记过程与“标记清除算法”一致,但后续步骤不是直接对可回收对象进行清理,而是让存活对象都向一端移动,然后直接清理掉端边界以外的内存。 相较于“标记复制算法”来说少了一个“保留区”。
Young区:复制算法(对象在被分配之后,可能生命周期比较短,Young区复制率较高)
Old区:标记清除或整理(Old区对象存活时间比较长,复制来复制去没有必要,不如做个标记再整理)
垃圾收集器分类:
串行收集器:Serial、Serial Old
只能有一个垃圾回收线程执行,用户线程暂停;适用于内存比较小的嵌入式设备
并行收集器(吞吐量优先):Parallel Scanvenge、 Parallel Old
多条垃圾回收线程并行工作,但此时用户线程仍然处理等待状态;适用于科学计算,后台处理等交互场景
并发收集器(停顿时间优先):CMS、G!
用户线程和垃圾收集线程同时执行(可能是交替执行),垃圾收集线程在执行的时候不会停顿用户线程的运行;适用于相对时间有要求的场景,比如Web
JVM查看参数
java -XX:+PrintFlagsFinal -version > flags.txt
常用参数含义