Java面试——数据库知识点

2022-06-24 15:00:19 浏览数 (1)

MySQL

1、建

  • 主键:数据库表中对储存数据对象予以唯一和完整标识的数据列或属性的组合。一个数据列只能有一个主键,且主键的取值不能缺失,即不能为空值(Null)。
  • 超键:在关系中能唯一标识元组的属性集称为关系模式的超键。一个属性可以为作为一个超键,多个属性组合在一起也可以作为一个超键。超键包含候选键和主键。
  • 候选键:是最小超键,即没有冗余元素的超键。
  • 外键:在一个表中存在的另一个表的主键称此表的外键。

2、事务的四个特性

数据库事务transanction正确执行的四个基本要素。ACID,原子性(Atomicity)、一致性(Correspondence)、隔离性(Isolation)、持久性(Durability)。

  • 原子性:整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
  • 一致性:在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。
  • 隔离性:隔离状态执行事务,使它们好像是系统在给定时间内执行的唯一操作。如果有两个事务,运行在相同的时间内,执行 相同的功能,事务的隔离性将确保每一事务在系统中认为只有该事务在使用系统。这种属性有时称为串行化,为了防止事务操作间的混淆,必须串行化或序列化请 求,使得在同一时间仅有一个请求用于同一数据。
  • 持久性:在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。

3、视图的作用

视图是虚拟的表,与包含数据的表不一样,视图只包含使用时动态检索数据的查询;不包含任何列或数据。使用视图可以简化复杂的sql操作,隐藏具体的细节,保护数据;视图创建后,可以使用与表相同的方式利用它们。

视图不能被索引,也不能有关联的触发器或默认值,如果视图本身内有order by 则对视图再次order by将被覆盖。

创建视图:

代码语言:javascript复制
create view XXX as XXX;

对于某些视图比如未使用联结子查询分组聚集函数Distinct Union等,是可以对其更新的,对视图的更新将对基表进行更新;但是视图主要用于简化检索,保护数据,并不用于更新,而且大部分视图都不可以更新。

drop、delete与truncate的区别

drop直接删掉表 truncate删除表中数据,再插入时自增长id又从1开始 delete删除表中数据,可以加where字句。

  • DELETE语句执行删除的过程是每次从表中删除一行,并且同时将该行的删除操作作为事务记录在日志中保存以便进行进行回滚操作。TRUNCATE TABLE 则一次性地从表中删除所有的数据并不把单独的删除操作记录记入日志保存,删除行是不能恢复的。并且在删除的过程中不会激活与表有关的删除触发器。执行速度快。
  • 表和索引所占空间。当表被TRUNCATE 后,这个表和索引所占用的空间会恢复到初始大小,而DELETE操作不会减少表或索引所占用的空间。drop语句将表所占用的空间全释放掉。
  • 一般而言,drop > truncate > delete
  • 应用范围。TRUNCATE 只能对TABLE;DELETE可以是table和view
  • TRUNCATE 和DELETE只删除数据,而DROP则删除整个表(结构和数据)。
  • truncate与不带where的delete :只删除数据,而不删除表的结构(定义)drop语句将删除表的结构被依赖的约束(constrain),触发器(trigger)索引(index);依赖于该表的存储过程/函数将被保留,但其状态会变为:invalid。
  • delete语句为DML(data maintain Language),这个操作会被放到 rollback segment中,事务提交后才生效。如果有相应的 tigger,执行的时候将被触发。
  • truncate、drop是DLL(data define language),操作立即生效,原数据不放到 rollback segment中,不能回滚
  • 在没有备份情况下,谨慎使用 drop 与 truncate。要删除部分数据行采用delete且注意结合where来约束影响范围。回滚段要足够大。要删除表用drop;若想保留表而将表中数据删除,如果于事务无关,用truncate即可实现。如果和事务有关,或老师想触发trigger,还是用delete。
  • Truncate table 表名 速度快,而且效率高,因为truncate table 在功能上与不带 WHERE 子句的 DELETE 语句相同:二者均删除表中的全部行。但 TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少。DELETE 语句每次删除一行,并在事务日志中为所删除的每行记录一项。TRUNCATE TABLE 通过释放存储表数据所用的数据页来删除数据,并且只在事务日志中记录页的释放。
  • TRUNCATE TABLE 删除表中的所有行,但表结构及其列、约束、索引等保持不变。新行标识所用的计数值重置为该列的种子。如果想保留标识计数值,请改用 DELETE。如果要删除表定义及其数据,请使用 DROP TABLE 语句。
  • 对于由 FOREIGN KEY 约束引用的表,不能使用 TRUNCATE TABLE,而应使用不带 WHERE 子句的 DELETE 语句。由于 TRUNCATE TABLE 不记录在日志中,所以它不能激活触发器。

