Redis

什么是 Redis

Redis 是完全开源免费的,遵守 BSD 协议,是一个高性能的 key-value 数据库
是一个使用 C 语言开发的数据库,不过与传统数据库不同的是 Redis 的数据是存在内存中的,它是内存数据库,所以读写速度非常快,因此 Redis 被广泛应用于缓存方向
Redis 除了做缓存之外,也经常用来做分布式锁,甚至是消息队列
Redis 还支持事务 、持久化、Lua 脚本、多种集群方案等,可以使用众多复杂的业务场景

常见的分布式缓存技术

缓存使用的比较多的主要是 Memcached 和 Redis。不过现在使用 Memcached 做缓存的比较少
Memcached 是分布式缓存最开始兴起的时候比较常用的方案,后来随着 Redis 的发展,Redis 逐渐成为了人们的首选
分布式缓存主要解决的是单机缓存的容量受服务器限制并且无法保存通用信息的问题。因为本地缓存只在当前服务里有效,比如如果部署了两个相同的服务,他们两者之间的缓存数据是无法共享的

Memcached 与 Redis 的异同

共同点

  1. 都是基于内存的数据库,一般都用来当作缓存使用
  2. 都有过期策略
  3. 两者的性能都非常高

    异同点

  4. Redis 支持更丰富的数据类型,Memcached 只支持最简单的 k/v 数据类型,所有的值均是简单的字符串

  5. Redis 支持数据的持久化,而 Memecache 把数据全部存在内存之中,重启之后数据丢失
  6. Redis 有灾难恢复机制,因为可以把缓存中的数据持久化到磁盘上
  7. Redis 在服务器内存使用完之后,可以将不用的数据放到磁盘上。但 Memcached 在服务器内存使用完之后,就会直接报异常
  8. Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但 Redis目前是原生支持 cluster 模式的
  9. Memcached 是多线程,非阻塞 IO 复用的网络模型;Redis 使用单线程的多路 IO 复用模型
  10. Redis 支持发布订阅模型、Lua 脚本、事务等功能,而 Memcached 不支持。并且,Redis 支持更多的编程语言
  11. Memcached 过期数据的删除策略只用了惰性删除,而 Redis 同时使用了惰性删除与定期删除

    Redis 的数据类型

    Redis 支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及 zsetsorted set:有序集合)
    实际中最为常用的还是 string 类型,不过还有几种高级数据类型也需要了解掌握:BloomFilter,RedisSearch,Redis-ML,还有更高级的数据类型,BloomFilter,RedisSearch,Redis-ML 如果使用过,那么在面试官眼里都是加分项!

    缓存数据的处理流程

    缓存的简单处理流程如下:

  12. 如果用户请求的数据命中缓存,就直接返回

  13. 缓存中不存在的话,查看数据库中是否存在
  14. 如果数据库中存在,则更新缓存中的数据
  15. 如果数据库中不存在,则返回空数据

Redis面试题 - 图1
上图就是一个最为简易的缓存流程图,从图中也可以看出在使用缓存时的一系列注意事项,比如当缓存失效时,如何更好的保护数据库,如何保证数据库与缓存数据一致等

Redis 运行模式(单进程还是单线程)

Redis 是单线程,利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销问题

一个字符串能存储的最大容量

512 M

为什么要使用缓存

高性能

将高频访问的数据放进缓存,保证高频操作可以快速响应,提高系统响应速度和用户体验

高并发

一般像 MySQL 这类数据库的 QPS 大概都在 1w 左右(4 核 8g) ,但是使用 Redis 缓存之后很容易达到 10w+,甚至最高能达到 30w+(redis 集群的话会更高)
QPS(Query Per Second):服务器每秒可以执行的查询次数
所以增加缓存可以大大提高系统的并发能力

具体说说 Redis 可以做什么

缓存

缓存机制几乎在所有的大型网站都有使用,合理地使用缓存不仅可以加 快数据的访问速度,而且能够有效地降低后端数据源的压力。Redis提供了键值过期时间设置,并且也提供了灵活控制最大内存和内存溢出后的淘汰策略。可以这么说,一个合理的缓存设计能够很好的为一个网站的稳定保驾护航

排行榜系统

