Redis

一、简述

Redis 全称Remote Dictionary Server。是一个开源的BSD许可的,使用 ANSIC 语言编写、支持网络、基于内存亦可持久化的日志型、高级的 key-value 存储系统,并提供多种语言的 API。整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据 flush 到硬盘上进行保存。

因为是纯内存操作,Redis 的性能非常出色,每秒可以处理超过 10 万次读写操作,是已知性能最快的 Key-Value DB。Redis 的出色之处不仅仅是性能,Redis 最大的魅力是支持保存多种数据结构,此外单个 value 的最大限制是 1GB,不像 memcached 只能保存 1MB 的数据,因此 Redis 可以用来实现很多有用的功能。Redis 支持的数据结构,包括五种基础数据结构:字符串(strings),散列(hashes), 列表(lists), 集合(sets),有序集合(sorted sets);比较复杂的数据结构:范围查询,bitmaps,hyperloglogs 和地理空间(geospatial)索引半径查询。

Redis 内置了复制(replication),LUA 脚本(Lua scripting),LRU 驱动事件(LRU eviction),事务(transactions)和不同级别的磁盘持久化(persistence),并通过 Redis 哨兵(Sentinel)和自动分区(Cluster)提供高可用性(high availability)。Redis 可以用作数据库、缓存和消息中间件,也经常用来做分布式锁。

比如说用它的 List 来做FIFO 双向链表,实现一个轻量级的高性能消息队列服务,用它的 Set 可以做高性能的 tag 系统等等。Redis 也可以对存入的 Key-Value 设置 expire 时间,因此也可以被当作一 个功能加强版的 memcached 来用。 Redis 的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此 Redis 适合的场景主要局限在较小数据量的高性能操作和运算上。

五种基础数据结构

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️⃣有序集合 sorted sets

  • zadd name score value
  • Redis zset 和 set 一样也是 String 类型元素的集合,且不允许重复的成员。
  • 不同的是每个元素都会关联一个 double 类型的分数。Redis 正是通过分数来为集合中的成员进行从小到大的排序。
  • zset 的成员是唯一的,但分数(score)却可以重复。

复杂的数据结构

  1. Bitmaps:位图,在 String 的基础上进行位操作,可以实现节省空间的数据结构。
  2. Hyperloglog:用于估计一个 set 中元素数量的概率性的数据结构。
  3. Geo:geospatial,地理空间索引半径查询。
  4. BloomFilter:布隆过滤器。

Redis 数据结构组成

虽然 Redis 支持的数据结构不同,但其实 Redis 的每一种数据结构都由一个 key 和 value 组成,可以抽象为:

所有数据结构的 key 的值都是任意合法的字符串,不同的数据结构的区别就在于 value 存储的值不同。最简单的 String 数据结构,其 value 为 String,所以 String 可以表示为:

而 Hash 数据结构,其 value 为一个哈希列表,所以 Hash 可以表示为:

二、Redis 的持久化

由于 Redis 的数据都存放在内存中,如果没有配置持久化,Redis 重启后数据就会全部丢失,需要开启 Redis 的持久化就是把内存的数据写到磁盘中去,防止服务宕机内存数据丢失。Redis 重启后,可以从磁盘中恢复数据。Redis 提供两种持久化方式:RDB(默认)原理是将 Reids 在内存中的数据记录定时 dump 到磁盘上的 dump.rdb 文件中;AOF原理是将 Reids 的操作日志以追加的方式写入文件。如果两个都配了,优先加载 AOF。

1️⃣RDB(默认) Redis DataBase

RDB 是指在指定的时间间隔内将内存中的数据集快照 dump 到磁盘的 dump.rdb 文件中,实际操作过程是 fork 一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。

可以通过配置文件来修改 Redis 服务器 dump 快照的频率,在 6379.conf 文件中搜索 save,可以看到下面的配置信息:

save 900 1   #在900秒(15分钟)之后,如果至少有1个key发生变化,则dump内存快照
save 300 10   #在300秒(5分钟)之后,如果至少有10个key发生变化,则dump内存快照
save 60 10000   #在60秒(1分钟)之后,如果至少有10000个key发生变化,则dump内存快照

功能核心函数 rdbSave(生成 RDB 文件)和 rdbLoad(从文件加载到内存)两个函数:Redis_第1张图片

RDB 优势:

  1. 一旦采用该方式,整个 Redis 数据库将只包含一个文件,这对于文件备份而言是非常完美的。比如,可能打算每个小时归档一次最近 24 小时的数据,同时还要每天归档一次最近 30 天的数据。通过这样的备份策略,一旦系统出现灾难性故障,可以非常容易的进行恢复。

  2. 对于灾难恢复而言,RDB 是非常不错的选择。因为可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上。

  3. 性能最大化。对于 Redis 的服务进程而言,在开始持久化时,它唯一需要做的只是 fork 出子进程,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行IO操作了。

  4. 相比于 AOF,如果数据集很大,RDB 的启动效率会更高。

