PHP 详细面试总结 (三 Redis 基础详解)

简介#

  • Redis 是一个开源的 key-value 存储系统。
  • 与 Memcached 类似,Redis 将大部分数据存储在内存中

redis - 系统文件说明#

当 mkae 命令执行完成后,会在当前目录生成多个可执行文件,分别是

  • redis-server:redis 服务器的 daemon 启动程序
  • redis-cli:redis 命令行操作工具。当然,你也可以用 telnet 根据其纯文本协议来操作
  • redis-benchmark:redis 性能测试工具,测试 redis 在你的系统及你的配置下的读写功能
  • redis-stat:redis 状态检测工具,可以检测 redis 当前状态参数及延迟状况

数据类型#

  • 支持的数据类型包括:字符串、哈希表、链表、集合、有序集合以及基于这些数据类型的相关操作。
  • 最为常用的数据类型主要由五种:String、Hash、List、Set 和 Sorted Set
  • 除了多种数据结构的支持,Redis 相比 Memcached 还提供了许多额外的特性,比如 Subscribe/publish 命令,以支持发布 / 订阅模式这样的通知机制等等,这些额外的特性同样有助于拓展它的应用场景
  • redis 通过 Multi / Watch /Exec 等命令可以支持事务的概念,原子性的执行一批命令。在 2.6 以后的版本中由于添加了对 Script 脚本的支持,而脚本固有的是以 transaction 事务的方式执行的,并且更加易于使用,所以不排除将来取消 Multi 等命令接口的可能性

String数据

  • 常用命令:set/get/decr/incr/mget 等;
  • 应用场景:String 是最常用的一种数据类型,普通的 key/value 存储都可以归为此类;
  • 实现方式:String 在 redis 内部存储默认就是一个字符串,被 redisObject 所引用,当遇到 incr、decr 等操作时会转成数值型进行计算,此时 redisObject 的 encoding 字段为 int。

Hash数据

  • 常用命令:hget/hset/hgetall 等
  • 应用场景:我们要存储一个用户信息对象数据,其中包括用户 ID、用户姓名、年龄和生日,通过用户 ID 我们希望获取该用户的姓名或者年龄或者生日;
  • 实现方式:Redis Hash 对应 Value 内部实际就是一个 HashMap,实际这里会有 2 种不同实现,这个 Hash 的成员比较少时 Redis 为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的 HashMap 结构,对应的 value redisObject 的 encoding 为 zipmap, 当成员数量增大时会自动转成真正的 HashMap, 此时 encoding 为 ht

List

  • 常用命令:lpush/rpush/lpop/rpop/lrange 等;
  • 应用场景:Redis list 的应用场景非常多,也是 Redis 最重要的数据结构之一,比如 twitter 的关注列表,粉丝列表等都可以用 Redis 的 list 结构来实现;
  • 实现方式:Redis list 的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis 内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。

Set数据

  • 常用命令:sadd/spop/smembers/sunion 等;
  • 应用场景:Redis set 对外提供的功能与 list 类似是一个列表的功能,特殊之处在于 set 是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set 是一个很好的选择,并且 set 提供了判断某个成员是否在一个 set 集合内的重要接口,这个也是 list 所不能提供的;
  • 实现方式:set 的内部实现是一个 value 永远为 null 的 HashMap,实际就是通过计算 hash 的方式来快速排重的,这也是 set 能提供判断一个成员是否在集合内的原因

Sorted Set数据

  • 常用命令:zadd/zrange/zrem/zcard 等;
  • 应用场景:Redis sorted set 的使用场景与 set 类似,区别是 set 不是自动有序的,而 sorted set 可以通过用户额外提供一个优先级 (score) 的参数来为成员排序,并且是插入有序的,即自动排序。当你需要一个有序的并且不重复的集合列表,那么可以选择 sorted set 数据结构,比如 twitter 的 public timeline 可以以发表时间作为 score 来存储,这样获取时就是自动按时间排好序的。
  • 实现方式:Redis sorted set 的内部使用 HashMap 和跳跃表 (SkipList) 来保证数据的存储和有序,HashMap 里放的是成员到 score 的映射,而跳跃表里存放的 是所有的成员,排序依据是 HashMap 里存的 score, 使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。

