Java基本语法回顾总结
(1)hashcode()、equals()[相关:4]
1.如果hashcode()不相同,两个对象一定不是同一个对象
2.如果hashcode()相同,两个对象不一定上同一个对象,需要进一步判别equals
3.如果equals相同,应当认为两个对象就是相同对象
由于hashcode方法仅返回一个值,equals里面有若干逻辑,因此,部分集合类的判断中,会优先判断hashcode,如果相同再继续equals判断
结论:如果改写了equals方法,则必须改写hashcode方法,以便于逻辑一致
(2)string、stringbuffer、stringbuilder
string:常量,每次修改都会创建新的字符串常量
stringbuffer:线程安全的字符串变量
stringbuilder:不安全的字符串变量
(3)extends、super
<? extends T>:?是T的子类
<? super T>:?是T的基类
(4)== 、 equals
==:基本类型-看值;引用类型-看引用地址
euqals:看各种类的重写逻辑[逻辑里面也有可能复用==]
(5)重载、重写
重载:针对单一类中,相同方法名,不同方法签名
重写:针对父子类,相同方法名,相同方法签名
重写:返回值类型、抛出异常都必须小于等于父类;访问修饰符大于等于父类
(6)list、set
list:有序,可重复,允许多个null,支持下标随机访问
set:无序,不可重复,单一null,必须遍历访问
(7)arraylist、linkedlist
arraylist:基于数组实现,有利于查找、修改,不利于插入、删除[适用场景不同]
linkedlist:基于链表实现,有利于插入、删除,不利于查找、修改[适用场景不同]
(8)concurrenthashmap扩容机理
1.7之前:基于segment分段hashmap存储实现的,segment分段存储部分不扩容,仅内部的hashmap进行扩容[哪个线程对应的内部hashmap需要扩容,哪个线程就负责做这个事情]
1.8之后:不是基于segment分段存储实现的,所有的K-V对都包含在一个map中,只是扩容的时候,那就每个子线程都参与扩容
(9)JDK版本变迁,hashmap的主要变更
1.7:底层是数组+链表,哈希算法复杂
1.8:底层是数组+链表+红黑树,由于引入红黑树,哈希算法得到简化,从而优化hashmap的插入、查询效率
(10)hashmap的put方法
大体流程:
1.依据key和哈希算法计算下标
2.如果下标位置为空,则封装成对象(1.7:entry对象,1.8:node对象),放置该位置
3.如果下标位置不空,
3.1 JDK1.7:判断是否需要扩容,然后,头插法插入到对应位置的链表中
3.2 JDK.8:判断是红黑树节点还是链表节点,然后插入到hashmap里面,最后判断扩容
3.2.1 红黑树节点:将KV对封装成红黑树节点,加入到红黑树中
3.2.2 链表节点:尾插法插入到对应位置的链表中,如果多于等于8个节点,将链表转为红黑树
(11)深拷贝与浅拷贝
浅拷贝:基本数据类型拷贝了第二份,引用类型的变量全部都是拷贝了一份引用地址,指向了原来的地方。
深拷贝:所有数据类型都拷贝了第二份,不仅仅拷贝引用地址。
(12)hashmap扩容机理
1.7:生成新数组,遍历原数组上的每个链表,将内部数据逐个转移至新数组
1.8:生成新数组,遍历原数组上的每个链表与红黑树
①如果原数组上是链表,遍历每个元素,重新计算下标并转移
②如果原数组上是红黑树,遍历每个元素,重新计算下标并转移,有冲突再购建链表与红黑树
(13)copyonwriteArrayList
线程安全的arrayList,底层也是用数组实现的,主要集中在读与写操作上
读:由于读写分别在老新数组上,因此,互相不干扰,也因此,读的性能不会受写的性能影响[适用于读多写少]
写:写操作会生成新数组,在完成之前,其他线程无法进行写操作[上了锁,线程安全];在完成之前,读的是原数组,写的是新数组,两者是不会互相干扰的。
(14)字节码
定义:字节码文件是Java源码编译过后的一种格式,各个平台上相同的源码编译出的字节码是相同的,但是,字节码转化的机器码不相同,这个转化上JDK(JRE)做的,因此,Java上跨平台语言
相当于编译过程所指的,中间代码[前有词法分析、语法分析、语义分析][后有代码优化、目标代码生成]
优点:实现了语言的跨平台;编译过程可以做代码优化,提高执行效率
(15)Java异常体系
error是非常严重的错误,程序已无法正常运行,没必要捕获
运行时异常,一般是逻辑问题,应尽量避免
非运行时异常,不处理无法正常运行,必须处理
(16)什么时候抛异常?什么时候捕获异常?
抛:自身无法处理就往上抛,由上级处理
捕获:自身能处理就捕获,捕获走既定处理流程
(17)类加载器
(18)类加载器的双亲委派模型
由16可知,类加载器共三个,AppClassLoader有两个父类
当AppClassLoader加载时,会优先调用父类的加载器,这种JVM机制叫做双亲委派模型
AppClassLoader.loadClass(){
ExtClassLoader.loadClass(){
bootstrapClassLoader.loadClass();
}
}
(19)线程共享区