RDB 劣势:

  1. 如果想保证数据的高可用性,即最大限度的避免数据丢失,RDB 不是一个很好的选择。因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。

  2. 由于 RDB 是通过 fork 子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至更长。

2️⃣AOF Append-only file

AOF 以日志的形式记录服务器所处理的每一个【写/删】操作,查不会记录,以文本的方式记录,打开文件可以看到详细的操作记录。

AOF 在 Redis 的配置文件中存在三种同步方式,它们分别是:

appendfsync always     #【每修改同步】每次有修改时都会写入AOF文件
appendfsync everysec  #【每秒同步】该策略为AOF的缺省策略
appendfsync no          #【不同步】高效但是数据不会被持久化

每当执行服务器(定时)任务或者函数时,flushAppendOnlyFile 函数都会被调用,这个函数执行两个工作。aof 写入保存:

  1. WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件;
  2. SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。

Redis_第2张图片

AOF 优势:

  1. 该机制可以带来更高的数据安全性,即数据持久性。Redis 提供了 3 种同步策略,即每修改同步、每秒同步和不同步。
    每修改同步,可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中。可以预见,这种方式在效率上是最低的。
    每秒同步是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。

  2. 由于该机制对日志文件的写入操作采用的是 append 模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。如果某次操作写入一半数据时出现了系统崩溃问题,在 Redis 下一次启动之前,可以通过 redis-check-aof 工具解决数据一致性的问题。

  3. 如果日志过大,Redis 可以自动启用 rewrite 机制,删除冗余日志记录。

  4. AOF 包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,也可以通过该文件完成数据的重建。

AOF 劣势:

  1. 对于相同数量的数据集而言,AOF 文件通常要大于 RDB 文件。RDB 在恢复大数据集时的速度比 AOF 快。

  2. 根据同步策略的不同,AOF 在运行效率上往往会慢于 RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和 RDB 一样高效。

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

存储结构:
内容是 Redis 通讯协议(RESP)格式的命令文本存储。

【Redis 序列化协议】RESP(Redis Serialization Protocol)是 Redis 客户端和服务端之间使用的一种通讯协议。RESP 的特点:实现简单、快速解析、可读性好

3️⃣Redis 如何做持久化的:RDB 和 AOF 结合

bgsave 做镜像全量持久化,aof 做增量持久化。因为 bgsave 会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据,所以需要 aof 来配合使用。在 Redis 实例重启时,会使用 bgsave 持久化文件重新构建内存,再使用 aof 重放近期的操作指令来实现完整恢复重启之前的状态。

机器如果突然掉电会怎样?取决于 aof 日志 sync 属性的配置,如果不要求性能,在每条写指令时都 sync 一下磁盘,就不会丢失数据。但是在高性能的要求下每次都 sync 是不现实的,一般都使用定时 sync,比如一秒一次,这个时候最多就会丢失一秒的数据。

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

4️⃣Redis 的同步机制

Redis 可以使用主从同步,从从同步。第一次同步时,主节点做一次 bgsave,并同时将后续修改操作记录到内存 buffer,待完成后将 rdb 文件全量同步到复制节点,复制节点接受完成后将 rdb 镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。

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

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

2️⃣Master AOF 持久化,如果不重写 AOF 文件,这个持久化方式对性能的影响是最小的,但是 AOF 文件会不断增大,AOF 文件过大会影响 Master 重启的恢复速度。Master 最好不要做任何持久化工作,包括内存快照和 AOF 日志文件,特别是不要启用内存快照做持久化,如果数据比较关键,某个 Slave 开启 AOF 备份数据,策略为每秒同步一次。

3️⃣Master 调用 BgRewriteAOF 重写 AOF 文件,AOF 在重写的时候会占大量的CPU和内存资源,导致服务 load 过高,出现短暂服务暂停现象。

4️⃣Redis 主从复制的性能问题,为了主从复制的速度和连接的稳定性,Slave 和 Master 最好在同一个局域网内。

四、Redis 分布式锁实现原理

先拿 setnx 来争抢锁,抢到之后,再用 expire 给锁加一个过期时间防止锁忘记了释放。如果在 setnx 之后执行 expire 之前,进程意外 crash 或者要重启维护,有可能导致永远不释放该锁,如何应对?set 可以同时把 setnx 和 expire 合成一条指令来用SET key value [EX seconds] [NX]

