redis总结

1、什么是Redis?

Redis(Remote Dictionary Server) 是一个使用 C 语言编写的,速度非常快的非关系型(NoSQL)内存键值数据库,可以存储键和五种不同类型的值之间的映射。不过与传统数据库不同的是 Redis 的数据是存在内存中的 ,也就是它是内存数据库,所以读写速度非常快,因此 Redis 被广泛应用于缓存方向。

2、为什么要用 Redis/为什么要用缓存?

“高性能”:从缓存读取数据比从硬盘读取数据快

“高并发”:虽然是单线程,但是可以支持并发!

一般像 MySQL 这类的数据库的 QPS 大概都在 1w 左右(4 核 8g) ,但是使用 Redis 缓存之后很容易达到 10w+,甚至最高能达到 30w+(就单机 redis 的情况,redis 集群的话会更高)。

QPS(Query Per Second):服务器每秒可以执行的查询次数;

2、值支持五种数据类型

键的类型只能为字符串,值支持五种数据类型:字符串、列表、集合、散列表、有序集合。

Redis的底层数据结构有 SDS,双向链表、整数数组、跳表,字典(哈希表),压缩列表

3、应用场景

计数器(可以对 String 进行自增自减运算),缓存,查找表,消息队列(List 是一个双向链表,可以通过 lpush 和 rpop 写入和读取消息),会话缓存,分布式锁实现,Set 可以实现交集、并集等操作,从而实现共同好友等功能,ZSet 可以实现有序性操作,从而实现排行榜等功能。

Bitmap二值状态统计,适用数据量大,且可以使用二值状态表示的统计,比如:

  • 签到打卡,当天用户签到数

  • 用户周活跃

  • 用户在线状态

4、Redis 与 Memcached

数据类型,分布式,持久化,

数据类型

Memcached 仅支持字符串类型,而 Redis 支持五种不同的数据类型,可以更灵活地解决问题。

数据持久化

Redis 支持两种持久化策略:RDB 快照和 AOF 日志,而 Memcached 不支持持久化。

分布式

Memcached 不支持分布式,只能通过在客户端使用一致性哈希来实现分布式存储,这种方式在存储和查询时都需要先在客户端计算一次数据所在的节点。

Redis Cluster 实现了分布式的支持。

内存管理(淘汰机制),

  • 在 Redis 中,并不是所有数据都一直存储在内存中,可以将一些很久没用的 value 交换到磁盘,而 Memcached 的数据则会一直在内存中。

  • Memcached 将内存分割成特定长度的块来存储数据,以完全解决内存碎片的问题。但是这种方式会使得内存的利用率不高,例如块的大小为 128 bytes,只存储 100 bytes 的数据,那么剩下的 28 bytes 就浪费掉了。

支持原生的集群模式,Memcached 是多线程,非阻塞 IO 复用的网络模型;Redis 使用单线程的多路 IO 复用模型。(Redis 6.0 引入了多线程 IO )

5、过期时间和淘汰机制

三种过期的数据的删除策略!Redis 可以为每个键设置过期时间,当键过期时,会自动删除该键。

注意:Redis 中除了字符串类型有自己独有设置过期时间的命令 setex 外,其他方法都需要依靠 expire 命令来设置过期时间 。另外, persist 命令可以移除一个键的过期时间:

过期时间除了有助于缓解内存的消耗,还有什么其他用么?

很多时候,我们的业务场景就是需要某个数据只在某一时间段内存在,比如我们的短信验证码可能只在 1 分钟内有效,用户登录的 token 可能只在 1 天内有效。如果使用传统的数据库来处理的话,一般都是自己判断过期,这样更麻烦并且性能要差很多。

Redis 通过一个叫做过期字典(可以看作是 hash 表)来保存数据过期的时间。过期字典的键指向 Redis 数据库中的某个 key(键),过期字典的值是一个 long long 类型的整数,这个整数保存了 key 所指向的数据库键的过期时间(毫秒精度的 UNIX 时间戳)。

对于散列表这种容器,只能为整个键设置过期时间(整个散列表),而不能为键里面的单个元素设置过期时间。

  1. 惰性删除 :只会在取出 key 的时候才对数据进行过期检查。这样对 CPU 最友好,但是可能会造成太多过期 key 没有被删除。

    优点:对 CPU友好,我们只会在使用该键时才会进行过期检查,对于很多用不到的key不用浪费时间进行过期检查。

    缺点:对内存不友好,如果一个键已经过期,但是一直没有使用,那么该键就会一直存在内存中,如果数据库中有很多这种使用不到的过期键,这些键便永远不会被删除,内存永远不会释放。从而造成内存泄漏。

  2. 定期删除 : 每隔一段时间抽取一批 key 执行删除过期 key 操作。并且,Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影响。

    优点:可以通过限制删除操作执行的时长和频率来减少删除操作对 CPU 的影响。另外定期删除,也能有效释放过期键占用的内存。

    缺点:难以确定删除操作执行的时长和频率。

定期删除对内存更加友好,惰性删除对 CPU 更加友好。两者各有千秋,所以 Redis 采用的是 定期删除+惰性删除 ,删除的对象是已过期的 key.

但是,仅仅通过给 key 设置过期时间还是有问题的。因为还是可能存在定期删除和惰性删除漏掉了很多过期 key 的情况。这样就导致大量过期 key 堆积在内存里,然后就 Out of memory 了。怎么解决这个问题呢?答案就是: Redis 内存淘汰机制。

volatile-lru,volatile-ttl,volatile-random,allkeys-lru,allkeys-random,noeviction,Redis 4.0 引入了 volatile-lfu 和 allkeys-lfu 淘汰策略。

在一些场景下,有些数据被访问的次数非常少,甚至只会被访问一次。当这些数据服务完访问请求后,如果还继续留存在缓存中的话,就只会白白占用缓存空间,所以需要 LFU 。

6、持久化

1.RDB

RDB的原理是 fork 和 cow 。优缺点。

RDB的原理是 fork 和 cow 。fork是指redis通过创建子进程来进行RDB操作,cow指的是copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。

快照持久化的内存策略是:子进程按照数据页进行复制。在持久化过程中,子进程负责持久化过程,它将主进程数据段的数据页复制一份出来,然后对原数据页进行存储;同一时间,主进程对那份复制出来的数据进行操作。通过复制小数据量数据页(通常每个数据页只有 4KB)的方式,保证了主进程修改数据不会影响子进程存储。

快照触发方式有自动触发与手动触发两种。

  • 自动触发:通过 redis.conf 配置文件进行配置;

  • 手动触发

    • save: 阻塞式触发,在未完成前,客户端无法进行命令操作;

    • bgsave: 非阻塞式触发,主进程 fork 出子进程进行备份操作;

在恢复文件时,将备份文件 (dump.rdb) 放在 Redis 安装目录,然后启动 Redis,就能把 RDB 中的文件加载到 Redis 服务中。

快照存储的优点缺点:

  • 优点

    • 数据结构紧凑,保存了 Redis 服务在某个时间点上的数据集,非常适合做备份和灾难恢复;

    • 进行 RDB 快照持久化时,主进程会 fork 出一个子进程进行备份工作,主进程不需要额外的IO 操作

    • RDB 恢复大数据集时,速度比 AOF 快;

  • 缺点

    • 版本兼容问题:Redis 版本更新过程中有多个 RDB 版本,存在老版 RDB 兼容性无法兼容新版的问题;

    • 无法做到实时持久化,因为 bgsave 时的 fork 操作每次都会创建子进程,内存中的数据被克隆了一份,属于重量级操作。

2.AOF

AOF (Append only file)机制默认关闭 。也有fork函数操作

AOF 机制默认关闭

# 每次操作都存储
appendfsync always
# 每个一秒存储一次
appendfsync everysec
# 关闭
appendonly no

AOF 日志存储的是 Redis 服务器的顺序指令序列,它只记录对内存进行修改的指令记录。AOF 使用追加记录的方式,在 Redis 长期运行的过程中,AOF 日志会越来越长,所以一旦宕机重启载入 AOF 日志,将会是一个非常耗时的功能,因此我们需要对 AOF 进行瘦身,即 AOF 重写。

由于AOF持久化是Redis不断将写命令记录到 AOF 文件中,随着Redis不断的进行,AOF 的文件会越来越大,文件越大,占用服务器内存越大以及 AOF 恢复要求时间越长。为了解决这个问题,Redis新增了重写机制,当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集。可以使用命令 bgrewriteaof 来重新。

AOF 重写并不是对原始的 AOF 文件进行重新整理,而是 fork 一个子进程遍历服务器的键值对,转换成一系列 Redis 的操作指令,序列化到一个新的 AOF 日志文件中。序列化完毕后,将原来的文件替换为序列化后的文件即可。

AOF 重写分为两个过程,一个是 fork 子进程重写过程执行的原始数据内容,另一个是在 fork 过程中主进程修改的指令

7、事务

在单线程程序中,任务一个一个地做,必须做完一个任务后,才会去做另一个任务。因而redis的操作保证了原子性。Redis 最简单的事务实现方式是使用 MULTI 和 EXEC 命令将事务操作包围起来。Redis 是不支持 roll back 的,因而不满足原子性的(而且不满足持久性)。

Redis 是单进程程序,并且它保证在执行事务时,不会对事务进行中断,事务可以运行直到执行完所有事务队列中的命令为止。因此,Redis 的事务是总是带有隔离性的。

Redis单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。

Redis支持两种方式的持久化,一种是RDB的方式,一种是AOF的方式。前者会根据指定的规则定时将内存中的数据存储在硬盘上,而后者在每次执行完命令后将命令记录下来。一般将两者结合使用。

Redis 作者认为基本只会出现在开发环境的编程错误其实在生产环境基本是不可能出现的(例如对 String 类型的数据库键执行 LPUSH 操作),所以他觉得没必要为了这事务回滚机制而改变 Redis 追求简单高效的设计主旨

8、事件

服务器通过套接字与客户端或者其它服务器进行通信,文件事件就是对套接字操作的抽象。

Redis 基于 Reactor 模式开发了自己的网络事件处理器,使用 I/O 多路复用程序来同时监听多个套接字,并将到达的事件传送给文件事件分派器,分派器会根据套接字产生的事件类型调用相应的事件处理器。

redis总结_第1张图片


9、单线程模式

1.为什么使用单线程?

  1. 单线程编程容易并且更容易维护;

  2. 多线程就会存在死锁、线程上下文切换等问题,甚至会影响性能;

  3. 使用单线程模型能带来更好的可维护性,方便开发和调试;

  4. 使用单线程模型也能并发的处理客户端的请求;

  5. Redis 服务中运行的绝大多数操作的性能瓶颈都不是 CPU,主要在内存和网络

最后一个是最终使用单线程模型的决定性因素。

Redis 并不是 CPU 密集型的服务,如果不开启 AOF 备份,所有 Redis 的操作都会在内存中完成不会涉及任何的 I/O 操作,这些数据的读写由于只发生在内存中,所以处理速度是非常快的;整个服务的瓶颈在于网络传输带来的延迟和等待客户端的数据传输,也就是网络 I/O,所以使用多线程模型处理全部的外部请求可能不是一个好的方案。

2.为什么引进多线程?

Redis 引入多线程操作也是出于性能上的考虑,对于一些大键值对的删除操作,通过多线程非阻塞地释放内存空间也能减少对 Redis 主线程阻塞的时间,提高执行的效率。

我们可以在 Redis 在中使用 DEL 命令来删除一个键对应的值,如果待删除的键值对占用了较小的内存空间,那么哪怕是同步地删除这些键值对也不会消耗太多的时间。

但是对于 Redis 中的一些超大键值对,几十 MB 或者几百 MB 的数据并不能在几毫秒的时间内处理完,Redis 可能会需要在释放内存空间上消耗较多的时间,这些操作就会阻塞待处理的任务,影响 Redis 服务处理请求的 PCT99 和可用性。

