Python面试汇总(三)——redis

Redis是什么?

  1. 是一个完全开源免费的key-value内存数据库
  2. 通常被认为是一个数据结构服务器,主要是因为其有着丰富的数据结构 strings、map、 list、sets、 sorted sets

redis支持的5种数据结构

https://blog.csdn.net/qq_36299025/article/details/92851603

Redis数据库

​ 通常局限点来说,Redis也以消息队列的形式存在,作为内嵌的List存在,满足实时的高并发需求。在使用缓存的时候,redis比memcached具有更多的优势,并且支持更多的数据类型,把redis当作一个中间存储系统,用来处理高并发的数据库操作

redis特点

  • 持久化:将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用
  • 支持多种数据类型:String, list, set, zset, hash
  • 数据备份:master(主) - slave(从) 模式的数据备份
  • 高性能:读速度110000次/s,写速度81000次/s
  • 原子性:所有操作都是原子性的

    Redis缺点

  • 是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。
  • Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源造成了很大的浪费。

什么是Redis持久化?Redis有哪几种持久化方式?优缺点是什么?

持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。

Redis 提供了两种持久化方式:RDB(默认) 和AOF 

RDB是Redis默认的持久化方式。按照一定的时间周期策略把内存的数据以快照的形式保存到硬盘的二进制文件。即Snapshot快照存储,对应产生的数据文件为dump.rdb,通过配置文件中的save参数来定义快照的周期。( 快照可以是其所表示的数据的一个副本,也可以是数据的一个复制品。)
AOF:Redis会将每一个收到的写命令都通过Write函数追加到文件最后,类似于MySQL的binlog。当Redis重启是会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容
当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复。

RDB

RDB的持久化方式是通过快照方式完成的,当符合某种规则时,会将内存中的数据全量生成一份副本存到硬盘行,这个过程称为“快照”。

快照执行原理

(1) Redis使用fork函数复制一份当前进程(父进程)的副本(子进程)

(2) 父进程继续处理来自客户端的请求,子进程开始将内存中的数据写入硬盘中的临时文件

(3) 当子进程写完所有的数据后,用该临时文件替换旧的RDB文件,至此,一次快照操作完成

(4) 注意:在执行fork的时候操作系统(类Unix操作系统)会使用写时复制(copy-on-write)策略,即fork函数发生的一刻,父进程和子进程共享同一块内存数据,当父进程需要修改其中的某片数据(如执行写命令)时,操作系统会将该片数据复制一份以保证子进程不受影响,所以RDB文件存储的是执行fork操作那一刻的内存数据。所以RDB方式理论上是会存在丢数据的情况的(fork之后修改的的那些没有写进RDB文件)

快照备份规则

(1) 根据配置规则进行自动快照:在配置文件redis/etc/redis.conf内,进行自定义快照条件。在快照条件缺省的条件下,快照会存放在简介中介绍的dump.rdb文件中。自定义快照条件则是通过「save time(时长, 单位s) num(变化key的个数) 」对进行快照备份的频率进行设置,例如「 save 900 1 」表示15分钟内1个key发生变化,进行快照备份。每个快照条件单独占一行,条件之间是‘or '或的关系,满足任何一个就进行自动快照备份

(2) 用户执行SAVE,BGSAVE命令:除了让Redis自动进行快照外,当我们需要重启,迁移,备份Redis时,我们也可以手动执行SAVE或BGSAVE命令主动进行快照操作。

  • SAVE命令当执行SAVE命令时,Redis同步进行快照操作,期间会阻塞所有来自客户端的请求,所以放数据库数据较多时,应该避免使用该命令;
  • BGSAVE命令从命令名字就能看出来,这个命令与SAVE命令的区别就在于该命令的快照操作是在后台异步进行的,进行快照操作的同时还能处理来自客户端的请求。执行BGSAVE命令后Redis会马上返回OK表示开始进行快照操作,如果想知道快照操作是否已经完成,可以使用LASTSAVE命令返回最近一次成功执行快照的时间,返回结果是一个Unix时间戳。

(3) 执行FLUSHALL命令:当执行FLUSHALL命令时,Redis会清除数据库中的所有数据。需要注意的是:不论清空数据库的过程是否触发了自动快照的条件,只要自动快照条件不为空,Redis就会执行一次快照操作,当没有定义自动快照条件时,执行FLUSHALL命令不会进行快照操作。

(4) 执行复制(replication)操作:当设置了主从模式时,Redis会在复制初始化时进行自动快照。

数据迁移

问题:有三个Redis,分别命名为Redis1、Redis2、Redis3;现在希望将Redis1和Redis2中的数据迁移到Redis3中,同时不希望Reds3中rdb的数据丢失,那么如何将三个rdb文件融合为一个呢?

