笔记-Redis 开发与运维

199 阅读12分钟

初识 Redis

Redis 是一种基于键值对(key-value)的 NoSQL 数据库,由多种数据结构和算法组成。

Redis 特性

1.速度快

  • Redis的所有数据都是存放在内存中的。

  • Redis是用C语言实现的。

  • Redis使用了单线程架构,预防了多线程可能产生的竞争问题。

  • 作者对于Redis源代码精打细磨。

2.基于键值对的数据结构

3.丰富的功能

  • 提供了键过期功能,可以用来实现缓存。

  • 提供了发布订阅功能,可以用来实现消息系统。

  • 支持Lua脚本功能,可以利用Lua创造出新的Redis命令。

  • 提供了简单的事务功能,能在一定程度上保证事务特性。

  • 提供了流水线(Pipeline)功能,这样客户端能将一批命令一次性传到 Redis,减少了网络的开销。

4.简单稳定

5.客户端语言多

  • 几乎涵盖了主流的编程语言

6.持久化

  • 为了避免断电或故障导致内存数据丢失,Redis提供了两种持久化方式:RDB和 AOF,即可以用两种策略将内存的数据保存到硬盘中

7.主从复制

8.高可用和分布式

  • Redis Sentinel,能够保证Redis节点的故障发现和故障自动转移。

  • Redis Cluster,提供了高可用、读写和容量的扩展性。

Redis 的使用场景

Redis 可以做什么

1.缓存

  • 合理地使用缓存不仅可以加快数据的访问速度,而且能够有效地降低后端数据源的压力

2.排行榜系统

  • 合理使用列表和有序集合数据结构可以很方便地构建各种排行榜系统。

3.计数器应用

  • 例如视频网站有播放数、电商网站有浏览数

4.社交网络

  • 社交网站上的赞/踩、粉丝、共同好友/喜好、推送、下拉刷新等功能

5.消息队列系统

  • Redis提供了发布订阅功能和阻塞队列的功能

Redis 不可用做什么

冷数据(即操作或查询不频繁的数据)不适合放在 Redis 中,这是对内存的浪费。

API 的理解和使用

为什么单线程还能这么快?

  • 纯内存访问,内存的响应时长大约为100纳秒

  • 非阻塞I/O,Redis使用epoll作为I/O多路复用技术的实现

  • 单线程,避免了线程切换和竞态产生的消耗

字符串

字符串类型的值实际可以是字符串(简单的字符串、复杂的字符串(例如JSON、XML))、数字(整数、浮点数),甚至是二进制(图片、音频、视频),但是值最大不能超过512MB。

内部编码

字符串类型的内部编码有3种:

  • int:8个字节的长整型。

  • embstr:小于等于39个字节的字符串。

  • raw:大于39个字节的字符串。

Redis会根据当前值的类型和长度决定使用哪种内部编码实现。

典型使用场景

1.缓存功能

  • 加速读写、降低后端压力

2.计数

  • 例如:用户每播放一次视频,相应的视频播放数就会自增1

3.共享Session

  • 以使用Redis将用户的Session进行集中管理,每次用户更新或者查询登录信息都直接从Redis中集中获取

4.限速

  • 限制访问频率

哈希

在Redis中,哈希类型的键值本身又是一个键值对结构。

内部编码

哈希类型的内部编码有两种:

  • ziplist(压缩列表)

元素个数较小时使用,因为ziplist使用更加紧凑的结构实现多个元素的连续存储,相比 hashtable更加节省空间。

  • hashtable(哈希表)

元素个数较多时,ziplist 的读写效率会下降,此时会使用读写时间复杂度为 O(1)的 hashtable。

使用场景

可以用来存用户信息等类似结构,但不适合复杂的关系查询。

列表

用来存储多个有序的字符串,最多可以存储232-1个元素

列表类型有两个特点:

1.列表中的元素是有序的,可以通过索引下标获取某个元素或者某个范围内的元素列表。

2.列表中的元素可以是重复的。

内部编码

  • ziplist(压缩列表)

元素个数较小时使用,因为ziplist使用更加紧凑的结构实现多个元素的连续存储,更加节省空间。

  • linkedlist(链表)

不满足 ziplist 使用条件时,列表通过 linkedlist 实现。

使用场景

1.消息队列

Redis的lpush+brpop命令组合即可实现阻塞队列,生产者客户端使用lrpush从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞式的“抢”列表尾部的元素。

2.文章列表

每个用户有属于自己的有序文章列表,支持按照索引范围获取元素。

其他使用场景

Redis 列表结构操作.png

  • lpush+lpop=Stack(栈)

  • lpush+rpop=Queue(队列)

  • lpush+ltrim=Capped Collection(有限集合)

  • lpush+brpop=Message Queue(消息队列)

集合

用来保存多个的字符串元素,集合中不允许有重复元素,并且集合中的元素是无序的,还支持多个集合取交集、并集、差集。

内部编码

集合类型的内部编码有两种:

  • intset(整数集合)

当集合中的元素都是整数且元素个数小于set-max-intset-entries配置(默认512个)时,Redis会选用intset来作为集合的内部实现,从而减少内存的使用。

  • hashtable(哈希表)

当集合类型无法满足intset的条件时,Redis会使用hashtable作为集合的内部实现。

使用场景

1.标签(tag)

例如一个用户可能对娱乐、体育比较感兴趣,另一个用户可能对历史、新闻比较感兴趣,这些兴趣点就是标签。

有序集合

不能重复,元素有序。有序集合提供了获取指定分数和元素范围查询、计算成员排名等功能。

有序集合中的元素不能重复,但是score可以重复,就像一个班里的同学学号不能重复,但是考试成绩可以相同。

内部编码

有序集合类型的内部编码有两种:

  • ziplist(压缩列表):当有序集合的元素个数小于默认配置(128个),同时每个元素的值都小于默认配置(64字节)时,Redis会用 ziplist来作为有序集合的内部实现,ziplist 可以有效减少内存的使用。

  • skiplist(跳跃表):当ziplist条件不满足时,有序集合会使用skiplist作为内部实现,因为此时ziplist的读写效率会下降。

使用场景

1.排行榜系统

  • 添加用户赞数

zadd和zincrby命令

  • 取消用户赞数

zrem命令

  • 展示获取赞数最多的十个用户

zrevrange命令

  • 展示用户信息以及用户分数

zscore和zrank命令

为什么一种数据结构对应多种编码方式?

通过不同编码实现效率和空间的平衡。比如存储只有10个元素的列表,当使用双向链表数据结构时,必然需要维护大量的内部字段如每个元素需要:前置指针,后置指针,数据指针等,造成空间浪费,如果采用连续内存结构的压缩列表(ziplist),将会节省大量内存,而由于数据长度较小,存取操作时间复杂度即使为O(n2)性能也可以满足需求。

遍历键

1.全量遍历键

keys* 代表匹配任意字符

如果Redis包含了大量的键,执行keys命令很可能会造成Redis阻塞。

2.渐进式遍历

scan采用渐进式遍历的方式来解决keys命令可能带来的阻塞问题,每次scan命令的时间复杂度是 O(1),但是要真正实现keys的功能,需要执行多次scan。

每次执行scan,只扫描一个字典中的一部分键,直到将字典中的所有键遍历完毕。第一次遍历从0开始,每次scan遍历完都会返回当前游标的值,直到游标值为0,表示遍历结束。

如果在scan的过程中如果有键的变化(增加、删除、修改),那么可能会出现新增的键没有遍历到,遍历出了重复的键等情况,也就是说scan并不能保证完整的遍历出来所有的键。

持久化

Redis支持RDB和AOF两种持久化机制,持久化功能有效地避免因进程退出造成的数据丢失问题,当下次重启时利用之前持久化的文件即可实现数据恢复。

RDB持久化

把当前进程数据生成快照保存到硬盘的过程。

触发机制

1.自动触发