排行榜系统几乎存在于所有的网站,例如按照热度排名的排行榜,按照 发布时间的排行榜,按照各种复杂维度计算出的排行榜,Redis提供了列表和有序集合数据结构,合理地使用这些数据结构可以很方便地构建各种排行榜系统

计数器应用

计数器在网站中的作用至关重要,例如视频网站有播放数、电商网站有 浏览数,为了保证数据的实时性,每一次播放和浏览都要做加1的操作,如果并发量很大对于传统关系型数据的性能是一种挑战。Redis天然支持计数功能而且计数的性能也非常好,可以说是计数器系统的重要选择

社交网络

赞/踩、粉丝、共同好友/喜好、推送、下拉刷新等是社交网站的必备功能,由于社交网站访问量通常比较大,而且传统的关系型数据不太适合保存这种类型的数据,Redis 提供的数据结构可以相对比较容易地实现这些功能

消息队列系统

消息队列系统可以说是一个大型网站的必备基础组件,因为其具有业务 解耦、非实时业务削峰等特性。Redis 提供了发布订阅功能和阻塞队列的功能,虽然和专业的消息队列比还不够足够强大,但是对于一般的消息队列功能基本可以满足

哪些是 Redis 不可以(不擅长)做的

可以站在数据规模和数据冷热的角度来进行分析
站在数据规模的角度看,数据可以分为大规模数据和小规模数据,Redis 的数据是存放在内存中的,虽然现在内存已经足够便宜,但是如果数据量非常大,例如每天有几亿的用户行为数据,使用 Redis 来存储的话,基本上是个无底洞,经济成本相当的高
站在数据冷热的角度看,数据分为热数据和冷数据,热数据通常是指需 要频繁操作的数据,反之为冷数据,例如对于视频网站来说,视频基本信息基本上在各个业务线都是经常要操作的数据,而用户的观看记录不一定是经常需要访问的数据,这里暂且不讨论两者数据规模的差异,单纯站在数据冷热的角度上看,视频信息属于热数据,用户观看记录属于冷数据。如果将这些冷数据放在 Redis 中,基本上是对于内存的一种浪费,但是对于一些热数据可以放在 Redis 中加速读写,也可以减轻后端存储的负载,可以说是事半功倍

Redis 常见数据结构与使用场景

String

String 数据结构是简单的 key-value 类型,也是平时使用的最多的数据类型
常用命令:set,get,strlen,exists,decr,incr,setex 等等
应用场景:一般常用在需要计数的场景,比如用户的访问次数、热点文章的点赞转发数量等等
基本操作

  1. 127.0.0.1:6379> set key value # 设置 key-value 类型的值
  2. OK
  3. 127.0.0.1:6379> get key # 根据 key 获得对应的 value
  4. "value"
  5. 127.0.0.1:6379> exists key # 判断某个 key 是否存在
  6. (integer) 1
  7. 127.0.0.1:6379> strlen key # 返回 key 所储存的字符串值的长度
  8. (integer) 5
  9. 127.0.0.1:6379> del key # 删除某个 key 对应的值
  10. (integer) 1
  11. 127.0.0.1:6379> get key
  12. (nil)

批量设置

  1. 127.0.0.1:6379> mset key1 value1 key2 value2 # 批量设置 key-value 类型的值
  2. OK
  3. 127.0.0.1:6379> mget key1 key2 # 批量获取多个 key 对应的 value
  4. 1) "value1"
  5. 2) "value2"

计数器设置

  1. 127.0.0.1:6379> set number 1
  2. OK
  3. 127.0.0.1:6379> incr number # 将 key 中储存的数字值增一
  4. (integer) 2
  5. 127.0.0.1:6379> get number
  6. "2"
  7. 127.0.0.1:6379> decr number # 将 key 中储存的数字值减一
  8. (integer) 1
  9. 127.0.0.1:6379> get number
  10. "1"

过期设置

  1. 127.0.0.1:6379> expire key 60 # 数据在 60s 后过期
  2. (integer) 1
  3. 127.0.0.1:6379> setex key 60 value # 数据在 60s 后过期 (setex:[set] + [ex]pire)
  4. OK
  5. 127.0.0.1:6379> ttl key # 查看数据还有多久过期
  6. (integer) 56

list