setnx lock.foo该锁包含一个 Unix 时间戳,如果这样一个时间戳等于当前的 Unix 时间,该锁将不再有效。另一个客户端 C4 检测到一个过期的锁并且都尝试去释放它的算法:

1️⃣C4 发送setnx lock.foo为了获得该锁。
2️⃣已经崩溃的客户端 C3 仍然持有该锁,所以 Redis 将会返回0给 C4。
3️⃣C4 发送get lock.foo检查该锁是否已经过期。如果没有过期,C4 客户端将会睡眠一会,并且从一开始进行重试操作。
4️⃣另一种情况,如果因为lock.foo键的 Unix 时间小于当前的 Unix 时间而导致该锁已经过期,C4 会尝试执行:GETSET lock.foo

  1. 由于GETSET 的语意,C4 会检查已经过期的旧值是否仍然存储在lock.foo中。如果是的话,C4 会获得锁。
  2. 如果另一个客户端,假如为 C5,比 C4 更快的通过GETSET操作获取到锁,那么 C4 执行GETSET操作会被返回一个不过期的时间戳。C4 将会从第一个步骤重新开始。请注意:即使 C4 在将来几秒设置该键,这也不是问题。

为了使这种加锁算法更加的健壮,持有锁的客户端应该总是要检查是否超时,保证使用DEL释放锁之前不会过期,因为客户端故障的情况可能是复杂的,不止是崩溃,还会阻塞一段时间,阻止一些操作的执行,并且在阻塞恢复后尝试执行DEL(此时,该 LOCK 已经被其他客户端所持有)。

五、Redis 查询 key 列表问题

1️⃣假如 Redis 里有 1 亿个 key,其中有 10w 个 key 是以某个固定的已知的前缀开头的,如何将它们全部找出来?

使用 keys 指令可以扫出指定模式的 key 列表。命令:keys matching*

2️⃣Redis 正在给线上的业务提供服务,使用 keys 指令会有什么问题?

这涉及到 Redis 关键的一个特性:Redis 是单线程的。keys 指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。为了避免这种问题,可以使用 scan 指令,scan 指令可以无阻塞的提取出指定模式的 key 列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用 keys 指令长。

六、Redis 的架构模式及特点

1️⃣单机版

  • 特点:简单
  • 问题:内存容量有限;处理能力有限;无法高可用。

Redis_第3张图片

2️⃣主从复制(一主二仆、薪火相传、反客为主)

Redis_第4张图片

Redis 的复制(replication)功能允许用户根据一个 Redis 服务器来创建任意多个该服务器的复制品,其中被复制的服务器为主服务器(master),而通过复制创建出来的服务器复制品则为从服务器(slave)。 只要主从服务器之间的网络连接正常,主从服务器两者会具有相同的数据,主服务器就会一直将发生在自身的数据更新同步给从服务器,从而一直保证主从服务器的数据相同。

  • 特点:master/slave 角色;master/slave 数据相同;降低 master 读压力。
  • 问题:无法保证高可用;没有解决 master 写的压力。

Redis_第5张图片

Redis 集群的主从复制模型是怎样的?
为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型,每个节点都会有 N-1 个复制品。

3️⃣哨兵

Redis sentinel 在分布式系统中监控 Redis 主从服务器,并在主服务器下线时自动进行故障转移。其中三个特性:

  • 监控(Monitoring):Sentinel 会不断地检查主服务器和从服务器是否运作正常。

  • 提醒(Notification):当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。

  • 自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时,Sentinel 会开始一次自动故障迁移操作。

  • 特点:保证高可用;监控各个节点;自动故障迁移。

  • 缺点:主从模式,切换需要时间容易丢数据;没有解决 master 写的压力。

Redis_第6张图片

4️⃣集群(proxy型)

Twemproxy 是一个 Twitter 开源的一个 redis 和 memcache 快速/轻量级代理服务器; Twemproxy 是一个快速的单线程代理程序,支持 Memcached ASCII 协议和 redis 协议。

  • 特点:多种 hash 算法:MD5、CRC16、CRC32、CRC32a、hsieh、murmur、Jenkins;支持失败节点自动删除;后端 Sharding 分片逻辑对业务透明,业务方的读写方式和操作单个 Redis 一致。
  • 缺点:增加了新的 proxy,需要维护其高可用;failover 逻辑需要自己实现,其本身不能支持故障的自动转移可扩展性差,进行扩缩容都需要手动干预

Redis_第7张图片

5️⃣集群(直连型)

从 Redis3.0 之后版本支持redis-cluster集群,Redis-Cluster 采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。

  • 特点:无中心架构(不存在哪个节点影响性能瓶颈),少了 proxy 层;数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布;可扩展性,可线性扩展到 1000 个节点,节点可动态添加或删除;高可用性,部分节点不可用时,集群仍可用。通过增加 Slave 做备份数据副本;实现故障自动 failover,节点之间通过 gossip 协议交换状态信息,用投票机制完成 Slave 到 Master 的角色提升。
  • 缺点:资源隔离性较差,容易出现相互影响的情况;数据通过异步复制,不保证数据的强一致性。

