a探索之路--redis

redis

https://www.processon.com/mindmap/60827d6ce401fd45d70648b3

1.Redis缓存穿透,缓存击穿,缓存雪崩

1.1 Redis缓存穿透,缓存击穿,缓存雪崩是什么 和对应解决方法

问题解释解决方法
缓存穿透key对应的数据在缓存和数据库不存在,黑客一直用这个信息请求,可能压垮数据源1、布隆过滤器 2、针对这个key设置值为null的数据存入缓存中
缓存击穿某个key失效,此时出现大量并发请求(热点数据)使⽤互斥锁(mutex key) :SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置
缓存雪崩大量key在某一集中时间段缓存失效或者缓存服务器重启1、在原有的失效时间基础上增加⼀个随机值,比如1-5分钟随机(加锁排队解决分布式环境高并发问题但是用户体验很差)2、redis故障:搭建集群主从 3.加hystrix限流 4.加多级缓存Ehcache
  • 缓存穿透:布隆过滤器(删除困难,因为有hash冲突)https://blog.csdn.net/qq_41125219/article/details/119982158
    布隆过滤器算法:
    用来判断一个元素是否在一个集合中:该算法由一个二进制数组和hash算法组成
    使用bitmap数据结构,一个二进制数组,存在是1,不存在是0,因为不同元素经过hash算法取余后在数组下标一样,产生hash冲突。
    产生误判问题:
    通过hash计算在数组上不一定在集合(hash冲突)【增大数组,或者经过两次hash计算】
    通过hash计算不在数组上的数据一定不在集合

  • 缓存击穿互斥锁代码

// 伪代码
public String get(key) {
 String value = redis.get(key);
 if (value == null) { //代表缓存值过期
 //设置3min的超时,防⽌del操作失败的时候,下次缓存过期⼀直不能load db
 if (redis.setnx(key_mutex, 1, 3 * 60) == 1) { //代表设置成功
 value = db.get(key);
 redis.set(key, value, expire_secs);
 redis.del(key_mutex);
 } else { //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓
存值即可
 sleep(50);
 get(key); //重试
 }
 } else {
 return value; 
 }
 }

1.2 如何使用redis设计分布式锁

使用setnx
如何避免死锁:给这个key设置一个过期时间(ttl)
锁过期时间不好评估:解决方法,分布式锁加入看门狗,通过开启守护线程定期扫描这边锁,如果锁快要过期,而资源还没有释放,就给锁续期

1.3 如何使用redis实现消息队列

  • 基于List:消费者难以做ack确认
  • 基于Zset :实现延迟队列,消费者没有办法阻塞,只能轮询,不允许重复消息
  • PUB/SUB 订阅/发布:会导致消息丢失,只可做即时通讯
  • 基于Stream(一般使用这种方式实现消息队列)

1.4 Redis并发竞争key的解决方案(缓存击穿)

  • 1. 分布式锁
    • 基于zookeeper,redis,数据库 实现
      分布式锁的特点 :
      1. 互斥性:只有一个客户端持有锁
      2. 无死锁:持有锁的客户端崩溃或发生意外,其他客户端仍然可以获取锁
      3. 容错:大部分redis节点存活就可以获取锁和释放锁
  • 2.使用队列
    • 让操作命令放在队列里,串行化执行。

1.5 Redis如何解决key的冲突

1.业务隔离
2.key的设计:业务模块+系统名称+关键信息
3.分布式锁:客户端拿到锁才可以进行修改等操作,以及利用时间戳确定修改执行顺序

1.6 如何提高缓存命中率

1.数据提前加载(优先将数据提前加载到redis)
2.增加缓存存储空间,提高缓存数据,从而提高缓存命中率
3.调整缓存的存储类型
4.提升缓存的更新频次(mysql binglog发生变化,就推送至redis修改,或者使用队列,客户端来查询队列数据更新redis)

2 、redis持久化

2.1 Redis RDB持久化、AOF持久化(缓存满了和数据丢失等问题)

  • 1.为什么要持久化(redis持久化不保证数据的完整性)
    redis是内存数据,宕机后会消失,持久化的目的不是为了存储,而是为了数据快速恢复
  • 2.redis持久化的两种方式
