1、Redis是什么
Redis是一个数据库,不过与传统数据库不同的是Redis的数据库是存在内存中,所以读写速度非常快,因此 Redis被广泛应用于缓存方向。
Redis也经常用来做分布式锁,Redis提供了多种数据类型来支持不同的业务场景。除此之外,Redis 支持事务持久化、LUA脚本、LRU驱动事件、多种集群方案。
2、Redis的数据结构
Redis支持五种数据结构:string字符串+hash哈希+list列表+set集合+Zset有序集合
2.1 string
String类型是二进制安全的,意思是 redis 的 string 可以包含任何数据。如数字,字符串,jpg图片或者序列化的对象。
1.缓存:经典使用场景,把常用信息,字符串,图片或者视频等信息放到redis中,redis作为缓存层,mysql做持久化层,降低mysql的读写压力。
2.计数器:redis是单线程模型,一个命令执行完才会执行下一个,同时数据可以一步落地到其他的数据源。
特点:
- 获取字符串长度的复杂度为O(1)。
- 杜绝缓冲区溢出。
- 减少修改字符串长度时所需要的内存重分配次数。
- 二进制安全。
- 兼容部分C字符串函数。
2.2 list
当有一个列表键包含了数量比较多的元素,又或者列表中包含的元素都是比较长的字符串时,Redis就会使用链表作为列表建的底层实现。
例如:粉丝列表, 消息列表等功能都可以用Redis的 list 结构来实现
节点底层结构
typedef struct listNode {
struct listNode *prev; 前置节点
struct listNode *next; 后置节点
void *value; 节点值
}listNode;
list底层结构
typedef struct list {
listNode *head; 表头节点
listNode *tail; 表尾节点
unsigned long len;// 链表所包含的节点数量
void *(*dup)(void *ptr); // 节点值复制函数
void (*free)(void *ptr); // 节点值是放过函数
int(*match)(void *ptr, void *key); // 节点值对比函数
} list;
特点:
- 链表被广泛用于实现Redis的各种功能,比如列表建、发布与订阅、慢查询、监视器等。
- 每个链表节点由一个listNode结构来表示,每个节点都有一个指向前置节点和后置节点的指针,所以Redis的链表实现是双端链表。
- 每个链表使用一个list结构表示,这个结构带有表头节点指针、表尾节点指针,以及链表长度等信息。
- 因为链表表头的前置节点和表尾节点的后置节点都指向NULL,所以Redis的链表实现是无环链表。
- 通过为链表设置不同的类型特定函数,Redis的链表可以用于保存各种不同类型的值。
2.3 hash
底层是哈希表,键值对;Redis的哈希表使用链地址法来解决键冲突,每个哈希表节点都有一个next指针,多个哈希表节点可以用这个单向链表连接起来。
用作缓存: 直观,相比string更节省空间,维护缓存信息,如用户信息,视频信息等。
2.4 set
不允许有重复的元素,集合中的元素是无序的,不能通过索引下标获取元素,支持集合间的操作,可以取多个集合取交集、并集、差集。
标签(tag),给用户添加标签,或者用户给消息添加标签,这样有同一标签或者类似标签的可以给推荐关注的事或者关注的人。
2.5 Zset
有序集合中的元素是可以排序的,它给每个元素设置一个分数,作为排序的依据。有序集合中的元素不可以重复,但是score 分数 可以重复。
特点:
-
跳跃表是有序集合的底层实现之一。
-
Redis的跳跃表实现由zskiplist和zskiplistNode两个结构组成,其中zskiplist用于保存跳跃表信息(比如表头节点、表尾节点、长度),而zskiplistNode则用于表示跳跃表节点。
-
每个跳跃表节点的层高都是1至32之间的随机数。层的数量越多,访问其他节点的速度越快。
-
跳表是一种实现起来很简单,单层多指针的链表,它查找效率很高,堪比优化过的二叉平衡树。
-
跳跃表以空间换时间的方式提升了查找速度。
-
Redis有序集合在节点元素较大或者元素数量较多时使用跳跃表实现。
3、Redis的特点:
高性能:假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。
操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,可以同步改变缓存中相应的数据。
高并发:直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。
Redis采用单线程+IO多路复用:
Redis的线程模型:
- Redis内部使用文件事件处理器 file event handler,这个文件事件处理器是单线程的,所以 Redis才叫做单线程的模型;
- 它采用 IO 多路复用机制同时监听多个 socket,根据 socket 上的事件来选择对应的事件处理器进行处理。I/O多路复用程序负责监听多个 套接字, 并向文件事件分派器传送那些产生了事件的套接字。
为什么采用单线程而不是多线程:
因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽,单线程比多线程更容易实现,因此采用单线程。
单线程Redis快主要是有三个原因:
- Redis的全部操作都是纯内存的操作;
- Redis采用单线程,有效避免了频繁的上下文切换;
- 采用了非阻塞I/O多路复用机制。
4、Redis事务
Redis 事务特性:
- 单独的隔离操作:所有命令会序列号,按顺序执行;事务执行过程中,不会被其他客户端发送的命令请求打断。
- 没有隔离级别:队列中命令在没有提交之前都不会被执行。
- 不保证原子性:事务中一条命令执行失败,其后的命令仍然被执行,没有回滚。
5、持久化操作:RDB+AOF
通过持久化机制把内存中的数据同步到硬盘文件来保证数据持久化。当Redis重启后通过把硬盘文件重新加载到内存,就能达到恢复数据的目的。
5.1 RDB为快照持久化:
在指定时间间隔内把内存中数据集快照写入磁盘;创建快照得到存储在内存里面的数据在某个时间点上的副本。
执行过程:
Redis单独创建一个子进程来进行持久化,将当前父进程的数据库数据复制到子进程的内存中,子进程把数据写入到一个临时文件中,等待持久化结束再使用临时文件替换上次持久化的文件,保证数据的完整性和一致性。
主进程不进行IO操作,确保了高性能,适用于大规模的数据恢复且对数据的完整性不敏感情况,节省磁盘空间。
缺点:
①最后一次持久化后的数据可能丢失(时间间隔内服务器挂掉)。
②写时复制在数据量大时,影响性能。
5.2 AOF(append-only file)持久化
开启AOF持久化后每执行一条会更改Redis中的数据的命令(写操作),Redis就会将该命令写入硬盘中的AOF文件。
同步频率设置:让Redis每秒同步一次AOF文件,Redis性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。
当硬盘忙于执行写入操作的时候,Redis还会优雅的放慢自己的速度以便适应硬盘的最大写入速度;恢复:重启Redis会重新加载AOF中内容 。
特点: 实时性更好;丢失数据概率更低;(数据+操作)占用更多空间,备份速度慢。
AOF重写
AOF重写可以产生一个新的AOF文件,这个新的AOF文件和原有的AOF文件所保存的数据库状态一样,但体积更小。
通过读取数据库中的键值对来实现的(set k1 v1; set k2 v2)==(set k1 v1 k2 v2)
- 会维护一个 AOF 重写缓冲区,该缓冲区会在子进程创建新AOF文件期间,记录服务器执行的所有写命令。
- 当子进程完成创建新AOF文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新AOF文件的末尾,使得新旧两个AOF文件所保存的数据库状态一致。
- 最后,服务器用新的AOF文件替换旧的 AOF文件,以此来完成AOF文件重写操作。
6、主从复制:
读写分离,性能扩展;容灾快速恢复;一主多从。
主从复制原理:
- 从服务器连接上主服务器后,从服务器向主服务器发送进行数据同步消息(从服务器发起)
- 主服务器接收到以后,把主服务器数据进行持久化到rdb文件,把rdb文件发送到从服务器。从服务器进行文件读取完成主从复制。
- 每次主服务器进行写操作之后,和从服务器进行数据同步(主服务器发起);增量复制/全量复制(重新连接都是全)
7、Redis应用问题
7.1 缓存穿透
应用服务器压力变大,查询一个一定不存在的数据,由于缓存不命中,接着查询数据库也无法查询出结果,因此也不会写入到缓存中,这将会导致每个查询都会去请求数据库,造成缓存穿透。
出现原因:出现很多非正常访问;Redis查询不到数据库;一般黑客攻击。
解决方法:
- 对空值做缓存:设置空结果的过期时间很短
- 设置可访问的名单(白名单):bitmaps类型定义白名单;存在才进行访问,不存在拦截;
- 采用布隆过滤器:二进制向量(位图)和一系列随机映射函数(哈希函数)空间效率和查询时间较优;但误识别率和删除困难
- 实时监控:命中率减低,排查对象;设置黑名单。
7.2 缓存击穿
数据库访问压力瞬时增大;Redis正常,没有大量key过期;
原因:Redis某个key过期,大量访问使用这个key;热门访问的key
解决方法:
- 预先设置热门数据:加大key时长;
- 实时调制:监控状态,现场设置;
- 使用锁:
7.3 缓存雪崩
数据库压力变大,服务器崩溃。
原因:极少时间内,大量key集中过期;后面的请求都会落到数据库上,造成数据库短时间
内承受大量请求而崩掉。
解决方法:
- 构建多级缓存架构:
- 使用锁或队列:不适应高并发
- 设置过期标志更新缓存:
- 缓存失效时间分散:随机数生成失效时间