work hard work smart

专注于Java后端开发。 不断总结,举一反三。
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

Redis 总结

Posted on 2020-01-11 21:52  work hard work smart  阅读(187)  评论(0编辑  收藏  举报

为什么Redis能这么快

100000 + QPS (QPS即query per second, 每秒内查询次数)

1) 完全基于内存,绝大部分的请求时纯粹的内存操作,执行效率高。

(采用单进程单线程的KV数据库,有C语言编写,将数据存储在内存中。 读写内存的时候,都不会受到硬盘和IO的限制。)

2) 数据结构简单,对数据操作也简单

3) 采用单线程,单线程也能处理高并发请求,想多核也可启动多实例。

因为是单线程,对同一个键进行读写操作,就不会有并发的问题,避免了频繁的上下文切换和锁竞争,是的Redis处理效率更高。

单线程也能处理高并发(并发不是并行,并行意味着服务器能同时处理几个事情,具有多个计算单元。而并发性IO流意味着同一个计算单元能处理多个客户端的流请求)

4) 使用多路I/O复用模型,非阻塞IO

 

 

多路I/O复用模型

FD: File Descriptor,文件描述符

一个打开的文件通过唯一的描述符进行引用,该描述符是打开文件的元数据到文件本身的映射。(文件描述符用一个整数描述)

 

传统的阻塞I/O模型

当使用Read或者Write对一个文件描述符FD进行读写时,如果当前FD不可读或者不可写时,整个Redis服务就不会对其它的操作进行响应,导致不可用。

 

 

 

多路I/O复用模型

Select系统调用

 

 Selector能够监控多个文件描述符的可读可写情况,返回可读可写的文件描述符个数。监听的任务交给Selector,系统去做别的事情,这样就不会被阻塞了。

 

Redis采用的I/O多路复用函数: epoll/kqueue/evport/select ?

 这些函数,如何选择

   因地制宜

  优先选择时间复杂度为O(1)的I/O多路复用函数作为底层实现

  以时间复杂度为O(n)的select作为保底

  基于react设计模式监听I/O事件

 

 Redis底层数据类型基础

1、简单动态字符窜

2、链表

3、字典

4、跳跃表

5、整数集合

6、压缩列表

7、对象

 

 

179、什么是Redis? 都与哪些使用场景?

Redis是一个使用C语言开发的高速缓存数据库。

Redis使用场景

1) 记录帖子点赞数、点击数、评论数;

2) 缓存近期热帖

3) 缓存文章详情

4) 记录用会话信息。

 

180、Reids有哪些功能 ?

1) 数据缓存功能

2) 支持分布式锁的功能

3) 支持数据持久化

4) 支持事务

5) 支持消息队列

 

181、Redis和memcache有什么区别

存储方式不同: memecache把数据全部存在内存中,断电后会挂掉,数据不能超过内存大小; Redis有部分存在硬盘上,这样能保证数据的持久性。

数据支持类型: memecache对数据类型支持相对简单; Redis支持丰富的数据类型(Set,list等)

使用底层模型不同: 它们之间底层实现方式,以及客户端之间的通信的应用协议不一样,Redis自己构建了vm机制,因为一般的系统调用系统函数的话,会浪费一定的实际去移动和请求。

value值大小不同: Redis最大可以达到1gb; memecache只有1mb。

是否支持主从同步:Redis支持主从, memcache不支持主从

是否支持分片: Redis支持片(Redis 3.0开始),memcache支持分片(分片:简单理解为将大数据分配到多个物理节点上)

如何选择Redis和memcache?

对数据有持久化需求或者对数据结构和处理有高级要求的应用应选择Redis。

 

 

 

182、Redis为什么是单线程

因为cup不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存和网络带宽。既然单线程容易实现,而且cpu又不会是瓶颈,那就顺理成章地采用单线程方案。

关于Reids的性能,官方网站也有,普通笔记本轻松处理每秒几十万的请求。

而且单线程并不代表慢,nginx和nodejs也都是高性能单线程的代表。

 

 

 

183、什么是缓存穿透?怎么解决?

缓存穿透: 指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。

解决方案:最简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们就把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。

另外一种解决缓存穿透的方法是布隆过滤器,因实现复杂,这里不做介绍。

 

184、Redis支持的数据类型有哪些?

Redis支持的数据类型: string(字符串),list(列表), hash(字典),set(集合), zset(有序集合)

 

185、Redis支持的Java客户端有哪些?

Redission, jedis, lettuce等。

 

186、jedis和Redission有哪些区别?

jedis: 提供了比较全面的Redis命令的支持

Redission: 实现了分布式和可扩展的Java数据结构,与jedis相比Redission的功能相对简单,不支持排序,事务,管道,分区等Redis特性。

 

187、怎么保证缓存和数据库的一致性?

1) 合理设置缓存的过期时间

2) 新增、更改、删除数据库操作时同步更新Redis,可以使用事务机制来保证数据的一致性。

 

188、Redis持久化有几种方式?