Redis_第8张图片

注意:
1️⃣Redis 集群方案什么情况下会导致整个集群不可用?
有 A,B,C 三个节点的集群,在没有复制模型的情况下,如果节点 B 失败了,那么整个集群就会以为缺少 5501-11000 这个范围的槽而不可用。
2️⃣Redis 集群会有写操作丢失吗?为什么?
Redis 并不能保证数据的强一致性,这意味着在实际中集群在特定的条件下可能会丢失写操作。
3️⃣Redis 集群最大节点个数是多少?16384 个
4️⃣Redis 集群如何选择数据库?Redis 集群目前无法做数据库选择,默认在 0 数据库。

七、Redis 做异步队列是怎么用的?有什么缺点?

一般使用 list 结构作为队列,rpush 生产消息,lpop 消费消息。当 lpop 没有消息的时候,要适当sleep一会再重试。可否不用 sleep ?list 还有个指令叫 blpop,在没有消息的时候,它会阻塞直到消息到来。

能不能生产一次消费多次呢?使用 pub/sub 主题订阅者模式,可以实现 1:N 的消息队列。pub/sub 有什么缺点?在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如 RabbitMQ 等。

如何实现延时队列?使用 sorted set,拿时间戳作为 score,消息内容作为 key 调用 zadd 来生产消息,消费者用 zrangebyscore 指令获取 N 秒之前的数据轮询进行处理。

八、【Redis限流】实现限制恶意登录功能,限制最多只能登录5次/h/用户

  1. 使用信号量进行控制并发,限制一项资源最多能够同时被多少个进程访问。参考:http://kklin.farbox.com/post/shu-ju-ku-she-ji/redis/2-shi-zhan/7-shi-yong-redisgou-jian-ji-shu-xin-hao-liang-kong-zhi-bing-fa

  2. 使用 List 数据结构,每条数据记录登录时间。登录时,首先将 list 中的超过一小时的数据清理,然后将此次登录时间加入到 list 中,如果 list 的 size>=5 时,说明 list 中已经记录了该用户最近一小时登录超过 5 次。

九、Redis 为什么会比 MySQL 快

1️⃣Redis 是基于内存存储的,MySQL 是基于磁盘存储的。
2️⃣Redis 存储的是 k-v 格式的数据,时间复杂度是O(1),常数阶。而 MySQL 引擎的底层实现是B+Tree,时间复杂度是O(logn),对数阶。Redis 会比 MySQL 快一点点。
3️⃣MySQL 数据存储是存储在表中,查找数据时要先对表进行全局扫描或者根据索引查找,这涉及到磁盘的查找,磁盘查找如果是按条点查找可能会快点,但是顺序查找就比较慢;而 Redis 本身就是存储在内存中,会根据数据在内存的位置直接取出。
4️⃣Redis 是单线程的多路复用 IO,单线程避免了线程切换的开销,而多路复用 IO 避免了 IO 等待的开销,在多核处理器下提高处理器的使用效率可以对数据进行分区,然后每个处理器处理不同的数据。

十、Redis 和 memcached(淘汰中) 区别

  1. Redis 支持更丰富的数据类型(支持更复杂的应用场景):Redis 不仅仅支持简单的 k/v 类型的数据,同时还提供 list,set,zset,hash 等数据结构的存储。memcached 所有的值均是简单的字符串。
  2. Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而 memcached 把数据全部存在内存之中。
  3. 集群模式:memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 Redis 目前是原生支持 cluster 模式的。
  4. Memcached 是多线程,非阻塞IO复用的网络模型;Redis 使用单线程的多路 IO 复用模型。Redis 的速度比 memcached 快很多。

Redis_第9张图片

十一、为什么要用 Redis 而不用 map/guava 做缓存?

缓存分为本地缓存和分布式缓存。

  1. 以 Java 为例,使用自带的 map 或者 guava 实现的是本地缓存,最主要的特点是轻量以及快速,生命周期随着JVM的销毁而结束,并且在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性。
  2. 使用 Redis 或 memcached 之类的称为分布式缓存,在多实例的情况下,各实例共用一份缓存数据,缓存具有一致性。缺点是需要保持 Redis 或 memcached服务的高可用,整个程序架构上较为复杂。

十二、Redis 有哪几种数据淘汰策略?

  • noeviction:返回错误当内存限制达到,并且客户端尝试执行会让更多内存被使用的命令。
  • allkeys-lru:尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
  • volatile-lru:尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
  • allkeys-random:回收随机的键使得新添加的数据有空间存放。
  • volatile-random:回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
  • volatile-ttl:回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。

