Fork me on GitHub

Python面试大全

数据库和缓存(46题)

1.列举常见的关系型数据库和非关系型都有那些

关系型数据库:MySQL,Oracle,SQL Server,PostgreSQL。

非关系型数据库:MongoDB,Redis。

2.MySQL常见数据库引擎及比较

ISAM

ISAM引擎适用的场景是数据库查询的次数要远大于更新的次数。因此,ISAM执行读取操作的速度很快,而且不占用大量的内存和存储资源。ISAM的两个主要不足之处在于,它不支持事务处理,也不能够容错:如果你的硬盘崩溃了,那么数据文件就无法恢复了。如果你正在把ISAM用在关键任务应用程序里,那就必须经常备份你所有的实时数据,通过其复制特性,MYSQL能够支持这样的备份应用程序。

MyISAM

MyISAM是基于ISAM扩展的数据库引擎。除了提供了ISAM中没有的索引和字段管理等功能,MyISAM还使用一种表格锁定的机制,来优化多个并发读写操作,其代价是需要经常运行OPTIMIZE TABLE ,来恢复更新机制所浪费的空间。MyISAM适用场景为快速读取操作。MyISAM一个重要缺陷是不能在表损坏后进行数据恢复。

InnoDB

InnoDB支持事务处理,支持外键,支持崩溃修复的能力和并发控制。如果需要对事务的完整性要求比较高,要实现并发控制,则应该选择InnoDB。

3.简述数据三大范式

第一范式1NF(域的原子性)

第一范式是对域的原子性的一个要求,在数据库设计中,一般都应该满足第一范式。如果数据库表中的所有字段值都是不可分解的原子值,就说明该数据库表满足了第一范式,不过有些关系模型中突破了第一范式的限制,这种称为非第一范式的关系模型。换句话说,是否必须满足第一范式的最低需求,主要依赖所使用的关系模型。

学 号 姓名 性别 年龄 学 院 班 级 地址
100101 张三 18 文史 1年1班 山东省济南市历城区花园路2号
100202 李四 19 农学院 1年3班 福建省福州市晋安区爱乡园3号
100303 王五 21 医学院 4年1班 山西省太原市小店区兴武小区3栋
100404 赵六 20 英语 3年2班 江苏省徐州市泉山区解放路21号
学 号 姓名 性别 年龄 学 院 班 级 省份 市(县区) 详细地址
100101 张三 18 文史 1年1班 山东 济南市 历城区花园路2号
100202 李四 19 农学院 1年3班 福建 福州市 晋安区爱乡园3号
100303 王五 21 医学院 4年1班 山西 太原市 小店区兴武小区3栋
100404 赵六 20 英语 3年2班 江苏 徐州市 泉山区解放路21号

大家可以对比一下表一和二,表二在表一的基础上,将’地址”字段拆分为”省份”、”市(县区)”、”详细地址”。如果《学生信息表》的按地区查询操作比较频繁,那么表二在很大程度上提高了用户对学生学籍所在“地区”的操作效率,不用再从“地址”字段中提取“地区”信息,因此表二的设计满足了域原子性设计,也就是满足了第一范式。

第二范式2NF(表中除主键外的字段都完全依赖主键)

第二范式是在第一范式基础上建立的。第二范式有两个重点:(1)表中必须有主键;(2)其他非主属性必须完全依赖主键,不能只依赖主键的一部分(主要针对联合主键而言)。

例如:学生体育选修课考试成绩表

学 号 课程编号 课程 代课老师 成绩
100101 101 篮球 张 一 88
100202 102 羽毛球 李 二 87
100303 101 瑜伽 杨 三 79
100404 105 足球 胡 四 70

表中,每个学生选修一门课,“学号”和“课程编号”为联合主键,“成绩”都依赖于主键,而“课程”和“代课老师”则部分依赖于“课程编号”。所以,为了符合第二范式,我们将修改表结构,如下:

表三 学生选修课成绩表

学 号 课程编号 成绩
100101 101 88
100202 102 87
100303 101 79
100404 105 70

表四 选修课信息表

课程编号 课程 代课老师
101 篮球 张 一
102 羽毛球 李 二
101 瑜伽 杨 三
105 足球 胡 四

将表中课程信息单独分离出来,符合了第二范式原则。表三中的非主属性都依赖于主键,即,“成绩”字段完全依赖于“学号”和“课程编号”组成的联合主键,符合第二范式原则。这样设计,在很大程度上减小了数据库的冗余。

第三范式3NF(表中除主键外的字段都完全直接依赖,不能是传递依赖)

不能是传递依赖,即不能存在:非主键列 A 依赖于非主键列 B,非主键列 B 依赖于主键的情况。

第二范式(2NF)和第三范式(3NF)的概念很容易混淆,区分它们的关键点在于,2NF:非主键列是否完全依赖于主键,还是依赖于主键的一部分;3NF:非主键列是直接依赖于主键,还是直接依赖于非主键列。