list 即是链表,链表是一种非常常见的数据结构,特点是易于数据元素的插入和删除并且可以灵活调整链表长度,但是链表的随机访问困难
常用命令:rpush,lpop,lpush,rpop,lrange,llen 等
应用场景:发布与订阅或者说消息队列、慢查询等
队列和栈的实现
Redis面试题 - 图2
通过 rpush/lpop 实现队列

  1. 127.0.0.1:6379> rpush myList value1 # 向 list 的头部(右边)添加元素
  2. (integer) 1
  3. 127.0.0.1:6379> rpush myList value2 value3 # 向 list 的头部(最右边)添加多个元素
  4. (integer) 3
  5. 127.0.0.1:6379> lpop myList # 将 list 的尾部(最左边)元素取出
  6. "value1"
  7. 127.0.0.1:6379> lrange myList 0 1 # 查看对应下标的 list 列表, 0 为 start,1为 end
  8. 1) "value2"
  9. 2) "value3"
  10. 127.0.0.1:6379> lrange myList 0 -1 # 查看列表中的所有元素,-1表示倒数第一
  11. 1) "value2"
  12. 2) "value3"

通过 rpush/rpop 实现栈

  1. 127.0.0.1:6379> rpush myList2 value1 value2 value3
  2. (integer) 3
  3. 127.0.0.1:6379> rpop myList2 # 将 list的头部(最右边)元素取出
  4. "value3"

hash

hash 类似于 JDK1.8 前的 HashMap,内部实现也差不多(数组 + 链表),不过 Redis 的 hash 做了更多优化。另外 hash 是一个 string 类型的 field 和 value 的映射表,特别适合用于存 储对象,比如可以用 hash 数据结构来存储用户信息,商品信息等等
常用命令:hset,hmset,hexists,hget,hgetall,hkeys,hvals 等
应用场景:系统中对象数据的存储

  1. 127.0.0.1:6379> hmset userInfoKey name "guide" description "dev" age "24"
  2. OK
  3. 127.0.0.1:6379> hexists userInfoKey name # 查看 key 对应的 value 中指定的字段是否存在
  4. (integer) 1
  5. 127.0.0.1:6379> hget userInfoKey name # 获取存储在哈希表中指定字段的值
  6. "guide"
  7. 127.0.0.1:6379> hget userInfoKey age
  8. "24"
  9. 127.0.0.1:6379> hgetall userInfoKey # 获取在哈希表中指定 key 的所有字段和值
  10. 1) "name"
  11. 2) "guide"
  12. 3) "description"
  13. 4) "dev"
  14. 5) "age"
  15. 6) "24"
  16. 127.0.0.1:6379> hkeys userInfoKey # 获取 key 列表
  17. 1) "name"
  18. 2) "description"
  19. 3) "age"
  20. 127.0.0.1:6379> hvals userInfoKey # 获取 value 列表
  21. 1) "guide"
  22. 2) "dev"
  23. 3) "24"
  24. 127.0.0.1:6379> hset userInfoKey name "GuideGeGe" # 修改某个字段对应的值
  25. 127.0.0.1:6379> hget userInfoKey name
  26. "GuideGeGe"

set

set 类似于 Java 中的 HashSet,Redis 中的 set 类型是一种无序集合,集合中的元素没有先后顺序。当需要存储一个列表数据,又不希望出现重复数据时,set 是一个很好的选择,并且 set 提供了判断某个成员是否在一个 set 集合内的重要接口,这个也是 list 所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。如:可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis 可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能
常用命令:sadd,spop,smembers,sismember,scard,sinterstore,sunion 等
应用场景:需要存放的数据不能重复以及需要获取多个数据源交集和并集等场景

  1. 127.0.0.1:6379> sadd mySet value1 value2 # 添加元素进去
  2. (integer) 2
  3. 127.0.0.1:6379> sadd mySet value1 # 不允许有重复元素
  4. (integer) 0
  5. 127.0.0.1:6379> smembers mySet # 查看 set 中所有的元素
  6. 1) "value1"
  7. 2) "value2"
  8. 127.0.0.1:6379> scard mySet # 查看 set 的长度
  9. (integer) 2
  10. 127.0.0.1:6379> sismember mySet value1 # 检查某个元素是否存在set 中,只能接收单个元素
  11. (integer) 1
  12. 127.0.0.1:6379> sadd mySet2 value2 value3
  13. (integer) 2
  14. 127.0.0.1:6379> sinterstore mySet3 mySet mySet2 # 获取 mySet 和 mySet2 的交集并存放
  15. mySet3
  16. (integer) 1
  17. 127.0.0.1:6379> smembers mySet3
  18. 1) "value2"