十三、Redis 官方为什么不提供 Windows 版本?

因为目前 Linux 版本已经相当稳定,而且用户量很大,无需开发 windows 版本。不然会带来兼容性等问题。

十四、为什么 Redis 需要把所有数据放到内存中?

Redis 为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以 Redis 具有快速和数据持久化的特征,如果不将数据放在内存中,磁盘 I/O 速度会严重影响 Redis 的性能。在内存越来越便宜的今天,Redis 将会越来越受欢迎。如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。

十五、Redis 有哪些适合的场景?

  1. 会话缓存(Session Cache)

最常用 Redis 的情景是会话缓存(sessioncache),用 Redis 缓存会话比其他存储(如Memcached)的优势在于:Redis 提供持久化。随着 Redis 这些年的改进,很容易找到怎么恰当的使用 Redis 来缓存会话的文档。甚至广为人知的商业平台 Magento 也提供 Redis 的插件。

  1. 全页缓存(FPC)

除基本的会话 token 之外,Redis 还提供很简便的 FPC 平台。回到一致性问题,即使重启了 Redis 实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似 PHP 本地FPC。

再次以 Magento 为例,Magento 提供一个插件来使用 Redis 作为全页缓存后端。

此外,对 WordPress 的用户来说,Pantheon 有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。

  1. 队列

Reids 在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得 Redis 能作为一个很好的消息队列平台来使用。Redis 作为队列使用的操作,就类似于本地程序语言(如 Python)对 list 的 push/pop操作。

如果在 Google 中搜索“Redis queues”,马上就能找到大量的开源项目,这些项目的目的就是利用 Redis 创建非常好的后端工具,以满足各种队列需求。例如,Celery 有一个后台就是使用Redis 作为 broker,可以从这里去查看。

  1. 排行榜/计数器

Redis 在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Sets)也使得在执行这些操作的时候变的非常简单,Redis 只是正好提供了这两种数据结构。

  1. 发布/订阅

发布/订阅的使用场景确实非常多。人们在社交网络连接中的使用,还可作为基于发布/订阅的脚本触发器,甚至用 Redis 的发布/订阅功能来建立聊天系统!

十六、Redis 支持的 Java 客户端都有哪些?官方推荐用哪个?

Redis_第10张图片

Redisson、Jedis、lettuce 等等,官方推荐使用 Redisson。Jedis 与 Redisson 区别:

  1. Jedis 是 Redis 的 Java 实现的客户端,其 API 提供了比较全面的 Redis 命令的支持。

  2. Redisson 实现了分布式和可扩展的 Java 数据结构,和 Jedis 相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等 Redis 特性。Redisson 的宗旨是促进使用者对 Redis 的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。

十七、Redis 的哈希槽

Redis 集群没有使用一致性 hash,而是引入了哈希槽的概念,Redis 集群有 16384 个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽,集群的每个节点负责一部分 hash 槽。

十八、Redis 中的管道(Pipeline)有什么用

服务器能实现处理新的请求即使旧的请求还未被响应,这样就可以将多个命令发送到服务器,而不用等待回复,最后在一个步骤中读取该答复。这就是管道(pipelining),是一种几十年来广泛使用的技术。例如许多 POP3 协议已经实现支持这个功能,大大加快了从服务器下载新邮件的过程。

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

十九、Redis 事务

Redis 事务中的所有命令都会序列化、按顺序地执行,事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。Redis 实现事务有四个命令,MULTI/EXEC/DISCARD/WATCH。Redis 事务的实现特征:

  1. 在事务中的所有命令都将会被串行化的顺序执行。事务执行期间,Redis 不会再为其它客户端的请求提供任何服务,从而保证了事务中的所有命令被原子的执行。

  2. 与关系型数据库中的事务相比,Redis 事务中如果有某一条命令执行失败,其后的命令仍然会被继续执行。

  3. MULTI开启一个事务,类似于关系型数据库中的BEGIN TRANSACTION。在该语句之后执行的命令都将被视为事务之内的操作,最后可以通过执行EXEC/DISCARD命令来提交/回滚该事务内的所有操作。如同关系型数据库中的COMMIT/ROLLBACK

  4. 在事务开启之前,如果客户端与服务器之间出现通讯故障并导致网络断开,其后所有待执行的语句都将不会被服务器执行。如果在客户端执行 EXEC 之后发生网络中断,那么该事务中的所有命令都会被服务器执行。

  5. 当使用 Append-Only 模式【即开启 AOF 持久化】时,Redis 会通过调用系统函数 write 将该事务内的所有写操作在本次调用中全部写入磁盘。如果在写入的过程中出现系统崩溃,如电源故障导致的宕机,那么此时也许只有部分数据被写入到磁盘,而另外一部分数据已经丢失。Redis 服务器会在重新启动时执行一系列必要的一致性检测,一旦发现类似问题,就会立即退出并给出相应的错误提示。此时,就要充分利用 Redis 工具包中提供的 redis-check-aof 工具,该工具可以帮助定位到数据不一致的错误,并将已经写入的部分数据进行回滚。修复之后就可以再次重新启动 Redis 服务器。