4.什么是事务?MySQL如何实现事务

什么时事务

事务由一个或多个sql语句组成一个整体,只有在该事务中所有的语句都执行成功才会将修改加入到数据库中。

MySQL如何实现事务

MySQL中事务是由undo/redo日志来实现的。不管是redo还是undo文件都会有一个缓存我们称之为redo_buf和undo_buf。同样,数据库文件也会有缓存称之为data_buf。

undo记录了数据在事务开始之前的值,当事务执行失败或者ROLLBACK时可以通过undo记录的值来恢复数据。

redo日志记录数据修改后的值,可以避免数据在事务提交之前必须写入到磁盘的需求,减少I/O。

例如,有两个值AA, BB 分别为3,5

1
2
3
4
5
6
7
A 事务开始
B 记录AA=3到undo_buf
C 修改AA=1 记录redo_buf
D 记录BB=5到undo_buf
E 修改BB=7 记录redo_buf
F 将redo_buf写到redo(磁盘)
G 事务提交

通过undo保证事务的原子性,redo保证持久性。
F之前崩溃由于所有数据都在内存,恢复后重新冲磁盘载入之前的数据,数据没有被破坏。
FG之间的崩溃可以使用redo来恢复
G之前的回滚都可以使用undo来完成

总结,当开始一个事务的时候,会记录该事务的lsn(log sequence number)号; 当事务执行时,会往InnoDB存储引擎的日志 的日志缓存里面插入事务日志;当事务提交时,必须将存储引擎的日志缓冲写入磁盘(通过innodb_flush_log_at_trx_commit来控制),也就是写数据前,需要先写日志。这种方式称为“预写日志方式”

5.简述数据库设计中一对多和多对多的应用场景?

例如学生管理系统。

学生和班级的关系就是一对多,班级为一的一方,学生为多的一方,双方通过在学生表中添加班级id作为外键关联。

学生和课程的关系就是多对多关系。一个学生会选多个课程,一个课程也会有多个学生进行选修。此时应该建立一张中间表保持其两者的关系。

6.如何基于数据库实现商城商品计数器?(待)

7.常见SQL(待)

8.简述触发器、函数、视图、存储过程?

触发器

定制用户对表进行【增、删、改】操作时前后的行为,触发器无法由用户直接调用,而知由于对表的【增/删/改】操作被动引发的。

函数

数据库中的函数封装了一些通用的功能,例如日期类型和字符串类型之间的转换,每个数据库系统都内置了一些函数,当然用户也可以自己定义自己的函数。

视图

视图是一个虚拟表(非真实存在),其本质是【根据SQL语句获取动态的数据集,并为其命名】,用户使用时只需使用【名称】即可获取结果集,可以将该结果集当做表来使用。

使用视图我们可以把查询过程中的临时表摘出来,用视图去实现,这样以后再想操作该临时表的数据时就无需重写复杂的sql了,直接去视图中查找即可,但视图有明显地效率问题,并且视图是存放在数据库中的,如果我们程序中使用的sql过分依赖数据库中的视图,即强耦合,那就意味着扩展sql极为不便,因此并不推荐使用。

存储过程

存储过程包含了一系列可执行的sql语句,存储过程存放于MySQL中,通过调用它的名字可以执行其内部的一堆SQL。

  • 使用存储过程的优点:
    • 用于替代程序写的SQL语句,实现程序与sql解耦
    • 基于网络传输,传别名的数据量小,而直接传SQL数据量大
  • 使用存储过程的缺点:
    • 执行效率低
    • 程序员扩展功能不便

9.MySQL索引种类

  1. 普通索引:是最基本的索引,它没有任何限制。
  2. 唯一索引:与前面的普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。
  3. 主键索引:是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。
  4. 组合索引:指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀集合。
  5. 全文索引:主要用来查找文本中的关键字,而不是直接与索引中的值相比较。

索引的缺点

  1. 虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行insert、update和delete。因为更新表时,不仅要保存数据,还要保存一下索引文件。
  2. 建立索引会占用磁盘空间的索引文件。一般情况这个问题不太严重,但如果你在一个大表上创建了多种组合索引,索引文件的会增长很快。 索引只是提高效率的一个因素,如果有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询语句。

10.索引在什么情况下遵循最左前缀的规则?

在使用组合索引时遵循最左前缀的原则。

11.主键和外键的区别?

数据库主键是指表中一个列或者列的组合,其值能够唯一的标识表中的每一个行。这样的一列或者多列成为表的主键,通过它可以强制表的实体完整性。当创建或者更改表时可以通过定义PRIMARY KEY约束来创建主键,一个表只能有一个主键约束,而且主键约束中的列不能是空值,由于主键约束确保唯一数据,所一经常来定义标识列。

