(精华)高级JAVA面试_流程图_架构图_系统图_数据库
2024-11-19 11:32:06 230 举报
AI智能生成
这份资源包含了高级JAVA面试中常见的问题,以及流程图、架构图、系统图和数据库的相关知识。它提供了对JAVA编程和系统设计的深入理解,为面试者提供了准备和复习的关键点。通过掌握这些概念和技术,面试者可以提高自己的技能水平,提高在就业市场中的竞争力。
作者其他创作
大纲/内容
jvm<br>
JVM构成图
<b><font color="#e0124f">类加载子系统<br></font></b>根据给定的全限定名类名(如:java.lang.Object)来装载class文件到运行时数据区中的方法区<br>
<b><font color="#e0124f">运行时数据区(JVM内存模型)</font></b>
<b><font color="#e0124f">执行引擎<br></font></b>执行classes中的指令<br>
<b><font color="#e0124f">本地库接口<br></font></b>与本地方法库(native libraries)交互,是其它编程语言交互的接口。<br>
类加载机制
<b><font color="#e0124f">类的生命周期?</font></b><br>加载->验证->准备->解析->初始化->使用->卸载<br>
<b><font color="#e0124f">类加载器有哪些?</font></b><br>
<b><font color="#e0124f">引导类加载器(BootstrapClassLoader)</font></b><br>负责加载支撑JVM运行的位于JRE的lib目录下的核心类库<br>
<b><font color="#e0124f">扩展类加载器(ExtensionClassLoader)</font></b><br>负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包<br>
<b><font color="#e0124f">应用加载器(AppClassLoader)</font></b><br>负责加载ClassPath路径下的类包,主要就是加载你自己写的那些类<br>
<b><font color="#e0124f">自定义加载器(继承ClassLoader)</font></b><br>负责加载用户自定义路径下的类包<br>
<b><font color="#e0124f">如何创建一个自定义加载器?<br></font></b>1、继承java.lang.ClassLoader<br>2、重写findClass方法(<font color="#0b38d9">想打破双亲委派机制就重写loadClass方法;不想打破双亲委派机制就重写findClass方法</font>)<br>
<b><font color="#e0124f">什么是双亲委派机制?</font></b><br>当收到了类加载请求时,不会自己先去加载这个类,而是将其委派给父加载器,由父加载器去加载,<br>如果此时父加载器不能加载,反馈给子加载器,由子加载器去完成类的加载。<br>
<b><font color="#e0124f">为什么要设计双亲委派机制?</font></b><br><ul><li>沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改</li><li>避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性</li></ul>
<b><font color="#e0124f">什么是全盘负责委托机制?</font></b><br>指当一个ClassLoder装载一个类时,该类所依赖及引用的类也由这个ClassLoder载入,除非显示的使用另外一个ClassLoder<br>
JVM内存模型
堆(共享)
年轻代(默认1/3)
Eden(默认8/10)
Survivor0(默认1/10)<br>
Survivor1(默认1/10)
老年代(默认2/3)
虚拟机栈(私有)
局部变量表
操作数栈
动态链接
方法出口
本地方法栈(私有)<br>
程序计数器(私有)
方法区(元空间)(共享)<br>
常量
类信息
<b><font color="#e0124f">Java 中堆和栈有什么区别?</font></b><br>1、内存区域不同<br>2、可见性不同(堆共享、栈私有)<br>3、存放数据不同(堆存放对象,静态变量,字符串常量池;栈存放常量、变量、类信息)<br>
JVM参数设置
<b><font color="#e0124f">-Xmx</font></b>3072m:设置JVM最大堆内存为3G<br>
<b><font color="#e0124f">-Xms</font></b>3072m:设置JVM初始堆内存为3G。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存<br>
<b><font color="#e0124f">-Xmn</font></b>2048:设置年轻代大小为2G
<b><font color="#e0124f">-Xss</font></b>256K:设置每个线程的栈大小,默认1m,值越小,JVM开启的线程越多<br>
<b><font color="#e0124f">-XX:SurvivorRatio</font></b>=4:设置年轻代中Eden区与Survivor区的比值,默认是8,相当于1个Survivor区占整个年轻代大小的1/10<br>
<b><font color="#e0124f">-XX:MaxMetaspaceSize</font></b>: 设置元空间最大值, 默认是-1, 即不限制, 或者说只受限于本地内存大小
<b><font color="#e0124f">-XX:MetaspaceSize</font></b>: 指定元空间触发Fullgc的初始阈值(元空间无固定初始大小), 以字节为单位,默认是21M
一般建议在JVM参数中将MetaspaceSize和MaxMetaspaceSize设置成一样的值,并设置得比初始值要大,<br>对于8G物理内存的机器来说,一般我会将这两个值都设置为256M。<br>
尽可能让对象都在新生代里分配和回收,尽量别让太多对象频繁进入老年代,避免频繁对老年代进行垃圾回收,<br>同时给系统充足的内存大小,避免新生代频繁的进行垃圾回收。<br>
GC类型
<b><font color="#e0124f">Minor GC 新生代收集</font></b><br><ul><li>指发生在堆中新生代的的垃圾收集动作,Minor GC非常频繁,回收速度一般也比较快。</li><li>当年轻代中的Eden区分配满的时候触发</li><li>MinorGC 采用标记复制算法。</li></ul>
<b><font color="#e0124f">Major GC 老年代收集</font></b><br><ul><li>指发生在堆中老年代的垃圾收集动作</li><li>老年代的对象比较稳定,所以MajorGC不会频繁执行。</li><li>目前只有CMS收集器才会有单独的MajorGC行为</li></ul>
<b><font color="#e0124f">Mixed GC 混合收集</font></b><br><ul><li>指目标是收集整个新生代以及部分老年代的垃圾收集动作。</li><li>目前只有G1收集器会有这种行为</li></ul>
<b><font color="#e0124f">Full GC 整堆收集</font></b><br><ul><li>收集整个Java堆和方法区的垃圾收集</li><li>Full GC 前一般都会先发生Minor GC</li></ul>
对象晋升老年代
<b><font color="#e0124f">大对象直接进入老年代</font></b><br>这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。<br><font color="#0b38d9">大对象是指需要大量连续内存空间的对象,如:大数组和长字符串</font><br>
<b><font color="#e0124f">长期存活的对象将进入老年代</font></b><br>虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,<br>之后每经过一次Minor GC那么对象的年龄加1,直到达到阀值(默认15)对象进入老年区。<br><font color="#0b38d9">可以通过参数 -XX:MaxTenuringThreshold 来设置</font><br>
<b><font color="#e0124f">对象动态年龄判断</font></b><br>如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。<br><font color="#0b38d9">对象动态年龄判断机制一般是在minor gc之后触发的</font><br>
对象
对象引用类型
<b><font color="#e0124f">强引用</font></b><br><font color="#0b38d9">不可达时回收</font><br>
<b><font color="#e0124f">软引用</font></b><br><font color="#0b38d9">内存不足时回收</font><br>
<b><font color="#e0124f">弱引用</font></b><br><font color="#0b38d9">垃圾收集器工作时回收</font><br>
<b><font color="#e0124f">虚引用</font></b><br><font color="#0b38d9">垃圾收集器工作时回收</font><br>
对象内存布局
对象访问
句柄访问
<b><font color="#e0124f">直接指针<br></font></b><font color="#0b38d9">HotSpot虚拟机主要使用直接指针来进行对象访问</font><br>
逃逸分析
栈上分配
同步消除
标量替换
垃圾收集理论及算法
<b><font color="#e0124f">对象内存回收算法</font></b>
<b><font color="#e0124f">引用计数器法</font></b><br>为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,当计数器为 0 时就可以被回收。<br>它有一个缺点不能解决循环引用的问题<br>
<b><font color="#e0124f">可达性分析算法</font></b><br>从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。<br>当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。<br>
<b><font color="#e0124f">可作为GC Root根的对象</font></b><br><ul><li><font color="#0b38d9">虚拟机栈(本地变量)引用的对象</font></li><li><font color="#0b38d9">本地方法栈引用的对象</font></li><li><font color="#0b38d9">静态变量引用的对象</font></li></ul>
<b><font color="#e0124f">分代收集理论</font></b><br>根据对象存活周期的不同将内存划分为几块,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法<br>
<b><font color="#e0124f">标记 - 清除</font></b><br>算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。<br>优点:实现简单,不需要对象进行移动。<br>缺点:标记、清除过程效率低,产生大量不连续的内存碎片,提高了垃圾回收的频率。<br><b><font color="#0b38d9">适用于老年代,CMS使用的就是标记清除</font></b>
<b><font color="#e0124f">标记 - 复制</font></b><br>将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。<br>优点:按顺序分配内存即可,实现简单、运行高效,不用考虑内存碎片。<br>缺点:可用的内存大小缩小为原来的一半,对象存活率高时会频繁进行复制。<br><font color="#0b38d9"><b>适用于年轻代</b></font><br>
<b><font color="#e0124f">标记 - 整理</font></b><br>标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存<br>优点:解决了标记-清理算法存在的内存碎片问题。<br>缺点:仍需要进行局部对象移动,一定程度上降低了效率。<br><font color="#0b38d9"><b>适用于老年代</b></font><br>
垃圾收集器
Serial收集器(标记 - 复制)<br>
Serial Old收集器(标记-整理)
ParNew收集器(标记 -复制)<br><b><font color="#0b38d9">可以搭配Serial Old和CMS</font></b>
Parallel收集器(标记 -复制)<br><font color="#0b38d9"><b>JDK8默认,侧重吞吐量</b></font><br>
Parallel Old收集器(标记-整理)<br><b><font color="#0b38d9">JDK8默认,侧重吞吐量</font></b><br>
CMS收集器(标记-清除)<br><b><font color="#0b38d9">侧重低停顿</font></b>
初始标记(STW)
并发标记
重新标记(STW)
并发清除
并发重置
G1收集器(标记-复制)<br><font color="#0b38d9"><b>JDK9默认</b></font><br>
初始标记(initialmark,STW)
并发标记(ConcurrentMarking)
最终标记(Remark,STW)
筛选回收(Cleanup,STW)
如何选择垃圾收集器?
Serial<br><font color="#0b38d9">内存小(约100m),单核心,对停顿时间没什么要求的</font><br>
Parallel<br><font color="#0b38d9">对吞吐量有要求,4G以下可以用parallel</font>
CMS/G1<br><font color="#0b38d9">低停顿,内存4-8G可以用ParNew+CMS,8G以上可以用G1</font><br>
ZGC<br><font color="#0b38d9">内存比较大,几百G以上用ZGC</font><br>
JVM常用调优命令
jps<br>JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程<br>
<b><font color="#0b38d9">jstat</font></b><br>JVM statistics Monitoring是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。<br>
<b><font color="#0b38d9">jmap</font></b><br>JVM Memory Map命令用于生成heap dump文件<br>
jhat<br>JVM Heap Analysis Tool命令是与jmap搭配使用,用来分析jmap生成的dump,jhat内置了一个微型的HTTP/HTML服务器,生成dump的分析结果后,可以在浏览器中查看<br>
<b><font color="#0b38d9">jstack</font></b><br>用于生成java虚拟机当前时刻的线程快照。<br>
jinfo<br>JVM Configuration info 这个命令作用是实时查看和调整虚拟机运行参数<br>
常见问题
<b><font color="#e0124f">什么时候会触发FullGC?</font></b>
System.gc()
老年代空间不足
永久代空间不足
<b><font color="#0b38d9">老年代空间担保失败</font></b>
Cocurrent mode failure
JVM中一次完整的GC流程是怎样的,对象如何晋升到老年代?
你知道哪几种垃圾收集器,各自的优缺点,重点讲下cms和G1,包括原理,流程,优缺点。
内存溢出(out of memory)的场景及解决方案?
java堆内存溢出
元空间内存溢出
永久代内存溢出
栈内存溢出
直接内存内存溢出
GC 开销超过限制
线程栈满
系统内存被占满
<b><font color="#e0124f">内存溢出如何排查?</font></b><br>1、配置-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\\log,出现溢出时会生成dumpfile.hprof文件。<br>2、通过jmap命令生成dump文件。jmap -dump:format=b,file=<dumpfile.hprof> <pid><br>3、通过MAT来查看具体的问题<br>
<b><font color="#e0124f">线上CPU使用比较高,如何快速定位问题?</font></b><br>1、定位进程,<b><font color="#0b38d9">top</font></b>命令查看CPU占比最高的<b><font color="#0b38d9">进程PID1893</font></b><br>2、定位线程,<b><font color="#0b38d9">top -Hp 1893</font></b>命令查看CPU占比最高的<b><font color="#0b38d9">线程PID4519</font></b><br>3、转16进制,<b><font color="#0b38d9">printf %x 4519</font></b>命令将线程B转换成<b><font color="#0b38d9">16进制11a7</font></b><br>4、定位代码,<b><font color="#0b38d9">jstack 1893 |grep -A 200 11a7</font></b>命令查看<b><font color="#0b38d9">栈信息</font></b>,定位到具体的代码行<br>
<b><font color="#e0124f">OOM会导致JVM直接退出吗?为什么?</font></b><br>不会,一个线程抛出OOM异常后,当前线程持有的对象所占用的资源都会被释放掉(gc),从而不影响其它线程<br>只有所有工作线程都退出了(只有守护线程),JVM才会退出<br><font color="#0b38d9">注:内存泄露不会马上导致异常,但是内存泄露堆积后,最终可能会导致内存溢出。</font>
分布式
CAP原则<br>
概念:指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可兼得
一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)<br>
可用性(A):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)<br>
分区容忍性(P):以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,<br>必须就当前操作在C和A之间做出选择。<br>
CP:zookeeper,Consul,Hbase,<font color="#0b38d9">Nacos(要配置,持久节点)</font>,<font color="#0b38d9">Kafka(replication.factor = 3、min.insync.replicas = 3、acks = all)</font><br>
AP:eureka,redis,<font color="#0b38d9">Nacos(默认,临时节点)</font>,<font color="#0b38d9">Kafka(replication.factor = 3、min.insync.replicas = 3、acks = 1)</font><br>
CA:关系型数据库、<font color="#0b38d9">Kafka(Kafka的开发人员申明Kafka是CA系统,因为运行在一个数据中心,网络分区问题基本不会发生,如果发生了,就有AP和CP之分)</font>
Base理论<br>
BASE理论是对CAP理论的延伸,核心思想是即使无法做到强一致性(Strong Consistency),但应用可以采用适合的方式达到最终一致性(Eventual Consitency)。
基本可用(Basically Available):指分布式系统在出现不可预知故障的时候,允许损失部分可用性<br>软状态( Soft State):指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性。<br>最终一致( Eventual Consistency):强调的是所有的数据更新操作,在经过一段时间的同步之后,最终都能够达到一个一致的状态。<br>
BASE理论面向的是大型高可用可扩展的分布式系统,和传统的事物ACID特性是相反的。它完全不同于ACID的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。
一致性协议
二阶段提交(2PC)<br>
<b><font color="#c40c0c">优点</font></b><br>- 实现原理简单
<b><font color="#c40c0c">缺点<br></font></b>- <b><font color="#210ddb">同步阻塞导致性能问题</font></b>:执行过程中,所有参与节点都是事务阻塞型的<br>- <b><font color="#210ddb">协调者单点故障问题</font></b>:一旦协调者发生故障,参与者会一直阻塞下去<br>- <b><font color="#210ddb">丢失消息导致的数据不一致问题</font></b>:发送Commit请求时,发生局部网络异常,只有部分参与者收到请求<br>- <b><font color="#210ddb">过于保守</font></b>:没有完善的容错机制,任意一个节点的失败都会导致整个事务的失败<br>
三阶段提交(3PC)<br>
<b><font color="#c40c0c">优点</font></b><br>- 相比较2PC而言,3PC对于协调者(Coordinator)和参与者(Participant)都设置了超时时间,<br>而2PC只有协调者才拥有超时机制。<br>- 加了一个事务询问过程,减少了阻塞范围<br>
缺点<br>- 没有解决数据不一致问题
Paxos<br>
执行流程
优缺点
ZAB<br>
Raft<br>
执行流程
优缺点
分布式事务
<b><font color="#c40c0c">两阶段提交方案XA 方案<br></font></b>- 效率低,适合单体多数据库应用,不适合互联网
<b><font color="#c40c0c">TCC 方案(常用)<br></font></b>- 适用于严格的保证数据正确性的分布式事务场景,即要么全部成功,要么全部回滚,比如资金转账等<br>- TCC 方案严重依赖回滚和补偿代码,使用场景较少
<b><font color="#c40c0c">本地消息表<br></font></b>- 严重依赖数据库消息表,无法支持高并发场景
<b><font color="#c40c0c">MQ事务消息方案(常用)</font></b><br>- 只保证发送端与MQ之间的事务性,不保证消费端事务<br>- 消费端事务执行失败的话,要么不断重试直到成功,要么通知发送端回滚或者人工补偿<br>- 支持高并发,大多数公司的选择,rocketmq的解决方案
<b><font color="#c40c0c">最大努力通知方案<br></font></b>- 有个最大努力通知服务会消费 MQ 然后写入数据库中记录下来<br>- 最大努力的通知系统B,反复 N 次,最后还是不行就放弃。
分布式框架
Netty
zookeeper
什么是zookeeper?
zookeeper的znode(目录节点)有哪些?<br>
PERSISTENT(持久化目录节点)<br>
PERSISTENT_SEQUENTIAL(持久化顺序编号目录节点)
EPHEMERAL(临时目录节点)<br>
EPHEMERAL_SEQUENTIAL(临时顺序编号目录节点)<br>
Container节点
TTL 节点
zookeeper的watch监听通知机制
服务器角色
Leader
Follower
Observer<br>
leader选举
选举相关的概念
Zookeeper 节点状态
事务ID(zxid)<br>
服务器ID(myid)<br>
逻辑时钟(epoch logicalclock)<br>
集群初始化启动时 Leader 选举<br>
集群运行期间 Leader 重新选<br>
zookeeper的经典使用场景?
分布式配置中心
分布式注册中心<br>
分布式互斥锁<br>
分布式共享锁
分布式队列
集群选举
负载均衡
集群使用几台服务器好?
ZAB协议<br>
消息广播
崩溃恢复
数据同步
zookeeper是如何保证事务的顺序一致性的?<br>
集群支持动态添加机器吗?
ZAB和Paxos算法的联系与区别?
ZooKeeper分布式锁的优点和缺点?
dubbo
ShardingSphere<br>
消息中间件
Kafka
概念
<b><font color="#e0124f">Broker</font></b><br>Kafka集群包含一个或多个服务器,这种服务器被称为broker。broker端不维护数据的消费状态,提升了性能。直接使用磁盘进行存储,线性读写,速度快:避免了数据在JVM内存和系统内存之间的复制,减少耗性能的创建对象和垃圾回收<br>
<b><font color="#e0124f">Producer</font></b><br>消息生产者,负责发布消息到Kafka broker<br>
<b><font color="#e0124f">Consumer</font></b><br>消息消费者,向Kafka broker读取消息的客户端,consumer从broker拉取(pull)数据并进行处理<br>
<b><font color="#e0124f">Topic</font></b><br>每条发布到Kafka集群的消息都有一个类别,这个类别被称为Topic,属于逻辑上的概念<br>
<b><font color="#e0124f">Partition</font></b><br>Parition是物理上的概念,每个Topic包含一个或多个Partition<br>
<b><font color="#e0124f">Consumer Group</font></b><br>每个Consumer属于一个特定的Consumer Group(可为每个Consumer指定group name,若不指定group name则属于默认的group)<br>
生产端
<b><font color="#e0124f">消息分发策略?</font></b><br>1、指定具体partition<br>2、没有指定partition但有key的情况下,将key的hash值与topic的partition数进行取余得到partition<br>3、没有指定partition且无key的情况下,会随机选择一个分区并一直使用,直到设定的时间到期后(metadata.max.age.ms,默认10分钟),<br>会重新随机另外一个分区
<b><font color="#e0124f">重要参数</font></b>
<b><font color="#e0124f">acks</font></b><br>0:生产者不会等待任何来自服务器的响应<br><font color="#0b38d9"><b>1(默认)</b></font>:leader写日志成功,才会返回成功<br>-1或者all:所有ISR的副本全部写日志成功才返回成功
<b><font color="#e0124f">retries</font></b><br><b><font color="#0b38d9">重试次数(默认0)</font></b>,生产者从服务器收到的错误有可能是临时性的错误(比如分区找不到首领),需要重试,<br><b><font color="#0b38d9">重试间隔默认100ms</font></b>,可以通过retry.backoff.ms来设置<br>
<b><font color="#e0124f">buffer.memory</font></b><br>生产端用来缓存消息的缓冲区大小,<b><font color="#0b38d9">默认32M<br></font></b>生产者发送消息不是直接发到broker,而是放在缓冲区队列里面,再由后台线程不断从队列中取出消息发送,<br>发送成功后会触发callback。<font color="#0b38d9">如果缓冲区不足,会先阻塞一段时间(max.block.ms),再抛出异常</font><br>
<b><font color="#e0124f">batch.size</font></b><br>当有多个消息需要被发送到<b><font color="#0b38d9">同一个分区</font></b>时,生产者会把它们放在同一个批次里,满了就发送。<br><b><font color="#0b38d9">默认16K</font></b>,该值过小的话,会降低吞吐量,过大的话,会带来较大的内存压力<br>
<b><font color="#e0124f">linger.ms</font></b><br>该参数指定了生产者在发送批次消息之前等待更多消息加入批次的时间。<br>KafkaProducer会在批次填满或linger.ms达到上限时把批次发送出去。<br><b><font color="#0b38d9">默认0</font></b>,表示不等待,这种情况会导致批次消息过少就被发送了<br>
<b><font color="#e0124f">max.block.ms</font></b><br><b><font color="#0b38d9">默认60秒</font></b>,KafkaProducer.send() and KafkaProducer.partitionsFor() 方法最大的阻塞时间,<br>当buffer.memory满了或者集群的Metadata不可用时,这两个方法会被阻塞。<br>
<b><font color="#e0124f">request.timeout.ms</font></b><br>Producer向Broker发送请求以后,等待响应的最长时间,<b><font color="#0b38d9">默认30秒</font></b><br>
<b><font color="#e0124f">消息发送方式</font></b>
<b><font color="#e0124f">发送并忘记</font></b><br>调用send()方法发送消息,但并不关心它是否正常到达。<br><font color="#0b38d9">producer.send(new ProducerRecord<Integer, String>(topic,msg))</font><br>
<b><font color="#e0124f">同步发送</font></b><br>调用send()方法发送消息,它会返回一个Future对象,调用get()方法进行等待,就可以知道消息是否发送成功。<br><font color="#0b38d9">producer.send(new ProducerRecord<Integer, String>(topic,msg)).get();</font><br>
<b><font color="#e0124f">异步发送</font></b><br>调用send()方法发送消息,并指定一个回调函数,服务器在返回响应时调用该函数<br>
<b><font color="#e0124f">数据的传递性语义</font></b>
<b><font color="#e0124f">至少一次(AtLeastOnce)</font></b><br><b><font color="#0b38d9">默认</font></b>,可以保证数据不丢失,但是不能保证数据不重复<br>
<b><font color="#e0124f">最多一次(AtMostOnce)</font></b><br>可以保证数据不重复,但是不能保证数据不丢失<br>
<b><font color="#e0124f">精确一次(Exactly Once)</font></b><br>不会漏传输也不会重复传输<br>
消费端
<b><font color="#e0124f">消费者消费原理?</font></b><br>1、一个partition里面的消息,只能被同一consumer group中的某一个consumer来消费<br>2、想要多个consumer来消费同一个partition,那这多个consumer必须要在不同的consumer group中<br>3、如果consumer多于partition,则多出的consumer处于空闲浪费状态
<b><font color="#e0124f">消费者分区分配策略?</font></b><br>1、<font color="#0b38d9">Range(范围)默认</font>:分区数/消费者数,多的按顺序分摊到前面几个消费者上面<br>2、RoundRobin(轮询):轮询分区策略是把所有partition和所有consumer线程都列出来,然后按照hashcode进行排序。<br>最后通过轮询算法分配partition给消费线程。<br>3、Stricky(粘性):它主要有两个目的(分区的分配尽可能的均匀分区的分配,尽可能和上次分配保持相同),初始采用轮询,后面变化。<br>
<b><font color="#e0124f">consumer和partition的数量建议?</font></b><br>1、最好partition和consumer数量一致,或者partition是consumer的整数倍<br>2、consumer大于partition,会导致consumer浪费<br>3、consumer远小于partition,会导致吞吐量降低<br><font color="#0b38d9">consumer从多个partition读取数据,不保证数据间的顺序性,kafka只保证在一个partition<br>上数据是有序的</font><br>
<b><font color="#e0124f">什么是rebalance机制?什么时候会触发该机制?</font></b><br>Rebalance 本质上是一种协议,规定了一个 Consumer Group 下的所有 consumer 如何达成一致,来分配订阅 Topic 的每个分区。<br>1、同一个consumer group内新增了消费者<br>2、消费者离开当前所属的consumer group,比如主动停机或者宕机<br>3、topic新增了分区(也就是分区数量发生了变化)<br>
<b><font color="#e0124f">什么是coordinator?coordinator又是如何协调consumer group消费的(rebalance过程)?</font></b><br>coordinator是用来统筹管理consumer group消费的<br>1、<font color="#e0124f">确定coordinator</font>:broker集群会将集群中<b><font color="#0b38d9">负载最小的broker定为coordinator</font></b><br>2、<font color="#e0124f">获取coordinator</font>:consumer会向集群中任意一个broker发送一个GroupCoordinatorRequest来获取coordinator<br>3、<font color="#e0124f">确定consumer leader</font>:所有的consumer向coordinator发送joinGroup请求,coordinator会从中挑选一个来担任<br>consumer leader角色(<b><font color="#0b38d9">如果没有leader,那么第一个请求的就是Leader;如果leader退出了消费组,则随机选举一个</font></b>)<br>4、<font color="#e0124f">确定分区分配策略</font>:每个consumer都可以有自己的分区分配策略,在上述joinGroup的过程中,consumer也会将自己的分区分配策略<br>发给coordinator,由coordinator组成一个侯选集,然后每个consumer都从中选举一个自己支持的策略并投票,最终票数多的为最终策略<br>5、<font color="#e0124f">确定消费partition方案</font>:leader选举出来后,会根据最终策略,产生一个消费partition的方案,然后向coordinator发送一个<br>SyncGroupRequest请求,将方案同步给coordinator<br>6、<font color="#e0124f">同步方案给消费者</font>:coordinator拿到方案后,将方案同步给其它consumer,除非发生rebalance,否则consumer只会消费对应的Partition<br><br><b><font color="#0b38d9">Rebalance期间,整个消费组无法消费消息</font></b><br>
服务端
概念
<b><font color="#e0124f">Controller(控制器)</font></b><br>在Kafka集群中会有一个broker会被选举为控制器(KafkaController),它负责管理整个集群中所有分区和副本的状态<br>1、监听broker相关的变化<br>2、监听topic相关的变化<br>3、从Zookeeper中读取获取当前所有与topic、partition以及broker有关的信息并进行相应的管理<br>4、更新集群的元数据信息,同步到其他普通的broker节点中<br>
<b><font color="#e0124f">Leader</font></b><br>partition中的Leader副本,提供读写服务,并且同步数据给Follower副本<br>
<b><font color="#e0124f">Follower</font></b><br>partition中的Follower副本,不提供读写服务,用于备份Leader副本中的数据,与Leader是一主多从关系<br>
<b><font color="#e0124f">AR(Assigned Repllicas)</font></b><br>partition中的所有副本集合<br>
<b><font color="#e0124f">ISR(In-Sync Replicas)</font></b><br>与leader副本保持一定程度同步(replica.lag.time.max.ms时间内有同步)的副本集合(<font color="#0b38d9"><b>ISR中的第一个副本就是Leader副本</b></font>),ISR是AR的子集<br><font color="#0b38d9">Leader副本负责维护和跟踪ISR集合中所有的follower副本的滞后状态,当follower副本落后太多或者失效时,leader副本会吧它从ISR集合中剔除</font><br>
<b><font color="#e0124f">OSR(Out-Sync Relipcas)</font></b><br>与leader副本同步滞后过多的副本集合,AR = ISR + OSR<br>
<b><font color="#e0124f">LEO(Log End Offset)</font></b><br>标识当前日志文件中<font color="#0b38d9"><b>下一条待写入的消息的offset</b></font>。LEO 的大小相当于当前日志分区中最后一条消息的offset值加1。<br>
<b><font color="#e0124f">HW(High Watermark)</font></b><br>俗称高水位,它标识了一个消息偏移量(offset),<b><font color="#0b38d9">消费者只能拉取到这个offset之前的消息</font></b>。<br><font color="#0b38d9"><b>HW为ISR中所有副本的最小LEO</b></font>
<b><font color="#e00707">LW(Low Watermark)</font></b><br>俗称低水位,AR集合中最小的Log Start Offset
<b><font color="#e0124f">Controller选举<br></font></b>优先到zookeeper上面创建<b><font color="#0b38d9">/controller临时节点</font></b>的broker为Controller<br>
<b><font color="#e0124f">Partition副本Leader选举</font></b><br>Controller会从ISR列表(参数unclean.leader.election.enable=false的前提下)里挑第一个broker作为Leader(第一个broker最先放进ISR列表,可能是同步数据最多的副本),如果参数unclean.leader.election.enable=true,代表在ISR列表里所有副本都挂了的时候可以在ISR列表以外的副本中选Leader<br>
<b><font color="#e0124f">Kafka数据同步方式?</font></b><br>Kafka的复制机制既不是完全的同步复制,也不是单纯的异步复制,而是半同步复制。<br>Kafka使用这种<b><font color="#0b38d9">ISR</font></b>的方式有效的权衡了数据可靠性和性能之间的关系<br>
<b><font color="#e0124f">Kafka如何保存消费端的消费位置?</font></b><br>Kafka是通过offset来保存消费者的位置的。在kafka中,提供了一个consumer_offsets_* 的一个topic,把offset信息写入到这个topic中。<br><font color="#0b38d9">offset不跨分区,只保证同一分区的offset的顺序性</font>
常见问题汇总
<b><font color="#e0124f">Kafka为什么速度这么快?</font></b><br>1、利用 Partition实现数据分区存储,提高并发处理效率<br>2、利用页缓存技术,提高IO效率<br>3、顺序写,在刷盘时,将数据追加到文件的末尾,性能不比写内存差多少<br>4、零拷贝(读):减少了从os cache拷贝到应用缓存和从应用缓存拷贝到socket缓存。<br>Socket缓存中仅仅会拷贝一个描述符过去,不会拷贝数据到Socket缓存<br>5、消息批量发送,提高吞吐量<br>6、数据压缩:Kafka还支持对消息集合进行压缩,Producer可以通过GZIP、Snappy、LZ4格式对消息集合进行压缩<br><br><b><font color="#0b38d9">操作系统本身有一层缓存,叫做page cache,是在内存里的缓存,我们也可以称之为os cache,意思就是操作系统自己管理的缓存。</font></b><br>
<b><font color="#e0124f">Kafka如何快速读取指定offset的消息?</font></b><br>1、Partition的数据是分段(Segment)存储的,每个分段都包含log、index、timeindex三个文件。<br>2、首先通过offset根据<b><font color="#0b38d9">二分法定位</font></b>到index稀疏索引文件<br>3、然后根据索引文件中的[offset,position](position为物理偏移地址)去log中获取指定offset的message数据。
<b><font color="#e0124f">Kafka如何保证消息不丢失?</font></b><br>1、设置 ack=-1,保证ISR所有副本全部写日志成功才返回;producer.type=sync,设置成同步模式<br>2、设置 replication.factor>=3,并且 min.insync.replicas>=2,保证ISR中副本集至少大于等于2<br>3、设置 unclean.leader.election.enable=false,关闭跟不上Leader的follower副本的选举<br>4、设置 enable.auto.commit=false,消费者改为手动提交<br><b><font color="#0b38d9">第1点是生产端,第2、3点是服务端,第4点为消费端<br></font></b><font color="#0b38d9">-- min.insync.replicas:ISR里面的最小副本数<br>--<b> </b>replication.factor:这个参数用来表示分区的副本数</font><br>
<b><font color="#e0124f">Kafka 是怎么去实现负载均衡的?</font></b><br>1、生产者:消息分发策略(具体见上面描述)<br>2、消费者:分区分配策略(具体见上面描述)
<b><font color="#e0124f">为什么Kafka不支持读写分离?</font></b><br>1、读写分离(主写从读)会存在数据一致性和延时问题<br>2、Kafka本身采取了分区的策略,天然提高了并发效率,不需要进一步采用读写分离来提升效率<br>
<b><font color="#e0124f">Kafka 分区数可以增加或减少吗?为什么?</font></b><br>1、支持分区增加,增加后会重新rebalance就好了<br>2、不支持分区减少,因为会丢失上面的消息
<b><font color="#e0124f">Kafka消息是采用Pull模式,还是Push模式?</font></b><br>1、Push:当消息生产速率远大于消费速率,消费者容易崩溃<br>2、Pull:可以根据自己的消费能力拉取数据,但是如果没有消息时,会不断轮询,可以在消费端设置轮询间隔<br>
<b><font color="#e0124f">Kafka的分区数是不是越多越好?</font></b> <b><font color="#0b38d9">-- 不是</font></b><br>1、生产者缓存占用内存会更多(一个分区会有一个batch.size的开销)<br>2、消费者线程数也会更多(一个分区需要一个线程来处理)<br>3、服务端的很多组件都在内存中维护了分区级别的缓存,比如controller,FetcherManager等,因此分区数越多,这种缓存的成本就越大<br>4、文件句柄的开销会更多(.index和.log文件数量会更多)<br>
<b><font color="#e0124f">谈谈你对Kafka生产者幂等性的了解?</font></b><br>Kafka精确一次性(Exactly-once)保障之一<br><font color="#0b38d9">为了实现Producer的幂等性,Kafka引入了Producer ID(即PID)和Sequence Number。</font><br><ul><li>PID。每个新的Producer在初始化的时候会被分配一个唯一的PID,这个PID对用户是不可见的。</li><li>Sequence Numbler。对于每个PID,该Producer发送数据的每个都对应一个从0开始单调递增的Sequence Number</li><li>Broker端在缓存中保存了这seq number,对于接收的每条消息,如果其序号比Broker缓存中序号大于1则接受它,<br>否则将其丢弃,这样就可以实现了消息重复提交了。但是只能保证单个Producer对于同一个的Exactly Once语义</li></ul><br>
<b><font color="#e0124f">Kafka如何保证消息的有序性?</font></b><br><font color="#0b38d9">单分区可以保证有序性,多分区不可以</font><br>1、将max.in.flight.requests.per.connection设置为1,消息队列中只允许有一个请求,<br>这样消息失败后,可以第一时间发送,不会产生乱序,但是会降低网络吞吐量。<br>2、开启生产者幂等性设置(Exactly-once)<br>
<b><font color="#e0124f">Kafka缺点?</font></b><br><ol><li><font color="#0b38d9">由于是批量发送,数据并非真正的实时;</font></li><li>对于mqtt协议不支持;</li><li>不支持物联网传感数据直接接入;</li><li><font color="#0b38d9">仅支持同一分区内消息有序,无法实现全局消息有序</font>;</li><li>监控不完善,需要安装插件;</li><li>依赖zookeeper进行元数据管理;3.0版本去除</li></ol>
RocketMQ
概念
<b><font color="#e0124f">Broker</font></b><br>-- RocketMQ集群包含一个或者多个服务器,这种服务器被称为Broker。Broker的作用是存储和转发消息, 单机大约能承受 10 万 QPS 的请求。<br>-- RocketMQ集群的Broker节点都保存总数据的一部分,可以实现横向扩展。为了提升可靠性(防止数据丢失),每个 Broker 可以有自己的副本(slave)<br>-- 默认情况下,读写都发生在 master上。在slaveReadEnable=true 的情况下,slave 也可以参与读负载。但是只有 Brokerld=1的 slave 才会参与读负载<br><b><font color="#0b38d9">注:Broker 会向所有的Name Server注册</font></b>
<b><font color="#c40c0c">Master</font></b><br>-- 主节点,通过配置来设置,一经设置,不会改变,即Master挂了后,从节点不会选举变成主节点<br><b><font color="#140cf2">注:4.5以后,基于dledger技术可以进行选举,推举一个slave成master。</font></b>
<b><font color="#e80707">Slave</font></b><br>-- 从节点,用于数据的备份,通过配置来设置,一经设置,不会改变,即Master挂了后,从节点不会选举变成主节点
<b><font color="#e0124f">Producer</font></b><br>-- Producer 跟 Name Server 的任意一个节点(随机)建立长连接,定期从 Name Server 拉取 Topic 路由信息<br>-- 根据Topic路由信息与指定的 Broker 建立 TCP长连接<br>-- 发送逻辑一致的Producer 可以组成一个 Group<br>-- RocketMQ的生产者同样支持批量发送,不过 List要自己传进去<br>-- <b><font color="#0b38d9">Producer写数据只能操作 master 节点</font></b><br>
<b><font color="#e0124f">Consumer</font></b><br>-- Consumer 跟 Name Server 的任意一个节点(随机)建立长连接,定期从 Name Server 拉取 Topic 路由信息<br>-- 根据Topic路由信息与指定的 Master和Slave 建立 TCP长连接<br>-- 消费逻辑一致的 Consumer可以组成一个Group<br>-- <b><font color="#0b38d9">在slaveReadEnable=true 的情况下,slave 也可以参与读负载</font></b>
<b><font color="#e0124f">NameServer<br></font></b>-- NameServer 可以理解为是RocketMQ的路由中心<br>-- Producer 和 Consumer都是通过NameServer拿到Topic的相关信息的<br>-- <b><font color="#0b38d9">NameServer之间不做数据通信,每一个NameServer 节点都保存着全量的路由信息</font></b><br>
<b><font color="#e0124f">Topic</font></b><br>Topic用于将消息按主题做划分,在 RocketMQ中,Topic是一个逻辑概念,消息不是按 Topic 划分存储的<br>
<b><font color="#e0124f">Message Queue</font></b><br>类似于Kafka里面的Partition分片的概念,topic的数据是分片存储的
生产端<br>
发送方式
<b><font color="#e0124f">同步发送<br></font></b>消息发送出去后,producer会等到broker回应后才能继续发送下一个消息<br>
<b><font color="#e0124f">异步发送<br></font></b>异步发送是指发送方发出数据后,不等接收方发回响应,接着发送下个数据包的通讯方式<br><b><font color="#0b38d9">注:有一个异步发送回调接口(SendCallback)来处理返回信息</font></b><br>
<b><font color="#e0124f">单向发送(Oneway)</font></b><br>只发送请求不等待应答,效率最高<br>
<b><font color="#e0124f">顺序发送(OrderProducer)<br></font></b>顺序发送比较复杂,支持局部顺序消息,要保证发送端,服务端,消费端的顺序保持一致
服务端
<b><font color="#e0124f">消息的存储结构是怎么样的?</font></b><br>-- <b><font color="#0b38d9">CommitLog</font></b>:存储消息的元数据。所有消息都会顺序存入到CommitLog文件当中。CommitLog<br>由多个文件组成,每个文件固定大小1G。以第一条消息的偏移量为文件名。<br>-- <b><font color="#0b38d9">ConsumerQueue</font></b>:存储消息在CommitLog的索引。一个MessageQueue一个文件,记录当前<br>MessageQueue被哪些消费者组消费到了哪一条CommitLog。<br>-- <b><font color="#0b38d9">IndexFile</font></b>:为了消息查询提供了一种通过key或时间区间来查询消息的方法,这种通过IndexFile来<br>查找消息的方法不影响发送与消费消息的主流程<br>
<b><font color="#e0124f">刷盘机制是怎么样的?<br></font></b>-- <b><font color="#0b38d9">同步刷盘</font></b>,消息写入内存的PAGECACHE后,立刻通知刷盘线程刷盘, 然后等待刷盘完成,<br>刷盘线程执行完成后唤醒等待的线程,返回消息写成功的状态<br>-- <b><font color="#0b38d9">异步刷盘</font></b>,消息可能只是被写入了内存的PAGECACHE就返回消息成功了,何时刷盘由操作系统自己决定<br><font color="#0b38d9">注:配置方式,Broker配置文件里的flushDiskType(SYNC_FLUSH、ASYNC_FLUSH)</font><br>
<b><font color="#e0124f">消息主从复制是怎么样的?</font></b><br>-- <b><font color="#0b38d9">同步复制</font></b>,Master和Slave都写入消息成功后才反馈给客户端写入成功的状态<br>-- <b><font color="#0b38d9">异步复制</font></b>,异步复制是只要master写入消息成功,就反馈给客户端写入成功的状态。然后再异步的将消息复制给<br>Slave节点。<br><font color="#0b38d9">注:配置方式,Broker配置文件里的brokerRole(ASYNC_MASTER、 SYNC_MASTER、SLAVE)</font><br>
消费端
消息模式
<b><font color="#e0124f">集群消费(Clustering)</font></b><br>相同Consumer Group的每个Consumer实例协作分摊消息<br>
<b><font color="#e0124f">广播消费(Broadcasting)</font></b><br>相同Consumer Group的每个Consumer实例都接收全量的消息<br>
消费形式
<b><font color="#e0124f">拉取式消费(pull)</font></b><br>Consumer主动从Broker服务器拉消息<br>
<b><font color="#e0124f">推动式消费(push)</font></b><br>Broker收到数据后会主动推送给消费端,实时性比较高,<b><font color="#0b38d9">底层同样是pull</font></b><br>
路由端(NameServer)
<b><font color="#e0124f">NameServer 作为路由中心到底怎么工作的呢?</font></b><br>-- 每个Broker节点在启动时,都会根据配置的NameServer列表,与每个NameServer建立TCP长连接,并注册自己的信息<br>-- 每个Broker(包含master和slave)<b><font color="#0b38d9">每隔30s</font></b>发送心跳信息,表示自己正常存活<br>-- 每个NameServer<b><font color="#0b38d9">每隔10s</font></b>检查一下各个Broker 的最近一次心跳时间,如果发现某个Broker超过<b><font color="#0b38d9">120s</font></b>都没发送心跳,<br>就认为这个Broker 已经挂掉了,会将其从路由信息里移除。<br>
<b><font color="#e0124f">NameServer 之间是互相不通信的,也没有主从之分,它们是怎么保持一致性的?<br></font></b>-- <b><font color="#0b38d9">服务注册</font></b>,每个Broker都会向所有NameServer进行注册,并且每隔30s发送心跳<br>-- <b><font color="#0b38d9">服务剔除</font></b>,Broker正常关闭(连接断开),Netty的通道关闭监听器会监听到连接断开事件;<br>Broker异常关闭,NameServer每隔10s检查,某个Broker120s都没有发送心跳,则可以剔除<br>-- <b><font color="#0b38d9">路由发现</font></b>,生产者在发送第一条消息的时候,根据Topic从NameServer 获取路由信息,<br>消费者一般是订阅固定的Topic,在启动的时候就要获取 Broker 信息<br><font color="#0b38d9">注:<br>-- NameServer 不会主动推送服务信息给客户端(指生产端和消费端),<br>-- 客户端也不会发送心跳到Nameserver<br>-- 客户端会<b>每隔30s</b>到NameServer拉取最新信息,缓存在本地<br></font><font color="#e809e8"><b>-- NameServer 是AP,只保证最终一致性,Zookeeper是CP</b></font>
<b><font color="#e0124f">如果作为路由中心的 NameServer 全部挂掉了,而且暂时没有恢复呢?<br></font></b>-- 客户端会缓存路由信息在本地,不完全依赖于 NameServer<br>
<b><font color="#e0124f">如果 Broker 刚挂,怎么处理数据?</font></b><br>-- 重试<br>-- 把无法连接的 Broker 隔离掉,不再连接<br>-- 优先选择延迟小的节点,就能避免连接到容易挂的 Broker 了<br>
常见问题汇总
<b><font color="#e0124f">RocketMQ磁盘保存文件慢吗?<br></font></b>-- <b><font color="#0b38d9">顺序写</font></b>,保证了消息存储的速度。高性能磁盘顺序写速度可以达到600MB/s<br>-- <font color="#0b38d9"><b>零拷贝</b></font>,减少了2次数据在内存中的复制(内核空间到用户空间及用户空间到内核socket)<br>-- <b><font color="#0b38d9">页缓存</font></b>,利用页缓存技术,提高IO效率<br>注:<br><font color="#0b38d9">-- RocketMQ采用的是mmap技术,Kafka采用的是sendfile技术<br>-- 关于零拷贝,JAVA的NIO中提供了两种实现方式,mmap和sendfile,其中mmap适合比较小的文<br>件(1.5~2G),而sendfile适合传递比较大的文件</font><br>
<b><font color="#e0124f">RocketMQ延时消息是怎么实现的?</font></b><br>1、producer 要将一个延迟消息发送到某个 Topic 中<br>2、Broker 判断这是一个延迟消息后,将其通过临时存储进行暂存<br>3、Broker 内部通过一个延迟服务(delay service)检查消息是否到期,将到期的消息投递到目标 Topic 中<br>4、消费者消费目标 topic 中的延迟投递的消息<br>注:<br><font color="#0b38d9">对外非商业的版本,延时设置有<b>18个</b>级别:</font><b><font color="#0b38d9">1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h</font></b><br>
<b><font color="#e0124f">RocketMQ事务消息实现原理?即怎么保证数据一致性?<br></font></b><font color="#0b38d9">注:half消息对消费者是不可见的</font>
<b><font color="#e0124f">RocketMQ如何保证消息不丢失?</font></b><br>1、同步发送+重试机制,尽可能减小消息丢失的可能性(或者回答生产者使用事务消息机制?)<br>2、Broker配置同步刷盘+Dledger主从架构 + 多个master节点<br>3、消费者不要使用异步消费 + 消费消息重试机制<br>
<b><font color="#e0124f">RocketMQ如何保证消息顺序消费?<br></font></b>1、发送端:同步发送(保证消息发送顺序) + 自定义投放策略(保证消息存储在同一个MessageQueue)<br>2、消费端:使用有序消费模式MessageListenerOrderly,MessageListenerOrderly是通过加分布式锁和本地锁<br>保证同时只有一条线程去消费一个队列上的数据。<br>
常见问题
<b><font color="#e0124f">为什么使用消息中间件?</font></b><br>-- 解耦、异步、削峰<br><b><font color="#e0124f">引入消息队列会存在什么问题?</font></b><br>-- 系统可用性降低(业务系统正常,但是消息中间件挂了)<br>-- 系统复杂性提高(消息丢失,重复消费、顺序消费、数据一致性问题等)<br><b><font color="#e0124f">Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么优点和缺点?</font></b><br>-- <b><font color="#0b38d9">Kafka</font></b>:高可用(分布式),高吞吐(10W级),低延时(ms级别),消息0丢失,功能简单(适合大数据计算及数据采集)<br>-- <b><font color="#0b38d9">RocketMQ</font></b>:高可用(分布式),高吞吐(10W级),低延时(ms级别),消息0丢失,MQ功能完善(适合业务需求)<br>-- <b><font color="#0b38d9">RabbitMQ</font></b>:高可用(主从),高吞吐(1W级),超低延时(微秒级别),消息基本不丢失,MQ功能完善<br>-- ActiveMQ:高可用(主从),高吞吐(1W级),低延时(ms级别),消息较低丢失,MQ功能完善<br>
<b><font color="#e0124f">如何保证消息队列的高可用?</font></b><br>以Kafka为例,<br>1、数据持久化到磁盘<br>2、数据分布式存储,每个分片数据都有其备份<br>3、partiton leader的选举机制(ISR)<br><b><font color="#0b38d9">注:Kafka读写数据都发生在leader上面,且leader的选举机制,保证了数据的一致性</font></b>
<b><font color="#e0124f">如何保证消息不丢失? </font><font color="#0b38d9">-- Kafka</font></b><br>1、设置 ack=-1,保证ISR所有副本全部写日志成功才返回;producer.type=sync,设置成同步模式,retries=MAX无限重试<br>2、设置 replication.factor>=3,并且 min.insync.replicas>=2,保证ISR中副本集至少大于等于2<br>3、设置 unclean.leader.election.enable=false,关闭跟不上Leader的follower副本的选举<br>4、设置 enable.auto.commit=false,消费者改为手动提交<br><b><font color="#0b38d9">第1点是生产端,第2、3点是服务端,第4点为消费端<br></font></b><font color="#0b38d9">-- min.insync.replicas:ISR里面的最小副本数<br>--<b> </b>replication.factor:这个参数用来表示分区的副本数</font><br>
<b><font color="#e0124f">如何保证消息不被重复消费(幂等性)?</font></b><br>1、消费者消费完消息,但还未提交到kafka更新offset,宕机了,导致offset未变化<br>2、通过数据库唯一约束或者redis唯一键来判断<br>
<b><font color="#e0124f">如何保证消息的顺序性? -- Kafka</font></b><br>-- 一个消费者开多个线程来处理一个Partition里面的顺序消息时,会出现顺序错乱的问题<br><font color="#0b38d9">解决方案:写N个queue,相同的Key的数据放到同一个queue,然后开N个线程,每个线程分别消费其中一个queue</font>。<br>这样就保证了同一个key的消息是顺序消费的,而且也兼顾了吞吐量
<b><font color="#e0124f">有几百万消息持续积压几小时,说说怎么解决? -- Kafka扩容</font></b><br>1、先修复 consumer 的问题,确保其恢复消费速度,然后将现有 consumer 都停掉。<br>2、新建一个 topic,partition 是原来的 10 倍<br>3、写一个临时分发的程序,将积压的消息转发到新的topic里面来<br>4、部署10倍的 consumer 来消费新的topic<br>5、消费完后,切换成原有的架构<br><font color="#0b38d9">注意保证消息幂等性<br></font><b><font color="#e0124f">mq 写满了丢失数据怎么处理?</font></b><br>如消息积压,上面扩容方案没有来得及实施,mq就满了,这时只能丢失部分数据,<br>后面再从业务系统中找出数据后写入mq<br>
<b><font color="#e0124f">如果让你写一个消息队列,该如何进行架构设计?</font></b><br>1、可伸缩(快速扩容)、高吞吐量:Kafka分区机制<br>2、数据安全性:持久化磁盘,顺序读写<br>3、高可用:Kafka多副本 -> leader & follower -> broker 挂了重新选举 leader 即可对外服务<br>4、数据0丢失:kafka 数据零丢失方案<br>
存储中间件
redis
什么是redis?
redis特性?
<b><font color="#c40c0c">redis是单线程的吗?</font></b><br><font color="#210ddb">- Redis 单线程指的是「接收客户端请求->解析请求 ->进行数据读写等操作->发送数据给客户端」这个过程是由一个线程(主线程)来完成的<br>- Redis 启动时,后台还会创建3个BIO线程来分别处理「关闭文件(BIO_CLOSE_FILE)、AOF 刷盘(BIO_AOF_FSYNC)、释放内存(BIO_LAZY_FREE)」这些耗时的任务。<br>- Redis6.0之后,后台还会创建3个线程来协助主线程来处理写请求(默认,也可以通过修改配置使读请求也使用多线程IO)<br><br>//读请求也使用io多线程<br>io-threads-do-reads </font><font color="#e855a4">yes </font><br><font color="#210ddb">// io-threads N,表示启用 N-1 个 I/O 多线程(主线程也算一个 I/O 线程)</font><br><font color="#210ddb">io-threads </font><font color="#e855a4">4</font><br>
redis支持的数据类型?
String 字符串
Hash 哈希<br>
List 列表<br>
Set 集合<br>
ZSet 有序集合
<b><font color="#c40c0c">redis线程模型</font></b>
redis持久化
RDB(Redis DataBase)<br>
RDB触发规则<br>
自动触发<br>
手动触发<br>
bgsave流程
fork()
CopyOnWrite<br>
AOF(Append Only File)
AOF文件格式
开启策略<br>
重写策略<br>
bgRewriteAof流程
redis内存回收
过期策略
<b><font color="#e0124f">定时过期(主动)</font></b><br>-- 在设置key的过期时间的同时,为该key创建一个定时器,让定时器在key的过期时间来临时,对key进行删除<br><font color="#0b38d9">对内存友好、对cpu不友好</font><br>
<b><font color="#e0124f">惰性过期(被动)<br></font></b>-- key过期的时候不删除,每次通过key获取值的时候去检查是否过期,若过期,则删除,返回null。<br><font color="#0b38d9">对cpu友好、对内存不友好;在get或setnx时,先检查key是否过期,过期则先删除再操作</font><br>
<font color="#e0124f"><b>定期过期</b></font><br>-- 每隔一定的时间(默认100ms),会扫描一定数量的数据库的 expires 字典中一定数量(<b><font color="#0b38d9">默认20个</font></b>)的 key,并清除其中已过期的 key<br><font color="#0b38d9">该策略是前两者的一个折中方案<br><ul><li><span style="color:rgb(11, 56, 217); font-size:inherit;">遍历每个数据库(默认16个)</span></li><li><span style="color:rgb(11, 56, 217); font-size:inherit;">随机获取当前库一个设置了过期时间的key,如果已过期,就删除,默认循环20次</span></li><li><span style="color:rgb(11, 56, 217); font-size:inherit;">判断定期删除操作是否已经达到指定时长,若已经达到,直接退出</span></li><li><span style="color:rgb(11, 56, 217); font-size:inherit;">定期删除</span><span style="color:rgb(11, 56, 217); font-size:inherit;">在程序中有一个全局变量currentdb来记录下一个将要遍历的库</span></li></ul></font><font color="#0b38d9"></font>
淘汰策略<br>
LRU策略<br><b><font color="#0b38d9">建议使用All-keys Lru</font></b>
LFU策略<br>
随机策略<br>
TTL策略<br>
noeviction策略(默认)<br>
<b><font color="#e0124f">Redis的LRU</font></b>
redis高可用架构
主从架构
<b><font color="#e0124f">主从数据是如何同步的?</font></b><br>1、slave连上master后(stocket长连接),会发送一个PSYNC命令给master请求复制数据<br>2、master通过bgsave来生成rdb文件,并在开始生成文件的时候,将后面的写命令都缓存到buffer中<br>3、rdb文件生成好后,master将文件send到slave<br>4、slave收到rdb文件后,先清空自己的旧文件,再将新的rdb内容加载到内存中<br>5、master发送buffer,slave执行buffer中的写命令<br>6、master通过stocket长连接,将写命令持续发到slave中,保持数据一致<br>
哨兵架构
<b><font color="#e0124f">服务下线判断</font></b><br>1、Sentinel 默认以<b><font color="#0b38d9">每秒钟 1 次</font></b>的频率向 Redis 服务节点发送 PING 命令<br>2、如果在down-after-milliseconds 内都没有收到有效回复,Sentinel 会将该服务器标记为下线<font color="#0b38d9"><b>(主观下线)</b></font><br>3、Sentinel 节点会继续询问其他的 Sentinel 节点,确认这个节点是否下线,如果多数 Sentinel 节点都认为 master 下线,<br>master 才真正确认被下线<b><font color="#0b38d9">(客观下线)</font></b>,这个时候就需要重新选举 master。<br>
<b><font color="#e0124f">Sentinel 集群的 Leader 选举(Raft算法)</font></b><br>1、master 客观下线触发选举<br>2、每个sentinel每次选举都会自增配置纪元(选举周期),每个纪元中只会选择一个sentinel(<b><font color="#0b38d9">先到先得</font></b>)的leader<br>3、<b><font color="#0b38d9">超过一半</font></b>的sentinel选举某sentinel作为leader<br><font color="#0b38d9">注:sentinel是没有主从是分的,只是在客观下线后,有个leader选举,leader来处理故障转移</font>
<b><font color="#e0124f">故障转移</font></b><br>1、选出 Sentinel Leader 之后,由 Sentinel Leader 从所有从服务器里面,挑选出一个从服务器,并将其转换为主服务器<br>2、让其它从服务器改为从新的主服务器复制数据<br>3、将原主服务器也改为从新的主服务器复制数据
<b><font color="#e0124f">这么多从节点,选谁成为新的主节点?</font></b><br>1、如果与哨兵<b><font color="#0b38d9">断开连接时长</font></b>的比较久,超过了某个阈值,就直接失去了选举权<br>2、如果拥有选举权,那就看谁的<b><font color="#0b38d9">优先级高</font></b>(replica-priority 100),数值越小优先级越高<br>3、优先级相同,就看谁从 master 中<b><font color="#0b38d9">复制的数据最多</font></b>(复制偏移量最大),选最多的那个<br>4、复制数量也相同,就选择<b><font color="#0b38d9">进程 id 最小</font></b>的那个<br>
集群架构
<b><font color="#e0124f">数据怎么相对均匀地分片?</font></b><br>1、hash取模:节点变化后,需要对所有数据进行重新分布<br>2、一致性hash:解决节点变化后,全部数据都要重新分布的问题。引入虚拟节点,解决数据分布不均匀问题<br>3、hash slot:对 key 用 CRC16 算法计算再%16384,得到一个 slot的值<br><b><font color="#0b38d9">注意:key 与 slot 的关系是永远不会变的,会变的只有 slot 和 Redis 节点的关系</font></b><br>
<b><font color="#e0124f">怎么让相关的数据落到同一个节点上?</font></b><br>-- 在 key 里面加入{hash tag}即可。Redis 在计算槽编号的时候只会获取{}之间的字符串进行槽编号计算<br>如:127.0.0.1:7293> set a{qs}a 1<br>
<b><font color="#e0124f">客户端连接到哪一台服务器?访问的数据不在当前节点上,怎么办?</font></b><br>-- 客户端操作的key不在当前连接的Redis服务器上,会返回一个MOVED,且含有Key所有服务器地址,重定向连接新服务器即可<br>如:127.0.0.1:7291> set qs 1<br>(error) MOVED 13724 127.0.0.1:7293<br>Jedis 等客户端会在本地维护一份 slot——node 的映射关系,大部分时候不需要重定向,所以叫做 smart jedis(需要客户端支持)。<br>
<b><font color="#e0124f">新增或下线了 Master 节点,数据怎么迁移(重新分配)?</font></b><br>-- 需要把原有的 slot分配给新的节点负责,并且把相关的数据迁移过来<br>如:redis-cli --cluster add-node 127.0.0.1:7291 127.0.0.1:7297<br>redis-cli --cluster reshard 127.0.0.1:7291<br>
<b><font color="#e0124f">Redis集群选举原理分析</font></b><br>当slave发现自己的master变为FAIL状态时,便尝试进行Failover,以期成为新的master。由于挂掉的master可能会有多个slave,<br>从而存在多个slave竞争成为master节点的过程,其过程如下:<br>1、slave发现自己的master变为FAIL<br>2、将自己记录的集群currentEpoch加1,并广播FAILOVER_AUTH_REQUEST信息<br>3、其他节点收到该信息,只有master响应,判断请求者的合法性,并发送FAILOVER_AUTH_ACK,对每一个epoch只发送一次ack<br>4、尝试failover的slave收集master返回的FAILOVER_AUTH_ACK<br>5、slave收到<b><font color="#0b38d9">超过半数master的ack</font></b>后变成新Master(这里解释了集群为什么至少需要三个主节点,如果只有两个,当其中一个挂了,只剩一个主节点是不能选举成功的)<br>6、slave广播Pong消息通知其他集群节点<br>
<b><font color="#e0124f">生产环境部署情况?</font></b><br>1、集群部署,3主3从,32G 内存+ 8 核 CPU + 1T 磁盘,redis只占用10g<br>2、用户数据,商品数据,库存数据
常见问题
<b><font color="#e0124f">为什么要用缓存?</font></b><br>-- 高性能(通过复杂耗时计算的结果,如果访问多,变动小,可以放缓存)<br>-- 高并发(内存天然支持高并发)<br><b><font color="#e0124f">用了缓存之后会有什么不良后果?</font></b><br>-- 缓存雪崩、缓存穿透、缓存击穿、缓存与数据库双写不一致<br>
<b><font color="#e0124f">Redis为什么会这么快?</font></b><br>1、纯内存中操作<br>2、单线程(避免创建销毁线程的开销、避免上下文切换、避免多线程的资源竞争引发的锁问题)<br>3、IO多路复用<br><font color="#0b38d9">注意:<br>-- 根据官方的数据,Redis 的 QPS 可以达到 10 万左右(每秒请求数)。<br>-- cpu并不会成为redis的瓶颈,redis的瓶颈最有可能是机器内存及网络带宽。</font><br>
<b><font color="#e0124f">如何保证缓存与数据库双写时的数据一致性?</font></b><br><b><font color="#0b38d9">-- 先更新数据库,再删除缓存,再配合删除缓存重试机制(mq或者binlog)(可能短暂时间数据不一致)</font></b><br>1、先更新数据库,再更新缓存(多写高并发时,无法保证顺序,存在数据库缓存数据不一致的情况;存在浪费性能的情况)<br>2、先删除缓存,再更新数据库(读写高并发时,存在数据库缓存数据不一致的情况)<br>3、<b><font color="#0b38d9">先删除缓存,再更新数据库,最后延时删除缓存(存在延长时间不好确认的情况)</font></b><br>
<b><font color="#e0124f">什么是缓存穿透?</font></b><br>-- 单一热点key,缓存、数据库均不存在<br><b><font color="#e0124f">解决方案是什么?</font></b><br>-- 缓存空对象并设置过期时间(缺点是浪费内存存储空对象,且会导致数据库与缓存数据不一致)<br>-- 布隆过滤器(缺点是没法删除元素,会影响其它key)<br><font color="#0b38d9"><b>注:布隆过滤器判断不存在即一定不存在,但判断存在,不一定代表存在</b></font>
<b><font color="#e0124f">什么是缓存击穿?</font></b><br>-- 单一热点key,缓存数据失效<br><b><font color="#e0124f">解决方案是什么?</font></b><br>-- 设置key永不过期<br>-- 使用分布式锁<br><font color="#0b38d9">查询key失效时,对这个key进行加锁,只允许其中一个查询拿到锁直接查询数据库(其它的自旋等待),<br>等结果查询出来,存储到缓存,就释放锁,其它查询就可以直接从缓存获取了</font><br>
<b><font color="#e0124f">什么是缓存雪崩?</font></b><br>-- 大量热点key,缓存数据同时失效<br><b><font color="#e0124f">解决方案是什么?</font></b><br>-- 设置key永不过期<br>-- 将key的过期时间打乱<br>-- 在redis前面添加一个本地缓存<br>-- 限流组件,限制每秒请求数,未通过的请求走降级<br>-- 高可用集群
monogdb
ES(ElasticSearch)<br>
概念
<b><font color="#e0124f">index(索引)<br></font></b>es 中存储数据的基本单位是索引,类似于表<br>
<b><font color="#e0124f">document(文档)<br></font></b>document就相当于表中的一条记录<br>
<b><font color="#e0124f">field(列)<br></font></b>field相当于表中的字段
<b><font color="#e0124f">mapping(映射)<br></font></b>相当于表结构
<b><font color="#e0124f">shard(分片)<br></font></b>类似于kafka中的partition分片,一般来说,shard最好在一个机房
<b><font color="#e0124f">replica(副本)<br></font></b>类似于kafka中的follow副本
<b><font color="#c40c0c">primary(主副本)<br></font></b>类似于kafka中的leader副本<br>
<b><font color="#e0124f">node(节点)<br></font></b>类似于kafka中的节点服务器broke
<b><font color="#e0124f">master<br></font></b>类似于kafka中的controller,选举出来的,<b><font color="#0b38d9">master选举采用Raft算法</font></b><br>
中文分词器
IK分词器
HanL分词器
<b><font color="#e0124f">什么是倒排索引?倒排索引是如何查找到数据的?<br></font></b>-- 简单说倒排索引就是通过value找key<br>
<b><font color="#e0124f">es 写入数据的流程?</font></b><br>1、客户端将写请求发送到任意节点,这个节点就变成了coordinating node(协调节点)<br>2、协调节点就会路由(hash取模),将请求转发到primary shard所在的datanode(数据节点)<br>3、实际的 node 上的 primary shard 处理请求,然后将数据同步到 replica node(副本节点)<br>4、协调节点发现 primary node 和所有 replica node 都保存好后,就返回响应结果给客户端<br>
<b><font color="#e0124f">es 读取数据的过程?<br></font></b>1、客户端将读请求发送到任意节点,这个节点就变成了coordinating node(协调节点)<br>2、协调节点通过对doc id 进路由(hash),将请求转发到对应的node(通过<b><font color="#0b38d9">随机轮询算法</font></b>,将请求转发到primary shard或者replica shard)<br>3、实际的 node 处理读请求,将结果返回给协调节点<br>4、协调节点将数据返回给客户端<br><b><font color="#0b38d9">注意:primary shard负责读写,replica shard负责读</font></b>
<b><font color="#e0124f">es 搜索数据过程?<br></font></b>1、客户端将请求发送到任意节点,这个节点就变成了coordinating node(协调节点)<br>2、协调节点将请求广播到所有数据节点,这些数据节点上的分片就开始处理查询请求<br>3、每个分片将查询的结果(包含文档ID,节点信息,分片信息)放在一个队列当中,返回给协调节点<br>4、协调节点将所有结果进行汇总,排序<br>5、协调节点再根据这些文档ID,去各个分片中拉取实际的文档数据,返回给客户端<br>
<b><font color="#e0124f">es 写数据底层原理?<br></font><font color="#0b38d9">-- master选举采用Raft算法</font></b>
<b><font color="#e0124f">es 性能优化方案?<br></font></b>-- 增加filesystem cache大小<br>-- 数据预热,将热点数据预热到filesystem cache中<br>-- 冷热分离,冷数据写入一个索引中,然后热数据写入另外一个索引中,尽量让热点数据保留在filesystem cache中<br>
常见问题
Redis与MySQL双写一致性如何保证?<br>
延时双删策略
删除缓存重试机制<br>
读取biglog异步删除缓存
为什么要用三级缓存架构?
Ngnix缓存
本地缓存
Redis缓存
秒杀超卖 解决方案?
<b><font color="#e0124f">设计一个线程安全且带有过期时间的LRU缓存?<br></font></b>LRU,全称Least Recently Used,最近最少使用缓存。<br>-- ConcurrentHashMap 存储数据(K, V)<br>-- ConcurrentLinkedQueue 存储key,FIFO,链表头就是最近未使用过的key,链表尾是最新数据<br>-- ScheduledExecutorService 实现到期清除过期key<br><br><b><font color="#0b38d9">注:HashMap + 链表 + 定时器</font></b>
微服务
Dubbo
Spring Boot
Spring Cloud Alibaba<br>
Nacos 服务注册与发现
<b><font color="#e0124f">服务注册</font></b><br>服务提供者(Nacos Client)会通过Rest请求,向Nacos Server注册自己的服务<br>
<b><font color="#e0124f">服务心跳</font></b><br>Nacos Client会维护一个定时心跳(<b><font color="#0b38d9">默认5秒</font></b>)来持续通知Nacos Server,更新Nacos Server中服务实例的一个时间属性为当前时间<br><b><font color="#0b38d9">临时节点才适用,非临时节点不上报</font></b>
<b><font color="#e0124f">服务健康检查</font></b><br>临时节点(默认):Nacos Client<b><font color="#0b38d9">每5秒</font></b>上报健康状况,Nacos Server会开启一个定时任务(<b><font color="#0b38d9">5秒</font></b>)用来检查注册服务实例的健康情况,超过<b><font color="#0b38d9">15秒</font></b>未更新的就设置成不健康状态(消费者看不到该实例了),超过<b><font color="#0b38d9">30秒</font></b>就剔除服务<br>非临时节点:Nacos Server主动探测(<b><font color="#0b38d9">TCP</font></b>),<b><font color="#0b38d9">每20秒</font></b>探测一次,若探测不到,认为服务异常,但是不会剔除服务。
<b><font color="#e0124f">服务发现</font></b><br>服务调用者(Nacos Client)在调用服务时,会发送一个REST请求给Nacos Server,获取上面注册的服务清单,并且缓存在Nacos Client本地,<br>同时会在Nacos Client本地开启一个定时任务(<b><font color="#0b38d9">默认30秒</font></b>)定时拉取服务端最新的注册表信息更新到本地缓存。<br>当Nacos Server发现服务异常时,也会通过<b><font color="#0b38d9">UDP</font></b>的方式立即推送给服务调用者<br>
<b><font color="#e0124f">服务数据同步(一致性)</font></b><br>cp模式:一般采用raft协议进行同步<br>ap模式:进行异步批量同步,有两个条件,当实例数达到1000条或者当前时间跟上一次同步的时间大于2秒时,进行同步<br>
<b><font color="#e0124f">Nacos是AP架构还是CP架构?</font></b><br>
<b><font color="#e0124f">AP架构(默认)</font></b><br>1、集群节点之间的数据同步是异步同步的<br>2、服务列表信息不会持久化到本地,只会存在内存中<br><b><font color="#0b38d9">临时节点</font></b>
<b><font color="#e0124f">CP架构</font></b><br>1、利用raft协议保证集群节点间的数据一致性及节点之间的选举<br>2、数据会持久化到本地,可以防止数据丢失<br><b><font color="#0b38d9">非临时节点</font></b>
<b><font color="#e0124f">Nacos的服务注册表结构是怎样的?</font></b><br>1、Nacos采用了双层Map来存储元数据<br>2、Map<nameSpaceId, Map<group::serverName, serviceInstance>>
<b><font color="#e0124f">Nacos如何支撑阿里内部数十万服务注册压力?</font></b><br>Nacos内部接收到注册的请求时,不会立即写数据,而是将服务注册的任务放入一个阻塞队列就立即响应给客户端。<br>然后利用线程池读取阻塞队列中的任务,异步来完成实例更新,从而提高并发写能力。<br>
<b><font color="#e0124f">Nacos如何避免并发读写冲突问题?</font></b><br>Nacos在更新实例列表时,会采用CopyOnWrite技术,首先将旧的实例列表拷贝一份,然后更新拷贝的实例列表,<br>再用更新后的实例列表来覆盖旧的实例列表。<br>1、复制旧注册表List实例数据,形成新的注册表List<br>2、新增,修改,删除操作都在新List里面完成<br>3、操作完成后,使用新List覆盖旧List<br><ul><li><font color="#0b38d9"><b>读写锁是遵循写写互斥、读写互斥、读读不互斥的原则,</b></font></li><li><font color="#0b38d9"><b>而copyOnWrite则是写写互斥、读写不互斥、读读不互斥的原则。</b></font></li></ul>
<b><font color="#e0124f">Nacos中的保护阈值的作用是什么?</font></b><br>1、保护阈值:当前服务健康实例数/当前服务总实例数,0-1之间的浮点数<br>2、正常情况下,Nacos会返回消费者所有健康的实例。当健康实例占比过低时,所有请求都会让剩余健康实例来处理,<br>导致压力过大,最后可能宕机,造成雪崩效应。保护阈值就是为了防止出现这种情况而设置的。<br>3、当健康实例占比降低到保护阈值以下时,Nacos会返回所有实例给消费者(健康和不健康的),让消费者的部分请求<br>会请求到不健康的实例上,牺牲部分请求,避免服务不可用<br>
<b><font color="#e0124f">Nacos与Eureka的区别有哪些?</font></b><br><ul><li>接口方式:Nacos与Eureka都对外暴露了Rest风格的API接口,用来实现服务注册、发现等功能</li><li>实例类型:Nacos的实例有永久和临时实例之分;而Eureka只支持临时实例</li><li>健康检测:Nacos对临时实例采用心跳模式检测,对永久实例采用主动请求来检测;Eureka只支持心跳模式</li><li>服务发现:Nacos支持定时拉取和订阅推送两种模式;Eureka只支持定时拉取模式</li></ul>
<b><font color="#e0124f">相比Nacos1.X,Nacos2.X为什么这么快?解决了哪些问题?</font></b><br>Nacos1.X存在的问题:<br>1、心跳数量多,导致 TPS 居高不下<br>2、通过心跳续约感知服务变化,时延长<br>3、UDP 推送不可靠,导致 QPS 居高不下(<font color="#0b38d9">主动推送,配置中心</font>)<br>4、基于 HTTP 短连接模型,TIME_WAIT 状态连接过多(<font color="#0b38d9">客户端请求</font>)<br>5、配置模块的 30 秒长轮询引起的频繁 GC(<font color="#0b38d9">配置中心实时感知变化</font>)<br><b><font color="#0b38d9">Nacos2.X通过了gRPC实现长连接,解决了上面问题,底层NIO</font></b>
Nacos 配置中心<br>
LoadBalancer 客户端负载均衡器<br>
Ribbon 客户端负载均衡<br>
OpenFeign 声明式服务调用<br>
Sentinel 限流降级熔断<br>
限流算法<br>
固定窗口算法
<font color="#e0124f"><b>滑动窗口算法</b></font><br>
漏桶算法<br>
令牌桶算法
Sentinel 对比 Hystrix?
Seata 微服务分布式事务<br>
Gateway 统一网关<br>
Skywalking 链路追踪组件<br>
Spring Security OAuth2 微服务安全<br>
Spring Cloud
Eureka 服务注册与发现<br>
<b><font color="#e0124f">服务注册</font></b><br>服务提供者(Eureka Client)会通过HTTP请求,向Eureka Server注册自己的服务
<b><font color="#e0124f">服务续约与剔除</font></b><br>Eureka Client默认<b><font color="#0b38d9">每隔30秒</font></b>发送一次心跳请求到Eureka Server,如果Eureka Server<b><font color="#0b38d9">90秒</font></b>还未收到请求,则将该实例从注册表中剔除<br>
<b><font color="#e0124f">服务自我保护机制</font></b><br>当Eureka Server 节点在短时间内(<b><font color="#0b38d9">默认15分钟</font></b>)丢失了过多(<b><font color="#0b38d9">默认85%</font></b>)实例的连接时(比如网络故障或频繁启动关闭客户端)节点会进入自我保护模式。<br>这种模式下,<font color="#0b38d9">Eureka仍然能够接受新服务的注册和查询请求,但是不再删除注册数据,且不会被同步到其他节点(高可用)</font>,当网络稳定时,自动退出自我保护模式,当前实例新的注册信息会被同步到其他节点中(最终一致性)。
<b><font color="#e0124f">服务下线</font></b><br>Eureka Client正常或者主动关闭时,会向Eureka Server发送下线请求,Eureka Server直接从注册表中剔除该服务实例<br><font color="#0b38d9">做灰度发布等正常关闭服务的操作时,如依靠服务剔除,会有一定的延时时间,这时可采用服务下线</font>
<b><font color="#e0124f">服务发现</font></b><br>1、Eureka Client在启动时会从Eureka Server中获取注册表信息,并将其缓存在本地<br>2、Eureka Client默认<b><font color="#0b38d9">每隔30秒</font></b>去找Eureka Server拉取最近注册表的变化,并保存在本地注册表中<br>
<b><font color="#e0124f">Eureka为什么能够抗住高并发?</font></b><br><font color="#0b38d9">一、维护注册表、拉取注册表、更新心跳时间,全部发生在内存里</font><br>注册表数据结构:new ConcurrentHashMap(String, Map<String, Lease<InstanceInfo>)<br>1、这个ConcurrentHashMap的key就是服务名称,比如说“inventory-service”<br>2、value:Map<String, Lease<InstanceInfo>则代表了一个服务的多个服务实例<br>3、这个Map的key就是服务实例的id<br>4、InstanceInfo就代表了服务实例的具体信息,比如机器的ip地址、hostname以及端口号<br>5、Lease,里面则会维护每个服务最近一次发送心跳的时间<br><font color="#0b38d9">二、Eureka采用多级缓存机制来进一步提升服务请求的响应速度(具体见下)</font>
<b><font color="#e0124f">Eureka是如何避免并发读写冲突的? -- 多级缓存</font></b><br>Eureka Server为了避免同时读写内存数据结构造成的并发冲突问题,还采用了多级缓存机制来进一步提升服务请求的响应速度。<br>拉取注册表:<br>1、首先从ReadOnlyCacheMap里查缓存的注册表<br>2、如果没有,就找ReadWriteCacheMap里缓存的注册表<br>3、如果还没有,就从内存中获取实际的注册表数据<br>注册表发生变更:<br>1、会在内存中更新变更的注册表数据,同时过期掉ReadWriteCacheMap<br>2、在一段时间内,<b><font color="#0b38d9">默认是30秒</font></b>,各个服务拉取注册表数据都会直接读ReadOnlyCacheMap<br>3、在30秒过后,Eureka Server的后台线程发现ReadWriteCacheMap已经清空了,那么也会清空ReadOnlyCacheMap中的缓存<br>4、下次有服务拉取注册表,又会从内存中获取最新的数据了,同时填充各个缓存<br>
Eureka服务注册中心是不保证数据一致性的(只保存最终一致性),<b><font color="#0b38d9">属于AP</font></b>,因为数据同步存在延时,并且全部对外提供服务<br>Eureka,是让各个客户端都必须每隔一定时间发送心跳到Eureka Server,属于集中式心跳机制<br>集群中每个Eureka实例都是对等的。每个Eureka实例都包含了全部的服务注册表<br>
<b><font color="#e0124f">Eureka存在哪些缺点?</font></b><br>1、超大规模集群时,可能存在单机内存不足的情况<br>2、超大规模集群时,服务注册中心无法保证数据一致性<br>3、所有的Eureka Client都默认每隔30秒向Eureka Server发送一次心跳请求,给Eureka Server压力太大
<b><font color="#e0124f">Eureka和ZooKeeper都可以提供服务注册与发现的功能,请说说两个的区别?</font></b><br>1、ZooKeeper保证的是CP,Eureka保证的是AP<br>2、ZooKeeper在选举期间注册服务瘫痪,虽然服务最终会恢复,但是选举期间不可用的<br>3、Eureka各个节点是平等关系,只要有一台Eureka就可以保证服务可用,而查询到的数据并不是最新的<br>4、ZooKeeper有Leader和Follower角色,Eureka各个节点平等<br>5、ZooKeeper采用过半数存活原则,Eureka采用自我保护机制解决分区问题<br>
<b><font color="#e0124f">Eureka如何保证高可用与数据一致?</font></b><br>1、Eureka通过集群保证高可用,使用AP来保证最终一致性<br>2、Eureka会区分正常的客户端请求与服务端发起的数据同步请求,对于任何服务端发起的数据同步请求,<br>Eureka不会再进行其他同步操作,从而避免数据同步出现死循环<br>3、Eureka会使用版本号(时间戳)来区分新旧数据<br>4、客户端定时拉取数据时,也会检测数据,与本地数据冲突,会重新发起注册请求<br>
Consul服务注册与发现
<b><font color="#e0124f">Consul注册中心架构设计是什么样的?</font></b><br>1、必须在每个服务所在的机器上部署一个Consul Agent,作为一个服务所在机器的代理<br>2、还得在多台机器上部署Consul Server,这就是核心的服务注册中心,一般要求部署3~5台机器,以保证高可用<br>3、Consul Agent可以用来收集你的服务信息然后发送给Consul Server,还会对你的服务不停的发送请求检查他是否健康<br>4、Consul Agent也会帮你转发请求给Consul Server,查询其他服务所在机器,方便直接调用其它服务<br>5、Consul Server集群会自动选举出一台机器作为leader,对外提供服务,其他的Consul Server就是follower,leader会自动同步数据给follower<br>
<b><font color="#e0124f">Consul是如何通过Raft协议实现数据一致性的?</font></b><br>1、Leader有最新的数据,并且只有Leader对外提供服务<br>2、Leader收到注册信息后,必须将数据同步到大部分follower后才会算成功<br>3、即便Leader服务宕机后,也会有其它follower有最新的数据,重新选举出新Leader,对外提供服务<br>
<b><font color="#e0124f">Consul 是如何进行leader选举的?</font></b><br>1、Create Session:参与选举的应用分别去创建Session,Session的存活状态关联到健康检查<br>2、Acquire KV:多个应用带着创建好的Session去锁定同一个KV,只能有一个应用锁定住,锁定成功的应用就是leader。<br>
<b><font color="#e0124f">Consul如何通过Agent实现分布式健康检查?</font></b><br>每个机器上的Consul Agent会不断的发送请求检查服务是否健康,是否宕机。如果服务宕机了,那么就会通知Consul Server。这样可以大幅度的减小Server端的压力。<br>
Ribbon 客户端负载均衡<br>
Fegin 声明式服务调用<br>
Hystrix 实现服务限流,降级,熔断<br>
Zuul 统一网关详解,服务路由,过滤器使用<br>
Config 分布式配置中心<br>
Sleuth 分布式链路跟踪<br>
相关工具
Docker
架构
命令
Docker run流程图
Docker底层原理
Docker 挂载
Docker 网络
Dockerfile
Docker Compose
Nginx
k8s
Jenkins
数据库
mysql
索引
数据结构
二叉数
红黑树
B树
<font color="#e0124f"><b>B+树</b></font><br>
hash表
Explain执行计划字段列<br>
<b><font color="#e0124f">mysql索引的最佳实践?</font></b><br>1、全值匹配<br>2、最左前缀法则(联合索引)<br>3、索引列不做函数<br>4、尽量索引覆盖,减少 select * 语句<br>5、不使用!=,not in,is null , is not null等<br>6、字符串要加单引号
<b><font color="#e0124f">mysql为什么采用B+树而不是B树?</font></b><br>1、相对于B数,B+树具有更少的层高(单一节点存储更多的元素),从而减少IO次数<br>2、只有叶子节点存储数据,非叶子节点不存储数据,一次可以加载更多数据<br>3、叶子节点是单向有序链表(mysql对B+树再次优化,使用的是双向有序链表),可高效地支持范围查找<br>
<b><font color="#e0124f">mysql为什么不使用哈希索引、平衡二叉树索引、B树索引?</font></b><br>1、hash索引支持等值查询,不适用于范围查询<br>2、平衡二叉树插入更新操作,维护平衡代价太大,且层级太高,IO次数过多<br>3、B+树相对于B树更具有优势,见上题
<b><font color="#e0124f">什么是聚簇索引、非聚簇索引、联合索引、索引覆盖、索引下推?</font></b><br>1、索引覆盖:直接在索引树中拿到结果,不用回表查询聚簇索引的情况(可以减少回表次数,减少IO)<br>2、索引下推:在使用联合索引时,通过减少回表的次数,来提高数据库的查询效率
数据库锁
按<b><font color="#140cf2">操作的粒度</font></b>分类
<b><font color="#c40c0c">表锁(MYISAM与INNODB支持)</font></b><br>表锁开销小,加锁快,无死锁,锁定粒度大,发生锁冲突的概率最高,并发最低<br>
<b><font color="#c40c0c">行锁(InnoDB默认)</font></b><br>行锁开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高<br><font color="#140cf2"><b>-- InnoDB是基于索引来完成行锁<br>-- </b></font><b><font color="#140cf2">索引未生效或查询条件没有建立索引,会导致行锁变表锁</font></b><br>
<b><font color="#c40c0c">页锁(BDB支持)</font></b><br>开销和加锁时间界于表锁和行锁之间:会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般<br>
<b><font color="#e00707">行锁</font></b>的算法
<b><font color="#c40c0c">Record Lock(记录锁)<br></font><font color="#1808f5">锁住一条索引记录</font><font color="#c40c0c"><br></font></b><font color="#1808f5">例:10,20</font><br>
<b><font color="#c40c0c">Gap Lock(间隙锁) -- 解决当前读的幻读问题<br></font><font color="#e00707">注:只发生在事务隔离级别为RR(Repeatable Read)的情况下,所以RC才会有幻读的问题</font><br><font color="#1808f5">不包含索引本身的开区间范围 (index1,index2)</font><font color="#140cf2"><br></font></b><font color="#1808f5">例:(negative infinity, 10),(10, 20),(20, positive infinity)</font><br>
<b><font color="#c40c0c">Next-key Lock(临键锁)</font><font color="#c4083a">-- 解决当前读的幻读问题</font><font color="#c40c0c"><br></font><font color="#140cf2">InnoDB默认,Record Lock + Gap Lock<br></font><font color="#e00707">注:只发生在事务隔离级别为RR(Repeatable Read)的情况下,所以RC才会有幻读的问题</font><font color="#140cf2"><br></font></b><font color="#1808f5"><b>左开右闭 ,除了最后一个锁区间,是全开区间</b></font><b><font color="#140cf2"><br></font></b><font color="#1808f5">例:(negative infinity, 10],(10, 20],(20, positive infinity)</font><br>
唯一索引等值查询<br>- 当查询的记录是存在的,临键锁退化成记录锁<br>select * from t_index where id = 16 for update;<br>加锁范围:(8, 16] -> 16<br>- 当查询的记录是不存在的,临键锁退化成间隙锁<br>select * from t_index whereid = 10 for update;<br>加锁范围:(8, 16] -> (8, 16)<br>
唯一索引范围查询<br><br>select * from t_index where id >= 8 and id < 10 for update;<br>加锁范围:(4, 8] -> 8 && (8, 16)<br>
非唯一索引等值查询<br>
非唯一索引范围查询
按<b><font color="#140cf2">操作的类型</font></b>分类<br>
<b><font color="#c40c0c">读锁(共享锁,Share Lock)</font></b><br>若事务T对数据对象A加上读锁,则事务T只能读A;其他事务只能再对A加读锁,而不能加写锁,直到事务T释放A上的读锁<br>
<font color="#c40c0c"><b>写锁(排它锁,Exclusive Lock)</b></font><br>若事务T对数据对象A加上写锁,则只允许事务T读取和修改A,其它任何事务都不能再对A加任何类型的锁,直到T释放A上的锁<br>
按<b><font color="#140cf2">锁思想</font></b>分类
<font color="#c40c0c"><b>悲观锁</b><br></font><b><font color="#140cf2">-- </font></b><b><font color="#140cf2">一种用来解决</font></b><b><font color="#140cf2"> </font><font color="#f50af5">读-写冲突</font><font color="#140cf2"> 和 </font><font color="#f50af5">写-写冲突 </font></b><b><font color="#140cf2">的加锁并发控制(</font><font color="#c4083a">利用数据库锁机制</font><font color="#140cf2">)</font></b><b><font color="#140cf2"><br></font></b>-- <b><font color="#140cf2">可以解决脏读,幻读,不可重复读,更新丢失的问题</font></b><br>
<b><font color="#c40c0c">乐观锁</font></b><br>-- <b><font color="#140cf2">是一种用来解决 </font><font color="#f50af5">写-写冲突</font><font color="#140cf2"> 的无锁并发控制</font><font color="#140cf2"><br>-- 方式一:使用数据版本(version)实现<br>-- 方式二:使用时间戳(timestamp)实现<br>-- 无法解决脏读,幻读,不可重复读,但是可以解决更新丢失问题</font></b><br>
<b><font color="#c40c0c">MVCC(多版本并发控制)<br></font><font color="#140cf2">-- 是一种主要解决 </font><font color="#f50af5">读-写冲突 </font><font color="#140cf2">的无锁并发控制<br>-- 可以解决脏读,幻读(</font><font color="#e855a4">RR级别</font><font color="#140cf2">),不可重复读等事务问题,但不可以解决更新丢失问题</font></b>
<b><font color="#c40c0c">MVCC + 悲观锁</font></b><br>MVCC解决读写冲突,悲观锁解决写写冲突<br>
<font color="#c40c0c"><b>MVCC + 乐观锁</b></font><br>MVCC解决读写冲突,乐观锁解决写写冲突<br>
MVCC
3个隐式字段(DB_ROW_ID,DB_TRX_ID,DB_ROLL_PTR)<br>
undo日志版本链<br>
Read View(一致性视图)<br>
数据库事务<br>
隔离级别
<b><font color="#c40c0c">读取未提交(READ-UNCOMMITTED)</font></b><br>含义:允许读取尚未提交的数据变更<br>与锁关系:读取数据不需要加共享锁,这样就不会跟被修改的数据上的排他锁冲突<br>问题:脏读、幻读或不可重复读<br>
<b><font color="#c40c0c">读取已提交(READ-COMMITTED)</font></b><br>含义:允许读取并发事务已经提交的数据<br>与锁关系:读操作需要加共享锁,但是在语句执行完以后释放共享锁<br>问题:幻读或不可重复读<br>
<b><font color="#c40c0c">可重复读(REPEATABLE-READ)</font></b><br>含义:对同⼀字段的多次读取结果都是⼀致的,除⾮数据是被本身事务⾃⼰所修改<br>与锁关系:读操作需要加共享锁,但是在事务提交之前并不释放共享锁,也就是必须等待事务执行完毕以后才释放共享锁。<br>问题:幻读<br><b><font color="#140cf2">InnDB默认隔离级别,通过MVCC来实现可重复读,</font><br><font color="#e00707">在RR级别下,通过MVCC解决快照读的幻读问题,通过临间锁解决当前读的幻读问题</font></b><br>
<b><font color="#c40c0c">可串⾏化(SERIALIZABLE)</font></b><br>含义:所有的事务依次逐个执⾏,这样事务之间就完全不可能产⽣⼲扰<br>与锁关系:该级别锁定整个范围的键,并一直持有锁,直到事务完成<br>问题:性能差<br>
并发事务处理带来的问题<br>
<b><font color="#c40c0c">脏读(DirtyReads)</font></b><br>
<b><font color="#c40c0c">更新丢失(LostUpdate)或脏写</font></b>
<b><font color="#c40c0c">不可重复读(Unrepeatableread)</font></b><br>
<b><font color="#c40c0c">幻读(Phantom read)</font></b>
mysql读写分离
<b><font color="#e0124f">mysql如何实现读写分离?</font></b><br>就是基于主从复制架构,简单来说,就搞一个主库,挂多个从库,然后我们就单单只是写主库,<br>然后主库会自动把数据给同步到从库上去。<br>
<b><font color="#e0124f">mysql 主从复制原理的是啥?</font></b><br>1、主库进行写表操作后,还会将数据写入binlog日志文件(需要打开bin-log功能)<br>2、从库(IO线程)从主库拉取binlog日志(主库上的操作是由IO线程-binlog dump线程完成的),将内容写到relay log的末端,并将读取到的binlog文件名和位置记录到master-info文件中,以便下一次读取的时候,知道从哪里开始读取。主库返回信息除了日志内容外,还会有binlog文件的名称和binlog位置。<br>3、从库的SQL线程检测到relay日志有新增后,会将新增的内容执行,写入从库中
<b><font color="#e0124f">如何解决主从库同步延时问题?</font></b><br>1、<b><font color="#0b38d9">半同步复制</font></b>:主库必须要等到数据同步到从库(至少一个)的relay log后才算成功<br>2、<b><font color="#0b38d9">并行复制</font></b>:从库多个SQL线程同时读取relay,写入到从库中<br>3、<b><font color="#0b38d9">分库</font></b>:将主库分成多个库,减少并发量
分库分表
<b><font color="#e0124f">为什么要分库分表?</font></b><br>分库:是为了减少数据库的并发压力和减少磁盘使用率<br>分表:是为了提高SQL执行效率<br>
<b><font color="#e0124f">有哪些分库分表中间件?各自优缺点?</font></b>
<b><font color="#e0124f">sharding-jdbc</font></b><br>优点:这种client 层方案的优点在于不用部署,运维成本低,不需要代理层的二次转发请求,性能很高<br>缺点:如果遇到升级啥的需要各个系统都重新升级版本再发布,各个系统都需要耦合sharding-jdbc 的依赖<br><font color="#0b38d9">中小公司使用</font><br>
<b><font color="#e0124f">mycat</font></b><br>优点:这种proxy 层方案的优点在于对于各个项目是透明的,如果遇到升级之类的都是自己中间件那里搞就行了<br>缺点:需要部署,自己运维一套中间件,运维成本高<br><font color="#0b38d9">大公司使用</font><br>
<b><font color="#e0124f">如何对数据库进行分库分表?</font></b>
<font color="#e0124f"><b>垂直拆分</b></font><br>把一个有很多字段的表给拆分成多个表,或者是多个库上去。一般来说,访问频率高的字段放一张表,访问频率低的字段放一张表<br>
<b><font color="#e0124f">水平拆分</font></b><br>把一个表的数据给弄到多个库的多个表里去,但是每个库的表结构都一样,只不过每个库表放的数据是不同的,所有库表的数据加起来就是全部数据<br>
<b><font color="#e0124f">水平拆分 -- range</font></b><br>一种是按照range 来分,就是每个库一段连续的数据,这个一般是按比如时间范围来的。<br>优点:扩容时很简单,因为你只要预备好,给每个月都准备一个库就可以了,到了一个新的月份的时候,自然而然,就会写新的库了。<br>缺点:很容易产生热点问题,因为大部分的请求,都是访问最新的数据<br>
<b><font color="#e0124f">水平拆分 -- hash</font></b><br>按照某个字段hash 一下均匀分散,这个较为常用。<br>优点:可以平均分配每个库的数据量和请求压力<br>缺点:扩容起来比较麻烦,会有一个数据迁移的过程,之前的数据需要重新计算hash 值重新分配到不同的库或表<br>
<b><font color="#e0124f">如何将未分库分表的系统切换成分库分表的系统?</font></b><br>
<b><font color="#e0124f">停机迁移方案</font></b>
<b><font color="#e0124f">双写迁移方案</font></b><br>1、所有写表的地方,老库和新库(通过中间件)都写<br>2、同步历史数据,但是不允许覆盖新库的数据,除非旧库的数据比新库更新<br>3、将程序切换成分库分表的最新代码即可<br>
<b><font color="#e0124f">分库分表如何动态扩容</font></b>
<b><font color="#e0124f">分库分表的主键ID如何生成?</font></b>
<b><font color="#e0124f">利用单台数据库自增主键</font></b><br>可适用:<b><font color="#0b38d9">并发不高,数据量大</font></b><br>不适用:并发高,数据量大
<b><font color="#e0124f">利用Redis生成ID<br></font></b>-- <b><font color="#0b38d9">使用Redis集群(如5台),每台步长一样,使用原子操作INCR来增加</font></b><br>优点:性能高,单机顺序递增<br>缺点:不能再扩展,但5台已经够使用了
<b><font color="#e0124f">Twitter的snowflake算法<br></font></b>生成一个64位的long型的ID<br>-- 1 bit 恒为0,保证生成的ID为正整数<br>-- 41 bit 作为日期毫秒数 - 41位的长度可以使用69年<br>-- 10 bit 作为机器编号 (5个bit是数据中心,5个bit的机器ID) - 10位的长度最多支持部署1024个节点<br>-- 12 bit 作为毫秒内序列号。12位的计数顺序号支持每个节点每毫秒产生4096个ID序号<br>优点:分布式,性能高,单机递增<br>缺点:分布式环境,可能由于服务器时间不一致,<b><font color="#0b38d9">导致生成的ID不是全局递增</font></b>;<b><font color="#0b38d9">时钟回拨</font></b>,就是服务器上的时间突然倒退回之前的时间<br><b><font color="#0b38d9">注:时钟回拨解决方案<br></font></b><font color="#0b38d9">1、回拨时间小的时候,不生成 ID,循环等待到时间点到达<br>2、可以提前在机器ID或者序列号中留出拓展位置0,当出现时钟回拨时,将拓展位置1,这样也可以保证生成ID的唯一性。</font><br>
常见问题
<b><font color="#e0124f">MyISAM与InnoDB 的区别?</font></b><br>
<b><font color="#e0124f">mysql的查询SQL执行过程?</font></b><br>1、客户端发送查询语句给服务器。<br>2、服务器首先检查缓存中是否存在该查询,若存在,返回缓存中存在的结果。若是不存在就进行下一步。<br>3、服务器进行SQL的解析、语法检测和预处理,再由优化器生成对应的执行计划。<br>4、Mysql的执行器根据执行计划执行,调用存储引擎的接口进行查询。<br>5、服务器将查询的结果返回客户端。<br>
<b><font color="#e0124f">InnoDB更新语句执行流程?<br></font></b>-- <b><font color="#0b38d9">undo log</font></b>:保证事务的一致性(事务回滚及MVCC),<b><font color="#0b38d9">随机读写</font></b>(因为说不定需要回滚)<br>-- <b><font color="#0b38d9">redo log</font></b>:重做日志,保证事务的持久性,<b><font color="#0b38d9">顺序写</font></b><br>-- <b><font color="#0b38d9">bin log</font></b>:归档日志,<b><font color="#0b38d9">顺序写<br><br>注:执行顺序 undoLog -> redoLog -> binLog -> redoLog</font></b>
架构设计问题<br>
秒杀系统设计<br>
<b><font color="#e0124f">秒杀超卖少卖及解决方案</font></b><br><b><font color="#0b38d9">Lua + Redis + MQ</font></b><br>1、先将库存数据预热到Redis<br>2、通过Lua脚本来做库存扣减(判断库存是否充足及扣减操作,属于原子操作),成功并返回订单号<br>3、Lua脚本将信息发到MQ(失败需要重试,重试不成功得持久化,再做补偿处理,保证订单系统可以拿到相关信息)<br>4、订单系统根据MQ信息来创建订单<br>5、客户端通过轮询订单系统来获取订单数据<br><b><font color="#0b38d9">注:用户未支付或者取消订单时,要对Redis库存进行添加</font></b>
高并发的红包系统
分布式ID
分布式限流<br>
0 条评论
下一页