二十、基于CAS的 WATCH 命令

Redis 事务,WATCH 命令可用于提供 CAS(check-and-set) 功能。假设通过 WATCH 命令在事务执行之前监控了多个 Keys,倘若在 WATCH 之后有任何 Key 的值发生了变化,EXEC 命令执行的事务都将被放弃,同时返回 Null multi-bulk 应答以通知调用者事务执行失败。例如,再次假设 Redis 中并未提供 incr 命令来完成键值的原子性递增,如果要实现该功能,只能自行编写相应的代码。其伪码如下:

val = GET mykey
val = val + 1
SET mykey $val

以上代码只有在单连接的情况下才可以保证执行结果是正确的,因为如果在同一时刻有多个客户端在同时执行该段代码,那么就会出现多线程程序中经常出现的一种错误场景–竞态争用(race condition)。

比如,客户端甲乙都在同一时刻读取了 mykey 的原有值,假设该值为 10,此后两个客户端又均将该值 +1 后 set 回 Redis 服务器,这样就会导致 mykey 的结果为 11,而不是 12。为了解决类似的问题,需要借助 WATCH 命令的帮助,见如下代码:

WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC

和此前代码不同的是,新代码在获取 mykey 的值之前先通过 WATCH 命令监控了该键,此后又将 set 命令包围在事务中,这样就可以有效的保证每个连接在执行 EXEC 之前,如果当前连接获取的 mykey 的值被其它连接的客户端修改,那么当前连接的 EXEC 命令将执行失败。这样调用者在判断返回值后就可以获悉 val 是否被重新设置成功。

二十一、【Redis 集群搭建】使用 redis 进行缓存数据时,当三台机器扩容到四台时,如何能做到迁移数据量最小

组建集群时使用分布式一致算法,扩容后能尽可能命中原来的机器。

二十二、Redis 集群的原理

Redis Sentinal 着眼于高可用,在 master 宕机时会自动将 slave 提升为 master,继续提供服务。

Redis Cluster 着眼于扩展性,在单个 Redis 内存不足时,使用 Cluster 进行分片存储。

二十三、Redis 如何做内存优化?

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

二十四、Redis 回收进程如何工作的?

一个客户端运行了新的命令,添加了新的数据。Redis 检查内存使用情况,如果大于 maxmemory 的限制,则根据设定好的策略进行回收。一个新的命令被执行,等等。
所以不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。如果一个命令的结果导致大量内存被使用(例如很大的集合的交集保存到一个新的键),不用多久内存限制就会被这个内存使用量超越。

二十五、Redis 的并发竞争问题如何解决?

Redis 为单进程单线程模式,采用队列模式将并发访问变为串行访问。Redis 本身没有锁的概念,Redis 对于多个客户端连接并不存在竞争,但是在 Jedis 客户端对 Redis 进行并发访问时会发生连接超时、数据转换错误、阻塞、客户端关闭连接等问题,这些问题均是由于客户端连接混乱造成。对此有两种解决方法:

1️⃣客户端角度,为保证每个客户端间正常有序与 Redis 进行通信,对连接进行池化,同时对客户端读写 Redis 操作采用内部锁synchronized。
2️⃣服务器角度,利用 setnx 实现锁。

注:对于第一种,需要应用程序自己处理资源的同步,可以使用的方法比较通俗,可以使用 synchronized 也可以使用 lock;第二种需要用到 Redis 的 setnx 命令,但是需要注意一些问题。


#附:Redis常用命令

1️⃣通用命令:

  1. DUMP key
    序列化给定的 key 并返回序列化的值。

  2. keys * pattern
    查找当前 reids 所有的 key。O(n)

  3. dbsize
    计算 key 的总数。O(1)

  4. exists key
    检查 key 是否存在。O(1)

  5. del key...
    删除一个或多个指定的 key。O(1)

  6. expire key seconds
    为 key 设置 seconds 后过期。O(1)

  7. expire key timestamp
    用时间戳的方式给 key 设置过期时间。

  8. pexpire key milliseconds
    设置 key 的过期时间以毫秒计。

  9. pttl key
    以毫秒为单位,返回 key 的剩余过期时间。

  10. ttl key
    以秒为单位,返回 key 的剩余过期时间。O(1)

  11. persist key
    去掉 key 的过期时间,key 将持久保存。O(1)

  12. type key
    返回 key 所存储的值的类型。O(1)

  13. move key db
    将当前数据库的 key 移动到数据库 db 当中。

  14. randomkey
    从当前数据库中随机返回一个 key。

  15. rename key newkey
    修改 key 的名称。

  16. renamenx key newkey
    仅当 newkey 不存在时,将 key 改名为 newkey。

