文章目录
- 一、Redis为什么快?
- 二、Redis合适的应用场景
- 三、Redis为什么6.0之前不支持多线程
- 四、Redis为什么6.0之后引入多线程
- 五、Redis有哪些高级功能
- 六、为什么需要使用Redis
- 七、Redis的事务
- 八、Redis的过期策略以及内存淘汰机制
- 九、什么是缓存穿透?如何避免?
- 十、什么是缓存雪崩?如何避免?
- 十一、缓存击穿
- 十二、Redis如何设计分布式锁
- 十三、什么是bigkey?会有什么影响?
- 十四、Redis如何解决key冲突
- 十五、怎么提高换成命中率
- 十六、Redis持久化方式有哪些方式?有什么区别?
- 十七、为什么Redis需要把所有数据放到内存中?
- 十八、如何保证缓存与数据库双写一致性?
- 十九、Redis集群方案
- 二十、Redis集群方案什么情况下会导致整个集群不可用?
- 二十一、Redis集群会有写操作丢失吗?为什么?
- 二十二、Redis常见性能问题和解决方案
- 二十三、热点数据和冷数据
- 二十四、什么情况下可能会导致Redis阻塞
- 二十五、线上Redis响应慢处理思路
- 二十六、 Redis 高可用
- 二十七、布隆过滤器
- 二十八、Redis的理解
一、Redis为什么快?
1、基于内存
内存读写效率远高于磁盘读写,省去磁盘IO操作
2、 存储形式
Redis作为K-V键值对型的内存数据库,所有键值都是用字典来存储,即哈希表结构。哈希表的特性就有在O(1)时间复杂度就可以获取对应的值。
3、 编码
支持多种数据结构及编码,针对不通业务场景,都有相对应的数据结构和编码。
根据元素的数量,有一个阈值,小于阈值和大于阈值的编码不同。
4 IO多路复用
核心思想:让单个线程去监视多个连接,某个连接就绪,就触发读写事件。即可以单个线程处理多个客户端连接,无需创建和维护过多的进程和线程。
5、单线程,避免上下文切换
内部执行命令为单线程,避免上下文切换带来的CPU开销
6、渐进式ReHash、缓存时间戳
(1)渐进式ReHash:
Redis使用全局哈希表来保存所有键值对,
哈希表相当于一个数组,数组的每个元素称为一个哈系桶,每个哈系桶中保存了键值对的数据。
数据增加到一定阈值,数组扩容会导致数据发生移动,此时访问会发生阻塞
渐进式ReHash:把一次性大量拷贝(数组移动)的开销,分摊到多次处理请求的过程中。
Redis默认使用两种全局哈希表,开始插入数据时默认使用哈希表1,此时哈希表2并没有被分配空间。随着数据逐步增多,开始执行ReHash。
- 给哈希表2分配更大的空间,
- 将哈希表1中的数据重新映射并拷贝到哈希表2中
- 释放哈希表1的空间
7、缓存时间戳:
业务中需要用到时间戳时,一般会使用System.currentTimeMillis()或者New Date()等方式获取系统的毫秒时间戳,每一次获取都是一次系统调用(需要调用操作系统中对应的函数,涉及上下文切换),相对比较耗时。
作为单线程的Redis承受不起,因此它由一个定时任务,每毫秒更新一次缓存,获取时间都是从缓存中直接拿。
二、Redis合适的应用场景
常用基本数据类型(5种)
名称 | 英文名 | 作用域 |
---|---|---|
字符串 | String | 缓存、计数器、分布式Session |
哈希 | Hash | 存放对象 |
列表 | list | 消息队列、文章列表 |
集合 | set | 标签、随机数、社交图谱 |
有序集合 | ZSET | 排行榜 |
Bitmaps | Bitmaps | 布隆过滤器 |
HyperLogLog | HyperLogLog | UV |
Geo | Geo | Redis3.2推出的,地理位置定位,用于存储地理位置信息,并对存储的信息进行操作 |
1、字符串(String)
命令的时间复杂度:
字符串这些命令中除了del、mset、mget支持多个键的批量操作,时间复杂度和键的个数相关,为O(n),getrange的字符串长度相关,也是O(n),其余的命令基本上都是O(1)的时间复杂度,在速度上还是非常快的。
(1)缓存
具有支撑高并发的特性,能起到加速读写的作用,降低后端压力
(2)计数器
实现快速计数、查询缓存的功能,同时数据可以异步落地到其他数据源
(3)分布式会话(共享Session)
问题:如果一个分布式Web服务将用户的Session信息保存在各自服务器中,出于负载均衡的考虑,分布式服务会将用户的访问均衡到不同服务器上,用户刷新一次访问,可能会发现需要重新登录。
解决方案:使用Redis将用户的Session进行集中管理,这种情况下只要保证Redis是高可用和扩展性的,每次用户更新或查询登录信息都直接从Redis集中获取。
(4)分布式锁
场景:同一个资源并发访问,如秒杀、下单减库存等场景。适用String数据类型。
- synchronize和reentrantlock本地锁性能较低
- 并发量不大,可以使用数据库悲观锁,但并发量大的场景会降抵DB性能
- 可以使用Redis Setnx来实现分布式锁
2、哈希(Hash)
适用于存放对象,相较于String类型存储对象时效率开发效率更高。
3、列表(list)
用来存储多个有序字符串
(1)消息队列
lpush+brpop命令组合即可实现阻塞队列,生产环境客户端使用lpush从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞式的”抢“列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性。
(2)文章列表
每个用户有属于自己的文章列表,现需要分页展示文章列表。此时可以考虑使用列表,有序,且支持按照索引范围获取元素。
还可实现其他数据结构
4、集合(set)
(1)标签(tag)
例如一个用户可能对娱乐、体育感兴趣,另一个用户可能对历史、新闻感兴趣,这些兴趣点就是标签。通过这些数据可以得到喜欢同一个标签的人,这些数据对于用户体验以及增强用户粘度比较重要。
(2)随机数(抽奖活动)
(3)社交网络
点赞、粉丝、共同好友、喜好、推送、下拉刷新等功能。适用String数据类型和hash类型,存放对象使用hash类型。
标签适用于set数据类型,例如一个用户可能对娱乐、体育感兴趣,另一个用户可能对历史、新闻感兴趣,这些兴趣点就是标签。通过这些数据可以得到喜欢同一个标签的人,这些数据对于用户体验以及增强用户粘度比较重要。
排行榜适用于有序集合(ZSET)数据类型。
5、有序集合(ZSET)
(1)排行榜
多维度:时间、浏览量、获赞数等等。
Redis高级数据结构
6、Bitmaps
可以实现对位的操作,单独提供了一套命令,可以想象成以位为单位的数组,数组下标叫做偏移量。
用于数据量上亿的场景下,例如几亿用户系统签到,去重登录次数统计,某用户是否在线状态等。此时不可能给每个用户一个key。
这里需要用到位操作,使用setbit、getbit、bitcount命令。
原理:redis内构一个足够长的数组,每个数组只能是0和1两个值,数组下标表示用户id,这个几亿长的大数组就可以通过下标和元素值(0和1)来构建一个记忆系统。
(1)布隆过滤器
7、HyperLogLog
(1)UV
统计每个网页每天的UC数据,HyperLogLog提供不精确的去重计数方案,误差0.81%
三、Redis为什么6.0之前不支持多线程
1、Redis的瓶颈不是CPU,受制于内存、网络
存储于内存,快速读写网络开销大
2、提高Redis性能,Pipeline(命令批量)
每秒100万个请求包装进Pipeline
3、单线程,内部维护成本相对较低,不需要管理多线程安全
命令执行顺序不确定性,读写并发问题
4、多线程(线程切换、加锁/解锁、导致死锁问题)
5、惰性Rehash(渐进式)减少阻塞
一般的公司,单线程Redis就够了。
四、Redis为什么6.0之后引入多线程
1、小数据包。数据-》内存 响应时间 100ns 8w-10wQPS(极限)
2、针对大的公司,需要更大的QPS,IO的多线程(内部执行命令还是单线程)
3、为什么不采用分布式架构—很大的缺点。
服务器数量多,维护成本高。Redis命令 不适用 需要数据分区,无法解决热点数据读写的问题。
数据倾斜、重新分配、扩容、缩容,更加复杂。
本质:多线程任务 分摊到Redis 同步IO中,读写负载。
五、Redis有哪些高级功能
(1)慢查询
快速定位系统中的慢操作,监测发生时间、耗时、命令的详细信息。
(2)Pipeline
(3)watch命令:
确保事务中的key有没有被其他客户端修改过,才执行事务,否则不执行(类似于乐观锁)。
(4)Redis+Lua语言实现限流
(5)分布式锁
首先需要Redis有互斥的能力,可以使用SETNX命令,(即如果key不存在,才会设置它的值,否则什么也不做。两个客户端进程可以执行这个命令,达到互斥,就可以实现一个分布式锁。
锁的过期时间不好计算
解决方案:分布式锁加入看门狗
加锁时,先设置一个过期时间,然后开启**“守护线程”**,定时检测这个锁的失效时间,如果快要过期了,操作共享资源还未完成,则自动对锁进行续期,重新设置过期时间。
(6)高并发高可用
主从复制:
提供了复制功能,实现了相同数据的多个Redis副本。每个主节点可以对应多个从节点,复制的数据流只能由主节点复制到从节点。
(7)哨兵:Redis Sentinel
背景:主从复制模式下,主节点故障,需要人工将从节点晋升为主节点。
2,8版本开始提供哨兵架构解决此问题。
主从复制的问题
- 需要手动晋升子节点,同时需要修改应用方的节点地址。
- 主节点的写能力收到单机限制
- 主节点的存储能力收到单机的限制
六、为什么需要使用Redis
1、高性能
Mysql(磁盘)毫秒级
Redis(内存)微秒级
更新策略:项目启动时全量同步:热点数据
2、高并发
Mysql 并发量:1000/s
Redis 并发量:100000/s
集群架构
七、Redis的事务
本质:按照顺序串行化执行队列中的每个命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
顺序性、一次性、排他性执行一个队列中的一系列命令。
命令 | 描述 |
---|---|
EXEC | 执行所有事务块命令 |
DISCARD | 取消事务,放弃执行事务块所有命令 |
MULTI | 标记一个事务块,开启事务 |
UNWATCH | 取消WATCH命令对所有key的监视 |
WATCH | 监视key,如果在事务执行之前,该key被其他命令改动,那么事务将被打断 |
//构建redis连接
Jedis jedis = jedisPool.getResource();
//标记一个事务块,开启事务
Transaction transaction = jedis.multi();
try {
transaction.set("name1","江先进");
// int n = 10/0;
transaction.set("name2","张硕");
//执行事务
transaction.exec();
}catch (Exception e){
//取消事务
transaction.discard();
e.printStackTrace();
}
回滚机制上,Redis只能对基本语法错误进行判断。运行时错误无法回滚。
八、Redis的过期策略以及内存淘汰机制
1、内存淘汰机制
-
定期删除(定时扫描策略)
设置了过期时间的key放入独立字典,Redis默认会每秒进行十次过期扫描,不会遍历Key,而是采用简单的贪心策略。
- 从过期字典中随机20个key;
- 删除其中已经过期的;
- 如果过期比例超过1/4,则重复真个步骤;
一定要注意过期时间,如果大批量key过期(雪崩),需要给过期时间设置一个时间范围,不能全部同一时间过期
-
惰性删除
客户端访问key的时候,redis对key的过期时间进行检查,如果过期就立即删除,不会返回任何东西。
总结:定期删除是集中处理,惰性删除是零散处理。
2、过期策略
- 定时过期:每个设置过期时间的key都需要创建一个定时器,到期清除key。该策略会立即清除过期的数据,对内存友好,但是会占用大量CPU去处理过期数据,影响吞吐量
- 惰性过期:只有访问一个key时,才会判断是否已过期,过期则清除。最大化节省CPU资源,对内存不友好。可能会导致大量过期的key因未被访问而无法清除。
- 定期过期:每隔一定时间,扫描一定数量的key,并删除其中过期的,通过调整定时扫描的时间间隔和扫描限定耗时,使CPU和内存达到一个最佳平衡状态。
Redis中同时使用了惰性过期和定期过期两种策略。
每隔100ms就随机抽取一定量key,检查和删除。同时获取key时,会检查一下是否过期,过期则删除。
隐患:同样可能定期删除,漏删了大量过期key,也没有走惰性删除,就会导致大量过期key堆积在内存。
3、缓存淘汰算法
Redis 缓存淘汰算法用于在内存资源不足时,决定哪些数据需要从缓存中移除。Redis 提供了多种策略以应对不同的应用场景:
-
volatile-lru(LRU for volatile keys):
- 当内存不足以容纳新写入的数据时,会优先剔除已设置过期时间的键值对,并根据LRU原则删除最近最少使用的数据。
-
allkeys-lru:
- 不区分是否设置了过期时间,所有键都会根据LRU规则淘汰,即删除最近最少使用的数据。
-
volatile-ttl:
- 同样针对设置了过期时间的键,但淘汰时不是基于访问频率而是基于键的剩余存活时间(TTL),选择TTL最短的键进行淘汰。
-
noeviction:
- 不进行任何数据淘汰,当内存满时,执行可能导致占用更多内存的命令将返回错误。
-
allkeys-random:
- 随机淘汰任意键,不论其是否被频繁使用或何时到期。
-
volatile-random:
- 淘汰一个随机的已设置过期时间的键。
值得注意的是,Redis 实现的 LRU 算法并不是精确的 LRU,因为为了性能考虑,它并没有真正维护一个完整的 LRU 链表结构。Redis 采用了一种近似 LRU 的实现方法:通过为每个键维护一个 lru
字段来记录最后一次访问的时间戳,在内存不足时,Redis 会选择一定数量(比如 5 个)的候选键,然后比较这些候选键的 lru
值,选择其中最小的一个进行淘汰。这样可以降低维护 LRU 数据结构的成本,同时在大部分情况下能够达到类似 LRU 的效果。
九、什么是缓存穿透?如何避免?
本质:数据库和Redis都不存在
场景:查询id为-1的数据
坏处:每次都需要查询数据库和redis,增加磁盘IO的压力
解决方案:
- 参数校验、屏蔽非法参数
- 数据库查询为空,可以给缓存一个空值或默认值,防止第二次再去数据库
- 使用布隆过滤器快速判断数据是否存在,将所有可能存在的数据哈希存到一个足够大的容器中,不存在的数据被这个bitmap拦截掉
布隆过滤器的应用:
十、什么是缓存雪崩?如何避免?
本质:数据库和Redis都存在,但redis都过期了
同一时间,缓存大面积失效,大量请求都直接去访问数据库
原因:
1、Redis失效、宕机(故障)
- 搭建Redis集群,主从架构
- RDB持久化、IOF持久化
- 加入缓存组件:EHCache,搭建多级缓存(容易高并发的数据存入)
- 加入限流组件:hystrix,超过一定流量后,增加请求限制(保护数据处理层)
2、Redis大量key的ttl过期
- ttl(过期时间)岔开,增加随机值,避免同一时间全部失效。
解决方案:
- 热点数据永不过期,或者通过异步线程在每次热点数据快要过期时,进行续期
- 数据的过期时间不要全一致,设为一定范围内的随机时间
- 并发量不高,可加入队列或者锁,限制同一时间访问数据库的阈值
- 分布式部署,将热点数据打散分不到多个节点
- 如果是缓存中间件宕机了,需要尽可能保证其高可用性,可以搭建redis集群,提前做好报警机制
十一、缓存击穿
本质:单个key没有或过期,同一时间查询这同一条数据并发量过多
解决方案:
- 热点数据用不过期,或者通过异步线程在每次热点数据快要过期时,进行续期
- 使用互斥锁,避免大量请求同时查询数据库
- 熔断、降级、防止系统崩溃
- 还可以考虑对重要的热点数据进行多级缓存
十二、Redis如何设计分布式锁
概念:
- 锁:同一时间只允许一个线程或者一个应用程序进入执行
- 分布式锁:必须要求Redis有【互斥】能力,可以使用SETNX命令:即key不存在了才会设置它的值,否则什么也不做。
注意事项:
- 如果过期时间是每个服务自己生成,需要保证每个客户端时间同步。
- 必须保存持有者唯一标识,否则可能被别的客户端释放/解锁。
- 给锁设置过期时间,以免进程挂了或异常了无法释放锁。
- 加入看门狗:开启守护线程,定期检测锁的失效时间,如果快过期了,业务还没有执行完,则续期。
看门狗:开源框架Redisson,只要线程一个线程加锁成功,就会启动一个watch dog,每隔10秒检查一下锁是否释放,只要第一个线程还持有锁,就延长锁的失效时间,解决了锁过期但业务还没执行完的问题。
十三、什么是bigkey?会有什么影响?
1、概念:
key对应的value所占内存空间较大
例如一个字符串类型的value最大存到512M,一个列表类型的value最大可以存储2的32次方-1个元素。
2、字符串类型:
体现在单个value值特别大,一般认为超过10kb就是bigkey,和具体OPS相关(不同系统不同并发)。
3、非字符串类型:
哈希、列表、集合、有序集合,体现在元素个数过多。
4、危害:
-
内存空间不均匀
-
超时堵塞:单线程操作bigkey比较耗时
-
网络拥塞:每次获取bigkey产生的网络流量较大
例如:一个bigkey为1MB,每秒访问为1000,则每秒产生1000MB的流量,普通千兆网(按照字节算是128MB/s)的服务器是灭顶之灾,而且服务器通常会采用单机多实例的方式来部署,可能会对其他实例造成影响。
5、解决方案:value拆分
十四、Redis如何解决key冲突
1、业务隔离
2、key的设计
业务模块+系统名称+关键(id),针对用户可以加入(userid)
3、分布式锁
场景:多个客户端并发写key
客户端拿到锁,才能进行操作,避免多个客户端竞争该key
4、时间戳
key拼接时间戳,根据时间戳保证多个客户端的业务执行顺序
十五、怎么提高换成命中率
1、提前加载
2、增加缓存的存储空间,增加缓存的数据
3、调整缓存的存储类型
例:对象通过Hash存储,而不用String。
根据业务做适当调整。
4、调整缓存的存储类型
- 定时任务更新
- MySQL通过检测binlog,将消息推送到Redis,更新缓存
- 通过Mq,业务更新修改数据时,通过MQ发送消息,消费更新缓存
十六、Redis持久化方式有哪些方式?有什么区别?
16.1 持久化
将数据写入磁盘,避免因进程退出而造成的数据丢失,下次重启时通过持久化文件恢复数据。
16.2 RDB
通过快照(内存中数据在某一时刻的状态记录)的方式实现持久化,根据快照的触发条件,将内存的数据快照写入磁盘,以二进制的压缩文件进行存储。
缺点:每隔一段时间触发持久化,数据安全性低。
16.3 AOF
以独立日志的方式记录每次写的命令,重启时重新执行AOF文件中的命令恢复数据
AOF重写机制:AOF文件的大小达到某个阈值时,会将其中指令进行压缩。(如果有对于某个key多次的变更指令,则仅保留最新的数据指令)。
优化:
- 因为AOF重写过程中需要读取当前内存中所有键值数据,性能较低,redis将其放在一个后台子线程中完成。
- 为了避免重写过程中出现数据变动,主进程的数据变更需要追加到AOF重写缓冲区中,等到AOF重写完成后,再把AOF重写换乘区里面的内容追加到新的AOF文件中。
缺点:AOF文件可能过大,性能较差
4、混合式
如果执行bgrewriteaof命令,将内存中已有的数据以二进制格式存放在AOF文件中(模拟RDB),后续命令亦然采用AOF追加方式。
生产环境中一般采用两种持久化机制混合使用。
将内存中数据快照存储在AOF文件中(模拟RDB),后续再以AOF追加方式。
如果仅作为缓存使用,可以承受几分钟数据丢失,可以使用RDB,对主程序性能影响最小。
十七、为什么Redis需要把所有数据放到内存中?
1、内存访问与磁盘访问的差距:
- 几乎是10倍以上,如果不是顺序读取而是随机读取效率会相差更大
- 同时还有CPU上下文切换的开销
2、Redis通过异步,持久化将数据写入磁盘
3、随着技术的发展,硬件上来说内存也越来越便宜了
4、默认情况下,哪怕Redis内存不够了,也不会发生宕机,而是只可读不能写(Noeviction策略)
5、通过内存淘汰策略,确保整体服务正常运行
十八、如何保证缓存与数据库双写一致性?
1、新增数据类
新增数据时,数据会直接写入数据库,不用对缓存做任何操作;此时缓存没有新增数据,而数据库中是最新值。
2、更新缓存类
(1)先更新缓存,在更新DB(一般不考虑)
原因:缓存更新成功,更新数据库时出现异常,会导致数据源与缓存数据完全不一致,而且很难察觉,因为缓存中的数据一直都存在。
(2)先更新DB,在更新缓存(一般不考虑)
原因:数据库更新成功了,缓存更新失败了,同样会导致数据源与缓存数据完全不一致,也很难察觉。
3、删除缓存
(3)先删除缓存,后更新DB
问题:
两个请求:A(更新)和B(查询)
A -> 删除缓存中的数据 -> 更新数据库
B -> 查询缓存为空 -> 查询数据库 -> 补录到缓存
A -> 还未更新成功/事务还未提交,B -> 查询到的其实是数据库旧值
解决方案:
- 先淘汰缓存
- 再写数据库
- 休眠1秒,再次淘汰缓存
这个休眠的时间需要评估项目的读数据业务逻辑的耗时,确保请求结束时,写请求可以删除读请求造成的缓存脏数据。
(4)先更新DB,后删除缓存
查询:先读缓存 -> 缓存没有就读数据库 -> 取出数据放入缓存 -> 同时返回响应。
更新:先更新数据库 -> 删除缓存
4、如何选择
一般线上更多偏向于删除缓存类操作(容易避免问题)
原因:
- 删除缓存比在DB中要快,所以一般先更新DB,后删除缓存
- 问题只会出现在查询比删除慢的情况,出现率相对最少
- 同时延迟双删可以有效避免缓存不一致情况。
伪代码实现延迟双删:
redis.deykey(X)
db.update(X)
Thread.sleep(N)
redis.delKey(X)
5、读取binlog日志
异步删除、更新缓存,
- 可以使用canal将binlog日志采集发送到MQ队列中。
- 通过ACK机制确认处理这条更新消息,删除缓存,保证数据一致性。
十九、Redis集群方案
1、分布式解决方案 :Redis Cluster
3.0版本推出
场景:单机内存、并发、流量等瓶颈
方案:
(1)客户端分区:
优点:分区逻辑可控
缺点:需要处理数据路由、高可用、故障转移等问题
(2)代理方案:
优点:简化客户端分布式逻辑,升级维护便利
缺点:加重架构部署复杂度和性能损耗
2、虚拟槽分区(0~16383)
主节点数量基本不可能超过1000个,节点连接需要不断发送ping/pong命令,消耗网络带宽
3、集群功能限制
(1)key批量操作支持有限:mset、mget仅支持相同slot值的key。
(2)key事务操作支持有限:仅支持在同一节点上的事务操作。
(3)key作为数据分区的最小颗粒度,不允许大的键值对(hash、list)映射在不同节点。
(4)不支持多数据库空间。单机为0-15(16个),集群模式仅能使用db0。
(5)复制结构仅支持一层,节点只能复制到主节点,不支持嵌套树状复制结构。
4、搭建集群
方式:
(1)Redis协议手工搭建。
(2)5.0之前有ruby语言脚本搭建。
(3)5.0之后搭建功能合并至redis-cli。
节点数至少奇数点个,官方推荐三主三从。
二十、Redis集群方案什么情况下会导致整个集群不可用?
A、B、C三个节点集群,B节点失败(主故障,且没有替代方案)整个集群都是不可用的。
集群不可用判定:
保护措施:默认情况下当16384个槽点任何一个没有指派到节点时,整个集群不可用。
主节点下线->故障发现->自动完成转移期间,整个集群为不可用状态。
可用通过设置cluster-require-full-coverage配置为no:主节点故障时,不影响其他主节点的可用性。
二十一、Redis集群会有写操作丢失吗?为什么?
Redis无法保证数据的强一致性
一般只能向主节点写入数据,再异步同步到子节点
此时如果响应给客户端后还未异步同步成功时,主节点宕机了,子节点升至主节点,此时就会出现写入操作丢失。
二十二、Redis常见性能问题和解决方案
1、持久化 性能问题
早期仅支持全量复制->部分复制(一台机器性能开销过大)
因此开始配置主从 :主节点不再做持久化而是交给从节点来做
2、数据比较重要,开启AOF。策略最好配置每秒同步。
3、主从复制 流畅,建议同一个局域网内操作,负责网络开销过大
4、尽量避免主库压力过大,增加从库
5、主从复制 尽量不要使用网状结构、线性结构
二十三、热点数据和冷数据
1、热数据
访问频次较高,考虑使用缓存Redis
地图信息
点赞数、收藏数、分享数(不断变化)同步Redis
数据更新之前至少读取2次才能放缓存
2、冷数据
访问频次少
不需要放缓存
二十四、什么情况下可能会导致Redis阻塞
1、客户端阻塞
命令执行时间过长: keys* Hgetall smembers 时间复杂度O(N)
2、BIGkey删除
需要释放大量占用内存 zset(100万的元素 删除大概需要2s)
3、清空库
flushdb flushall 涉及删除所有键值对
4、AOF日志同步写,记录AOF日志
大量写的操作
1一个同步写磁盘操作大概耗时1~2ms
5、从库 加载RDB文件
RDB文件过大
6、Redis尽量部署在独立的服务器中
二十五、线上Redis响应慢处理思路
-
1、紧急处理方案,扩容
-
2、生产环境查看Redis内存使用率,分析一定时间段内key数量变化
分析是否是大量数据未设置过期时间,或者是因为新版本迭代引起
-
3、清除bigkey,优化生成bigkey的代码块,调整未设置过期时间的代码块
-
4、根据业务场景调整淘汰策略
二十六、 Redis 高可用
高可用:数据不能丢失(尽量减少丢失),保证Redis
26.1 主从模式
- 部署多台Redis服务器,主从复制以保证数据副本一致。主库通过将RDB文件发送给从库实现复制。
- 主从之间采用读写分离,主库写操作,从库仅负责读操作。
- 主库如果宕机了,人工切换一台从库称为主库,通知应用方更新主节点地址。
问题:数据不一致。
原因:主从库网络延迟,从库接收到命令,但它正在执行阻塞性命令。
解决方法:保证网络通畅,监控主从库复制进度。
26.2 哨兵模式
Redis从2.8开始提供哨兵机制。
####26.2.1 作用
- 监控:周期性ping主从库,检测是否挂了,标记下线状态。
- 自动选主切换:多个从库中按照一定规则选一个作为主库。
- 通知:选出主库后,将新主库的连接信息发送给其他从库以及应用方,重新建立联系。
26.2.2 哨兵
由一个或多个哨兵实例组成哨兵系统,监控其他Redis节点的同时,哨兵实例之间也互相监控。
哨兵之间通过发布订阅机制组成集群。一主多从
缺点:无法实现在线扩容,并发压力受限于单个服务器的资源配置。
26.3 Redis Cluster
哨兵模式解决了自动切换主从的问题,但是没有解决在线扩容的问题。
本质:Redis Cluster实现了Redis的分布式存储,每个节点存储不同的数据,实现数据分片。
引入Slot槽实现数据分片,每个节点分配一个Slot区间,当我们存取Key的时候,Redis根据key计算得到Slot值,找到对应的节点进行读写。多主多从。
二十七、布隆过滤器
布隆过滤器可以应对缓存穿透问题
数据结构:一个很长的二进制向量和一组Hash映射函数组成。
作用:检索一个元素是否在一个集合中,空间查询效率比一般的算法要好的多。
缺点:有一定误识别率和删除困难。
原理:集合A中有n个元素,利用K个哈希散列函数,将A中每个元素映射到一个长度为a位的数组B中不同位置上。这些位置上的二进制数均设置为1。经过这个K个哈希散列函数映射后,发现其k个位置上的二进制数全部为1,这个元素很可能属于集合A。
二十八、Redis的理解
本质:基于Key-Value存储结构的非关系型数据库
数据类型:提供5种基本数据类型,String、set、zset、list、Hash。
性能:基于内存存储,并且在数据结构上做了大量优化,IO性能较好。
作用:作为应用与数据库之间的分布式缓存组件。
高可用:提供了主从复制、哨兵、以及集群方式实现高可用,集群中通过hash槽的方式实现了数据分片,进一步提升了性能。