外键是建立于表与表之间的联系,方便程序的编写。

12.MySQL常见的函数?

数学函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ABS(x)                    返回x的绝对值
BIN(x)          返回x的二进制(OCT返回八进制,HEX返回十六进制)
CEILING(x) 返回大于x的最小整数值
EXP(x)          返回值e(自然对数的底)的x次方
FLOOR(x)         返回小于x的最大整数值
GREATEST(x1,x2,...,xn)  返回集合中最大的值
LEAST(x1,x2,...,xn) 返回集合中最小的值
LN(x) 返回x的自然对数
LOG(x,y)         返回x的以y为底的对数
MOD(x,y) 返回x/y的模(余数)
PI()           返回pi的值(圆周率)
RAND()          返回0到1内的随机值,可以通过提供一个参数(种子)使RAND()随机数生成器生成一个指定的值。
ROUND(x,y)        返回参数x的四舍五入的有y位小数的值
SIGN(x)          返回代表数字x的符号的值
SQRT(x)          返回一个数的平方根
TRUNCATE(x,y) 返回数字x截短为y位小数的结果

聚合函数

1
2
3
4
5
6
AVG(col)         返回指定列的平均值
COUNT(col)        返回指定列中非NULL值的个数
MIN(col)         返回指定列的最小值
MAX(col)         返回指定列的最大值
SUM(col)         返回指定列的所有值之和
GROUP_CONCAT(col)     返回由属于一组的列值连接组合而成的结果

字符串函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ASCII(char)        返回字符的ASCII码值
BIT_LENGTH(str)      返回字符串的比特长度
CONCAT(s1,s2...,sn)    将s1,s2...,sn连接成字符串
CONCAT_WS(sep,s1,s2...,sn)  将s1,s2...,sn连接成字符串,并用sep字符间隔
INSERT(str,x,y,instr)    将字符串str从第x位置开始,y个字符长的子串替换为字符串instr,返回结果
FIND_IN_SET(str,list)   分析逗号分隔的list列表,如果发现str,返回str在list中的位置
LCASE(str)或LOWER(str)   返回将字符串str中所有字符改变为小写后的结果
LEFT(str,x)        返回字符串str中最左边的x个字符
LENGTH(s)         返回字符串str中的字符数
LTRIM(str)         从字符串str中切掉开头的空格
POSITION(substr,str)    返回子串substr在字符串str中第一次出现的位置
QUOTE(str)         用反斜杠转义str中的单引号
REPEAT(str,srchstr,rplcstr)返回字符串str重复x次的结果
REVERSE(str)        返回颠倒字符串str的结果
RIGHT(str,x)        返回字符串str中最右边的x个字符
RTRIM(str)         返回字符串str尾部的空格
STRCMP(s1,s2)       比较字符串s1和s2
TRIM(str)         去除字符串首部和尾部的所有空格
UCASE(str)或UPPER(str)   返回将字符串str中所有字符转变为大写后的结果

日期和时间函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CURDATE()或CURRENT_DATE() 返回当前的日期
CURTIME()或CURRENT_TIME() 返回当前的时间
DATE_ADD(date,INTERVAL int keyword) 返回日期date加上间隔时间int的结果(int必须按照关键字进行格式化),如:SELECT DATE_ADD(CURRENT_DATE,INTERVAL 6 MONTH);
DATE_FORMAT(date,fmt) 依照指定的fmt格式格式化日期date值
DATE_SUB(date,INTERVAL int keyword) 返回日期date加上间隔时间int的结果(int必须按照关键字进行格式化),如:SELECT DATE_SUB(CURRENT_DATE,INTERVAL 6 MONTH);
DAYOFWEEK(date) 返回date所代表的一星期中的第几天(1~7)
DAYOFMONTH(date) 返回date是一个月的第几天(1~31)
DAYOFYEAR(date) 返回date是一年的第几天(1~366)
DAYNAME(date) 返回date的星期名,如:SELECT DAYNAME(CURRENT_DATE);
FROM_UNIXTIME(ts,fmt) 根据指定的fmt格式,格式化UNIX时间戳ts
HOUR(time) 返回time的小时值(0~23)
MINUTE(time) 返回time的分钟值(0~59)
MONTH(date) 返回date的月份值(1~12)
MONTHNAME(date) 返回date的月份名,如:SELECT MONTHNAME(CURRENT_DATE);
NOW() 返回当前的日期和时间
QUARTER(date) 返回date在一年中的季度(1~4),如SELECT QUARTER(CURRENT_DATE);
WEEK(date) 返回日期date为一年中第几周(0~53)
YEAR(date) 返回日期date的年份(1000~9999)

加密函数

