两种方式:RDB和AOF
通过快照的方式,当符合一定条件时就会自动将内存中的所有数据生成一份副本存到磁盘中,根据配置文件,SAVE或BGSAVE命令。
配置文件:配置文件中设置了两个参数,一个是时间串口M,一个是改动的键的个数N,每当时间M内被改动的键的个数大于N时,就生成新的RDB文件替换原来的RDB文件存到磁盘。
SAVE:用户执行save命令同步执行快照时,在快照执行过程中会阻塞所有来自客户端的请求,导致redis长时间不响应。
BGSAVE:后台异步执行快照
过程:(1)redis使用fork函数复制一份当前进程(父进程)的副本(子进程)
(2)父进程继续接收并处理客户端发来的命令,而子进程开始将内存中的数据写入硬盘中的临时文件
(3)当子进程写入完所有数据后会用该临时文件替换旧的RDB文件
默认不开启,开启之后在每次执行命令后将命令本身记录下来。
当AOF记录冗余的命令时,会在配置文件中设置一定的参数,当达到当前参数的值时(比如文件大小超过了配置文件中设置的大小的值),重写AOF文件。
主数据库可以进行读写操作,从数据库保留主数据的副本,当主数据库写操作导致数据变化时,会自动把数据同步给从数据库,而从数据库一般是只读的。
过程:当一个从数据库启动后,会向主数据库发送SYNC的命令,主数据库收到连接命令后会开始在后台保存快照,并将保存期间接收到的命令缓存起来。当快照完成后,就会把快照文件和缓存命令发送给从数据库。从数据库收到之后就会载入快照文件并执行命令。这个过程也叫复制初始化。
若主从的连接断开重连:以前的版本是重新进行复制初始化,缺点是恢复过程的效率低下;现在的版本的一个改进是能够支持有条件的增量传输,重连后,主数据库只要将断连期间执行的命令发送给从数据库即可。
读写分离,适合读多写少的场景;当主数据库崩溃时,可以从从数据库恢复。
增量复制(重连使用):
三个基础:(1)从数据库会存储主数据库的运行ID(每个redis运行实例都有自己唯一的ID)
(2)在复制同步阶段,主数据库每将一个命令传送给从数据库时,都会同时把该命令存放到一个
积压队列中,并记录下当前积压队列中存放命令的偏移量的范围
(3)同时,从数据库接收到主数据库传来的命令时,会记录下该命令的偏移量
过程:(1)首先主数据库会判断从数据库传过来的ID跟自己的ID是不是一样的,确保从数据库之前是和自己
同步的。
(2)判断从数据库最后同步成功的命令的偏移量是否在积压队列中,如果在则可以执行增量复制,并
将积压队列中相应的命令发送给从数据库
作用:(1)监控主数据库和从数据库是否正常运行(哨兵之间可以相互监控)
(2)主数据库出现故障时自动将从数据库转换为主数据库
哨兵定时执行的3个操作:
(1)每10s哨兵会向主数据库和从数据库发送INFO命令,可以获得当前数据库的相关信息从而实现新节点的
自动发现
(2)每2s哨兵会向主数据库和从数据库发送自己的信息(哨兵的地址,端口等)
(3)每1s哨兵会向主数据库,从数据库和其他哨兵发送ping命令,监控数据库是否还在服务
RAFT算法(选哨兵)
作用:当监控到主数据库下线时,哨兵们会选举一个领头哨兵对主数据库进行故障恢复
过程:(1)发现主数据库下线的哨兵节点(A)向每个哨兵节点发送命令,要求对方选自己成为领头哨兵
(2)如果目标哨兵没有选过其他人,则会统一将A设置成领头哨兵
(3)如果A发现又半数且超过quorm参数值的哨兵统一选择其成为领头哨兵,则A成功成为领头哨兵
(4)如果又多个哨兵节点同时参与选举,则会出现没有任何哨兵当选的可能。此时随机等待一个时
间后重新选举
原因:即使使用哨兵,redis集群的每个数据库依然存有集群中的所有数据,从而导致集群的总数据存储量受限于可用存储内存最小的数据库节点,形成木桶效应。
原理是:不同的节点管理不同的插槽,一个集群中共有16384个插槽
节点的增加:在集群中的任一节点(B)向新节点(A)发送CLUSTER MEET ip port的命令,新节点A接收到这个命令后,会与B进行握手,使B把A当作当前集群中的一员。握手成功后,B会使用Gossip协议将节点A的消息通知给集群中的每一个节点。
插槽分配:
新的节点加入集群后,要么复制每个主数据库来以从数据库的形式运行,要么向集群申请分配插槽来以主数据库的形式运行
在一个集群中,所有的键会被分配给16384的插槽,而每个主数据库会负责处理其中的一部分插槽。
两种情况:
(1)插槽之前没有被分配过,现在想分配给指定节点
使用CLUSTER ADD SLOT S的命令实现
(2)插槽之前被分配过,现在想移动到指定节点
若槽中无数据,可用CLUSTER SETSLOT 插槽号 NODE 新节点的运行ID,若槽中有数据,数据是不会被
迁移的,会导致数据的丢失。因此还要把数据迁移过去
迁移过程发生客户端的访问的处理:0号插槽从A迁移到B的过程中,当客户端向A请求插槽0中的键时,如果键存在,正常返回,否则A就会返回一个move的重定向请求,客户端接收这个请求后再向B发送请求。
获取与插槽对应的节点:
(1)每个键键名的有效部分使用CRC16算法计算哈希值,然后取对16384的余数,分配到16384个插槽
(2)使用-c参数来实现自动重定向
(3)请求重定向会影响性能,优化方法是设置缓存,缓存插槽的路由信息,先到缓存里面找插槽所在的节点
故障查找
(1)每个节点会定期向其他节点发送ping命令,来判断是否有节点下线
(2)一旦A节点认为B疑似下线,会在集群中传播这个消息,所有其他节点收到消息后会记录这一信息
(3)当C收集到半数以上的节点认为B是疑似下线,就会把B设置为下线,并通知其他节点,让B在整个集群
中下线
若下线的是主数据库,且存在从数据库,对从数据库使用raft算法选一个从数据库转为主数据库
LRU算法(Redis的并发竞争问题)
内部实现采用epoll
Redis为单进程单线程模式,采用队列模式将并发访问变为串行访问。Redis本身没有锁的概念,Redis对于多个客户端连接并不存在竞争
采用了线程封闭的观念,把任务封闭在一个线程,自然避免了线程安全问题,不过对于需要依赖多个redis操作的复合操作来说,依然需要锁,而且有可能是分布式锁。
先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放 。因为把setnx和expire合成一条指令来用的 ,所以如果在setnx之后执行expire之前进程意外crash或者要重启维护的时候,不会造成锁无法释放。
假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来?
使用keys指令可以扫出指定模式的key列表。
如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?
redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。
提供了对数据库ACID(原子性,一致性,隔离性,持久性)事务支持,提供了行级锁和外键约束,他的设计目标是处理大容量的数据库系统。MYSQL执行Innodb会在内存中建立缓冲池,用于缓冲数据和索引。但没有保存表的行数,当要获取表的行数时要扫描全表。因为支持行级锁,锁的粒度比较小,写操作不会锁定全表,所以在并
发较高的情况时,使用这个引擎会提升效率。
1. show status like 'innodb_row_lock%'
2. Show engine innodb status\G;
共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。
另外,为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁。
意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。
该引擎是MYSQL的默认引擎,没有提供对数据库事务的支持,也不支持行级锁和外键,因此写操作需要锁定整个表,效率较低。该引擎存储了表的行数,当需要表的行数,不需要扫描全表,可以直接获取。当读操作远远多于写操作时,用这个引擎就很不错。
两个引擎的索引都是用B+树的数据结构,主要有两个区别
MYIASM分为两个文件,一个是数据文件,一个是索引文件,检索的时候先搜索索引文件,获得对应的数据的地址,然后根据地址在数据文件中取数据;
Innodb的数据文件本身就是索引文件,一般是主键作为索引的key值,检索时根据主键找到对应的数据
MYIASM的索引文件的数据域存储的是真实数据在数据文件中的地址;
Innodb也可以用辅助索引文件,只是该索引文件的数据域存放的是数据的主键,当在辅助索引文件中找到主键后,通过主键在数据文件中找到真实数据。因此需要检索两遍
大量的数据存储趋向于选择Innod,因为它支持事务处理和故障恢复。Innob可以利用事务日志进行数据恢复
根节点至少有两个子节点
每个节点最多有M-1关键值和M个子节点
除根节点外其他节点至少有M/2个子节点
有k个子节点必然有k个关键关键值
非叶子节点仅具有索引的作用,跟记录有关的信息都放在叶子节点
树的所有叶子节点构成一个有序链表
每个节点或者是黑色,或者是红色
根节点是黑色
每个叶子节点(NIL)是黑色[注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
如果一个节点是红色的,则它的子节点必须是黑色的
从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点
红黑树每个节点只有左右两个子节点,树的深度过大,造成磁盘IO读写过于频繁,进而导致效率低下 ,而B树可以有多个子女,减少树的高度,极大提高查询效率
往往通过硬件技术支持,比如事务日志回滚,因此不会存储多份来保证
使用paxos和Raft
见上面的分布式数据一致性
三个步骤:选举领头数据库,日志复制,安全保证
Raft算法中,对节点的状态分为3种角色,分别是Leader,Follower和Candidate
Leader:负责处理来自客户端的请求,负责将日志同步到Follower中,并收集Follower发送过来的心跳
Follower:当集群刚刚启动时,所有节点均为Follower状态,它的主要工作是响应Leader的日志同步请求,响应Candidate的请求,以及把请求到Follower中的事务请求转发给leader
Candidate:选举leader时负责投票,选举出leader后,节点将candidate状态变为leader状态
Raft把时间划分为一个个时间段(term),每个时间段有以下原则:
Raft的选举定时器触发之后,每个节点的触发时间都不相同
所有节点开始状态为Follower,当定时器触发选举后该节点Term编号递增,该节点的状态由Follower转为Candidate,并向其他节点发起投票请求。选举由三种情况:
当Leader选出来后,所有的事务操作必须要经过Leader的处理。这些事务操作成功之后,会被按顺序写入日志中,此时不提交。
然后leader发送请求将日志同步到follower中,follower返回ack响应。当leader接到超过半数的follower的ack响应后,提交事务。
当某个follower在同步日志时发生了失败,但未来该follower又可能被选举为leader时,就有可能导致前一个leader已经提交的日志发生覆盖,这样就导致节点执行不同序列的日志
安全性体现在选举出来的leader一定包含之前已经提交的日志机制,主要遵循的原则如下:
HAVING语句通常与GROUP BY语句联合使用,用来过滤由GROUP BY语句返回的记录集
HAVING语句的存在弥补了WHERE关键字不能与聚合函数联合使用的不足。
Where,Group By,Having,Order By的执行顺序如下:首先Where将原始记录中不满足条件的记录删除(所以应该在where中尽量将不满足条件的记录筛掉,这样可以减少分组),然后通过Group By关键字后面指定的分组条件将筛选得到的试图进行分组,接着系统根据Havig关键字后面指定的筛选条件,将分组视图后不满足条件的记录筛掉,然后按照Order By语句对试图进行排序,这样最终结果就产生了。
建议看这个博客 很全面
Atomicity:原子性
Consistency:持久性
Isolation:隔离性
Durability:一致性
保证原子性,就是在异常发生时,对已经执行的操作进行回滚。Mysql中的恢复机制是用回滚日志(undo log),所有事务的修改都会先记录到回滚日志当中,然后再对数据库进行写入。当错误发生时,数据库会按照日志逻辑地撤销之前的修改。
一旦事务提交,那么数据就会被写入数据库持久保存起来,无法再次回滚。通过重做日志来实现的(redo log)。重做日志由两部分组成:一是内存中的重做日志缓冲区,这是易失的,二是磁盘上的重做日志文件,这是持久的。
当我们尝试对数据进行修时,它先将数据从磁盘取到内存,并更新内存中缓存的数据,然后生成一条重做日志写入重做日志缓存中。当事务提交时,会先把重做日志缓存刷新到重做日志文件中,再将数据修改到数据库中。如果事务提交后还没来得及写入数据库就宕机了,可以从重做日志中恢复,保证了数据的持久性。
用来解决并行事务带来的问题,通过锁或者时间戳来实现。四种数据库的事务的隔离级别:READ UNCOMMITED
、READ COMMITED
、REPEATABLE READ
和 SERIALIZABLE
;每个事务的隔离级别其实都比上一级多解决了一个问题 ,一级比一级更加严格
乐观锁和悲观锁其实都是并发控制的机制,同时它们在原理上就有着本质的差别;
CAP 定理中的数据一致性,分布式系统中的各个节点中对于同一数据的拷贝有着相同的值;
ACID 中的一致性是指数据库的规则,如果 schema 中规定了一个值必须是唯一的,那么一致的系统必须确保在所有的操作中,该值都是唯一的
根据下面的两个博客整理的
https://cloud.tencent.com/developer/article/1328292
https://www.cnblogs.com/butterfly100/p/9034281.html
进行分库分表,数据切分根据其切分类型,可以分为两种方式:垂直(纵向)切分和水平(横向)切分 。
垂直分库
垂直分库就是根据业务耦合性,将关联度低的不同表存储在不同的数据库。做法与大系统拆分为多个小系统类似,按业务分类进行独立划分。与"微服务治理"的做法相似,每个微服务使用单独的一个数据库。如图:
垂直分表
垂直分表是基于数据库中的"列"进行,某个表字段较多,可以新建一张扩展表,将不经常用或字段长度较大的字段拆分出去到扩展表中。在字段很多的情况下(例如一个大表有100多个字段),通过"大表拆小表",更便于开发与维护,也能避免跨页问题,MySQL底层是通过数据页存储的,一条记录占用空间过大会导致跨页,造成额外的性能开销。另外数据库以行为单位将数据加载到内存中,这样表中字段长度较短且访问频率较高,内存能加载更多的数据,命中率更高,减少了磁盘IO,从而提升了数据库性能。
优点:
缺点