堆区与方法区是所有线程共享的
栈区:主要存放基本数据类型与对象引用(仅限局部变量,成员变量不管是不是基本数据类型都存放于堆)
堆区:主要存放对象实例与数组[我的理解就是,由代码操作生成的对象或者说引用类型存放于堆]
方法区:类信息(class文件)、静态变量与静态方法
本地方法栈:非静态方法
(20)如何排查JVM问题-※
空间:jmap 查看 JVM各个区域的占用情况,查看是否栈区过大?是否堆区过大?
时间:jstack 查看线程的运行情况,是否存在死锁或者阻塞?
OOM:
查看: jstat 查看垃圾回收情况,fullgc 与 younggc 的运行情况
记录: JVM启动参数 --> -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/base,发生OOM的时候记录下当时的情况-dump文件,便于回溯定位问题
分析:jvisualvm 可以针对dump文件进行分析异常对象,异常线程
(21)从加载JVM到GC清理,一共经历什么过程-√
1.方法区加载字节码,加载静态方法、静态成员变量
2.堆区创建实例化对象,保存至 年轻代 Eden 区
3.将引用传回栈区
4.经过minor GC,对象每次存活,年龄加一,并迁移至survival区-存活区,年龄大于等于15的时候,对象进入老年代
5.栈区的引用被移除以后,对象最终被GC回收
(22)如何确定对象是否需要回收
根可达算法:所有对象都是基于一个根对象上的,从根对象开始遍历,能找到引用的就是还需要的对象,否则就是不需要的对象。
(23)JVM垃圾回收机制
总共三种算法:
标记:将所有对象判断是否根可达,将不可达对象标记并进行删除
缺点:会有碎片空间出现
复制法:申请一块与当前存储空间相同的空间,根可达算法标记所有存活对象,将存活对象迁移至新申请空间,迁移完毕以后,原空间统一清空
缺点:需要的空间较大
标记优化法:根可达算法标记所有存活对象,并将存活对象重新排列至连续空间,标记以及迁移排列完毕以后,将存活对象连续空间以外的空间统一清除
(24)什么是STW
STW,stop the world,即,在GC运行过程中,要将所有Java进程冻结,以便于GC进程进行垃圾回收。
参考:
- https://blog.csdn.net/liwenxiang629/article/details/109508530
- https://blog.csdn.net/weixin_43235210/article/details/90444710?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-90444710-blog-109508530.235%5Ev38%5Epc_relevant_sort&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-90444710-blog-109508530.235%5Ev38%5Epc_relevant_sort&utm_relevant_index=2
- 常用的JVM启动参数有哪些?-※
- 线程安全的理解? 线程安全说的是,当多个线程并发访问互斥资源时,读写互斥资源的代码逻辑能正常处理,获得正确结果,不会互相干扰的情况。
- 守护线程的理解? 守护线程是与普通线程相区分的概念,用户一般使用的就是普通线程,普通线程有自身独立的生命周期,而守护线程的生命周期取决于普通线程,只有当所有相关的普通线程都结束时,守护线程才会结束。 守护线程是为所有普通线程提供服务的线程
- threadlocal的底层原理 threadlocal:在线程里面多保存了一份map结构,由于变量存在线程内部,从而保证不同线程互相不干扰。 现在大多数用的都是线程池,由于线程都是复用的,而threadlocal往线程里面添加了一个map,因此,需要代码里面手动回收移除threadlocal保存的数据,否则,线程一直复用,数据量会一直累加至OOM。
- 并发、并行与串行之间的区别? 并行:多条处理流同时执行,例如多端口同时工作,多条线路同时传送数据 串行:多条处理流排队执行。 并发:例如微机系统上有多个进程存活,从宏观上,多个进程上同时工作,是个并行的流程,但是,从微观底层上来看,多个进程是由CPU通过时间片轮转,逐个调度执行的,是个串行的流程,这个就是并发。
- Java死锁应如何避免?-√ Java死锁由四个因素导致:
- 资源是互斥资源 每次仅可由单个线程持有
- 在获得所有资源之前,线程自身不释放已持有资源
- 所需资源在其他线程上,且不可被剥夺
- 循环依赖,A持有B所需资源,B持有A所需资源
- 线程池底层工作原理-√ num:线程数量 当有任务需求时, 当num
- 线程池中阻塞队列的作用?线程池为何先添加队列而不是先创建线程-√ 阻塞队列可以通过阻塞保留那些想要入队的任务;阻塞队列自带阻塞和唤醒功能,不需要一直占有CPU资源。 因为创建新线程需要获取全局锁,影响其他线程的效率,且线程数量不足有可能是临时性不足,因此,优先排队等待。
- ReentrantLock中的公平锁与非公平锁的底层实现-※ 加锁[竞争锁]: 公平锁:新线程直接进入到队列 非公平锁:先直接竞争锁,竞争不到再进入队列
- ReentrantLock的tryLock()与lock()区别 tryLock()可能加锁成功[返回真],也可能加锁不成功[返回假],非阻塞加锁 lock()阻塞性加锁,一直执行,直到加锁成功
- CountDownLatch与Semphore的区别和底层原理-※
- sychronized的偏向锁、轻量锁以及重量锁 sychronized语法,某个线程持有锁的时候,锁就已经是偏向锁 当某个线程持有锁,且有其他线程也在等待锁释放,锁就升级为轻量锁 某个线程持有锁,其他线程会自旋尝试获取锁,自旋次数达到一定程度,线程仍然没有获取到锁时,轻量锁就升级为重量锁
唤醒:无论公平锁还是非公平锁都会直接唤醒队列第一个获取锁
- ReentrantLock与synchronized的区别?-√
- 对AQS的理解,AQS如何实现可重入锁-※
- 对IOC的理解
- 单例bean与单例模式
- spring事务传播机制-※
- spring事务什么时候会失效-√
- spring中创建的bean,生命周期有哪些-※
- spring中的bean是线程安全的吗? 如果bean上有状态,就不是线程安全的
- applicationContext和beanFactory有什么区别? beanFatory的功能,applicationContext都有,但applicationContext还有其他功能[获取系统环境变量、事件发布]
- spring中的事务是如何实现的?-※
- @Transactional什么时候会失效?
- spring容器的启动流程是怎样的?-※
含义:ReentrantLock是一个类;synchronized是一个关键字
是否公平:ReentrantLock可以加公平锁,也可以加非公平锁;synchronized只能是非公平锁。
synchronized自动加锁释放锁,ReentrantLock需要手动加锁释放锁
synchronized是JVM层面的锁,ReentrantLock是API层面的锁
AQS是一个用于JAVA线程同步的框架。
AQS内部维护了一个状态变量state和一个线程双向队列。
在可重入锁的场景下,state表示加锁次数,当state减少至0时,代表持有锁的线程释放锁了,就唤醒下一个线程竞争锁。
inverse of control,控制翻转,将bean的生成交给spring管理,例如,@autowired自动装配的mapper层对象。
好处:单例bean可以有效复用,减少对象生成的时间成本。各种bean还有自身的应用场景。
spring自动装配出来的对象全都会指向同一个单例bean,不强制约束用户不可创建第二个对象。
单例模式针对的是类而言的,如果某个类应用了单例模式,那么这个类就不可能创建出第二个对象,所有该类的引用都会指向同一个对象,不管是spring自动装配还是用户自己new。
数据库:数据库如果不支持事务,那么spring事务就会失效
Java:
类:事务所在类没有对应的注解,spring没有在容器里面生成该类的bean[类上面没有component之类的注解]
方法:方法上没有加上事务注解或者方法的权限修饰符不是public
调用:调用方法没有使用代理对象,而是通过原对象调用的方法。[自己new]
1.推断构造方法
2.实例化
3.依赖注入[构造方法可能需要部分外部注入属性;成员变量中依赖于其他类,等等多种需要将其他实例注入的情况]
4.处理@postConstruct注解,构造之后还可以执行的自定义处理
5.处理initializingBean接口复写内容,如果实现了该接口的话[前面就是bean的实例化,这一步就是bean的初始化,这就是两者的区别]
6.生成代理对象,最后生成bean
如果bean上没有状态,就是线程安全的
类:事务所在类没有对应的注解,spring没有在容器里面生成该类的bean
方法:方法上没有加上事务注解或者方法的权限修饰符不是公共的
调用:调用方法没有使用代理对象,而是通过原对象调用的方法。
1.扫描所有beanDefinition对象,并保存至map
2.筛选出单例bean进行bean创建,推断构造方法-实例化-依赖注入-postConstruct注解-beanInition接口复写方法-生成代理对象-生成bean
3.applicationContext发布spring容器启动事件
- spring用到了哪些设计模式?
- spring boot常用注解及其底层实现-※
- springboot是如何启动Tomcat?
- mybatis的优缺点-※
- mybatis中#{}和${}的区别?
- 索引的基本原理-√
- 索引的设计原则
- 事务的基本特性和隔离级别-※
- 什么是MVCC
- 简述MyISAM和InnoDB的区别-※
- Explain语句中各个字段分别表示什么?-※
- 索引覆盖是什么?
1.beanFactory-工厂模式
2.单例bean-单例模式
3.代理对象-代理模式
常用注解有
1.@bean 方法级注解,可用于对应的单例bean对象
2.@autowired 用于生成单例bean的引用;要求原类增加类注解例如component\service\controller\mapper等来交给spring托管
3.@controller\@service\@mapper,这三都是类注解,用于指定controller、service、mapper类,从而可以自动将请求匹配到controller对应方法上,以及自动调用对应的SQL
4.@GetMapping,@PostMapping,用于指定请求的匹配类型,方法接收get请求、post请求等等
具体实现 不太清楚
1.检索是否有tomcat依赖
2.将tomcat实例化,并加入到spring容器中
3.启动tomcat实例
优点:
1.SQL写在XML里面,与业务代码分离,因此相对比较灵活,便于统一管理
2.spring集成很方便,由于使用的JDBC连接数据库,因此,不需要针对不同数据专门做兼容
缺点:
SQL的编写,SQL的语法都强关联数据库的类型,因此,无法随意切换数据库
# 的作用是占位符,将Java里面的变量按类型注入进来,可以有效防止SQL注入
$ 的作用是替换,不能防SQL注入,会在传递到数据库之前,就将内容直接文本替换到对应的位置,可以用于SQL语句不确定的场景[或者说可变]
1.索引对应列进行排序
2.将排序结果建立倒排表
3.将数据地址链拼接到倒排表上
4.查询的时候,拿到倒排表,查询到数据地址链,最后取到需要的数据
1.类型不多的列没必要建索引,占空间,效果还不好
2.由于索引也是要占空间的,而且索引建立完毕以后,之后的写入操作都需要更新索引,因此,索引越多对写入操作越不友好,索引的数量要控制
3.索引建立的列,不能是全模糊和前模糊查询的那种情况[全模糊与前模糊都不走索引],尽量建立在精确匹配的场景
4.索引建立的列,尽量不要建立在text类型上
基本特性
ACID: atom consistent isolation duration
原子性:每个事务的执行应当是原子性的,要么都执行,要么都不执行
一致性:数据库总是从一致性的状态变化到另外一种一致性的状态,所有数据应当具有一致的状态,不存在矛盾;即无论何时,数据都应当满足本身的约束条件
隔离性:事务之间应当是隔离的,不会互相影响
持久性:事务一旦提交,就持久化到数据库里面了
隔离级别
1.读未提交 脏读,一个事务A没有执行完,另一个事务B读到了A的中间处理结果
2.读已提交 不可重复读,一个事务A没有执行完,事务B读到的数据为C,事务A执行完毕,事务B读到的数据为D
3.可重复读 ,利用多版本并发控制,事务A写操作的数据版本与事务B读操作的数据版本不一致,从而保证两个事务读写操作是独立的
4.串行,每个操作都加锁,保证串行执行,但效率很低,一般不用
MVCC multi version concurrent control 多版本并发控制
简而言之就是 每个数据都有若干版本,读写分离,操作的是不同版本的数据
读已提交:每次读 都生成一个readView
可重复读:每次读都复用同一个readView
查询索引的时候,所有目标结果列就已经包含在索引里面了,因此,就没有必要再去查找原表了,从而提高了查询效率
- 最左前缀原则是什么?-※
- InnoDB是如何实现事务的?-※
- B树和B+树的区别?为什么Mysql使用B+树?-※
- Mysql的锁有哪些?如何理解?-√
- 什么是RDB和AOF?-√
- Redis的过期键删除策略?
- 简述redis的事务实现?
- redis主从复制的核心原理?-√
- Redis有哪些数据结构?分别有哪些经典应用场景?-※
- Redis分布式锁底层是如何实现的?-√
- redis集群策略?
- 缓存穿透、缓存刺穿、缓存雪崩
查询SQL的时候,如果利用到了联合索引,那么使用的顺序与建立索引的顺序必须保持一致
1.共享锁 排他锁
共享锁:其他线程可以读 但是不能写,也不能加排他锁,可以加共享锁
排他锁:其他线程不可读不可写,也不能继续加锁
2.行锁 表锁
行锁:锁定一行
表锁:锁定一张表
3.乐观锁、悲观锁
乐观锁:不是真正上锁 而是通过数据多版本控制的方式实现上锁,乐观认为 自己上锁以后 不会有其他线程同时访问修改该数据,最后判断一下上锁的版本号是不是最新的版本,如果不是最新版本就放弃操作
悲观锁:例如行锁、表锁
RDB:保存redis某个时刻快照的文件,可以用于redis全量同步,或者持久化
优点:
1.仅一个dump.rdb,方便持久化
2.方便容灾备份
3.恢复性能比AOF好
缺点:
1.由于是全量快照,因此,只能一段时间持久化一次。那就有可能存在,这段时间数据丢失的风险
2.由于是全量快照,因此,如果数据量较大的时候,可能会比较耗时。
AOF:保存redis一段时间内的操作日志,可以用于redis增量同步,或者持久化
优点:
1.AOF保存比RDB保存会更频繁,因此,就算丢失,丢失的改动也会少很多,甚至如果选择每修改同步的策略的话,那么最多就丢失一条操作指令
缺点:
1.恢复起来比RDB方式要慢,即性能是比不上RDB的
2.AOF文件比RDB文件要大
定时过期:每隔一段时间,将redis所有过期键删除[CPU-难过 内存-友好]
惰性过期:当且仅当用到的时候判断是否过期,过期就删除[CPU-友好 内存-难过]
定期过期:另外两种策略的折中,每隔一段时间就触发一次,每次都去遍历过期键删除,但是,这个过程有限时,超过一定时间就恢复redis正常使用
首先得从下面几个关键字开始说,
multi、exec、discard、watch
multi:开始一个事务
命令入队列:这个阶段输入要执行的命令
exec与discard命令输入,代表事务的结束,redis就开始执行整个事务或者抛弃整个事务
watch:给整个事务加乐观锁 如果监听的数据被别人改动了,那么整体事务回滚
核心就是 主节点成功以后,就直接返回,然后才同步到从节点,因此,存在不一致的风险[ZK则是先保证一致性 再返回给调用方]
两种复制同步内容:
全量同步:
1.将主节点上的所有数据保存一份快照,bgsave持久化RDB文件
2.将文件传递给从节点,全量同步到从节点[阻塞]
增量同步:
1.增量同步的前提,需要维护一份偏移量和一个缓冲队列
2.在缓冲队列没有满到溢出的情况下,从节点将同步请求发给主节点[里面带上运行ID]
2.1 第一次连接[运行ID不相同],主节点将运行ID传回给从节点,并进行全量RDB同步,从节点保存当前主节点的运行ID
2.2 非初次连接[运行ID相同],那么就只需要将缓冲队列中改动同步到从节点即可