索引

数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B 树。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。

为表设置索引要付出代价的:

  • 一是增加了数据库的存储空间;
  • 二是在插入和修改数据时要花费较多的时间(因为索引也要随之变动)。

优点:

  • 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性;
  • 可以大大加快数据的检索速度,这也是创建索引的最主要的原因;
  • 可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义;
  • 在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间;
  • 通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。

缺点:

  • 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加;
  • 索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大;
  • 当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。

不具备创建索引列的特点:

  • 对于那些在查询中很少使用或者参考的列不应该创建索引。这是因为,既然这些列很少使用到,因此有索引或者无索引,并不能提高查询速度。相反,由于增加了索引,反而降低了系统的维护速度和增大了空间需求。
  • 对于那些只有很少数据值的列也不应该增加索引。这是因为,由于这些列的取值很少,例如人事表的性别列,在查询的结果中,结果集的数据行占了表中数据行的很大比例,即需要在表中搜索的数据行的比例很大。增加索引,并不能明显加快检索速度。
  • 对于那些定义为text, image和bit数据类型的列不应该增加索引。这是因为,这些列的数据量要么相当大,要么取值很少。
  • 当修改性能远远大于检索性能时,不应该创建索引。这是因为,修改性能和检索性能是互相矛盾的。当增加索引时,会提高检索性能,但是会降低修改性能。当减少索引时,会提高修改性能,降低检索性能。因此,当修改性能远远大于检索性能时,不应该创建索引。

数据库的三种索引:

  • 唯一索引:是不允许其中任何两行具有相同索引值的索引。当现有数据中存在重复的键值时,大多数数据库不允许将新创建的唯一索引与表一起保存。数据库还可能防止添加将在表中创建重复键值的新数据。
  • 主键索引 :数据库表经常有一列或列组合,其值唯一标识表中的每一行。该列称为表的主键。在数据库关系图中为表定义主键将自动创建主键索引,主键索引是唯一索引的特定类型。
  • 聚集索引 :在聚集索引中,表中行的物理顺序与键值的逻辑(索引)顺序相同。一个表只能包含一个聚集索引。

连接查询

外连接 :

包括左向外联接、右向外联接或完整外部联接。

  1. 左连接:left join 或 left outer join

左向外联接的结果集包括 LEFT OUTER 子句中指定的左表的所有行,而不仅仅是联接列所匹配的行。如果左表的某行在右表中没有匹配行,则在相关联的结果集行中右表的所有选择列表列均为空值(null)。

代码语言:javascript复制
select * from table1 left join table2 on table1.id=table2.id
  1. 右连接:right join 或 right outer join

右向外联接是左向外联接的反向联接。将返回右表的所有行。如果右表的某行在左表中没有匹配行,则将为左表返回空值。

代码语言:javascript复制
select * from table1 right join table2 on table1.id=table2.id
  1. 完整外部联接:full join 或 full outer join

完整外部联接返回左表和右表中的所有行。当某行在另一个表中没有匹配行时,则另一个表的选择列表列包含空值。如果表之间有匹配行,则整个结果集行包含基表的数据值。

代码语言:javascript复制
select * from table1 full join table2 on table1.id=table2.id

内连接:

内联接是用比较运算符比较要联接列的值的联接

1.内连接:join 或 inner join