解决方式一:将Rdeis3先设置为Redis1的从数据库,进行数据同步;然后在将Redis3设置为Redis2的从数据库,进行数据同步。

解决方式二:(注意该方法适合用于临时恢复和导出数据,且需要关闭AOF持久化功能)

(1) 复制RDB文件:将Redis1的dump1.rdb、Redis2的dump2.rdb复制到Redis3的data目录下

(2) 获取RDB文件大小:dump1.rdb大小 = 27441041、dump2.rdb大小 = 37、dump3.rd大小 = 570214520

(3) 按照文件合并顺序截取文件:dump1.rdb为文件头,去掉尾部的9个字节:「dd bs=1 if=dump1.rdb of=dump.rdb count=27441032」

      dump2.rdb为中间文件,去掉头部的11个字节,去掉尾部的9个字节:「dd bs=1 if=dump2.rdb of=res2.rdb count=17」

      dump3.rdb为尾部文件,去掉头部的11个字节,去掉北部的8个字节:「dd bs=1 if=dump3.rdb of=res3.rdb count=570214501」

(4) 合并截取文件:cat res2.rdb >> dump.rdb

cat res3.rdb >> dump.rdb

(5) 验证备份文件:执行命令「redis-check-rdb dump.rdb 」

(6) 修改配置文件:将redis.conf中的rdbchecksum设置为no

(7) 重启Redis3,所有备份数据在数据库内生效

AOF

AOF的持久化方式是将Redsi执行的每一条命令追加写到硬盘文件中,这一过程坑你会降低Redis的性能,但大部分情况这个影响是可以接收的,默认情况下,AOF的持计划功能是关闭的,可以通过修改redis.conf文件的配置进行功能开启,本分文件的存储路径与dump.rdb是一致的。

实现过程

(1) 概述:AOF以纯文本的形式记录Redis执行的写命令,将Redis按照上面提到的配置打开AOF持久化功能,执行命令「set test ceshi」,打开appendonly.aof 查看持久化内容信息,其文本中记录命令的规则这里不展开细说,感兴趣可以深入学习一下

(2) AOF文件重写:AOF记录用户操作Redis的命令行,但如果每一条命令行都记录,那appendonly.aof文件会变的特别大,所以Redis设计了AOF文件重写机制。重写机制就是重新生成一份AOF文件,新的AOF文件中一条记录的操作只会有一次,而不像老文件中存在对同一数值进行多次操作的现象。重写的实现过程与RDB备份快照的过程类似,也是fork一个进程,直接遍历数据,写入新的AOF临时文件。在写入新文件的过程中,所有的写操作日志还是会写到原来老的AOF文件中,同时还会记录在内存缓冲区中。当重完操作完成后,会将所有缓冲区中的日志一次性写入到临时文件中。然后调用原子性的rename命令用新的 AOF文件取代老的AOF文件。

如下图中的操作,重写后的AOF文件只会记录最后一条命令「set test ceshi3」

AOF文件重写规则

(1) 根据配置规则进行自动重写:在配置文件redis/etc/redis.conf内,进行自定义重写条件。

  • auto-aof-rewrite-percentage:目前AOF文件的大小超出上一次重写时AOF文件大小的百分之多少时进行重写,如果没有重写过就以启动时AOF文件的大小为标准
  • auto-aof-rewrite-min-size:当AOF文件的大小超过设定的数字时进行重写

通常以上两个配置是一起使用

(2) 用户执行BGREWRITEAOF命令进行重写:执行BGREWRITEAOF命令后,AOF文件中仅保留了「set test ceshi3」命令

同步硬盘

虽然每次执行更改数据库的内容时,AOF都会记录执行的命令,但是由于操作系统本身的硬盘缓存的缘故,AOF文件的内容并没有真正地写入硬盘,在默认情况下,操作系统会每隔30s将硬盘缓存中的数据同步到硬盘,但是为了防止系统异常退出而导致丢数据的情况发生,我们还可以在Redis的配置文件中配置这个同步的频率

RDB与AOF的优劣势

二者选择的标准,就是看系统是愿意牺牲一些性能,换取更高的缓存一致性(AOF),还是愿意写操作频繁的时候,不启用备份来换取更高的性能,待手动运行save的时候,再做备份(RDB)

RDB的优势:(1) 数据可移植性好:仅存在一个数据备份文件,对数据压缩后可以轻松转移到其他存储介质上;(2) 性能最大化:RDB本分方式只需fork一个子进程,由子进程完成全部持久化工作,极大的避免了服务进程执行IO操作,当数据集很大时,其效率也高于AOF机制;

RDB的劣势:(1) 在数据未持久化之前出现宕机现象,那么未持久化的数据会丢失;(2) 当数据集较大时,fork子进程的备份工作会导致服务整体停止对外服务几百毫秒,甚至1秒

