G1 GC 算法
八股文
一、基础篇
网络基础
TCP/IP
三次握手过程(两次握手只能保证单向连接是畅通的):
客户端——发送带有SYN标志的数据包,客户端进入syn_sent
状态
服务端——发送带有SYN/ACK标志的数据包,服务端进入syn_rcvd
客户端——发送带有ACK标志的数据包,连接就进入established
状态
四次挥手过程(可单向关闭):
客户端——发送FIN数据包,关闭与服务端的连接,进入FIN_WAIT_1
状态
服务端–收到这个FIN,发回ACK报⽂确认,进入CLOSE_WAIT
状态
服务端——发送FIN数据包,关闭与客户端的连接,进入FIN-WAIT-2
状态
客户端–收到这个FIN,发回ACK报⽂确认,进入TIME_WAIT
状态
netstat -an | grep TIME_WAIT | wc -l
查看连接数等待time_wait状态连接数
OSI与TCP/IP 模型
- OSI七层:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层 - TCP/IP五层:物理层、数据链路层、网络层、传输层、应用层
常见网络服务分层
- 应用层:HTTP、SMTP、DNS、FTP - 传输层:TCP 、UDP - 网络层:ICMP 、IP、路由器、防火墙 - 数据链路层:网卡、网桥、交换机 - 物理层:中继器、集线器
TCP与UDP区别及场景
类型 | 特点 | 性能 | 应用场景 | 首部字节 | 应用层协议 |
---|---|---|---|---|---|
TCP | 面向连接、可靠、字节流 | 传输效率慢、所需资源多 | 文件、邮件传输 | 20-60 | HTTP、FTP、SMTP |
UDP | 无连接、不可靠、数据报文段 | 传输效率快、所需资源少 | 语音、视频、直播 | 8 | RIP、DNS、SNMP |
TCP滑动窗口,拥塞控制
TCP通过应用数据分割、对数据包进行编号、校验和、流量控制、拥塞控制、超时重传等措施保证数据的可靠传输
拥塞控制目的:为了防止过多的数据注入到网络中,避免网络中的路由器、链路过载
拥塞控制过程:TCP维护一个拥塞窗口,该窗口随着网络拥塞程度动态变化,通过慢开始、拥塞避免等算法减少网络拥塞的发生
TCP粘包原因和解决方法
TCP粘包是指发送方发送的若干包数据到接收方接收时粘成一包,
发送方原因:TCP默认使用Nagle算法(主要作用:减少网络中报文段的数量),收集多个小分组,在一个确认到来时一起发送
接收方原因:TCP将接收到的数据包保存在接收缓存里,如果TCP接收数据包到缓存的速度大于应用程序从缓存中读取数据包的速度,多个包就会被缓存,应用程序就有可能读取到多个首尾相接粘到一起的包
解决粘包问题:
- 发送定长包
- 包尾加上\r\n标记(FTP协议正是这么做的)
- 包头加上包体长度。包头是定长的4个字节
TCP报文格式
- 源端口号 16 目的端口号 16:用于寻找发端和收端应用进程,这两个值加上ip首部源端ip地址和目的端ip地址唯一确定一个tcp连接
- 序号 32:该连接的初始序号ISN(Initial Sequence Number)
- 确认号 32:上次已成功收到的序号加1,设置ACK标志时有效(建立起来的TCP连接总会设置ACK)
- 首都长度 4 保留 6 标志 6:可选的首都长度(最多60B,没有可选字段时20B),标志有URG紧急指针、ACK确认号、PSH接收方应该尽快将这个报文段交给应用层、RST重建连接、SYN同步序号、FIN关闭连接
- 窗口 16:接收端期望接收的字节,最大为 16KB
- 检验和 16:整个报文段的检验和
- 紧急指针 16:正的偏移量,和序号字段中的值相加表示紧急数据最后一个字节的序号
- 可选的选项:最常见的可选字段是最长报文大小,MSS (Maximum Segment Size)。通常在建立连接的第一个报文段中指明这个选项,指明本端所能接收的最大长度的报文段
- 填充:尾部填充,使最终长度为32的倍数
UDP报文格式
- 源端口号 16 目的端口号 16
- 长度 16:整个报文段的字节长度,最小值为8B(即UDP数据部分为空)
- 检验和 16:整个报文段的检验和
IP报文格式
- 版本 4:IPV4协议版本号4
- 首部长度 4:首部长度指的是首部占32bit字的数目,最长为60B
- 服务类型(TOS)8:包括一个3bit的优先权字段,4bit的TOS子字段和1bit未用位(必须置0)。4bit的TOS分别代表:最小时延、最大吞吐量、最高可靠性和最小费用,只能置其中1比特
- 总长度 16:整个IP数据报的长度,以字节为单位,所以IP数据报最长可达16KB
- 标识字段 16:唯一地标识主机发送的每一份数据报,通常每发送一份报文它的值就会加1
- 标识 3:
- 片偏移 13:
- 生存时间TTL 8:生存时间字段设置了数据报可以经过的最多路由器数。它指定了数据报的生存时间。TTL的初始值由源主机设置(通常为 32或64),一旦经过一个处理它的路由器,它的值就减去1。当该字段的值为 0时,数据报就被丢弃,并发送 ICMP 报文通知源主机
- 协议 8:
- 首部校验和 16:根据IP首部计算的检验和码
- 源IP地址 32 目标IP地址 32:
- 可选的选项:
- ****:
- ****:
HTTP
HTTP2.0
- HTTP1.0:服务器处理完成后立即断开TCP连接
- HTTP1.1:KeepAlived长连接避免了连接建立和释放的开销;通过Content-Length来判断当前请求数据是否已经全部接受
- HTTP2.0:引入二进制数据帧和流(二进制格式)的概念,其中帧对数据进行顺序标识;因为有了序列,服务器可以并行的传输数据
http1.1和http2.0的主要区别: 1、新的传输格式:2.0使用二进制格式,1.x依然使用基于文本格式 2、多路复用:连接共享,不同的request可以使用同一个连接传输(最后根据每个request上的id号组合成正常的请求) 3、header压缩:由于1.x中header带有大量的信息,并且得重复传输,2.0使用encoder来减少需要传输的hearder大小 4、服务端推送:同google的SPDUY(1.x的一种升级)一样
HTTPS
1. 首先客户端先给服务器发送一个请求 2. 服务器发送一个SSL证书给客户端,内容包括:证书的发布机构、有效期、所有者、签名以及公钥 3. 客户端对发来的公钥进行真伪校验,校验为真则使用公钥对对称加密算法以及对称密钥进行加密 4. 服务器端使用私钥进行解密并使用对称密钥加密确认信息发送给客户端 5. 随后客户端和服务端就使用对称密钥进行信息传输
Get和Post区别:GET能被缓存,数据长度受限为2kb
Cookie和Session区别:都是用来跟踪浏览器用户身份的会话方式;Cookie 数据保存在客户端,Session 数据保存在服务器端; Cookie⼀般⽤来保存⽤户信息,Session 的主要作⽤就是通过服务端记录⽤户的状态
操作系统基础
进程和线程
进程:是资源分配的最小单位,一个进程可以有多个线程,多个线程共享进程的堆和方法区资源,不共享栈、程序计数器
线程:是任务调度和执行的最小单位,线程并行执行存在资源竞争和上下文切换的问题
协程:
进程间通信方式IPC
- 管道pipe:亲缘关系使用匿名管道,非亲缘关系使用命名管道,管道遵循FIFO,半双工,数据只能单向通信
- 信号:比如用户调用kill命令将信号发送给其他进程
- 消息队列:
- 共享内存(Share Memory):多个进程直接读写同一块内存空间,需要依靠某种同步机制(如信号量)来达到进程间的同步及互斥
- 信号量(Semaphores):计数器,⽤于多进程对共享数据的访问,这种通信⽅式主要⽤于解决与同步相关的问题并避免竞争条件
- 套接字(Sockets):⽤套接字中的相关函数来完成通信过程
用户态和核心态
用户态:只能受限的访问内存,运行所有的应用程序 核心态:运行操作系统程序,cpu可以访问内存的所有数据,包括外围设备
由于需要限制不同的程序之间的访问能力, 防止他们获取别的程序的内存数据, 或者获取外围设备的数据, 并发送到网络
用户态切换到内核态的3种方式:
- 系统调用:系统中断
- 异常: 当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,比如缺页异常,这时会触发切换内核态处理异常
- 外围设备的中断:当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会由用户态到内核态的切换
内存管理
分段管理: 将程序的地址空间划分为若干段(segment),如代码段,数据段,堆栈段;这样每个进程有一个二维地址空间,相互独立,互不干扰。段式管理的优点是:没有内碎片(因为段大小可变,改变段大小来消除内碎片)。但段换入换出时,会产生外碎片(比如4k的段换5k的段,会产生1k的外碎片)
分页管理: 在页式存储管理中,将程序的逻辑地址划分为固定大小的页(page),而物理内存划分为同样大小的页框,程序加载时,可以将任意一页放入内存中任意一个页框,这些页框不必连续,从而实现了离散分离。页式存储管理的优点是:没有外碎片(因为页的大小固定),但会产生内碎片(一个页可能填充不满)
段页式管理: 段⻚式管理机制结合了段式管理和⻚式管理的优点。简单来说段⻚式管理机制就是把主存先分成若⼲段,每个段⼜分成若⼲⻚,也就是说 段⻚式管理机制 中段与段之间以及段的内部的都是离散的
页面置换算法
置换算法:先进先出FIFO、最近最久未使用LRU、最佳置换算法OPT
- 先进先出FIFO:没有考虑到实际的页面使用频率,性能差、与通常页面使用的规则不符合
- 最近最久未使用LRU:考虑到了程序访问的时间局部性,有较好的性能
- 最佳置换算法OPT:每次选择当前物理块中的页面在未来长时间不被访问的或未来不再使用的页面进行淘汰,实际上无法实现(没办法预知未来的页面)
死锁
死锁条件:
- 互斥条件:进程对所分配到的资源不允许其他进程访问,若其他进程访问该资源,只能等待至占有该资源的进程释放该资源 - 请求与保持条件:进程获得一定的资源后,又对其他资源发出请求,阻塞过程中不会释放自己已经占有的资源 - 非剥夺条件:进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用后自己释放 - 循环等待条件:系统中若干进程组成环路,环路中每个进程都在等待相邻进程占用的资源
解决方法:破坏死锁的任意一条件,比如乐观锁(互斥条件)、资源一次性分配(请求与保持条件)、可剥夺资源(非剥夺条件),资源有序分配(循环等待条件)
多线程基础
线程调度
线程是cpu任务调度的最小执行单位,每个线程拥有自己独立的程序计数器、虚拟机栈、本地方法栈
线程状态:创建、就绪、运行、阻塞、死亡
线程池
通过复用已创建的线程,降低资源损耗、线程可以直接处理队列中的任务加快响应速度、同时便于统一监控和管理
线程池大小设置
- CPU 密集型(n+1)
- IO 密集型(2*n)
Java基础
面向对象三大特性:封装、继承、多态
虚拟机栈中会存放当前方法调用的栈帧(局部变量表、操作栈、动态连接 、返回地址)。多态的实现过程,就是方法调用动态分派的过程,如果子类覆盖了父类的方法,则在多态调用中,动态绑定过程会首先确定实际类型是子类,从而先搜索到子类中的方法。这个过程便是方法覆盖的本质。
集合遍历快速和安全失败
fail—fast 快速失败:当异常产生时,直接抛出异常,程序终止 fail—safe:安全失败:采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改
ThreadLocal
将变量放在当前线程的 ThreadLocalMap 中,以ThreadLocal的弱引用WeakReference<ThreadLocal<?>>
为key
该引用在下一次垃圾回收的时候必然会被清理掉,从而会出现 key 为 null 的 value,此时线程长时间不被销毁,可能会产⽣内存泄露(value不会被回收)
使⽤完ThreadLocal ⽅法后,⼿动调⽤ remove() ⽅法可避免上述问题
二、JVM篇
内存模型
Java 内存模型(Java Memory Model,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了 Java 程序在各种平台下对内存的访问都能保证效果一致的机制及规范
volatile
lock前缀指令实际上相当于一个内存屏障(也成内存栅栏)
- 1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面
- 2)它会强制将对缓存的修改操作立即写入主存
- 3)如果是写操作,它会导致其他CPU中对应的缓存行无效
JVM内存
虚拟机栈
局部变量表:局部变量表是一组变量值存储空间,用来存放方法参数、方法内部定义的局部变量。底层是变量槽(variable slot)
操作数栈:是用来记录一个方法在执行的过程中,字节码指令向操作数栈中进行入栈和出栈的过程。大小在编译的时候已经确定了,当一个方法刚开始执行的时候,操作数栈中是空发的,在方法执行的过程中会有各种字节码指令往操作数栈中入栈和出栈。
动态链接:因为字节码文件中有很多符号的引用,这些符号引用一部分会在类加载的解析阶段或第一次使用的时候转化成直接引用,这种称为静态解析;另一部分会在运行期间转化为直接引用,称为动态链接。
返回地址(returnAddress):类型(指向了一条字节码指令的地址)
对象引用
普通的对象引用关系就是强引用。
软引用用于维护一些可有可无的对象。只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。
弱引用对象相比软引用来说,要更加无用一些,它拥有更短的生命周期,当 JVM 进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。
虚引用是一种形同虚设的引用,在现实场景中用的不是很多,它主要用来跟踪对象被垃圾回收的活动。
类加载:加载、验证、准备、解析、初始化
加载阶段: 1.通过一个类的全限定名来获取定义此类的二进制字节流。 2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。 3.在Java堆中生成一个代表这个类的java.lang.class对象,作为方法区这些数据的访问入口
验证阶段: 1.文件格式验证(是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理) 2.元数据验证(对字节码描述的信息进行语意分析,以保证其描述的信息符合Java语言规范要求) 3.字节码验证(保证被校验类的方法在运行时不会做出危害虚拟机安全的行为) 4.符号引用验证(虚拟机将符号引用转化为直接引用时,解析阶段中发生)
准备阶段: 准备阶段是正式为类变量分配内存并设置类变量初始值的阶段。将对象初始化为“零”值
解析阶段: 解析阶段时虚拟机将常量池内的符号引用替换为直接引用的过程。
初始化阶段: 执行类中定义的Java程序代码
GC
- MinorGC 在年轻代空间不足的时候发生,
- MajorGC 指的是老年代的 GC,出现 MajorGC 一般经常伴有 MinorGC。
- FullGC 当老年代无法再分配内存或元空间不足的时候等
CMS JDK8
一种年老代垃圾收集器,使用标记-清除算法,关注最短垃圾回收停顿时间,但是会导致⼤量空间碎⽚产⽣
初始标记:只是标记一下 GC Roots 能直接关联的对象,速度很快,STW 并发标记:进行 ReferenceChains跟踪的过程,和用户线程一起工作,不需要暂停工作线程 重新标记:为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,STW 并发清除:清除 GC Roots 不可达对象,和用户线程一起工作,不需要暂停工作线程
G1 JDK9
基于标记-整理算法,精准控制停顿时间,区域划分和优先级区域回收机制
初始标记:STW,仅使用一条初始标记线程对GC Roots关联的对象进行标记 并发标记:使用一条标记线程与用户线程并发执行。此过程进行可达性分析,速度很慢 最终标记:STW,使用多条标记线程并发执行 筛选回收:STW,回收废弃对象,并使用多条筛选回收线程并发执行
ZGC JDK11
染色指针,读屏障,在不关注容量的情况获取最小停顿时间5TB/10ms
JVM性能调优
堆空间设置成操作系统的 2/3,超过 8GB 的堆,优先选用 G1 借用 GCeasy 这样的日志分析工具,定位问题
故障排查
第一步是隔离(把机器从请求列表里摘除),第二步是保留现场,第三步问题排查
# 系统当前网络连接
ss -antp > $DUMP_DIR/ss.dump 2>&1
# 网络状态统计
netstat -s > $DUMP_DIR/netstat-s.dump 2>&1
sar -n DEV 1 2 > $DUMP_DIR/sar-traffic.dump 2>&1
# 进程资源
lsof -p $PID > $DUMP_DIR/lsof-$PID.dump
# CPU 资源
mpstat > $DUMP_DIR/mpstat.dump 2>&1
vmstat 1 3 > $DUMP_DIR/vmstat.dump 2>&1
sar -p ALL > $DUMP_DIR/sar-cpu.dump 2>&1
uptime > $DUMP_DIR/uptime.dump 2>&1
# I/O 资源
iostat -x > $DUMP_DIR/iostat.dump 2>&1
# 内存
free -h > $DUMP_DIR/free.dump 2>&1
# 进程
ps -ef > $DUMP_DIR/ps.dump 2>&1
dmesg > $DUMP_DIR/dmesg.dump 2>&1
sysctl -a > $DUMP_DIR/sysctl.dump 2>&1
# JVM进程快照
jinfo $PID > $DUMP_DIR/jinfo.dump 2>&1
# JVM运行时的状态信息,包括内存状态、垃圾回收
jstat -gcutil $PID > $DUMP_DIR/jstat-gcutil.dump 2>&1
jstat -gccapacity $PID > $DUMP_DIR/jstat-gccapacity.dump 2>&1
# JVM堆信息
jmap $PID > $DUMP_DIR/jmap.dump 2>&1
jmap -heap $PID > $DUMP_DIR/jmap-heap.dump 2>&1
jmap -histo $PID > $DUMP_DIR/jmap-histo.dump 2>&1
jmap -dump:format=b,file=$DUMP_DIR/heap.bin $PID > /dev/null 2>&1
# JVM 执行栈
jstack $PID > $DUMP_DIR/jstack.dump 2>&1
top -Hp $PID -b -n 1 -c > $DUMP_DIR/top-$PID.dump 2>&1
# Java 进程几乎不响应时候的替补
kill -3 $PID # 打印 jstack 的 trace 信息
gcore -o $DUMP_DIR/core $PID # jmap替补
jhsdb jmap --exe java --core $DUMP_DIR/core --binaryheap # 利用gcore文件生成dump文件
三、MySQL篇
NoSQL
- 列存储 Hbase
- KV存储 Redis
- 图存储 Neo4j
- 文档存储 MongoDB
事务
MySQL的存储引擎InnoDB使用重做日志保证一致性与持久性,回滚日志保证原子性,使用各种锁来保证隔离性
读未提交:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读 读已提交:允许读取并发事务已经提交的数据,可以阻⽌脏读,但是幻读或不可重复读仍有可能发⽣ 可重复读:同⼀字段的多次读取结果都是⼀致的,除⾮数据是被本身事务⾃⼰所修改,可以阻⽌脏读和不可重复读,会有幻读 串行化:最⾼的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执⾏,这样事务之间就完全不可能产⽣⼲扰
行锁,表锁,意向锁
InnoDB按照不同的分类的锁: - 共享/排它锁(Shared and Exclusive Locks):行级别锁 - 意向锁(Intention Locks),表级别锁 - 间隙锁(Gap Locks),锁定记录的范围,不包含索引项本身,其他事务不能在锁范围内插入数据。 - 记录锁(Record Locks),对索引项加锁,锁定符合条件的行
Next-key Lock:锁定索引项本身和索引范围。即Record Lock和Gap Lock的结合。可解决幻读问题
MVCC多版本并发控制
InnoDB的MVCC,是通过在每行记录后面保存系统版本号(可以理解为事务的ID),每开始一个新的事务,系统版本号就会自动递增,事务开始时刻的系统版本号会作为事务的ID。这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的,防止幻读的产生
索引
聚簇索引:将数据存储与索引放到了一块,索引结构的叶子节点保存了行数据(主键索引)
非聚簇索引:将数据与索引分开存储,索引结构的叶子节点指向了数据对应的位置(辅助索引)
哈希索引
散列的分布方式,不支持范围查找和排序的功能
B+树索引
B+树是B树的升级版,B+树只有叶节点存放数据,其余节点用来索引。索引节点可以全部加入内存,增加查询效率,叶子节点可以做双向链表,从而提高范围查找的效率,增加的索引的范围
调优
索引优化: - ①最左前缀索引:like只用于’string%’,语句中的=和in会动态调整顺序 - ②唯一索引:唯一键区分度在0.1以上 - ③无法使用索引:!= 、is null 、 or、>< 、(5.7以后根据数量自动判定)in 、not in - ④联合索引:避免select * ,查询列使用覆盖索引
语句优化: - ①char固定长度查询效率高,varchar第一个字节记录数据长度 - ②应该针对Explain中Rows增加索引 - ③group/order by字段均会涉及索引 - ④Limit中分页查询会随着start值增大而变缓慢,通过子查询+表连接解决
select * from mytbl order by id limit 100000,10;
-- 改进后的SQL语句如下
select * from mytbl where id >= ( select id from mytbl order by id limit 100000,1 ) limit 10
select * from mytbl inner join (select id from mytbl order by id limit 100000,10) as tmp on tmp.id=mytbl.id;
- ⑤count会进行全表扫描,如果估算可以使用explain - ⑥delete删除表时会增加大量undo和redo日志,确定删除可使用trancate
表结构优化: - ①单库不超过200张表 - ②单表不超过500w数据 - ③单表不超过40列 - ④单表索引不超过5个
配置优化: - 配置连接数、禁用Swap、增加内存、升级SSD硬盘
分库分表
分表用户id进行分表,每个表控制在300万数据。 分库根据业务场景和地域分库,每个库并发不超过2000 不是写瓶颈优先进行分表
Sharding-jdbc或Mycat
富查询:采用分库分表之后,如何满足跨越分库的查询?使用ES的宽表 数据倾斜:数据分库基础上再进行分表 深分页问题:按游标查询,或者叫每次查询都带上上一次查询经过排序后的最大 ID
双写不中断迁移:
- 线上系统里所有写库的地方,增删改操作,除了对老库增删改,都加上对新库的增删改
- 系统部署以后,还需要跑程序读老库数据写新库,写的时候需要判断updateTime
- 循环执行,直至两个库的数据完全一致,最后重新部署分库分表的代码就行了
深分页问题
- 内存排序
将
order by time offset X limit Y
,改写成order by time offset 0 limit X+Y
,服务层对得到的N*(X+Y)条数据进行内存排序,内存排序后再取偏移量X后的Y条记录 - 禁止跳页查询
order by time where time > $time_max limit Y
- 允许模糊数据
order by time offset X/N limit Y/N
- 二次查询法
四、Redis篇
速度快,完全基于内存,使用C语言实现,网络层使用epoll解决高并发问题,单线程模型避免了不必要的上下文切换及竞争条件
redis数据类型
类型 | 底层 | 应用场景 | 编码类型 |
---|---|---|---|
String | SDS数组 | 帖子、评论、热点数据、输入缓冲 | RAW « EMBSTR « INT |
List | QuickList | 评论列表、商品列表、发布与订阅、慢查询、监视器 | LINKEDLIST « ZIPLIST |
Set | IntSet | 适合交集、并集、查集操作,例如朋友关系 | HT « INSET |
Zset | 跳跃表 | 去重后排序,适合排名场景 | SKIPLIST « ZIPLIST |
Hash | 哈希 | 结构化数据,比如存储对象 | HT « ZIPLIST |
Stream | 紧凑列表 | 消息队列 |
SDS,用于存储字符串和整型数据及输入缓冲
struct sdshdr{
int len; // 记录buf数组中已使用字节的数量
int free; // 记录 buf 数组中未使用字节的数量
char buf[]; // 字符数组,用于保存字符串
}
跳跃表 Redis使用跳表而不使用红黑树,是因为跳表的索引结构序列化和反序列化更加快速,方便持久化。
字典dict Redis整个数据库是用字典来存储的(K-V结构) —Hash+数组+链表
渐进式rehash:根据服务器空闲程度批量rehash部分节点
五、Kafka篇
消息队列的作用:异步、削峰填谷、解耦 由于是异步的和批处理的,延迟也会高,不适合电商场景
六、Spring
AOP 动态代理
如果要代理的对象,实现了某个接⼝,那么Spring AOP会使⽤JDKProxy,去创建代理对象,⽽对于没有实现接⼝的对象,这时候Spring AOP会使⽤ Cglib ⽣成⼀个被代理对象的⼦类来作为代理