方式RDBAOF
介绍在指定时间间隔对数据进行快照存储记录每次对服务器写的操作,以redis协议追加每次写操作到文件末尾
不同1.只记录结果 2.默认开启 3.性能高1.会记录过程 2.默认不开启 3.性能低
文件格式dump.rdb(二进制压缩文件)存储的rdis命令
优点1.RDB是二进制压缩文件,占用空间小,便于传输 2.主进程fork子进程,主进程阻塞,主进程fork子进程后继续工作。子进程创建RDB文件 3.相对于数据集大时,比AOF的启动效率更高数据安全
缺点不保证数据的完整性,最后一次快照后的更改数据丢失(用AOF辅助)1.AOF文件比RDB文件大,且恢复速度慢 2.数据集大的时候,比RDB启动效率低 3.运行效率没有RDB高
  • 如果服务器开启了AOF功能,会优先使用AOF方式还原数据,只有关闭了AOF持久化功能,才会使用RDB方式还原数据
  • 高并发场景下:一般使用RDB,AOF混合方式来进行持久化

3、Redis快的原因

1. Redis为什么是单线程、及高并发快的原因

  1. redis是基于内存的,内存的读写速度非常快(纯内存访问)
  2. redis是单线程的,减少了上下文的切换 (redis 单线程指的是网络请求模块使用了一个线程,即一个线程处理所有网络请求,其他模块仍用了多个线程。)单线程下不需要考虑锁的性能消耗,缺点:无法发挥多核cpu性能,不过可以单机开多个redis实例来完善。
  3. redis采用IO多路复用技术,可以处理多个连接
  4. 数据结构,全程采用hash结构,特殊的数据结构对数据存储进行优化,采用压缩表对数据进行压缩存储,采用跳表,有序的数据结构对数据进行快速的读取
  5. 渐进式rehash:把一次性大量拷贝的开销,分摊到了多次处理请求的过程中
    Redis默认使用了两个全局hash表,一开始插入数据的时候默认使用hash表1,hash表2没有分配空间,直到数据越来越多,redis开始执行 rehash。
      1. 给hash表2分配更大的空间,如是hash1大小的两倍
      1. 把hash1的值重新映射到hash2
      1. 释放hash1的空间
        第2步涉及到大量数据的拷贝,如果一次性把数据迁移完,会造成Redis线程阻塞,无法服务其他请求,此时,redis就无法快速访问数据了。因此,每次请求就会处理一次数组下标位置的一个元素或者一个链表迁移。查询时会在表1查不到时查询表2
  6. 缓存时间戳:redis使用系统时间戳,不通过系统调用System.currentTimeInMills ,因为一次调用就会造成上下文切换,因此redis是通过缓存时间戳,每毫秒更新一次时间缓存,获取时间都是从缓存里取值

注:复用指的是多路复用一个线程。多路指的是多个网络连接。IO多路可以监听多个套接字(socket连接)

1. 1 Redis使用多路复用模型epoll模型

epoll模型:epoll是一种事件通知模型,当发生了IO事件时,应用程序才进行I/O操作。
【运用:tomcat,nginx,nio,redis】
工作流程:
分配一个内存块,创建一个事件收集器selector,所有的事件都要注册到这里,例如redis启动时就要往selector注册一个accept事件,当有客户端要连接redis,首先由selector感知到有accept事件发生,通知redis来连接,建立好连接后,客户端还会发送数据给redis,一旦建立连接后,客户端会将IO事件也注册到selector中,当多个客户端发数据触发IO事件,selectors会返回一个事件的数组集合events,redis拿到数据后进行处理。如果连接之后没有发数据,selector会处于一个阻塞状态等待事件发生,如果有事件发生马上返回一个events数组
操作系统内核提供函数:
epoll-create 创建收集器分配空间
epoll-wait 阻塞
epoll-ctl 把事件注册进来,由epoll-wait 监听