代码语言:javascript复制
select * from table1 join table2 on table1.id=table2.id

2.等价(与下列执行效果相同)

代码语言:javascript复制
select a.*,b.* from table1 a,table2 b where a.id=b.id
select * from table1 cross join table2 where table1.id=table2.id #注:cross join后加条件只能用where,不能用on

交叉连接(完全):

没有 WHERE 子句的交叉联接将产生联接所涉及的表的笛卡尔积。第一个表的行数乘以第二个表的行数等于笛卡尔积结果集的大小。(table1和table2交叉连接产生3*3=9条记录)

1.交叉连接:cross join (不带条件where…)

代码语言:javascript复制
select * from table1 cross join table2

2.、等价(与下列执行效果相同)

代码语言:javascript复制
select * from table1,table2

数据库范式

第一范式(1NF):

  • 在任何一个关系数据库中,第一范式(1NF)是对关系模式的基本要求,不满足第一范式(1NF)的数据库就不是关系数据库。
  • 所谓第一范式(1NF)是指数据库表的每一列都是不可分割的基本数据项,同一列中不能有多个值,即实体中的某个属性不能有多个值或者不能有重复的属性。如果出现重复的属性,就可能需要定义一个新的实体,新的实体由重复的属性构成,新实体与原实体之间为一对多关系。在第一范式(1NF)中表的每一行只包含一个实例的信息。简而言之,第一范式就是无重复的列。

第二范式(2NF):

  • 是在第一范式(1NF)的基础上建立起来的,即满足第二范式(2NF)必须先满足第一范式(1NF)。第二范式(2NF)要求数据库表中的每个实例或行必须可以被惟一地区分。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。这个惟一属性列被称为主关键字或主键、主码。
  • 第二范式(2NF)要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性,如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。简而言之,第二范式就是非主属性非部分依赖于主关键字。

第三范式(3NF):

  • 满足第三范式(3NF)必须先满足第二范式(2NF)。简而言之,第三范式(3NF)要求一个数据库表中不包含已在其它表中已包含的非主关键字信息。例如,存在一个部门信息表,其中每个部门有部门编号(dept_id)、部门名称、部门简介等信息。那么在员工信息表中列出部门编号后就不能再将部门名称、部门简介等与部门有关的信息再加入员工信息表中。如果不存在部门信息表,则根据第三范式(3NF)也应该构建它,否则就会有大量的数据冗余。简而言之,第三范式就是属性不依赖于其它非主属性。

SQL语句优化

  • 应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描;
  • 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描。如:select id from t where num is null可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:select id from t where num=0;
  • 很多时候用 exists 代替 in 是一个好的选择;
  • 用Where子句替换HAVING 子句 因为HAVING 只会在检索出所有记录之后才对结果集进行过滤;
  • 在表中建立索引,优先考虑where、group by使用到的字段;
  • 尽量避免使用select *,返回无用的字段会降低查询效率;
  • 尽量避免使用in和not in,会导致数据库引擎放弃索引进行全表扫描;
  • 尽量避免使用or,会导致数据库引擎放弃索引进行全表扫描;
  • 尽量避免在字段开头模糊查询,会导致数据库引擎放弃索引进行全表扫描。

数据库结构优化

  • 范式优化:比如消除冗余(节省空间);
  • 反范式优化:比如适当加冗余等(减少join);
  • 拆分表(垂直拆分和水平拆分):分区将数据在物理上分隔开,不同分区的数据可以制定保存在处于不同磁盘上的数据文件里。

MySQL中myisam与innodb的区别

  • InnoDB支持事物,而MyISAM不支持事物;
  • InnoDB支持行级锁,而MyISAM支持表级锁;
  • InnoDB支持MVCC, 而MyISAM不支持;
  • InnoDB支持外键,而MyISAM不支持;
  • InnoDB不支持全文索引,而MyISAM支持;
  • InnoDB不能通过直接拷贝表文件的方法拷贝表到另外一台机器, myisam 支持;
  • InnoDB表支持多种行格式, myisam 不支持;
  • InnoDB是索引组织表, myisam 是堆表。

