图灵面试题视频:https://www.bilibili.com/video/BV17z421B7rB?spm_id_from=333.788.videopod.episodes&vd_source=be7914db0accdc2315623a7ad0709b85&p=20。
本文是学习笔记,如果需要面试没有时间阅读原博文,可以快速浏览笔记。
推荐深度阅读对应书籍或者知识点原文,避免碎片化学习。
当一个SQL想要利用索引时,就一定要提供该索引所对应的字段中最左边的字段,也就是排在最前面的字段。比如针对a
、b
、c
三个字段建立了一个联合索引,那么在写一个SQL时就一定要提供a
字段的条件,这样才能用到联合索引。这是由于在建立a
、b
、c
三个字段的联合索引时,底层的B+树是按照a
、b
、c
三个字段从左往右去比较大小进行排序的,所以如果想要利用B+树进行快速查找也得符合这个规则。
Innodb通过Buffer Pool
、LogBuffer
、Redo Log
、Undo Log
来实现事务。以一个update
语句为例:
update
语句后,会先根据条件找到数据所在的页,并将该页缓存在Buffer Pool
中。update
语句,修改Buffer Pool
中的数据,也就是内存中的数据。update
语句生成一个RedoLog
对象,并存入LogBuffer
中。update
语句生成undolog
日志,用于事务回滚。RedoLog
对象进行持久化,后续还有其他机制将Buffer Pool
中所修改的数据页持久化到磁盘中。undolog
日志进行回滚。从锁的粒度来区分:
select xxx LOCK IN SHARE MODE
语句添加。update
、delete
、insert
语句自动添加排他锁,也可使用SELECT xxx FOR UPDATE
手动添加。Flush tables with read lock
语句添加。加锁之后整个数据库实例都处于只读状态,所有的数据变更操作都会被挂起,一般用于全库备份的时候。常见的锁算法:
(-∞, 1)
、(1, 4)
、(4, 9)
、(9, +∞)
这些区间。(-∞,1]
、(1,4]
、(4,9]
、(9, +∞]
。RDB:即Redis DataBase
,在指定的时间间隔内将内存中的数据集快照写入磁盘。实际操作过程是fork
一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。
dump.rdb
,方便持久化。fork
子进程来完成写操作,让主进程继续处理命令,所以是IO最大化。使用单独子进程来进行持久化,主进程不会进行任何IO操作,保证了redis的高性能。fork
子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。AOF:即Append Only File
,以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。
append
模式写文件,即使中途服务器宕机也不会破坏已经存在的内容,可以通过redis-check-aof
工具解决数据一致性问题。rewrite
模式,定期对AOF文件进行重写,以达到压缩的目的。AOF文件比RDB更新频率高,优先使用AOF还原数据。AOF比RDB更安全也更大,RDB性能比AOF好,如果两个都配了优先加载AOF。
Redis是key-value
数据库,可以设置Redis中缓存的key
的过期时间。Redis的过期策略就是指当Redis中缓存的key
过期了,Redis如何处理。
key
时,才会判断该key
是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key
没有再次被访问,从而不会被清除,占用大量内存。expires
字典中一定数量的key
,并清除其中已过期的key
。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。(expires
字典会保存所有设置了过期时间的key
的过期时间数据,其中,key
是指向键空间中的某个键的指针,value
是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。)Redis中同时使用了惰性过期和定期过期两种过期策略。
Redis基于Reactor
模式开发了网络事件处理器,这个处理器叫做文件事件处理器file event handler
。这个文件事件处理器是单线程的,所以Redis才叫做单线程的模型,它采用IO多路复用机制来同时监听多个Socket
,根据Socket
上的事件类型来选择对应的事件处理器来处理这个事件。可以实现高性能的网络通信模型,又可以跟内部其他单线程的模块进行对接,保证了Redis内部的线程模型的简单性。
文件事件处理器的结构包含4个部分:多个Socket
、IO多路复用程序、文件事件分派器以及事件处理器(命令请求处理器、命令回复处理器、连接应答处理器等)。
多个Socket
可能并发的产生不同的操作,每个操作对应不同的文件事件,但是IO多路复用程序会监听多个Socket
,会将Socket
放入一个队列中排队,每次从队列中取出一个Socket
给事件分派器,事件分派器把Socket
给对应的事件处理器。然后一个Socket
的事件处理完之后,IO多路复用程序才会将队列中的下一个Socket
给事件分派器。文件事件分派器会根据每个Socket
当前产生的事件,来选择对应的事件处理器来处理。
单线程快的原因:
MULTI
命令的执行,标识着一个事务的开始。MULTI
命令会将客户端状态的flags
属性中打开REDIS_MULTI
标识来完成。MULTI
、EXEC
、WATCH
、DISCARD
中的一个,立即执行这个命令,否则将命令放入一个事务队列里面,然后向客户端返回QUEUED
回复。
EXEC
、DISCARD
、WATCH
、MULTI
四个命令的其中一个,那么服务器立即执行这个命令。redisClient
)的flags
属性关闭REDIS_MULTI
标识,并且返回错误信息给客户端。如果正确,将这个命令放入一个事务队列里面,然后向客户端返回QUEUED
回复。事务队列是按照FIFO的方式保存入队的命令。EXEC
命令,服务器执行EXEC
命令逻辑。
flags
属性不包含REDIS_MULTI
标识,或者包含REDIS_DIRTY_CAS
或者REDIS_DIRTY_EXEC
标识,那么就直接取消事务的执行。flags
有REDIS_MULTI
标识),服务器会遍历客户端的事务队列,然后执行事务队列中的所有命令,最后将返回结果全部返回给客户端。redis不支持事务回滚机制,但是它会检查每一个事务中的命令是否错误。Redis事务不支持检查那些程序员自己逻辑错误,例如对String
类型的数据库键执行对HashMap
类型的操作。
WATCH
命令是一个乐观锁,可以为Redis事务提供check-and-set (CAS)
行为。可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC
命令。
MULTI
命令用于开启一个事务,它总是返回OK
。MULTI
执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC
命令被调用时,所有队列中的命令才会被执行。
EXEC
:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。当操作被打断时,返回空值nil
。
通过调用DISCARD
,客户端可以清空事务队列,并放弃执行事务,并且客户端会从事务状态中退出。
UNWATCH
命令可以取消watch
对所有key
的监控。
通过执行slaveof
命令或设置slaveof
选项,让一个服务器去复制另一个服务器的数据。主数据库可以进行读写操作,当写操作导致数据变化时会自动将数据同步给从数据库。而从数据库一般是只读的,并接受主数据库同步过来的数据。一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库。
全量复制:
bgsave
命令fork
子进程进行RDB持久化,该过程是非常消耗CPU、内存(页表复制)、硬盘IO的。bgrewriteaof
,也会带来额外的消耗。部分复制:
offset
。offset
的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。runid
与主节点现在的runid
相同,说明主从节点之前同步过,主节点会继续尝试使用部分复制(到底能不能部分复制还要看offset
和复制积压缓冲区的情况)。runid
与主节点现在的runid
不同,说明从节点在断线前同步的Redis节点并不是当前的主节点,只能进行全量复制。从服务器收到slaveof
命令,第一次执行复制时,向master
发送psync {runid} {offset}
(runid
是上一次主节点的运行ID,offset
是当前从节点的复制偏移量),如果master
返回continue
则执行增量同步,如果master
返回fullresync {runid} {offset}
(runid
表示主节点的运行ID,offset
表示当前主节点的复制偏移量)则执行全量同步。
Redis的数据结构有:
Session
共享、分布式ID。key-value
对,更适合用来存储对象。setnx
来保证:如果key
不存在才能获取到锁,如果key
存在,则获取不到锁。lua
脚本来保证多个redis操作的原子性。N/2 + 1
个节点申请锁,都申请到了才证明获取锁成功,这样就算其中某个redis节点挂掉了,锁也不能被其他客户端获取到。Redis提供了三种集群策略:
Cluster
模式是用得比较多的模式,它支持多主多从。这种模式会按照key
进行槽位的分配,可以使得不同的key
分散到不同的主节点上,利用这种模式可以使得整个集群支持更大的数据容量。同时每个主节点可以拥有自己的多个从节点,如果该主节点宕机,会从它的从节点中选举一个新的主节点。缓存中存放的大多都是热点数据,目的是防止请求直接访问MySQL,而是先从缓存中获取数据。
key
突然失效,也导致了大量请求直接访问MySQL数据库。解决方案是考虑这个热点key
不设过期时间。key
都在redis中不存在(比如黑客故意伪造一些乱七八糟的key
),那么也会给数据库造成压力,这就是缓存穿透。解决方案是使用布隆过滤器,它的作用是如果它认为一个key
不存在,那么这个key
就肯定不存在,所以可以在缓存之前加一层布隆过滤器来拦截不存在的key
。Redis基于Reactor
模式开发了网络事件处理器、文件事件处理器file event handler
。它是单线程的,所以Redis才叫做单线程的模型,它采用IO多路复用机制来同时监听多个Socket
,根据Socket
上的事件类型来选择对应的事件处理器来处理这个事件。可以实现高性能的网络通信模型,又可以跟内部其他单线程的模块进行对接,保证了Redis内部的线程模型的简单性。
文件事件处理器的结构包含4个部分:多个Socket
、IO多路复用程序、文件事件分派器以及事件处理器(命令请求处理器、命令回复处理器、连接应答处理器等)。
多个Socket
可能并发的产生不同的事件,IO多路复用程序会监听多个Socket
,会将Socket
放入一个队列中排队,每次从队列中有序、同步取出一个Socket
给事件分派器,事件分派器把Socket
给对应的事件处理器。然后一个Socket
的事件处理完之后,IO多路复用程序才会将队列中的下一个Socket
给事件分派器。文件事件分派器会根据每个Socket
当前产生的事件,来选择对应的事件处理器来处理。
AE_READABLE
事件关联。AE_READABLE
事件,然后由连接应答处理器负责和客户端建立连接,创建客户端对应的socket
,同时将这个socket
的AE_READABLE
事件和命令请求处理器关联,使得客户端可以向主服务器发送命令请求。socket
都会产生一个AE_READABLE
事件,触发命令请求处理器。处理器读取客户端的命令内容,然后传给相关程序执行。socket
的AE_WRITABLE
事件和命令回复处理器关联,当客户端准备好读取响应数据时,会在socket
产生一个AE_WRITABLE
事件,由对应命令回复处理器处理,即将准备好的响应数据写入socket
,供客户端读取。socket
后,就会删除该socket
的AE_WRITABLE
事件和命令回复处理器的映射。单线程快的原因:
CAP理论是分布式领域中非常重要的一个指导理论,C(Consistency)
表示强一致性,A(Availability)
表示可用性,P(Partition Tolerance)
表示分区容错性。CAP理论指出在目前的硬件条件下,一个分布式系统是必须要保证分区容错性的,而在这个前提下,分布式系统要么保证CP
,要么保证AP
,无法同时保证CAP
。
分区容错性表示,一个系统虽然是分布式的,但是对外看上去应该是一个整体,不能由于分布式系统内部的某个结点挂点,或网络出现了故障,而导致系统对外出现异常。所以,对于分布式系统而言是一定要保证分区容错性的。
强一致性表示,一个分布式系统中各个结点之间能及时的同步数据,在数据同步过程中,是不能对外提供服务的,不然就会造成数据不一致,所以强一致性和可用性是不能同时满足的。
可用性表示,一个分布式系统对外要保证可用。
CAP
理论,因此出现了BASE
理论。RPC,表示远程过程调用,对于Java这种面向对象语言,也可以理解为远程方法调用。RPC调用和HTTP调用是有区别的,RPC表示的是一种调用远程方法的方式,可以使用HTTP协议、或直接基于TCP协议来实现RPC。在Java中,可以通过直接使用某个服务接口的代理对象来执行方法,而底层则通过构造HTTP请求来调用远端的方法。所以,有一种说法是RPC协议是HTTP协议之上的一种协议,也是可以理解的。