1
2
3
4
5
6
7
8
AES_ENCRYPT(str,key)   返回用密钥key对字符串str利用高级加密标准算法加密后的结果,调用AES_ENCRYPT的结果是一个二进制字符串,以BLOB类型存储
AES_DECRYPT(str,key) 返回用密钥key对字符串str利用高级加密标准算法解密后的结果
DECODE(str,key) 使用key作为密钥解密加密字符串str
ENCRYPT(str,salt) 使用UNIX crypt()函数,用关键词salt(一个可以惟一确定口令的字符串,就像钥匙一样)加密字符串str
ENCODE(str,key) 使用key作为密钥加密字符串str,调用ENCODE()的结果是一个二进制字符串,它以BLOB类型存储
MD5() 计算字符串str的MD5校验和
PASSWORD(str) 返回字符串str的加密版本,这个加密过程是不可逆转的,和UNIX密码加密过程使用不同的算法。
SHA() 计算字符串str的安全散列算法(SHA)校验和

控制流函数

1
2
3
4
5
CASE WHEN[test1] THEN [result1]...ELSE [default] END 如果testN是真,则返回resultN,否则返回default
CASE [test] WHEN[val1] THEN [result]...ELSE [default] END 如果test和valN相等,则返回resultN,否则返回default
IF(test,t,f) 如果test是真,返回t;否则返回 f
IFNULL(arg1,arg2) 如果arg1不是空,返回arg1,否则返回arg2
NULLIF(arg1,arg2) 如果arg1=arg2返回NULL;否则返回arg1

格式化函数

1
2
3
4
5
DATE_FORMAT(date,fmt)   依照字符串fmt格式化日期date值
FORMAT(x,y) 把x格式化为以逗号隔开的数字序列,y是结果的小数位数
INET_ATON(ip) 返回IP地址的数字表示
INET_NTOA(num) 返回数字所代表的IP地址
TIME_FORMAT(time,fmt) 依照字符串fmt格式化时间time值

类型转化函数

1
为了进行数据类型转化,MySQL提供了CAST()函数,它可以把一个值转化为指定的数据类型。类型有:BINARY,CHAR,DATE,TIME,DATETIME,SIGNED,UNSIGNED

系统信息函数

1
2
3
4
5
6
DATABASE()    返回当前数据库名
BENCHMARK(count,expr) 将表达式expr重复运行count次
CONNECTION_ID() 返回当前客户的连接ID
FOUND_ROWS() 返回最后一个SELECT查询进行检索的总行数
USER()或SYSTEM_USER() 返回当前登陆用户名
VERSION() 返回MySQL服务器的版本s

13.列举创建索引但是无法命中索引的情况

  1. 请求表上的数据航超过表总记录数的30%,则变为全表扫描。
  2. 索引列上存在NULL值
  3. 模糊查询like 最左溢通配符%开始
  4. 复合索引中,第一个查询条件不是最左索引
  5. 当使用or的情况下,如果不是每一列的条件都有索引,则索引失效
  6. 如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引
  7. 如果MySQL认为全表扫描比使用索引快,则不会使用索引

14.如何开启慢日志查询?

1
2
3
4
5
6
7
8
9
10
打开my.cnf配置文件,加入以下代码: 

# 必须指定file或者是table如果是table则慢查询信息会保存到mysql库下的slow_log表中。默认值是NONE
log_output=file
# 用于指定是否打开慢查询日志
slow_query_log=on ;
# 慢查询日志文件路径
slow_query_log_file = /tmp/mysql-slow.log
# 超过多少秒的查询就写入日志
long_query_time = 2

15.数据库导入导出命令

导出数据库

1
2
3
4
# 只导出表结构
mysqldump -u用户名 -p -d 数据库名 > 数据库名.sql
# 导出表结构和数据
mysqldump -u用户名 -p 数据库名 > 数据库名.sql

导入数据

1
2
3
4
5
6
7
8
9
# 方法一
mysql -u用户名 -p 数据库名 < 数据库名.sql
# 方法二
# 选择数据库
use abc;
#设置数据库编码
set names utf8;
# 导入数据
source /home/root/abc.sql

16.数据库优化方案?

  1. 避免索引失效
  2. 建立有效的索引
  3. 分库分表
  4. 优化SQL
  5. 进行读写分离
  6. 利用缓存代替一些热点查询操作,减轻MySQL压力

17.char和varchar的区别?

char(n)是固定长度,存储需要空间n个字节,处理速度比vachar快,费内存空间,当存储的值没有达到指定的范围时,会用空格替代

vachar(n)是不固定长度,需要存储空间n+1个字节,节约存储空间,存储的是真实的值,会在存储的值前面加上1-2个字节,用来表示真实数据的大小

18.简述MySQL的执行计划?

MySql提供了EXPLAIN语法用来进行查询分析,在SQL语句前加一个”EXPLAIN”即可。其中结果的type可以看出是否是用到了索引,因此执行计划常用来优化慢SQL。

