什么是Redis?
Redis是一个高性能的开源键值对数据库,以其卓越的读写速度和多种内置数据结构而著称。它支持数据持久化,能够在内存中快速处理数据,并通过丰富的特性如事务、发布/订阅、Lua脚本以及主从复制等,为开发人员提供了强大的数据管理能力。Redis还具备高可用性解决方案,如Sentinel和Cluster,使其在需要快速访问和处理大量数据的现代应用中成为首选的解决方案。
大概知道Redis是什么东西了,那让我们来讨论:
1、是个什么样的数据库?
2、为什么读写数据那么快?
3、高可用性在哪里?
1、是个什么样的数据库?
- Redis里包含多种数据结构:动态字符串、列表、集合、有序集合、散列
动态字符串SDS:
实现结构:
/*
* redis中保存字符串对象的结构
*/
struct sdshdr {
//用于记录buf数组中使用的字节的数目,和SDS存储的字符串的长度相等
int len;
//用于记录buf数组中没有使用的字节的数目
int free;
//字节数组,用于储存字符串
char buf[]; //buf的大小等于len+free+1,其中多余的1个字节是用来存储’\0’的
};
基于这样的实现结构,可以为SDS赋予一下几点优点:
1、O(1)复杂度的操作:获取字符串长度,只需要读取len即可,相比于c++里的字符串结构需要O(n)的复杂度
2、动态扩展:SDS可以动态的扩展,在buf数组里可以动态的添加,并修改相对应的len和free即可,相比于c++的静态字符串结构有更大的优势。
3、惰性空间释放: 当SDS缩短字符串时,并不会立即释放多余的空间,而是将多余的空间保留下来,以备后续的再利用。这种惰性空间释放的策略可以减少内存分配和释放的开销,提高内存利用率。
4、二进制安全:SDS可以存储任意二进制数据,而不仅仅是文本字符串。这意味着SDS可以存储包括图片、视频、音频等在内的各种二进制数据,而不会受到特殊字符或者空字符的限制,具有更广泛的适用性。
列表
Redis的列表是简单的字符串列表,按照插入顺序排序。列表的实现为一个双向链表。
typedef struct list {
// 表头和表尾节点
listNode *head, *tail;
// 列表中节点的数量
unsigned long len;
} list;
基于这样的实现结构,可以为列表赋予一下几点优点:
1. 顺序存储:列表保持元素的插入顺序,可以当作栈或队列使用。每当需要插入新数据的时候只需要让尾节点指向最后一个结点,并让前一个结点指向插入的结点即可。
2. 双向迭代:Redis列表作为双向链表实现,每个列表节点都包含指向前一个和后一个节点的指针。这种结构允许从列表的任一端开始迭代,并能在迭代过程中轻松地前后移动。
3. 动态伸缩:列表大小可以根据需要动态变化。每当节点数变化的时候,len也跟着变化,所以读取列表的长度时间复杂度为O(1)。
集合
Redis的集合是通过哈希表实现的,它能够存储不重复的字符串元素。
typedef struct HashNode
{
Elemtype data; //数据域
HashNode* link; //可以指向下一个链表元素
}HashNode;
typedef HashNode* HashTable[P]; //创建哈希数组,数组元素大小为P,每个元素类型为指针
基于这样的实现结构,可以为集合赋予一下几点优点:
1. 唯一性:当尝试向集合添加一个新元素时,Redis会计算该元素的哈希值,并确定它应该被存储在哈希表的哪个位置。如果该位置上已经有了元素,哈希表会检查该元素是否与要添加的新元素相同。由于集合元素作为键存储在哈希表中,而键不能有重复,所以Redis集合自动确保了元素的唯一性。
2. 快速查找:基于哈希表,提供平均O(1)的查找复杂度。当需要查找一个元素是否存在于集合中时,Redis只需计算该元素的哈希值,然后直接访问哈希表中相应的位置来检查元素是否存在。
3. 灵活操作:支持添加、删除、测试成员资格等操作。由于Redis是一个键值对数据库,集合的每个操作都是原子性的,这意味着多个客户端可以同时对集合进行操作,而不会出现数据竞争或不一致的问题。
有序集合
有序集合类似于集合,但它为每个元素关联了一个分数(score),并根据分数对元素进行排序。
// 有序集合的元素
typedef struct zset {
dict *dict; // 有序集合的字典表示
robj *subject[2]; // 两个压缩列表,分别存储元素和分数
} zset;
基于这样的实现结构,可以为有序集合赋予一下几点优点:
1. 自动排序:根据元素的分数自动排序。这种唯一性对于需要去重的场合非常有用,例如,当跟踪独立用户、唯一事件或商品时,集合可以确保每个条目只被记录一次。
2. 范围查询:支持通过分数范围进行查询。通过使用ZRANGEBYSCORE
等命令,可以快速地执行这些查询,而不需要对整个集合进行遍历,这在处理大量数据时尤其重要。
3. 灵活的操作:可以添加、删除、更新分数等。
散列
Redis的散列提供了一个键值对集合,其中的键和值都是字符串。
// 散列键值对
typedef struct dictEntry {
void *key;
unsigned int keyLen;
void *val;
struct dictEntry *next;
} dictEntry;
基于这样的实现结构,可以为散列赋予一下几点优点:
1. 键值对存储:存储键值对,其中键是唯一的。键值对的方式提供了一种灵活的数据存储方法,使得可以轻松地将整个对象序列化并存储在Redis中,例如,用户信息、配置设置或任何需要多个字段来描述的数据实体。
2. 快速访问:通过键快速访问值,提供平均O(1)的访问复杂度。
3. 灵活的映射:散列的灵活性也体现在可以存储各种类型的值,包括字符串、列表、集合等,这使得Redis散列可以用于实现复杂的数据模型。
这些数据结构使得Redis不仅能够处理简单的缓存需求,还能够支持复杂的数据存储和查询场景。每种数据结构都针对特定的使用案例进行了优化,提供了高效的数据操作能力。
2、为什么读写数据那么快?
1、因为独特的数据结构为Redis的读写提供的独特的遍历条件
2、Redis所有数据都是存储于内存的。
Redis其关键特性在于将所有数据实体,包括键值对及其相关的复杂数据结构,完全寄宿于内存之中。在读取操作层面,Redis无需经过耗时的磁盘I/O过程,只需在内存空间内迅速定位所需数据;而在写入操作时,Redis同样直接作用于内存区域,新数据能即刻生效,仅在执行持久化策略时,例如RDB快照或AOF日志记录,数据才会被异步地或按需地同步至磁盘,以确保即使在系统重启后数据仍能得以恢复,但此过程并不会妨碍Redis在常规操作中维持其卓越的性能表现。
同时Redis对于数据的清理有两种方式:
(1)、惰性删除 :在访问某个键时,Redis会检查该键是否已经过期,如果已经过期,则在访问时将其删除。这意味着只有当有客户端尝试访问过期的键时,Redis才会执行删除操作。这种方式的优势在于避免了不必要的操作,只有在需要时才进行删除,但缺点是可能会导致过期键在一段时间内仍然占用内存。
(2)、定期删除 :Redis周期性地(默认每秒10次)随机抽取一部分键,并检查它们的过期时间。如果发现某个键已经过期,则立即将其删除。这种方式可以保证过期键在一定时间内被及时删除,避免了过期键长时间占用内存。但定期删除会带来额外的CPU消耗,因为需要在每次抽取时检查键的过期时间。
3、Redis是单线程模型
Redis在其核心数据处理部分采用单一的主线程来执行网络IO操作、接收客户端命令请求、执行命令操作以及返回结果。Redis服务端的网络IO和键值对读写操作都由一个线程统一负责,而诸如持久化、集群数据同步等任务则是由其他线程来执行。在单线程模型下,Redis服务器是单线程运行的,即每个客户端的请求都是依次顺序执行的。
4、Redis使用IO多路复用技术:
Redis通过使用IO多路复用技术(如epoll、kqueue或select等),在一个线程内同时监听多个socket连接,当有网络事件发生时(如读写就绪),再逐一处理。这样可以处理大量并发连接,并在单线程中高效地调度网络事件,使得单线程也能应对高并发场景。所以Redis服务端,整体来看,就是一个以事件驱动的程序,它的操作都是基于事件的方式进行的。
3、高可用性在哪里?
什么是高可用性:高可用性(High Availability,简称HA)是指系统在面临各种挑战(如硬件故障、软件故障、网络问题等)时,仍能持续运行并提供服务的能力。高可用性的系统设计旨在最大限度地减少停机时间,确保服务的连续性和可靠性。
1、主从复制机制:它允许多个从服务器同步主服务器的数据。如果主服务器发生故障,一个从服务器可以迅速被提升为新的主服务器,继续提供服务。
2、Redis Sentinel系统:可以监控Redis服务器的状态,自动检测故障并在必要时触发故障转移过程,确保服务的持续性
3、Redis Cluster模式:它通过分片来分布式存储数据,每个分片可以独立地运行在不同的Redis节点上。这种方式不仅提高了存储容量,还通过数据的分布式存储和复制,提高了系统的容错性。即使某些节点发生故障,其他节点仍然可以继续工作,保护数据不受损失。
4、Redis的持久化功能:如RDB和AOF,确保了即使在系统崩溃后,数据也能被恢复。