Java面试梳理
2024-07-23 17:02:17 3 举报
AI智能生成
瞎写的,别看
作者其他创作
大纲/内容
Java基础
数据类型
基本数据类型: byte(1字节),char(2),short(2),int(4),float(4),long(8),double(8),boolean
引用数据类型:类、接口、数组
泛型
本质:参数化类型,可以将操作的数据类型指定为一个参数
泛型擦除:java在编译期间会擦除所有泛型信息。(例如通过反射可以在List<Integer>中添加指定Integer类型之外的数据)
泛型使用方式:泛型类、泛型接口、泛型方法
==与equals
== 比较的是两个对象的地址值是否相等,对于基本数据类型而言,比较的就是值
equals 是Object类中的方法,不能用于比较基本数据类型的变量。没有被重写时等价于== ,被重写时一般用于判断两个对象的内容是否相等
hashCode()与equals()
hashCode()是Object中的native方法,用于将对象的内存地址转换为整数后返回。在判断对象是否相同时,可以先通过判断hashCode是否相等,如果hashCode相等,才会去调用equals方法来检查hashCode相等的对象是否真的相同。hashCode不相等,则两个对象一定不相等,不会去调用equals方法,这样一来减少了调用次数,提升了执行效率
如果两个对象相等,hashCode一定相等,equals方法会返回true。但是两个对象有相等的hashCode值,他们也不一定是相等的。因此重写equals时一定要重写hashCode方法
自动拆箱与装箱
装箱:基本数据类型转换为对应的包装类
拆箱:包装类型转换为对应的基本数据类型
重载与重写
重载:同一个类中的同名方法,根据传入参数的不同,进行不同的逻辑处理。方法的返回值和权限访问修饰符也可以不同。
重写:子类继承父类时,可对父类中的同名方法进行改造。父类引用指向子类对象时,调用的是子类的同名方法。
1、返回值类型、方法名、参数列表必须相同,访问权限修饰符大于等于父类,抛出异常类型小于等于父类;
2、父类中的private、static、final方法不能被重写
3、构造方法无法被重写
浅拷贝与深拷贝
浅拷贝:对基本数据类型进行值拷贝,对引用数据类型进行引用地址的拷贝
深拷贝:对基本数据类型进行值拷贝,对引用数据类型而言,会重新创建一个新的对象并复制其内容
接口与抽象类
接口中的方法默认是public abstract,方法不能有默认实现(jdk8中static、default方法可以有默认实现);抽象类中可以没有抽象方法,抽象方法没有方法体
接口中的变量默认是public static final
一个类可以实现多个接口,只能继承一个抽象类
从设计层面来讲,抽象类是对类的抽象,是一种模板设计;接口是对行为的抽象,是一种行为规范
字符串
String 不可变,底层是final字节数组,常用api:length、equals、equalsIgnoreCase、subString、isEmpty、contains
StringBuffer 可变,线程安全,效率比String高
StringBuilder 可变,线程不安全,效率比StringBuffer高
Stream常用api
foreach、filter、max、min、count、map、distinct、sort
优雅判空 Optional
并行流parallel()
集合
Collection
List 有序 可重复
ArrayList
基于数组实现,查询快 增删慢
插入时候,会先检查是否需要扩容,如果当前容量+1 > 数组长度,就会进行扩容。ArrayList的扩容是创建一个1.5倍的新数组,然后把原数组的值拷贝过去。
LinkedList
基于双向链表实现,增删快 查询慢
Set 无序 不可重复
HashSet 底层是哈希表
TreeSet 底层是二叉树
Map
HashMap
jdk1.7
数据结构
数组 + 链表
扩容:先扩容,再使用头插法添加数据,多线程成环
jdk1.8
数据结构
数组 + 链表 + 红黑树
扩容:尾插法,先插后判断是否需要扩容
put流程
计算hash值,判断数组是否为空,为空进行扩容,否则计算出数组下标hashCode&(length-1);判断该位置是否为空,为空直接添加元素,否则进行判断key是否相等,相等则覆盖,否则判断是否为树节点,是树节点则在红黑树中插入元素,否则遍历链表,判断key是否存在,存在则覆盖,否则在链表尾部插入元素,最后判断链表元素是否>8 && 数组元素是否>64,进行转换为红黑树;最后会进行一次容量判断,容器中的元素>容器容量的0.75倍时扩容原来的2倍
get流程
计算数组下标,在数组中判断是否匹配,否则判断当前节点是否是树节点,是则查找红黑树,否则遍历链表
初始容量
2的整数倍 让元素在数组中的位置更加均匀
(建议)已知容量/0.75+1
遍历方式
Iterator迭代器 iterator.remove() 的方法来删除数据 遍历时使用集合的remove()是不安全的
forEach
stream流
lambda表达式
LinkedHashMap 保证有序
TreeMap 使用键值进行排序
HashTable 线程安全 效率较低,使用ConcurrentHashMap代替
ConcurrentHashMap
JDK1.7
Segement数组 + ReentrantLock 实现分段锁
JDK1.8
synchronized实现分段锁 + CAS锁机制
IO
字节流
InputStream
FileInputStream
OutputStream
FileOutputStream
字符流
Reader
Writer
异常Throwable
Exception
运行时异常
NullPointException
IndexOutOfBoundException
ClassCastException
非运行时异常
IOException
FileNotFindException
SQLException
Error
OOM
反射
获取Class
.getClass()
Class.forName("class path")
.class
反射机制
在运行时过程中,对于任何一个类都可以获取到类中的所有属性和方法,并且可以调用所有方法
优势和劣势
运行时类型检查,动态类加载,增加了代码的灵活性
效率低,编译器没法对反射相关的代码做优化
应用场景
JDBC加载数据库驱动
Spring通过解析xml装载bean
常用设计模式
单例模式
私有化构造器 私有化静态实例 暴露公有方法获取单例对象
饿汉式 私有化静态实例时new对象赋值
懒汉式:双检 进入方法后判空一次 synchronized加锁后再次判空
应用场景
spring容器默认创建单例
线程池对象
数据库连接池对象
观察者模式
对象之间存在一对一或者一对多的依赖关系时,一个对象状态改变就会通知到依赖他的对象
应用场景:mq消息订阅
代理模式
使用代理类完成目标类的操作
静态代理
动态代理
基于接口:JDK
基于子类:Cglib
应用场景
Spring AOP
Spring声明式事务
工厂模式
适配器模式
让没有关联的类可以一起工作
应用场景:springmvc适配各种handler
并发编程
线程创建方式
继承Thread类,重写run()
实现Runnable接口,重写run()
实现Callable接口,重写call(),可通过FutureTask.get()获取返回值
线程启动
调用线程的start(),会创建一个新的线程,在新的线程中执行run()
并发三大特性
原子性
synchronized
可见性
volatile 保证可见性,不保证原子性
有序性
volatile 防止指令重排
JMM 描述了线程与主内存之间的抽象关系 ,每一个线程都有只有自己能访问的私有变量,以及每个线程都能访问的共享变量,存放在主存中
线程通讯
volatile 保证修饰字段的可见性以及防止指令重排,访问字段时从共享内存中获取,对字段的修改也会同步刷新至共享内存中
等待wait/通知notify机制,实现一个线程修改一个对象的值,而另一个线程感知到了变化,然后进行相应的操作
thread.join() 当前线程等待thread执行结束后执行
ThreadLocal 线程本地变量,为每个线程创建一个ThreadLocal变量的副本,操作共享变量时到达线程隔离的效果,防止线程安全问题
存放当前用户信息,数据库连接
进程通讯
rpc
消息队列
管道
信号量
socket
linux中的进程与线程
线程就是共享一块地址空间的一组进程
锁(互斥锁)
synchronized
是一种对象锁。
作用在类方法上时,锁的是当前类对象
作用在实例方法上时,锁的使用当前的实例对象
作用在代码块时,锁的是括号里面的对象
jdk1.6对其性能进行了优化。引入偏向锁 ,轻量级锁,重量级锁
锁升级的过程:首先对对象加偏向锁,当该线程再次获取锁的时候不需要做任何同步操作,直接可获取锁。当发生资源竞争激烈的场景时,偏向锁升级为轻量级锁,轻量级锁适用于线程交替执行同步块的场景,当同一时间访问同一把锁时,轻量级锁会进行自旋,(让当前需要获得锁的线程做空循环)如果自旋一定次数后还没获取到锁,则会升级成为重量级锁
ReentrantLock
支持手动加锁与解锁,以及加锁的公平性
公平锁(构造函数传入true参数):哪个线程先来就先获取锁
非公平锁(默认):线程在竞争锁的时候,如果锁没被占用,会尝试获取锁,获取不到才会继续排队
非公平锁能直接获取锁,减少了线程加入队列,以及线程唤醒这些步骤,所以效率较高
锁时间长 并发度低 可以使用公平锁,否则使用非公平锁
可重入
获取锁之后还可以重新获取锁,写锁可以降级为读锁
读写锁
读锁会阻塞写,写锁会阻塞读和写
通过自旋进行加锁(CAS算法),加锁成功后跳出循环,没有获取锁的线程将进入等待队列,同时将其阻塞
JUC
Condition 线程协调接口 代替Object类的同步方法
Condition condition = Lock.newCondition()
await 阻塞线程
signal 唤醒线程
AQS
AbstractQueueSynchronizer抽象类 定义了一套多线程访问共享资源的同步器框架(模板模式)
同步状态的管理
state=0表示可用,1被占用,大于1,表示被占用了几次,重入的次数。
线程的阻塞和唤醒
LockSupport
park 阻塞
unpark 唤醒
等待队列的管理(AQS核心)
先进先出队列,基于双向链表实现,CLH队列的变种,由自旋机制(轮询前驱节点 释放锁结束自旋)改为阻塞机制(尝试自旋一次后让线程阻塞让出时间片)
队列的节点是对线程状态的封装
独占 :只有一个线程能执行 如ReentrantLock
共享:多个线程能同时执行 Semaphore/CountDownLatch
大部分的方法都是final / private 不希望关注细节 只需要重写特定的方法
CountDownLatch 倒数计数器 >0所有线程都为阻塞 =0全部释放
CyclicBarrier 不满足某个条件时,阻挡所有线程
信号量 Semaphore 类似于许可证
ConcurrentSkipListMap
跳表,线程安全,有序,底层维护了索引,通过索引达到的时间复杂度
线程池ThreadPoolExecutor
java线程模型基于KLT,线程交给内核空间管理,线程挂起时会进行上下文切换,从用户态转化为内核态
线程状态切换
使用线程池的目的 :减少创建和销毁线程时资源的损耗,提高响应速度
线程池参数
核心线程数
CPU密集型:1.5~2倍CPU核心数
IO密集型:一般设置小于CPU核心数
最大线程数
2*核心线程数或稍多一点
线程空闲存活时间
时间单位
阻塞队列
ArrayBlockingQueue 基于数组,有界
LinkedBlockingQueue 基于链表,无界 吞吐量比ArrayBlockingQueue高
PriorityBlockingQueue 具有优先级的无界阻塞队列
线程工厂
拒绝策略
AbortPolicy(默认) 直接抛异常
CallerRunsPolicy 使用调用者所在的线程来执行任务
DiscardOldestPolicy 丢弃最靠前的任务 执行当前任务
DiscardPolicy 直接丢弃当前任务
执行过程
执行task时会不断创建核心线程,当核心线程不够用了,会将task放入阻塞队列里,核心线程会取出阻塞队列中的task进行执行,当阻塞队列也不够用了,则会创建非核心线程执行任务
当线程空闲超过指定的时间后,线程会被销毁,默认销毁的是非核心线程,销毁核心线程需要手动进行设置
JVM
类加载过程
类加载顺序:父类静态变量/父类静态代码块->子类静态变量/子类静态代码块->父类成员变量/父类非静态代码块->父类构造器->子类成员变量/子类非静态代码块->子类构造器
类加载机制
双亲委派机制设计的原因:
沙箱安全机制,防止核心类库被篡改
避免类的重复加载
Tomcat打破双亲委派机制:
加载相同类库的不同版本(默认类加载器只在乎你的全限定类名,并且只有一份。)
实现热加载,修改文件时会清空类加载器(默认类加载器会直接取方法区中已经存在的,修改后的jsp是不会重新加载的)
自定义类加载器:
继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是
loadClass(String, boolean),实现了双亲委派机制;还有一个方法是findClass,默认实现是空
方法,所以我们自定义类加载器主要是重写findClass方法。
loadClass(String, boolean),实现了双亲委派机制;还有一个方法是findClass,默认实现是空
方法,所以我们自定义类加载器主要是重写findClass方法。
JVM内存模型
整体结构
线程独有:虚拟机栈,本地方法栈,程序计数器
堆
创建的对象首先存在于Eden区,Eden区满了触发Minor gc,会清空Eden区,同时将存在引用的对象放入S0区;第二次gc会清空Eden区和S0区,将存在引用的对象存入S1区,如此交换15次,还存在引用的对象将存入老年代;老年代满了,触发Full gc,清空新生代和老年代,老年代仍然无法腾出空间,则会抛出OOM。
STW机制:每次gc时会暂停用户线程
垃圾回收算法
标记-复制算法(新生代GC)
将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。
标记-清除算法(老年代GC)
标记存活对象,将未被标记的对象全部回收
缺点
效率问题:需要标记的对象太多
空间问题:标记清除后可能会产生大量不连续的内存碎片
标记-整理算法
标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回
收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
垃圾回收器
Serial收集器:单线程
Parallel收集器:多线程
ParNew收集器:只能用于年轻代,可以与CMS收集器配合使用
CMS收集器:基于标记-清除算法实现,基本上实现了用户线程与gc线程同时工作,提升了用户的体验
会和服务抢占cpu资源
浮动垃圾,并发标记和并发清理中对象清理后又重新创建,只能等待下一次并发清理
标记-清除算法产生内存碎片,可开启内存整理解决
G1收集器:替代CMS,自适应新生代、老年代、元空间的大小
JVM内存参数
-XX: MaxMetaspaceSize 元空间最大值,默认-1,即不限制。一般设置为MetaspaceSize相同且比初始值要大
-XX: MetaspaceSize 指定元空间触发Full gc的初始阈值。默认21M,达到该值后触发Full gc,同时收集器会对该值进行调整: 如果释放了大量的空间, 就适当降低该值; 如果释放了很少的空间, 那么在不超过MaxMetaspaceSize(如果设置了的话) 的情况下, 适当提高该值
对象创建过程
分配内存
划分内存的方法
指针碰撞(默认)
通过指针将未分配与已分配的区域进行划分,下次分配时从指针的位置开始分配,同时将指针后移
空闲列表
维护了一张未分配区域的表,在分配时在表中找到大小合适的区域进行划分,同时更新表
并发问题解决方式
CAS+失败重试
TLAB 本地线程缓冲(默认)
每个线程在堆中预先分配一小块内存,将内存分配动作划分在不同空间中进行
栈上分配
逃逸分析
当JVM开启逃逸分析后,会在方法执行时进行分析,如果方法没有对象返回,则采用标量替换的方式在栈中为对象分配内存
标量替换
通过逃逸分析确定该对象不会被外部访问后,JVM不会创建该对象,而是将该对象在方法中使用到的成员变量在栈中进行内存分配
大对象分配
直接进入老年代,避免大对象来回复制影响效率
初始化
给成员变量赋初始值
设置对象头
对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对
象的哈希码、对象的GC分代年龄等信息
象的哈希码、对象的GC分代年龄等信息
执行init方法
按照程序员的意愿进行初始化,为属性赋值
对象回收
判断对象是否回收
引用计数法
每次对象引用时加1,引用失效时减1,当计数器为0时,表示对象可以被回收。
问题: 循环引用的对象永远不会被回收,会造成内存泄漏的问题。
可达性分析
以GC Root为根节点,向下寻找引用对象,找到的引用对象标记为不会回收,没找到的对象标记为可回收
GC Root : 线程栈中的局部变量、静态变量、本地方法栈中的变量
标记为可回收的对象,如果没有重写finalize方法,则会直接回收;如果重写了finalize方法,则会去执行重写的finalize
常见引用类型
强引用:普通对象引用,例如 : new对象
软引用: new SoftReference(new 对象),GC做完后,发现腾不出空间了才会去回收
弱引用:new WeakReference(new 对象),GC会直接回收
虚引用
SSM
Spring
一句话定义 : 一款轻量级的,非侵入式的控制反转IOC、面向切面AOP的框架
常用注解
Web相关
@RestController 相当于@Controller+@ResponseBody 将Java对象转换为Json字符串返回
@RequestMapping
@GetMapping
@PostMapping
@RequestBody 将前端传过来的Json字符串转换为Java对象
@PathVarible 用于解析以Restful风格请求路径下的参数 例如 :/getUserInfo/{userId} ====> @PathVarible("userId")
容器相关
@Component
@Service
@Configuration
@Value
@Autowired
@Qualifier 配合@Autowired使用byType变为byName
@Bean
AOP相关
@Aspect 声明该类为切面
@PointCut 声明切点
@Before
@After
@Around
事务相关
@Transactional
IOC
控制反转,将对象的创建和管理交给容器,不再由自己创建。
DI 依赖注入,容器在实例化对象时将对象依赖的类注入给它,是IOC的一种实现
构造器注入
setter注入
属性注入
IOC加载过程
1、容器启动阶段
2、Bean实例化阶段
循环依赖问题
自己依赖自己,或者bean之间存在相互依赖
spring没有解决循环依赖的场景
原型bean情况下(每次getBean时重新创建 不走缓存)
构造器注入情况下(当使用构造器注入时,Spring在创建Bean实例时,会调用构造器并传递所有需要的依赖项。
这意味着在Bean实例创建的过程中,所有依赖项必须已经完全初始化并可用)
用Setter方法进行注入,Spring可以先创建Bean的实例,然后在后续阶段注入依赖项,从而解决循环依赖
这意味着在Bean实例创建的过程中,所有依赖项必须已经完全初始化并可用)
用Setter方法进行注入,Spring可以先创建Bean的实例,然后在后续阶段注入依赖项,从而解决循环依赖
三级缓存的意义: 一级缓存:存放创建好的bean;二级缓存:存放早期bean(未初始化),防止多线程环境下getBean出现问题;三级缓存:存放创建bean的工厂,解决aop代理对象存在循环依赖的问题,如果需要代理对象,通过三级缓存中的对象工厂就可以创建;如果不需要,通过三级缓存中的对象工厂同样也可以创建普通对象,即在三级缓存中对于创建普通对象还是代理对象进行了逻辑处理,二级缓存只需要无脑存无脑返回即可
Bean的作用域
singleton,Spring容器中只存在一个Bean实例,Spring默认作用域
prototype,每次getBean时都会重新创建
Web
request,每次http请求都会创建一个bean,只在当前请求中有效
session,同一个session共享一个bean
globalSeesion,同一个全局Session共享一个Bean,Spring5移除
AOP
面向切面编程,将业务代码中的一些共同的逻辑(例如:日志、数据校验、权限判断),抽取出来,写成一个单独的类,在业务需要时通过动态代理完成织入降低了代码的耦合度,在开发时只需要关注业务本身
AOP原理
spring通过定义一个切面类,就是在类上添加@Aspect注解,类中添加一个@Pointcut修饰的方法以及一系列的advice方法(@Before、@After、@Around 等修饰),来完成一个切面操作
1、找到所有切面类
2、解析所有advice并缓存
3、创建一个动态代理类
4、调用被代理类的方法时,找到对应的所有advice实现方法增强处理
基于动态代理
JDK动态代理(目标对象实现了接口)
基于接口生成代理类(.class文件),通过反射调用具体方法
Cglib动态代理
基于子类生成代理类
声明式事务
基于AOP,对事务方法前后进行拦截,将事务处理的功能编织到拦截的方法中,结合@Transactional注解实现。封装了JDBC中事务提交、回滚的代码
事务传播机制
sevice层中的已经声明了事务的方法在相互调用时,需要对事务机制进行协商,这就是事务的传播机制
spring 是使用 aop 来代理事务控制 ,是针对于接口或类的,所以在同一个 service 类中两个方法的调用,传播机制是不生效的
REQUIRED(Spring默认):加入当前事务,当前没有事务就创建一个新的事务。场景:内层方法即使捕获了异常,外层方法也会回滚,他们都是在一个事务里面。
SUPPORTS: 支持当前方法的事务,当前方法没有添加事务控制,则以非事务方式运行
REQUIRES_NEW:总是会创建一个新的事务,使用场景:捕获不同类型的异常日志
失效场景
只在public方法生效
同一个类中非事务方法调用事务方法
没有抛出异常,异常被处理掉了
默认抛出运行时异常或Error才会回滚,可通过rollbackfor指定触发回滚的异常
涉及到的设计模式
简单工厂模式
Beanfactory 通过beanName创建对应的对象
工厂方法
实现FactoryBean接口,重写getObject方法,在从容器中获取对象时会自动调用
单例模式
由beanFactory创建的对象默认是单例的
代理模式
AOP就是基于动态代理创建代理对象实现切面的织入
模板方法
JdbcTemplate、RedisTemplate、RabbitTemplate等等
适配器模式
SpringMVC中的HandlerAdapter根据Handler规则执行不同的Handler
SpringMVC
处理流程
MyBatis
半自动ORM框架,对jdbc的封装,引入连接池,缓存
执行过程:创建SqlSessionFactory->创建SqlSession->获取Mapping->执行Sql->提交事务->关闭SqlSession
多参数传递
顺序传参,不建议使用
Java Bean传参 不易扩展
@Param 注解指定参数名称
Map传参 灵活
#{} 与${}
#{}是占位符,预编译处理,防止SQL注入;${}是拼接符,字符串替换,没有预编译处理
模糊查询
CONCAT('%',#{name},'%')
获取自动生成的主键
<insert id="insert" useGeneratedKeys="true" keyProperty="userId" >
mapper.insert(user);
user.getId();
user.getId();
常用标签
<resultMap> 结果集映射
<sql> sql占位符
<insert>
<delete>
<update>
<select>
<association> 关联查询 一对一
<collection> 关联查询 一对多
<if test="">
<set> 动态更新
<foreach collection="listName" item="item" separator=",">
<trim>
<where> 避免使用 where 1=1
缓存机制
一级缓存 (默认开启): sqlSession级别,各个sqlSession之间相互隔离,sqlSession关闭后,缓存清空
二级缓存 : namespace级别,多个sqlSession之间共享
工作原理
MyBatis整体工作原理
微服务技术栈
SpringBoot
约定大于配置
本质上就是说,系统、类库、框架这些东西都应该有一个默认的配置,而不是需要提供不必要的配置信息
配置读取顺序:.properties ---> .yml ---> .yaml
自动装配
@SpringBootApplication
@ComponentScan
@EnableAutoConfigurtion 扫描类路径下所有jar包中的spring.factory进行自动配置类的注入
@SpringBootConfiguration
启动流程
创建SpringApplication对象,注册监听器;通过监听器根据时机触发不同的事件,如容器初始化,容器的创建。以及容器刷新,进行组件的扫描,创建,加载等
自定义starter
1、导入spring自动配置依赖
2、编写javabean 使用注解:@EnableConfigurationProperties、 @ConfigurationProperties
3、创建自动配置类 使用注解:@Configuration @ConditionalOnClass
4、在resources下创建/META-INF/spring.factories 把自己创建的自动配置类全路径类名配置上去,org.springframework.boot.autoconfigure.EnableAutoConfiguration= com.xxx.config.MyAutoConfiguration
5、在其他项目中导入自定义的starter即可使用
SpringCloud(微服务协调和治理)
Erueka 注册中心
注重服务的可用性,只要有一台机器可用就能对外提供服务
zk强调一致性,超过半数的节点挂了需要120s进行重选才能对外提供服务
Ribbon 负载均衡
@LoadBalanced
Hystrix 熔断降级 下游服务因为压力过大而访问变慢或者失败时,上游服务暂时切断对下游服务的调用,防止服务雪崩。局部牺牲,保全整体
Feign 服务的远程调用
轻量级RESTful的HTTP服务客户端
Gateway API网关
统一接入(路由)
调用微服务
安全防护
统一的认证和鉴权
黑白名单
流量监控
限流
容错
负载均衡
协议适配
Config 配置中心
Sleuth + Zipkin 链路追踪 对一次请求的整个调用链路进行日志收集、性能监控
SpringCloud Alibaba
Nacos 注册中心 + 配置中心
Sentinel 熔断 + 限流 + 降级
Dubbo 服务远程调用 + 负载均衡
功能
远程通讯
集群容错
服务的自动发现
服务提供者端使用@DubboService : 将被修饰的类注册到注册中心中
dubbo支持多种协议的调用
服务消费者端使用@DubboReference:生成代理对象,根据协议+IP+端口+服务名(类名)实现rpc调用,并且会将代理对象注入spring容器中
与Feign对比
Feign通过REST API实现远程调用,是基于HTTP的,服务提供者需要对外暴露HTTP接口供消费者调用。通过短连接进行通信,不适合高并发的访问。服务粒度是http接口级的。
Dubbo更加灵活,通过RPC调用实现远程调用,支持多种传输协议(Dubbo、http、rmi、redis等等)。默认使用Dubbo协议,采用长连接进行通信,适合高并发场景。服务粒度是方法级的。
MySQL
常用数据类型
整型
tinyint 1字节
int 4字节
bigint 8字节 自增主键
浮点型
decimal
字符型
varchar
日期型
datetime 8字节
timestamp 4字节 范围小1920~2038,默认当前时间
常用函数
max/min/sum/concat/date_format
索引原理:排好序的数据结构
索引数据结构的选择
二叉树:遇到单列递增数据时,树退化为一张链表,查询效率降低
红黑树:通过自平衡解决单列递增数据的问题,但是随着数据量的增加,树的深度也会增加,查询效率也会降低
B树:通过对树的根节点进行拓展,降低了树的深度,索引值和数据都存储在节点上
所有节点都存储数据
B+树:对B树进行优化,将数据存储在叶子节点上,非叶子节点只存储索引值,可在相同深度的树中存储更多的数据(每个节点只能储存16kb的数据);叶子节点通过指针连接,提高了区间的访问效率
innodb 索引和数据在一个文件里(聚簇)
聚集索引 (叶子节点存储所有数据)
联合索引
最左前缀原理:联合索引的第一个字段作为查询条件的时候才会走索引
myisam 索引和数据是分离的(非聚簇)
非聚集索引(叶子节点只储存主键)
hash:通过索引的key进行一次hash运算就可以快速定位到数据的位置。问题:仅能满足in、=查询,不支持范围查询;哈希冲突
索引分类
单列索引
普通索引
唯一索引
主键索引
复合索引
全文索引 myisam
为什么推荐使用整型自增主键
整型:方便比较大小
自增:因为索引是按顺序进行组织的,插入无序的主键时,mysql会对索引进行重排,降低了效率
为什么非主键索引存储的是主键值
节约存储空间
一致性:主键索引更新完成后再更新非主键索引
Explain使用
type列:依次从最优到最差分别为:system > const > eq_ref > ref > range > index > ALL
const, system:mysql能对查询的某部分进行优化并将其转化成一个常量(可以看show warnings 的结果)。用于
primary key 或 unique key 的所有列与常数比较时,所以表最多有一个匹配行,读取1次,速度比较快。system是
const的特例,表里只有一条元组匹配时为system
primary key 或 unique key 的所有列与常数比较时,所以表最多有一个匹配行,读取1次,速度比较快。system是
const的特例,表里只有一条元组匹配时为system
eq_ref : primary key 或 unique key 索引的所有部分被连接使用 ,最多只会返回一条符合条件的记录
ref : 不使用唯一索引,或者只使用了唯一索引中的某个字段,会返回多个符合条件的结果
range : 范围扫描通常出现在 in(), between ,> ,<, >= 等操作中。使用一个索引来检索给定范围的行
index : 扫描全索引就能拿到结果,一般是扫描某个二级索引,这种扫描不会从索引树根节点开始快速查找,而是直接
对二级索引的叶子节点遍历和扫描
对二级索引的叶子节点遍历和扫描
ALL : 全表扫描,扫描聚簇索引的所有叶子节点。
SQL执行原理
客户端->连接器(权限验证)->词法分析器(词法分析,语法分析)->优化器(生成执行计划,选择索引)->执行器(调用执行引擎)
SQL执行顺序
from -> on -> join -> where -> group by -> having -> select -> order by -> limit
sql优化
like
最左前缀原则
避免使用select * ,同时可以通过联合索引进行索引覆盖,防止回表
order by
尽量使用索引排序,防止文件排序
文件排序
单路排序:一次性取出满足条件行的所有字段,然后在sort buffer中进行排序
双路排序:取出排序字段和id,在sort buffer中进行排序,然后由id回表去取全部数据
MySQL 通过比较系统变量 max_length_for_sort_data(默认1024字节) 的大小和需要查询的字段总大小来
判断使用哪种排序模式。
判断使用哪种排序模式。
遵循最左前缀原则
group by
先排序后分组,如果不需要排序的可以加上order by null禁止排序
join
关联字段加索引
不使用索引 : BNL算法,将小表的所有数据加载到join buffer中,把小表中的每一行记录与大表的每一行记录进行比较,找到符合条件的结果后合并
使用索引 : NLJ算法,从小表中读取一行数据后,拿着连接条件去取大表中满足条件的数据,然后与小表中取出的那行数据进行合并
in
select * from A where id in (select id from B) B表是小表
is
合理使用非空,为指定字段设置了非空(not null),在使用is null 或 is not null时是不走索引的
合理使用mysql函数,函数统计索引字段会造成失效
or关键字时,切记两个条件都要添加索引,否则会导致索引失效
事务
ACID特性
原子性:事务中的操作要么都执行成功,要么都不成功(操作层面)
一致性:在事务开始和完成时,数据都必须保持一致状态(数据层面)
隔离性:多个事务之间是相互隔离的,不会影响到各自的执行
持久性:事务执行完成后,对数据库的改变就是永久的
隔离级别
读未提交:存在的并发问题----脏读、不可重复读、幻读
读已提交:存在的并发问题----不可重复读、幻读(oracle默认级别)
(可看到其他事务的新增数据)可重复读:存在的并发问题----幻读(mysql默认级别)
通过MVCC机制来保证可重复读——undo日志版本链以及read-view(读视图)对比,在不同事务中会根据对比规则,读取同一条数据在版本链上不同版本的数据
串行化 : 事务中select时都会加锁,影响性能
锁
效率上
乐观锁:不会操作锁等待,通过版本号控制写操作是否能执行
悲观锁 : 会产生锁等待
数据操作上
读锁(共享锁):针对同一份数据,所有读操作可以同时进行互不影响
写锁(排他锁):在对当前数据进行操作时,其他的读操作和写操作都不能进行
粒度上
表锁 :开销小,加锁快;不会出现死锁;并发度低
行锁 : 开销大,加锁慢;会出现死锁;并发度高
innodb的行锁是针对索引添加的行锁,索引失效时,行锁会升级成表锁
间隙锁
执行 update user set name = 'zyz' where id > 8 and id <18,则其他Session没法在这个范围所包含的所有行记录以及行记录所在的间隙里插入或修改任何数据
myisam与innodb
innodb支持事务,行锁,聚簇索引
myisam支持表锁,非聚簇索引
sql执行过程
undo log 、redo log、bin log详解
undo log 记录变更前的数据以及事务id,用于事务失败回滚
redo log 记录的是磁盘数据变更
bin log 记录的是表数据变更
分布式组件
Redis
基本数据结构
String
计数器
对象缓存:存储session信息,验证码,权限菜单,字典比如省份地区
分布式锁
redisson 看门狗机制:不指定过期时间默认30s,每10s对锁进行续期
最佳实践:建议将锁的过期时间设置为大于业务时间,而不是不指定过期时间,省去为锁续期造成的性能损耗
读写锁:保证读到最新数据。读操作加读锁,写操作加写锁。写锁存在时,不能读和写
Hash
对象缓存
电商购物车
List
微信公众号消息流
Set
点赞
抽奖
关注模型(共同关注、可能认识的人)
ZSet
排行榜
高性能原理
单线程
redis在处理网络IO、数据读写时是单线程,避免线程上下文切换造成资源损失;持久化、异步删除、集群同步使用额外的线程完成
内存操作
IO多路复用
redis内核中存在多个监听套接字和已连接套接字,一个线程可以处理多个IO请求,并且将多个IO请求放入事件队列中,由文件事件分派器分派给cpu执行
持久化
RDB(默认开启) : 定期将内存数据保存快照到磁盘,重启时恢复
AOF : 将每行修改操作记录保存到磁盘,重启时通过执行每一行命令来恢复数据
混合持久化:AOF的优化,AOF在重写时以RDB的方式追加到文件中
高可用
主从
一主一从
一主多从
树型结构
主从复制:主节点同步数据到从节点
好处
1、数据冗余 实现了数据的热备份
2、单机故障 实现快速的故障恢复
3、读写分离
4、负载均衡
5、高可用的基石
复制原理
全量复制 : 从节点初始化时进行全量复制,首先会清空自身的数据,然后通过主节点发过来的RDB文件进行数据同步
增量复制 :通过偏移量来实现增量复制
减少数据丢失
先存入本地缓存,定期同步
mq延时队列
防止脑裂造成数据丢失,配置redis,将同步时间和从节点数量进行限制,超过限制后主节点不提供服务
哨兵
哨兵节点 + 数据节点 :实现自动故障转移
集群
数据分区:解决单机的读写限制。每个节点都能提供读写能力,提高了集群的响应能力
高可用,支持主从复制和自动故障转移。当任一节点发生故障时,集群仍然可以对外提供服务。
缓存设计
缓存穿透
查询存储层不存在的数据,请求直接打到存储层,造成存储层崩溃
解决方案
缓存空对象,并设置过期时间
布隆过滤器:将所有可能存在的数据哈希到一个足够大的 bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
缓存击穿
一个热点的Key,有大并发集中对其进行访问,突然间这个Key失效了,导致大量并发全部打在数据库上,导致数据库压力剧增
解决方案
热点key设置永不过期
加锁
缓存雪崩
大量请求的数据同时失效,直接访问存储层
解决方案
将缓存过期时间设置为随机值
熔断限流并降级
删除策略
被动删除
过期的数据在下次get时才会删除
主动删除
定时删除一批过期的数据
可以设置maxmemory,超过时触发主动删除
LRU : 淘汰最近没有被访问的数据
LFU : 淘汰访问次数少的数据
数据一致性方案
基于binlog
通过canal监听binlog,同时将数据同步到mq中,再起一个数据应用去消费mq数据,将数据写入到redis
延时双删
先删除redis中数据,然后更新db,更新成功后通过mq延时队列再删除一次redis数据(非高并发业务使用)
定时任务
通过一个定时任务定义将db中的数据同步至redis
Zookeeper
文件系统结构
持久化节点(默认)
临时节点(sessionh过期时删除)
持久化顺序节点
临时顺序节点
容器节点(没有子目录时会被删除)
ttl节点(需要手动开启,过了指定时间就会被删除)
监听通知机制
客户端注册监听它关心的任意节点,节点被修改或删除时客户端将收到通知
所有监听都是一次性的,一旦触发该节点上的监听就会被移除
事务日志
数据快照
集群架构
Leader:处理读请求和写请求,一个集群只能存在一个
Follower:与Leader保持心跳连接,Leader宕机后,参与选举成为新的Leader
Observer:只能处理读请求,不参与选举
使用场景
分布式锁
集群选举 : 半数以上机器存活,才能对外提供服务
注册中心
MQ
优势
应用解耦 : 提高系统的容错性和可维护性
异步提速 : 提升系统的吞吐量和用户体验
流量削峰 : 提高系统的稳定性
劣势
复杂度提高
可用性降低
主流消息中间件
RabbitMQ
消息模型
队列模型
helloworld模型 : 生产者生产消息发送到队列,消费者从队列中取消消息进行消费
work模型 : 避免消息积压,引入多个消费者来消费消息
发布/订阅模型
广播模型 : 发消息到交换机,交换机将消息发给所有与交换机绑定的队列中
路由模型
direct模型 :交换机根据不同的key将消息发送到不同的队列中
topic模型 : key支持通配符
消息可靠性
生产者 :保证消息可靠投递,confirm机制
消息发送到交换机的回调
消息发送到队列的回调
RabbitMQ集群 : 高可用、持久化
消费者 : 消息的ack机制
手动签收消息
延时消息
ttl
死信队列
消息积压
增加消费者
将积压的消息存库,分批处理
消息幂等性
乐观锁机制
数据库添加版本号,通过版本号比对,在进行写库操作
mysql/redis中存入唯一业务标识,判断是否存在再执行写库操作
集群架构
HAProxy : 实现负载均衡
Rabbitmq节点通过同步cookie实现数据同步
RocketMQ
消息模型
队列模型
发布/订阅模型
topic : 区分一类消息。例如交易消息,物流消息等
tag : 可对一类消息进行再次分类。例如交易消息中的交易创建消息,交易完成消息等
offset : message queue中的偏移量,记录了消息的消费进度
消费方式
集群消费
一个topic下面的所有队列会被一个消费组共同消费,一个队列只会被一个消费者消费
广播消费
消息会发送给消费组中的每一个消费者进行消费
集群架构
producer 集群 : 生产消息
同步发送 : 一般用于发送重要消息,例如通知邮件、营销短信
异步发送 : 一般用于可能链路耗时较长而对响应时间敏感的业务场景,例如文件上传后通知解码服务
单向发送 : 一般用于耗时短且对可靠性要求不高的场景,例如日志收集
name server 集群 :broker的发现与注册
与broker保持长连接
维护topic的路由信息
broker server 集群 : 实现消息的转发和存储
消息的转发和存储
consumer queue存储数据索引,实际的数据存储在CommitLog
与每一个name server保持长连接和心跳,定时同步topic信息
cusumer 集群 : 消费消息
主动拉取消息
被动推送消息
消息可靠性
生产阶段
同步消息通过响应结果,进行失败重试
异步消息的回调机制,进行失败重试
存储阶段
消息持久化到CommitLog
同步刷盘
异步刷盘 : 将消息存入pageCache中后立即返回,通过后台线程异步刷盘
broker server采用主从集群,实现多副本和高可用
消费阶段
ack确认机制,执行完业务逻辑后再进行确认
消息幂等性
业务幂等:状态判断;分布式锁
消息去重
乐观锁机制,在redis/mysql中存入业务唯一标识,判断是否存在再进行消费
消息积压
消费者宕机重启?
增加消费者
上线专门的处理消息的服务,将消息直接存库,然后离线分批处理
消息有序性
部分有序
生产者 : 通过MessageQueueSelector将有序的消息放到一个message queue中
消费者 : 通过MessageListenerOrderly 从队列中一个一个取消息,一个队列取完后再去其他队列中取
全局有序
只能消除并发
延时消息
RocketMQ是支持延时消息的,只需要在生产消息的时候设置消息的延时级别
临时存储+定时任务
事务消息
事务消息只保证了发送者本地事务和发送消息这两个操作的原子性,但是并不保证消费者本地事务
的原子性,所以,事务消息只保证了分布式事务的一半。
的原子性,所以,事务消息只保证了分布式事务的一半。
死信队列
消息消费失败后,rocketmq会进行重试,当重试次数达到最大重试次数后,还没有消费成功,则会进入死信队列。不会被消费者正常消费,三天后自动删除
ElasticSearch
功能:数据的存储、检索、分析
全文数据 : 非结构化数据,不定长没有固定格式的数据,例如邮件,word文档等等
全文检索 : 通过索引程序(分词器)对文章中的所有词建立索引,记录出现次数和文字。查询时根据索引进行查找
正排索引 : 通过 key为文档id ,value为 List<关键词,关键词出现的位置列表,出现次数> 建立索引。检索时要遍历所有文档的全部内容
倒排索引 : 通过 key为关键词,value为 List<文档id,关键词出现次数,关键词位置> 建立索引。通过分词器建立索引后,可根据关键词快速定位文档列表
集群
主节点
数据节点 : 只存储数据
协调节点 : 负载均衡
分片机制:将文本数据切割成n个小份存储在不同的节点上,减少大文件存储在单个节点上对设备带来的压力。
分片副本:在集群中某个节点宕掉后,通过副本可以快速对缺失数据进行复盘
垂直扩容和水平扩容
负载均衡
实时搜索
发起写请求时暂时存入memery cache中,此时数据还不能被查询;每一秒会将memery cache中的数据写入(refresh)到file system cache中并清空memery cache,此时数据能被查询到;最后过30s才会将system cache中的数据写盘(reflush)
Translog
记录所有操作,同步磁盘时才会删除
translog 也被用来提供实时 CRUD
XXL-Job
调度中心 + 任务执行器
调度中心:管理调度信息,按照配置发起调度任务,本身不承担业务代码
任务执行器:接受调度请求并执行任务逻辑 @XxlJob
执行流程
基于竞争数据库锁保证只有一个节点执行任务,支持水平扩容。可以手动增加定时任务,启动和暂停任务,有监控
Seata
CAP定理
C Consistency 一致性
A Availablity 可用性
P partition tolerance 分区容错性
BASE理论
在保证不了CAP中的强一致性时,采用适当的弱一致性,保证最终一致性
分布式事务解决方案
2PC 两阶段提交(seata)
第一阶段:RM端(资源管理器)提交本地事务,生成undo日志表,释放本地锁和连接资源。并向TC(事务协调者)注册分支事务,通过全局事务的 XID 进行关联。
第二阶段:完全异步
分布式事务操作成功,则TC通知RM异步删除undo日志表
分布式事务操作失败,则TM(事务管理器)向TC发送回滚请求,RM 收到协调器TC发来的回滚请求,通过 XID 和 Branch ID 找到相应的回滚日志记录,通过回滚记录生成反向的更新 SQL 并执行,以完成分支的回滚。
第二阶段:完全异步
分布式事务操作成功,则TC通知RM异步删除undo日志表
分布式事务操作失败,则TM(事务管理器)向TC发送回滚请求,RM 收到协调器TC发来的回滚请求,通过 XID 和 Branch ID 找到相应的回滚日志记录,通过回滚记录生成反向的更新 SQL 并执行,以完成分支的回滚。
柔性事务
TCC事务补偿
try
confirm
cancel
最大努力通知
可靠消息-最终一致性
开源的分布式事务框架
@GlobalTransactional,将事务交给seata server来管理
(默认)AT模式,加锁解锁影响了性能,不适合高并发场景,高并发场景采用消息队列进行错误补偿
TCC模式
Saga模式 长事务解决方案,在 Saga 模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者。
XA 模式,XA 可以认为是一种强一致性的事务解决方法
事务:操作本地数据库本地数据库的一组操作要么都成功,要么都失败。分布式事务:在分布式环境下操作不同数据库的一组操作要么都成功,要么都失败
ShardingSphere
一个表中超过500万数据就需要分片了
垂直分片:根据业务进行拆分成不同的表
水平分片:将一个表的数据拆分成多个表
分片策略
取余、取模:数据均匀,不方便扩容,适合数据量比较稳定的
时间拆分:数据不均匀
Linux常用命令
ps -ef | grep
df -h
top
nohup
tail -f
docker
docker ps
docker -build
docker run
docker log -f
docker exec -it 容器名称 bash
网络相关
网络通信
Socket
Socket通讯流程(阻塞式)
IO模型
BIO同步阻塞:流传输。一个请求一个线程,客户端请求服务端时就会启动一个线程去处理,如果这个线程不做任何事情就会造成资源的浪费
NIO同步非阻塞:块传输,基于通道(Channel)和缓存区(Buffer)。一个线程处理多个请求,每个客户端请求会注册到选择器(Selector)上,选择器会进行事件监听(轮询),对事件做出相应处理
SelectionKey 标记事件
建立新连接事件
已连接事件
读事件
写事件
ServerSocketChannel 监听连接
SocketChannel 读写数据
AIO异步非阻塞:操作系统处理完成后才会通知服务端线程处理
RPC架构(RMI)
客户端 :服务调用方
客户端存根:存放服务端地址信息,将客户端发送的请求参数打包(序列化)成网络消息,通过网络远程发送给服务提供方
服务端:服务提供方
服务端存根:接收客户端发送过来的消息,将消息解包(反序列化),调用本地服务
http状态码
200 成功
301 重定向
400 请求错误
401 未授权
403 权限拒绝
404 资源不存在
405 请求方法不支持
500 服务器内部异常
502 网关错误
TCP/IP模型
应用层 HTTP/HTTPS
传输层 TCP/UDP
网络层 IP
数据链路层
物理层
TCP连接
三次握手
四次挥手
加密算法
对称加密
AES
DES
非对称加密,公钥加密私钥解密,私钥加密公钥解密
RSA
DSA
0 条评论
下一页