19.在对name做了唯一索引前提下,简述以下区别

1
2
select * from tb where name = ‘Oldboy-Wupeiqi’ 
select * from tb where name = ‘Oldboy-Wupeiqi’ limit 1

wherelimit 都具有 避免全表扫描 的功能 (mysql),区别在于:where能够充分利用 索引,而limit 能够限制查询行数

limit的存在主要是为了防止 全表扫描,如果一个语句本身可以得出不用全表扫描,有没有limit 那么性能的差别是不大的,比如唯一索引,主键 [没试验过,NND]

对于偏移量offset较大的查询,建议用好where语句,来避免全表扫描;因为limit本身没有利用索引的缩小范围能力

对于任何一个查询,首先应该想到的是如何利用 where 语句来 缩小范围,然后利用limit限制返回行数

我认为上述两个没有区别

20.1000w条数据,使用limit offset 分页时,为什么越往后翻越慢?如何解决?

因为例如LIMIT 100000, 30 时,其意思是扫描满足条件的100030行数据,并扔掉前100000行数据,返回最后的30行。因此,随着页数的后移,扫描出来的数据越来越多,因此速度也就越来越慢。

这样的情况可以使用子查询来进行优化,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
例如表 collect ( id, title ,info ,vtype) 就这4个字段,其中 title 用定长,info 用text, id 
是逐渐,vtype是tinyint,vtype是索引。
这是一个基本的新闻系统的简单模型。现在往里面填充数据,填充10万篇新闻。


select id,title from collect limit 1000,10; # 这种情况下会是很快滴
select id,title from collect limit 90000,10; # 这种情况就有变满

# 因此可以使用子查询进行优化

select id,title from collect where id>=(select id from collect order by id
limit 90000,1) limit 10;
# 子查询中使用id主键做索引,速度是非常快的

21.什么是索引合并?

1、索引合并是把几个索引的范围扫描合并成一个索引。

2、索引合并的时候,会对索引进行并集,交集或者先交集再并集操作,以便合并成一个索引。

3、这些需要合并的索引只能是一个表的。不能对多表进行索引合并。

简单的说,索引合并,让一条sql可以使用多个索引。对这些索引取交集,并集,或者先取交集再取并集。从而减少从数据表中取数据的次数,提高查询效率。

22.什么是覆盖索引?

使用索引来直接获取列的数据。如果索引的叶子节点包含了要查询的数据,那么就不用回表查询了,也就是说这种索引包含(亦称覆盖)所有需要查询的字段的值,我们称这种索引为覆盖索引。

(1)索引条目通常远小于数据行大小,如果只读取索引,MySQL就会极大地减少数据访问量。

(2)索引按照列值顺序存储,对于I/O密集的范围查询会比随机从磁盘中读取每一行数据的I/O要少很多。

(3)InnoDB的辅助索引(亦称二级索引)在叶子节点中保存了行的主键值,如果二级索引能够覆盖查询,则可不必对主键索引进行二次查询了。

​ 覆盖索引就是从索引中直接获取查询结果,要使用覆盖索引需要注意select查询列中包含在索引列中;where条件包含索引列或者复合索引的前导列;查询结果的字段长度尽可能少。

23.简述数据库读写分离?

就是讲数据库的读写操作分开,将读写的压力分担到多台服务器上,主数据库提供写操作,从数据库提供读操作 ,通常用于读取原大于写的场景。

24.简述数据库分库分表?(水平、垂直)

垂直划分

按照功能划分,把数据分别放到不同的数据库和服务器。

当一个网站开始刚刚创建时,可能只是考虑一天只有几十或者几百个人访问,数据库可能就个db,所有表都放一起,一台普通的服务器可能就够了,而且开发人员也非常高兴,而且信心十足,因为所有的表都在一个库中,这样查询语句就可以随便关联了,多美的一件事情。但是随着访问压力的增加,读写操作不断增加,数据库的压力绝对越来越大,可能接近极限,这时可能人们想到增加从服务器,做什么集群之类的,可是问题又来了,数据量也快速增长。

这时可以考虑对读写操作进行分离,按照业务把不同的数据放到不同的库中。其实在一个大型而且臃肿的数据库中表和表之间的数据很多是没有关系的,或者更加不需要(join)操作,理论上就应该把他们分别放到不同的服务器。例如用户的收藏夹的数据和博客的数据库就可以放到两个独立的服务器。这个就叫垂直划分(其实叫什么不重要)。

水平划分

水平划分则把一个表的数据划分到不同的数据库,两个数据库的表结构一样。怎么划分,应该根据一定的规则,可以根据数据的产生者来做引导,上面的数据是由人产生的,可以根据人的id来划分数据库。然后再根据一定的规则,先获知数据在哪个数据库。