2. Redis是否存在线程安全问题

  • redis服务端层面:redis服务端是一个线程安全的key-value的数据库,在redis的客户端执行指令的时候不需要任何同步机制,不会存在任何线程安全的问题,虽然redis6.0里面增加了多线程的模型,但这只是去处理网络的IO事件,对于指令的执行仍然是通过主线程处理的,所以并不会出现多个线程同时执行操作指令的情况。
    为什么redis不采用多线程执行指令,因为redis server 本身可能出现的性能瓶颈点无非就是网络IO,CPU,内存,而cpu不是redis的瓶颈点,所以没有必要采用多线程执行指令,另外如果采用多线程,对redis的所有指令操作就需要考虑线程安全加锁,这样性能影响更大
  • redis客户端层面:如果有多个redis客户端同时执行多个指令,多线程下共享资源访问竞争的问题使得数据安全性无法得到保障。因此对于客户端层面的线程安全问题,应该尽量使用Redis里的原子指令,对客户端的资源访问加锁,通过lua脚本来实现多个指令的操作。

3. Redis6.0之前为什么不使用多线程

1、使用Redis,CPU不是瓶颈,受限于内存、网络
2、提高redis性能可以使用pipeline(命令批量)每秒处理100万个请求
3、单线程、内部维护比较低(并发读写,增加系统复杂度)
4、如果是多线程(线程切换,加锁解锁,导致死锁问题)
5、渐进式rehash,阻塞很少
一般公司够用

4. Redis6.0之后为什么引入多线程

1、单线程够用。 数据-》内存 响应时间 100纳秒
比较小的数据包,8w~10w QPS(极限值)
2、大公司,需要更大QPS,解决方式就是使用多线程,IO多线程,内部执行命令依旧是单线程
3、为什么不采用分布式架构(分区)
很大的缺点:

  • 服务器数量多,维护成本高
  • redis命令不适用于数据分区
  • 数据分区,无法解决热点读/写问题
  • 数据倾斜,分区重新分配,扩容缩容更加复杂

可以把多线程任务分摊到redis的同步IO中,进行读写负载,减少I/O时间,充分利用CPU资源

5. Redis是多线程还是单线程

redis5.0:单线程:worker线程是单线程
多个客户端连接redis后发送指令到redis,由于redis是单worker线程,因此采用串行方式执行这些命令,执行指令的的前提是要将IO流(因为是TCP协议所有是流的方式)读到,然后将指令进行计算(把数据插到内存中),计算完之后把计算的结果往回写(write IO)插入成功或失败把计算结果告诉客户端

redis6.0:多线程:worker线程还是单线程
多个客户端连接redis后发送指令到redis,由于redis是单worker线程,因此采用串行方式执行这些命令,执行指令的的前提是要将IO流(因为是TCP协议所有是流的方式)读到,之前的读IO是由worker线程读的,这里当一个客户端有指令发送过来时会开辟一个IO子线程。这时就是多线程了,IO子线程专门用来readIO,然后worker线程将指令进行计算(把数据插到内存中),计算完之后由IO子线程把计算的结果往回写(write IO)插入成功或失败把计算结果告诉客户端。
此时又有第二个客户端发送指令,便会开启第二个IO子线程,步骤同上
在这里插入图片描述
从执行时间维度上看,单线程只需要6个时间维度,多线程只需要4个时间维度,执行时间更短,性能更高在这里插入图片描述

4、Redis数据一致性

1. Redis缓存和MySQL数据一致性的解决方案

先更新缓存,后更新数据库:数据库异常,缓存的数据就和数据库不一致(不考虑)
先更新数据库,后更新缓存:缓存的数据就和数据库不一致(不考虑)
先删缓存,后更新数据库:问题出现在同时出现两个请求,A修改还未提交事务,B查询缓存为null继而查询数据库旧值更新到了缓存中(解决方法:延时双删)
先更新数据库,再删除缓存

  • 1. 采用延时双删策略
    • 步骤:
      1、删除缓存
      2、写数据库
      3、休眠500ms(Thread.sleep(N))
      4、再删除缓存
    • 设置缓存超时时间
    • 弊端:最差的情况下超时时间内数据不一致
  • 2.异步更新缓存(基于订阅binlog的同步机制)
    订阅,消息队列推送----》没看懂

5、Redis集群

1. Redis哨兵、复制、集群的设计原理,以及区别

1. 保证redis的高可用

  • 1.哨兵 :提供了监控
    • 故障监控和转移。主从节点,采用Raft算法选举
  • 2.复制

2. Redis 主从复制、哨兵和集群这三个有什么区别

