Java面试点汇总
2021-06-06 15:47:03 22 举报
Java面试点汇总(持续更新)
作者其他创作
大纲/内容
Java 基础
多线程与高并发
MySQL 优化及分库分表
锁
事务
索引优化
主从同步
分库分表
Redis
数据结构
Redis 对象头
所有 Redis 对象都具有对象头
Redis 对象头的总大小为16 bytes
string(字符串)
底层结构为动态字符串(SDS)
字符串在数组空间不足时,会进行扩容操作,它会分配一个新数组,将原有旧数组的内容拷贝到新数组中,此时会产生内存分配和复制的开销
字符串长度不超过44bytes时,SDS 使用 embstr 形式存储,当字符串长度超过44bytes时,SDS 使用 raw 形式存储(44bytes临界点根据Redis对象头决定)
list(列表)
在列表元素较少的情况下,list底层采用压缩列表(ziplist)进行存储,当数据量比较多的时候,list底层则改用快速列表(quicklist)进行存储
ziplist 是一块连续的内存空间,元素之间紧挨着存储,没有任何冗余空隙
ziplist 在插入元素时会因为扩容时重新分配内存而导致影响性能,ziplist 在修改和删除元素时也会因为级联更新而造成性能损耗
quicklist 是 ziplist 和 linkedlist 的混合体,它将 linkedlist 按段切分,每一段使用 ziplist 让存储紧凑,多个 ziplist 之间使用双向指针串接起来
quicklist 内部默认单个 ziplist 长度为8KB,超出这个字节数,就会另起一个 ziplist
quicklist 默认的压缩深度为0,也就是不压缩。为了支持快速的 push/pop 操作,quicklist 的首位两个 ziplist 不压缩,此时压缩深度就是1。如果压缩深度为2,就表示 quicklist 的首尾第一个 ziplist 以及首尾第二个 ziplist 都不压缩
hash(字典)
hash 结构底层使用字典(dict)进行存储,除 hash 外,整个 Redis 数据库的所有 key 和 value 也组成了一个全局字典,还有带过期时间的 key 集合也是一个字典。zset 集合中存储 value 和 score 值的映射关系也是通过字典结构实现的
字典结构内部包含两个 hashtable,通常情况下只有一个 hashtable 是有值的,但是在字典扩容缩容时,需要分配新的 hashtable,然后进行渐进式搬迁,这时候两个 hashtable 存储的分别是旧的 hashtable 和新的 hashtable。待搬迁后,旧的 hashtable 被删除,新的 hashtable 取而代之
hashtable 结构类似 Java 语言的 HashMap,第一维是数组,第二维是链表
Redis 使用渐进式 rehash 实现字典的扩容
正常情况下,当 hash 表中元素的个数等于第一维数组的长度时,就开始扩容,扩容的新数组是原数组大小的2倍。如果 Redis 正在做 bgsave,为了减少内存页的过多分离(Copy On Write),Redis 尽量不去扩容,但是如果 hash 表元素的个数已经达到了第一维数组长度的5倍,就会进行强制扩容。
hash 表缩容的条件是元素个数低于数组长度的10%,缩容不会考虑 Redis 是否正在做 bgsave 操作
set(集合)
set 内部实现相当于一个特殊的字典,字典中所有的 value 都是一个值 NULL
zset(有序集合)
zset 底层由一个 hash 来存储 value 和 score 的对应关系,另外,需要 skiplist 提供按照 score 进行排序
Redis 的跳表共64层,kv 之间使用指针串起来形成双向链表结构。每一个层元素的遍历都是从 kv header 出发的
跳表的查找复杂度为O(log(n))
对于每一个新插入的节点,都需要调用一个随机算法给它分配一个合理的层数。直观上期望的目标是50%的概率被分配到Level1,25%的概率被分配到Level2,以此类推,2^-63的概率被分配到最顶层,每一层晋升的概率是50%
持久化
RDB 快照
原理:使用操作系统的多进程 COW(Copy On Write),Redis 在持久化时调用 glibc 的函数 fork 产生一个子进程,快照持久化完全交给子进程去管理,父进程继续处理客户端请求。子进程不会修改现有内存的数据结构,只对数据结构进行遍历读取
AOF 增量
原理:记录 Redis 的顺序指令序列
分布式锁
加锁和解锁
加锁:使用 setnx 指令
解锁:使用 del 指令
死锁
产生原因:加锁后,如果逻辑执行中出现异常,会导致 del 指令没有调用,产生死锁
解决方案1:在拿到锁之后,使用 expire 命令给锁加上一个过期时间
不足之处:在 setnx 命令和 expire 命令执行期间服务器突然挂掉,导致 expire 命令无法执行,仍然会产生死锁(核心问题在于 setnx 和 expire 操作不是原子操作)
解决方案2:(1)使用 set :key true ex :expiretime nx 命令,该命令是一个原子命令 (2)使用 Lua 脚本,Lua 脚本可以保证操作的原子性
锁超时
产生原因:在加锁和释放锁之间的执行逻辑太长,以至于超出了锁的超时限制
解决方案:(1)分布式锁尽量不要应用于执行时间太长的事务,可以将事务进行拆分(2)可以适当将超时时间设置的长一些,保证逻辑正常执行完毕
可重入
解决方案:使用线程的 ThreadLocal 变量存储当前持有锁的计数
Redis 集群导致多锁
产生原因:在 Sentinel 集群中,当主节点挂掉时,从节点会取而代之,但客户端上却并没有感知。此时,若第一个客户端在主节点中申请成功了一把锁,但是这把锁还没有来得及同步到从节点,主节点突然挂掉了,然后从节点变为了主节点,这个新的主节点内部没有这个锁,当另一个客户端过来请求加锁时,立即就批准了,这样就会导致系统中同样一把锁被两个客户端所持有
解决方案:Redlock算法(加锁时,向过半节点发送 set(key, value, nx=True, ex=xxx) 指令,只要半数节点 set 成功,就认为加锁成功。释放锁时,需向所有节点发送 del 指令
性能优势
单线程
运算均为内存级别运算
避免了CPU时间片之间的切换
使用多路复用处理客户端连接
管道技术
改变读写顺序,减少网络IO请求
小对象压缩
对小的数据结构,采用紧凑存储形式进行压缩存储(embstr、ziplist)
数据结构调整做到极致
集群部署
主从同步
快照同步过程
(1)主节点进行一次 bgsave,将当前内存的数据全部快照到磁盘文件中
将快照文件全部传输到从节点(Redis 2.8.18后支持无盘复制,主服务器通过套(2)接字将快照文件发送给从节点,生成快照是一个遍历的过程,主节点会一边遍历内存,一边将序列化的内容发送到从节点)
(3)从节点接将快照文件接收完毕后,立即执行一次全量加载,加载之前先将当前内存数据清空,加载完毕后通知主节点继续进行增量同步
增量同步过程
(1)主节点会将对自己的状态产生修改性影响的指令记录在本地的内存 buffer (环形数组)中,然后异步将 buffer 中的指令同步到从节点
(2)从节点一边执行同步的指令流来达到和主节点一样的状态,一边向主节点反馈自己同步到哪里了
当 buffer 环形数组,因为网络状况的原因,从节点短时间内无法和主节点进行同步,造成 Redis 主节点新的指令覆盖掉之前的指令,此时会再次发起快照同步
Sentinel 哨兵
Sentinel负责持续监控主从节点的健康,当主节点挂掉时,自动选择一个最优的从节点切换为主节点。客户端来连接集群时,会首先连接Sentinel,会通过Sentinel来查询主节点的地址,然后再连接主节点进行数据交互。当主节点发生故障时,客户端会重新向Sentinel要地址,Sentinel会将最新的主节点地址告诉客户端
故障切换(failover)过程
(1)假设主服务器宕机,Sentinel A 先监测到了这个结果,系统并不会马上进行 failover 过程,仅仅是 Sentinel A 主观认为主服务器不可用,这个现象称为主观下线
(2)当后面的 Sentinel 也监测到了主服务器不可用,并数量到达一定值时,此时 Sentinel 之间会进行一次投票,投票的结果由一个 Sentinel 发起,进行 failover 操作
(3)切换成功后,会通过发布/订阅模式,让各个 Sentinel 把自己监控的从服务器实现切换主机,这个过程称为客观下线
Codis 中间件
Codis 分片原理
Codis 默认将所有的 key 划分为1024个槽位(slot),它首先对客户端传过来的 key 进行 crc32 运算计算 hash 值,再将 hash 后的整数值对1024这个整数进行取模得到一个余数,这个余数就是对应 key 的槽位
Codis 实例之间的槽位信息在 Zookeeper 中进行持久化
Codis 的优缺点
优点
在设计上比官方的 Redis Cluster 集群方案要简单很多
有强大的 Dashboard 功能,能够便捷地对 Redis 集群进行管理
缺点
Codis 不再支持事务,事务只能在单个 Redis 实例中完成
为了支持扩容,单个 key 对应的 value 不宜过大,因为在迁移过程中的最小单位是 key,对于一个 hash 结构,它会一次性使用 hgetall 拉取所有内容,然后使用 hmset 将之放置到另一个节点中
Codis 增加了 Proxy 作为中转层,在网络开销上要比单个 Redis 大
Codis 配置中心使用了 Zookeeper,额外增加了运维成本
Codis 不是官方项目,所以 Redis 提供最新功能后,Codis 往往需要等很久才能同步
Redis Cluster 集群配置
槽位定位
Redis Cluster 默认会对 key 值使用 crc16 算法进行 hash,得到一个整数值,然后用这个整数值对16384进行取模来得到具体槽位
跳转
当客户端向一个错误的节点发出了指令后,该节点会发现指令的 key 所在的槽位并不归自己管理,这时它会向客户端发送一个特殊的跳转指令携带目标操作的节点地址,告诉客户端去连接这个节点以获取数据
Kafka
Zookeeper
Netty 源码
Spring 源码
0 条评论
下一页