其实很多大型网站都经历了数据库垂直划分和水平的划分的阶段。其实这个可以根据经验来确定,不一定由某些硬性的规则。

以刚才的博客为例,数据可以根据userid的奇偶来确定数据的划分。把id为基数的放到A库,为偶数的放B库。

25.redis和memcached比较?

性能

都比较高,性能对我们来说应该都不是瓶颈 ,总体来讲,TPS方面redis和memcache差不多

操作的便利性

Memcache数据结构单一,只支持k/v类型

Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,hash等数据结构的存储。

内存空间的大小和数据量的大

redis在2.0版本后增加了自己的VM特性,突破物理内存的限制;可以对key value设置过期时间(类似memcache)

memcache可以修改最大可用内存,采用LRU算法

可用性(单点问题)

redis,依赖客户端来实现分布式读写;主从复制时,每次从节点重新连接主节点都要依赖整个快照,无增量复制,因性能和效率问题, 所以单点问题比较复杂;不支持自动sharding,需要依赖程序设定一致hash 机制。 一种替代方案是,不用redis本身的复制机制,采用自己做主动复制(多份存储),或者改成增量复制的方式(需要自己实现),一致性问题和性能的权衡

Memcache本身没有数据冗余机制,也没必要;对于故障预防,采用依赖成熟的hash或者环状的算法,解决单点故障引起的抖动问题。

可靠性(持久化)

redis支持(快照、AOF):依赖快照进行持久化,aof增强了可靠性的同时,对性能有所影响

memcache不支持,通常用在做缓存,提升性能;

数据一致性(事务支持)

Memcache 在并发场景下,用cas保证一致性

redis事务支持比较弱,只能保证事务中的每个操作连续执行

26.redis中数据库默认是多少个db 及作用?

16个db。

使用不同的库(database)可以从而避免键命名冲突

27.python操作redis的模块?

1
pip install redis

28.如果redis中的某个列表中的数据量非常大,如果实现循环显示每一个值?

先获取列表长度长度,然后分段读取

1
2
LLEN key
LRANGE key start stop

29.redis如何实现主从复制?以及数据同步机制?

全量同步

Redis全量复制一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份。具体步骤如下:  

  1. 从服务器连接主服务器,发送SYNC命令;   
  2. 主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令;
  3. 主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;   
  4. 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
  5. 主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
  6. 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;

增量同步

Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。

增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。

Redis的主从策略

主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。

30.redis中的sentinel的作用?

Redis-Sentinel是Redis官方推荐的高可用性(HA)解决方案。实际上这意味着你可以使用Sentinel模式创建一个可以不用人为干预而应对各种故障的Redis部署。

它的主要功能有以下几点

  • 监控:Sentinel不断的检查master和slave是否正常的运行。
  • 通知:如果发现某个redis节点运行出现问题,可以通过API通知系统管理员和其他的应用程序。
  • 自动故障转移:能够进行自动切换。当一个master节点不可用时,能够选举出master的多个slave中的一个来作为新的master,其它的slave节点会将它所追随的master的地址改为被提升为master的slave的新地址。
  • 配置提供者:哨兵作为Redis客户端发现的权威来源:客户端连接到哨兵请求当前可靠的master的地址。如果发生故障,哨兵将报告新地址。

31. 如何实现redis集群?

Redis集群介绍

Redis 集群是一个提供在多个Redis间节点间共享数据的程序集。

Redis集群并不支持处理多个keys的命令,因为这需要在不同的节点间移动数据,从而达不到像Redis那样的性能,在高负载的情况下可能会导致不可预料的错误.

Redis 集群通过分区来提供一定程度的可用性,在实际环境中当某个节点宕机或者不可达的情况下继续处理命令. Redis 集群的优势:

  • 自动分割数据到不同的节点上。
  • 整个集群的部分节点失败或者不可达的情况下能够继续处理命令。

Redis 集群的数据分片

Redis 集群没有使用一致性hash, 而是引入了 哈希槽的概念.

Redis 集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽.集群的每个节点负责一部分hash槽,举个例子,比如当前集群有3个节点,那么:

  • 节点 A 包含 0 到 5500号哈希槽.
  • 节点 B 包含5501 到 11000 号哈希槽.
  • 节点 C 包含11001 到 16384号哈希槽.

这种结构很容易添加或者删除节点. 比如如果我想新添加个节点D, 我需要从节点 A, B, C中得部分槽到D上. 如果我像移除节点A,需要将A中得槽移到B和C节点上,然后将没有任何槽的A节点从集群中移除即可. 由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态.

Redis 集群的主从复制模型

为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型,每个节点都会有N-1个复制品.

在我们例子中具有A,B,C三个节点的集群,在没有复制模型的情况下,如果节点B失败了,那么整个集群就会以为缺少5501-11000这个范围的槽而不可用.