2️⃣String相关命令:

  1. set key value
    key 存在,才设置 key-value。O(1)

  2. get key
    获取 key 对应的 value。O(1)

  3. getrange key start end
    获取 key 中字符串指定下标所有的值。O(1)

  4. getset key newValue
    设置 key 新值为 newValue,并返回旧的 value。O(1)

  5. GETBIT KEY OFFSET
    对 key 所储存的字符串值,获取指定偏移量上的位

  6. mget key1 key2 ...
    获取一个或者多个给定 key 的值,原子操作。O(n)

  7. setbit key offset value
    对 key 所存储的字符串值,设置或清除指定偏移量上的位。

  8. setex key value seconds
    将值 value 关联到 key ,并将 key 的过期时间设为 seconds(以秒为单位)。O(1)

  9. setnx key value
    只有在 key 不存在时,才设置 key 的值。

  10. setrange key index value
    用 value 参数覆写给定 key 所储存的字符串值,从指定下标 index 开始。O(1)

  11. strlen key
    返回 key 所储存的字符串值的长度。注意中文占用字节数的问题。O(1)

  12. mset key1 value1 key2 value2 ...
    同时设置一个或多个 key-value 对。O(n)

  13. msetnx key value key value ...
    同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。

  14. psetex key milliseconds value
    这个命令和 setex 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 setex 以秒为单位。

  15. incr key
    将 key 中储存的数字值增一。如果 key 不存在,自增后 get(key)=1。O(1)

  16. incrby key increment
    将 key 所储存的值加上给定的增量值(increment)。如果 key 不存在,自增后get(key)=k。O(1)

  17. incrbyfloat key increment
    将 key 所储存的值加上给定的浮点增量值(increment)。如需减法,传入负值即可。O(1)

  18. decr key
    将 key 中储存的数字值减一。如果 key 不存在,自减后get(key)=-1。O(1)

  19. decrby key decrement
    key 所储存的值减去给定的减量值(decrement)。如果 key 不存在,自减后get(key)=-k。O(1)

  20. append key value
    如果 key 已经存在并且是一个字符串, append 命令将指定 value 追加到该 key 原来的值(value)的末尾。O(1)

3️⃣Hash相关命令:

  1. hget key field
    获取 hash key 对应的 field 的 value。O(1)

  2. hset key field value
    设置 hash key 对应的 field 的 value。O(1)

  3. hdel key field
    删除 hash key 对应的 field 的 value。O(1)

  4. hexists key field
    判断 hash key 是否有field。O(1)

  5. hlen key
    获取 hash key field 的数量,redis 内部维护了这个值的计数,而不是每次遍历,效率高。O(1)

  6. hmget key field1 field2 ...
    批量获取 hash key 的一批 field 对应的值。O(n)

  7. hmset key field1 field2 ...
    批量设置 hash key 的一批 field 的 value。O(n)

  8. hincrby key field count
    设置 hash key 的 field 字段自增count。O(1)

  9. hincrbyfloat key field float
    hincrby 的浮点数版。O(1)

  10. hgetall key
    返回 hash key 对应所有的 field 和 value。O(n)

  11. hvals key
    返回 hash key 对应所有 field 的 value。O(n)

  12. hkeys key
    返回 hash key 对应的所有field。O(n)

  13. hsetnx key field value
    设置 hash key 对应的 field 的 value。如果 field 存在,则失败。O(1)

4️⃣List相关命令:

  1. rpush key value1 value2...valueN
    从列表右端(尾部)插入值 (1-N个)。O(1~n)

  2. lpush key value1 value2...valueN
    从列表左侧(头部)插入值 (1-N个)。O(1~n)

  3. linsert key before|after value newValue
    在 list 指定的值(前|后)插入 newValue,需要遍历。O(n)

  4. lpop key
    从列表左侧弹出一个item。O(1)

  5. rpop key
    从列表右侧弹出一个item。O(1)

  6. lrem key count value
    根据 count 值,从列表中删除所有 value 相等的项。O(n)

  • count > 0,从左到右,删除最多 count 个与 value 相等的项
  • count < 0,从右到左,删除最多 count 个与 value 相等的项
  • count = 0,删除 list 中所有与 value 相等的项
  1. ltrim key start end
    按照索引范围修剪列表。O(n)

  2. lrange key start end
    获取 list 指定索引范围的所有 item,end 为 -1 时,取到末尾。O(n)

  3. lindex key index
    获取 list 指定索引的 item,index 为 -1,取最后一个item。O(n)

  4. llen key
    获取 list 长度,内部优化值。O(1)

  5. lset key index newValue
    设置 list 指定索引值为newValue。O(n)

  6. blpop key timeout
    lpop 阻塞版本,timeout 是阻塞超时时间,timeout=0 为永不阻塞。O(1)

  7. brpop key timeout
    rpop 阻塞版本,timeout 是阻塞超时时间,timeout=0 为永不阻塞。O(1)