核心 就是利用lua脚本的原子性
1.setnx 保证当且仅当 没有其他线程设置这个key的时候 才往里写数据上锁
2.最后一定要将自己上的锁给释放掉
3.用try保证出异常的时候,最后一定会释放锁
4.确保释放的锁是自己加的
5.每把锁都必须设置过期时间,以确保不会出现某个线程一直持有锁
6.要额外一个线程进行锁续命
服务端 向redis主节点请求锁
主节点锁请求成功,返回给服务端
然后将状态同步到从节点
redis共三种模式
主从模式:主节点挂了可以访问从节点
哨兵模式:增加部分节点,设为哨兵,监视主节点状态,主节点挂了以后,从节点重新选举出主节点[单主多从]
集群模式:多主多从,同样会自动选举出新-主节点
缓存雪崩: 多个key在redis中不存在,且有很多请求同时访问这些数据,导致数据库压力较大,常见于服务端刚起,缓存内无数据
处理:
1.缓存预热,请求访问之前,预先查询一遍,将部分key的内容保存至缓存
2.互斥锁,执行SQL的部分上锁,同样的请求,仅放第一个执行SQL,后续全部走redis
3.热点数据,缓存过期时间稍微设置偏差一点,防止一批数据同时过期失效
缓存穿透:请求访问的是,缓存和数据库都没有的数据[有可能不是合法数据],当key没有的时候,就会去查询数据库,但同时,又没有合法数据会存入redis,导致如果利用这种请求,做重放攻击的话,就会导致数据库浪费很多性能
处理:
1.可以枚举所有情况的场景下,利用布隆过滤器可以筛选掉部分请求,就不让请求走到业务逻辑
2.无法枚举的情况下,请求还是正常走业务逻辑,只是,查询数据库没有数据的情况下,直接往redis里面存放null等提前预设的空值,等下一次请求判断redis的时候,获取到该值即可直接判断,无需再查数据库。
缓存刺穿:某个key过期了,导致请求一直走到数据库,且有很多请求同时访问这个数据[热点数据]
处理:
1.互斥锁,key过期以后,同一个key的查询请求,有且仅有一个请求能进入查询SQL的流程,SQL查询完毕就保存redis,其余都直接查询redis
参考:
1.https://www.jb51.net/article/280467.htm
2.https://baijiahao.baidu.com/s?id=1767052815720228275&wfr=spider&for=pc
- redis与MySQL如何保持数据一致?
- redis的持久化机制
- redis单线程为什么这么快-※
- 简述redis事务实现
- CAP理论与BASE理论-√
- 什么是RPC
- 数据一致性模型有哪些-√
- 分布式ID是什么?有哪些解决方案
- 分布式锁的应用场景是什么?有哪些实现方案?
- 什么是ZAB协议-※
- 为什么ZK可以用来做注册中心-√
- ZK的leader领导选举流程是怎样的
1.删除redis缓存
2.更新MySQL
3.删除redis缓存
两种持久化命令:
save:阻塞性持久化,会阻塞redis主进程,直到持久化完成
bgsave:非阻塞性持久化,通过新建子线程专门持久化,从而不影响redis主进程
手动就是上面两种命令
自动就是save M N ,相当于设置一个定时器,定时备份持久化的感觉[非阻塞持久化]
两种持久化格式
RDB:redis某个时刻的快照
优点:
1.仅一个RDB文件,方便持久化与备份
2.方便恢复,性能较好
缺点:
1.有可能丢数据,因为记录的是某个时刻的状态,因此,记录时刻与同步时刻之间的这段时间改动是有可能丢失的
2.数据多的时候,持久化的速度会比AOF慢
AOF:记录命令的操作日志
优点:
1.持久化可以设置每修改同步,即每次改动都记录操作日志,因此,最理想的情况,仅丢失一条改动
2.持久化的速度会更快
缺点:
1.恢复性能不如RDB,会更慢,启动速度也会更慢
2.文件整体会比RDB更大
1.multi开始事务
2.命令入队
3.exec 或 discard ,执行事务 或者 放弃事务
4.watch在multi之前输入,用于给事务加上乐观锁
CAP: consistent && avaliable && partition tolerance
一致性:整个系统的数据状态应该是一致的,不存在逻辑矛盾的
可用性:整个系统应该保证是可用的
分区容错性:整个系统应当有容灾性能,有哪个节点失效或者哪个服务挂掉了,也要保证系统的基本可用
一般是保证AP 或者 CP
BASE:basic avaliable && soft state && eventually consistent
基本可用:即,允许系统有一定程度的不可用,例如,请求时间变长 或者 非核心功能 暂时不可用
软状态:允许系统短期不一致,即系统处于某种中间状态
最终一致性:经过一段时间,整个系统最终能达到数据状态一致的情况
RPC remote process call 远程过程调用
与编程语言、运行环境等其他东西都无关
它可以利用http,也可以利用tcp
强一致:每次修改过后,系统就已经做到数据一致,没有逻辑冲突矛盾
弱一致:每次修改以后,不承诺可以读到修改后的数据,也不承诺多久之后可以达到数据一致的状态
最终一致:过一段时间以后,系统最终能达到数据一致的状态
在分布式部署的情况下,全局唯一ID,称之为分布式ID
0.UUID生成,能搞定,但是需要额外的空间与性能,且不直观
1.数据库统一维护,每次请求ID都向同一个数据库请求,数据库负责自增
2.redis等存储中间件,每次请求ID都向redis请求,redis负责自增
3.雪花算法[1+41+10+12][不用+时间+主机号+进程号]
多个分布式部署的后台进程同时操作互斥资源,需要分布式锁,才能保证数据一致,处理逻辑正常
1.ZK实现 侧重CP
2.redis实现 侧重AP
redis的分布式锁,有可能出现主从不同步的风险,解决方案是红锁,但是官方都不推荐使用这个
成熟的用法:redisson
具体的注意事项:
1.setnx 当且仅当key不存在的时候,才上锁,否则就上锁失败
2.处理逻辑最后要移除锁
3.异常处理
4.判断锁归属
5.锁超时
6.锁续命
ZAB协议是保证ZK一致性的原子广播协议
ZK实现一致性共三个阶段:
1.选举leader:因为只有leader节点处理写操作
2.数据同步:所有follower要与leader保持数据一致性
3.请求广播:收到写请求的时候,会将写请求广播到所有follower节点,从而尽量使得所有节点的写操作是同时处理的
1. ZK可以保存对应的key-value
2. ZK的watch机制可以做服务发现
3. ZK底层用的多线程模型,性能也还行
ZK可以监控每个服务的连接情况,从而通报给监控的节点,从而更新对应的服务URL
核心是投票选举
所有follower
1.首先投票给自己
2.两两PK,赢家不做操作,输家将票改投赢家,或者赢家的赢家。
3.如果PK是平局,则票还是投给自己
4.PK结束,输家将票数情况传给下一个PK对象
5.如果有节点PK赢过一半以上的节点,则直接被选为leader
- ZK节点数据是如何同步的[或者说同步流程有哪些?]
- dubbo负载均衡策略?
- dubbo如何服务导出
- dubbo如何服务引入
- dubbo架构设计是怎样的?
- 负载均衡算法有哪些
- 分布式架构,session有哪些方案
- 如何实现接口幂等性
- 简述ZK的命名服务、配置管理与集群管理
- ZK的watch机制
- ZK和euraka的区别
- 存储拆分以后,如何保证唯一主键[解决方案应当与分布式ID的解决方案相同]
1.集群启动时,首先选举出leader节点
2.leader节点收到写请求
-------------日志持久化-----------------------
3.leader将日志发送给follower
4.follower日志持久化成功以后,返回ACK确认给leader
---------------内存更新-----------------------
5.leader收到半数以上的ACK以后,leader更新内存数据
6.将commit指令发送给从节点
---------------返回客户端------------------------------
7.leader节点返回响应成功
1.随机:随机分配
2.轮询:循环遍历
3.源地址哈希:每个请求都计算哈希值,相同的请求计算出相同的哈希值,就会分配到相同的服务
1.随机:随机分配
2.轮询:循环遍历
3.源地址哈希:每个请求都计算哈希值,相同的请求计算出相同的哈希值,就会分配到相同的服务
4.加权随机:部分服务的权重增加,随机到的次数就更多
5.加权轮询:部分服务的权重增加,遍历的时候,多访问几次
所谓的session共享,一般是在分布式多服务的场景下探讨的。由于后台服务较多,且因为负载均衡的原因,每次访问的后台可能不是同一个服务,因此,一些临时信息需要在不同服务之间共享[例如登录信息]
1.客户端保留,比如,存在cookies里面,由前端每次带入;存在浏览器localstorage;存在vuex
2.存入Redis等存储中间件
3.后台若干服务做同步[感觉要开发较长时间还不稳定]
所谓的幂等性,指的是,相同的请求,发一次和发多次,效果应当是稳定一致的。
就请求类型而言,
删除、查询
1.通过请求里面带的ID,或者请求参数拼接计算出一个ID,将ID存入Redis等存储中间件,用ID来标识请求是否处理过
修改、新增
2.除了阻断请求到数据库处理以外,还可以通过在数据库处理的时候,加上乐观锁,保证如果有其他请求修改的话,就放弃修改
命名服务:通过某个名字获得对应的URL等配置,由于ZK可以保存类似的KV对,将key设为名字即可获得对应的URL
配置管理:将服务地址等多个多个配置,保存到ZK的节点中。配置改动以后,ZK通过watch机制可以通知到其他服务
集群管理:减少服务或者服务不可用时,ZK连接断开以后,watch机制也能将服务改动通知到其他服务
实体:
客户端线程
客户端watchManage
ZK
消息:
消息次数:
每个watch监听仅生效一次,每次通知过后,监听器会被移除,需要重新注册
消息内容:
仅通知事件本身,不通知事件内容
流程:
1.客户端向ZK注册watch监听器
2.ZK返回监听器相关信息,并存入客户端watchManage
3.ZK监听到变化,客户端调用watcher对象的回调方法
0.UUID
1.单数据库自增ID
2.Redis存储中级件
3.雪花算法
- 雪花算法原理
- spring cloud 常用组件及其作用
- 如何避免缓存雪崩、缓存穿透、缓存刺穿
- 分布式系统中常用的缓存方案
- 缓存过期策略
- 常见的缓存淘汰算法
- 布隆过滤器原理及优缺点
- 分布式缓存寻址算法
- spring cloud 和 dubbo有哪些区别
- 什么是服务雪崩和服务限流
- 什么是服务熔断和服务降级
- SOA、分布式、微服务,什么关系与区别
1+41+10+12
不用+时间+机器ID+进程ID
缓存雪崩:
1.缓存预热
2.互斥锁
3.过期岔开
缓存刺穿:
1.缓存预热
2.互斥锁
缓存穿透:
1.能枚举:布隆过滤器,做请求拦截
2.不能枚举:互斥锁,设置Null,将后续请求拦截到数据库处理之外
1.客户端:localstorage、vuex
2.nginx:静态资源
3.Java:内存缓存
4.Redis:存储中间件
5.操作系统缓存
6.数据库缓存:mybatis分页组件利用了
定时过期
惰性过期
定期过期
FIFO:先进先出
LRU:最近最少使用
LFU:一段时间,最少使用
原理
存
1.每个key通过若干哈希算法,得到对应下标
2.对应下标的位置全部置为1
取
3.给定key计算对应下标,如果都为1,可能在里面,有0,肯定不在里面
优点:
1.占用空间小
2.速度快,耗时少,O(K),K为哈希算法数量
缺点:
1.仅能判断在或不在,拿不到原值
2.存在假阳性误判情况
哈希:点对点
一致性哈希:点对段
哈希slot:还是点对段,但是,由于增加了逻辑层,可以减小节点上下线的开销
服务雪崩:
服务之间存在调用关系,A-->B-->C,当C的并发量超出最大能力时,C阻塞 导致 B阻塞,最终导致 A阻塞,这个称之为服务雪崩
服务限流:
为了避免超出服务的最大承受能力,控制服务接收的请求数量,称之为服务限流
服务熔断
服务之间存在调用关系,A-->B-->C,当C服务不可用或者阻塞时,B服务不再去调用C,而是直接返回,这个称之为服务熔断
服务降级
由于异常情况,将部分服务停掉或者限流,这个称之为服务降级
分布式,指的是服务部署在多台服务器上,一般微服务都是分布式部署
SOA主要是针对服务而言的,主要是指所有服务都挂载到总线上的一种架构
微服务主要是针对服务而言的,将系统功能拆分为互相不重叠的微服务,通过各种微服务的互相调用来完成业务功能
- 怎么拆分微服务
- DDD领域驱动设计-※
- 什么是中台
- 项目怎么保证敏捷开发
- 消息队列选型-√
- RocketMQ事务消息实现-※
- ZK为什么能作为注册中心
- RocketMQ底层实现原理-※
- 消息队列如何保证可靠传输
- 消息队列的作用
- 死信队列和延时队列是什么
- 如何保证消息的高速读写
- epoll和poll的区别-※
- TCP三次握手和四次挥手
- 浏览器一个请求到一个响应,经历的步骤
- 跨域请求是什么,有什么问题,怎么解决
- 零拷贝是什么-※
1.按照功能划分,各个微服务的功能应当没有重叠
2.微服务不能绕过对方的接口去访问数据
所谓中台,就是将可复用功能,形成公共组件[CSP人像服务]
代码公共组件:业务中台
数据公共组件:数据中台
1.会议
2.文档
3.持续构建
4.持续集成
kafka:
优点:吞吐量高,性能高
缺点:功能单一,有丢失消息的风险
rocketMQ:
优点:功能完善,性能好
缺点:客户端仅支持Java
1. ZK可以保存对应的key-value
2. ZK的watch机制可以做服务发现
3. ZK底层用的多线程模型,性能也还行
ZK可以监控每个服务的连接情况,从而通报给监控的节点,从而更新对应的服务URL
可靠传输:不能多 不能少
1.消费者实现幂等性,哪怕多收消息,也能保证逻辑正常
2.关闭自动提交,收发消息都要收到ACK确认
本身独立: 各功能解耦
收发异步: 缓冲、削峰
下游能处理: 异步通信
下游不能处理:健壮性
死信队列:没有成功消费的消息,可以用于消息重试
延时队列:隔一段时间才发送消息,可以用于限时操作之类的判断
核心在于零拷贝
A请求连接B
B发送ACK给A
A发送ACK给B
A请求断开连接B
B发送ACK给A[已确认收到请求,但是还需要等待B空闲]
B发送断连请求给A[B空闲才发断连请求]
A发送ACK给B
1.请求URL,通过DNS解析找到对应IP以及端口
2.NGINX之类反向代理到对应的服务上
3.后台服务依据URL,将请求转发至对应的类,对应的接口上
4.接口走完所有逻辑以后,返回响应
5.客户端收到响应后渲染
客户端发起请求时,会检查请求的协议、域名、端口是否与当前一致,如果不一致就会出现跨域问题
要处理该问题:
1.请求:请求通过后台转发至真正的接口[夹一层转发层,利用后台转发,类似网关]
2.响应:响应上面加上“access-control-allow-origin”
零拷贝的定义在于 一块区域的数据需要迁移的时候,不再需要拷贝至用户空间,而是直接在内核空间完成操作
核心在于两种方式:mmap transfile[DMA方式]
mmap:原本文件要拷贝至应用层,mmap则取消这个步骤,仅将数据映射引入到用户空间,数据本身不拷贝至用户空间
transfile[sendfile]:数据映射都不引入用户空间,直接使用DMA,将数据在内核之中,直接写入到socket缓冲区,缓冲区直接迁移至目标区域
两者区别:sendfile方式比mmap少了两次上下文切换