Redis
Redis
1、Redis使用
1)为什么要用redis?
redis是现在最受欢迎的NoSQL数据库之一,相比于传统的关系型数据库,具备高性能和高并发、支持丰富的数据类型、基于内存的、也是单线程的,响应速度很快。
高性能:比如一个请求过来,在数据库中查询需要100ms,且这个数据在一段时间都不会变了,如果我们把它放到缓存中,通过key查出一个value,2ms就可以搞定,能大大提高性能。
高并发:如果有大量的请求同时去访问数据库,数据库很容易就会宕机,这个时候如果能把数据放到缓存中,那么redis一秒几万十几万的并发量是可以支撑下来的。
2)单线程的redis为什么这么快?
- 纯内存操作
- 单线程操作,避免了频繁的上下文切换
- 采用了非阻塞I/O多路复用机制
3)reids 为什么是单线程的?
redis是基于一个文件事件处理器,这个文件事件处理器是单线程的,所以redis才叫单线程模型。
文件事件处理器:
- 多个socket
- IO多路复用程序
- 文件事件分配器
- 事件处理器

过程:I/O多路复用程序会监听多个socket,会将socket产生的事件放入队列中排队,每次以一个套接字的方式向文件时间分派器传送套接字。当上一个套接字产生的事件被处理完毕之后,I/O多路复用程序才会继续向文件事件分派器传送下一个套接字。
2、丰富的数据类型

