一、了解 Redis
Redis 官网:Redis - The Real-time Data Platform
Redis是一种基于键值对 (key-value)的 NoSQL 数据库 。与很多键值对数据库不同的是,Redis 中的 key 都是 string(字符串),值(value)可以是由 string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)、Bitmaps(位图)、HyperLogLog、GEO(地理信息定位)等多种数据结构和算法组成。因此,Redis 可以满足很多的应用场景,而且 Redis 会将所有数据都存放在内存中,所以它的读写性能非常惊人。
不仅如此,Redis 还可以将内存的数据利用快照和日志的形式保存到硬盘上,这样在发生类似断电或者机器故障的时候,内存中的数据不会 "丢失"。除了上述功能以外,Redis 还提供了键过期、发布订阅、事务、流水线、Lua 脚本等附加功能。
【了解】
2008 年,Redis 的作者 Salvatore Sanfilippo(List of posts - <antirez>) 在开发⼀个叫 LLOOGG 的网站时,需要实现⼀个高性能的队列功能,最开始是使用 MySQL 来实现的,但后来发现无论怎么优化 SQL 语句等都不能使网站的性能提高上去,再加上自己囊中羞涩,于是他决定自己做⼀个专属于 LLOOGG 的数据库,这个就是 Redis 的前身。后来,Salvatore Sanfilippo 将 Redis 1.0 的源码发布到 Github 上,可能连他自己都没想到,Redis 后来如此受欢迎。
假如现在有人问 Redis 的作者都有谁在使用 Redis,我想他可以开句玩笑的回答:还有谁不使用 Redis,当然这只是开玩笑,但是从 Redis 的官方公司统计来看,有很多重量级的公司都在使用 Redis,如国外的 Twitter、Instagram、Stack Overflow、Github 等,国内就更多了,如果单单从体量来统计,新浪微博可以说是全球最大的 Redis 使用者,除了新浪微博,还有像阿里巴巴、腾讯、搜狐、优酷土豆、美团、小米、唯品会等公司都是 Redis 的使用者。除此之外,许多开源技术像 ELK 等已经把 Redis 作为它们组件中的重要一环,而且 Redis 还提供了模块系统让第三方人员实现功能扩展,让 Redis 发挥出更大的威力。所以,可以这么说,熟练使用和运维 Redis 已经成为开发运维人员的一个必备技能。
二、Redis 的特性
1、速度快
正常情况下,Redis 执行命令的速度非常快(相较于 MySQL 这样的关系型数据库),官方给出的数字是读写性能可以达到 10 万 / 秒,当然这也取决于机器的性能,但这里先不讨论机器性能上的差异,只分析一下是什么造就了 Redis 如此之快,可以大概归纳为以下四点:
-
Redis 的所有数据都是存放在内存中的(通过一系列的数据结构(主要是键值对)进行存储),下表是谷歌公司 2009 年给出的各层级硬件执行速度,所以把数据放在内存中是 Redis 速度快的最主要原因。
-
Redis 是用 C 语言实现的,一般来说 C 语言实现的程序 "距离" 操作系统更近,执行速度相对会更快。(网上绝大部分资料这么说,但我不是很赞同,因为 MySQL 也是 C 语言开发的)
-
Redis 使用了单线程,预防了多线程可能产生的竞争问题,减少了不必要的线程之间的竞争开销。Redis 在 6.0 版本引入了多线程机制,但主要也是在处理网络和 IO,不涉及到数据命令,即命令的执行仍然采用了单线程模式。(对比:多线程提高效率的前提:CPU 密集型的任务,使用多个线程可以充分利用 CPU 多核资源。但是 Redis 的核心任务是操作内存的数据结构,不会吃很多 CPU。如果此时选择多线程,多核资源也用不上,因为单个核心的处理速度已经很快了,不仅没有明显的提升,同时还要考虑线程安全、加锁(一旦竞争就会阻塞,阻塞又会涉及到什么时候唤醒))
-
作者对于 Redis 源代码可以说是精打细磨,曾经有人评价 Redis 是少有的集性能和优雅于一身的开源代码。
从网络的角度上,Redis 使用了 IO 多路复用的方式(epoll),也就是使用一个线程管理多个 socket。
谷歌公司给出的各层级硬件执行速度:
2、基于键值对的数据结构服务器
几乎所有的编程语言都提供了类似字典的功能,例如 C++ 里的 map、Java 里的 map、Python 里的 dict 等,类似于这种组织数据的方式叫做基于键值对的方式。
与很多键值对数据库不同的是,Redis 中的值不仅可以是字符串,而且还可以是具体的数据结构,这样不仅能便于在许多应用场景的开发,同时也能提高开发效率。Redis 的全程是 REmote Dictionary Server,它主要提供了 5 种数据结构:字符串(string)、哈希(hash)、列表(list)、集合(set)、有序集合(ordered set / zet) ,同时在字符串的基础之上演变出了位图(Bitmaps)和 HyperLogLog 两种神奇的 "数据结构",并且随着 LBS(Location Based Service,基于位置服务)的不断发展,Redis 3.2. 版本种加入有关 GEO(地理信息定位)的功能,总之在这些数据结构的帮助下,开发者可以开发出各种 "有意思" 的应用。
3、丰富的功能
除了 5 种数据结构,Redis 还提供了许多额外的功能:
- 提供了键过期 功能,可以用来实现缓存。
- 提供了发布订阅 功能,可以用来实现消息系统。
- 支持 Lua 脚本 功能,可以利用 Lua 创造出新的 Redis 命令。
- 提供了简单的事务功能 ,能在⼀定程度上保证事务特性。
- 提供了流水线(Pipeline) 功能,这样客户端能将一批命令一次性传到 Redis,减少了网络的开销。
4、简单稳定
Redis 的简单主要表现在三个方面:
- 首先,Redis 的源码很少,早期版本的代码只有 2 万行左右,3.0 版本以后由于添加了集群特性,代码增至 5 万行左右,相对于很多 NoSQL 数据库来说代码量相对要少很多,也就意味着普通的开发和运维⼈员完全可以 "吃透" 它。
- 其次,Redis 使用单线程模型,这样不仅使得 Redis 服务端处理模型变得简单,而且也使得客户端开发变得简单。
- 最后,Redis 不需要依赖于操作系统中的类库(例如 Memcache 需要依赖 libevent 这样的系统类库),Redis 自己实现了事件处理的相关功能。
但与简单相对的是 Redis 具备相当的稳定性,在大量使用过程中,很少出现因为 Redis 自身 BUG 而导致宕掉的情况。
5、客户端语言多(扩展能力,Extensibility)
Redis提供了简单的 TCP 通信协议,很多编程语言可以很方便地接入到 Redis ,并且由于 Redis 受到社区和各大公司的广泛认可,所以支持 Redis 的客户端语言也非常多,几乎涵盖了主流的编程语言,例如:C、C++、Java、PHP、Python、NodeJS 等,后续我会对 Redis 的客户端使用做详细说明。
可以在 Redis 原有的功能基础上再进行扩展,Redis 提供了一组 API。通过 C,C++,Rust 这几个语言编写 Redis 扩展(本质上就是一个动态链接库),让 Redis 支持更多的数据结构,以及支持更多的命令。
6、持久化(Persistence)
为了更快速的访问,Redis 把数据存储在内存上。通常来看,将数据放在内存中是不安全的(易失),一旦发生断电或者机器故障,重要的数据可能就会丢失,因此 Redis 提供了两种持久化方式:
- RDB
- AOF
即我们可以用两种策略将内存的数据保存到硬盘中(内存为主、硬盘为辅,硬盘相当于对内存中的数据进行了备份),这样就保证了数据的可持久性。如果 Redis 重启,那么就会在重启时加载硬盘中的备份数据,使 Redis 的内存恢复到重启前的状态。
Redis 内存到硬盘的持久化:
7、主从复制(Replication)
Redis 提供了复制功能,**实现了多个相同数据的 Redis 副本(Replica),**复制功能是分布式 Redis 的基础。
Redis 主从复制架构:
8、高可用(High Availability)和分布式(Distributed)
Redis 提供了高可用实现的 Redis 哨兵(Redis Sentinel),能够保证 Redis 结点的故障发现和故障自动转移。也提供了 Redis 集群(Redis Cluster),是真正的分布式实现,提供了高可用、读写和容量的扩展性。
Redis 自身也是支持 "主从" 结构的,从节点就相当于主节点的备份。
9、可编程的 / 编程能力(Programmability)
针对 Redis 的操作,可以直接通过简单的交互式命令进行操作,也可以通过一些脚本的方式,批量执行一些操作(可以带有一些逻辑)。
10、集群(Clustering)
Redis 作为一个分布式系统的中间件,能够支持集群是非常关键的。
这个水平扩展类似于 "分库分表"。一个 Redis 能够存储的数据是有限的(内存空间有限)。如果想存储更多的数据,那就需要引入多个主机,部署多个 Redis 节点,每个 Redis 存储数据的一部分。
三、Redis 的主要应用场景
学习以下内容之前可以先了解学习:服务端高并发分布式结构演进之路-CSDN博客
1、Redis 可以做什么
(1)缓存(Cache)& 会话(Session)
缓存机制几乎在所有大型网站都有使用,合理地使用缓存不仅可以加速数据的访问速度 ,而且能够有效地降低后端数据源的压力。Redis 提供了键值过期时间设置 ,并且也提供了灵活控制最大内存和内存溢出后的淘汰策略。可以这么说,一个合理的缓存设计能够为一个网站的稳定保驾护航。
在 Linux 的学习中,我们知道了 cookie 可以实现用户身份信息的保存(只是在浏览器这边存储了用户的身份标识 sessionId),同时这是需要 session 配合(服务器这时才真正的存储了用户数据)。
之前 session 是存储在应用服务器上的,现在变成了分布式系统,引入了负载均衡:
如何解决上述问题?
- 想办法让负载均衡器把同一个用户的请求始终打到同一个机器上(不能再采用轮询了,而是要通过 userId 之类的方式来分配机器)。
- 把会话数据单独拎出来,放到一组独立的机器上进行存储(Redis,把应用程序重启了,会话也不会丢失,就算丢失了大不了让用户重新进行登录)。
(2)排行榜系统
排行榜系统几乎存在于所有的网站,例如按照热度排名的排行榜,按照发布时间的排行榜,按照
各种复杂维度计算出的排行榜。Redis 提供了列表 和有序集合的结构,合理地使用这些数据结构可以很方便地构建各种排行榜系统。
(3)计数器应用
计数器在网站中的作用至关重要。
例如:视频网站有播放数、电商网站有浏览数,为了保证数据的实时性,每一次播放和浏览都要做加 1 的操作,如果并发量很大对于传统关系型数据的性能是一种挑战。Redis 天然支持计数功能,而且计数的性能也非常好,可以说是计数器系统的重要选择。
(4)社交网络
赞 / 踩、粉丝、共同好友 / 喜好、推送、下拉刷新 等是社交网站的必备功能,由于社交网站访问量通常比较大 ,而且传统的关系型数据不太合适保存这种类型的数据,Redis 提供的数据结构可以相对比较容易地实现这些功能。
(5)消息队列(服务器)系统(Streaming & Messaging)
消息队列系统可以说是⼀个大型网站的必备基础组件,因为其具有业务解耦、非实时业务削峰等特性。基于这个可以实现一个网络版本的生产者消费者模型。对于分布式系统来说,服务器和服务器之间有时候也需要使用到生产者消费者模型。优势如下:
- 解耦合
- 削峰填谷
业界也有很多知名的消息队列,例如:RabbitMQ、Kafka、RocketMQ...
Redis 提供了发布订阅功能和阻塞队列的功能,虽然和专业的消息队列比还不够足够强大,但是对于一般的消息队列功能基本可以满足。如果当前场景中,对于消息队列的功能以来不是很多,并且又不想引入额外的依赖,那么 Redis 可以作为一个选择。
2、Redis 不可以做什么
实际上和任何一门技术⼀样,每个技术都有自己的应用场景和边界,也就是说 Redis 并不是万金油,有很多合适它解决的问题,但是也有很多不合适它解决的问题。我们可以站在数据规模和数据冷热的角度来进行分析。
站在数据规模的角度看,数据可以分为大规模数据和小规模数据,我们知道 Redis 的数据是存放在内存中的,虽然现在内存已经足够便宜,但是如果数据量非常大,例如每天有几亿的用户行为数据,使用 Redis 来存储的话,基本上是个无底洞,经济成本相当高。
站在数据冷热的角度,数据分为热数据和冷数据,热数据通常是指需要频繁操作的数据,反之为冷数据,例如对于视频网站来说,视频基本信息基本上在各个业务线都是经常要操作的数据,而用户的观看记录不⼀定是经常需要访问的数据。这里暂且不讨论两者数据规模的差异,单纯站在数据冷热的角度上看,视频信息属于热数据,用户观看记录属于冷数据。如果将这些冷数据放在 Redis 上,基本上是对于内存的⼀种浪费,但是对于一些热数据可以放在 Redis 中加速读写,也可以减轻后端存储的负载,可以说是事半功倍。
将热数据放在 Redis 中存储,而将冷数据用 MySQL 来存储。很多领域都涉及到 "二八原则":20% 的热数据能满足 80% 访问需求。(系统的复杂程度大大提升,而且如果数据发生修改,还会涉及到 Redis 和 MySQL 之间的数据同步问题)
四、Redis 重大版本
Redis 借鉴了 Linux 操作系统对于版本号的命名规则:版本号第二位如果是奇数,则为非稳定版本(例如:2.7、2.9、3.1);如果是偶数,则为稳定版本(例如:2.6、2.8、3.0、3.2)。当前奇数版本就是下一个稳定版本的开发版本,例如 2.9 版本是 3.0 版本的开发版本。所以,我们生产环境通常选取偶数版本的 Redis,如果对于某些新的特性想提前了解和使用,可以选择最新的奇数版本。
1、Redis 2.6
Redis 2.6 在 2012 年正式发布,相比于 Redis 2.4,主要特性如下:
-
服务端支持 Lua 脚本。
-
去掉虚拟内存相关功能。
-
放开对客户端连接数的硬编码限制。
-
键的过期时间支持毫秒。
-
从结点提供只读功能。
-
两个新的位图命令:bitcount 和 bitop。
-
增强了 redis-benchmark 的功能:支持定制化的压测、CSV 格式输出等功能。
-
基于浮点数自增命令:incrbyfloat 和 hincrbyfloat。
-
redis-cli 可以使用 --eval 参数实现 Lua 脚本执行。
-
shutdown 命令增强。
-
Info 可以按照 setction 输出,并且添加了一些统计项。
-
重构了大量的核心代码,所有集群相关的代码都去掉了,会在 3.0 支持 cluster 功能。
-
sort 命令优化。
2、Redis 2.8
Redis 2.8 在 2013 年正式发布,相比于 Redis 2.6,主要特性如下:
- 添加部分主从复制的功能,在⼀定程度上降低了由于网络问题,造成频繁全量复制生成 RDB 对系统造成的压力。
- 尝试性地支持 IPv6。
- 可以通过 config set 命令设置 maxclients。
- 可以用 bind 命令绑定多个 IP 地址。
- Redis 设置了明显的进程名,方便使用 ps 命令查看系统进程。
- config rewrite 命令可以将 config set 持久化到 Redis 配置文件中。
- 发布订阅添加了 pubsub 命令。
- Redis Sentinel 第二版,相比于 Redis 2.6 的 Redis Sentinel,此版本已经变成生产可用。
3、Redis 3.0
Redis 3.0 在 2015 年正式发布,相比于 Redis 2.8,主要特性如下:
- Redis Cluster:Redis 提供的官方分布式实现。
- 全新的 embedded string 对象编码结果,优化了小对象内存访问,在特定的工作负载时,下载速度大幅提高。
- 优化了 LRU 算法,大幅提供性能。
- migrate 链接缓存,大幅提供键迁移的速度。
- migrate 命令新增两个参数:copy 和 replace。
- client pause 命令,在指定时间内中止处理客户端请求。
- bitcount 命令性能提高。
- config set 设置 maxmemory 时候能够设置不⼀样的单位(以前只能是字节)。
- Redis 日志小作调整:日志中会反应当前实例的角色(master 或者 slave)。
- incr命令性能提⾼。
4、Redis 3.2
Redis 3.2 在 2016 年正式发布,相比于 Redis 3.0,主要特性如下:
- 添加 GEO 相关功能。
- SDS 在速度和节省空间上都作了优化。
- 支持用 upstart 或者 systemd 管理 Redis 进程。
- 新的 List 编码类型:quicklist。
- 从节点读取过时数据保证⼀致性。
- 添加了 hstrlen 命令。
- 加强了 debug 命令,⽀持了更多的参数。
- Lua 脚本功能加强。
- 添加了 Lua Debugger。
- config set 支持更多的配置参数。
- 优化了 Redis 崩溃后的相关报告。
- 新的 RDB 格式,可是仍然兼容旧的 RDB。
- 加速 RDB 的加载速度。
- spop 命令支持个数参数。
- cluster nodes 命令获得加速。
- Jemalloc 更新到 4.0.3 版本。
5、Redis 4.0
Redis 4.0 在 2017 年正式发布,相比于 Redis 3.2,主要特性如下:
- 提供了模块系统(module),方便第三方开发者拓展 Redis 的功能。
- PSYNC 2.0:优化了以前版本中,主从节点切换必然引发全量复制的问题。
- 提供了新的缓存剔除算法:LFU(Last Frequently Used),注意 LFU 和 LRU 算法的不同之处,LRU 的淘汰规则是基于访问时间,而 LFU 是基于访问次数的,并对已有算法进行了优化。
- 提供了非阻塞 del 和 flushall / flushdb 功能,新添加了 unlink 命令, 这个命令是 del 命令的异步版本, 它可以将删除指定键的操作放在后台线程里面执⾏。
- 提供了 memory 命令,实现对内存更为全面的监控统计。
- 提供了交互数据库功能,实现 Redis 内部数据库的数据置换。
- 提供了 RDB-AOF 混合持久化格式,充分利用了 AOF 和 RDB 各自优点。
- Redis Cluster 兼容 NAT 和 docker 。
6、Redis 5.0
Redis 5.0 在 2018 年正式发布,相比于 Redis 4.0,主要特性如下:
- 新的流数据类型(stream)。
- 新的 Redis 模块 API:定时器、集群和字典 API。
- RDB 现在可存储 LFU 和 LRU 信息。
- redis-cli 中的集群管理器从 Ruby(redis-trib.rb)移植到了 C 语言代码。执行 `redis-cli --cluster help` 命令以了解更多信息。
- 新的有序集合(sorted set)命令:zpopmin / zpopmax 和阻塞变体(blocking variants)。
- 升级 Active defragmentation 至 v2 版本。
- 增强 HyperLogLog 的实现。
- 更好的内存统计报告。
- 许多包含子命令的命令现在都有一个 help 子命令。
- 客户端频繁连接和断开连接时,性能表现更好。
- 许多错误修复和其他方面的改进。
- 升级 Jemalloc 至 5.1 版本。
- 引入 client unblock 和 client id。
- 新增 lolwut 命令。
- 在不存在需要保持向后兼容性的地方,弃用 "slave" 术语。
- 网层中的差异优化。
- 增强对 Lua 的支持:将 Lua 脚本更好地传播到 replicas / AOF、Lua 脚本现在可以超时并在副本中进⼊ -BUSY 状态。
- 引入动态的 HZ(Dynamic HZ)以平衡空闲 CPU 使用率和响应性。
- 对 Redis 核心代码进行了重构并在许多方面进行了改进。
7、Redis 6.0
Redis 6.0 在 2020 年正式发布,相比于 Redis 5.0,主要特性如下:
-
Redis 6.0 引入多线程 IO,但多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程。
-
实现了client-side-caching(客户端缓存)功能。放弃了caching slot,而只使用 key names。
-
Redis 6.0 开始在兼容 RESP 2 的基础上,开始支持 RESP 3(RESP,Redis Serialization Protocol 是 Redis 服务端与客户端之间通信的协议)。
-
连接支持 SSL,更加安全。
-
增强 ACL 权限控制:支持对客户端的权限控制,实现对不同的 key 授予不同的操作权限、新增一个新的 ACL 日志命令,允许查看所有违反 ACL 的客户机、访问不应该访问的命令、访问不应该访问的密钥,或者验证尝试失败。这对于调试 ACL 问题非常有用。
-
提升了 RDB 日志加载速度。
-
发布官方的 Redis 集群代理模块 Redis Cluster Proxy。
-
提供了众多的新模块(modules)API。
8、Redis 7.0
Redis 7.0 在 2022 年正式发布,相比于 Redis 6.0,主要特性如下:
-
将 AOF 文件的存储方式改为在一个⽂件夹下存储多个文件。
-
将持久化文件 RDB 的版本升级为 10,与之前的 RDB 文件版本不再兼容。
-
在读取老的 RDB 文件格式的时候将 ziplist 转换为 listpack,这种转换发生于两种情况之下:从磁盘读取文件或者从一个主节点进行复制⽂件的时候。
-
在 redis.conf 配置文件中,protected-mode 默认更改为 yes,只有当你希望你的客户端在没有授权的情况下可以连接到 Redis server 的时候可以将 protected-mode 设置为 no。
-
在 ACL 中,pub / sub channel 默认是被阻塞的。
-
在从节点中,TTL 的时间标识的是绝对时间,不再是相对时间,从而保证了过期数据被及时删除。
-
不再支持 gopher 协议。
-
当在配置文件中设置 replica-serve-stale-data=no,当主节点不再提供服务时,ping 命令得不到返回值。
五、安装并启动 Redis
下面我将选择 5.0 版本,原因:5.0 已经支持了大部分的功能特性,而且相比较于 7.0 版本,更容易进行安装使用。
Redis 的官方并不支持微软的 Windows 操作系统,因为 Redis 的许多特性都是和操作系统相关的,所以支持 Windows 会增加维护成本,而且更重要的是大部分公司都在使用 Linux 操作系统,而 Redis 在 Linux 操作系统上的表现已经得到实践的证明。
当然,Redis 作为一款优秀的开源技术,还是吸引到微软公司的注意,微软公司的开源技术组在 Github 上维护了一个 Redis 分支:
不过,还是强烈建议大家在 Linux 上使用 Redis。
可参考:【Redis】Centos7 安装 redis(详细教程)-CSDN博客
六、Redis 重要文件及作用
1、启动 / 停止命令或脚本
/usr/bin/redis-benchmark
/usr/bin/redis-check-aof -> /usr/bin/redis-server
/usr/bin/redis-check-rdb -> /usr/bin/redis-server
/usr/bin/redis-cli
/usr/bin/redis-sentinel -> /usr/bin/redis-server
/usr/bin/redis-server
/usr/libexec/redis-shutdown
redis-server 是 Redis 服务器程序,redis-check-aof、redis-check-rdb、redis-sentinel 都是 redis-server 的软链接。redis-check-aof 是修复 AOF 文件用的工具,同理 redis-check-rdb 是修复 RDB 文件的工具,redis-sentinel 是 Redis 哨兵程序。
redis-cli 是需要频繁用到的一个命令行客户端程序。
redis-benchmark 用于对 Redis 做性能基准测试的工具。
redis-shutdown 是用于停止 Redis 的专用脚本。
相比较直接使用这些命令,更建议使用前面讲过的,systemd 托管的方式来进行 Redis 的启动 / 停止。
2、配置文件
/etc/redis-sentinel.conf
/etc/redis.conf
/etc/redis.conf 是 Redis 服务器的配置文件。
/etc/redis-sentinel.conf 是 Redis Sentinel 的配置文件。
3、持久化文件存储目录
/var/lib/redis/
Redis 持久化生产的 RDB 和 AOF文件都默认生成于该目录下。
4、日志文件目录
/var/log/redis/
/var/log/redis/ 目录下会保存 Redis 运行期间生产的日志文件,默认按照天进行分割,并且会将一定日期的日子文件使用 gzip 格式压缩保存。可以使用任意文本编辑器打开。
5、Redis 命令行客户端
现在已经启动了 Redis 服务,下面将介绍如何使用 redis-cli 连接、操作 Redis 服务。客户端和服务端的交互过程如下图所示。
redis-cli 可以使用以下两种方式连接 Redis 服务器。
(1)交互式方式
通过 redis-cli -h { host } -p { port } 的方式连接到 Redis 服务,后续所有的操作都是通过交互式的方式实现,不需要再执行 redis-cli 了,例如:
(2)命令方式
用 redis-cli -h { host } -p { port } { command } 就可以直接得到命令的返回结果。
注意:由于我们连接的 Redis 服务位于 127.0.0.1,端口也使用的是默认的 6379 端口,所以可以省略 -h { host } -p { port }。
Redis 客户端与服务端的交互过程: