1、Redis 是什么?
Redis 是一个使用 C 语言开发的数据库,不过与传统数据库不同的是 Redis 的数据是存在内存中的 ,所以读写速度非常快,因此 Redis 被广泛应用于缓存方向。
2、分布式缓存有哪些方案?
memcache和redis
3、为什么要使用缓存?
主要是为了提升用户体验(响应速度更快)以及应对更多的用户(查询并发量更大)。
4、redis除了缓存,还能做什么?
分布式锁 、限流、消息队列、某些业务(通过 sorted set 维护排行榜)等。
5、常见的数据结构及其使用场景是什么?
在线 redis 环境学习命令。
5.1 String
简单的 key-value 类型,自己构建了一种简单动态字符串(simple dynamic string,SDS),不光可以保存文本数据还可以保存二进制数据,不会造成缓冲区溢出。
一般常用在需要计数的场景,比如用户的访问次数、热点文章的点赞转发数量等等。
5.2 list
链表,易于数据元素的插入和删除并且可以灵活调整链表长度,但是链表的随机访问困难。Redis 的 list 的实现为一个 双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。
用于发布与订阅或者说消息队列。
5.3 hash
内部实现也差不多(数组 + 链表)。hash 是一个 string 类型的 field 和 value 的映射表,适合用于存储对象,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 比如存储用户信息,商品信息等等。
5.4 set
一种无序集合,集合中的元素没有先后顺序。当你需要存储一个列表数据,又不希望出现重复数据时,set 是一个很好的选择,可以基于 set 实现交集、并集、差集的操作。比如:你可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合,可以非常方便的实现如共同关注、共同粉丝等功能,这个过程也就是求交集的过程。
5.5 sorted set
sorted set 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,还可以通过 score 的范围来获取元素的列表。适用于需要对数据根据某个权重进行排序的场景。
5.6 bitmap
bitmap 存储的是二进制数字(0 和 1),通过 bitmap, 只需要一个 bit 位来表示某个元素对应的值或者状态,key 就是对应元素本身。 bitmap 本身会极大的节省储存空间。
适合需要保存状态信息并需要进一步对这些信息进行分析的场景。比如用户签到情况、活跃用户情况、用户行为统计(比如是否点赞过某个视频)。
6、Redis开始为什么不使用多线程?4.0之后为什么又开始使用多线程?
不使用多线程的原因:
- 单线程编程和维护容易
- Redis的瓶颈不在CPU,主要是内存和网络
- 多线程存在死锁,线程上下文切换等问题。
4.0增加的多线程主要是针对一些大键值对的删除操作的命令,使用这些命令就会使用主处理之外的其他线程来“异步处理”。
6.0引入多线程主要是为了提高网络 IO 读写性能,只是在网络数据的读写这类耗时操作上使用了,执行命令仍然是单线程顺序执行。因此,不需要担心线程安全问题。默认是禁用的,只适用主线程,需要在配置文件中开启io-threads-do-reads yes
,且需要设置线程数,否则是不生效的 io-threads 4 #官网建议4核的机器设置为2或3,8核的建议设置为6
。
7、过期时间有什么用?
- 有助于缓解内存的消耗,避免内存不足。
- 有的业务场景就是需要某个数据只在某一时间段内存在,比如短信验证码可能只在 1 分钟内有效,用户登录的 token 可能只在 1 天内有效。
8、假设设置了一批 key 只能存活 1 分钟,那么 1 分钟后,Redis 是怎么对这批 key 进行删除的呢?
- 惰性删除 :在取出 key 的时候才对数据进行过期检查。这样对 CPU 最友好,但是可能会造成太多过期 key 没有被删除。
- 定期删除 : 每隔一段时间抽取一批 key 执行删除过期 key 操作。且Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影响。
定期删除对内存更加友好,惰性删除对 CPU 更加友好。两者各有千秋,所以 Redis 采用的是 定期删除+惰性/懒汉式删除 。
仅仅通过给 key 设置过期时间还是有问题的,可能存在定期删除和惰性删除漏掉了很多过期 key 的情况。这样就导致一定过期 key 堆积在内存里,久而久之可能内存不足。
怎么解决这个问题呢?答案就是:内存淘汰机制。
9、内存淘汰策略有哪些?
- volatile-lru(least recently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
- volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰
- volatile-random:从已设置过期时间的数据集中任意选择数据淘汰
- volatile-lfu(least frequently used):从已设置过期时间的数据集中挑选最不经常使用的数据淘汰
- allkeys-lru(least recently used):当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)
- allkeys-random:从数据集中任意选择数据淘汰
- no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。
- allkeys-lfu(least frequently used):当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key
1和4的差异和选择。
10、怎么做到持久化的?
一种持久化方式叫快照(snapshotting,RDB),另一种方式是只追加文件(append-only file, AOF)。
RDB:
通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis 创建快照之后,对快照进行备份。默认开启。
AOF:
每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到内存缓存 server.aof_buf 中,然后再根据 appendfsync 配置来决定何时将其同步到硬盘中的 AOF 文件。三种:
appendfsync always #每次有数据修改时都会写入AOF文件,这样会降低Redis的速度
appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘(常用)
appendfsync no #让操作系统决定何时进行同步
11、Redis的事务是怎样的?
Redis 事务提供了一种将多个命令请求打包的功能。然后,再按顺序执行打包的所有命令,并且不会被中途打断。
可以通过 MULTI,EXEC,DISCARD 和 WATCH 等命令来实现。
12、什么是缓存穿透?如何解决?
是大量请求的 key 不存在于缓存中,导致请求直接到了数据库上,没有经过缓存这一层。
- 缓存无效 key,缓存时间设置短一点
- 布隆过滤器
13、什么是缓存雪崩?如何解决?
缓存在同一时间大面积的失效,后面的请求都直接落到了数据库上,造成数据库短时间内承受大量请求。 这就好比雪崩一样,摧枯拉朽之势。
- 集群,避免单机宕机无法使用
- 设置不同的缓存时间避免大量key同一时间过期。
14、如何保证缓存和数据库数据的一致性?
Cache Aside Pattern(旁路缓存模式),遇到写请求是这样的:更新 DB,然后直接删除 cache 。如果更新数据库成功,而删除缓存这一步失败的情况的话,常用的解决方案是增加 cache 更新重试机制: 如果 cache 服务当前不可用导致缓存删除失败的话,我们就隔一段时间进行重试,重试次数可以自己定。如果多次重试还是失败的话,我们可以把当前更新失败的 key 存入队列中,等缓存服务可用之后,再将缓存中对应的 key 删除即可。如果redis做到了高可用,那么就不用考虑缓存删除失败了。
15、进一步深入学习