Redis回顾

1. 什么是Redis?Redis是用来干什么的?

Redis :是一种非关系型数据库,以键值对的形式进行存储(key-value),主要用来做数据缓存,Redis运行在内存中,数据也保存在内存中,目的在于让数据离浏览器更进,

Redis的它的读写速度非常快,结构简单易拓展,高性能,灵活的数据类型。

2. Redis的基本数据类型

String(字符串)

String是Redis最基础的数据结构类型,它是二进制安全的,可以存储图片或者序列化的对象,值最大存储为512M。

简单使用举例: set key valueget key

内部编码有3种:int(8字节长整型)/embstr(小于等于39字节字符串)/raw(大于39个字节字符串)

应用场景

  • 共享session
  • 分布式锁
  • 计数器、限流

Hash(哈希表)

在Redis中,哈希类型是指v(值)本身又是一个键值对(k-v)结构.

简单使用举例:hset key field value 、hget key field

内部编码:ziplist(压缩列表) 、hashtable(哈希表)

应用场景:缓存用户信息等。

List(列表)

列表(list)类型是用来存储多个有序的字符串

内部编码:ziplist(压缩列表)、linkedlist(链表)

应用场景参考以下:

  • lpush+lpop=Stack(栈)
  • lpush+rpop=Queue(队列)
  • lpsh+ltrim=Capped Collection(有限集合)
  • lpush+brpop=Message Queue(消息队列)

Set(集合)

集合(set)类型也是用来保存多个的字符串元素,但是不允许重复元素。

底层内部编码:ziplist(压缩列表)skiplist(跳跃表)

应用场景:

  • 用户标签
  • 生成随机数抽奖、社交需求

ZSet(有序集合)

已排序的字符串集合,同时元素不能重复。

底层内部编码:ziplist(压缩列表)skiplist(跳跃表)

应用场景:

  • 排行榜
  • 社交需求(如用户点赞)

三种特殊数据类型

  • Geo:Redis3.2推出的,地理位置定位,用于存储地理位置信息,并对存储的信息进行操作。
  • HyperLogLog:用来做基数统计算法的数据结构,如统计网站的UV。
  • Bitmaps :用一个比特位来映射某个元素的状态,在Redis中,它的底层是基于字符串类型实现的,可以把bitmaps成作一个以比特位为单位的数组

3. Redis这么快的原因?

  • 性能极高(读写速度快)(基于内存实现)

  • 丰富的数据类型(list、set、Map等)

  • 原子性 (所有的操作都是原子性的)

  • 丰富的特性 (支持publish,通知等)

  • 合理的线程模型(单线程模型:避免上下文切换;IO多路复用)

  • 虚拟内存机制

4. 什么是缓存击穿、缓存穿透和缓存雪崩及其解决方案?

缓存穿透

我们所查询的数据在数据库中没有,查询后也不在缓存中存,每次还是回去访问数据库。

特点:数据库没有,缓存也没有

解决

  • 本来数据库没有,将查询出来的空值放到缓存中。key = null

  • 对查询的参数进行验证

缓存击穿

数据库有数据,只是某个热点key在某个时间点上过期了,此时有大量查询请求到达(查询是不加锁),查询缓存没有,一起都向数据库发送请求,导致数据库崩溃。

解决

  • 可以设置热点的key过期时间要把握好  永不过期

  • 查询缓存后,访问数据库时,可以加锁  (使用互斥锁方案

缓存雪崩

大量的热点key过期或者redis服务器故障,导致大量的请求到达数据库。

解决方案:

  • 随机设置有效时间,避免同时生效

  • 把不同的热点key放在不同的redis服务器上(集群)

  • 设置较长的过期时间

  • 在java中设置定时任务,去检测key是否过期

5. 什么是热点key问题?如何解决热点key问题?

什么是热Key呢

在Redis中,我们把访问频率高的key,称为热点key。

如果某一热点key的请求到服务器主机时,由于请求量特别大,可能会导致主机资源不足,甚至宕机,从而影响正常的服务。

热点Key是怎么产生的呢?主要原因有两个:

  • 用户消费的数据远大于生产的数据,如秒杀、热点新闻等读多写少的场景。
  • 请求分片集中,超过单Redi服务器的性能,比如固定名称key,Hash落入同一台服务器,瞬间访问量极大,超过机器瓶颈,产生热点Key问题。

6. Redis过期策略和内存淘汰策略

key的过期策略

为key设置过期时间,那么时间到了后,Redis如何处理过期的key

立即删除。到期立即执行回调函数,立即释放内存,对redis性能有影响。

惰性删除。到期不会立即删除,到下次使用该键的时候,根据状态(设置时是会记录的),来决定是删除还是继续使用,占用内存)

定期删除。每隔一段时间对所有到期的键进行删除(类似于java中垃圾回收线程)

Redis中同时使用了惰性过期和定期过期两种过期策略。

7. Redis的应用场景

  • 缓存:降低数据库的访问压力
  • 排行榜:各式各样的排行榜
  • 计数器应用
  • 共享Session
  • 分布式锁
  • 社交网络
  • 消息队列
  • 位操作