Innodb引擎的4大特性

  • 插入缓冲(insert buffer)
  • 二次写(double write)
  • 自适应哈希索引(ahi)
  • 预读(read ahead)

四种事务隔离级别

  • 读未提交(read uncommitted) :可以读取其他 session 未提交的脏数据。
  • 读已提交(read committed) :允许不可重复读取,但不允许脏读取。提交后,其他会话可以看到提交的数据。
  • 可重复读(repeatable read) :禁止不可重复读取和脏读取、以及幻读(innodb 独有)。
  • 串行(serializable):事务只能一个接着一个地执行,但不能并发执行。事务隔离级别最高。

事务隔离级别

脏读

不可重复读

幻读

读未提交(read-uncommitted)

不可重复读(read-committed)

可重复读(repeatable-read)

串行化(serializable)

注:mysql默认是 可重复读(repeatable-read),oracle默认是读已提交(read committed)。

MySQL B Tree索引和Hash索引的区别

  • Hash索引结构的特殊性,其检索效率非常高,索引的检索可以一次定位;
  • B 树索引需要从根节点到枝节点,最后才能访问到页节点这样多次的IO访问。

乐观锁和悲观锁

  • 悲观锁(Pessimistic Lock)的特点是先获取锁,再进行业务操作,即“悲观”的认为获取锁是非常有可能失败的,因此要先确保获取锁成功再进行业务操作。通常所说的“一锁二查三更新”即指的是使用悲观锁。通常来讲在数据库上的悲观锁需要数据库本身提供支持,即通过常用的select … for update操作来实现悲观锁。当数据库执行select for update时会获取被select中的数据行的行锁,因此其他并发执行的select for update如果试图选中同一行则会发生排斥(需要等待行锁被释放),因此达到锁的效果。select for update获取的行锁会在当前事务结束时自动释放,因此必须在事务中使用。
  • 乐观锁(Optimistic Lock),也叫乐观并发控制,它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,那么当前正在提交的事务会进行回滚。乐观锁的特点先进行业务操作,不到万不得已不去拿锁。即“乐观”的认为拿锁多半是会成功的,因此在进行完业务操作需要实际更新数据的最后一步再去拿一下锁就好。

总结:

悲观锁和乐观锁是数据库用来保证数据并发安全防止更新丢失的两种方法,例子在select … for update前加个事务就可以防止更新丢失。悲观锁和乐观锁大部分场景下差异不大,一些独特场景下有一些差别,一般我们可以从如下几个方面来判断。

  • 响应速度:如果需要非常高的响应速度,建议采用乐观锁方案,成功就执行,不成功就失败,不需要等待其他并发去释放锁。
  • 冲突频率:如果冲突频率非常高,建议采用悲观锁,保证成功率,如果冲突频率大,乐观锁会需要多次重试才能成功,代价比较大。
  • 重试代价:如果重试代价大,建议采用悲观锁。

非关系型数据库和关系型数据库区别

非关系型数据库的优势:

  • 性能:NOSQL是基于键值对的,可以想象成表中的主键和值的对应关系,而且不需要经过SQL层的解析,所以性能非常高。
  • 可扩展性:同样也是因为基于键值对,数据之间没有耦合性,所以非常容易水平扩展。

关系型数据库的优势:

  • 复杂查询:可以用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询。
  • 事务支持:使得对于安全性能很高的数据访问要求得以实现。

EXPLAIN

语法:

代码语言:javascript复制
EXPLAIN SELECT
  • EXPLAIN EXTENDED SELECT 将执行计划“反编译”成SELECT语句,运行SHOW WARNINGS 可得到被MySQL优化器优化后的查询语句 ;
  • EXPLAIN PARTITIONS SELECT 用于分区表的EXPLAIN;
  • 在Navicat图形化界面中,点击“解释”出现执行计划的信息。

执行计划中的信息:

1.id:包含一组数字,表示查询中执行select子句或操作表的顺序。id相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行。

