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

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

CAP定理

一致性(Consistency) (所有节点在同一时间具有相同的数据)
可用性(Availability) (保证每个请求不管成功或者失败都有响应)
分隔容忍(Partition tolerance) (系统中任意信息的丢失或失败不会影响系统的继续运作)
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,最多只能同时较好的满足两个。

因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类:

CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
CP - 满足一致性,分区容忍性的系统,通常性能不是特别高。
AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。

Redis支持的数据类型?

字符串类型String

字典Hash

列表List

集合Set

有序集合SortedSet

HyperLogLog、Geo、Pub/Sub

Redis Module、BloomFilter、RedisSearch、Redis-ML

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

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

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

  1. RDB(默认)
    RDB是Redis DataBase缩写,
    功能核心函数rdbSave(生成RDB文件)和rdbLoad(从文件加载内存)两个函数。
  2. Redis是国产中间件吗 redis是cap_数据库

  3. AOF
    AOF 是 Append-only file 缩写
  4. Redis是国产中间件吗 redis是cap_Redis是国产中间件吗_02

  5. 每当执行服务器(定时)任务或者函数时,flushAppendOnlyFile 函数都会被调用, 这个函数执行以下两个工作,“AOF写入保存”:
    WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件
    SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。
  6. RDB与AOF比较:
    1、AOF文件比RDB更新频率高,优先使用AOF还原数据。
    2、AOF比RDB更安全也更大
    3、RDB性能比AOF好
    4、如果两个都配了优先加载AOF

Redis中save和bgsave的区别
save 和 bgsave 两个命令都会调用 rdbSave 函数,但它们调用的方式各有不同:
save 直接调用 rdbSave ,阻塞 Redis 主进程,直到保存完成为止。在主进程阻塞期间,服务器不能处理客户端的任何请求。
bgsave 则 fork 出一个子进程,子进程负责调用 rdbSave ,并在保存完成之后向主进程发送信号,通知保存已完成。 Redis 服务器在bgsave 执行期间仍然可以继续处理客户端的请求。

Redis如何做持久化?
bgsave做镜像全量持久化, aof做增量持久化。因为bgsave会耗费较长时间, 不够实时, 在停机的时候会导致大量丢失数据, 所以aof来配合使用。在redis实例重启时, 会使用bgsave持久化文件重新构建内存, 再使用aof重放近期的操作指令来实现完整恢复重启之前的状态。
如果不要求性能, 在每条写指令是都sync一下磁盘, 就不会丢失数据。但是在高性能的要求下每次都sync是不现实的, 一般都使用定时sync, 比如1s1次, 这个时候最大就会丢失1s的数据。
bgsave的原理是, fork和cow。fork是指redis通过创建子进程来进行bgsave操作, cow指的是copy on write, 子进程创建后, 父进程通过共享数据段, 父进程继续提供读写服务, 写脏的页面数据会逐渐和子进程分离开来。

解释下什么是RESP?有什么特点?

RESP 是Redis客户端和服务端之前使用的一种通讯协议;
RESP 的特点:实现简单、快速解析、可读性好
在 RESP 中, 一些数据的类型通过它的第一个字节进行判断:

单行回复:回复的第一个字节是 “+”
错误信息:回复的第一个字节是 “-”
整形数字:回复的第一个字节是 “:”
多行字符串:回复的第一个字节是 “$”
数组:回复的第一个字节是 “*”

怎么使用Redis做异步队列?
Redis 的 list(列表) 数据结构常用来作为异步消息队列使用,使用rpush/lpush操作入队列,使用lpop 和 rpop来出队列。rpush 和 lpop 结合 或者lpush 和rpop 结合;
可是如果队列空了,客户端就会陷入 pop 的死循环,不停地 pop,没有数据,接着再 pop,又没有数据。这就是浪费生命的空轮询。空轮询不但拉高了客户端的 CPU,redis 的 QPS 也会被拉高,如果这样空轮询的客户端有几十来个,Redis 的慢查询可能会显著增多。 通常我们使用 sleep 来解决这个问题,让线程睡一会,睡个 1s 钟就可以了。不但客户端的 CPU 能降下来,Redis 的 QPS 也降下来了。
blpop/brpop。 这两个指令的前缀字符b代表的是blocking,也就是阻塞读。 阻塞读在队列没有数据的时候,会立即进入休眠状态,一旦数据到来,则立刻醒过来。消息的延迟几乎为零。用blpop/brpop替代前面的lpop/rpop,就可以显著的降低时延。

什么是“延迟队列”,有哪些应用场景
“延迟队列”首先是个消息队列,其次是个带延迟功能的消息队列。相对于普通消息队列,延迟队列中的消息除了消息本身外,还要有一个重要元素就是说明这条消息应该何时被消费掉!也就说在指定时间消费掉指定消息。

在业务发展过程中,会出现一些需要延时处理的场景,比如:

a.订单下单之后超过30分钟用户未支付,需要取消订单
b.订单一些评论,如果48h用户未对商家评论,系统会自动产生一条默认评论
c.点我达订单下单后,超过一定时间订单未派出,需要超时取消订单等。。。

处理这类需求,比较直接简单的方式就是定时任务轮训扫表。这种处理方式在数据量不大的场景下是完全没问题,但是当数据量大的时候高频的轮训数据库就会比较的耗资源,导致数据库的慢查或者查询超时。所以在处理这类需求时候,采用了延时队列来完成。

如何实现Redis延迟队列?
redis中有个数据结构叫做zset,给每个键都添加了一个score的元素,就是分数。我们可以拿时间戳当作score,消息内容作为key,然后通过zrevrange获取key的时候指定score范围即可。
使用sortedset, 拿时间戳作为score, 消息内容作为key调用zadd来生产消息, 消费者用zrangebysocre指令获取N秒之前的数据轮询进行处理。

什么是缓存穿透?如何避免?什么是缓存雪崩?如何避免?

  1. 什么是“缓存穿透”?
    一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。
  2. 如何避免“缓存穿透”?
    1) 对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。
    2) 对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。
  3. 什么是“缓存雪崩”?
    当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃。
  4. 如何避免“缓存雪崩”?
    1)在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
    2)做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期
    3)不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

Redis分布式锁是怎么实现的?

先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。
如果在setnx之后,执行expire之前进程意外crash或重启维护, 那么就需要把setnx和expire合成一条指令来用。

Redis和数据库双写一致性问题

一致性问题是分布式常见问题,还可以再分为最终一致性和强一致性。数据库和缓存双写,就必然会存在不一致的问题。答这个问题,先明白一个前提。就是如果对数据有强一致性要求,不能放缓存。我们所做的一切,只能保证最终一致性。另外,我们所做的方案其实从根本上来说,只能说降低不一致发生的概率,无法完全避免。因此,有强一致性要求的数据,不能放缓存。

首先,采取正确更新策略,先更新数据库,再删缓存。其次,因为可能存在删除缓存失败的问题,提供一个补偿措施即可,例如利用消息队列。