Loading... ### 认识Redis --- #### 什么是Redis Redis 是一种基于内存的数据库,对数据的读写操作都是在内存中完成,因此 **读写速度非常快** ,常用于 **缓存,消息队列、分布式锁** 等场景 。 Redis 提供了多种数据类型来支持不同的业务场景,比如 String(字符串)、Hash(哈希)、 List (列表)、Set(集合)、Zset(有序集合)、Bitmaps(位图)、HyperLogLog(基数统计)、GEO(地理信息)、Stream(流),并且对数据类型的操作都是**原子性**的,因为执行命令由单线程负责的,不存在并发竞争的问题。 除此之外,Redis 还支持<font color=blue>**事务 、持久化、Lua 脚本、多种集群方案(主从复制模式、哨兵模式、切片机群模式)、发布/订阅模式,内存淘汰机制、过期删除机制**</font>等等。 #### 本地连接远程redis - 修改redis.conf,注释掉 bind 绑定的所有ip,包括 bind:127.0.0.1 - 修改redis.conf中protected-mode 为 NO - 执行以下命令,开放防火墙端口 ```bash - 开放redis的6379端口【假设redis端口为6379】 firewall-cmd --zone=public --add-port=6379/tcp --permanent - 重启防火墙使得配置生效 systemctl restart firewalld - 查看系统所有开放的端口 firewall-cmd --zone=public --list-ports ``` #### Redis 和 Memcached 有什么区别? - Redis 与 Memcached <font color=blue>**共同点**</font>: 1. 都是基于内存的数据库,一般都是用来当作缓存使用 2. 都有过期策略 3. 两者的性能都非常高 - Redis 与 Memcached <font color=blue>**区别**</font>: 1. Redis 支持的数据类型丰富(String、Hash、List、Set、Zset),而 Memcached 只支持最简单的 key-value 数据类型 2. Redis 支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用,而 Memcached 没有持久化功能,数据全部存在内存之中,Memcached 重启或者挂掉后,数据就没了 3. Redis 原生支持集群模式,Memcached 没有原生的集群模式,需要依靠客户端往集群中分片写入数据 4. Redis 支持发布订阅模型、Lua脚本、事务等功能,而 Memcached 不支持 #### 为什么 Redis 作为 MySQL 的缓存 主要是因为 Redis 具备 <font color=blue>**高性能** 和 **高并发** </font>两种特性 1. <font color=blue>*Redis 具备高性能*</font> 假如用户第一次访问 MySQL 中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据缓存在 Redis 中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了,操作 Redis 缓存就是直接操作内存,所以速度相当快。 ![redis-mysql.png](https://qiniu.program-er.com/blog/typecho/usr/uploads/redis-mysql.png) 2. <font color=blue>*Redis 具备高并发*</font> 单台设备的 Redis 的QPS(Query Per Second,每秒钟处理完请求的次数)是MySQL的 10 倍,Redis 单机的 QPS 能轻松破 10w,而 MySQL 单机的 QPS 很难破 1w。 所以,直接访问 Redis 能够承受的请求是远远大于直接访问 MySQL 的,所以我们可以考虑把数据库中的部分数据转移到缓存中区,这样用户的一部分请求会直接到缓存这里而不用经过数据库,降低数据库的压力。 ### Redis 数据结构 --- #### Redis 数据类型以及使用场景分别是什么? Redis 提供了丰富的数据类型,常见的五种数据类型:<font color=blue>String(字符串),Hash(哈希),List(列表),Set(集合),Zset(有序集合)</font>。 ![redis-data-type.png](https://qiniu.program-er.com/blog/typecho/usr/uploads/redis-data-type.png) | 结构类型| 结构存储的值 | 结构的读写能力| | --- | --- | --- | | String 字符串 | 可以是字符串、整数或浮点数 | 对整个字符串或字符串的一部分进行操作;对整数或浮点进行自增或自增操作 | | List 列表 | 一个链表,链表上的每个节点都包含一个字符串 | 对链表的两端进行push和pop操作,读取单个或多个元素;根据值查找或删除元素 | | Set 集合 | 包含字符串的无序集合 | 字符串的集合,包含基础的方法有看是否存在添加、获取、删除;还包含计算交集、并集、差集等 | | Hash散列 | 包含键值对的无序散列表 | 包含方法有添加、获取、删除单个元素 | | Zset有序集合 | 和散列一样,用于存储键值对 | 字符串成员与浮点数分数之间的有序映射;元素的排序顺序由分数的大小决定;包含方法有添加、获取、删除单个元素以及根据分值范围或成员来获取元素 | 随着 Redis 版本的更新,后面又支持了四种数据类型:<font color=blue>BitMap(2.2 版新增)、HyperLogLog(2.8新增)、GEO(3.2新增)、Stream(5.0新增)</font>。 Redis 五种数据类型的应用场景: - String 类型的应用场景:缓存对象、分布式锁、常规计数、共享 session 信息等 - List 类型的应用场景:消息队列 (但是有两个问题:1. 生产者需要自行实现全局唯一ID;2. 不能以消费形式消费数据)等 - Hash 类型:缓存对象、购物车等 - Set 类型:聚合计算(并集、交集、差集)场景,比如点赞、共同关注、抽奖活动等 - Zset 类型:排序场景,比如排行榜、电话和姓名排序等 Redis 后续版本又支持四种数据类型,它们的应用场景如下: - BitMap:二值状态统计的场景,比如签到、判断用户登陆状态、连续签到用户总数等 - HyperLogLog:海量数据基数统计的场景,比如百万级网页UV计数等 - GEO:存储地理位置信息的场景,比如滴滴叫车 - Stream:消息队列,相比于基于 List 类型实现的消息队列,有这两个特有的特性: - 自动生成全局唯一消息 ID - 支持以消费组形式消费数据 #### 五种常见的 Redis 数据类型是怎么实现? ![redis数据类型与数据结构的关系.png](https://qiniu.program-er.com/blog/typecho/usr/uploads/redis数据类型与数据结构的关系.png) <div class="tip inlineBlock info"> String类型内部实现 </div> String 类型的底层数据结构主要是 SDS (简单动态字符串)。SDS 和我们认识的 C 字符串不太一样,之所以没有使用 C 语言的字符串表示,因为 SDS 相比于 C 的原生字符串: - <font color=blue>**SDS 不仅可以保存文本数据,还可以保存二进制数据**</font>。因为 SDS 使用 len 属性的值而不是空字符来判断字符串是否结束,并且 SDS 的所有 API 都会以处理二进制的方式来处理 SDS 存放在 buff[] 数组里的数据。所以 SDS 不光能存放文本数据,而且能保存图片、音频、视频、压缩文件这样的二进制数据 - <font color=blue>**SDS 获取字符串长度的时间复杂度是 o(1)**</font>。因为 C 语言的字符串并不记录自身长度,所以获取长度的复杂度为 O(n);而 SDS 结构里用 len 属性记录了字符串长度,所以复杂度为 O(1)。 - <font color=blue> **Redis 的 SDS API 是安全的,拼接字符串不会造成缓冲区溢出**</font>。因为 SDS 在拼接字符串之前会检查 SDS 空间是否满足要求,如果空间不够会自动扩容,所以不会导致缓冲区溢出的问题。 <div class="tip inlineBlock info"> List类型内部实现 </div> List 类型的底层数据结构是由<font color=blue>**双向链表或压缩列表**</font>实现的: - 如果列表的元素小于 512 个(默认值,可由 list-max-ziplist-entries 配置),列表每个元素的值都小于 64 字节(默认值,可有 list-max-ziplist-value 配置),Redis 会使用<font color=blue>**压缩列表**</font>作为 List 类型的底层数据结构 - 如果列表的元素不满足上面的条件,Redis 会使用 <font color=blue>**双向列表**</font>作为 List 类型的底层数据结构 但是<font color=blue>**在 Redis 3.2 版本之后,List 数据类型底层数据结构就只由 quicklist 实现了,替代了双向列表和压缩列表**</font> <div class="tip inlineBlock info"> Hash类型内部实现 </div> Hash 类型的底层数据结构是由 <font color=blue>**压缩列表或哈希表**</font>实现的: - 如果哈希类型元素个数小于 512 个(默认值,可有 hash-max-ziplist-entries 配置),所有值小于 64 字节(默认值,可由 hash-max-ziplist-value 配置)的话,Redis 会使用<font color=blue>**压缩列表**</font>作为 Hash 类型的底层数据结构 - 如果哈希类型元素不满足上面条件,Redis 会使用 <font color=blue>**哈希表**</font>作为 Hash 类型的底层数据结构 <font color=blue>**在 Redis 7.0 中,压缩列表数据结构已经废弃了,交由 listpack 数据结构来实现了**</font> <div class="tip inlineBlock info"> Set 类型内部实现 </div> Set 类型的底层数据结构是由<font color=blue>**哈希表或整数集合**</font>实现的: - 如果集合中的元素都是整数,且元素个数小于 512 (默认值,set-maxintset-entries配置)个,Redis 会使用<font color=blue>**整数集合**</font>作为 Set 类型的底层数据结构 - 如果集合中的元素不满足上面的条件,则 Redis 使用<font color=blue>**哈希表**</font>作为 Set 类型的底层数据结构 <div class="tip inlineBlock info"> ZSet 类型内部实现 </div> ZSet 类型的底层数据结构是由<font color=blue>**压缩列表或跳表**</font>实现的: - 如果有序结合的元素个数小于 128 个,并且每个元素的值小于 64 字节时,Redis 会使用<font color=blue>**压缩列表**</font>作为 ZSet 类型的底层数据结构 - 如果有序结合的元素不满足上面的条件,Redis 会使用<font color=blue>**跳表**</font>作为 ZSet 类型的底层数据结构 <font color=blue>在 Redis 7.0中,压缩列表数据结构已经废弃了,交由 listpack 数据结构来实现了。</font> ### Redis 线程模型 --- #### Redis 是单线程吗 <font color=blue>**Redis 单线程指的是「接收客户端请求->解析请求->进行数据读写等操作->发送数据给客户端」这个过程是由一个线程(主线程)来完成的**</font>,这也是我们常说 Redis 是单线程的原因。 但是,<font color=blue>**Redis 并不是单线程的**</font>,Redis 在启动的时候,是会<font color=blue>**启动后台线程**</font>(BIO)的: - <font color=blue>**Redis 在2.6版本**</font>,会启动 2 个后台线程,分别处理关闭文件、AOF刷盘这两个人 - <font color=blue>**Redis 在 4.0 版本之后**</font>,新增了一个新的后台线程,用来异步释放 Redis 内存,也就是 lazyfree 线程。例如执行 unlink key /flushdb async / flushall async 等命令,会把这些删除操作交给后台线程来执行,好处是不会导致 Redis 主线程卡顿。因此,当我们要删除一个大 key 的时候,不要使用 del 命令删除,因为 del 是在主线程处理的,这样会导致 Redis 主线程卡顿,因此我们应该是 unlink 命令来删除大 key 之所以 Redis 为「关闭文件、AOF 刷盘、释放内存」这些任务创建单独的线程来处理,是因为这些任务的操作都是很耗时的,如果把这些任务都放在主线程来处理,那么 Redis 主线程就很容易发生阻塞,这样就无法处理后续的请求了 后台线程相当于一个消费者,生产者把好事任务丢到任务队列中,消费者(BIO)不停轮询这个队列,拿出任务就去执行对应的方法即可 ![redis后台线程.png](https://qiniu.program-er.com/blog/typecho/usr/uploads/redis后台线程.png) 关闭文件、AOF 刷盘、释放内存这三个任务都有各自的任务队列: - BIO_CLOSE_FILE,关闭文件任务队列:当队列有任务后,后台线程会调用 close(fd),将文件关闭 - BIO_AOF_FSYNC,AOF 刷盘任务队列:当 AOF 日志配置成 everysec 选项后,主线程会把 AOF 写日志操作封装成一个任务,也放到队列中。当发现队列有任务后,后台线程会调用 faync(fd),将 AOF 文件刷盘; - BIO_LAZY_FREE, 最后修改:2023 年 12 月 17 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 0 感谢赏赐的coffee~