sorted set

和 set 相比,sorted set 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,还可以通过 score 的范围来获取元素的列表
常用命令:zadd,zcard,zscore,zrange,zrevrange,zrem 等
应用场景:需要对数据根据某个权重进行排序的场景。比如在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息等信息

  1. 127.0.0.1:6379> zadd myZset 3.0 value1 # 添加元素到 sorted set 中 3.0 为权重
  2. (integer) 1
  3. 127.0.0.1:6379> zadd myZset 2.0 value2 1.0 value3 # 一次添加多个元素
  4. (integer) 2
  5. 127.0.0.1:6379> zcard myZset # 查看 sorted set 中的元素数量
  6. (integer) 3
  7. 127.0.0.1:6379> zscore myZset value1 # 查看某个 value 的权重
  8. "3"
  9. 127.0.0.1:6379> zrange myZset 0 -1 # 顺序输出某个范围区间的元素,0 -1 表示输出所有元素
  10. 1) "value3"
  11. 2) "value2"
  12. 3) "value1"
  13. 127.0.0.1:6379> zrange myZset 0 1 # 顺序输出某个范围区间的元素,0 为 start 1 为 stop
  14. 1) "value3"
  15. 2) "value2"
  16. 127.0.0.1:6379> zrevrange myZset 0 1 # 逆序输出某个范围区间的元素,0 为 start 1 为
  17. stop
  18. 1) "value1"
  19. 2) "value2"

bitmap

bitmap 存储的是连续的二进制数字(0 和 1),通过 bitmap,只需要一个 bit 位来表示某个元素对应的值或者状态,key 就是对应元素本身。8 个 bit 可以组成一个 byte,所以 bitmap 本身会极大的节省储存空间
常用命令:setbit 、 getbit 、 bitcount 、 bitop
应用场景:适合需要保存状态信息(比如是否签到、是否登录…)并需要进一步对这些信息进行分析的场景。比如用户签到情况、活跃用户情况、用户行为统计

  1. # SETBIT 会返回之前位的值(默认是 0)这里会生成 7 个位
  2. 127.0.0.1:6379> setbit mykey 7 1
  3. (integer) 0
  4. 127.0.0.1:6379> setbit mykey 7 0
  5. (integer) 1
  6. 127.0.0.1:6379> getbit mykey 7
  7. (integer) 0
  8. 127.0.0.1:6379> setbit mykey 6 1
  9. (integer) 0
  10. 127.0.0.1:6379> setbit mykey 8 1
  11. (integer) 0
  12. # 通过 bitcount 统计被被设置为 1 的位的数量。
  13. 127.0.0.1:6379> bitcount mykey
  14. (integer) 2

Redis 为什么不用多线程以及为什么 Redis6.0 之后又引入了多线程

使用单线程的原因

  1. 单线程编程更容易并且更容易维护
  2. Redis 的性能瓶颈不在 CPU ,主要在内存和网络
  3. 多线程就会存在死锁、线程上下文切换等问题,甚至会影响性能

Redis6.0 引入多线程主要是为了提高网络 IO 读写性能,因为这个是 Redis 中的一个性能瓶颈
虽然 Redis6.0 引入了多线程,但是 Redis 的多线程只是在网络数据的读写这类耗时操作上使用了,执行命令仍然是单线程顺序执行

设置缓存过期的作用

因为内存是有限的,如果缓存中的所有数据都是一直保存的话,很快就会 Out of memory 了
Redis 中除了字符串类型有自己独有设置过期时间的命令 setex 外,其他方法都需要依靠 expire 命令来设置过期时间 。另外 persist 命令可以移除一个键的过期时间
还有一种情况需要设置过期时间,当业务场景就是需要某个数据只在某一时间段内存在,比如短信验证码可能只在 1 分钟内有效,用户登录的 token 可能只在 1 天内有效,此时设置过期时间就能比较好的处理,如果使用传统的数据库来处理的话,一般都是自己判断过期,这样更麻烦并且性能要差很多

