年终Redis总结:数据结构+持久化+事务;附《深入理解Redis》+面试题

本文转载自:年终Redis总结:数据结构+持久化+事务;附<深入理解Redis>+面试题


一. Redis 简介

Redis 是一个速度非常快的非关系型数据库(non-relational database)/ NoSQL 数据库。

Redis 不使用表,也不会预定义或者强制去要求用户对 Redis 存储不同的数据进行关联。

Redis 为什么速度非常快?

  • 纯内存操作:数据存在内存中,类似于 HashMap。HashMap 的优势就是查找和操作的时间复杂度都是 O(1)
  • 数据结构简单:不仅数据结构简单,而且对数据操作也简单
  • 单线程:避免了频繁的上下文切换,也不存在多进程或者多线程导致的切换而消耗 CPU 资源,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗

二. Redis 数据结构

Redis 存储键(key)和 5 种不同类型的值(value)之间的映射。这 5 中类型分别为:

STRING(字符串)、LIST(列表)、HASH(散列)、SET(集合)和 ZSET(有序集合)。

1. STRING(字符串)

字符串示例,键为 hello,值为 world:

①. 字符串命令:

②. 使用

127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> get hello
"world"
127.0.0.1:6379> del hello
(integer) 1
127.0.0.1:6379> get hello
(nil)
127.0.0.1:6379> set num 1
OK
127.0.0.1:6379> incr num
(integer) 2
127.0.0.1:6379> get num
"2"

2. LIST(列表)

list-key 是一个包含 3 个元素的列表键,列表中的元素是可以重复的:

①. 列表命令

②. 使用

127.0.0.1:6379> rpush list-key item
(integer) 1
127.0.0.1:6379> rpush list-key item2
(integer) 2
127.0.0.1:6379> rpush list-key item
(integer) 3 # 返回的列表长度
127.0.0.1:6379> lrange list-key 0 -1
1) "item"
2) "item2"
3) "item"
# 使用 0 为范围的开始索引,-1 为范围索引的结束索引,可以去除列表包含的所有元素
127.0.0.1:6379> lindex list-key 1
"item2"
127.0.0.1:6379> lpop list-key
"item"
127.0.0.1:6379> lrange list-key 0 -1
1) "item2"
2) "item"

3. HASH(散列)

hash-key 是一个包含两个键值对的散列键:

①. 散列命令

②. 使用

127.0.0.1:6379> hset hash-key sub-key1 value1
(integer) 1
127.0.0.1:6379> hset hash-key sub-key2 value2
(integer) 1
127.0.0.1:6379> hset hash-key sub-key1 value2
(integer) 0
127.0.0.1:6379> hgetall hash-key
1) "sub-key1"
2) "value2"
3) "sub-key2"
4) "value2"
127.0.0.1:6379> hdel hash-key sub-key2
(integer) 1
127.0.0.1:6379> hdel hash-key sub-key2
(integer) 0
127.0.0.1:6379> hget hash-key sub-key1
"value2"
127.0.0.1:6379> hgetall hash-key
1) "sub-key1"
2) "value2"

4. SET(集合)

set-key 是一个包含 3 个元素的集合键:

①. 集合命令

②. 使用

127.0.0.1:6379> sadd set-key item
(integer) 1
127.0.0.1:6379> sadd set-key item2
(integer) 1
127.0.0.1:6379> sadd set-key item3
(integer) 1
127.0.0.1:6379> sadd set-key item
(integer) 0
127.0.0.1:6379> smembers set-key
1) "item"
2) "item3"
3) "item2"
127.0.0.1:6379> sismember set-key item4
(integer) 0
127.0.0.1:6379> sismember set-key item
(integer) 1
127.0.0.1:6379> srem set-key item
(integer) 1
127.0.0.1:6379> srem set-key item
(integer) 0
127.0.0.1:6379> smembers set-key
1) "item3"
2) "item2"

5. ZSET(有序集合)

zset-key 是已一个包含 2 个元素的有序集合键:

①. 有序集合命令:

②. 使用