持久化#

  • Redis 可以以 master-slave 的方式配置服务器,Slave 节点对数据进行 replica 备份,Slave 节点也可以充当 Read only 的节点分担数据读取的工作。
  • Redis 虽然是基于内存的存储系统,但是它本身是支持内存数据的持久化的,而且提供两种主要的持久化策略:RDB 快照和 AOF 日志
  • Redis 内建支持两种持久化方案,snapshot 快照和 AOF 增量 Log 方式。快照顾名思义就是隔一段时间将完整的数据 Dump 下来存储在文件中。AOF 增量 Log 则是记录对数据的修改操作(实际上记录的就是每个对数据产生修改的命令本身),两种方案可以并存,也各有优缺点,具体参见
  • blog.chinaunix.net/uid-20682890-id-...
  • www.cnblogs.com/rollenholt/p/387444...

内存配置#

/etc/sysctl.conf 添加

vm.overcommit_memory=1

刷新配置使之生效

sysctl vm.overcommit_memory=1

补充介绍:
如果内存情况比较紧张的话,需要设定内核参数:

echo 1 > /proc/sys/vm/overcommit_memory

内核参数说明如下:

  • overcommit_memory 文件指定了内核针对内存分配的策略,其值可以是 0、1、2。
  • 0, 表示内核将检查是否有足够的可用内存供应用进程使用;如果有足够的可用内存,内存申请允许;否则,内存申请失败,并把错误返回给应用进程。
  • 1, 表示内核允许分配所有的物理内存,而不管当前的内存状态如何。
  • 2, 表示内核允许分配超过所有物理内存和交换空间总和的内存

编辑 redis.conf 配置文件(/etc/redis.conf),按需求做出适当调整,比如:

daemonize yes #转为守护进程,否则启动时会每隔5秒输出一行监控信息
save 60 1000 #减小改变次数,其实这个可以根据情况进行指定
maxmemory 256000000 #分配256M内存

Redis 的 7 个应用场景#

一:缓存 —— 热数据#

  • 热点数据(经常会被查询,但是不经常被修改或者删除的数据),首选是使用 redis 缓存,毕竟强大到冒泡的 QPS 和极强的稳定性不是所有类似工具都有的,而且相比于 memcached 还提供了丰富的数据类型可以使用,另外,内存中的数据也提供了 AOF 和 RDB 等持久化机制可以选择,要冷、热的还是忽冷忽热的都可选。
  • 结合具体应用需要注意一下:很多人用 spring 的 AOP 来构建 redis 缓存的自动生产和清除,过程可能如下:
  • Select 数据库前查询 redis,有的话使用 redis 数据,放弃 select 数据库,没有的话,select 数据库,然后将数据插入 redis
  • update 或者 delete 数据库前,查询 redis 是否存在该数据,存在的话先删除 redis 中数据,然后再 update 或者 delete 数据库中的数据
  • 上面这种操作,如果并发量很小的情况下基本没问题,但是高并发的情况请注意下面场景:
  • 为了 update 先删掉了 redis 中的该数据,这时候另一个线程执行查询,发现 redis 中没有,瞬间执行了查询 SQL,并且插入到 redis 中一条数据,回到刚才那个 update 语句,这个悲催的线程压根不知道刚才那个该死的 select 线程犯了一个弥天大错!于是这个 redis 中的错误数据就永远的存在了下去,直到下一个 update 或者 delete。

二:计数器#

  • 诸如统计点击数等应用。由于单线程,可以避免并发问题,保证不会出错,而且 100% 毫秒级性能!爽。
  • 命令:INCRBY
  • 当然爽完了,别忘记持久化,毕竟是 redis 只是存了内存!

三:队列#

  • 相当于消息系统,ActiveMQ,RocketMQ 等工具类似,但是个人觉得简单用一下还行,如果对于数据一致性要求高的话还是用 RocketMQ 等专业系统。
  • 由于 redis 把数据添加到队列是返回添加元素在队列的第几位,所以可以做判断用户是第几个访问这种业务
  • 队列不仅可以把并发请求变成串行,并且还可以做队列或者栈使用