5️⃣Set相关命令:

  1. sadd key element
    向集合 key 添加 element。如果 element 存在,添加失败。ßO(1)

  2. srem key element
    将集合 key 中的 element 移除掉。O(1)

  3. scard key
    计算集合大小。O(1)

  4. sismember key value
    判断 value 是否在集合中

  5. srandmember key count
    从集合中随机挑 count 个元素,只是选取,不会提出

  6. spop key
    从集合中随机弹出一个元素

  7. smembers key
    获取集合所有元素,返回结果无序,如果集合大小心使用

  8. sdiff key1 key2
    取两个集合的差集

  9. sinter key1 key2
    取两个集合的交集

  10. sunion key1 key2
    取两个集合的并集

  11. sdiff | sinter | sunion + store destkey
    将差集、交集、并集结果保存到 destkey 中

6️⃣Zset相关命令:

  1. zadd key score element(可以是多对)
    向集合中添加 score 和 element。O(logN)

  2. zrem key element(可以是多个)
    删除集合中元素。O(1)

  3. zscore key element
    返回该元素的分数。O(1)

  4. zincrby key increScore element
    增加或减少元素分数。O(1)

  5. zcard key
    返回元素的总个数。O(1)

  6. zrank key element
    返回 element 在集合中的排名,从小到大排

  7. zrevrank key element
    返回 element 在集合中的排名,从大到小排

  8. zrange key start end withscores
    获取指定排名范围的的元素和它的分数,可以不带 withscores,即不打印分数。O(log(n)+m)

  9. zrevrange key start end withscores
    zrange 的倒序版本

  10. zrangebyscore key minScore maxScore [withscores]
    返回指定分数范围的升序元素。O(log(n)+m)
    命令说明:
    用于获取有序集合 key 中,score 在 minScore-maxScore 之间的,按 score 的值递增排列。
    相同 score 的 member,按 member 的字典序排列。可选参数 limit,语法与 mysql 的 limit 相似。min 与 max 可以是 -inf 与 +inf。

  11. zrevrangebyscore key minScore maxScore withscores
    zrangebyscore 降序版本

  12. zcount key minScore maxScore
    返回有序集合指定分数范围的元素个数。O(log(n)+m)

  13. zremrangebyrank key start end
    删除指定排名内的升序元素。O(log(n)+m)

  14. zremrangebyscore key minScore maxScore
    删除指定分数内的升序元素。O(log(n)+m)

  15. zinterstore
    取两集合交集并存储

  16. zunionstore
    取两集合并集并存储

7️⃣Bitmap相关命令:

1.setbit key offset value
给位图指定索引设置值(0 或 1)注意 offset 偏移量设置,可能有较大耗时

2.getbit key offset
获取位图指定索引的值

  1. `bitcount key start end]
    获取位图指定范围内(start 到 end,单位为字节,如果不指定就是获取全部)值为 1 的个数

  2. bitop op destkey key...
    做多个 bitmap 的 and、or、not、xor 操作,并将结果保存在 destkey 中

  3. bitpos key targetBit start end
    计算位图指定范围(start 到 end,单位为字节,如果不指定就是获取全部)第一个偏移量对应的值等于 targetBit(0或1)的位置

8️⃣HyperLoglog相关命令:

  1. pfadd key element...
    向 hyperloglog 添加元素

  2. pfcount key...
    计算 hyperloglog 的独立总数

  3. pfmerge destkey sourcekey...
    合并多个 sourcekey 到 destkey

9️⃣GEO相关命令(redis3.2版本):

  1. geoadd key longitude latitude member ...
    增加一个或多个地理位置信息

  2. geopos key member...
    获取一个或多个 member 的经纬度

  3. geodist key member1 member2 unit
    按某个单位(unit:m、km、mi、ft)获取两个地理位置的距离

  4. georadius
    功能参数太多,使用查 api

geo说明:
type geoKey = zset
没有删除api:zrem key member

你可能感兴趣的:(cache,redis)