127.0.0.1:6379> zadd zset-key 728 member1
(integer) 1
127.0.0.1:6379> zadd zset-key 982 member2
(integer) 1
127.0.0.1:6379> zadd zset-key 982 member2
(integer) 0
127.0.0.1:6379> zrange zset-key 0 -1 withscores
1) "member1"
2) "728"
3) "member2"
4) "982"
127.0.0.1:6379> zrangebyscore zset-key 0 800 withscores
1) "member1"
2) "728"
127.0.0.1:6379> zrem zset-key member1
(integer) 1
127.0.0.1:6379> zrem zset-key member1
(integer) 0
127.0.0.1:6379> zrange zset-key 0 -1 withscores
1) "member2"
2) "982"

三. Redis 与 Memcached

两者都是非关系型内存键值数据库,主要有以下不同:

1. 数据类型

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

2. 数据持久化

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

3. 分布式

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

4. 内存管理机制

  • 在 Redis 中,并不是所有数据都一直存储在内存中,可以将一些很久没用的 value 交换到磁盘,而 Memcached 的数据则会一直在内存中。
  • Memcached 将内存分割成特定长度的块来存储数据,以完全解决内存碎片的问题。但是这种方式会使得内存的利用率不高,例如块的大小为 128 bytes,只存储 100 bytes 的数据,那么剩下的 28 bytes 就浪费掉了。

5. 线程模型

  • Redis 使用单线程的多路 IO 复用模型
  • Memcached 是多线程,非阻塞IO复用的网络模型

四. Redis持久化

Redis 是内存型数据库,为了保证数据断电后不丢失,需要将内存中的数据持久化到磁盘上;另一方面,保存中间结果,不必重新计算。

持久化有 2 种:

  • 快照持久化:将存在某一时刻的所有数据都写入磁盘中
  • AOF 持久化:在执行写命令时,将被执行的写命令复制到文件中

Redis 提供的持久化配置选项:

# 快照持久化
save 60 1000
stop-writes-on-bgsave-error yes
rdbcompression no

# AOF 持久化
appendonly yes
appendfsync always # 同步选项
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# 共享选项,决定了快照文件和 AOF 文件的保存位置
dir ./

1. 快照持久化

快照持久化是通过创建快照来获得 Redis 存储在内存中数据在某个时间点上的副本。在创建快照后,用户可对快照备份,可将快照留在原地以便重启 Redis 时使用。

①. 快照持久化配置

save 60 10000        
# 在60秒(1分钟)之后,如果至少有10000个键发生变化,Redis 就会自动触发 BGSAVE 命令创建快照。

stop-writes-on-bgsave-error yes 
# 表示备份进程出错的时候,主进程就停止接收新的写入操作,是为了保护持久化数据的一致性。

rdbcompression no
# RDB 的压缩设置为 no,因为压缩会占用更多的 CPU 资源。

②. 创建快照的方法

  • BGAVSE 命令:客户端向 Redis 发送 BGSAVE 命令来创建一个快照。对于支持 BGSAVE 命令的平台来说(基本上所有平台支持,除了Windows平台),Redis 会调用 fork 来创建一个子进程,然后子进程负责将快照写入硬盘,而主进程则继续处理命令请求。
  • 用户设置 save 配置选项
save 60 10000
# 从 redis 最近一次创建快照之后算起,满足 “60 s 内 10000 次写入”,Redis 会自动触发 BGSAVE 命令

③. 存在的问题

  • 如果系统发生故障,将会丢失最近一次创建快照之后的数据
  • 如果数据量很大,保存快照的时间会很长

2. AOF 持久化

AOF 持久化将被执行的写命令写到 AOF 文件末尾,记录数据发生的变化。Redis 值要从头到尾重新执行一次 AOF

文件包含的所有写命令,就可恢复 AOF 文件所记录的数据。

默认情况下,Redis 没有开启 AOF(append only file)方式的持久化,可以通过 appendonly 参数开启:

appendonly yes

①. 快照持久化配置

appendonly yes

appendfsync always # 同步选项

auto-aof-rewrite-percentage 100
    
auto-aof-rewrite-min-size 64mb

②. 同步选项

使用 AOF 持久化需要设置同步选项,从而确保写命令什么时候会同步到磁盘文件上。

3. 重写 / 压缩 AOF 文件

AOF 持久化存在的问题:

  • 随着 Redis 不断运行,AOF 文件的体积会不断增长,占用更多的磁盘空间
  • 若 AOF 文件体积非常大,则恢复的时间可能会比较长

为了解决 AOF 文件不断增大的问题,我们采用重写 / 压缩 AOF 文件的方式:

用户 Redis 发送 BGREWRITEAOF 命令,通过移除 AOF 文件中冗余命令来重写 AOF 文件来减小 AOF 文件的体积。

(新的 AOF 文件和原有的 AOF 文件所保存的数据状态是一致的,但是体积更小)。

BGREWRITEAOF 原理:

Redis 维护 AOF 重写缓冲区和 AOF 缓冲区。子进程创建新的 AOF 文件期间,记录服务器执行的所有写命令会同时写入 2 个缓冲区中,使得新旧两个 AOF 文件中所保存的数据状态一致。

4. 对比

5. 优化

Redis 4.0 以后将快照和 AOF 混合持久化(默认关闭,使用 aof-user-rdb-preamble开启),AOF 重写时将快照写入 AOF 文件开头。

这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据,当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。

五. Redis事务

1. 命令

  • MULTI命令:将客户端从非事务状态切换到事务状态,标志着事务的开始。
  • EXEC命令:客户端向服务端发送该命令后,服务器会遍历这个客户端的事务队列,并将所有命令的执行结果返回给客户端。
  • WATCH命令:乐观锁,可以在EXEC命令执行之前,监视任意数量的数据库键,并在EXEC命令执行的时候,检查被监视的键是否至少有一个已经被修改过了,如果有,服务器将拒绝执行该事务,并向客户端返回代表事务执行失败的空回复。

2. 简述

Redis 通过 MULTI、EXEC、WATCH 等命令来实现事务功能。

事务提供了一种将多个命令请求打包,然后一次性、按顺序地执行多个命令的机制。服务器在执行事务期间,不会改去执行其它客户端的命令请求,它会将事务中的所有命令都执行完毕,然后才去处理其他客户端的命令请求。

事务中的多个命令被一次性发送给服务器,而不是一条一条发送,这种方式被称为pipeline。

注意:

  • Redis 最简单的事务实现方式是使用 MULTI 和 EXEC 命令将事务操作包围起来。
  • 使用 pineline 的好处:Redis 使用的是客户端 / 服务器(C/S)模型和请求/响应协议的 TCP 服务器。Redis 客户端与 Redis 服务器之间使用 TCP 协议进行连接,一个客户端可以通过一个 Socket 连接发起多个请求命令。每个请求命令发出后客户端通常会阻塞并等待 Redis 服务器处理,Redis 处理完请求命令后会将结果通过响应报文返回给客户端,因此当执行多条命令的时候都需要等待上一条命令执行完毕才能执行。
  • pipeline 可以一次性发送多条命令并在执行完后一次性将结果返回,可以减少客户端与服务器之间的网络通信次数从而提升性能,并且 pineline 基于队列,而队列的特点是先进先出,这样就保证数据的顺序性。
  • Redis 的事务和传统关系型数据库事务的最大区别在于,Redis 不支持事务回滚机制(rollback),即使事务队列中的某个命令在执行期间出现了错误,整个事务也会继续执行下去,直到将事务队列中的所有命令都执行完毕为止。因为其作者认为,Redis 事务执行时错误通常都是编程错误产生的,这种错误通常只会出现在开发环境中,而很少会在实际的生产环境中出现,所以他任务没有必要开发 Redis 的回滚功能。

六. 深入理解Redis

  1. 为何选择Redis?
  2. 高级键管理与数据结构
  3. 内存管理的建议与技巧
  4. Redis编程第一.部分: Redis 核心、客户端和编程语言
  5. Redis编程第二部分: Lua 脚本、管理与DevOps
  6. 可伸缩性: Redis 集群和Sentinel
  7. Redis与互补的NoSQL技术
  8. Docker容器与云端部署
  9. 任务管理与消息队列
  10. 信息流的测量与管理

七. Redis面试题

1. 什么是Redis?简述它的优缺点?

Redis的全称是:Remote Dictionary.Server,本质上是一个Key-Value类型的内存数据库,很像 memcached,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘 上进行保存。

因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过 10万次读写操作,是已知性能最快的 Key-Value DB。

Redis的出色之处不仅仅是性能,Redis最大的魅力是支持保存多种数据结构,此外单个value的最大限 制是1GB,不像 memcached只能保存1MB的数据,因此Redis可以用来实现很多有用的功能。