2.手动触发

  • save命令 阻塞当前Redis服务器,直到RDB过程完成为止。

  • bgsave命令

Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短。

bgsave的运作流程:

1.执行bgsave命令,Redis父进程判断当前是否存在正在执行的子进程,如RDB/AOF子进程,如果存在bgsave命令直接返回。

2.父进程执行fork操作创建子进程,fork操作过程中父进程会阻塞。

3.父进程fork完成后,bgsave命令返回“Background saving started”信息 并不再阻塞父进程,可以继续响应其他命令。

4.子进程创建RDB文件,根据父进程内存生成临时快照文件,完成后对原有文件进行原子替换。

5.进程发送信号给父进程表示完成,父进程更新统计信息。

RDB的优缺点

RDB的优点:

  • RDB是一个紧凑压缩的二进制文件,代表Redis在某个时间点上的数据快照。非常适用于备份,全量复制等场景。

  • 加载RDB恢复数据远远快于AOF的方式。

RDB的缺点:

  • RDB方式数据没办法做到实时持久化/秒级持久化。因为bgsave每次运行都要执行fork操作创建子进程,属于重量级操作,频繁执行成本过高。

  • RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个格式的RDB版本,存在老版本Redis服务无法兼容新版RDB格式的问题。

AOF持久化

以追加日志的方式实时记录每次写命令,重启时再重新执行AOF文件中的命令达到恢复数据的目的。

AOF 执行步骤

1)所有的写入命令会追加到aof_buf(缓冲区)中。

2)AOF缓冲区根据对应的策略向硬盘做同步操作。

- always: 每次写都落盘
- everysec: 由专门线程每秒落盘一次
- no: 由操作系统落盘,最长 30 秒同步一次

3)随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩文件的目的。

4)当Redis服务器重启时,可以加载AOF文件进行数据恢复。

重写后的AOF文件为什么可以变小?有如下原因:

1)进程内已经超时的数据不再写入文件。

2)旧的AOF文件含有无效命令,重写时使用进程内数据直接生成,这样新的AOF文件只保留最终数据的写入命令。

3)多条写命令可以合并为一个。

重启加载

AOF和RDB文件都可以用于服务器重启时的数据恢复。

1)AOF持久化开启且存在AOF文件时,优先加载AOF文件

2)AOF关闭或者AOF文件不存在时,加载RDB文件

3)加载AOF/RDB文件成功后,Redis启动成功。

4)AOF/RDB文件存在错误时,Redis启动失败并打印错误信息。

内存管理

内存回收策略

1.删除过期键对象

  • 惰性删除

当客户端读取带有超时属性的键时,如果已经超过键设置的过期时间,会执行删除操作并返回空。

当过期键一直没有访问将无法及时删除,从而导致内存不能及时释放。

  • 定时任务删除

Redis内部维护一个定时任务,默认每秒运行10次。定时任务中删除过期键逻辑采用了自适应算法,根据键的过期比例、使用快慢两种速率模式进行回收。

流程说明:

1)定时任务在每个数据库空间随机检查20个键,当发现过期时删除对应的键。

2)如果超过检查数25%的键过期,循环执行回收逻辑直到不足25%或运行超时为止,慢模式下超时时间为25毫秒。

3)如果之前回收键逻辑超时,则在Redis触发内部事件之前再次以快模式运行回收过期键任务,快模式下超时时间为1毫秒且2秒内只能运行1次。

4)快慢两种模式内部删除逻辑相同,只是执行的超时时间不同。

内存溢出控制策略

当Redis所用内存达到maxmemory上限时会触发相应的溢出控制策略。

1)noeviction:默认策略,不会删除任何数据,拒绝所有写入操作并返回客户端错误信息。

2)volatile-lru:根据LRU算法删除设置了超时属性(expire)的键,直到腾出足够空间为止。

3)allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。

4)allkeys-random:随机删除所有键,直到腾出足够空间为止。

5)volatile-random:随机删除过期键,直到腾出足够空间为止。

6)volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。

哨兵

集群

未完待续。。。