Java总结-4
八、Redis高并发
0.Redis线程模型,为什么快?
Redis是基于Reactor模式开发了网络事件处理器,这个事件处理器叫作file event handler,这个事件是单线程的,因此说Redis是单线程模型。这个事件处理器使用IO多路复用机制监听多个socket,根据每一个socket上的事件类型选择相应的事件处理器处理这个事件。这样可以实现高性能的网络通信模型,又可以跟内部其他的单线程模块进行对接,保证了Redis内部线程模型的简单性。
graph TB st(( ))-.->a(文件事件处理器) a--监听-->b(多个socket) b-.有.->c(事件) c--根据事件类型-->d(选择相应的事件处理器) d--处理-->c d-.->e(命令处理器) d-.->f(命令回复处理器) d-.->g(连接应答处理器)
文件事件处理器结构由四个部分组成:
- 多个socket
- IO多路复用程序
- 文件事件分派器
- 事件处理器(命令处理器、命令回复处理器、连接应答处理器)
为什么快?
- 完全基于内存操作
- 核心是基于非阻塞的IO多路复用
- 单线程避免了上下文切换带来的性能损耗
Ps: netty也是基于Reactor模式实现的!
1.Redis的高并发和快速原因?
(1)Redis是基于内存的,内存的读写速度非常快;
(2)Redis是单线程的,省去了很多上下文切换线程的时间;
(3)Redis使用多路复用技术,可以处理并发的连接。非阻塞IO 内部实现采用epoll,采用了epoll+自己实现的简单的事件框架。epoll中的读、写、关闭、连接都转化成了事件,然后利用epoll的多路复用特性,绝不在io上浪费一点时间。
2.为什么Redis是单线程的?
(1)官方答案
因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。
(2)性能指标
关于redis的性能,官方网站也有,普通笔记本轻松处理每秒几十万的请求。
(3)详细原因
- 不需要各种锁的性能消耗
Redis的数据结构并不全是简单的Key-Value,还有list,hash等复杂的结构,这些结构有可能会进行很细粒度的操作,比如在很长的列表后面添加一个元素,在hash当中添加或者删除一个对象。这些操作可能就需要加非常多的锁,导致的结果是同步开销大大增加。总之,在单线程的情况下,就不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗。
- 单线程多进程集群方案
单线程的威力实际上非常强大,每核心效率也非常高,多线程自然是可以比单线程有更高的性能上限,但是在今天的计算环境中,即使是单机多线程的上限也往往不能满足需要了,需要进一步摸索的是多服务器集群化的方案,这些方案中多线程的技术照样是用不上的。所以单线程、多进程的集群不失为一个时髦的解决方案。
- CPU消耗
采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU。
3.Redis支持的数据结构?
(1)String:String数据结构是简单的key-value类型,value其实不仅可以是String,也可以是数字。
(2)List:list 就是链表,Redis list 的应用场景非常多,也是Redis最重要的数据结构之一,比如微博的关注列表,粉丝列表,消息列表等功能都可以用Redis的 list 结构来实现。
(3)hash:hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。
(4)Set:set 对外提供的功能与list类似是一个列表的功能,特殊之处在于 set 是可以自动排重的。
(5)ZSet:和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列。
4.redis 提供 6种数据淘汰策略?
编号 | 名称 | 解释 |
---|---|---|
1 | volatile-lru | 从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰 |
2 | volatile-ttl | 从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰 |
3 | volatile-random | 从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰 |
4 | allkeys-lru | 当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的) |
5 | allkeys-random | 从数据集(server.db[i].dict)中任意选择数据淘汰 |
6 | no-eviction | 禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧! |
7 | volatile-lfu | 从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰 |
8 | allkeys-lfu | 当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的key |
Redis的过期键的删除策略?Redis使用了这两种策略。
- 惰性过期
- 定期过期:每个一段时间,会扫描一定数量的数据库的expires字典中的key,删除其中已经过期的key。
5.持久化机制?
Redis不同于Memcached的很重一点就是,Redis支持持久化,而且支持两种不同的持久化操作。Redis的一种持久化方式叫快照(snapshotting,RDB),另一种方式是只追加文件(append-only file,AOF)。这两种方法各有千秋,下面我会详细这两种持久化方法是什么,怎么用,如何选择适合自己的持久化方法。
6.Redis事务?(REmote DIctionary Server(Redis))
Redis 通过 MULTI、EXEC、WATCH 、UNWATCH等命令来实现事务(transaction)功能。事务提供了一种将多个命令请求打包,然后一次性、按顺序地执行多个命令的机制,并且在事务执行期间,服务器不会中断事务而改去执行其他客户端的命令请求,它会将事务中的所有命令都执行完毕,然后才去处理其他客户端的命令请求。
在传统的关系式数据库中,常常用 ACID 性质来检验事务功能的可靠性和安全性。在 Redis 中,事务总是具有原子性(Atomicity)、一致性(Consistency)和隔离性(Isolation),并且当 Redis 运行在某种特定的持久化模式下时,事务也具有持久性(Durability)。
7.epoll相关的原理?
8.redis 和 memcached 的区别?
编号 | Redis | memcached |
---|---|---|
1(数据类型) | Redis不仅仅支持简单的k/v类型的数据, 同时还提供list,set,zset,hash等数据结构的存储。 |
memcache支持简单的数据类型,String。 |
2(持久化) | Redis支持数据的持久化,可以将内存中的数据保持在磁盘中, 重启的时候可以再次加载进行使用 |
Memecache把数据全部存在内存之中。 |
3(多线程) | 是多线程,非阻塞IO复用的网络模型 | Redis使用单线程的多路 IO 复用模型。 |
4(集群) | redis 目前是原生支持 cluster 模式的. | memcached没有原生的集群模式, 需要依靠客户端来实现往集群中分片写入数据; |
9.Redis的rehash?
- Redis的rehash采用的是渐进式rehash。
- rehash要和数据分片区分开!!!
10.redis高可用?
11.redis相关的命令?
12.redis集群怎么进行数据分配,hash槽?
13.aof和rdb的优缺点,你在项目中使用的哪一个?
(默认的配置是什么?)
- RDB:Redis Database:fork一个子进程,将数据集写入到临时文件,写入成功后,替换原来的文件,并且使用了压缩存储。
- AOF:Append only File
优点 | 缺点 | |
---|---|---|
RDB | (1)整个Redis只包含一个dump.rdb文件,方便持久化。 (2)容灾性好,方便备份。 (3)性能最大化,fork进程完成写操作,主进程批量处理命令,所以是IO最大化。使用单独的子进程进行持久化,主进程不会有任何IO操作,保证了Redis的性能。 (4)相对于数据集大的时候,比AOF效率更高。 |
(1)数据安全性低。RDB是间隔一段时间进行持久化的,如果在持久化之前发生故障,会导致数据丢失。 (2)由于RDB是通过fork子进程来协助完成数据持久化工作的,如果数据集大,可能导致整个服务暂停服务几百毫秒,甚至更长。 |
AOF | (1)数据安全,存在三种同步模式,每秒同步、每次修改同步、不同步。 (2)通过append模式写文件,即使中途服务器宕机也不会破坏已经存在的文件内容,可以通过redis-check-aof工具解决一致性问题。 (3)AOF的rewrite模式,定期对AOF文件进行重写,达到压缩的目的。 |
(1)AOF文件大。 (2)数据集大的时候,比rdb慢。 |
14. Redis资料
https://redislabs.com/ebook/part-2-core-concepts/chapter-3-commands-in-redis/3-5-sorted-sets/
15.Rehash
16.Jedis
- 使用zset实现积分榜
- 跳表实现
17.缓存雪崩、缓存击穿、缓存穿透
缓存雪崩 | 缓存击穿 | 缓存穿透(一般来自攻击) | |
---|---|---|---|
现象 | 同一个事件大量的key失效,请求落在数据库上,导致数据库短时间承担了大量的请求而崩溃。(查询多条数据) | 缓存中没有的数据数据库中有的数据,由于并发用户特别多,同时没有读到数据,有同时去数据库读取数据,和缓存雪崩不同,缓存击穿指的是查询同一条数据。 | 缓存和数据库中都没有数据,导致请求落在了数据库上。 |
原因 | - 同一时间key大面积过期 - 系统刚启动的时候缓存没有数据 |
||
解决方案 | - 将过期键设置为随机 - 缓存预热 - 给缓存添加标记位,记录缓存是否失效 - 互斥锁 |
- 设置热键永远不过期 - 互斥加锁 |
- 接口层校验,比如用户鉴权,基础校验拦截 - 从缓存中取不到、数据库中也取不到,使用key-null,并且设置过期时间。 - 使用布隆过滤器,将所有可能出现的数据hash到同一个bitmap中,一定不存在的数据会被这个bitmap拦截。 |
18. 集群机制
18.1 哨兵模式
18.2 cluster模式
18.3 Redis sharding(客户端分片)
redis集群可以被分为16384个槽,只有这些槽全被指派了处理的节点的情况下,集群的状态才能是上线状态(ok)
操作redis集群的时候,将key作为参数,就可以计算出对应的处理槽上,所以存储等操作都应该在该槽对应的节点上。
重要的数据结构包括:
- ClusterNode:slots,numslots
- ClusterState:slots
18.4 集群故障转移
(1)发现故障节点
- 集群内的节点会向其他节点发送PING命令,检查是否在线
- 如果未能在规定时间内做出PONG响应,则会把对应的节点标记为疑似下线
- 集群中一半以上负责处理槽的主节点都将主节点X标记为疑似下线的话,那么这个主节点X就会被认为是已下线
- 向集群广播主节点X已下线,大家收到消息后都会把自己维护的结构体里的主节点X标记为已下线
(2)从节点选举
- 当从节点发现自己复制的主节点已下线了,会向集群里面广播一条消息,要求所有有投票权的节点给自己投票(所有负责处理槽的主节点都有投票权)
- 主节点会向第一个给他发选举消息的从节点回复支持
- 当支持数量超过N/2+1的情况下,该从节点当选新的主节点
(3)故障的转移
- 新当选的从节点执行 SLAVEOF no one,修改成主节点
- 新的主节点会撤销所有已下线的老的主节点的槽指派,指派给自己
- 新的主节点向集群发送命令,通知其他节点自己已经变成主节点了,负责哪些槽指派
- 新的主节点开始处理自己负责的槽的命令
18.5 哨兵模式故障转移
(1)主观下线
哨兵(Sentinel)节点会每秒一次的频率向建立了命令连接的实例发送PING命令,如果在down-after-milliseconds毫秒内没有做出有效响应包括(PONG/LOADING/MASTERDOWN)以外的响应,哨兵就会将该实例在本结构体中的状态标记为SRI_S_DOWN主观下线。
(2)客观下线
当一个哨兵节点发现主节点处于主观下线状态是,会向其他的哨兵节点发出询问,该节点是不是已经主观下线了。如果超过配置参数quorum个节点认为是主观下线时,该哨兵节点就会将自己维护的结构体中该主节点标记为SRI_O_DOWN客观下线
询问命令SENTINEL is-master-down-by-addr
(3)leader选举
- 新当选的从节点执行 SLAVEOF no one,修改成主节点
(4)故障迁移
在从节点中挑选出新的主节点(通讯正常、优先级排序、优先级相同是选择offset最大的)
将该节点设置成新的主节点 SLAVEOF no one,并确保在后续的INFO命令时,该节点返回状态为master
将其他的从节点设置成从新的主节点复制, SLAVEOF命令
将旧的主节点变成新的主节点的从节点
九、Kafka
1.kafka
https://www.cnblogs.com/qingyunzong/p/9004509.html
(0)概念
kafka中有不同的broker,保存数据的是topic,一个topic被分为多个partition。一个partition又被分为多个segment,一个segment又被分为index和log两部分构成。
partion:负载均衡使用
topic中有partition,partition有副本,客服端读写都是找leader,不会找follower
消费者组:同组的不能够消费同一个分区,可以消费不同的分区,不同的组的可以消费同一个分区。
zookeeper:consumer和broker和zookeeper打交道,producer不会。
(1)broker.id每一个broker,唯一的int类型数据 (2)delete.topic.enable (3)logs.dirs存储数据的位置 (4)zookeeper的集群 (4)配置zookeeper (5)时间 7天,168小时 (6)大小 1G kafka-server-start
- 生产数据的流程
kafka-topics —create —zookeeper hadoop:2181 —partition 2 —replication-factor —topic first
副本数目不能够超过broker的数目。
kafka-console-consumer kafka-console-producer
启动的进程的时候要记录进程名和id,jps -l
新版本:offset维护在本地,需要和leader通信,这样就会提高效率,—bootsrap-server。
面试的时候介绍:
(1)数据的流程,
(2)生产过程分析:
- 每一个消息都会append到分区(partition),属于顺序写磁盘(保证吞吐率)
- 写:分区内有序,每一个消息都赋予了一个offset
- 分区原则(三种分区规则):指定partition,直接使用;指定了key,通过key的value进行hash;都没指定就采用轮询。
- 写入流程:1)producer首先从broker-list中获取partition的leader;2)producer将消息发送给leader;3)leader将消息写入到本地的log。4)follower从leader pull消息;5)写入本地的log后向leader发送ack。
- ACK应答(0(不管leader),1(不管follower),2(多完成))follower(如何producer不丢失数据—-ACK设置为2)
(3)存储
1)broker
2)zookeeper
broker————>[ids,topics,seqid], topics————->partitions——->state
consumer———->offset
(1)request.required.acks来设置数据的可靠性:
值 | 含义 | 可靠性 |
---|---|---|
0 | 发送即可,不管是否成功 | 会丢失数据 |
1 | Leader写成功即可 | 主备切换的时候可能丢失数据 |
-1 | ISR中所有的机器写成功才算成功 | 不会丢失数据 |
(2)Kafka的用途有哪些?使用场景如何?
(3)Kafka中的ISR、AR又代表什么?ISR的伸缩又指什么
Kafka的高可靠性的保障来源于其健壮的副本(replication)策略。
ISR:排好序,最接近的顺序排序,用于leader挂了选举用的。
分区中的所有副本统称为AR(Assigned Repllicas)。所有与leader副本保持一定程度同步的副本(包括Leader)组成ISR(In-Sync Replicas),ISR集合是AR集合中的一个子集。消息会先发送到leader副本,然后follower副本才能从leader副本中拉取消息进行同步,同步期间内follower副本相对于leader副本而言会有一定程度的滞后。前面所说的“一定程度”是指可以忍受的滞后范围,这个范围可以通过参数进行配置。与leader副本同步滞后过多的副本(不包括leader)副本,组成OSR(Out-Sync Relipcas),由此可见:AR=ISR+OSR。在正常情况下,所有的follower副本都应该与leader副本保持一定程度的同步,即AR=ISR,OSR集合为空。
(4)Kafka中的HW、LEO、LSO、LW等分别代表什么?
(5)Kafka中是怎么体现消息顺序性的?
kafka每个partition中的消息在写入时都是有序的,消费时,每个partition只能被每一个group中的一个消费者消费,保证了消费时也是有序的。整个topic不保证有序
(6)Kafka中的分区器、序列化器、拦截器是否了解?它们之间的处理顺序是什么?
(7)Kafka生产者客户端的整体结构是什么样子的?
(8)Kafka生产者客户端中使用了几个线程来处理?分别是什么?
(9)Kafka的旧版Scala的消费者客户端的设计有什么缺陷?
(10)“消费组中的消费者个数如果超过topic的分区,那么就会有消费者消费不到数据”这句话是否正确?如果不正确,那么有没有什么hack的手段?
(11)消费者提交消费位移时提交的是当前消费到的最新消息的offset还是offset+1?
offset+1
(12)有哪些情形会造成重复消费?
消费者消费后没有commit offset(程序崩溃/强行kill/消费耗时/自动提交偏移情况下unscrible)
(13)那些情景下会造成消息漏消费?
消费者没有处理完消息 提交offset(自动提交偏移 未处理情况下程序异常结束)
(14)KafkaConsumer是非线程安全的,那么怎么样实现多线程消费?
(15)简述消费者与消费组之间的关系
(16)当你使用kafka-topics.sh创建(删除)了一个topic之后,Kafka背后会执行什么逻辑?
创建topic后的逻辑:
删除topic后的逻辑:
(17)topic的分区数可不可以增加?如果可以怎么增加?如果不可以,那又是为什么?
分区可以增加
(18)topic的分区数可不可以减少?如果可以怎么减少?如果不可以,那又是为什么?
分区不可以减少,会丢失数据.ps:topic是可以删除的。
(19)创建topic时如何选择合适的分区数?
(20)Kafka目前有那些内部topic,它们都有什么特征?各自的作用又是什么?
__consumer_offsets 以双下划线开头,保存消费组的偏移
(21)优先副本是什么?它有什么特殊的作用?
(22)Kafka有哪几处地方有分区分配的概念?简述大致的过程及原理
(23)简述Kafka的日志目录结构
partition相当于一个大文件,平均分成了多个segment数据文件,每一个segment由两个文件构成×××.index (索引)和×××.log(数据)两部分组成。
(24)Kafka中有那些索引文件?
(25)如果我指定了一个offset,Kafka怎么查找到对应的消息?
(26)如果我指定了一个timestamp,Kafka怎么查找到对应的消息?
(27)聊一聊你对Kafka的Log Retention的理解
(28)聊一聊你对Kafka的Log Compaction的理解
(29)聊一聊你对Kafka底层存储的理解(页缓存、内核层、块层、设备层)
(30)聊一聊Kafka的延时操作的原理
(31)聊一聊Kafka控制器的作用
(32)消费再均衡的原理是什么?(提示:消费者协调器和消费组协调器)
(33)Kafka中的幂等是怎么实现的
(34)Kafka中的事务是怎么实现的(这题我去面试6家被问4次,照着答案念也要念十几分钟,面试官简直凑不要脸。实在记不住的话…只要简历上不写精通Kafka一般不会问到,我简历上写的是“熟悉Kafka,了解RabbitMQ….”)
(35)Kafka中有那些地方需要选举?这些地方的选举策略又有哪些?
(36)失效副本是指什么?有那些应对措施?
(37)多副本下,各个副本中的HW和LEO的演变过程
(38)为什么Kafka不支持读写分离?
(39)Kafka在可靠性方面做了哪些改进?(HW, LeaderEpoch)
(40)Kafka中怎么实现死信队列和重试队列?
(41)Kafka中的延迟队列怎么实现(这题被问的比事务那题还要多!!!听说你会Kafka,那你说说延迟队列怎么实现?)
(42)Kafka中怎么做消息审计?
(43)Kafka中怎么做消息轨迹?
(44)Kafka中有那些配置参数比较有意思?聊一聊你的看法
(45)Kafka中有那些命名比较有意思?聊一聊你的看法
(46)Kafka有哪些指标需要着重关注?
(47)怎么计算Lag?
(注意read_uncommitted和read_committed状态下的不同)
(48)Kafka的那些设计让它有如此高的性能?
(49)Kafka有什么优缺点?
(50)还用过什么同质类的其它产品,与Kafka相比有什么优缺点?为什么选择Kafka?
(51)在使用Kafka的过程中遇到过什么困难?怎么解决的?
(52)怎么样才能确保Kafka极大程度上的可靠性?
十、MySQL
0. MySQL结构
graph TB A1(客户端)-.->B1(连接器) B1-.->C1(优化器) C1-.->D1(执行器) D1-.->E1(存储引擎) E1-.->F1(InnoDB)
1.数据库引擎?(InnoDB和MyISAM区别)
编号 | 区别 | InnoDB | MyISAM |
---|---|---|---|
1 | 锁 | InnoDB 支持行级锁(row-level locking)和表级锁 | MyISAM 只有表级锁(table-level locking) |
2 | 索引 | (B+树,存具体数据,聚簇索引)其数据文件本身就是索引文件。相比MyISAM,其表数据文件本身就是按B+Tree组织的一个索引结构,树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。这被称为“聚簇索引(或聚集索引)”。 | (B+树,存地址,非聚簇索引)B+Tree叶节点的data域存放的是数据记录的地址。在索引检索的时候,首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为“非聚簇索引”。 |
3 | 事务 | InnoDB 提供事务支持事务,外部键等高级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。 | 强调的是性能,每次查询具有原子性,其执行数度比InnoDB类型更快,但是不提供事务支持。 |
4 | 外键 | InnoDB支持 | MyISAM不支持 |
5 | 崩溃恢复 | InnoDB支持 | MyISAM不支持 |
6 | MVCC | 应对高并发事务, MVCC比单纯的加锁更高效;MVCC只在 READ COMMITTED 和 REPEATABLE READ 两个隔离级别下工作;MVCC可以使用 乐观(optimistic)锁 和 悲观(pessimistic)锁来实现 |
MyISAM不支持 |
特变注意,两种数据库引擎对应的文件是不同的
- InnoDB:由数据库的结构文件和(数据+索引)两部分构成:test_innodb_lock.frm,test_innodb_lock.idb,叶子节点存储看数据,索引文件和数据文件合并了。聚集索引:数据和索引聚集在一起了,主键索引和其他索引之间的区别:其他索引存储的是主键,主键存的是data。为什么推荐整型数据自增?主要是在分裂的时候减少平衡操作。
- MyISAM:由数据库的结构文件,数据,索引三部分构成:test_myisam.frm,test_myisam.MYD,test_myisam.MYI,非聚集索引数据文件和索引文件是分开的。
2.sql语句
(1)建立一张表格?
1 | CREATE TABLE `user` ( |
(2)分页limit?
1 | SELECT a.* FROM 表 1 a, (select id from 表 1 where 条件 LIMIT 100000,20 ) b where a.id=b.id |
(3)成绩排名第三?
1 | select * from employees |
DML———数据库操作语言
DDL———数据库定义语言
3.什么是事务?
事务是逻辑上的一组操作,要么都执行,要么都不执行。
- 原子性: 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
- 一致性: 执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;
- 隔离性: 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
- 持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
4.并发事务带来哪些问题?
编号 | 名称 | 解释 |
---|---|---|
1 | 脏读(读了还没有提交的数据) | 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中, 这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据, 那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。(写读) |
2 | 数据丢失 | 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后, 第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。(写写) |
3 | 不可重复读(已提交事务更改数据) | 在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。(同一个事务中多次读)(read_view在read committed隔离级别下,read_view会在别的事务commit后更新,因此在这个事务中可能会不一样) |
4 | 幻读(针对其他事务已提交新增加的数据) | 它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。 在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录(同一个事务中多次读)(read_view在read repeatable 隔离级别下,read_view是在事务开始的时候建立的,当前的事务没有结束不会更新。) |
5.事务隔离级别?
编号 | 名称 | 解释 |
---|---|---|
1 | read uncommitted(读取未提交) | 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。 |
2 | read committed(读取已提交) | 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。 |
3 | repeatable read(可重复读) | 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。(默认的级别) |
4 | serializable(可串行化) | 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行 |
6.MySQL InnoDB的锁?
编号 | 名称 | 解释 |
---|---|---|
1 | Record lock(行锁) | 单个行记录上的锁 |
2 | Gap lock(间隙锁) | 间隙锁,锁定一个范围,不包括记录本身 |
3 | Next-key lock | record+gap 锁定一个范围,包含记录本身 |
7.大表优化?
(1)限定数据的范围:务必禁止不带任何限制数据范围条件的查询语句。
(2)读写分离:经典的数据库拆分方案,主库负责写,从库负责读;
(3)垂直分表
(4)水平分表
8.一条sql执行的过程?(基本结构+执行+日志)
(1)MySQL的基本架构
- 连接器: 身份认证和权限相关(登录 MySQL 的时候)。
- 查询缓存: 执行查询语句的时候,会先查询缓存(MySQL 8.0 版本后移除,因为这个功能不太实用)。
- 分析器: 没有命中缓存的话,SQL 语句就会经过分析器,分析器说白了就是要先看你的 SQL 语句要干嘛,再检查你的 SQL 语句语法是否正确。
- 优化器: 按照 MySQL 认为最优的方案去执行。
- 执行器: 执行语句,然后从存储引擎返回数据。
简单来说 MySQL 主要分为 Server 层和存储引擎层:
- Server 层:主要包括连接器、查询缓存、分析器、优化器、执行器等,所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图,函数等,还有一个通用的日志模块binglog 日志模块。
- 存储引擎: 主要负责数据的存储和读取,采用可以替换的插件式架构,支持 InnoDB、MyISAM、Memory 等多个存储引擎,其中 InnoDB 引擎有自有的日志模块 redolog 模块。现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始就被当做默认存储引擎了。
两个日志模块 redo log和bin log
更新的时候肯定要记录日志啦,这就会引入日志模块了,MySQL 自带的日志模块式 binlog(归档日志) ,所有的存储引擎都可以使用,我们常用的 InnoDB 引擎还自带了一个日志模块 redo log(重做日志),我们就以 InnoDB 模式下来探讨这个语句的执行流程。流程如下:
- 先查询到张三这一条数据,如果有缓存,也是会用到缓存。
- 然后拿到查询的语句,把 age 改为 19,然后调用引擎 API 接口,写入这一行数据,InnoDB 引擎把数据保存在内存中,同时记录 redo log,此时 redo log 进入 prepare 状态,然后告诉执行器,执行完成了,随时可以提交。
- 执行器收到通知后记录 binlog,然后调用引擎接口,提交 redo log 为提交状态。
- 更新完成。
9.一条sql很慢的原因?
一个 SQL 执行的很慢,我们要分两种情况讨论:
(1)大多数情况下很正常,偶尔很慢,则有如下原因
- 数据库在刷新脏页,例如 redo log 写满了需要同步到磁盘。
- 执行的时候,遇到锁,如表锁、行锁。
(2)这条 SQL 语句一直执行的很慢,则有如下原因。
- 没有用上索引:例如该字段没有索引;由于对字段进行运算、函数操作导致无法用索引。
- 数据库选错了索引。
10.MySQL join操作?
https://www.cnblogs.com/reaptomorrow-flydream/p/8145610.html
最常见的 JOIN 类型:SQL INNER JOIN(简单的 JOIN)、SQL LEFT JOIN、SQL RIGHT JOIN、SQL FULL JOIN
表A
id name 1 2 淘宝 3 微博 4 表B
id address 1 美国 5 中国 3 中国 6 美国
(1)Inner join
1 | select * from Table A inner join Table B |
执行以上SQL输出结果如下:
id | name | address |
---|---|---|
1 | 美国 | |
3 | 微博 | 中国 |
(2)left join
1 | select column_name(s) |
id | name | address |
---|---|---|
1 | 美国 | |
2 | 淘宝 | null |
3 | 微博 | 中国 |
4 | null |
(3)right join
1 | select column_name(s) |
id | name | address |
---|---|---|
1 | 美国 | |
5 | null | 中国 |
3 | 微博 | 中国 |
6 | null | 美国 |
(4)outer join
1 | select column_name(s) |
id | name | address |
---|---|---|
1 | 美国 | |
2 | 淘宝 | null |
3 | 微博 | 中国 |
4 | null | |
5 | null | 中国 |
6 | null | 美国 |
11.MySQL索引?
(0)底层的数据结构
B+树,主要是考虑二叉树的深度,主要考虑的是IO特别耗时间。一个节点会存储多个索引。
- 主键索引名为pk_字段名;
- 唯一索引名为 uk字段名;
- 普通索引名则为 idx_字段名。
(1)索引的类别?
编号 | 名称 | 解释 |
---|---|---|
1 | 普通索引 | 是最基本的索引,它没有任何限制. |
2 | 唯一索引 | 与前面的普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一. |
3 | 主键索引 | 是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。 |
4 | 组合索引 | 指多个字段上创建的索引,只有在查询条件中使用了创建一个字段,索引才会被使用。使用组合索引时遵循最左前缀。 |
5 | 全文索引 | 主要用来查找文本中的关键字,而不是直接与索引中的值相比较.fulltext索引跟其它索引大不相同, 它更像是一个搜索引擎,而不是简单的,其中语句的参数匹配.fulltext索引配合匹配操作使用,而不是一般的where语句加像。 |
(2)Hash索引和B+树所有有什么区别或者说优劣呢?
(3)InnoDB为什么需要主键?
(4)B树,B-树,B+树?
- B树:B树和平衡二叉树稍有不同的是B树属于多叉树又名平衡多路查找树(查找路径不只两个),数据库索引技术里大量使用者B树和B+树的数据结构。
- B-树:就是B树
- B+树:在B树基础上,最大的改动是B+树叶子节点存放了关键字,添加了索引。
12.阿里巴巴技术手册SQL?
【强制】表必备三字段:id, gmt_create, gmt_modified。
- 说明: 其中 id 必为主键,类型为 bigint unsigned、单表时自增、步长为 1。
- gmt_create,gmt_modified 的类型均为datetime 类型,前者现在时表示主动创建,后者过去分词表示被动更新。
【强制】在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度即可。
索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分度会高达 90%以上,可以使用 count(distinct left(列名, 索引长度))/count(*)的区分度来确定。
13.sql的explain?
14.超大分页处理?
(1)分页limit
【推荐】利用延迟关联或者子查询优化超多分页场景。(阿里巴巴技术手册)
说明: MySQL 并不是跳过 offset 行,而是取 offset+N 行,然后返回放弃前 offset 行,返回N 行,那当 offset 特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行 SQL 改写。 正例: 先快速定位需要获取的 id 段,然后再关联: SELECT a.* FROM 表 1 a, (select id from 表 1 where 条件 LIMIT 100000,20 ) b where a.id=b.id
数据库层面,这也是我们主要集中关注的(虽然收效没那么大),类似于select from table where age > 20 limit 1000000,10这种查询其实也是有可以优化的余地的. 这条语句需要load1000000数据然后基本上全部丢弃,只取10条当然比较慢. 当时我们可以修改为select from table where id in (select id from table where age > 20 limit 1000000,10).这样虽然也load了一百万的数据,但是由于索引覆盖,要查询的所有字段都在索引中,所以速度会很快. 同时如果ID连续的好,我们还可以select * from table where id > 1000000 limit 10,效率也是不错的,优化的可能性有许多种,但是核心思想都一样,就是减少load的数据
(2)在Spring boot中的实践?
15.触发器,视图?
16.什么是聚集索引?
17.数据库如何建立索引?
18. MVCC
- 版本链:trx_id
- read_view
- undo日志
- 记录版本链
- record header里的deleted flag设置为true
- begin/start transaction
不同的session查询到的数据可能不一致。read_view的时机生成的时机是session在执行第一条select语句,
19. Q&A?
19.1 简述数据库三大范式
第一范式是最基本的范式。如果数据库表中的所有字段值都是不可分解的原子值,就说明该数据库表满足了第一范式。
数据库第二范式:关系模式必须满足第一范式,并且所有非主属性都完全依赖于主码。注意,符合第二范式的关系模型可能还存在数据冗余、更新异常等问题。关系模型(学号,姓名,专业编号,专业名称)中,学号->姓名,而专业编号->专业名称,不满足数据库第二范式
数据库第三范式:关系模型满足第二范式,所有非主属性对任何候选关键字都不存在传递依赖。即每个属性都跟主键有直接关系而不是间接关系。接着以学生表举例,对于关系模型(学号,姓名,年龄,性别,所在院校,院校地址,院校电话)院校地址,院校电话和学号不存在直接关系,因此不满足第三范式。
19.2 简述MySQL的架构
MySQL可以分为应用层,逻辑层,数据库引擎层,物理层。
应用层:负责和客户端,响应客户端请求,建立连接,返回数据。
逻辑层:包括SQK接口,解析器,优化器,Cache与buffer。
数据库引擎层:有常见的MyISAM,InnoDB等等。
物理层:负责文件存储,日志等等。
19.3 简述执行SQL语言的过程
- 客户端首先通过连接器进行身份认证和权限相关
- 如果是执行查询语句的时候,会先查询缓存,但MySQL 8.0 版本后该步骤移除。
- 没有命中缓存的话,SQL 语句就会经过解析器,分析语句,包括语法检查等等。
- 通过优化器,将用户的SQL语句按照 MySQL 认为最优的方案去执行。
- 执行语句,并从存储引擎返回数据。
19.4 简述MySQL的共享锁排它锁
共享锁也称为读锁,相互不阻塞,多个客户在同一时刻可以同时读取同一个资源而不相互干扰。排他锁也称为写锁,会阻塞其他的写锁和读锁,确保在给定时间内只有一个用户能执行写入并防止其他用户读取正在写入的同一资源。
19.5 简述MySQL中的按粒度的锁分类
表级锁: 对当前操作的整张表加锁,实现简单,加锁快,但并发能力低。
行锁: 锁住某一行,如果表存在索引,那么记录锁是锁在索引上的,如果表没有索引,那么 InnoDB 会创建一个隐藏的聚簇索引加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。
Gap 锁:也称为间隙锁: 锁定一个范围但不包括记录本身。其目的是为了防止同一事物的两次当前读出现幻读的情况。
Next-key Lock: 行锁+gap锁。
19.6 如何解决数据库死锁
- 预先检测到死锁的循环依赖,并立即返回一个错误。
- 当查询的时间达到锁等待超时的设定后放弃锁请求。
19.7 简述乐观锁和悲观锁
乐观锁:对于数据冲突保持一种乐观态度,操作数据时不会对操作的数据进行加锁,只有到数据提交的时候才通过一种机制来验证数据是否存在冲突。
悲观锁:对于数据冲突保持一种悲观态度,在修改数据之前把数据锁住,然后再对数据进行读写,在它释放锁之前任何人都不能对其数据进行操作,直到前面一个人把锁释放后下一个人数据加锁才可对数据进行加锁,然后才可以对数据进行操作,一般数据库本身锁的机制都是基于悲观锁的机制实现的。
19.8 简述InnoDB存储引擎
InnoDB 是 MySQL 的默认事务型引擎,支持事务,表是基于聚簇索引建立的。支持表级锁和行级锁,支持外键,适合数据增删改查都频繁的情况。
InnoDB 采用 MVCC 来支持高并发,并且实现了四个标准的隔离级别。其默认级别是 REPEATABLE READ,并通过间隙锁策略防止幻读,间隙锁使 InnoDB 不仅仅锁定查询涉及的行,还会对索引中的间隙进行锁定防止幻行的插入。
19.9 简述MyISAM存储引擎
MySQL5.1及之前,MyISAM 是默认存储引擎。MyISAM不支持事务,Myisam支持表级锁,不支持行级锁,表不支持外键,该存储引擎存有表的行数,count运算会更快。适合查询频繁,不适合对于增删改要求高的情况
19.10 简述Memory存储引擎
Memory存储引擎将所有数据都保存在内存,不需要磁盘 IO。支持哈希索引,因此查找速度极快。Memory 表使用表级锁,因此并发写入的性能较低。
19.11索引是什么?
索引是存储引擎中用于快速找到记录的一种数据结构。在关系型数据库中,索引具体是一种对数据库中一列或多列的值进行排序的存储结构。
19.12 为什么引入索引?
为了提高数据查询的效率。索引对数据库查询良好的性能非常关键,当表中数据量越来越大,索引对性能的影响越重要。
但是,(1)索引会降低插入、删除、更新表的速度。(2)索引需要占据物理空间。聚集索引一旦改变,还需要改变非聚集索引。
19.13 Mysql有哪些常见索引类型?
- 数据结构角度
B-Tree索引 、哈希索引 、R-Tree索引 、全文索引(使用ES更好)
- 物理存储角度
主键索引(聚簇索引):叶子节点存的是整行的数据 非主键索引(二级索引):叶子节点存的主键的值
19.14 简述B-Tree与B+树
B-Tree 是一种自平衡的多叉树。每个节点都存储关键字值。其左子节点的关键字值小于该节点关键字值,且右子节点的关键字值大于或等于该节点关键字值。
B+树也是是一种自平衡的多叉树。其基本定义与B树相同,不同点在于数据只出现在叶子节点,所有叶子节点增加了一个链指针,方便进行范围查询。
B+树中间节点不存放数据,所以同样大小的磁盘页上可以容纳更多节点元素,访问叶子节点上关联的数据也具有更好的缓存命中率。并且数据顺序排列并且相连,所以便于区间查找和搜索。
B树每一个节点都包含key和value,查询效率比B+树高。
19.15 简述Hash索引
哈希索引对于每一行数据计算一个哈希码,并将所有的哈希码存储在索引中,同时在哈希表中保存指向每个数据行的指针。只有 Memory 引擎显式支持哈希索引。
Hash索引不支持范围查询,无法用于排序,也不支持部分索引列匹配查找。
19.16 简述自适应Hash索引
InnoDB对于频繁使用的某些索引值,会在内存中基于 B-Tree 索引之上再创键一个哈希索引,这也被称为自适应Hash索引。
19.17 简述聚集索引和稀疏索引
聚集索引按每张表的主键构建一棵B+树,数据库中的每个搜索键值都有一个索引记录,每个数据页通过双向链表连接。表数据访问更快,但表更新代价高。
稀疏索引不会为每个搜索关键字创建索引记录。搜索过程需要,我们首先按索引记录进行操作,并按顺序搜索,直到找到所需的数据为止。
19.18 简述辅助索引与回表查询
辅助索引是非聚集索引,叶子节点不包含记录的全部数据,包含了一个书签用来告诉InnoDB哪里可以找到与索引相对应的行数据。
通过辅助索引查询,先通过书签查到聚集索引,再根据聚集索引查对应的值,需要两次,也称为回表查询。
19.19 简述联合索引和最左匹配原则
联合索引是指对表上的多个列的关键词进行索引。
对于联合索引的查询,如果精确匹配联合索引的左边连续一列或者多列,则mysql会一直向右匹配直到遇到范围查询(>,<,between,like)就停止匹配。Mysql会对第一个索引字段数据进行排序,在第一个字段基础上,再对第二个字段排序。
联合索引怎么实现?采用B+树实现,每个节点含有多个关键字,排序时按照多个关键字来排序。最左前缀原则:顾名思义是最左优先,以最左边的为起点任何连续的索引都能匹配上,
注:在创建联合索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边。这样的话扩展性较好。
19.20 简述覆盖索引
覆盖索引指一个索引包含或覆盖了所有需要查询的字段的值,不需要回表查询,即索引本身存了对应的值。
19.21 为什么数据库不用红黑树用B+树
红黑树的出度为 2,而 B Tree 的出度一般都非常大。红黑树的树高 h 很明显比 B Tree 大非常多,IO次数很多,导致会比较慢,因此检索的次数也就更多。
B+Tree 相比于 B-Tree 更适合外存索引,拥有更大的出度,IO次数较少,检索效率会更高。
19.22 基于主键索引的查询和非主键索引的查询有什么区别?
对于select from 主键=XX,基于主键的普通查询仅查找主键这棵树,对于select from 非主键=XX,基于非主键的查询有可能存在回表过程(回到主键索引树搜索的过程称为回表),因为非主键索引叶子节点仅存主键值,无整行全部信息。
19.23 非主键索引的查询一定会回表吗?
不一定,当查询语句的要求字段全部命中索引,不用回表查询。如select 主键 from 非主键=XX,此时非主键索引叶子节点即可拿到主键信息,不用回表。
19.24 简述MySQL使用EXPLAIN 的关键字段
explain关键字用于分析sql语句的执行情况,可以通过他进行sql语句的性能分析。
字段 | 应用 |
---|---|
type:表示连接类型, | 从好到差的类型排序为: system:系统表,数据已经加载到内存里。 const:常量连接,通过索引一次就找到。 eq_ref:唯一性索引扫描,返回所有匹配某个单独值的行。 ref:非主键非唯一索引等值扫描,const或eq_ref改为普通非唯一索引。 range:范围扫描,在索引上扫码特定范围内的值。 index:索引树扫描,扫描索引上的全部数据。 all:全表扫描。 |
key:显示MySQL实际决定使用的键。 | |
key_len | 显示MySQL决定使用的键长度,长度越短越好 |
Extra:额外信息 | Using filesort:MySQL使用外部的索引排序,很慢需要优化。 Using temporary:使用了临时表保存中间结果,很慢需要优化。 Using index:使用了覆盖索引。 Using where:使用了where。 |
19.25 简述MySQL优化流程
- 通过慢日志定位执行较慢的SQL语句
- 利用explain对这些关键字段进行分析
- 根据分析结果进行优化
19.26 简述MySQL中的日志log
redo log: 存储引擎级别的log(InnoDB有,MyISAM没有),该log关注于事务的恢复.在重启mysql服务的时候,根据redo log进行重做,从而使事务有持久性。
undo log:是存储引擎级别的log(InnoDB有,MyISAM没有)保证数据的原子性,该log保存了事务发生之前的数据的一个版本,可以用于回滚,是MVCC的重要实现方法之一。
bin log:数据库级别的log,关注恢复数据库的数据。
19.27 简述事务
事务内的语句要么全部执行成功,要么全部执行失败。
事务满足如下几个特性:
- 原子性(Atomicity): 一个事务中的所有操作要么全部完成,要么全部不完成。
- 一致性(Consistency): 事务执行前后数据库的状态保存一致。
- 隔离性(Isolation) 多个并发事务对数据库进行操作,事务间互不干扰。
- 持久性(Durability) 事务执行完毕,对数据的修改是永久的,即使系统故障也不会丢失
19.28 数据库中多个事务同时进行可能会出现什么问题?
- 丢失修改
- 脏读:当前事务可以查看到别的事务未提交的数据。
- 不可重读:在同一事务中,使用相同的查询语句,同一数据资源莫名改变了。
- 幻读:在同一事务中,使用相同的查询语句,莫名多出了一些之前不存在的数据,或莫名少了一些原先存在的数据。
19.29 SQL的事务隔离级别有哪些?
读未提交: 一个事务还没提交,它做的变更就能被别的事务看到。
读提交: 一个事务提交后,它做的变更才能被别的事务看到。
可重复读: 一个事务执行过程中看到的数据总是和事务启动时看到的数据是一致的。在这个级别下事务未提交,做出的变更其它事务也看不到。
串行化: 对于同一行记录进行读写会分别加读写锁,当发生读写锁冲突,后面执行的事务需等前面执行的事务完成才能继续执行。
19.30 什么是MVCC?
MVCC为多版本并发控制,即同一条记录在系统中存在多个版本。其存在目的是在保证数据一致性的前提下提供一种高并发的访问性能。对数据读写在不加读写锁的情况下实现互不干扰,从而实现数据库的隔离性,在事务隔离级别为读提交和可重复读中使用到。
在InnoDB中,事务在开始前会向事务系统申请一个事务ID,该ID是按申请顺序严格递增的。每行数据具有多个版本,每次事务更新数据都会生成新的数据版本,而不会直接覆盖旧的数据版本。数据的行结构中包含多个信息字段。其中实现MVCC的主要涉及最近更改该行数据的事务ID(DB_TRX_ID)和可以找到历史数据版本的指针(DB_ROLL_PTR)。InnoDB在每个事务开启瞬间会为其构造一个记录当前已经开启但未提交的事务ID的视图数组。通过比较链表中的事务ID与该行数据的值与对应的DB_TRX_ID,并通过DB_ROLL_PTR找到历史数据的值以及对应的DB_TRX_ID来决定当前版本的数据是否应该被当前事务所见。最终实现在不加锁的情况下保证数据的一致性。
19.31 读提交和可重复读都基于MVCC实现,有什么区别?
在可重复读级别下,只会在事务开始前创建视图,事务中后续的查询共用一个视图。而读提交级别下每个语句执行前都会创建新的视图。因此对于可重复读,查询只能看到事务创建前就已经提交的数据。而对于读提交,查询能看到每个语句启动前已经提交的数据。
19.32 InnoDB如何保证事务的原子性、持久性和一致性?
利用undo log保障原子性。该log保存了事务发生之前的数据的一个版本,可以用于回滚,从而保证事务原子性。
利用redo log保证事务的持久性,该log关注于事务的恢复.在重启mysql服务的时候,根据redo log进行重做,从而使事务有持久性。
利用undo log+redo log保障一致性。事务中的执行需要redo log,如果执行失败,需要undo log 回滚。
19.33 MySQL是如何保证主备一致的?
MySQL通过binlog(二进制日志)实现主备一致。binlog记录了所有修改了数据库或可能修改数据库的语句,而不会记录select、show这种不会修改数据库的语句。在备份的过程中,主库A会有一个专门的线程将主库A的binlog发送给备库B进行备份。其中binlog有三种记录格式:
- statement:记录对数据库进行修改的语句本身,有可能会记录一些额外的相关信息。优点是binlog日志量少,IO压力小,性能较高。缺点是由于记录的信息相对较少,在不同库执行时由于上下文的环境不同可能导致主备不一致。
- row:记录对数据库做出修改的语句所影响到的数据行以及对这些行的修改。比如当修改涉及多行数据,会把涉及的每行数据都记录到binlog。优点是能够完全的还原或者复制日志被记录时的操作。缺点是日志量占用空间较大,IO压力大,性能消耗较大。
- mixed:混合使用上述两种模式,一般的语句使用statment方式进行保存,如果遇到一些特殊的函数,则使用row模式进行记录。MySQL自己会判断这条SQL语句是否可能引起主备不一致,如果有可能,就用row格式, 否则就用statement格式。但是在生产环境中,一般会使用row模式。
19.34 redo log与binlog的区别?
redo log是InnoDB引擎特有的,只记录该引擎中表的修改记录。binlog是MySQL的Server层实现的,会记录所有引擎对数据库的修改。
redo log是物理日志,记录的是在具体某个数据页上做了什么修改;binlog是逻辑日志,记录的是这个语句的原始逻辑。
redo log是循环写的,空间固定会用完;binlog是可以追加写入的,binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
19.35 crash-safe能力是什么?
InnoDB通过redo log保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为crash-safe。
19.36 WAL技术是什么?
WAL的全称是Write-Ahead Logging,它的关键点就是先写日志,再写磁盘。事务在提交写入磁盘前,会先写到redo log里面去。如果直接写入磁盘涉及磁盘的随机I/O访问,涉及磁盘随机I/O访问是非常消耗时间的一个过程,相比之下先写入redo log,后面再找合适的时机批量刷盘能提升性能。
19.37 两阶段提交是什么?
为了保证binlog和redo log两份日志的逻辑一致,最终保证恢复到主备数据库的数据是一致的,采用两阶段提交的机制。
- 执行器调用存储引擎接口,存储引擎将修改更新到内存中后,将修改操作记录redo log中,此时redo log处于prepare状态。
- 存储引擎告知执行器执行完毕,执行器生成这个操作对应的binlog,并把binlog写入磁盘。
- 执行器调用引擎的提交事务接口,引擎把刚刚写入的redo log改成提交commit状态,更新完成。
19.38 只靠binlog可以支持数据库崩溃恢复吗?
不可以。 历史原因:
- InnoDB在作为MySQL的插件加入MySQL引擎家族之前,就已经是一个提供了崩溃恢复和事务支持的引擎了。InnoDB接入了MySQL后,发现既然binlog没有崩溃恢复的能力,那引入InnoDB原有的redo log来保证崩溃恢复能力。 实现原因:
- binlog没有记录数据页修改的详细信息,不具备恢复数据页的能力。binlog记录着数据行的增删改,但是不记录事务对数据页的改动,这样细致的改动只记录在redo log中。当一个事务做增删改时,其实涉及到的数据页改动非常细致和复杂,包括行的字段改动以及行头部以及数据页头部的改动,甚至b+tree会因为插入一行而发生若干次页面分裂,那么事务也会把所有这些改动记录下来到redo log中。因为数据库系统进程crash时刻,磁盘上面页面镜像可以非常混乱,其中有些页面含有一些正在运行着的事务的改动,而一些已提交的事务的改动并没有刷上磁盘。事务恢复过程可以理解为是要把没有提交的事务的页面改动都去掉,并把已经提交的事务的页面改动都加上去这样一个过程。这些信息,都是binlog中没有记录的,只记录在了存储引擎的redo log中。
- 操作写入binlog可细分为write和fsync两个过程,write指的就是指把日志写入到文件系统的page cache,并没有把数据持久化到磁盘,fsync才是将数据持久化到磁盘的操作。通过参数设置sync_binlog为0的时候,表示每次提交事务都只write,不fsync。此时数据库崩溃可能导致部分提交的事务以及binlog日志由于没有持久化而丢失。
19.39 简述MySQL主从复制
MySQL提供主从复制功能,可以方便的实现数据的多处自动备份,不仅能增加数据库的安全性,还能进行读写分离,提升数据库负载性能。
主从复制流程:
- 在事务完成之前,主库在binlog上记录这些改变,完成binlog写入过程后,主库通知存储引擎提交事物
- 从库将主库的binlog复制到对应的中继日志,即开辟一个I/O工作线程,I/O线程在主库上打开一个普通的连接,然后开始binlog dump process,>将这些事件写入中继日志。从主库的binlog中读取事件,如果已经读到最新了,线程进入睡眠并等待ma主库产生新的事件。
读写分离:即只在MySQL主库上写,只在MySQL从库上读,以减少数据库压力,提高性能。
19.40 如何保证数据库和缓存数据一致
binlog刷新缓存。
20. MySQL事务和Spring事务
spring事务本质上使用数据库事务,而数据库事务本质上使用数据库锁,所以spring事务本质上使用数据库锁,开启spring事务意味着使用数据库锁。假如数据库不支持事务的话,spring的事务是没有作用的.数据库的事务说简单就只有开启,回滚和关闭,spring对数据库事务的包装,原理就是拿一个数据连接,根据spring的事务配置,操作这个数据连接对数据库进行事务开启,回滚或关闭操作.但是spring除了实现这些,还配合spring的传播行为对事务进行了更广泛的管理.其实这里还有个重要的点,那就是事务中涉及的隔离级别,以及spring如何对数据库的隔离级别进行封装
20.1 Spring 事务的传播属性
所谓spring事务的传播属性,就是定义在存在多个事务同时存在的时候,spring应该如何处理这些事务的行为。这些属性在TransactionDefinition中定义,具体常量的解释见下表:默认的传播级别是什么?
常量名称 | 常量解释 |
---|---|
PROPAGATION_REQUIRED | 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择,也是 Spring 默认的事务的传播。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。新建的事务将和被挂起的事务没有任何关系,是两个独立的事务,外层事务失败回滚之后,不能回滚内层事务执行的结果,内层事务失败抛出异常,外层事务捕获,也可以不处理回滚操作 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 支持当前事务,如果当前没有事务,就抛出异常。(ps: mandatory强制的; 法定的; 义务的;) |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。 |
传播行为和数据库功能无关,只是事务管理器为了处理复杂业务而设计的一个机制。比如现在有这样一个调用场景,A Service -> B Service -> C Service
,但是希望A/B在一个事务内,C是一个独立的事务,同时C如果出错,不影响AB所在的事务。此时,就可以通过传播行为来处理;将C Service的事务配置为@Transactional(propagation = Propagation.REQUIRES_NEW)
即可
20.2 数据库隔离级别
隔离级别 | 隔离级别的值 | 导致的问题 |
---|---|---|
Read-Uncommitted | 0 | 导致脏读 |
Read-Committed | 1 | 避免脏读,允许不可重复读和幻读 |
Repeatable-Read | 2 | 避免脏读,不可重复读,允许幻读 |
Serializable | 3 | 串行化读,事务只能一个一个执行,避免了脏读、不可重复读、幻读。执行效率慢,使用时慎重 |
20.3 Spring中的隔离级别
常量 | 解释 |
---|---|
ISOLATION_DEFAULT | 这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与 JDBC 的隔离级别相对应。 |
ISOLATION_READ_UNCOMMITTED | 这是事务最低的隔离级别,它充许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。 |
ISOLATION_READ_COMMITTED | 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。 |
ISOLATION_REPEATABLE_READ | 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。 |
ISOLATION_SERIALIZABLE | 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。 |
20.4 Spring 事务同步管理器的原理
20.4.1 事务管理的关键抽象
graph TB B1(PlatformTransactionManager)<-.->A1(TransactionDefinition) B1-.->C1(TransactionStatus)-.->
- TransactionDefinition:描述事务的隔离级别、超时时间、是否为只读事务、事务的传播规则等控制事务具体行为的事务属性。
- PlatformTransactionManager:根据TransactionDefinition提供的事务属性配置创建事务。(采用事务管理器提交/回滚事务操作)
- TransactionStatus:描述事务的状态。
20.4.2 ThreadLocal的使用
Spring将JDBC的Connection,Hibernate的Session等访问数据库的连接或者会话对象统称为资源,这些资源在同一时刻是不能够被多线程共享的,为了让DAO,Service类可以做到singleton,Spring的事务同步管理器Transaction SynchronizationManager使用ThreadLocal为不同的事务提供独立的资源。
20.4.3 编程式事务、申明式事务
- try catch住事务提交还是回滚?答案:提交,异常没有提交给AOP拦截!!!使用@Transactional注解的时候不要使用try catch,如果try catch后需要手动使用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();回滚。
十一、tomcat等web服务
十二、RPC
https://xiaomi-info.github.io/2020/03/02/rpc-achieve/
- Proxy、InvocationHandler
- 网络通信
- 序列化、反序列化,编解码
- 管理服务地址
- 响应
本文主要论述的是“RPC 实现原理”,那么首先明确一个问题什么是 RPC 呢?RPC 是 Remote Procedure Call 的缩写,即,远程过程调用。RPC 是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而开发人员无需额外地为这个交互编程。
值得注意是,两个或多个应用程序都分布在不同的服务器上,它们之间的调用都像是本地方法调用一样。接下来我们便来分析一下一次 RPC 调用发生了些什么?
现在业界内比较流行的一些 RPC 框架,例如 Dubbo 提供的是基于接口的远程方法调用,即客户端只需要知道接口的定义即可调用远程服务。在 Java 中接口并不能直接调用实例方法,必须通过其实现类对象来完成此操作,这意味着客户端必须为这些接口生成代理对象,对此 Java 提供了 Proxy、InvocationHandler 生成动态代理的支持;生成了代理对象,那么每个具体的发方法是怎么调用的呢?jdk 动态代理生成的代理对象调用指定方法时实际会执行 InvocationHandler 中定义的 #invoke 方法,在该方法中完成远程方法调用并获取结果。
抛开客户端,回过头来看 RPC 是两台计算机间的调用,实质上是两台主机间的网络通信,涉及到网络通信又必然会有序列化、反序列化,编解码等一些必须要考虑的问题;同时实际上现在大多系统都是集群部署的,多台主机/容器对外提供相同的服务,如果集群的节点数量很大的话,那么管理服务地址也将是一件十分繁琐的事情,常见的做法是各个服务节点将自己的地址和提供的服务列表注册到一个 注册中心,由 注册中心 来统一管理服务列表;这样的做法解决了一些问题同时为客户端增加了一项新的工作——那就是服务发现,通俗来说就是从注册中心中找到远程方法对应的服务列表并通过某种策略从中选取一个服务地址来完成网络通信。
聊了客户端和 注册中心,另外一个重要的角色自然是服务端,服务端最重要的任务便是提供服务接口的真正实现并在某个端口上监听网络请求,监听到请求后从网络请求中获取到对应的参数(比如服务接口、方法、请求参数等),再根据这些参数通过反射的方式调用接口的真正实现获取结果并将其写入对应的响应流中。
综上所述,一次基本的 RPC 调用流程大致如下:
- 生产者端流程:
- 加载服务接口,并缓存
- 服务注册,将服务接口以及服务主机信息写入注册中心(本例使用的是 zookeeper)
- 启动网络服务器并监听
- 反射,本地调用
- 消费者端流程:
- 代理服务接口生成代理对象
- 服务发现(连接 zookeeper,拿到服务地址列表,通过客户端负载策略获取合适的服务地址)
- 远程方法调用(本例通过 Netty,发送消息,并获取响应结果)
十三、服务发现设计
1. 为什么需要服务发现?
为什么一定需要一个服务发现系统呢?服务启动的时候直接读取一个本地配置,然后通过远程配置系统,动态推送下来不行吗?实际上,当服务节点规模较小时,该方案也行得通,但如果遇到以下的场景呢?
场景 |
---|
1. 在微服务的世界中,服务节点的扩缩容、服务版本的迭代是常态,服务消费端需要能够快速及时的感知到节点信息的变更(网络地址、节点数量)。 |
2. 当服务节点规模巨大时,节点的不可用也会变成常态,服务提供者要能够及时上报自己的健康状态,从而做到及时剔除不健康节点(或降低权重)。 |
3. 当服务部署在多个可用区时,需要将多个可用区的服务节点信息互相同步,当某个可用区的服务不可用时,服务消费者能够及时切换到其他可用区(通过负载均衡算法自动切换或手动紧急切换),从而做到多活和高可用。 |
4. 服务发现背后的存储应该是分布式的,这样当部分服务发现节点不可用的时候,也能提供基本的服务发现功能 |
5. 除了ip、port我们需要更多的信息,比如节点权重、路由标签信息等等。 |
2. 服务发现设计?
2.1 客户端发现模式
2.2 服务端发现模式
3. 服务发现中的数据一致性问题
4. 健康检查
5. 服务优雅上下线
6. 容灾和高可用
十四、设计模式
1. 设计模式大纲?
编号 | 设计模式 | 解释 | 举例子 |
---|---|---|---|
1 | 观察者模式 | 表示对象与对象之间具有依赖关系,当一个对象发生改变的时候,这个对象依赖的对象也会作出响应.(Observable,Obser) | Spring 事件驱动模型就是观察者模式很经典的⼀个应⽤。 |
2 | 装饰者模式 | 不修改底层代码的前提下,给对象赋予新的职责 | |
3 | 工厂模式 | BeanFactory(AppliclicationContext) | |
4 | 单例模式 | 创建独一无二的,只能有一个的实例对象。(懒汉,恶汉) | Spring Bean默认是单例模式 |
5 | 命令模式 | ||
6 | 适配器模式和外观模式 | 适配器模式将一个接口转换成客户希望的另外一个接口,适配器模式使接口不兼容的那些类可以一起工作 | Spring AOP 的增强或通知(Advice)使⽤到了适配器模式、Spring MVC 中也是⽤到了适配器模式适配 Controller 。 |
7 | 模板方法模式 | Mysql,Redis,Kafaka,MongoDB(Spring中jdbcTemplate, hibernateTemplate) | |
8 | 迭代和组合模式 | ||
9 | 状态模式 | ||
10 | 代理模式 | Spring AOP的实现 | |
11 | 复合模式 |