比方说用他的List来做FIFO双向链表,实现一个轻量级的高性 能消息队列服务,用他的Set可以做高 性能的tag系统等等。

另外Redis也可以对存入的Key-Value设置expire时间,因此也可以被当作一 个功能加强版的 memcached来用。 Redis的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能 读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。

2. Redis与memcached相比有哪些优势?

  • memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型
  • redis的速度比memcached快很多redis的速度比memcached快很多
  • redis可以持久化其数据redis可以持久化其数据

3. Redis支持哪几种数据类型?

String、List、Set、Sorted Set、hashes

4. Redis主要消耗什么物理资源?

内存。

5. Redis有哪几种数据淘汰策略?

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

6. Redis官方为什么不提供Windows版本?

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

7. 一个字符串类型的值能存储最大容量是多少?

512M

8. 为什么Redis需要把所有数据放到内存中?

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

9. Redis集群方案应该怎么做?都有哪些方案?

  • codis
  • 目前用的最多的集群方案,基本和twemproxy一致的效果,但它支持在节点数量改变情况下,旧节点 数据可恢复到新hash节点。 redis cluster3.0自带的集群,特点在于他的分布式算法不是一致性hash,而是hash槽的概念,以及自 身支持节点设置从节点。具体看官方文档介绍。
  • 在业务代码层实现,起几个毫无关联的redis实例,在代码层,对key进行hash计算,然后去对应的 redis实例操作数据。这种方式对hash层代码要求比较高,考虑部分包括,节点失效后的替代算法方 案,数据震荡后的自动脚本恢复,实例的监控,等等。

10. Redis集群方案什么情况下会导致整个集群不可用?

有A,B,C三个节点的集群,在没有复制模型的情况下,如果节点B失败了,那么整个集群就会以为缺少 5501-11000这个范围的槽而不可用。

11. Redis有哪些适合的场景?

  • 会话缓存(Session Cache):最常用的一种使用Redis的情景是会话缓存(sessioncache),用Redis缓存会话比其他存储(如 Memcached)的优势在于:Redis提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的 购物车信息全部丢失,大部分人都会不高兴的,现在,他们还会这样吗? 幸运的是,随着 Redis 这些年的改进,很容易找到怎么恰当的使用Redis来缓存会话的文档。甚至广为 人知的商业平台Magento也提供Redis的插件。
  • 全页缓存(FPC):除基本的会话token之外,Redis还提供很简便的FPC平台。回到一致性问题,即使重启了Redis实 例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似PHP本地 FPC。 再次以Magento为例,Magento提供一个插件来使用Redis作为全页缓存后端。 此外,对WordPress的用户来说,Pantheon有一个非常好的插件wp-redis,这个插件能帮助你以最快 速度加载你曾浏览过的页面。
  • 队列:Reids在内存存储引擎领域的一大优点是提供list和set操作,这使得Redis能作为一个很好的消息队列 平台来使用。Redis作为队列使用的操作,就类似于本地程序语言(如Python)对 list 的 push/pop 操作。 如果你快速的在Google中搜索“Redis queues”,你马上就能找到大量的开源项目,这些项目的目的 就是利用Redis创建非常好的后端工具,以满足各种队列需求。例如,Celery有一个后台就是使用 Redis作为broker,你可以从这里去查看。
  • 排行榜/计数器:Redis在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(SortedSet)也使 得我们在执行这些操作的时候变的非常简单,Redis只是正好提供了这两种数据结构。 所以,我们要从排序集合中获取到排名最靠前的10个用户–我们称之为“user_scores”,我们只需要像 下面一样执行即可: 当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执 行: ZRANGE user_scores 0 10 WITHSCORES Agora Games就是一个很好的例子,用Ruby实现的,它的排行榜就是使用Redis来存储数据的,你可 以在这里看到。
  • 发布/订阅: 最后(但肯定不是最不重要的)是Redis的发布/订阅功能。发布/订阅的使用场景确实非常多。我已看见 人们在社交网络连接中使用,还可作为基于发布/订阅的脚本触发器,甚至用Redis的发布/订阅功能来建 立聊天系统!

本文转载自:年终Redis总结:数据结构+持久化+事务;附<深入理解Redis>+面试题

发布了209 篇原创文章 · 获赞 13 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Java_supermanNO1/article/details/103702939