然而如果在集群创建的时候(或者过一段时间)我们为每个节点添加一个从节点A1,B1,C1,那么整个集群便有三个master节点和三个slave节点组成,这样在节点B失败后,集群便会选举B1为新的主节点继续服务,整个集群便不会因为槽找不到而不可用了

不过当B和B1 都失败后,集群是不可用的.

Redis 一致性保证

Redis 并不能保证数据的强一致性. 这意味这在实际中集群在特定的条件下可能会丢失写操作.

第一个原因是因为集群是用了异步复制. 写操作过程:

  • 客户端向主节点B写入一条命令.
  • 主节点B向客户端回复命令状态.
  • 主节点将写操作复制给他得从节点 B1, B2 和 B3.

主节点对命令的复制工作发生在返回命令回复之后, 因为如果每次处理命令请求都需要等待复制操作完成的话, 那么主节点处理命令请求的速度将极大地降低 —— 我们必须在性能和一致性之间做出权衡。 注意:Redis 集群可能会在将来提供同步写的方法。 Redis 集群另外一种可能会丢失命令的情况是集群出现了网络分区, 并且一个客户端与至少包括一个主节点在内的少数实例被孤立。

32.redis中默认有多少个哈希槽?

16384个

33.简述redis的有哪几种持久化策略及比较?

Redis的持久化策略有2种

RDB

快照形式是直接把内存中的数据保存到一个dump文件中,定时保存,保存策略

这种方式不能完全保证数据持久化,因为是定时保存,所以当redis服务down掉,就会丢失一部分数据,而且数据量大,写操作多的情况下,会引起大量的磁盘IO操作,会影响性能。

AOF

把所有的对redis的服务器进行修改的每一个写命令都通过write函数追加到appendonly.aof中.

34.列举redis支持的过期策略

定时删除

含义:在设置key的过期时间的同时,为该key创建一个定时器,让定时器在key的过期时间来临时,对key进行删除
优点:保证内存被尽快释放
缺点:
若过期key很多,删除这些key会占用很多的CPU时间,在CPU时间紧张的情况下,CPU不能把所有的时间用来做要紧的事儿,还需要去花时间删除这些key
定时器的创建耗时,若为每一个设置过期时间的key创建一个定时器(将会有大量的定时器产生),性能影响严重
没人用

惰性删除

含义:key过期的时候不删除,每次从数据库获取key的时候去检查是否过期,若过期,则删除,返回null。
优点:删除操作只发生在从数据库取出key的时候发生,而且只删除当前key,所以对CPU时间的占用是比较少的,而且此时的删除是已经到了非做不可的地步(如果此时还不删除的话,我们就会获取到了已经过期的key了)
缺点:若大量的key在超出超时时间后,很久一段时间内,都没有被获取过,那么可能发生内存泄露(无用的垃圾占用了大量的内存)

定期删除

含义:每隔一段时间执行一次删除过期key操作
优点:
通过限制删除操作的时长和频率,来减少删除操作对CPU时间的占用–处理”定时删除”的缺点
定期删除过期key–处理”惰性删除”的缺点
缺点
在内存友好方面,不如”定时删除”
在CPU时间友好方面,不如”惰性删除”
难点
合理设置删除操作的执行时长(每次删除执行多长时间)和执行频率(每隔多长时间做一次删除)(这个要根据服务器运行情况来定了)

Redis采用的过期策略

惰性删除+定期删除

  • 惰性删除流程
    • 在进行get或setnx等操作时,先检查key是否过期,
    • 若过期,删除key,然后执行相应操作;
    • 若没过期,直接执行相应操作
  • 定期删除流程(简单而言,对指定个数个库的每一个库随机删除小于等于指定个数个过期key)
    • 遍历每个数据库(就是redis.conf中配置的”database”数量,默认为16)
    • 检查当前库中的指定个数个key(默认是每个库检查20个key,注意相当于该循环执行20次,循环体时下边的描述)
      • 如果当前库中没有一个key设置了过期时间,直接执行下一个库的遍历
      • 随机获取一个设置了过期时间的key,检查该key是否过期,如果过期,删除key
      • 判断定期删除操作是否已经达到指定时长,若已经达到,直接退出定期删除。

35. MySQL 里有 2000w 数据,redis 中只存 20w 的数据,如何保证 redis 中都是热点数据?

redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。

redis 提供 6种数据淘汰策略:

  • 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(驱逐):禁止驱逐数据

36.写代码,基于redis的列表实现 先进先出、后进先出队列、优先级队列。

先进先出

1
2
3
4
# 进队列
lpush list 1
# 出队列
rpop list

后进先出

1
2
3
4
# 进队列
lpush list 1
# 出队列
lpop list

优先级队列

单一列表的实现

在遇到高级别任务时,可以直接插队,直接放入队列头部(rpush),这样,从队列头部(右侧)获取任务时,取到的就是高优先级的任务(rpop)