1.主从复制:读写分离,一个master可以有多个slave
2.哨兵:监听,自动转移,哨兵发现主服务器挂了之后会从slave种选出一个新的主服务器
3.集群: 为了解决单机redis容量有限的问题,将数据按一定规则分配给多台集群

2. Redis集群怎么做(没记住)

redis cluster:根据一致性hash算法(变种hash算法)进行虚拟分槽,让数据进行均匀分布。集群功能限制:

  • key批量操作支持有限:只支持相同槽的key的批量操作
  • key事务支持有限
  • key作为数据分区的最小粒度
  • 复制结构只支持一层,主-从,不支持树状

集群搭建:建议三主三从

6、Redis应用场景

1.redis数据结构

1. 1 Redis有哪些数据结构,分别有哪些应用场景

  1. 字符串:缓存简单字符串,JSON格式的字符串(应用于redis分布式锁)
    应用场景:计数器,session共享,分布式ID。
  2. 哈希表:可以用来存储key-value,更适合用来存储对象。
  3. 列表:Redis的列表通过命令的组合,可以当作栈,也可以当作消息队列来使用,可以用来缓存微信公众号、微博等消息流数据。
  4. 集合:和列表类似,可以存储多个元素,但是不能重复,集合可以进行交集,并集,差集操作,从而实现共同关注,朋友圈点赞功能,给不同标签的用户做不同类型的兴趣爱好推荐。
  5. 有序集合:集合是无序的,有序集合可以设置顺序,实现排行榜功能。
  6. 高级数据结构bitmap:布隆过滤器
    在这里插入图片描述

1.2 redis底层数据结构zset :压缩表或者跳表存储

redis底层数据结构zset 有序集合,使用压缩表或者跳表存储。
(有序集合保存的元素小于128个,或者有序列表存到所有元素的长度小于64个字节,使用压缩表,否则使用跳表)
为什么不使用红黑树:
跳表支持范围查找,效率高于红黑树
跳表实现比红黑树简单易懂

  • 压缩表 :本质上是一个数组
    压缩表介绍
    增加了列表长度,尾部偏移量,列表元素个数,列表的结束标志,有利于快速寻找列表首和尾的节点,但是对于寻找其他正常的元素,仍然没有很高效,只能一个个便利
    在这里插入图片描述

  • 跳表 :为什么使用跳表,由于zset底层使用链表实现,因此增删快,查询则需要从表头到表尾比较慢,因此对链表进行了优化。因此为该链表建立索引层,每两个元素的第一个元素往上建立一个冗余的元素。因此查询可以从左上角索引查询,如图所示(类似于折半查找)。
    跳表:将有序链表改造为支持近似“折半查找算法”,可以进行快速的插入,删除,查找操作。
    在这里插入图片描述

2. Redis有哪些高级功能

  1. redis+lua脚本实现限流(支持分布式项目)
  2. RDB+AOP结合的持久化功能
  3. 分布式锁
  4. 主从复制,高可用的话引入哨兵

2.1 Redis+lua脚本实现限流

预防库存超卖,Redis+lua是如何实现原子性的
decr命令对库存计数器-1
lpush 往list列表左侧增加userid

多线程并发下,goods:1001(商品编号,库存10)>0,但是20个线程同时开始,都对库存进行减少,并将自己用户编号压入列表,导致超卖

redis 2.6 集成lua脚本语言
执行原理:redis单线程命令队列,保证redis+lua的原子性
支持多行模式(lua代码)根据1或者0判断是否抢购成功
在这里插入图片描述

3. 为什么要使用Redis

从高性能和高并发两个维度切入
1.高性能:访问mysql数据库(访问磁盘效率低,1k~2k/s),为了提高性能加redis缓存(微妙)
2.高并发:mysql(1k/s),redis (10w/s)集群下就可以更多,还可以进行故障转移

4. 什么是bigKey

key对应的value所占的空间比较大,value最大存储512M
字符串类型:体现在单个value值过大(10kb,会随业务设定波动)
非字符串类型:哈希、列表、集合、有序集合,体现在元素个数过多

bigkey的危害:

  • 内存空间不均匀,造成有的节点使用内存很大,有的很小
  • 超时阻塞:由于redis处理命令单线程的处理,操作bigkey比较耗时
  • 网络拥塞:每次获取bigkey产生的网络流量较大
    如果是热点数据的话,就会造成很大的影响
    解决:对bigkey进行结构拆分