四:位操作(大数据处理)#

  • 用于数据量上亿的场景下,例如几亿用户系统的签到,去重登录次数统计,某用户是否在线状态等等。
  • 想想一下腾讯 10 亿用户,要几个毫秒内查询到某个用户是否在线,你能怎么做?千万别说给每个用户建立一个 key,然后挨个记(你可以算一下需要的内存会很恐怖,而且这种类似的需求很多,腾讯光这个得多花多少钱。。)好吧。这里要用到位操作 —— 使用 setbit、getbit、bitcount 命令。
  • 原理是:redis 内构建一个足够长的数组,每个数组元素只能是 0 和 1 两个值,然后这个数组的下标 index 用来表示我们上面例子里面的用户 id(必须是数字哈),那么很显然,这个几亿长的大数组就能通过下标和元素值(0 和 1)来构建一个记忆系统,上面我说的几个场景也就能够实现。用到的命令是:setbit、getbit、bitcount

五:分布式锁与单线程机制#

  • 验证前端的重复请求(可以自由扩展类似情况),可以通过 redis 进行过滤:每次请求将 request Ip、参数、接口等 hash 作为 key 存储 redis(幂等性请求),设置多长时间有效期,然后下次请求过来的时候先在 redis 中检索有没有这个 key,进而验证是不是一定时间内过来的重复提交
  • 秒杀系统,基于 redis 是单线程特征,防止出现数据库 “爆破”
  • 全局增量 ID 生成,类似 “秒杀”

六:最新列表#

  • 例如新闻列表页面最新的新闻列表,如果总数量很大的情况下,尽量不要使用 select a from A limit 10 这种 low 货,尝试 redis 的 LPUSH 命令构建 List,一个个顺序都塞进去就可以啦。不过万一内存清掉了咋办?也简单,查询不到存储 key 的话,用 mysql 查询并且初始化一个 List 到 redis 中就好了。

七:排行榜#

  • 谁得分高谁排名往上。命令:ZADD(有续集,sorted set)

其他 web 应用场景#

1.在主页中显示最新的项目列表。#

  • redis 使用的是常驻内存的缓存,速度非常快。lpush 用来插入一个内容 id,作为关键字存储在列表头部。ltrim 用来限制列表中的项目数最多为 5000。如果用户需要的检索的数据量超越这个缓存容量,这时才需要把请求发送到数据库。

2.删除和过滤。#

  • 如果一篇文章被删除,可以使用 lrem 从缓存中彻底清除掉。

3.排行榜及相关问题。#

  • 排行榜(leader board)按照得分进行排序。zadd 命令可以直接实现这个功能,而 zrevrange 命令可以用来按照得分来获取前 100 名的用户,zrank 可以用来获取用户排名,非常直接而且操作容易。

4.按照用户投票和时间排序。#

  • 这就像 reddit 的排行榜,得分会随着时间变化。lpush 和 ltrim 命令结合运用,把文章添加到一个列表中。一项后台任务用来获取列表,并重新计算列表的排序,zadd 命令用来按照新的顺序填充生成列表。列表可以实现非常快速的检索,即使是负载很重的站点。

5.过期项目处理。#

  • 使用 unix 时间作为关键字,用来保持列表能够按时间排序。对 current_time 和 time_to_live 进行检索,完成查找过期项目的艰巨任务。另一项后台任务使用 zrange…withscores 进行查询,删除过期的条目。

6.特定时间内的特定项目。#

  • 这是特定访问者的问题,可以通过给每次页面浏览使用 sadd 命令来解决。sadd 不会将已经存在的成员添加到一个集合。

7.实时分析正在发生的情况,用于数据统计与防止垃圾邮件等。#

  • 使用 redis 原语命令,更容易实施垃圾邮件过滤系统或其他实时跟踪系统。

8.pub/sub#

  • 在更新中保持用户对数据的映射是系统中的一个普遍任务。redis 的 pub/sub 功能使用了 subscribe、unsubscribe 和 publish 命令,让这个变得更加容易。

Redis 与 Memcache 对比#

  • 网络 IO 模型 性能对比:(占用的核数,线程数,网络模型)
  • 由于 Redis 只使用单核,而 Memcached 可以使用多核,所以平均每一个核上 Redis 在存储小数据时比 Memcached 性能更高。而在 100k 以上的数据中,Memcached 性能要高于 Redis,虽然 Redis 最近也在存储大数据的性能上进行优化,但是比起 Memcached,还是稍有逊色。
  • Memcached 是多线程,非阻塞 IO 复用的网络模型,分为监听主线程和 worker 子线程,监听线程监听网络连接,接受请求后,将连接描述字 pipe 传递给 worker 线程,进行读写 IO, 网络层使用 libevent 封装的事件库,多线程模型可以发挥多核作用,但是引入了 cache coherency 和锁的问题,比如,Memcached 最常用的 stats 命令,实际 Memcached 所有操作都要对这个全局变量加锁,进行计数等工作,带来了性能损耗。
  • Redis 使用单线程的 IO 复用网络模型,自己封装了一个简单的 AeEvent 事件处理框架,主要实现了 epoll、kqueue 和 select,对于单纯只有 IO 操作来说,单线程可以将速度优势发挥到最大,但是 Redis 也提供了一些简单的计算功能,比如排序、聚合等,对于这些操作,单线程模型实际会严重影响整体吞吐量,CPU 计算过程中,整个 IO 调度都是被阻塞住的。

内存管理方面:#

  • Memcached 使用预分配的内存池的方式,使用 slab 和大小不同的 chunk 来管理内存,Item 根据大小选择合适的 chunk 存储,内存池的方式可以省去申请 / 释放内存的开销,并且能减小内存碎片产生,但这种方式也会带来一定程度上的空间浪费,并且在内存仍然有很大空间时,新的数据也可能会被剔除
  • Redis 使用现场申请内存的方式来存储数据,并且很少使用 free-list 等方式来优化内存分配,会在一定程度上存在内存碎片,Redis 跟据存储命令参数,会把带过期时间的数据单独存放在一起,并把它们称为临时数据,非临时数据是永远不会被剔除的,即便物理内存不够,导致 swap 也不会剔除任何非临时数据(但会尝试剔除部分临时数据),这点上 Redis 更适合作为存储而不是 cache。

数据一致性问题:#

  • Memcached 提供了 cas 命令,可以保证多个并发访问操作同一份数据的一致性问题。 Redis 没有提供 cas 命令,并不能保证这点,不过 Redis 提供了事务的功能,可以保证一串 命令的原子性,中间不会被任何操作打断。由于需要使用 cas 方法,php 的 memcache 客户端不支持该方法,所以改用 php 的 memcached 客户端 。这个东西还是很强大的,它所使用的 libmemcached 库是各种语言都支持的。(需要额外安装编译,可以使用 PECL 装,或手动编译)
    如果采用 CAS(check and set)方式协议,则是如下的情景。
  • 第一步,A 取出数据对象 X,并获取到 CAS-ID1;
  • 第二步,B 取出数据对象 X,并获取到 CAS-ID2;
  • 第三步,B 修改数据对象 X,在写入缓存前,检查 CAS-ID 与缓存空间中该数据的 CAS-ID 是否一致。结果是 “一致”,就将修改后的带有 CAS-ID2 的 X 写入到缓存。
  • 第四步,A 修改数据对象 Y,在写入缓存前,检查 CAS-ID 与缓存空间中该数据的 CAS-ID 是否一致。结果是 “不一致”,则拒绝写入,返回存储失败。

Redis 数据类型:#

> *  Redis相比Memcached来说,拥有更多的数据结构和并支持更丰富的数据操作。
> * 通常在Memcached 里,你需要将数据拿到客户端来进行类似的修改再set回去。

这大大增加了网络 IO 的次数和数据体积。在 Redis 中,这些复杂的操作通常和一般的 GET/SET 一样高效。所以,如果需要缓存能够支持更复杂的结构和操作,那么 Redis 会是不错的选择

存储方式及其它方面#

> * Memcached基本只支持简单的key-value存储,不支持枚举,不支持持久化和复制等功能
> * Redis除key/value之外,还支持list,set,sorted set,hash等众多数据结构,提供了KEYS

进行枚举操作,但不能在线上使用,如果需要枚举线上数据,Redis 提供了工具可以直接扫描其 dump 文件,枚举出所有数据,Redis 还同时提供了持久化和复制等功能。

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 6年前 自动加精
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
讨论数量: 7

@BIBIBABIBO 哈哈 谢谢 我还有很多需要研究的地方 有啥不对 提出来 我修改

6年前 评论

@A_aliane 补充一下缓存穿透吧,基本上都会问

6年前 评论

@BIBIBABIBO 好 我总结一下 明天发 好嘛

6年前 评论

还有一个缓存雪崩

6年前 评论

厉害,不是太明白就感觉很厉害

6年前 评论