AOF的优势:(1) 数据安全性更高:写Redis命令采用append的模式记录,出现宕机,原有数据不会丢失,写入了一半数据就出现了系统崩溃问题,不用担心,在Redis下一次启动之前,我们可以通过redis-check-aof工具来帮助我们解决数据一致性的问题;

AOF的劣势:(1) 相同数据集,AOF文件大于RDB文件,且恢复速度慢于RDB;(2) 数据同步效率低于RDB

Redis主从复制原理

原理概述:一主多从 (1个master,多个slave) ;全量同步&增量同步。

一主多从:主数据库进行读写操作,当发生写操作时自动将数据同步到从数据库;从数据库一般只进行读操作,并接收主数据库同步过来的数据,一个主数据库可以有多个从数据库,但一个从数据库只能有一个主数据库。通过这种架构能够很好的实现读写分离,从而提高服务器的负载能力。

全量同步:

    • 当数据库启动时,会向主数据库发送sync命令
    • 主数据库接收到sync命令会开始在后台保存快照(执行rdb操作),并用缓存区记录后续的所有写操作
    • 当主服务器的快照保存完成后,redis会将快照文件发送给从数据库
    • 从数据库接收快照文件后,会丢弃所有的旧数据,载入收到的快照
    • 主服务器的快照发送完毕后,开始向从服务器发送缓冲区中的写命令
    • 从服务器完成对快照对载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令

增量同步:

    • 从数据库初始化工作完成后,主数据库发生的写操作同步到从数据库到过程为增量同步
    • 增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。

全量&增量同步选择:

    • 主数据库启动创建链接时,采用全量同步
    • 全量同步结束后,采用增量同步
    • 如果需要,slave在任何时候都可以发起全量同步,redis的策略是首先尝试增量同步,如果失败,进行全量同步

redis主从配置:

(1)在slave的redis.conf中修改配置信息,ip : port对应master的ip : port,masterauth参数对应master的密码,有则配置,没有则不用关

(2)启动slave:./bin/redis-server etc/redis.conf

(3)登录master:./redis-cli -p 'port',输入info,查看slave是否配置成功;

(4)登录slave也可以查看:./redis-cli -p 'port',输入info

(5)如果master挂了,需要将slave切换成master登录slave:./redis-cli -p 'port',执行:SLAVEOF NO ONE,执行:info

(6)master修复后,可在slave上执行:SLAVEOF 127.0.0.1 'port'(SLAVEOF master_ip master_port),重新将其挂接在master上

(六) Redis的原子性

原子性概念:一个事务中的所有操作,要么全部完成,要不全部不完成,不会结束在之间某个环节,Redis的原子性就是指其操作命令要么执行,要么不执行

Redis操作原子性的原因:Redis是单线程的,线程是操作系统运算调度的最小单元

(七) Redis的事务性

MULTI, EXEC, DISCARD and WATCH 是Redis事务的基础。用来显式开启并控制一个事务,它们允许在一个步骤中执行一组命令。并提供两个重要的保证:

    事务中的所有命令都会被序列化并按顺序执行。在执行Redis事务的过程中,不会出现由另一个客户端发出的请求。这保证 命令队列 作为一个单独的原子操作被执行。
    队列中的命令要么全部被处理,要么全部被忽略。EXEC命令触发事务中所有命令的执行,因此,当客户端在事务上下文中失去与服务器的连接,
        如果发生在调用MULTI命令之前,则不执行任何commands;
        如果在此之前EXEC命令被调用,则所有的commands都被执行。

Redis为什么会这么快

1、Redis是纯内存操作,需要的时候需要我们手动持久化到硬盘中

2、Redis是单线程,从而避开了多线程中上下文频繁切换的操作。

3、Redis数据结构简单、对数据的操作也比较简单

4、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求

5、使用多路I/O复用模型,非阻塞I/O

多路I/O复用: I/O 多路复用技术是为了解决进程或线程阻塞到某个 I/O 系统调用而出现的技术,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪,就是这个文件描述符进行读写操作之前),能够通知程序进行相应的读写操作

redis的过期策略以及内存淘汰机制

问题:比如你redis只能存5G数据,可是你写了10G,那会删5G的数据。怎么删的,这个问题思考过么?还有,你的数据已经设置了过期时间,但是时间到了,内存占用率还是比较高,有思考过原因么? 

策略:定期删除 + 惰性删除策略 + 内存淘汰机制

定期删除策略的工作原理:定时删除,用一个定时器来负责监视key,过期则自动删除。虽然内存及时释放,但是十分消耗CPU资源。在大并发请求下,CPU要将时间应用在处理请求,而不是删除key,因此没有采用这一策略。