7、Redis和memcached

1. Redis和memcached比较

Redismemcached
整体类型1、支持内存(key-value) 2、非关系型数据库1、支持内存 2、key-value (简单存储)
数据类型String 、List、Set、Zset、Hash1、文本类型 2、二进制类型
操作类型1、单个操作 2、批量操作 3、事务支持(弱势务,可以结合lua脚本做出事务比较完整的方案) 4、每个类型不同的curdcurd、比较少的其他命令
附加功能1、发布和订阅 2、主从高可用(哨兵:故障转移,集群)3、序列化支持 4、支持lua脚本多线程服务支持
网络IO模型执行命令-单线程(有子任务线程进行AOF,有锁冲突) 、网络操作-多线程(多路复用IO)支持多线程、非阻塞的IO模式 (多路复用)
持久化支持RDB,AOF不支持

redis功能比较强大,memacahe 多线程比较优势

8、Redis事务

要么全都执行,要么全不执行
将一组命令放到multi和exec两个命令之间,multi命令代表事务开始,exec代表事务结束,discard代表事务回滚
redis的事务功能很弱,在事务回滚机制上,对基本的语法错误进行判断,但是运行时错误不会回滚

9、Redis过期策略以及内存淘汰机制

9.1 Redis过期策略

内存有限,因此数据结构会设置过期时间(ttl)(加入过期字典)时间一到,会自动删除过期键,但是redis是单线程的,大量key过期进行删除会发生卡顿。
因此redis过期策略有两种:1.定期删除 2.惰性删除(相结合)

  1. 定期删除:定期扫描策略
    默认每秒进行10次扫描,不会遍历过期字典中所有的key,而是使用一种简单的贪心算法
    1.随机从过期字典中随机20个key
    2.删除20个key中过期的key
    3.如果过期key的比例超过1/4,就会重复第一步

    如果一个redis实例中大量key同时过期,就会持续扫描过期字典(循环多次),知道字典中过期的key变得稀疏,才会停止,这就导致线上读写请求出现明显卡顿现象。

    从库的过期策略:从库不会进行过期扫描,而是接受主库的命令(在AOF增加del指令同步给从库执行)因为指令同步是异步进行的,所以主库过期的key的del指令没有及时同步给从库的话会出现主从数据不一样。

  2. 惰性删除
    客户端访问key时检测过期时间,如果过期就立即删除,不会返回任何东西。定期删除导致很多key到了时间没有被删掉,所以有了惰性删除。

总结:定期删除是集中处理,惰性删除是零散处理

9.2 内存淘汰机制

  1. Noevition:不进行处理,当redis内存超过物理内存限制,默认可读但是不能写
  2. VOLATILE-LRU :最少使用的key被优先淘汰,没有设置过期时间的key不会被淘汰,这样需要持久化的数据不会突然丢失
  3. VOLATILE-TTL :key剩余寿命TTL的值越小优先被淘汰
  4. VOLATILE-Random :过期key集合中随机的key
  5. ALLKEYS-LRU :与VOLATILE区别在于,没有设置过期时间key也会被淘汰
  6. ALLKEYS-Random

9.3 LRU算法

实现LRU算法除了需要key-value字典外,还需要附加一个链表,链表中的元素按照一定的顺序进行排列,当空间满的时候,会踢掉链表尾部的元素,当一个元素被访问时,这个元素就被被移动到链表头
redis中采用近似LRU算法,给每个key增加一个长度是24bit的字段,也就是最后异常被访问的时间戳,随机抽样五个key,淘汰掉最旧的,直至内存低于maxmemory,停止随机抽样淘汰

10.redis性能

10.1 redis性能问题和解决方案

1.主从复制流畅,在同一个局域网
2.主从复制尽量不要网状结构
3.尽量避免主库压力很大,增加从库
4.主从,主不要做持久化,从做持久化
5.从库做AOF持久化,策略每秒同步一次

10.2 redis什么情况下会发生阻塞

1.AOF日志同步时,大量的写操作(1个同步写磁盘耗时1-2ms)
2.从库加载RDB文件,文件越大,阻塞越久
3.删除BigKey(zset 100万个元素2s)
4.主从节点切换,清空库 flushdb flushall

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值