8. Redis的持久化机制有哪些及优缺点

Redis是基于内存的非关系型K-V数据库,既然它是基于内存的,如果Redis服务器挂了,数据就会丢失。为了避免数据丢失了,Redis提供了持久化,即把数据保存到磁盘。

Redis提供了RDB和AOF两种持久化机制。

RDB,就是把内存数据以快照的形式保存到磁盘上。

配置触发持久化的机制:

  1. save m n save 多少秒内 多少键

  2. flushall 命令

  3. 退出

RDB 的优点

  • 适合大规模的数据恢复场景,如备份,全量复制等

RDB缺点

  • 没办法做到实时持久化/秒级持久化。
  • 新老版本存在RDB格式兼容问题

AOF:

以日志的形式,将写命令存储到文件 set name jim

还原时,将命令逐个执行还原数据

默认是不开启的 appendonly no

同步机制

appendfsync always 每次set记录一次

appendfsyns everysec 每秒记录一次

AOF的优点

  • 数据的一致性和完整性更高

AOF的缺点

  • AOF记录的内容越多,文件越大,数据恢复变慢。

9. Redis 是实现高可用的?

单点部署一旦宕机,就不可用了。为了实现高可用,通常的做法是,将数据库复制多个副本以部署在不同的服务器上,其中一台挂了也可以继续提供服务。Redis 实现高可用有三种部署模式:主从模式,哨兵模式,集群模式

主从复制

主从---> 主机和从机(集群架构)

主机负责写数据,将数据同步到从机,一般的读数据从从机查询,

实现读写分离:写命令由主机执行,而读命令由从机执行

为什么要使用集群?

如果只有一台redis服务,万一服务宕机,所有的请求都会到达mysql,导致mysql宕机,可以搭建多台redis服务器,如果其中一台出现故障,其他服务可以正常使用。

哨兵机制

有一个单独线程,对集群中的多态服务进行监听,给每个服务发请求,如果没有响应,表明出现故障。

比如,主机出现宕机,会在从机随机选取一台当做主机。当原来主机恢复后,又可以当做主机继续使用。

10. 什么是Redis分布锁?有哪些需要注意的?

分布式锁,是控制分布式系统不同进程共同访问共享资源的一种锁的实现。秒杀下单、抢红包等等业务场景,都需要用到分布式锁,我们项目中经常使用Redis作为分布式锁。

Redis分布式锁的几种实现方法:

  • 命令setnx + expire分开写
  • setnx + value值是过期时间
  • set的扩展命令(set ex px nx)
  • set ex px nx + 校验唯一随机值,再删除

11. 什么是Redisson及其原理?

       分布式锁可能存在锁过期释放,业务没执行完的问题。有些小伙伴认为,稍微把锁过期时间设置长一些就可以啦。其实我们设想一下,是否可以给获得锁的线程,开启一个定时守护线程,每隔一段时间检查锁是否还存在,存在则对锁的过期时间延长,防止锁过期提前释放。

12. 什么是RedLock算法?

  • 按顺序向5个master节点请求加锁
  • 根据设置的超时时间来判断,是不是要跳过该master节点。
  • 如果大于等于三个节点加锁成功,并且使用的时间小于锁的有效期,即可认定加锁成功啦。
  • 如果获取锁失败,解锁!

13. Redis的跳跃表?

Redis回顾_第1张图片

跳跃表

  • 跳跃表是有序集合zset的底层实现之一
  • 跳跃表支持平均O(logN),最坏 O(N)复杂度的节点查找,还可以通过顺序性操作批量处理节点。
  • 跳跃表实现由zskiplist和zskiplistNode两个结构组成,其中zskiplist用于保存跳跃表信息(如表头节点、表尾节点、长度),而zskiplistNode则用于表示跳跃表节点。
  • 跳跃表就是在链表的基础上,增加多级索引提升查找效率。

14. redis和Mysql 如何保证双写一致性?

  • 缓存延时双删
  • 删除缓存重试机制
  • 读取biglog异步删除缓存

什么是延时双删呢?

  1. 先删除缓存
  2. 再更新数据库
  3. 休眠一会(比如1秒),再次删除缓存。

删除缓存重试机制

因为延时双删可能会存在第二步的删除缓存失败,导致的数据不一致问题。

优化:删除失败就多删除几次呀,保证删除缓存成功就可以了,所以可以引入删除缓存重试机制

删除缓存重试流程

  1. 写请求更新数据库
  2. 缓存因为某些原因,删除失败
  3. 把删除失败的key放到消息队列
  4. 消费消息队列的消息,获取要删除的key
  5. 重试删除缓存操作

读取biglog异步删除缓存

        重试删除缓存机制还可以吧,就是会造成好多业务代码入侵。其实,还可以这样优化:通过数据库的binlog来异步淘汰key。

以mysql为例吧

  • 可以使用阿里的canal将binlog日志采集发送到MQ队列里面
  • 然后通过ACK机制确认处理这条更新消息,删除缓存,保证数据缓存一致性