定期删除 + 惰性删除策略的工作原理:定期删除,redis默认每个100ms检查,是否有过期的key,有过期key则删除。需要说明的是,redis不是每个100ms将所有的key检查一次,而是随机抽取进行检查(如果每隔100ms,全部key进行检查,redis岂不是卡死)。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。于是,惰性删除派上用场。也就是说在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除。

内存淘汰机制:如果定时删除没有删除key,用户也没有及时请求key,即惰性删除也没有生效,这样redis的内存使用率会越来越高,那么就需要使用内存淘汰机制。在redis/etc/redis.conf中会有内存淘汰机制的配置规则:

该配置为redis内存淘汰策略的设定配置:

  • volatile-lru : 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。这种情况一般是把redis既当缓存,又做持久化存储的时候才用。
  • allkeys-lru : 当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。推荐使用。
  • volatile-random : 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。
  • allkeys-random : 当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。应该也没人用吧,你不删最少使用Key,去随机删。
  • volatile-ttl : 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。
  • noeviction : 当内存不足以容纳新写入数据时,新写入操作会报错。应该没人用吧。

同时有多个子系统去set一个key。这个时候要注意什么呢?

不推荐使用redis的事务机制。因为我们的生产环境,基本都是redis集群环境,做了数据分片操作。你一个事务中有涉及到多个key操作的时候,这多个key不一定都存储在同一个redis-server上。因此,redis的事务机制,十分鸡肋。
(1)如果对这个key操作,不要求顺序: 准备一个分布式锁,大家去抢锁,抢到锁就做set操作即可
(2)如果对这个key操作,要求顺序: 分布式锁+时间戳。 假设这会系统B先抢到锁,将key1设置为{valueB 3:05}。接下来系统A抢到锁,发现自己的valueA的时间戳早于缓存中的时间戳,那就不做set操作了。以此类推。
(3) 利用队列,将set方法变成串行访问也可以

redis遇到高并发,如果保证读写key的一致性
对redis的操作都是具有原子性的,是线程安全的操作,你不用考虑并发问题,redis内部已经帮你处理好并发的问题了。

Redis主要有哪些功能?


1.哨兵(Sentinel)和复制(Replication)
Redis服务器毫无征兆的罢工是个麻烦事,如何保证备份的机器是原始服务器的完整备份呢?这时候就需要哨兵和复制。

哨兵Sentinel可以管理多个Redis服务器,它提供了监控,提醒以及自动的故障转移的功能,Replication则是负责让一个Redis服务器可以配备多个备份的服务器。

Redis也是利用这两个功能来保证Redis的高可用的。

2.事务
很多情况下我们需要一次执行不止一个命令,而且需要其同时成功或者失败。redis对事务的支持也是源自于这部分需求,即支持一次性按顺序执行多个命令的能力,并保证其原子性。

3.LUA脚本
在事务的基础上,如果我们需要在服务端一次性的执行更复杂的操作(包含一些逻辑判断),则lua就可以排上用场了。

4.持久化
redis的持久化指的是redis会把内存的中的数据写入到硬盘中,在redis重新启动的时候加载这些数据,从而最大限度的降低缓存丢失带来的影响。

5.集群(Cluster)
单台服务器资源的总是有上限的,CPU资源和IO资源我们可以通过主从复制,进行读写分离,把一部分CPU和IO的压力转移到从服务器上,这也有点类似mysql数据库的主从同步。

 Redis为什么是单线程的?

多线程处理会涉及到锁,而且多线程处理会涉及到线程切换而消耗CPU。因为CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存或者网络带宽。单线程无法发挥多核CPU性能,不过可以通过在单机开多个Redis实例来解决。

使用Redis的优势?

1、速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)

2、支持丰富数据类型,支持string,list,set,sorted set,hash

3、支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行

4、丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除

Redis读写分离模型

通过增加Slave DB的数量,读的性能可以线性增长。为了避免Master DB的单点故障,集群一般都会采用两台Master DB做双机热备,所以整个集群的读和写的可用性都非常高。

读写分离架构的缺陷在于,不管是Master还是Slave,每个节点都必须保存完整的数据,如果在数据量很大的情况下,集群的扩展能力还是受限于单个节点的存储能力,而且对于Write-intensive类型的应用,读写分离架构并不适合。

Redis数据分片模型

为了解决读写分离模型的缺陷,可以将数据分片模型应用进来。
可以将每个节点看成都是独立的master,然后通过业务实现数据分片。
结合上面两种模型,可以将每个master设计成由一个master和多个slave组成的模型。

 Redis常见性能问题和解决方案?

(1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
(2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
(3) 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
(4) 尽量避免在压力很大的主库上增加从库
(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3...
这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。

你可能感兴趣的:(Python面试)