数据库基础知识整理
- 1. 什么是数据库, 数据库管理系统, 数据库系统, 数据库管理员?
- 2. 什么是元组, 码, 候选码, 主码, 外码, 主属性, 非主属性?
- 3. 主键和外键有什么区别?
- 4. 为什么不推荐使用外键与级联?
- 5. 什么是 ER 图?
- 6. 数据库范式了解吗?
- 7. 什么是存储过程?
- 8. drop、delete 与 truncate 区别?
- 9. 数据库设计通常分为哪几步?
- 字符集
- MySQL知识点&面试题总结
- 1. MySQL 基础
- 2. MySQL 介绍
- 存储引擎
- 1. 存储引擎相关的命令
- 2. MyISAM 和 InnoDB 的区别
- 3. 锁机制与 InnoDB 锁算法
- 4. 查询缓存
- 事务
- 2. 何为 ACID 特性呢?
- 3. 数据事务的实现原理呢?(以 MySQL 的 InnoDB 引擎为例)
- 4. 并发事务带来哪些问题?
- 5. 事务隔离级别有哪些?
- 6. MySQL 的默认隔离级别是什么?
- Redis 知识点&面试题总结
- 1. 简单介绍一下 Redis
- 2. 分布式缓存常见的技术选型方案有哪些?
- 3. Redis 和 Memcached 的区别和共同点
- 2. 缓存数据的处理流程是怎样的?
- 3. 为什么要用 Redis/为什么要用缓存?
- 4. Redis 除了做缓存,还能做什么?
- 5. Redis 没有使用多线程?为什么不使用多线程?
- 6. Redis 给缓存数据设置过期时间有啥用?
- 7. Redis 是如何判断数据是否过期的呢?
- 8. 过期的数据的删除策略了解么?
- 9. Redis 内存淘汰机制了解么?
- 10. Redis 持久化机制(怎么保证 Redis 挂掉之后再重启数据可以进行恢复)
- 11. Redis bigkey
- 12. Redis 事务
- 13. Redis 可以做消息队列么?
- 14. 缓存穿透
- 15. 缓存雪崩
- 16. 如何保证缓存和数据库数据的一致性?
- 参考
本资料源于javaguide,本人只为整理方便自己熟记,不适合学习。详细请访问javaguide网站或微信公众号。
javaguide
1. 什么是数据库, 数据库管理系统, 数据库系统, 数据库管理员?
- 数据库 : 数据库(DataBase 简称 DB)就是信息的集合或者说数据库是由数据库管理系统管理的数据的集合。
- 数据库管理员 : 数据库管理员(Database Administrator, 简称 DBA)负责全面管理和控制数据库系统。
- 数据库系统 : 数据库系统(Data Base System,简称 DBS)通常由软件、数据库和数据管理员(DBA)组成。
- 数据库管理系统 : 数据库管理系统(Database Management System 简称 DBMS)是一种操纵和管理数据库的大型软件,通常用于建立、使用和维护数据库。
2. 什么是元组, 码, 候选码, 主码, 外码, 主属性, 非主属性?
元组 : 元组(tuple)是关系数据库中的基本概念,关系是一张表,表中的每行(即数据库中的每条记录)就是一个元组,每列就是一个属性。 在二维表里,元组也称为行。
码 :码就是能唯一标识实体的属性,对应表中的列。
候选码 : 若关系中的某一属性或属性组的值能唯一的标识一个元组,而其任何、子集都不能再标识,则称该属性组为候选码。例如:在学生实体中,“学号”是能唯一的区分学生实体的,同时又假设“姓名”、“班级”的属性组合足以区分学生实体,那么{学号}和{姓名,班级}都是候选码。
主码 : 主码也叫主键。主码是从候选码中选出来的。 一个实体集中只能有一个主码,但可以有多个候选码。
外码 : 外码也叫外键。如果一个关系中的一个属性是另外一个关系中的主码则这个属性为外码。
主属性 : 候选码中出现过的属性称为主属性。比如关系 工人(工号,身份证号,姓名,性别,部门). 显然工号和身份证号都能够唯一标示这个关系,所以都是候选码。工号、身份证号这两个属性就是主属性。如果主码是一个属性组,那么属性组中的属性都是主属性。
非主属性: 不包含在任何一个候选码中的属性称为非主属性。比如在关系——学生(学号,姓名,年龄,性别,班级)中,主码是“学号”,那么其他的“姓名”、“年龄”、“性别”、“班级”就都可以称为非主属性。
3. 主键和外键有什么区别?
主键(主码) :主键用于唯一标识一个元组,不能有重复,不允许为空。一个表只能有一个主键。
外键(外码) :外键用来和其他表建立联系用,外键是另一表的主键,外键是可以有重复的,可以是空值。一个表可以有多个外键。
4. 为什么不推荐使用外键与级联?
- 增加了复杂性: a. 每次做DELETE 或者UPDATE都必须考虑外键约束,会导致开发的时候很痛苦, 测试数据极为不方便; b. 外键的主从关系是定的,假如那天需求有变化,数据库中的这个字段根本不需要和其他表有关联的话就会增加很多麻烦。
- 增加了额外工作: 数据库需要增加维护外键的工作,比如当我们做一些涉及外键字段的增,删,更新操作之后,需要触发相关操作去检查,保证数据的的一致性和正确性,这样会不得不消耗资源;(个人觉得这个不是不用外键的原因,因为即使你不使用外键,你在应用层面也还是要保证的。所以,我觉得这个影响可以忽略不计。)
- 对分库分表不友好 :因为分库分表下外键是无法生效的。
…
5. 什么是 ER 图?
E-R 图 也称实体-联系图(Entity Relationship Diagram),提供了表示实体类型、属性和联系的方法,用来描述现实世界的概念模型。 它是描述现实世界关系概念模型的有效方法。 是表示概念关系模型的一种方式。
下图是一个学生选课的 ER 图,每个学生可以选若干门课程,同一门课程也可以被若干人选择,所以它们之间的关系是多对多(M: N)。另外,还有其他两种关系是:1 对 1(1:1)、1 对多(1: N)。
我们试着将上面的 ER 图转换成数据库实际的关系模型(实际设计中,我们通常会将任课教师也作为一个实体来处理):
6. 数据库范式了解吗?
- 1NF(第一范式)
属性(对应于表中的字段)不能再被分割,也就是这个字段只能是一个值,不能再分为多个其他的字段了。1NF 是所有关系型数据库的最基本要求 ,也就是说关系型数据库中创建的表一定满足第一范式。 - 2NF(第二范式)
2NF 在 1NF 的基础之上,消除了非主属性对于码的部分函数依赖。如下图所示,展示了第一范式到第二范式的过渡。第二范式在第一范式的基础上增加了一个列,这个列称为主键,非主属性都依赖于主键。
函数依赖(functional dependency) :若在一张表中,在属性(或属性组)X 的值确定的情况下,必定能确定属性 Y 的值,那么就可以说 Y 函数依赖于 X,写作 X → Y。
部分函数依赖(partial functional dependency) :如果 X→Y,并且存在 X 的一个真子集 X0,使得 X0→Y,则称 Y 对 X 部分函数依赖。比如学生基本信息表 R 中(学号,身份证号,姓名)当然学号属性取值是唯一的,在 R 关系中,(学号,身份证号)->(姓名),(学号)->(姓名),(身份证号)->(姓名);所以姓名部分函数依赖与(学号,身份证号);
完全函数依赖(Full functional dependency) :在一个关系中,若某个非主属性数据项依赖于全部关键字称之为完全函数依赖。比如学生基本信息表 R(学号,班级,姓名)假设不同的班级学号有相同的,班级内学号不能相同,在 R 关系中,(学号,班级)->(姓名),但是(学号)->(姓名)不成立,(班级)->(姓名)不成立,所以姓名完全函数依赖与(学号,班级);
传递函数依赖 : 在关系模式 R(U)中,设 X,Y,Z 是 U 的不同的属性子集,如果 X 确定 Y、Y 确定 Z,且有 X 不包含 Y,Y 不确定 X,(X∪Y)∩Z=空集合,则称 Z 传递函数依赖(transitive functional dependency) 于 X。传递函数依赖会导致数据冗余和异常。传递函数依赖的 Y 和 Z 子集往往同属于某一个事物,因此可将其合并放到一个表中。比如在关系 R(学号 , 姓名, 系名,系主任)中,学号 → 系名,系名 → 系主任,所以存在非主属性系主任对于学号的传递函数依赖。。 - 3NF(第三范式)
3NF 在 2NF 的基础之上,消除了非主属性对于码的传递函数依赖 。符合 3NF 要求的数据库设计,基本上解决了数据冗余过大,插入异常,修改异常,删除异常的问题。比如在关系 R(学号 , 姓名, 系名,系主任)中,学号 → 系名,系名 → 系主任,所以存在非主属性系主任对于学号的传递函数依赖,所以该表的设计,不符合 3NF 的要求。
总结:
1NF
:属性不可再分。2NF
:1NF 的基础之上,消除了非主属性对于码的部分函数依赖。3NF
:3NF 在 2NF 的基础之上,消除了非主属性对于码的传递函数依赖 。
7. 什么是存储过程?
我们可以把存储过程看成是一些 SQL 语句的集合,中间加了点逻辑控制语句。存储过程在业务比较复杂的时候是非常实用的,比如很多时候我们完成一个操作可能需要写一大串 SQL 语句,这时候我们就可以写有一个存储过程,这样也方便了我们下一次的调用。存储过程一旦调试完成通过后就能稳定运行,另外,使用存储过程比单纯 SQL 语句执行要快,因为存储过程是预编译过的。
存储过程在互联网公司应用不多,因为存储过程难以调试和扩展,而且没有移植性,还会消耗数据库资源。
阿里巴巴 Java 开发手册里要求禁止使用存储过程。
8. drop、delete 与 truncate 区别?
- 用法不同
drop(丢弃数据)
:drop table 表名
,直接将表都删除掉,在删除表的时候使用。
truncate
(清空数据):truncate table 表名
,只删除表中的数据,再插入数据的时候自增长 id 又从 1 开始,在清空表中数据的时候使用。
delete
(删除数据):delete from 表名 where 列名=值
,删除某一行的数据,如果不加 where 子句和truncate table
表名作用类似。
truncate
和delete
只删除数据不删除表的结构(定义),执行drop
语句,此表的结构也会删除 - 属于不同的数据库语言
truncate
和drop
属于DDL
(数据定义语言)语句,操作立即生效,原数据不放到rollback segment
中,不能回滚,操作不触发trigger
。而delete
语句是DML
(数据库操作语言)语句,这个操作会放到rollback segement
中,事务提交之后才生效。
DML
语句和DDL
语句区别:DML
是数据库操作语言(Data Manipulation Language)的缩写,是指对数据库中表记录的操作,主要包括表记录的插入(insert)、更新(update)、删除(delete)和查询(select),是开发人员日常使用最频繁的操作。DDL
(Data Definition Language)是数据定义语言的缩写,简单来说,就是对数据库内部的对象进行创建、删除、修改的操作语言。它和 DML 语言的最大区别是 DML 只是对表内部数据的操作,而不涉及到表的定义、结构的修改,更不会涉及到其他对象。DDL 语句更多的被数据库管理员(DBA)所使用,一般的开发人员很少使用。
- 执行速度不同
drop
>truncate
>delete
9. 数据库设计通常分为哪几步?
- 需求分析 : 分析用户的需求,包括数据、功能和性能需求。
- 概念结构设计 : 主要采用 E-R 模型进行设计,包括画 E-R 图。
- 逻辑结构设计 : 通过将 E-R 图转换成表,实现从 E-R 模型到关系模型的转换。
- 物理结构设计 : 主要是为所设计的数据库选择合适的存储结构和存取路径。
- 数据库实施 : 包括编程、测试和试运行
- 数据库的运行和维护 : 系统的运行与数据库的日常维护。
字符集
字符集 就是一系列字符的集合。
常见的字符集有 ASCII
、GB2312
、GBK
、GB18030
、BIG5
、UTF-8
…。
ASCII
:128 个字符
GB2312
:6700 多个汉字
GBK
:20000 多个汉字
GB18030
:最全面的汉字字符集
BIG5
:繁体中文
utf8
: utf8编码只支持1-3个字节 。
utf8mb4
:UTF-8
的完整实现,存储 emoji 符号。
MySQL知识点&面试题总结
1. MySQL 基础
1. 关系型数据库介绍
关系型数据库就是一种建立在关系模型的基础上的数据库。
关系模型表明了数据库中所存储的数据之间的联系(一对一、一对多、多对多)。
关系型数据库中,我们的数据都被存放在了各种表中(比如用户表),表中的每一行就存放着一条数据(比如一个用户的信息)。
大部分关系型数据库都使用 SQL 来操作数据库中的数据。并且,大部分关系型数据库都支持事务的四大特性(ACID)。
(MySQL、PostgreSQL、Oracle、SQL Server、SQLite(微信本地的聊天记录的存储就是用的 SQLite))
2. MySQL 介绍
MySQL 是一种关系型数据库,主要用于持久化存储我们的系统中的一些数据比如用户信息。
存储引擎
1. 存储引擎相关的命令
查看 MySQL 提供的所有存储引擎
mysql> show engines;
MySQL
当前默认的存储引擎是 InnoDB
,并且在 5.7 版本所有的存储引擎中只有 InnoDB
是事务性存储引擎,也就是说只有 InnoDB
支持事务。
查看 MySQL 当前默认的存储引擎
mysql> show variables like '%storage_engine%';
查看表的存储引擎
show table status like "table_name" ;
2. MyISAM 和 InnoDB 的区别
- 是否支持行级锁
MyISAM
只有表级锁(table-level locking)(一锁就是锁住了整张表)InnoDB
支持行级锁(row-level locking)和表级锁,默认为行级锁。
- 是否支持事务
MyISAM
不提供事务支持。
InnoDB
提供事务支持,具有提交(commit)和回滚(rollback)事务的能力。 - 是否支持外键
MyISAM
不支持,而 `InnoDB 支持。 - 是否支持数据库异常崩溃后的安全恢复
MyISAM
不支持,而InnoDB
支持(依赖于redo log
)。
3. 锁机制与 InnoDB 锁算法
- 表级锁:资源消耗也比较少,加锁快,不会出现死锁。其锁定粒度最大,触发锁冲突的概率最高,并发度最低,MyISAM 和 InnoDB 引擎都支持表级锁。
- 行级锁: 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。
InnoDB 存储引擎的锁的算法有三种: Record lock
:记录锁,单个行记录上的锁Gap lock
:间隙锁,锁定一个范围,不包括记录本身Next-key lock
:record+gap
临键锁,锁定一个范围,包含记录本身
4. 查询缓存
执行查询语句的时候,会先查询缓存(MySQL 8.0 版本后移除)。
my.cnf 加入以下配置,重启 MySQL 开启查询缓存
query_cache_type=1
query_cache_size=600000
MySQL 执行以下命令也可以开启查询缓存
set global query_cache_type=1;
set global query_cache_size=600000;
还可以通过 sql_cache 和 sql_no_cache 来控制某个查询语句是否需要缓存:
select sql_no_cache count(*) from usr;
开启查询缓存后在同样的查询条件以及数据情况下,会直接在缓存中返回结果。
查询缓存不命中的三种情况:
(1)因此任何两个查询在任何字符上的不同都会导致缓存不命中。
(2)如果查询中包含任何用户自定义函数、存储函数、用户变量、临时表、MySQL
库中的系统表,其查询结果也不会被缓存。
(3)缓存建立之后,MySQL
的查询缓存系统会跟踪查询中涉及的每张表,如果这些表(数据或结构)发生变化,那么和这张表相关的所有缓存数据都将失效。
缓存虽然能够提升数据库的查询性能,但是缓存同时也带来了额外的开销,每次查询后都要做一次缓存操作,失效后还要销毁。
事务
事务是逻辑上的一组操作,要么都执行,要么都不执行。
1. 何为数据库事务?
数据库事务可以保证多个对数据库的操作(也就是 SQL 语句)构成一个逻辑上的整体。构成这个逻辑上的整体的这些数据库操作遵循:要么全部执行成功,要么全部不执行 。
# 开启一个事务
START TRANSACTION;
# 多条 SQL 语句
SQL1,SQL2...
## 提交事务
COMMIT;
2. 何为 ACID 特性呢?
关系型数据库(例如:MySQL、SQL Server、Oracle 等)事务都有 ACID 特性:
- 原子性(
Atomicity
) : 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;(undo log
(回滚日志) ) - 一致性(
Consistency
): 执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的; - 隔离性(
Isolation
): 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;(锁机制、MVCC
) - 持久性(
Durability
): 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。(redo log
(重做日志) )
3. 数据事务的实现原理呢?(以 MySQL 的 InnoDB 引擎为例)
MySQL InnoDB
引擎使用 redo log
(重做日志) 保证事务的持久性,使用 undo log
(回滚日志) 来保证事务的原子性。
MySQL InnoDB
引擎通过 锁机制、MVCC
等手段来保证事务的隔离性( 默认支持的隔离级别是 REPEATABLE-READ
)。
4. 并发事务带来哪些问题?
- 脏读(Dirty read):当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。
- 丢失修改(Lost to modify):指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。
- 不可重复读(Unrepeatable read):指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。
- 幻读(Phantom read):它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
不可重复读和幻读区别:
不可重复读的重点是修改比如多次读取一条记录发现其中某些列的值被修改,幻读的重点在于新增或者删除比如多次查询同一条查询语句(DQL)时,记录发现记录增多或减少了。
5. 事务隔离级别有哪些?
- READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
- READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
- REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
- SERIALIZABLE(可串行化): 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ-UNCOMMITTED | ✓ | ✓ | ✓ |
READ-COMMITTED | × | ✓ | ✓ |
READ-UNCOMMITTED | × | × | ✓ |
SERIALIZABLE | × | × | × |
6. MySQL 的默认隔离级别是什么?
MySQL InnoDB
存储引擎的默认支持的隔离级别是 REPEATABLE-READ
(可重读)。
并不保证避免幻读,需要应用使用加锁读来保证。而这个加锁读使用到的机制就是 Next-Key Locks
(record+gap 临键锁
,锁定一个范围,包含记录本身)。
因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是 READ-COMMITTED
(读取提交内容) ,但是你要知道的是 InnoDB 存储引擎默认使用 REPEATABLE-READ
(可重读) 并不会有任何性能损失。
Redis 知识点&面试题总结
1. 简单介绍一下 Redis
C 语言开发的数据库;存在内存中的 ,也就是它是内存数据库,所以读写速度非常快。
Redis 提供了多种数据类型来支持不同的业务场景。Redis 还支持事务 、持久化、Lua 脚本、多种集群方案。
2. 分布式缓存常见的技术选型方案有哪些?
Memcached
和 Redis
。
分布式缓存主要解决的是单机缓存的容量受服务器限制并且无法保存通用信息的问题。因为,本地缓存只在当前服务里有效,比如如果你部署了两个相同的服务,他们两者之间的缓存数据是无法共同的。
3. Redis 和 Memcached 的区别和共同点
共同点:
- 都是基于内存的数据库,一般都用来当做缓存使用。
- 都有过期策略。
- 两者的性能都非常高。
区别:
Redis
支持更丰富的数据类型(支持更复杂的应用场景)。Redis
不仅仅支持简单的k/v
类型的数据,同时还提供list
,set
,zset
,hash
等数据结构的存储。Memcached
只支持最简单的k/v
数据类型。Redis
支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而Memcached
把数据全部存在内存之中。Redis
有灾难恢复机制。 因为可以把缓存中的数据持久化到磁盘上。Redis
在服务器内存使用完之后,可以将不用的数据放到磁盘上。但是,Memcached
在服务器内存使用完之后,就会直接报异常。Memcached
没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是Redis
目前是原生支持cluster
模式的。Memcached
是多线程,非阻塞IO
复用的网络模型;Redis 使用单线程的多路IO
复用模型。 (Redis 6.0
引入了多线程 IO )Redis
支持发布订阅模型、Lua
脚本、事务等功能,而Memcached
不支持。并且,Redis
支持更多的编程语言。Memcached
过期数据的删除策略只用了惰性删除,而Redis
同时使用了惰性删除与定期删除。
2. 缓存数据的处理流程是怎样的?
- 如果用户请求的数据在缓存中就直接返回。
- 缓存中不存在的话就看数据库中是否存在。
- 数据库中存在的话就更新缓存中的数据。
- 数据库中不存在的话就返回空数据。
3. 为什么要用 Redis/为什么要用缓存?
简单,来说使用缓存主要是为了提升用户体验以及应对更多的用户。
高性能:保证用户下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。
不过,要保持数据库和缓存中的数据的一致性。 如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!
高并发:
直接操作缓存能够承受的数据库请求数量是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。进而,我们也就提高了系统整体的并发。
(一般像 MySQL 这类的数据库的 QPS 大概都在 1w 左右(4 核 8g) ,但是使用 Redis 缓存之后很容易达到 10w+,甚至最高能达到 30w+(就单机 redis 的情况,redis 集群的话会更高)。)
(QPS(Query Per Second):服务器每秒可以执行的查询次数。)
4. Redis 除了做缓存,还能做什么?
- 分布式锁
- 限流:
Redis
和lLua
脚本 - 消息队列:
Redis
自带的list
数据结构可以作为一个简单的队列使用。 - 复杂业务场景
5. Redis 没有使用多线程?为什么不使用多线程?
Redis 4.0
增加的多线程主要是针对一些大键值对的删除操作的命令,使用这些命令就会使用主处理之外的其他线程来“异步处理”。
Redis6.0
之前 为什么不使用多线程?- 单线程编程容易并且更容易维护;
- Redis 的性能瓶颈不在 CPU ,主要在内存和网络;
- 多线程就会存在死锁、线程上下文切换等问题,甚至会影响性能。
- Redis6.0 之后为何引入了多线程?
主要是为了提高网络 IO 读写性能。(性能瓶颈(Redis 的瓶颈主要受限于内存和网络))
虽然,Redis6.0 引入了多线程,但是 Redis 的多线程只是在网络数据的读写这类耗时操作上使用了,执行命令仍然是单线程顺序执行。因此,你也不需要担心线程安全问题。
6. Redis 给缓存数据设置过期时间有啥用?
因为内存是有限的,如果缓存中的所有数据都是一直保存的话,分分钟直接 Out of memory
。
(其他作用如验证码/登录信息等)
7. Redis 是如何判断数据是否过期的呢?
Redis
通过一个叫做过期字典(可以看作是 hash
表)来保存数据过期的时间。
过期字典的键指向 Redis
数据库中的某个 key
(键),过期字典的值是一个 long long
类型的整数,这个整数保存了 key
所指向的数据库键的过期时间(毫秒精度的 UNIX
时间戳)。
8. 过期的数据的删除策略了解么?
常用的过期数据的删除策略就两个
- 惰性删除:只会在取出
key
的时候才对数据进行过期检查。这样对CPU
最友好,但是可能会造成太多过期key
没有被删除。(对CPU好) - 定期删除:每隔一段时间抽取一批
key
执行删除过期key
操作。并且,Redis
底层会通过限制删除操作执行的时长和频率来减少删除操作对CPU
时间的影响。(对内存好)
Redis
采用的是 定期删除+惰性删除。Redis
内存淘汰机制。
9. Redis 内存淘汰机制了解么?
Redis
提供 6 种数据淘汰策略:
-
volatile-lru
(least recently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰 -
volatile-ttl
:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰 -
volatile-random
:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰 -
allkeys-lru
(least recently used):当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的) -
allkeys-random
:从数据集(server.db[i].dict)中任意选择数据淘汰 -
no-eviction
:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!
4.0 版本后增加以下两种: -
volatile-lfu
(least frequently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰 -
allkeys-lfu
(least frequently used):当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的key
10. Redis 持久化机制(怎么保证 Redis 挂掉之后再重启数据可以进行恢复)
-
快照(snapshotting)持久化(RDB)(默认)
Redis
可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。
Redis 创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis 主从结构,主要用来提高 Redis 性能),还可以将快照留在原地以便重启服务器的时候使用。 -
AOF(append-only file)持久化
与快照持久化相比,AOF
持久化的实时性更好,因此已成为主流的持久化方案。
开启AOF
持久化后每执行一条会更改Redis
中的数据的命令,Redis 就会将该命令写入到内存缓存server.aof_buf
中,然后再根据appendfsync
配置来决定何时将其同步到硬盘中的AOF
文件。
为了兼顾数据和写入性能,用户可以考虑appendfsync everysec
选项 ,让Redis
每秒同步一次AOF
文件,Redis
性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。
11. Redis bigkey
bigkey
:简单来说,如果一个 key
对应的 value
所占用的内存比较大。会消耗更多的内存空间,bigkey 对性能也会有比较大的影响。
如何发现 bigkey?
1、使用 Redis 自带的 --bigkeys 参数来查找。
2、分析 RDB 文件。前提是RDB持久化,现成代码套用。
12. Redis 事务
Redis
可以通过 MULTI
,EXEC
,DISCARD
和 WATCH
等命令来实现事务(transaction)功能。
MULTI
:开始EXEC
:执行DISCARD
:取消WATCH
:监听指定的键
(被WATCH
命令监视的键被修改的话,整个事务都不会执行,直接返回失败。)
使用 MULTI
命令后可以输入多个命令。Redis
不会立即执行这些命令,而是将它们放到队列,当调用了 EXEC
命令将执行所有命令。
这个过程是这样的:
- 开始事务(
MULTI
)。 - 命令入队(批量操作
Redis
的命令,先进先出(FIFO)的顺序执行)。 - 执行事务(
EXEC
)。
Redis
是不支持 roll back 的,因而不满足原子性的(而且不满足持久性)。ACID
13. Redis 可以做消息队列么?
Redis 5.0
新增加的一个数据结构 Stream
可以用来做消息队列,Stream 支持:
- 发布 / 订阅模式
- 按照消费者组进行消费
- 消息持久化( RDB 和 AOF)
和专业的消息队列相比,还是有很多欠缺的地方比如消息丢失和堆积问题不好解决。
选择市面上比较成熟的一些消息队列比如RocketMQ
、Kafka
。
14. 缓存穿透
大量请求的 key
根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。
有哪些解决办法?
1)缓存无效 key
如果缓存和数据库都查不到某个 key
的数据就写一个到 Redis
中去并设置过期时间
2)布隆过滤器
把所有可能存在的请求的值都存放在布隆过滤器中,当用户请求过来,先判断用户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信息给客户端,存在的话才会走下面的流程。
布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不在,那么这个元素一定不在。
当一个元素加入布隆过滤器中的时候,会进行哪些操作:
- 使用布隆过滤器中的哈希函数对元素值进行计算,得到哈希值(有几个哈希函数得到几个哈希值)。
- 根据得到的哈希值,在位数组中把对应下标的值置为 1。
15. 缓存雪崩
缓存在同一时间大面积的失效,后面的请求都直接落到了数据库上,造成数据库短时间内承受大量请求。
这就好比雪崩一样,摧枯拉朽之势,数据库的压力可想而知,可能直接就被这么多请求弄宕机了。
还有一种缓存雪崩的场景是:有一些被大量访问数据(热点缓存)在某一时刻大面积失效,导致对应的请求直接落到了数据库上。(如秒杀活动)
-
针对
Redis
服务不可用的情况:- 采用
Redis
集群,避免单机出现问题整个缓存服务都没办法使用。 - 限流,避免同时处理大量的请求。
- 采用
-
针对热点缓存失效的情况:
- 设置不同的失效时间比如随机设置缓存的失效时间。
- 缓存永不失效。
16. 如何保证缓存和数据库数据的一致性?
如果更新数据库成功,而删除缓存这一步失败的情况的话,简单说两个解决方案:
- 缓存失效时间变短(不推荐,治标不治本) :我们让缓存数据的过期时间变短,这样的话缓存就会从数据库中加载数据。另外,这种解决办法对于先操作缓存后操作数据库的场景不适用。
- 增加
cache
更新重试机制(常用): 如果cache
服务当前不可用导致缓存删除失败的话,我们就隔一段时间进行重试,重试次数可以自己定。如果多次重试还是失败的话,我们可以把当前更新失败的key
存入队列中,等缓存服务可用之后,再将缓存中对应的key
删除即可。
参考
本文档为个人方便自己熟记而整理,来自javaguide。
javaguide是个优秀的计算机知识整理:https://javaguide.cn/