Redis的持久化有两种方式,或者说策略

RDB(Redis Database): 指定的时间间隔能对你的数据进行快照存储。

AOF(Append Only File): 每一个收到的写命令都通过write函数追加到文件中。

RDB-AOF混合持久化方式 (Reids 4.0之后) 

 

1) RDB持久化

Redis的保存策略 进入 redis/redis.conf文件

 

save 900 1 : 900秒之内有1条是写入指令,就触发一条快照(理解为进行一次备份)

save 300 10 : 300秒内有10条写入,就触发一次快照

save 60 10000 60秒内有10000条写入,就触发一次快照

 

快照数据保存在dump.rdb文件中,该文件是如何生成的?

SAVE: 阻塞Redis的服务器进程,直到RDB文件被创建完毕(很少被使用)

BGSAVE: Fork出一个子进程来创建RDB文件,不阻塞服务器进程

 

自动触发RDB持久化的方式

根据redis.conf 配置里SAVE  m n定时触发(用的是BGSAVE)

组从复制时,主节点自动触发

执行Debug Reload

执行Shutdown且没有开启AOF持久化

 

RDB持久化缺点:

内存数据的全量同步,数据量大会由于I/O而严重影响性能

可能会因为Redis挂掉而丢失从当前至最近一次快照期间的数据。

 

2)、AOF(Append Only File)持久化,保存写状态

记录下除了查询以外的所有变更数据库状态的指令

以append的形式追加到AOF文件中(增量)

默认redis.conf 中,AOF是关闭的

 

 启动的话,将no改为yes

 

日志重写解决AOF文件大小不断增大的问题(假设对key改动了10次,就好保存10次记录,实际上我们只需要最新的那次)

用用fork(), 创建一个子进程

子进程把新的AOF写到一个临时文件里,不依赖原来的AOF文件

主进程持续将新的变动同时写到内存和原来的AOF里

主进程获取子进程重写的AOF的完成信号,往新AOF同步增量变动

使用新的AOF文件替换旧的AOF文件。

 

RDB和AOF文件共存情况下的恢复流程

 

 

RDB和AOF的优缺点

FDB优点: 全量数据快照,文件小,恢复快

RDB缺点:无法保存最近一次快照之后的数据

AOF优点:可读性高,适合保存增量数据,数据不易丢失

AOF缺点: 文件体积大,恢复时间长

 

 3)RDB-AOF混合持久化方式

BGSAVE做镜像全量持久化,AOF做增量持久化

 

 

189、Redis怎么实现分布式锁?

Redis分布式锁其实就是在系统里面占一个“坑“, 其他程序也要占“坑”的时候,占用成功了就可以继续执行,失败了就只能放弃或稍后重试。

占坑一般使用sennx(set if not exist) key  value指令,如果key不存在,则创建并赋值。只允许被一个程序占有,使用完调用del释放锁。

 

sennx(set if not exist) key  value

时间复制读 O(1)

返回值: 设置成功,返回1,设置失败,返回0

 

分布式锁需要解决的问题

互斥性: 任一时刻,只能有一个客户端获取锁

安全性: 只有持有该锁的客户端才能删除锁,其它客户端不能删除锁

死锁:  获取锁的客户端因为某些原因Down机而未能释放锁,其它客户端再也不能获取锁而导致的死锁。此时需要由机制避免死锁的发生。

容错:当有些Redis节点Down机的时候,客户端仍然能获取锁和释放锁。

 

 如何解决Setnx长期有效的问题

设置key的生存时间,当key过期时(生存时间为0)

 

 如下面的伪代码

 

 上述伪代码的缺点: 不能保证原则性,如setnx成功了,但是程序down了,expire设置过期没有被执行。

那怎么解决呢? 从redis 2.6.12 以后,就把setnx和expire结合起来

 SET key value [EX seconds] [PX milliseconds][NX|XX]

EX second : 设置键的过期时间为second秒

PX millsecond: 设置键的过期时间为millisecond毫秒

NX: 值在建不存在时,才对键进行设置操作。
XX: 只在键已经存在时,才对键进行设置操作。

SET操作成功完成时,返回OK,否则返回nil

 

 设置为10秒过期,10秒内再次设置,返回nil。 10秒后设置,返回OK

 如下面的伪代码:

 

 这样就能保住原子性了。

 

190、Redis分布式锁有什么缺陷?

Redis分布式锁不能解决超时的问题,分布式锁有一个超时时间,程序的执行如果超出了锁的超时时间,就会出现问题。

 

 

191、Redis如何做内存优化?

尽量使用Redis的散列表,把相关的信息放到散列表里存储,而不是把每个字段单独存储,这样可以有效的减少内存使用。比如将web系统的用户对象,应该放到散列表里面再整体存储到Redis,而不是把用户、姓名、年龄、密码、邮箱等字段分别设置可以进行存储。

 

193、Redis常见的性能问题有哪些? 该如何解决?

主服务器写内存快照,会阻塞主线程的工作,当快照比较大时对性能影响是非常到的,会间断性暂停服务,所以主服务最好不要写内存快照。