然而释放内存空间的工作其实可以由后台线程异步进行处理,这也就是 UNLINK 命令的实现原理,它只会将键从元数据中删除,真正的删除操作会在后台异步执行。

步骤一:将待删除数据对从字典表中删除; 步骤二:释放待删除数据所占用的内存空间。 如果这两个步骤同步执行,就叫 同步删除;而如果只执行步骤一,将来通过后台线程来执行步骤二,就叫 异步删除。而惰性删除 里的惰性其实指的就是 删除时只执行步骤一,而将步骤二 "延迟" 到后台线程执行。

10、缓存一致性问题

11、缓存穿透、缓存击穿和缓存雪崩

缓存穿透

缓存穿透是指,缓存和数据库都没有的数据,被大量请求,比如订单号不可能为-1,但是用户请求了大量订单号为-1的数据,由于数据不存在,缓存就也不会存在该数据,所有的请求都会直接穿透到数据库。

如果被恶意用户利用,疯狂请求不存在的数据,就会导致数据库压力过大,甚至垮掉。

注意:穿透的意思是,都没有,直接一路打到数据库。

解决方法

  • 接口增加业务层级的Filter,进行合法校验,这可以有效拦截大部分不合法的请求

  • 作为第一点的补充,最常见的是使用布隆过滤器,针对一个或者多个维度,把可能存在的数据值hash到bitmap中,bitmap证明该数据不存在则该数据一定不存在,但是bitmap证明该数据存在也只能是可能存在,因为不同的数值hash到的bit位很有可能是一样的,hash冲突会导致误判,多个hash方法也只能是降低冲突的概率,无法做到避免。

  • 另外一个常见的方法,则是针对数据库与缓存都没有的数据,对空的结果进行缓存,但是过期时间设置得较短,一般五分钟内。而这种数据,如果数据库有写入,或者更新,必须同时刷新缓存,否则会导致不一致的问题存在。

BitMap ,用一个比特位来映射某个元素的状态, 只有 0 和 1 两种状态,非常典型的二值状态,且其本身是用 String 类型作为底层数据结构实现的一种统计二值状态的数据类型 ,优势大量节省内存空间,可是使用在二值统计场景。 可以把Bitmaps想象成一个以位为单位的数组, 数组的每个单元只能存储0和1, 数组的下标在Bitmaps中叫做偏移量。

缓存击穿

缓存击穿是指数据库原本有得数据,但是缓存中没有,一般是缓存突然失效了,这时候如果有大量用户请求该数据,缓存没有则会去数据库请求,会引发数据库压力增大,可能会瞬间打垮。

解决方法

  1. 如果是热点数据,那么可以考虑设置永远不过期。

  2. 如果数据一定会过期,那么就需要在数据为空的时候,设置一个互斥的锁,只让一个请求通过,只有一个请求去数据库拉取数据,取完数据,不管如何都需要释放锁,异常的时候也需要释放锁,要不其他线程会一直拿不到锁。

缓存雪崩

缓存雪崩是指缓存中有大量的数据,在同一个时间点,或者较短的时间段内,全部过期了,这个时候请求过来,缓存没有数据,都会请求数据库,则数据库的压力就会突增,扛不住就会宕机。

解决方法

  1. 如果是热点数据,那么可以考虑设置永远不过期。

  2. 缓存的过期时间除非比较严格,要不考虑设置一个波动随机值,比如理论十分钟,那这类key的缓存时间都加上一个1~3分钟,过期时间在7~13分钟内波动,有效防止都在同一个时间点上大量过期。

  3. 采用 Redis 集群,避免单机出现问题整个缓存服务都没办法使用。

  4. 限流,避免同时处理大量的请求。

缓存预热

缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!

缓存降级(丢卒保帅思想)

当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。

缓存降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。

12、分布式缓存

Redis 支持三种集群方案

  • 主从复制模式:原理;优缺点

  • Sentinel(哨兵)模式:原理;优缺点;集群监控,消息通知,故障转移

  • Cluster 模式:原理(slot插槽),如何保证高可用

13、Redis问题

你可能感兴趣的:(#,Redis,面试,java,面试,redis)