1)String 字符串类型
是redis中最基本的数据类型,一个key对应一个value。
底层数据结构:简单动态字符串(SDS)
/*
* 保存字符串对象的结构
*/
struct sdshdr {
// buf 中已占用空间的长度
int len;
// buf 中剩余可用空间的长度
int free;
// 数据空间
char buf[];
};
- SDS具有常数复杂度获取字符串长度
- 杜绝了 缓存区的溢出
- 减少了修改字符串长度时所需的 内存重分配次数
- 二进制安全能存储各种类型的文件
实战场景:
-
缓存: 做简单的 KV 缓存。经典使用场景,把常用信息,字符串,图片或者视频等信息放到redis中,redis作为缓存层,mysql做持久化层,降低mysql的读写压力。
-
计数器:redis是单线程模型,一个命令执行完才会执行下一个,同时数据可以一步落地到其他的数据源。
2)Hash
是一个Mapmap,指值本身又是一种键值对结构,如 value={{field1,value1},......fieldN,valueN}}
底层数据结构:压缩表+哈希表
实战场景:
-
更适合对象的存储 可以用来存储用户信息
-
这里value存放的是结构化的对象,比较方便的就是操作其中的某个字段。
3)list
List 说白了就是链表(redis 使用双端链表实现的 List),是有序的,value可以重复,可以通过下标取出对应的value值,左右两边都能进行插入和删除数据。
底层数据结构:双向循环链表+压缩表
实战场景:
- 简单队列
- 微博关注人时间轴列表
4)set
集合类型也是用来保存多个字符串的元素,但和列表不同的是集合中
- 不允许有重复的元素,
- 2.集合中的元素是无序的,不能通过索引下标获取元素,
- 3.支持集合间的操作,可以取多个集合取交集、并集、差集。
底层数据结构:哈希表+整数列表
实战场景:
- 共同关注、共同爱好
- 推荐好友
5)zset
有序集合和集合有着必然的联系,保留了集合不能有重复成员的特性,区别是,有序集合中的元素是可以排序的,它给每个元素设置一个分数,作为排序的依据。
底层数据结构:压缩表+跳表
实战场景:
- 排行榜, 榜单可以按照用户关注数,更新时间,字数等打分,做排行。
6) Geospatial(地理位置) 两人之间的距离
7) Hyperloglog 基数统计
8) Bitmap 位存储、活跃不活跃、登录不登录、打卡不打卡
3、持久化方式
1) RDB(快照方式snapshotting)(全量持久化)
RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘, 实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。
三种触发机制:
- save触发方式(该服务会阻塞当前的Redis服务器,执行save命令期间,Redis不能处理其他命令,直到Rdb过程完成为止)
- bgsave触发方式 ( redis后在后台异步进行快照操作,快照同时还可以响应客户端请求 )
- 自动触发( 满足保存条件时将会把数据持久化到硬盘 )
- save 900 1:表示 900 秒内如果至少有 1 个 key 值变化,则把数据持久化到硬盘;
优点:
- 性能比较高
- 生成RDB文件的时候,redis主进程会fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作
- RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快
缺点:
- 不能保证数据的完整性
- 系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。
2) AOF(append-only-file)(增量持久化)
AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。
三种触发机制:
-
每修改同步always:同步持久化 每次发生数据变更会被立即记录到磁盘 性能较差但数据完整性比较好
-
每秒同步everysec:异步操作,每秒记录 如果一秒内宕机,有数据丢失
-
不同步no:从不同步
优点:
- 该机制可以带来更高的数据安全性,即数据持久性 (一般AOF会每隔1秒,通过一个后台线程执行一次fsync操作,最多丢失1秒钟的数据 )
- 写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容
缺点:
- AOF文件通常要大于RDB文件,在数据恢复时要慢
- 运行效率慢
4、过期回收策略和内存淘汰机制
1)过期回收策略(删除超过过期时间的Redis数据)
-
定时过期:设置过期时间的key会创建一个定时器,到过期时间就会立即清除。
- 优点: 可以立即清除过期的数据,对内存友好
- 缺点: 占用大量的CPU资源处理过期的数据,影响Redis缓存的响应时间和吞吐量。
-
惰性过期: 只有当访问一个key时,才会判断该key是否已过期,过期则清除。
- 优点: 可以最大节省CPU资源。
- 缺点: 可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
-
定期过期:每隔一定的时间,会扫描一定数量的expires字典中的key,并清除其中已过期的key。
- 前两者的一个折中方案
2)淘汰机制(当内存使用到达最大内存(maxmemory)上限时触发内存淘汰策略)
1、allkeys-lru:当内存大小不能容纳新写入数据时,在键空间中移除最近最少使用的键(key)。(默认)
2、allkeys-random:当内存大小不能容纳新写入数据时,在键空间中,随机移除某个键(key)。
3、volatile-lru: 当内存大小不能容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的键(key)。
4、volatile-random:当内存大小不能容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个键(key)。
5、volatile-ttl:当内存大小不能容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的键(key)优先移除。
6、noeviction:当内存大小不能容纳新写入数据时,新写入数据会报错。
5、主从复制机制
1)同步
全量同步:一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份
-
从服务器连接主服务器
-
主服务器生成RDB文件并使用缓冲区记录此后执行的所有写命令
-
主服务器把快照文件发送给所有的从服务器
-
从服务器收到快照文件后丢弃所有旧数据,载入收到的快照
-
主服务器发送完快照之后开始向服务器发送缓冲区中的写命令
-
从服务器完成快照的载入之后,开始接收命令请求,并执行来自主服务器缓冲区的命令
增量同步: 主服务器发生的写操作同步到从服务器的过程
- 主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。
主从同步策略:
主从刚刚连接的时候,就行全量同步,全同步结束后,进行增量同步。当然,如果有需要,slave在任何时候都可以发起全量同步。redis策略是,无论如何,首先会尝试进行增量同步,如果不成功,要求从机进行全量同步。
2)作用
-
数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
-
故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务
的冗余。 -
负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务,分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
3)哨兵
自动选举老大的模式, 如果故障了根据投票数自动将从库转换为主库
6、事务
- 一组命令的集合! 一个事务中的所有命令都会被序列化,在事务执行过程的中,会按
照顺序执行! - Redis事务没有没有隔离级别的概念!
- Redis单条命令式保存原子性的,但是事务不保证原子性!
- 事务中任意命令执行失败,其余的命令依然被执行。
7、常见四大问题
1) 缓存穿透
原因:查询没有的数据 ,redis中查不到,数据库中也查不到,本次查询失败。
解决方案 :
- 布隆过滤器
- 组成: 一个很长的二进制向量和一堆散列函数
- 实现原理: 当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。
- 实现原理:
- 优点: 空间效率和查询时间都远远超过一般的算法
- 缺点: 有一定的误识别率和删除困难
- 缓存空对象
- 当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源
2) 缓存击穿
原因: 热点数据过期, 当某个热点key在过期的瞬间,有大量的请求并发访问,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导致数据库瞬间压力过大
解决方案:
- 设置热点数据永不过期, 不设置过期时间,热点key就不会产生过期问题
- 加互斥锁,每个key同一时间只有一个线程去查询后端数据
3) 缓存雪崩
原因: 海量数据, 在某一时间段,缓存集中过期失效,Redis宕机!
解决方案:
- redis 高可用,搭建集群,一台redis挂掉,其他redis顶上来
- 限流降级, 在缓存失效后,通过加锁或者对来来控制读数据库写缓存的线程数量
- 数据预热
- 在正式部署前,把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。
- 在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
4) 一致性问题
原因:数据库和缓存数据不一致
解决方案:
- 延时双删的解决办法
- 线程1删除缓存,然后去更新数据库
- 线程2来读缓存,发现缓存已经删除,所以直接从数据库中读取,这个时候,由于线程1还没有更新完成,所以读到的是旧值,然后把旧值写入缓存
- 线程1 根据估算时间, sleep 由于Sleep 的时间大于线程2 读数据+写缓存的时间,所以缓存再次被删除
- 如果还有其他线程来读取缓存的话,就会再次从数据库中读取到最新值
- 更新数据库产生的binlog订阅(使用canal)
- 先更新数据库,成功后往消息队列发消息,消费到消息后再删除缓存,借助消息队列的重试机制来实现,达到最终一致性的效果。

浙公网安备 33010602011771号