Redis主从复制的性能问题,为了主从复制的速度和连接的稳定性,主从库最好在同一个局域网内。

 

 

从海量Key里查询出某个固定前缀的Key?

摸清数据规模,即问清楚边界。

方法1、KEYS pattern: 查找所有符合给定模式pattern的key

 

 keys指令一次性返回所有匹配的key

缺点: 键的数量过大会使服务卡顿

 

方法2:

SCAN cursor [MATCH pattern] [COUNT count]

基于游标的迭代器,需要基于上一次的游标延续之前的迭代过程

以0作为游标开始一次新的迭代,直到命令返回游标0完成一次遍历

不保证每次执行都返回某个给定数量的元素,支持模糊查询

一次返回的数量不可控,只能是大概率符合count参数

 

 

 

查看key的个数 : dbsize

 

大量的key同时过期注意事项

集中过期,由于清除大量的key很耗时,会出现短暂的卡顿现象

解决方案:在设置key的过期时间的时候,给每个可以加上随机值 (不同的key,设置不同的过期时间)

 

如何使用Redis做异步队列?

使用List最为队列,RPUSH生产消息,LPOP消费消息

BLPOP Key [key ...] timeout: 阻塞直到队列有消息或者超时

 

 

 BLPOP的缺点: 只能供一个消费者消费

 

pub/sub :主题订阅模式

发送者(pub)发行消息,订阅者(sub)接收消息

订阅者可以订阅任意数量的频道(主题)

 subscript myTopic: 监听myTopic

public myTopic hello : 生产消息hello

如下图:两个订阅者都收到了

 

pub/sub 缺点:

消息的发布时无状态的,无法保证可达。

解决这个问题,要使用专用的消息队列,如kafka等。

 

使用Pipeline的好处

Pipeline和Linux的管道类似

Redis基于请求/响应模型,单个请求处理需要一一应答

Pipeline批量执行指令,节省多次IO往返的时间

有顺序依赖的指令建议分批发送

 

Redis的同步机制

分为全同步过程和增量同步过程

 

 1) 全同步过程

Salve发送sync命令到Master

Master启动一个后台进程,将Redis中的数据快照保存到文件中(BGSAVE)

同时Master将保存数据快照期间接收到的写命令缓存起来(增量数据缓存)

Master完成写文件操作后,将该文件发送给Salve

Salve接收文件保存到磁盘中,然后加载文件到内存中去恢复数据快照,使用新的AOF文件替换掉旧的AOF文件

Master将这期间收集的增量写命令发送给Salve端。

之后所有写操作都在Master上进行,所有读操作在Salve上进行

 

2)增量同步过程

Master接收到用户的操作指令,判断是否需要传播到Slave

将操作记录追加到AOF文件

将操作传播到其他Slave: 1、对齐主从库(确保从数据库是该操作对应的数据库)  2) 往响应缓存写入指令

将缓存中的数据发送给Slave

 

Redis Sentinel

解决主从同步Master宏机后的主从切换问题。

(Redis主从的缺点,当Master挂了之后,Redis不能对外提供写操作。

而Redis Sentinel解决了这个问题)

其本身是一个独立的进程,它能监控多个Master和Slave集群,发现Master Down机后自动切换

主要功能有如下几点:

监控: 检查主从服务器是否运行正常

提醒:通过API向管理员或者其它应用程序发送故障通知。

自动故障迁移: 主从切换

 

Redis的集群原理

如何从海量数据快速找到所需?

分片: 按照某种规则去划分数据,分散存储在多个节点上

常规的按照哈希划分无法实现节点的动态增减(根据哈希值与节点数求模,但是节点增减或减少后,造成大量的key不被命中)。为了解决该问题,Reids引入了一致性哈希算法

一致性哈希算法:

1) 对2^32取模,将哈希值空间组织成虚拟的圆环

 

 2)将数据key使用相同的函数Hash计算出哈希值

 

 假设对四台服务器的IP或者主机名作为关键字进行哈希,这样每台服务器就能取得在哈希环上的位置。如上图。

接下来对数据key使用刚才同样哈希函数计算哈希值,并确定此数据在环上的位置。

 

 如对象A计算出哈希环的位置,然后顺时针落到了服务Node A上。

同理对象B计算出哈希环的位置,然后顺时针落到了服务Node B上。

这样做的好处:

假设Node C down机了,对象A, B, D都不会受到影响, 对象C将会保存到Node D上。

 

 

假设增加了Node X,对象A, B, D都不会受到影响,对象C将会保存到Node X上。

 

 

 Hash环缺点: Hash环的数据切斜问题(指的是大部分数据被缓存在一台服务器上)

 

引入虚拟节点解决数据倾斜问题

为了解决Hash环的数据切斜问题,引入虚拟节点

 

可以在服务器IP或者主机名后面增加编号

将Node A变成Node A#1, Node A#2, Node A#3

 将Node B变成Node B#1, Node B#2, Node B#3