2.select_type:主要用于区别普通查询, 联合查询, 子查询等复杂查询。

  • SIMPLE:查询中不包含子查询或者UNION
  • 查询中若包含任何复杂的子部分,最外层查询则被标记为:PRIMARY
  • 在SELECT或WHERE列表中包含了子查询,该子查询被标记为:SUBQUERY
  • 在FROM列表中包含的子查询被标记为:DERIVED(衍生)
  • 若第二个SELECT出现在UNION之后,则被标记为UNION;若UNION包含在FROM子句的子查询中,外层SELECT将被标记为:DERIVED
  • 从UNION表获取结果的SELECT被标记为:UNION RESULT

3.type:表示MySQL在表中找到所需行的方式,又称“访问类型”(ALL、index、range、ref、eq_ref、const、system、NULL),由左至右,由最差到最好

  • ALL:Full Table Scan, MySQL将遍历全表以找到匹配的行
  • index:Full Index Scan,index与ALL区别为index类型只遍历索引树
  • range:索引范围扫描,对索引的扫描开始于某一点,返回匹配值域的行,常见于between、<、>等的查询
  • ref:非唯一性索引扫描,返回匹配某个单独值的所有行。常见于使用非唯一索引即唯一索引的非唯一前缀进行的查找
  • eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描
  • const、system:当MySQL对查询某部分进行优化,并转换为一个常量时,使用这些类型访问。如将主键置于where列表中,MySQL就能将该查询转换为一个常量
  • system是const类型的特例,当查询的表只有一行的情况下, 使用system
  • NULL:MySQL在优化过程中分解语句,执行时甚至不用访问表或索引

4.possible_keys:指出MySQL能使用哪个索引在表中找到行,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用

5.key:显示MySQL在查询中实际使用的索引,若没有使用索引,显示为NULL 查询中若使用了覆盖索引,则该索引仅出现在key列表中

6.key_len:表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度 key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的

7.ref:表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值

8.rows:表示MySQL根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数

9.Extra:包含不适合在其他列中显示但十分重要的额外信息

  • Using index:该值表示相应的select操作中使用了覆盖索引(Covering Index)。
  • MySQL可以利用索引返回select列表中的字段,而不必根据索引再次读取数据文件包含所有满足查询需要的数据的索引称为 覆盖索引(Covering Index)
  • Using where:表示MySQL服务器在存储引擎受到记录后进行“后过滤”(Post-filter),如果查询未能使用索引,Using where的作用只是提醒我们MySQL将用where子句来过滤结果集
  • Using temporary:表示MySQL需要使用临时表来存储结果集,常见于排序和分组查询
  • Using filesort:MySQL中无法利用索引完成的排序操作称为“文件排序”

注意:如果要使用覆盖索引,一定要注意select列表中只取出需要的列,不可select *,因为如果将所有字段一起做索引会导致索引文件过大,查询性能下降

执行计划的局限:

  • EXPLAIN不会告诉你关于触发器、存储过程的信息或用户自定义函数对查询的影响情况
  • EXPLAIN不考虑各种Cache
  • EXPLAIN不能显示MySQL在执行查询时所作的优化工作
  • 部分统计信息是估算的,并非精确值
  • EXPALIN只能解释SELECT操作,其他操作要重写为SELECT后查看执行计划

Redis

概念

Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API的非关系型数据库。

传统数据库遵循 ACID 规则。而 Nosql(Not Only SQL 的缩写,是对不同于传统的关系型数据库的数据库管理系统的统称) 一般为分布式而分布式一般遵循 CAP 定理。

Redis支持的数据类型

  • String:string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。string类型是Redis最基本的数据类型,一个键最大能存储512MB;
  • Hash:hash 是一个键值(key=>value)对集合。Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象;
  • List:列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边);
  • Set:Set是string类型的无序集合。集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1);
  • zset(有序集合):zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数(score)却可以重复。

Redis的持久化

1.持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。Redis 提供了两种持久化方式:

  • RDB(默认):Redis DataBase缩写,功能核心函数rdbSave(生成RDB文件)和rdbLoad(从文件加载内存)两个函数;
  • AOF :Append-only file缩写,每当执行服务器(定时)任务或者函数时flushAppendOnlyFile 函数都会被调用, 这个函数执行以下两个工作aof写入保存:1).WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件;2).SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。

2.存储结构:内容是redis通讯协议(RESP )格式的命令文本存储。

3.RDB和AOF的比较:

  • aof文件比rdb更新频率高,优先使用aof还原数据;
  • aof比rdb更安全也更大;
  • rdb性能比aof好;
  • 如果两个都配了优先加载AOF。

4.RESP:是redis客户端和服务端之前使用的一种通讯协议;RESP 的特点:实现简单、快速解析、可读性好。

Redis的架构模式

单机版:

  • 特点:简单
  • 问题:内存容量有限 、处理能力有限 、无法高可用。

主从复制:

Redis 的复制(replication)功能允许用户根据一个 Redis 服务器来创建任意多个该服务器的复制品,其中被复制的服务器为主服务器(master),而通过复制创建出来的服务器复制品则为从服务器(slave)。只要主从服务器之间的网络连接正常,主从服务器两者会具有相同的数据,主服务器就会一直将发生在自己身上的数据更新同步 给从服务器,从而一直保证主从服务器的数据相同。

  • 特点:master/slave 角色、master/slave 数据相同、降低 master 读压力在转交从库;
  • 问题:无法保证高可用、没有解决 master 写的压力。

哨兵:

Redis sentinel 是一个分布式系统中监控 redis 主从服务器,并在主服务器下线时自动进行故障转移。

1.特性:

  • 监控(Monitoring):Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
  • 提醒(Notification):当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
  • 自动故障迁移(Automatic failover):当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作。

2.特点:保证高可用、监控各个节点、自动故障迁移;

3.缺点:主从模式,切换需要时间丢数据、没有解决 master 写的压力。

集群(proxy 型):

Twemproxy 是一个 Twitter 开源的一个 redis 和 memcache 快速/轻量级代理服务器;Twemproxy 是一个快速的单线程代理程序,支持 Memcached ASCII 协议和 redis 协议。

1.特点:

  • 多种 hash 算法:MD5、CRC16、CRC32、CRC32a、hsieh、murmur、Jenkins ;
  • 支持失败节点自动删除;
  • 后端 Sharding 分片逻辑对业务透明,业务方的读写方式和操作单个 Redis 一致。

2.缺点:增加了新的 proxy,需要维护其高可用。

集群(直连型):

从redis 3.0之后版本支持redis-cluster集群,Redis-Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。

1.特点:

  • 无中心架构(不存在哪个节点影响性能瓶颈),少了 proxy 层;
  • 数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布;
  • 可扩展性,可线性扩展到 1000 个节点,节点可动态添加或删除;
  • 高可用性,部分节点不可用时,集群仍可用。通过增加 Slave 做备份数据副本;
  • 实现故障自动 failover,节点之间通过 gossip 协议交换状态信息,用投票机制完成 Slave到 Master 的角色提升。

2.缺点:

  • 资源隔离性较差,容易出现相互影响的情况;
  • 数据通过异步复制,不保证数据的强一致性。

缓存穿透和缓存雪崩

缓存穿透:

1.理解一:一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。

2.如何避免:

  • 对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。
  • 对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。

3.理解二:缓存穿透是指查询一个一定不存在的数据。由于缓存不命中,并且出于容错考虑,如果从数据库查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,失去了缓存的意义。

请求的数据在缓存大量不命中,导致请求走数据库。

4.解决方法:

  • 由于请求的参数是不合法的(每次都请求不存在的参数),于是我们可以使用布隆过滤器(BloomFilter)或者压缩filter提前拦截,不合法就不让这个请求到数据库层;
  • 当我们从数据库找不到的时候,我们也将这个空对象设置到缓存里边去。下次再请求的时候,就可以从缓存里边获取了。

缓存雪崩:

1.理解一:当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃。

2.如何避免:

  • 在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
  • 做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期
  • 不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

3.理解二:

Redis挂掉了,请求全部走数据库;

对缓存数据设置相同的过期时间,导致某段时间内缓存失效,请求全部走数据库。

4.解决方法:

  • 对于“Redis挂掉了,请求全部走数据库”这种情况,可以有以下的思路:

事发前:实现Redis的高可用(主从架构 Sentinel 或者Redis Cluster),尽量避免Redis挂掉这种情况发生; 事发中:万一Redis真的挂了,我们可以设置本地缓存(ehcache) 限流(hystrix),尽量避免我们的数据库被干掉(起码能保证我们的服务还是能正常工作的); 事发后:redis持久化,重启后自动从磁盘上加载数据,快速恢复缓存数据。

  • 对于“对缓存数据设置相同的过期时间,导致某段时间内缓存失效,请求全部走数据库。”这种情况,非常好解决:

在缓存的时候给过期时间加上一个随机值,这样就会大幅度的减少缓存在同一时间过期。

为什么redis需要把所有数据放到内存中

Redis为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以redis具有快速和数据持久化的特征。如果不将数据放在内存中,磁盘I/O速度为严重影响redis的性能。在内存越来越便宜的今天,redis将会越来越受欢迎。如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。

Redis的单进程单线程

Redis利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销。

Redis的回收策略

  • volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰;
  • volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰;
  • volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰;
  • allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰;
  • allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰;
  • no-enviction(驱逐):禁止驱逐数据。

使用Redis的好处

  • 速度快:因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
  • 支持丰富数据类型:支持string,list,set,sorted set,hash;
  • 支持事务:操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行;
  • 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除。

Redis 最适合的场景

会话缓存(Session Cache)

最常用的一种使用Redis的情景是会话缓存(session cache)。用Redis缓存会话比其他存储(如Memcached)的优势在于:Redis提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的,现在,他们还会这样吗?幸运的是,随着 Redis 这些年的改进,很容易找到怎么恰当的使用Redis来缓存会话的文档。甚至广为人知的商业平台Magento也提供Redis的插件。

全页缓存(FPC)

除基本的会话token之外,Redis还提供很简便的FPC平台。回到一致性问题,即使重启了Redis实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似PHP本地FPC。再次以Magento为例,Magento提供一个插件来使用Redis作为全页缓存后端。此外,对WordPress的用户来说,Pantheon有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。

队列

Reids在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得Redis能作为一个很好的消息队列平台来使用。Redis作为队列使用的操作,就类似于本地程序语言(如Python)对 list 的 push/pop 操作。

如果你快速的在Google中搜索“Redis queues”,你马上就能找到大量的开源项目,这些项目的目的就是利用Redis创建非常好的后端工具,以满足各种队列需求。例如,Celery有一个后台就是使用Redis作为broker,你可以从这里去查看。

排行榜/计数器

Redis在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单,Redis只是正好提供了这两种数据结构。所以,我们要从排序集合中获取到排名最靠前的10个用户–我们称之为“user_scores”,我们只需要像下面一样执行即可:

当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行:

ZRANGE user_scores 0 10 WITHSCORES

Agora Games就是一个很好的例子,用Ruby实现的,它的排行榜就是使用Redis来存储数据的,你可以在这里看到。

发布/订阅

Redis的发布/订阅功能。发布/订阅的使用场景确实非常多。我已看见人们在社交网络连接中使用,还可作为基于发布/订阅的脚本触发器,甚至用Redis的发布/订阅功能来建立聊天系统。

Redis支持的Java客户端

  • Redisson(官方推荐使用):是一个高级的分布式协调Redis客服端,能帮助用户在分布式环境中轻松实现一些Java的对象;
  • Jedis:Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持;
  • lettuce

Redisson和Jedis的区别:Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。Redisson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。

Redis哈希槽

Redis集群没有使用一致性hash,而是引入了哈希槽的概念,Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽

Redis管道的作用

一次请求/响应服务器能实现处理新的请求即使旧的请求还未被响应。这样就可以将多个命令发送到服务器,而不用等待回复,最后在一个步骤中读取该答复。

Redis事务

事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。

Redis事务相关的命令:MULTI、EXEC、DISCARD、WATCH

Redis的分布式事务

对于读操作

  • 如果我们的数据在缓存里边有,那么就直接取缓存的;
  • 如果缓存里没有我们想要的数据,我们会先去查询数据库,然后将数据库查出来的数据写到缓存中;
  • 最后将数据返回给请求。

对于更新操作

一般来说,执行更新操作时,我们会有两种选择:

  • 先操作数据库,再操作缓存
  • 先操作缓存,再操作数据库

如果原子性被破坏了,可能会有以下的情况:

  • 操作数据库成功了,操作缓存失败了。
  • 操作缓存成功了,操作数据库失败了。

发布/订阅

  • 更新缓存
  • 删除缓存

一般我们都是采取删除缓存缓存策略的,原因如下:

  • 高并发环境下,无论是先操作数据库还是后操作数据库而言,如果加上更新缓存,那就更加容易导致数据库与缓存数据不一致问题。(删除缓存直接和简单很多)
  • 如果每次更新了数据库,都要更新缓存【这里指的是频繁更新的场景,这会耗费一定的性能】,倒不如直接删除掉。等再次读取时,缓存里没有,那我到数据库找,在数据库找到再写到缓存里边(体现懒加载)

基于这两点,对于缓存在更新时而言,都是建议执行删除操作!

发布/订阅

  • 先操作数据库,成功;
  • 再删除缓存,也成功;

如果原子性被破坏了:

  • 第一步成功(操作数据库),第二步失败(删除缓存),会导致数据库里是新数据,而缓存里是旧数据。
  • 如果第一步(操作数据库)就失败了,我们可以直接返回错误(Exception),不会出现数据不一致。

如果在高并发的场景下,出现数据库与缓存数据不一致的概率特别低,也不是没有:

  • 缓存刚好失效
  • 线程A查询数据库,得一个旧值
  • 线程B将新值写入数据库
  • 线程B删除缓存
  • 线程A将查到的旧值写入缓存

要达成上述情况,还是说一句概率特别低:

因为这个条件需要发生在读缓存时缓存失效,而且并发着有一个写操作。而实际上数据库的写操作会比读操作慢得多,而且还要锁表,而读操作必需在写操作前进入数据库操作,而又要晚于写操作更新缓存,所有的这些条件都具备的概率基本并不大。

删除缓存失败的解决思路:

  • 将需要删除的key发送到消息队列中
  • 自己消费消息,获得需要删除的key
  • 不断重试删除操作,直到成功

发布/订阅

  • 先删除缓存,成功;
  • 再更新数据库,也成功;

如果原子性被破坏了:

  • 第一步成功(删除缓存),第二步失败(更新数据库),数据库和缓存的数据还是一致的。
  • 如果第一步(删除缓存)就失败了,我们可以直接返回错误(Exception),数据库和缓存的数据还是一致的。

看起来是很美好,但是我们在并发场景下分析一下,就知道还是有问题的了:

  • 线程A删除了缓存
  • 线程B查询,发现缓存已不存在
  • 线程B去数据库查询得到旧值
  • 线程B将旧值写入缓存
  • 线程A将新值写入数据库

所以也会导致数据库和缓存不一致的问题,并发下解决数据库与缓存不一致的思路:将删除缓存、修改数据库、读取缓存等的操作积压到队列里边,实现串行化。

发布/订阅

我们可以发现,两种策略各自有优缺点:

  • 先删除缓存,再更新数据库
  • 在高并发下表现不如意,在原子性被破坏时表现优异
  • 先更新数据库,再删除缓存(Cache Aside Pattern设计模式)
  • 在高并发下表现优异,在原子性被破坏时表现不如意

Redis与Memcached的区别与比较

  • Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。memcache支持简单的数据类型,String;
  • Redis支持数据的备份,即master-slave模式的数据备份;
  • Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而Memecache把数据全部存在内存之中;
  • Redis的速度比memcached快很多;
  • Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的IO复用模型。

0 人点赞