优点是实现简单,缺点是高级别任务总是后进先出,适用于简单的队列需求,高优先级任务较少的情况

多队列实现

使用两个队列,一个普通队列,一个高级队列,针对任务的级别放入不同的队列

获取任务时也很简单,redis的BRPOP命令可以按顺序从多个队列中取值

BRPOP会按照给出的 key 顺序查看,并在找到的第一个非空 list 的尾部弹出一个元素

redis> BRPOP list1 list2 0

list1 做为高优先级任务队列

list2 做为普通任务队列

这样就实现了先处理高优先级任务,当没有高优先级任务时,就去获取普通任务

使用权值

有4个元素需要入队

a级别1,b级别2,c级别3,d级别3

使用 lpush 把他们入队,同时设置权值

1
2
3
4
5
6
7
8
9
10
11
redis> lpush mylist a
redis> set mylist_score_a 1

redis> lpush mylist b
redis> set mylist_score_b 2

redis> lpush mylist c
redis> set mylist_score_a 3

redis> lpush mylist d
redis> set mylist_score_a 3

根据权值排序,并取出排名第一的元素

1
redis> sort mylist by mylist_score_* limit 0 1

结果为:c,正是我们想要的,c 的级别最高,并且是先进入队列的

获取完成后,要移除此元素

1
redis> lrem mylist 0 c

37.如何基于redis实现消息队列?

通过编写生成者和消费者。

生产者将生产的队列信息添加到Redis中,然后通过轮训队列,如果队列不为空,则取出队列中的任务进行消费。

38.如何基于redis实现发布和订阅?以及发布订阅和消息队列的区别?

“发布/订阅”模式包含两种角色,分别是发布者和订阅者。订阅者可以订阅一个或若干个频道(channel),而发布者可以向指定的频道发送消息,所有订阅此频道的订阅者都会收到此消息。

发布者发送消息的命令是PUBLISH,用法是PUBLISH channel message,

订阅频道的命令是SUBSCRIBE,可以同时订阅多个频道,

发出去的消息不会被持久化,也就是说当客户端订阅channel后只能收到后续发布到该频道的消息,之前发送到就收不到了。

而消息队列的数据可以被持久化。

39.什么是codis及作用?

Codis 是一个分布式 Redis 解决方案, 对于上层的应用来说, 连接到 Codis Proxy 和连接原生的 Redis Server 没有明显的区别 (不支持的命令列表), 上层应用可以像使用单机的 Redis 一样使用, Codis 底层会处理请求的转发, 不停机的数据迁移等工作, 所有后边的一切事情, 对于前面的客户端来说是透明的, 可以简单的认为后边连接的是一个内存无限大的 Redis 服务.。

40.什么是twemproxy及作用?

Twemproxy 也叫 nutcraker。是 Twtter 开源的一个 Redis 和 Memcache 代理服务器,主要用于管理 Redis 和 Memcached 集群,减少与Cache 服务器直接连接的数量。

其功能:

通过代理的方式减少缓存服务器的连接数。

自动在多台缓存服务器间共享数据。

通过不同的策略与散列函数支持一致性散列。

通过配置的方式禁用失败的结点。

运行在多个实例上,客户端可以连接到首个可用的代理服务器。

支持请求的流式与批处理,因而能够降低来回的消耗。

其缺点:

不支持针对多个值的操作,比如取sets的子交并补等。

不支持Redis的事务操作。

错误消息、日志信息匮乏,排查问题困难。

41.写代码实现redis事务操作。

redis事务从开始到结束通常会通过三个阶段:

1.事务开始 2.命令入队 3.事务执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
redis > MULTI 
OK

redis > SET "username" "bugall"
QUEUED

redis > SET "password" 161616
QUEUED

redis > GET "username"

redis > EXEC
1) ok
2) "bugall"
3) "bugall"

42.redis中的watch的命令的作用?

WATCH命令可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行。监控一直持续到EXEC命令(事务中的命令是在EXEC之后才执行的,所以在MULTI命令后可以修改WATCH监控的键值)

43.基于redis如何实现商城商品数量计数器?

46.如何高效的找到redis中所有以oldboy开头的key?

但由于KEYS命令一次性返回所有匹配的key,所以,当redis中的key非常多时,对于内存的消耗和redis服务器都是一个隐患,

对于Redis 2.8以上版本给我们提供了一个更好的遍历key的命令 SCAN 该命令的基本格式:

1
SCAN cursor [MATCH pattern] [COUNT count]
-------------本文结束感谢您的阅读-------------

本文标题:Python面试大全

文章作者:jiangyixin

发布时间:2018年06月10日 - 13:06

最后更新:2018年06月10日 - 18:06

原始链接:http://blog.jiangyixin.top/2018/06/10/Python面试题大全/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。