赞
踩
目录
Redis是一个开源的内存数据库,它支持持久化存储和多种数据结构,包括字符串、哈希表、列表、集合、有序集合等。Redis被广泛应用于缓存、消息队列、实时统计、排行榜和实时数据分析等场景。
Redis的特点包括:
高性能:Redis将数据存储在内存中,读写速度非常快。
多种数据结构:Redis支持多种数据结构,包括字符串、哈希表、列表、集合、有序集合等。
持久化存储:Redis支持将数据持久化存储到磁盘中,以保证数据不会因为进程终止而丢失。
分布式支持:Redis支持分布式架构,可以通过多个Redis实例实现数据的分布式存储和负载均衡。
扩展性好:Redis支持数据的复制和主从同步,可以实现读写分离和高可用性。
总之,Redis是一个功能丰富、性能卓越、易于使用的内存数据库,被广泛应用于各种场景中。
三个原因:
1. Redis 是一种基于 RAM 的数据存储。 RAM 访问至少比随机磁盘访问快 1000 倍。
2. Redis利用IO多路复用和单线程执行循环来提高执行效率。
3. Redis 利用了几个高效的底层数据结构。
除了redis。还有Memcached比较常见
Memcached 是一个轻量级的缓存系统,使用简单,性能高,可以处理高并发访问。Memcached 不支持持久化,数据一般在内存中存储,当内存不足时,会根据一致性哈希算法将数据迁移到其他节点上。Memcached 支持多线程,可以同时处理多个请求。
共同点:
但是,Redis和Memcached也有一些区别:
列举一些常见的使用场景:
缓存:Redis最常见的使用场景就是缓存。由于Redis使用内存作为存储介质,因此可以快速响应读写请求,提高访问速度。可以将热点数据缓存到Redis中,避免频繁访问数据库,从而提高应用程序的性能。
消息队列:Redis的发布订阅机制可以用来构建简单的消息队列系统。可以将消息发布到Redis中的频道,订阅该频道的客户端可以收到该消息,从而实现消息的发布和消费。
分布式锁:Redis可以用来实现分布式锁。由于Redis支持原子性操作,可以使用Redis实现分布式锁,避免多个进程同时访问共享资源,从而保证系统的数据一致性和安全性。
计数器:Redis支持原子性操作,可以用来实现计数器。可以将计数器存储在Redis中,多个客户端可以同时对计数器进行操作,从而实现分布式计数器的功能。
地理位置应用:Redis支持地理位置数据类型,可以用来存储地理位置信息,如经纬度等,从而实现地理位置应用。
实时排行榜:Redis可以用来存储实时数据,并根据数据进行排序,可以用来实现实时排行榜功能。
会话管理:Redis可以用来存储会话数据,如用户信息、登录状态等,从而实现会话管理的功能。
Redis支持多种数据结构,包括以下常用的数据结构:
字符串(string):字符串是Redis最基本的数据结构之一,可以存储字符串、整数和浮点数等数据类型。
哈希(hash):哈希是一个键值对的集合,可以存储多个字段和值,类似于一个小型的关系型数据库。
列表(list):列表是一个有序的字符串集合,可以在列表的两端进行插入和删除操作,支持队列和栈等多种数据结构。
集合(set):集合是一个无序的字符串集合,可以对集合进行添加、删除、交集、并集等操作,可以用来进行数据去重。
有序集合(sorted set):有序集合是一个有序的字符串集合,每个元素都有一个分值,可以进行排名、区间查询等操作,常用于实现排行榜和计分系统等功能。
三种特殊的数据类型 分别是 HyperLogLogs(基数统计), Bitmaps (位图) 和 geospatial (地理位置)
Redis支持以下5种主要数据类型,每种类型在底层都有不同的数据结构实现:
String(字符串):底层采用简单动态字符串(SDS)实现,支持二进制安全操作。
Hash(哈希):底层采用哈希表实现,支持快速的查找、插入和删除操作。
List(列表):底层采用双向链表实现,支持在头部和尾部进行快速的插入和删除操作。
Set(集合):底层采用哈希表或者跳跃表实现,支持高效的集合运算操作,比如并集、交集、差集等。
Sorted Set(有序集合):底层采用跳跃表和哈希表结合实现,同时支持按照分值(score)排序和按照成员(member)排序两种方式进行排序操作。
Redis设计了SDS(Simple Dynamic String)简单动态字符串,主要是为了解决C语言中传统的字符数组(char array)存在的一些问题,如以下几点:
空间不易动态调整:C语言的字符数组在定义时需要指定其长度,如果长度不够时需要重新定义一个更大的数组,再将原来数组中的数据拷贝到新数组中,这样的操作十分繁琐且容易出错。
不支持二进制安全:C语言的字符数组中以'\0'作为结束符,因此不支持存储二进制数据或者包含'\0'字符的字符串。
性能问题:C语言的字符数组在获取其长度时需要遍历整个数组才能计算出长度,效率较低。
SDS通过维护一个buf指针指向动态分配的内存,同时记录字符串的长度信息,可以解决上述问题,其优点如下:
1.动态扩容
SDS 的内部结构是由长度、可用空间和字符数组组成。可用空间指的是字符串数组中未被使用的空间。相比于 C 语言原生字符串类型,SDS 可以进行动态扩容,即在字符串长度超过可用空间时,自动增加可用空间。这种设计使得 SDS 的操作更加高效,避免了多次重新分配内存和复制字符串的开销。
2.减少缓冲区溢出的风险
C 语言原生字符串类型没有对长度进行限制,如果字符串长度超过了字符数组的长度,就会出现缓冲区溢出的问题。SDS 设计了一个 buf 属性,用于存储字符串的实际内容,这个 buf 数组的长度被控制在字符串的实际长度之内,从而避免了缓冲区溢出的风险。
3.兼容 C 语言原生字符串类型
为了兼容 C 语言原生字符串类型,SDS 在内部结构中保留了一个空字符('\0')。这使得 SDS 在与其他 C 语言函数交互时,能够像原生字符串一样正常工作。
4.二进制安全
C 语言原生字符串类型是以空字符('\0')作为字符串的结束标志的,这意味着它只能存储文本数据。SDS 则是二进制安全的,可以存储任何二进制数据。这种设计使得 SDS 在 Redis 中存储键和值时非常有用。
5.优化字符串操作
SDS 内部结构中存储了字符串的长度信息,这使得获取字符串长度的操作 O(1) 时间复杂度。此外,SDS 还提供了一些其他字符串操作函数,如字符串追加、字符串复制等,这些函数都比 C 语言原生的字符串操作函数更加高效。
Redis7.0 的 SDS 的部分源码如下(https://github.com/redis/redis/blob/7.0/src/sds.h):
- /* Note: sdshdr5 is never used, we just access the flags byte directly.
- * However is here to document the layout of type 5 SDS strings. */
- struct __attribute__ ((__packed__)) sdshdr5 {
- unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
- char buf[];
- };
- struct __attribute__ ((__packed__)) sdshdr8 {
- uint8_t len; /* used */
- uint8_t alloc; /* excluding the header and null terminator */
- unsigned char flags; /* 3 lsb of type, 5 unused bits */
- char buf[];
- };
- struct __attribute__ ((__packed__)) sdshdr16 {
- uint16_t len; /* used */
- uint16_t alloc; /* excluding the header and null terminator */
- unsigned char flags; /* 3 lsb of type, 5 unused bits */
- char buf[];
- };
- struct __attribute__ ((__packed__)) sdshdr32 {
- uint32_t len; /* used */
- uint32_t alloc; /* excluding the header and null terminator */
- unsigned char flags; /* 3 lsb of type, 5 unused bits */
- char buf[];
- };
- struct __attribute__ ((__packed__)) sdshdr64 {
- uint64_t len; /* used */
- uint64_t alloc; /* excluding the header and null terminator */
- unsigned char flags; /* 3 lsb of type, 5 unused bits */
- char buf[];
- };
通过源码可以看出,SDS 共有五种实现方式 SDS_TYPE_5(并未用到)、SDS_TYPE_8、SDS_TYPE_16、SDS_TYPE_32、SDS_TYPE_64,其中只有后四种实际用到。Redis 会根据初始化的长度决定使用哪种类型,从而减少内存的使用。
类型 | 字节 | 位 |
---|---|---|
sdshdr5 | < 1 | <8 |
sdshdr8 | 1 | 8 |
sdshdr16 | 2 | 16 |
sdshdr32 | 4 | 32 |
sdshdr64 | 8 | 64 |
对于后四种实现都包含了下面这 4 个属性:
len
:字符串的长度也就是已经使用的字节数alloc
:总共可用的字符空间大小,alloc-len 就是 SDS 剩余的空间大小buf[]
:实际存储字符串的数组flags
:低三位保存类型标志跳跃表支持平均O(logN),最坏O(N)复杂度查找 ,zset(有序集合)的底层实现
zset特性:
zskiplistNode
结构体包含了元素值、分值、后退指针和层级指针数组。层级指针数组中的每个元素包含了当前节点在对应层级的下一个节点和当前节点到下一个节点的跨度,用于计算排名和删除节点时的操作。
在 Redis 中,跳跃表中的元素被用于实现有序集合,每个元素都包含了一个值和一个分值,其中分值用于对元素进行排序。因此,zskiplistNode
结构体中的 obj
指针指向了一个 Redis 对象(robj
),该对象包含了元素的值,而 score
则表示元素的分值。
除了元素值和分值之外,zskiplistNode
结构体中还包含了一个后退指针,指向当前节点的前一个节点。后退指针的作用是,方便在跳跃表中进行反向遍历。
- /* ZSETs use a specialized version of Skiplists */
- typedef struct zskiplistNode {
- //数据
- sds ele;
- //节点分值
- double score;
- //节点的后退指针
- struct zskiplistNode *backward;
- struct zskiplistLevel {
- //节点的前进指针
- struct zskiplistNode *forward;
- //跨度
- unsigned long span;
- } level[];
- } zskiplistNode;
- typedef struct zskiplist {
- //跳跃表的头结点和尾节点
- struct zskiplistNode *header, *tail;
- //跳跃表的长度
- unsigned long length;
- //跳跃表的层数
- int level;
- } zskiplist;
最后,跳跃表节点的层级指针数组是跳跃表实现中的核心部分,用于实现快速查找和删除。每个节点的层级数组都包含了若干个指针,指向当前节点在各个层级上的下一个节点。层级指针数组的长度是根据随机算法生成的,每个节点的层级数不同,最大值为 32。层级指针数组中每个元素还包含了一个 span
属性,表示当前节点到下一个节点的跨度,用于计算排名。
Redis字符串类型的值最大可以存储512MB的数据。如果需要存储更大的数据,可以将数据分为多个小于512MB的块,分别存储在多个字符串类型的键中,或者使用Redis的其他数据结构来存储数据。
Redis是单进程单线程的模型,官方提供的数据是可以达到100000+的QPS(每秒内查询次数)
效率为何高:
1,纯内存操作
2,基于非阻塞的IO多路复用机制
3,避免了多线程的频繁上下文切换
Redis 基于 Reactor 模式开发了自己的网络事件处理器 - 文件事件处理器,而该处理器又是单线程的,所以redis设计为单线程模型。
Reactor模式是一种事件驱动的设计模式,它的核心思想是将事件处理分为两个阶段:事件分发和事件处理。在事件分发阶段,系统会通过一个事件循环(Event Loop)不断地监听事件;在事件处理阶段,当一个事件发生时,系统会将事件交给相应的事件处理器进行处理。这种模式可以减少系统中的线程数量,并且能够提高系统的可伸缩性和性能。
在Redis中,事件循环被称为"main loop",它会监听所有的网络事件和文件事件,并将这些事件加入到一个事件队列中。当事件队列中有事件时,Redis会调用相应的事件处理器进行处理。在处理器的执行过程中,Redis会调用内部的数据结构和算法来高效地处理数据。
此外,Redis还使用了多路复用技术来实现事件监听,即使用一个线程监听多个网络连接,以此来避免线程切换的开销,并提高系统的性能。
Redis 给缓存数据设置过期时间的主要作用是为了控制缓存数据的生命周期,
1.确保缓存中的数据不会一直存在,从而保证缓存的有效性和减轻内存压力。
2.业务需求。
具体来说,当 Redis 中的某个键值对设置了过期时间后,在该时间到达之后,Redis 会自动将这个键值对从缓存中删除,这样就可以及时释放缓存空间,避免因缓存过期而导致的内存浪费问题。同时,当客户端请求某个键值对时,如果 Redis 发现该键值对已经过期,它会返回空值或者重新加载最新数据到缓存中。
除此之外,设置过期时间还有助于防止缓存击穿和缓存雪崩等问题的发生。当缓存击穿时,大量请求同时请求一个不存在于缓存中但是在数据库中存在的数据,导致数据库压力过大;而缓存雪崩则是指因为缓存中大量的键值对同时过期而导致数据库压力骤增的情况。设置过期时间可以有效地避免这些问题的发生。
Redis 通过使用惰性删除和定期删除两种方式来判断数据是否过期。
1.惰性删除
惰性删除是指当 Redis 客户端请求某个键值对时,Redis 在返回数据之前先检查这个键值对是否过期,如果过期则删除并返回空值或者重新加载最新数据到缓存中。这种方式的优点是能够保证数据的实时性,缺点是需要在客户端请求时才能发现过期的数据,可能会导致一些请求响应时间变长。
2.定期删除
定期删除是指 Redis 在后台启动一个定时器,定期检查所有设置了过期时间的键值对,如果发现某个键值对已经过期,则删除该键值对。Redis 通过使用一个叫做「过期字典」的结构来保存所有设置了过期时间的键值对,这个字典中的键是键值对的键,值是键值对的过期时间戳。当定期删除器执行时,Redis 会遍历过期字典,删除所有过期的键值对。
为了平衡惰性删除和定期删除的优缺点,Redis 使用了一种混合策略。当一个键过期时,Redis 会将其添加到一个过期字典中,并使用惰性删除策略来清理过期键。此外,Redis 还会周期性地执行定期删除任务,来清理那些惰性删除未能及时清理的过期键。
为了避免过期键过多堆积在内存中,Redis 在过期键的清理过程中会使用一些技术手段,如:使用分区技术将过期键分散到不同的区域中,限制每个定期删除任务处理的过期键数量等。
Redis 实现了多种内存淘汰机制。以下是 Redis 内存淘汰机制的几种常见实现方式:
LRU (Least Recently Used) 最近最少使用算法:Redis 使用 LRU 算法来选择要淘汰的键。LRU 算法会优先淘汰最近最少使用的键,即最近很少或没有被访问过的键。
LFU (Least Frequently Used) 最不经常使用算法:Redis 使用 LFU 算法来选择要淘汰的键。LFU 算法会优先淘汰使用次数最少的键,即最近很少或没有被访问过的键。
Random 随机算法:Redis 也可以使用随机算法来选择要淘汰的键。随机算法会从所有的键中随机选择一个键来淘汰。
TTL (Time To Live) 过期时间算法:Redis 还可以根据键的过期时间来淘汰键。当 Redis 内存不足时,它会优先淘汰已过期的键。
Redis 的持久化机制是指将 Redis 的数据持久化到磁盘上,以防止数据丢失。Redis 提供了两种不同的持久化机制:RDB 和 AOF。
1.RDB(Redis DataBase)持久化机制
RDB 是 Redis 默认的持久化机制。RDB 机制会在指定的时间间隔内,对 Redis 数据进行快照(snapshot)并写入磁盘中的 RDB 文件。RDB 文件是一个二进制文件,其中包含了 Redis 在某个时间点上的数据快照。
优点:
缺点:
2.AOF(Append-Only File)持久化机制
AOF 机制会记录所有写命令,并将这些写命令追加到磁盘中的 AOF 文件中。当 Redis 重启时,可以通过重新执行 AOF 文件中的所有命令,恢复数据。
优点:
缺点:
一般情况下,需要根据实际情况来选择合适的持久化机制。如果数据集较小,可以使用 AOF 机制以最大程度地减少数据丢失;如果数据集较大,可以使用 RDB 机制以较小的成本备份和恢复数据。
同时,也可以将 RDB 和 AOF 机制结合使用,以充分利用它们各自的优势。例如,可以将 AOF 机制设置为 always,并使用 RDB 机制定期进行数据备份。这样既能保证数据不丢失,也能在数据集较大时提高恢复数据的速度。
手动触发:执行save命令,问题:数据量大会阻塞当前服务器,直到rdb完,生产环境禁用!
解决方案:用bgsave命令(background save),也就是后台备份,主进程会fork一个子进程来完成RDB的过程,完成后自动结束(操作系统的多进程Copy On Write机制,简称COW)。fork的时候也会阻塞子进程,但是时间很短;
自动触发:
1,配置redis.conf,配置触发规则,自动执行:
- # 当在规定的时间内,Redis发生了写操作的个数满足条件,会触发发生BGSAVE命令。
- # save <seconds> <changes>
- # 当用户设置了多个save的选项配置,只要其中任一条满足,Redis都会触发一次BGSAVE操作
- save 900 1
- save 300 10
- save 60 10000
- # 以上配置的含义:900秒之内至少一次写操作、300秒之内至少发生10次写操作、
- # 60秒之内发生至少10000次写操作,只要满足任一条件,均会触发bgsave
2,执行shutdown命令关闭服务器时,如果没有开启AOF持久化功能,那么会自动执行一次bgsave
3,主从同步(slave和master建立同步机制)
从库和主库建立连接后,主库会执行bgsave将当前备份数据传给从库,同时也会缓存备份过程中进来的写操作
redis主要是用操作系统的多进程cow(copy on write)机制来实RDB快照持久化;
1,执行bgsave时,主线程会先判断是否有子进程在执行rdb/aof持久化任务,如果有直接返回。
2,主进程会fork出一个子进程,fork过程中会对主进程有短暂的阻塞。
3,子进程会根据主进程的内存生成临时的快照文件,持久化完成后会替换掉原来的rdb文件,由于备份的过程中还会不断的有新的写请求进来,这些写操作不会在主进程的主内存中(也就是备份时正在读的内存)。会写到一个临时的内存区域作为副本。
4,子进程完成RDB持久化后会通知主进程,并且会将备份期间写道临时内存的写数据同步到主内存。
AOF是一种写后日志机制,也就是说,当Redis执行写操作时,它首先会将命令追加到内存中的AOF缓冲区中,等待同步到硬盘。只有当命令成功被追加到AOF文件中并同步到硬盘后,Redis才会执行实际的写操作。因此,即使Redis在写操作期间发生崩溃,已经被追加到AOF缓冲区中但尚未同步到硬盘中的命令也不会丢失,可以通过重放AOF文件来恢复数据。
由于写后日志机制不需要立即将每个写操作同步到硬盘中,因此可以大大提高Redis的写入性能。在使用写前日志机制时,每个写操作都必须等待同步到硬盘后才能返回客户端,这会造成较大的延迟,尤其是在写入负载较高的情况下。而采用写后日志机制后,Redis可以将写操作先保存到内存中的缓冲区,然后异步地将缓冲区中的内容写入硬盘,从而大大提高了写入性能。
其次,采用写后日志机制还可以提高Redis的可靠性。由于AOF文件记录了Redis执行的每个写操作,因此即使Redis在运行过程中发生崩溃,也可以通过重放AOF文件来恢复数据。而如果采用写前日志机制,由于每个写操作必须立即同步到硬盘中,一旦发生崩溃就会导致数据的丢失。
要实现AOF(Append-Only File)机制,需要在Redis配置文件中启用AOF功能,并设置AOF的相关参数。在Redis中,可以通过以下步骤来实现AOF:
启用AOF功能:在Redis的配置文件redis.conf中,可以通过将appendonly参数设置为yes来启用AOF功能。例如:appendonly yes
设置AOF文件名和路径:可以通过设置dir和appendfilename参数来指定AOF文件的保存路径和文件名。例如:dir /var/lib/redis
和appendfilename "appendonly.aof"
设置AOF的同步策略:可以通过设置appendfsync参数来指定AOF的同步策略,即指定何时将AOF缓冲区中的数据同步到硬盘中的AOF文件中。Redis提供了以下三种同步策略:
always:每个写操作都会立即同步到硬盘中的AOF文件中,保证数据完整性,但是性能较低。
everysec:每秒钟将AOF缓冲区中的数据同步到硬盘中的AOF文件中,可以在一定程度上提高性能,但是可能会丢失1秒钟的数据。
no:将AOF缓冲区中的数据定期刷写到硬盘中的AOF文件中,性能最高,但可能会导致数据的丢失。
例如:appendfsync everysec
4.重启Redis服务:在修改了Redis配置文件中的AOF相关参数后,需要重启Redis服务才能使配置生效。
aof Rewrite 是压缩aof文件的过程,但不是基于原文件进行压缩,而是类似于RDB快照的方式,基于COPY ON WRITE,遍历内存中的数据,将最终态的数据以命令的形式存储,相当于一条数据可能经过多次操作对应多条命令,但是最终会变成一个命令来表示最终状态就可以了。
和rdb一样,重写也是需要fork子进程,在子进程中完成的,重写过程中,新的操作还是会写入原aof文件,同时这些操作也会被redis收集起来。当重写完成后,这些操作也会被追加到新的aof文件中。
触发机制
手动触发:直接调用bgrewriteaof命令执行一次重写操作
自动触发:根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数确定自动触发时机
- auto-aof-rewrite-min-size:表示运行AOF重写时文件最小体积,默认为64MB(我们线上是512MB)。
-
- auto-aof-rewrite-percentage:相对于“上一次”rewrite,本次rewrite触发时aof文件应该增长的百分比。 代表当前AOF文件空间(aof_current_size)和上一次重写后AOF文件空间(aof_base_size)的值
在 Redis fork 子进程时,需要拷贝父进程的内存映射表,这个操作会阻塞主线程。
在 Redis 主进程写入 bigkey 数据时,操作系统会为该数据创建一个页面的副本,并拷贝原有的数据,这个操作也会阻塞主线程。
在 AOF 重写过程中,子进程会独立执行重写操作,但是在子进程执行完毕之后,子进程会发送一个信号给主进程,让主进程将重写缓冲区的内容追加到 AOF 文件中。这个操作可能会阻塞主线程。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。