Redis 是一个开源(BSD 许可)、基于内存、支持多种数据结构的存储系统,可以作为数据库、缓存和消息中间件。它支持的数据结构有字符串(strings)、哈希(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)等,除此之外还支持 bitmaps、hyperloglogs 和地理空间( geospatial )索引半径查询等功能。它内置了复制(Replication)、LUA 脚本(Lua scripting)、LRU 驱动事件(LRU eviction)、事务(Transactions)和不同级别的磁盘持久化(persistence)功能,并通过 Redis 哨兵(哨兵)和集群(Cluster)保证缓存的高可用性(High availability)。
类型 | 简介 | 特性 | 场景 |
---|---|---|---|
String(字符串) | 二进制安全 | 可以包含任何数据,比如 JPG 图片或者序列化的对象,一个键最大能存储 512M | 简短的字符场景 |
Hash(哈希) | 键值对集合,即编程语言中的 Map 类型 | 适合存储对象,并且可以像数据库中 update 一个属性一样只修改某一项属性值(Memcached 中需要取出整个字符串反序列化成对象修改完再序列化存回去) | 存储、读取、修改用户属性 |
List(列表) | 链表(双向链表) | 增删快,提供了操作某一段元素的 API | ①最新消息排行等功能(比如朋友圈的时间线); ②消息队列 |
Set(集合) | 哈希表实现,元素不重复 | ①添加、删除,查找的复杂度都是 O(1) ②为集合提供了求交集、并集、差集等操作 | ①共同好友; ②利用唯一性,统计访问网站的所有独立 IP; ③好友推荐时,根据 tag 求交集,大于某个阈值就可以推荐 |
Sorted Set(有序集合) | 将 Set 中的元素增加一个权重参数 score,元素按 score 有序排列 | 数据插入集合时,已经进行天然排序 |
| ①排行榜; ②带权重的消息队列 |
Redis 是单进程单线程的,它可以通过队列技术将并发访问变为串行访问,避免了传统数据库串行控制的开销。
Redis 将数据放在内存中有一个好处,那就是可以实现最快的对数据读取,如果数据存储在硬盘中,磁盘 I/O 会严重影响 Redis 的性能。而且 Redis 还提供了数据持久化功能,不用担心服务器重启对内存中数据的影响。其次现在硬件越来越便宜的情况下,Redis 的使用也被应用得越来越多,使得它拥有很大的优势。
Redis 支持主从同步、从从同步。如果是第一次进行主从同步,主节点需要使用 bgsave 命令,再将后续修改操作记录到内存的缓冲区,等 RDB 文件全部同步到复制节点,复制节点接受完成后将 RDB 镜像记载到内存中。等加载完成后,复制节点通知主节点将复制期间修改的操作记录同步到复制节点,即可完成同步过程。
因为 Redis 是基于 TCP 协议的请求/响应服务器,每次通信都需要经过 TCP 协议的三次握手,所以当需要执行的命令足够大时,会产生很大的网络延迟。并且网络的传输时间成本和服务器开销没有计入其中,总的延迟可能更大。Pipeline 主要就是为了解决存在这种情况的场景,对此存在类似的场景都可以考虑使用 Pipeline。
可以适用场景有:如果存在批量数据需要写入 Redis,并且这些数据允许一定比例的写入失败,那么可以使用 Pipeline,后期再对失败的数据进行补偿即可。
使用 pipeline(管道)的好处在于可以将多次 I/O 往返的时间缩短为一次,但是要求管道中执行的指令间没有因果关系。
用 pipeline 的原因在于可以实现请求/响应服务器的功能,当客户端尚未读取旧响应时,它也可以处理新的请求。如果客户端存在多个命令发送到服务器时,那么客户端无需等待服务端的每次响应才能执行下个命令,只需最后一步从服务端读取回复即可。
在 MySQL 中,事务是指一组操作中,要么全部执行,要么全部不执行。而在 Redis 也存在事务的概念,Redis 中的事务是一组命令的集合,是 Redis 的最小执行单位。它可以保证一次执行多个命令,每个事务是一个单独的隔离操作,事务中的所有命令都会序列化、按顺序地执行。服务端在执行事务的过程中,不会被其他客户端发送来的命令请求打断。
它的原理是先将属于一个事务的命令发送给 Redis,然后依次执行这些命令。
Redis 的事务不支持回滚,但是执行的命令有语法错误,Redis 会执行失败,这些问题可以从程序层面捕获并解决。但是如果出现其他问题,则依然会继续执行余下的命令。这样做的原因是因为回滚需要增加很多工作,而不支持回滚则可以保持简单、快速的特性。
Redis密码设置有两种方式:
需要注意的是,通过这种方式设置访问密码,如果 redis.conf 配置文件中没有设置对应的访问密码,那么服务器重启后访问密码会失效。
多线程处理会涉及到锁,并且多线程处理会涉及到线程切换而消耗 CPU。采用单线程,避免了不必要的上下文切换和竞争条件。其次 CPU 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是机器内存或者网络带宽。
通过命令 ping 执行后,会得到回复结果 pong。如果没有得到 pong 的结果说明 Redis 没有正常连通。
Redis 客户端和 Redis 服务器通信时使用的是 RESP(Redis 序列化协议)通讯协议,该协议是专门为 Redis 设计的,但是也可以用于其他客户端和服务器软件项目中。
RESP 的特点为:实现简单、快速解析、可读性好。
单点 TPS 达到 8 万/秒,QPS 达到 10 万/秒。TPS 和 QPS 的意思:
QPS:应用系统每秒钟最大能接受的用户访问量。每秒钟处理完请求的次数,注意这里是处理完,具体是指发出请求到服务器处理完成功返回结果。可以理解在 Server 中有个 counter,每处理一个请求加 1,1s 后 counter=QPS。
TPS:每秒钟最大能处理的请求数。每秒钟处理完的事务次数,一个应用系统 1s 能完成多少事务处理,一个事务在分布式处理中,可能会对应多个请求,对于衡量单个接口服务的处理能力,用 QPS 比较合理。
因为 Redis 在重启才能加载配置项中的配置,所以需要重启才能生效。针对运行实例,有许多配置选项可以通过 CONFIG SET 命令进行修改,而无需执行任何形式的重启。
从 Redis 2.2 开始,可以从 AOF 切换到 RDB 的快照持久性或其他方式而不需要重启 Redis。检索 ‘CONFIG GET *’ 命令获取更多信息。
但偶尔重新启动是必须的,如为升级 Redis 程序到新的版本,或者当你需要修改某些目前 CONFIG 命令还不支持的配置参数的时候。
发布订阅是一种消息通信模式,发送者发送消息到某个频道,订阅了该频道的用户都可以接收到消息。该模式由发布者、接收者和频道组成。
zset 的功能和 sets 类似,但是它在集合内的元素是有顺序,不能重复的。所以适合做排行榜之类的功能。它底层实现机制的实现方式有两种,分别为 ziplist(压缩列表) 或者 skiplist(跳跃表)
它们的区别为:
命令 | 作用 | 使用方法 |
---|---|---|
MULTI | 标记一个事务块的开始 | Multi |
DISCARD | 取消事务、放弃执行事务块的所有命令 | Discard |
WATCH key [key …] | 监视一个 (或多个) key,如果在事务执行之前这个 (或这些) key 被其他命令所改动,那么事务将被打断 | Watch key |
EXEC | 执行所有事务块内的命令 | Exec |
UNWATCH | 取消 WATCH 命令对所有 key 的监视 | Unwatch |
可以使用 EXPIRE 和 PERSIST 命令。对一个已经带有生存时间的 key 执行 EXPIRE 命令,新指定的生存时间会取代旧的生存时间。PERSIST 命令可以移除给定 key 的生存时间,将这个 key 从带生存时间转换成持久的。
Redis 实例最多可以存放 2 的 32 次方 -1 个 keys,只要 Redis 的内存空间足够可以支持,任何的 list、set、sorted set 都可以放 2^32 -1 个元素。
设置 Redis 的最大连接数的命令为:
redis-server -maxclients 100000(连接数);
查看 Redis 最大连接数的命令为:
config get maxclients
查看 Redis 连接数的命令为:
info 命令
Redis 提供了过期时间处理函数,可以对指定的键名设置过期时间。如果对键名不设置过期时间也可以使用 DEL 函数对数据进行删除。当用户对一个键名设置了生效时间,我们可以说这个键名存在“生存时间”或“在指定时间后过期”。对键名设置过期时间可以有效地释放键名占用的内存空间,在实际的开发过程中是非常提倡的一种做法。相关命令如下:
命令 | 说明 | Cli 命令写法 |
---|---|---|
PERSIST | 移除键的过期时间 | PERSIST key-name |
EXPIRE | 让给定键在指定的秒数之后过期 | EXPIRE key-name seconds |
EXPIREAT | 将给定键的过期时间设置为给定的 UNIX 时间戳,以秒为单位 | EXPIREAT key-name timestamp |
PEXPIRE | 让给定键在指定的毫秒数之后过期 | PEXPIRE key-name milliseconds |
PEXPIREAT | 将一个毫秒级精度的 UNIX 时间戳设置为给定键过期时间 | PEXPIREAT key-name timestamp-milliseconds |
PTTL | 查看给定键距离过期时间还有多少毫秒 | PTTL key-name |
TTL | 查看给定键距离过期还有多少秒 | TTL key-name |
SDS 实现方式相对 C 语言的 String 的好处有:
以上几点好处可以概括如下:
C 字符串 | SDS |
---|---|
获取字符串长度的复杂度为 O(N) | 获取字符串长度的复杂度为 O(1) |
API 是不安全的,可能会造成缓冲区溢出 | API 是安全的,不会造成缓冲区溢出 |
修改字符串长度 N 次必然需要执行 N 次内存重分配 | 修改字符串长度 N 次最多需要执行 N 次内存重分配 |
只能保存文本数据 | 可以保存文本或者二进制数据 |
可以使用所有库中的函数 | 可以使用一部分库的函数 |
Redis 底层实现了简单动态字符串的类型(SDS),来表示 String 类型。没有直接使用 C 语言定义的字符串类型。
Redis 底层使用简单动态字符串(simple dynamic string,SDS)的抽象类型实现的。默认以 SDS 作为自己的字符串表示。而没有直接使用 C 语言定义的字符串类型。
SDS 的定义格式如下:
struct sdshdr{//记录 buf 数组中已使用字节的数量//等于 SDS 保存字符串的长度int len;//记录 buf 数组中未使用字节的数量int free;//字节数组,用于保存字符串char buf[]; //buf的大小等于len+free+1,其中多余的1个字节是用来存储‘\0’的。
}
SDS 的存储示例如下:
内存消耗可以分类为:
可以使用 info 命令来获取内存统计使用信息。
可以使用 redis-cli –bigkeys 命令统计 bigkey 的分布。也可以在生产环节下执行 debug object key 命令查看 serializedlength 属性,获得 key 对应的 value 序列化之后的字节数。
bigkey 是指键值占用内存空间非常大的 key。例如一个字符串 a 存储了 200M 的数据。
bigkey 的主要影响有:
因为内存的空间是有限的,所以 Redis 淘汰机制主要为了解决在某个时间点,Redis 中存在很多过期键,定期删除策略随机抽查时没有抽查到,并且也没有走惰性删除策略时,大量过期键占用内存的问题。如果内存只能存 20w 数据,而我们需要存储 2000w 的数据时,自然就需要对多出来的数据进行删除或覆盖,保证内存中存储的数据都是热数据。所以当 Redis 内存数据集的大小上升到一定数量时,就会执行数据淘汰策略。
我们可以使用 keys 命令和 scan 命令,但是会发现使用 scan 更好。
大量的请求瞬时涌入系统,而这个数据在 Redis 中不存在,所有的请求都落到了数据库上把数据库打死。造成这种情况的原因有系统设计不合理、缓存数据更新不及时,或爬虫等恶意攻击。 解决办法有:
但是缓存空值的缺点是:
缓存雪崩是指当大量缓存失效时,大量的请求访问直接请求数据库,导致数据库服务器无法抗住请求或挂掉的情况。这时网站常常会出现 502 错误,导致网站不可用问题。
在预防缓存雪崩时,建议遵守以下几个原则:
如果有大量的 key 在同一时间过期,那么可能同一秒都从数据库获取数据,给数据库造成很大的压力,导致数据库崩溃,系统出现 502 问题。也有可能同时失效,那一刻不用都访问数据库,压力不够大的话,那么 Redis 会出现短暂的卡顿问题。所以为了预防这种问题的发生,最好给数据的过期时间加一个随机值,让过期时间更加分散。
Redis 的哨兵作用是管理多个 Redis 服务器,提供了监控、提醒以及自动的故障转移的功能。哨兵可以保证当主服务器挂了后,可以从从服务器选择一台当主服务器,把别的从服务器转移到读新的主机。Redis 哨兵的主要功能有:
Redis 的集群的功能是为了解决单机 Redis 容量有限的问题,将数据按一定的规则分配到多台机器,对内存的每秒访问不受限于单台服务器,可受益于分布式集群高扩展性。
外部原因主要是服务器的原因,例如服务器的 CPU 线程在切换过程中竞争过大,内存出现问题、网络问题等。
对于 big key 先分析业务存在大键值是否合理,如果不合理我们可以把它们拆分为多个小的存储。或者看是否可以使用别的存储介质存储这种 big key 解决占用内存空间大的问题。
对于 hot key 我们可以在其他机器上复制这个 key 的备份,让请求进来的时候,去随机的到各台机器上访问缓存。所以剩下的问题就是如何做到让请求平均的分布到各台机器上。
分区可以让 Redis 管理更大的内存,Redis 将可以使用所有机器的内存。如果没有分区,你最多只能使用一台机器的内存。
分区使 Redis 的计算能力通过简单地增加计算机得到成倍提升,Redis 的网络带宽也会随着计算机和网卡的增加而成倍增长。
需要知道 Redis 的复制方式前,需要知道主从复制(Master-Slave Replication)的工作原理,具体为:
整个执行的过程都是使用异步复制的方式进行复制。
当一个数据需要更新时,因为不可能做到同时更新数据库和缓存,那么此时读取数据的时候就一定会发生数据不一致问题,而数据不一致问题在金融交易领域的系统中是肯定不允许的。
解决办法:读的时候先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。更新的时候,先更新数据库,然后再删除缓存。
为防止以后扩容增加难度,最好的办法就是一开始就使用分布式。即便只有一台服务器,也可以一开始就让 Redis 以分布式的方式运行,使用分区,在同一台服务器上启动多个实例。
刚开始操作时比较麻烦,但是当数据不断增长,需要更多的 Redis 服务器时,只需要将 Redis 实例从一台服务迁移到另外一台服务器即可,而不用考虑重新分区的问题。一旦添加了另一台服务器,只需要将一半的 Redis 实例从第一台机器迁移到第二台机器。
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在单机或者单进程环境下,多线程并发的情况下,使用锁来保证一个代码块在同一时间内只能由一个线程执行。
分布式锁的作用是当多个进程不在同一个系统中,用分布式锁可以控制多个进程对资源的访问。
fork 和 cow。fork 是指 Redis 通过创建子进程来进行 bgsave 操作,cow 指的是 copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。
Redis 主要支持的持久化机制为 RDB(快照)和 AOF(追加文件)。
RDB 持久化:是在指定时间间隔内保存数据快照到硬盘中。但 RDB 的持久化方式没有办法实现实时性的持久化。当应用使用 RDB 持久化后,如果 Redis 系统发生崩溃,那么使用 RDB 恢复数据时,恢复后的数据中,存在丢失最近一次生成快照之后更改的所有数据。所以 RDB 持久化不适用于丢失一部分数据也会对应用造成很大影响的备份中。
AOF 持久化:是把命令追加到操作日志的尾部,然后保存所有历史操作。AOF 主要是解决数据持久化的实时性。Redis 服务器默认开启 RDB,关闭 AOF;要开启 AOF,需要在配置文件中配置:appendonly yes。AOF 持久化相对于 RDB 持久化的优点在于可以实时的对 Redis 缓存进行写入记录,保证快速恢复缓存时的完整性。
优点:
缺点:
优点:
缺点:
在实际的生产环境当中对于选择 RDB 持久化方式还是 AOF 持久化方式都需要根据数据量、应用对数据的安全要求、硬件相关预算等情况综合考虑,同时也可以考虑主从复制的策略,可以设置主机(master)和从机(slave)使用不同的持久化方案。对此以下只是简单的进行一个选择的介绍,具体在实际的开发应用过程中还需要根据实际情况综合选择。
如果 Redis 被当做缓存使用,使用一致性哈希实现动态扩容缩容。如果 Redis 被当做一个持久化存储使用,必须使用固定的 keys-to-nodes 映射关系,节点的数量一旦确定不能变化。否则的话(即 Redis 节点需要动态变化的情况),必须使用可以在运行时进行数据再平衡的一套系统,而当前只有 Redis 集群可以做到这样。
如果 AOF 文件数据出现异常,为了保证数据的一致性,Redis 服务器会拒绝加载 AOF 文件。可以尝试使用 redis-check-aof -fix 命令修复。
Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0~16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。
Redis 没有使用哈希一致性算法,而是使用哈希槽。Redis 中的哈希槽一共有 16384 个,计算给定密钥的哈希槽,我们只需要对密钥的 CRC16 取摸 16384。假设集群中有 A、B、C 三个集群节点,不存在复制模式下,每个集群的节点包含的哈希槽如下:
节点 A 包含从 0 到 5500 的哈希槽;
节点 B 包含从 5501 到 11000 的哈希槽;
节点 C 包含从 11001 到 16383 的哈希槽;
这时,如果节点 B 出现故障,整个集群就会出现缺少 5501 到 11000 的哈希槽范围而不可用。
Redis 集群架构是支持单节点单机模式的,也支持一主多从的主从结构,还支持带有哨兵的集群部署模式。
Redis 集群并没有使用一致性 hash,而是引入了哈希槽的概念。Redis 集群有 16384(2^14)个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽,集群的每个节点负责一部分 hash 槽。
Redis 集群支持的主从复制,数据同步主要有两种方法:一种是全量同步,一种是增量同步。
Redis 集群中有可能存在写操作丢失的问题,但是丢失概率一般可以忽略不计。主要是 Redis 并没有一个机制保证数据一定写不丢失。在以下问题中可能出现键值丢失的问题:
使用异步复制。
Redis 集群有 16384 个哈希槽。
Redis 集群不支持选择数据库操作,默认在 0 数据库。
主从复制的模式相对于单节点的好处在于,实行读写分离提高了系统的读写效率,提高了网站数据的读取加载速度。但是缺点是由于写数据主要在主节点上操作,主节点内存空间有限,并且主节点存在单点风险。
过期键的删除策略是将惰性删除策略和定期删除策略组合使用。
Redis 采用的删除策略是将惰性删除策略和定期删除策略组合使用。
当客户端运行了新命令添加新数据,Redis 都会检查内存使用情况,如果内存使用情况大于 maxmemory 的限制,那么回根据设置的淘汰策略进行回收。所以会出现内存的使用限制不断的超过边界又被回收到边界以下。
可以使用 Hash、list、sorted set、set 等集合类型数据,更有利于将数据存储得更紧凑,更有利于内存空间的使用。
可以对大对象数据进行拆分,防止执行一次命令操作过多的数据。也可以将一些算法复杂度高的命令或执行效率低的命令,禁用或者替换成高的指令。
慢查询是指系统执行命令之后,当计算系统执行的指令时间超过设置的阀值,该命令就会被记录到慢查询中,该命令叫做慢指令。
如果 Redis 的内存使用达到了 redis.conf 配置文件中的设置上限,执行 Redis 的写命令会返回错误信息,但是还是支持读操作。解决这个问题的办法是,可以开启 Redis 的淘汰机制,当 Redis 内存达到上限时可以根据配置的策略删除一些数据,防止内存用完。
缓存的更新策略主要有以下几种:
对应的策略分别需要注意的事项为:
这种策略可能导致数据库数据和缓存数据不一致的问题。如果存在线程 1 和线程 2,线程 1 写数据先删除缓存,有一个线程 2 正好需要查询该缓存,发现缓存不存在,去访问数据库,并得到旧值放入缓存重,线程 1 再更新数据库。那么这时就出现了数据不一致的问题。如果缓存没有过期时间,那么这个脏数据一直存在。如果要解决这个问题,那么可以在更新完数据库后,对缓存再淘汰一次。
这种策略可能导致数据库数据和缓存数据不一致的问题。如果在更新完数据库还没来得及删除缓存的时候,有请求过来从缓存中获取数据,那么可能会造成缓存和数据库数据不一致的问题。但是正常情况下,机器不出现故障或其他影响的情况下,不一致性的可能性相对较低。
可以对 Redis 进行集群部署,实行主从同步读写分离,可以方便的对 Redis 进行横向扩容,可以支撑系统更大数据量的缓存和提高系统的可用性。
惰性删除的优点为:对 CPU 比较友好,因为每次键空间获取键时,检查获取到的键是否过期删除,删除的键只在选中中饿键,不用花费过多的时间和资源在其它任务上,不会增加 CPU 的负担。所以对 CPU 比较友好。
惰性删除的缺点为:但是对内存并不友好,没有被选中的键也有可能存在过期的,但是未被选中删除一直存在内存中。当存在很多这些过期键一直未被获取和未被选中删除,就会一直在内存中。所以对内存并不友好。
可以在同一个服务器部署多个 Redis 的实例,并把他们当作不同的服务器来使用,在某些时候,无论如何一个服务器是不够的,所以,如果你想使用多个 CPU,你可以考虑一下分片(shard)。
因为内存的空间是有限的,所以 Redis 引入了淘汰机制,主要为了解决在某个时间点,Redis 中存在很多过期键,定期删除策略随机抽查时没有抽查到,并且也没有走惰性删除策略时,大量过期键占用内存的问题。
设置方法为:修改配置文件 redis.conf 的 maxmemory 参数,该参数可以限制最大可用内存。
内存管理方式主要有两个,一是控制内存上限;二是优化回收策略,对内存回收。
我们从题目中分析知道,首页的热门售卖商品知道,访问的数据是热数据,且访问量很大,还需要对当天的热门售卖商品进行排行。
可以使用 zset 数据类型对热数据进行缓存,zset 可以对数据进行排行,把 key 作为商品的 ID,value 作为商品当天销售的数量,可以根据 value 对商品排行。
缓存命中率表示从缓存中读取数据时可以获取到数据的次数,命中率越高代表缓存的使用效率越高,应用的性能越好。
它的公式为:缓存命中率 = 缓存中获取数据次数/获取数据总次数
Redis(即 Remote Dictionary Server 的缩写,中文意思为远程字典服务)是一个使用 ANSI C 语言编写,开源的、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,它提供了多种语言的 API。
它内部支持多种数据结构、数据持久化机制、主从结构、Redis 集群等功能。
Redis 如果 key 相同,后一个 key 会覆盖前一个 key。如果要解决 key 冲突,最好给 key 取好名区分开,可以按业务名和参数区分开取名,避免重复 key 导致的冲突。