15. 为什么Redis 6.0 之后改为多线程?

redis是单线程模式还是多线程模式。

不同版本的区别:

6.x之前是真正意义上的单线程,处理客户端的连接和执行操作的命令,都是由一个线程完成的。

6.x之后引入多线程,处理客户端请求是由专门的线程处理,执行命令还是单线程

为什么是单线程模式,速度还非常快?

  1. 数据存储在内存中,读取速度快,cpu不是性能瓶颈

  2. 结构简单,key-value 底层是哈希结构,查询操作速度是O(1)

  3. 采用IO多路复用,非阻塞IO模型,提高连接访问效率

  4. 单线程执行命令,不存在线程切换,节省开销,而且是线程安全的。

16. Redis事物机制?

        redis在执行单条命令时,是原子性(单线程的一次只能有一个线程执行命令),有时候,一次操作需要执行多条命令,如何保证多条命令整体执行。

  • 开始事务(MULTI)
  • 命令入队
  • 执行事务(EXEC)、撤销事务(DISCARD )

可以通过redis事务执行:

开启事务:multi

添加命令:... 命令添加进来不会立即执行,添加到一个多列。

执行exec命令时,才会将对列中的多条命令依次执行,执行一个事务多条命令时,其他客户端会被隔离,不会交替执行,但是事务不保证多条命令执行的原子性(假如执行3条命令,其中一条语法错误,那么会将其他两条正确的命令继续执行)

17. Redis的Hash冲突如何解决?

Redis为了解决哈希冲突,采用了链式哈希。链式哈希是指同一个哈希桶中,多个元素用一个链表来保存,它们之间依次用指针连接。

为了保持高效,Redis 会对哈希表做rehash操作,也就是增加哈希桶,减少冲突。为了rehash更高效,Redis还默认使用了两个全局哈希表,一个用于当前使用,称为主哈希表,一个用于扩容,称为备用哈希表

18. 在生成RDB期间,Redis可以同时写请求?

可以的,Redis提供两个指令生成RDB,分别是save和bgsave

  • 如果是save指令,会阻塞,因为是主线程执行的。
  • 如果是bgsave指令,是fork一个子进程来写入RDB文件的,快照持久化完全交给子进程来处理,父进程则可以继续处理客户端的请求。

19. Redis底层使用什么协议?

RESP,英文全称是Redis Serialization Protocol,它是专门为redis设计的一套序列化协议。这个协议其实在redis的1.2版本时就已经出现了,但是到了redis2.0才最终成为redis通讯协议的标准。

RESP主要有实现简单、解析速度快、可读性好等优点。

20. 布隆过滤器

应对缓存穿透问题,我们可以使用布隆过滤器。布隆过滤器是什么呢?

布隆过滤器是一种占用空间很小的数据结构,它是由一个很长的二进制向量和一组Hash映射函数组成,它用于检索一个元素是否在一个集合中,空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。

布隆过滤器原理:
假设我们有个集合A,A中有n个元素。利用k个哈希散列函数,将A中的每个元素映射到一个长度为a位的数组B中的不同位置上,这些位置上的二进制数均设置为1。如果待检查的元素,经过这k个哈希散列函数的映射后,发现其k个位置上的二进制数全部为1,这个严肃很可能属于集合A,反之,一定不属于集合A

例:假设集合A有3个元素,分别为{d1,d2,d3}。有1个哈希函数,为Hash1。现在将A的每个元素映射到长度为16位数组B。

Redis回顾_第2张图片

 我们现在把d1映射过来,假设Hash1(d1)= 2,我们就把数组B中,下标为2的格子改成1,如下:

Redis回顾_第3张图片

 我们现在把d2也映射过来,假设Hash1(d2)= 5,我们把数组B中,下标为5的格子也改成1,如下:

Redis回顾_第4张图片

 接着我们把d3也映射过来,假设Hash1(d3)也等于 2,它也是把下标为2的格子标1:

Redis回顾_第5张图片

  因此,我们要确认一个元素dn是否在集合A里,我们只要算出Hash1(dn)得到的索引下标,只要是0,那就表示这个元素不在集合A,如果索引下标是1呢?那该元素可能是A中的某一个元素。因为你看,d1和d3得到的下标值,都可能是1,还可能是其他别的数映射的,布隆过滤器是存在这个缺点的:会存在hash碰撞导致的假阳性,判断存在误差。

如何减少这种误差呢?

  • 搞多几个哈希函数映射,降低哈希碰撞的概率
  • 同时增加B数组的bit长度,可以增大hash函数生成的数据的范围,也可以降低哈希碰撞的概率

   这样即使存在误差,我们可以发现,布隆过滤器并没有存放完整的数据,它只是运用一系列哈希映射函数计算出位置,然后填充二进制向量。如果数量很大的话,布隆过滤器通过极少的错误率,换取了存储空间的极大节省,还是挺划算的。 

你可能感兴趣的:(redis,缓存,java)