Redis 过期删除策略

  1. 定时删除:在设置键的过期时间的同时,创建一个定时器 time,让定时器在键的过期时间来临时,立即执行对键的删除操作
  2. 惰性删除:放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键;如果没有过期,就返回该键
  3. 定期删除:每隔一段时间程序就对数据库进行一次检查,删除里面的过期键。至于要删除多少过期键,以及要检查多少个数据库,则由算法决定

更新策略对比

更新策略 一致性 维护成本
LRU/LFU/FIFO 算法剔除 最差
超时删除 较差 较低
主动更新

Redis 内存淘汰机制

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

  1. volatile-lru(least recently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
  2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
  3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
  4. allkeys-lru(least recently used):当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)
  5. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  6. no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错

4.0 版本后增加以下两种:

  1. volatile-lfu(least frequently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
  2. allkeys-lfu(least frequently used):当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key

    使用策略的规则:

    (1)如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用 allkeys-lru
    (2)如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用 allkeys-random

    Redis 持久化

    Redis 的一种持久化方式叫快照(snapshotting,RDB),另一种方式是只追加文件(append-only file,AOF)

    快照(snapshotting)持久化(RDB)

    Redis 可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis 创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis 主从结构,主要用来提高 Redis 性能),还可以将快照留在原地以便重启服务器的时候使用

    AOF(append-only file)持久化

    与快照持久化相比,AOF 持久化的实时性更好,因此已成为主流的持久化方案,是指所有的命令行记录以 Redis 命令请求协议的格式完全持久化存储)保存为 aof 文件
    默认没有开启 AOF,可以通过如下命令开启

    1. appendonly yes

    两种持久化方案对比:
    Redis面试题 - 图3

    Redis 常见的性能问题

  3. Master 最好不要写内存快照,如果 Master 写内存快照,save 命令调度 rdbSave 函数,会阻塞主线程的工作,当快照比较大时对性能影响会非常大,会间断性暂停服务

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

    Redis 同步机制

    Redis 可以使用主从同步,从从同步。第一次同步时,主节点做一次 bgsave,并同时将后续修改操作记录到内存 buffer,待完成后将 rdb 文件全量同步到复制节点,复制节点接收完成后将 rdb 镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程
    Redis 主从同步有新旧两种方案,具体看下面图示
    老方案:
    Redis面试题 - 图4
    新方案:
    Redis面试题 - 图5

    Pipeline 的好处

    可以将多次 IO 往返的时间缩减为一次,前提是 pipeline 执行的指令之间没有因果相关性。使用 redis-benchmark 进行压测的时候可以发现影响 Redis 的 QPS 峰值的一个重要因素是 pipeline 批次指令的数目

    缓存穿透

    缓存穿透说简单点就是大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。比如某个攻击者故意制造缓存中不存在的 key 发起大量请求,导致大量请求落到数据库上,使得数据库由于性能的原因崩溃,导致系统异常

    缓存穿透问题产生

    Redis面试题 - 图6

    解决办法:

    1、缓存无效 key

    如果缓存和数据库都查不到某个 key 的数据就写一个到 Redis 中去并设置过期时间,可以解决请求的 key 变化不频繁的情况,如果攻击者的恶意攻击,每次构建不同的请求 key,会导致 Redis 中缓存大量无效的 key 。很明显,这种方案并不能从根本上解决此问题
    Redis面试题 - 图7

    2、使用布隆过滤器

    布隆过滤器是一个非常神奇的数据结构,通过它可以非常方便地判断一个给定数据是否存在于海量数据中
    具体是这样做的:把所有可能存在的请求的值都存放在布隆过滤器中,当用户请求过来,先判断用户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信息给客户端,存在的话才会走下面的流程
    Redis面试题 - 图8

    缓存雪崩

    缓存雪崩描述的就是这样一个简单的场景:缓存在同一时间大面积的失效,后面的请求都直接落到了数据库上,造成数据库短时间内承受大量请求
    还有一种缓存雪崩的场景是:有一些被大量访问数据(热点缓存)在某一时刻大面积失效,导致对应的请求直接落到了数据库上
    以上两种常见,就好比雪崩一样,摧枯拉朽之势,数据库的压力可想而知,可能直接就被这么多请求弄宕机了
    Redis面试题 - 图9

    解决办法

    针对 Redis 服务不可用的情况:

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

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

针对热点缓存失效的情况:

  1. 设置不同的失效时间比如随机设置缓存的失效时间
  2. 缓存永不失效

    缓存击穿

    如果缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮,这就是缓存击穿的问题
    击穿其实可以看做是雪崩的一个子集,解决方法一般有两种,设置热点数据永不过期和设置互斥锁
    所谓的互斥锁,就是保证同一时间只有一个业务线程更新缓存,对于没有获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值
    Redis面试题 - 图10

    如何保证缓存和数据库的数据一致性

    其实这是一个非常庞大且复杂的问题,根本不是一两句话能够说清楚的,如果要完全规避一致性问题,那么整个系统也会变得非常复杂
    一般来说可以进行如下处理:更新 DB,然后删除 cache
    当然此时有可能遇到更新 DB 成功,但是删除 cache 失败的情况,处理办法大致有两种:

  3. 缓存失效时间变短(不推荐,治标不治本) :让缓存数据的过期时间变短,这样的话缓存就会从数据库中加载数据。另外,这种解决办法对于先操作缓存后操作数据库的场景不适用

  4. 增加 cache 更新重试机制(常用):如果 cache 服务当前不可用导致缓存删除失败的话,就隔一段时间进行重试,重试次数可以自己定。如果多次重试还是失败的话,可以把当前更新失败的 key 存入队列中,等缓存服务可用之后,再将缓存中对应的 key 删除即可

    简单介绍 Redis 事物

    Redis 事务可以一次执行多个命令,并且带有以下三个重要的保证:
  • 批量操作在发送 EXEC 命令前被放入队列缓存
  • 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行
  • 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中

一个事务从开始到执行会经历以下三个阶段:

  • 开始事务
  • 命令入队
  • 执行事务

命令:

  • DISCARD 取消事务,放弃执行事务块内的所有命令
  • EXEC 执行所有事务块内的命令
  • MULTI 标记一个事务块的开始
  • UNWATCH 取消 WATCH 命令对所有 key 的监视
  • WATCH key [key …] 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断

    Redis 集群

    (1)Redis Sentinal 着眼于高可用,在 master 宕机时会自动将 slave 提升为 master,继续提供服务
    (2)Redis Cluster 着眼于扩展性,在单个 redis 内存不足时,使用 Cluster 进行分片存储
    集群之间是异步复制的,最大节点个数为16384个,Redis 集群目前无法做数据库选择,默认在 0 数据库
    为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型,每个节点都会有 N-1 个复制品
    Redis 并不能保证数据的强一致性,这意味着在实际中集群在特定的条件下可能会丢失写操作

    Redis 内存优化

    尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以应该尽可能的将数据模型抽象到一个散列表里面。比如 web 系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的 key,而是应该把这个用户的所有信息存储到一张散列表里面

    一个 Redis 实例最多能存放多少的 keys?各数据类型最多能存放多少元素

    理论上 Redis 可以处理多达 232 的 keys,并且在实际中进行了测试,每个实例至少存放了 2 亿 5 千万的 keys。任何 list、set、和 sorted set 都可以放 232 个元素。换句话说,Redis 的存储极限是系统中的可用内存值

    如何保证 Redis 中的20W数据都是数据库中200W数据的热点数据

    正确设置内存淘汰策略,当 Redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略,从而剩下的数据就是热点数据

    如何将10W个以某个固定前缀开头的key全部找出

    使用 keys 指令可以扫出指定模式的 key 列表,但是,keys 指令会导致线程阻塞一段时间,此时整个 Redis 系统不再能接受其他指令
    这个时候可以使用 scan 指令,scan 指令可以无阻塞的提取出指定模式的 key 列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用 keys 指令长,但是安全性更高!

    如果有大量的 key 需要设置同一时间过期,一般需要注意什么

    如果大量的 key 过期时间设置的过于集中,到过期的那个时间点,Redis 可能会出现短暂的卡顿现象。一般需要在时间上加一个随机值,使得过期时间分散一些

    Redis 分布式锁

    通过 SETNX 来争抢锁,再用 EXPIRE 给锁加一个过期时间,当然为了保证 SETNX 和 EXPIRE 原子性执行,在 Redis 2.6.12 之后,Redis 扩展了 SET 命令的参数,用这一条命令就可以了:
    1. 127.0.0.1:6379> SET lock 1 EX 10 NX
    2. OK