Skip to content

Latest commit

 

History

History
454 lines (272 loc) · 27.2 KB

Redis.md

File metadata and controls

454 lines (272 loc) · 27.2 KB

Redis


介绍下redis

  • Redis本质上是一个Key-Value类型的内存数据库

很像memcached,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存。因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。

  • Redis最大的魅力是支持保存多种数据结构,此外单个value的最大限制是1GB,不像 memcached只能保存1MB的数据

Redis可以用来实现很多有用的功能,比方说用他的List来做FIFO双向链表,实现一个轻量级的高性 能消息队列服务,用他的Set可以做高性能的tag系统等等。另外Redis也可以对存入的Key-Value设置expire时间,因此也可以被当作一 个功能加强版的memcached来用。

  • Redis的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。

Redis的全称是什么?

Remote Dictionary Server

缓存穿透可以介绍一下么?你认为应该如何解决这个问题?

怎么理解强一致性、单调一致性和最终一致性?

为什么缓存更新策略是先更新数据库后删除缓存

你是怎么触发缓存更新的?

(比如设置超时时间(被动方式), 比如更新的时候主动update)?如果是被动的方式如何控制多个入口同时触发某个缓存更新?

你们用Redis来做什么?为什么不用其他的KV存储例例如Memcached,Cassandra等?

  1. memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型
  2. redis的速度比memcached快很多
  3. redis可以持久化其数据

一个字符串类型的值能存储最大容量是多少?

512M

Redis相比memcached有哪些优势?

  • memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型
  • redis的速度比memcached快很多
  • redis可以持久化其数据

你们用什么Redis客户端?

Redisson、Jedis、lettuce等等,官方推荐使用Redisson。

Redisson是一个高级的分布式协调Redis客服端,能帮助用户在分布式环境中轻松实现一些Java的对象 (Bloom filter, BitSet, Set, SetMultimap, ScoredSortedSet, SortedSet, Map, ConcurrentMap, List, ListMultimap, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, ReadWriteLock, AtomicLong, CountDownLatch, Publish / Subscribe, HyperLogLog)。

Jedis与Redisson对比有什么优缺点?

  • Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持;
  • Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。
  • Redisson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。

Redis支持哪几种数据类型?

String、List、Set、Sorted Set、hashes

你熟悉哪些Redis的数据结构? zset是干什么的? 和set有什么区别?

说说Redis哈希槽的概念?

Redis集群没有使用一致性hash,而是引入了哈希槽的概念,Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。

Redis的hash, 存储和获取的具体命令叫什么名字?

LPOP和BLPOP的区别?

Redis的有一些包含SCAN关键字的命令是干嘛的? SCAN返回的数据量是固定的吗?

Redis有哪几种数据淘汰策略?

  • noeviction: 返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
  • allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
  • volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
  • allkeys-random: 回收随机的键使得新添加的数据有空间存放。
  • volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
  • volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。

Redis中的Lua有没有使用过? 可以用来做什么? 为什么可以这么用?

Redis的Pipeline是用来干什么的?

Redis持久化大概有几种方式? aof和rdb的区别是什么? AOF有什么优缺点吗?

Redis Replication的大致流程是什么? bgsave这个命令的执行过程? -- 偏题

如果有很多 KV数据要存储到Redis, 但是内存不足, 通过什么方式可以缩减内存? 为什么这样可以缩小内存?

为什么要做Redis分区?

分区可以让Redis管理更大的内存,Redis将可以使用所有机器的内存。如果没有分区,你最多只能使用一台机器的内存。分区使Redis的计算能力通过简单地增加计算机得到成倍提升,Redis的网络带宽也会随着计算机和网卡的增加而成倍增长。

Redis中List, HashTable都用到了ZipList, 为什么会选择它?

Redis是单线程的,如何提高多核CPU的利用率?

可以在同一个服务器部署多个Redis的实例,并把他们当作不同的服务器来使用,在某些时候,无论如何一个服务器是不够的,

所以,如果你想使用多个CPU,你可以考虑一下分片(shard)

Redis集群方案应该怎么做?都有哪些方案?

  • twemproxy,大概概念是,它类似于一个代理方式,使用方法和普通redis无任何区别,设置好它下属的多个redis实例后,使用时在本需要连接redis的地方改为连接twemproxy,它会以一个代理的身份接收请求并使用一致性hash算法,将请求转接到具体redis,将结果再返回twemproxy

使用方式简便(相对redis只需修改连接端口),对旧项目扩展的首选。 问题:twemproxy自身单端口实例的压力,使用一致性hash后,对redis节点数量改变时候的计算值的改变,数据无法自动移动到新的节点。

  • codis,目前用的最多的集群方案,基本和twemproxy一致的效果,但它支持在 节点数量改变情况下,旧节点数据可恢复到新hash节点。
  • redis cluster3.0自带的集群,特点在于他的分布式算法不是一致性hash,而是hash槽的概念,以及自身支持节点设置从节点。
  • 在业务代码层实现,起几个毫无关联的redis实例,在代码层,对key 进行hash计算,然后去对应的redis实例操作数据。

这种方式对hash层代码要求比较高,考虑部分包括,节点失效后的替代算法方案,数据震荡后的自动脚本恢复,实例的监控,等等。

Redis扩容,失效key清理策略

Redis的持久化怎么做,aof和rdb,有什么区别,有什么优缺点。

  • RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储.
  • AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大.

如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.

你也可以同时开启两种持久化方式, 在这种情况下, 当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.

最重要的事情是了解RDB和AOF持久化方式的不同,让我们以RDB持久化方式开始

Redis集群会有写操作丢失吗?为什么?

Redis并不能保证数据的强一致性,这意味这在实际中集群在特定的条件下可能会丢失写操作

怎么理解Redis事务

事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

Redis事务相关的命令有哪几个?

MULTI、EXEC、DISCARD、WATCH


Redis实现分布式锁与Zookeeper实现分布式锁区别

Redis实现分布式锁思路

基于Redis实现分布式锁(setnx)setnx也可以存入key,如果存入key成功返回1,如果存入的key已经存在了,返回0.

Zookeeper实现分布式锁思路

同时在zookeeper上创建相同的一个临时节点,因为临时节点路径是保证唯一,只要谁能够创建节点成功,谁就能够获取到锁,没有创建成功节点,就会进行等待,当释放锁的时候,采用事件通知给客户端重新获取锁的资源。

相同点

在集群环境下,保证只允许有一个jvm进行执行。

不同点

  • 获取锁
    1. 多个客户端(jvm),会在Zookeeper上创建同一个临时节点,因为Zookeeper节点命名路径保证唯一,不允许出现重复,只要谁能够先创建成功,谁能够获取到锁。
    2. 多个客户端(jvm),会在Redis使用setnx命令创建相同的一个key,因为Redis的key保证唯一,不允许出现重复,只要谁能够先创建成功,谁能够获取到锁。
  • 释放锁
    1. Zookeeper使用直接关闭临时节点session会话连接,如果session会话连接关闭的话,该临时节点也会被删除。
    2. Redis在释放锁的时候,为了确保是锁的一致性问题,在删除的redis 的key时候,需要判断同一个锁的id,才可以删除。
  • 如何解决死锁现象问题
    1. Zookeeper使用会话有效期方式解决死锁现象。
    2. Redis 是对key设置有效期解决死锁现象
  • 性能角度考虑因为Redis是NoSQL数据库,相对比来说Redis比Zookeeper性能要好。
    1. Redis分布式锁,必须使用者自己间隔时间轮询去尝试加锁,当锁被释放后,存在多线程去争抢锁,并且可能每次间隔时间去尝试锁的时候,都不成功,对性能浪费很大
    2. Zookeeper分布锁,首先创建加锁标志文件,如果需要等待其他锁,则添加监听后等待通知或者超时,当有锁释放,无须争抢,按照节点顺序,依次通知使用者。
  • 可靠性, Zookeeper可靠性比Redis更好。
    1. 因为Redis有效期不是很好控制,可能会产生有效期延迟;
    2. Zookeeper就不一样,因为Zookeeper临时节点先天性可控的有效期

Redis

Redis可以做什么

提到Redis 大多数人的第一想法就是做缓存,但是如果作为开发人员的我们,对于Redis的作用只停留在做缓存上,那么是不合格的。Redis还具有许多其他用途,例如:分布式锁,分布式系统唯一序列号生成器,计数器等等。

在不同的场景下,我们应该选择Redis不同的数据结构进行处理

Redis的数据类型和用法
  1. String字符串 可以为整形、浮点型和字符串,统称为元素,点赞、计数、粉丝数
  2. Hash 一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象、用户信息
  3. List 有序串列表,粉丝列表、消息队列
  4. Set 无序集合,集合成员是唯一的,这就意味着集合中不能出现重复的数据。共同关注、喜好、好友,标签
  5. ZSet 有序集合,集合成员是唯一的。排行榜
Redis高可用模式——主从复制、哨兵模式、群集模式

1、主从复制:主从复制是高可用Redis的基础,哨兵和集群都是在主从复制基础上实现高可用的。主从复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复。 缺陷: ●故障恢复无法自动化; ●写操作无法负载均衡; ●存储能力受到单机的限制。 2、哨兵:在主从复制的基础上,哨兵实现了自动化的故障恢复。 缺陷: ●写操作无法负载均衡; ●存储能力受到单机的限制; ●哨兵无法对从节点进行自动故障转移,在读写分离场景下,从节点故障会导致读服务不可用,需要对从节点做额外的监控、切换操作。 3、集群:通过集群,Redis解决了写操作无法负载均衡,以及存储能力受到单机限制的问题,实现了较为完善的高可用方案。

哈希槽介绍

Redis Cluster在设计中没有使用一致性哈希(Consistency Hashing),而是使用数据分片引入哈希槽(hash slot)来实现;

一个 Redis Cluster包含16384(0~16383)个哈希槽(补充:为什么redis集群的最大槽数是16384个?),存储在Redis Cluster中的所有键都会被映射到这些slot中,集群中的每个键都属于这16384个哈希槽中的一个。按照槽来进行分片,通过为每个节点指派不同数量的槽,可以控制不同节点负责的数据量和请求数.

常用的IO复用模型有三种:select,poll,epoll

(1)select==>时间复杂度O(n)

它仅仅知道了,有I/O事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。所以select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长。

(2)poll==>时间复杂度O(n)

poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态, 但是它没有最大连接数的限制,原因是它是基于链表来存储的.

(3)epoll==>时间复杂度O(1)

epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/O事件通知我们。所以我们说epoll实际上是事件驱动(每个事件关联上fd)的,此时我们对这些流的操作都是有意义的。(复杂度降低到了O(1))

select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

epoll跟select都能提供多路I/O复用的解决方案。在现在的Linux内核里有都能够支持,其中epoll是Linux所特有,而select则应该是POSIX所规定,一般操作系统均有实现

热点发现

本地缓存的方案中,有一个问题需要解决,**那就是怎么知道哪些数据是热点数据?**因为本地缓存资源有限,不可能把所有的商品数据进行缓存,它只会缓存热点的数据。那怎么知道数据是热点数据呢?

人为预测

就是人工标记,预测这个商品会成为热点,打个标记。web应用根据这个标记把此商品保存到本地缓存中

这个方案,是根据运营人员的经验进行预测,太不靠谱了。

系统推算

这个方案是根据实实在在的数据访问量进行推算形成,网上也介绍了用访问日志的什么算法,推算哪些是热点数据。 老顾这里分享一个比较简单的方式,就是利用redis4.x自身特性,LFU机制发现热点数据。实现很简单,只要把redis内存淘汰机制设置为allkeys-lfu或者volatile-lfu方式,再执行

./redis-cli --hotkeys

会返回访问频率高的key,并从高到底的排序

线程模型

多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO多路复用程序会监听多个 socket,会将产生事件的 socket 放入队列中排队,事件分派器每次从队列中取出一个 socket,根据 socket 的事件类型交给对应的事件处理器进行处理。

文件事件处理器的结构:
  • 多个 socket
  • IO 多路复用程序
  • 文件事件分派器
  • 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
Redis是单线程吗

Redis的演变历程

1、Redis 4之前的版本是单线程

2、Redis 4.x 版本不是严格意思的删除,处理客户端请求的是单线程,添加了异步删除的功能

3、Redis 5.x 版本采用多线程解决问题

Redis的单线程指什么

Redis 单线程主要指的是网络IO和键值对读写是由一个线程来完成的,Redis在处理客户端的请求的时候包括获取(Socket),解析,执行,内容返回(Socket写)等由一个顺序串行的主线程处理,这即为单线程 但Redis的其它功能,比如持久化,异步删除,集群数据同步等等,都是由额外的线程执行的,是多线程 即Redis工作线程是单线程的,但是,整个Redis来说是多线程的

Redis单线程为什么很快

1、基于内存操作:Redis的所有数据都存在内存中,因此所有的运算都是内存级别

2、数据结构简单:Redis的数据结构简单,查找和操作时间大部分时间复杂度是O(1)

3、多路复用和非阻塞:Redis使用多路复用功能来监听多个Socket链接客服端,这样可以一个线程处理多个请求,减少线程切换带来的开销,同时也避免了IO阻塞操作

4、避免上下文切换:因为是单线程模型,因此避免了多线程切换和多线程竞争,减少时间和性能的消耗

Redis之前为什么采用单线程

1、单线程易于开发和维护

2、单线程模型也可以并发处理多客户端的请求,主要使用多路复用和非阻塞IO

3、对于Redis来说,主要的性能瓶颈是内存或者网络IO而不是CPU

Redis为什么要引用多线程

大key删除可以会造成Redis主线程卡顿 当被删除的key是一个非常大的对象时,例如几十兆的对象时,那么删除将是一个非常耗时的操作,其他命令将会不能执行发生阻塞,造成Redis主线程卡顿。如果是在一个高并发的环境下,将会产生十分严重的问题。

这就是redis3.x单线程时代最经典的故障,大key删除的头疼问题。

那么如何解决大key删除问题呢

1、惰性删除

2、当删除大key时,因为是单线程,所以会造成Redis服务卡顿,于是在4.0版本新增了多线程的模块用于解决删除数据效率低的问题。处理读写请求仍然只是一个线程

3、unlink key 、flushdb async 、flushall async 异步删除,新起子线程进行删除工作

引入多线程后能够解决大key删除阻塞问题吗

如果是同步删除,那么同样会面临阻塞问题。因为Redis的工作线程(执行命令)任然是单线程。因此在实际工作中应该避免大key的出现

redis的角色

Redis是一个可基于内存亦可持久化的日志型、Key-Value数据库。

论持久化,我们平时使用的MySQL就足够了,那为什么还需要引入Redis呢?无法就是 :性能

数据获取的流程,一般是前端请求,后台先从缓存中取数据,缓存取不到则去数据库中取,数据库取到了则返回给前端,然后更新缓存,如果数据库取不到则返回空数据给前端。

那Redis的性能表现在哪方面呢——并发

redis和数据库的数据一致性

3种方案保证数据库与缓存的一致性

  • 延时双删策略:延时双删的步骤:

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

  • 删除缓存重试机制:删除缓存重试机制

    不管是延时双删还是Cache-Aside的先操作数据库再删除缓存,如果第二步的删除缓存失败呢?

    删除失败会导致脏数据哦~

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

  • 读取biglog异步删除缓存:同步biglog异步删除缓存

    重试删除缓存机制还可以,就是会造成好多业务代码入侵。还可以通过数据库的binlog来异步淘汰key。以mysql为例 可以使用阿里的canal将binlog日志采集发送到MQ队列里面,然后编写一个简单的缓存删除消息者订阅binlog日志,根据更新log删除缓存,并且通过ACK机制确认处理这条更新log,保证数据缓存一致性

在分布式系统中,缓存和数据库同时存在时,如果有写操作的时候,「先操作数据库,再操作缓存」。如下:

1.读取缓存中是否有相关数据 2.如果缓存中有相关数据value,则返回 3.如果缓存中没有相关数据,则从数据库读取相关数据放入缓存中key->value,再返回 4.如果有更新数据,则先更新数据库,再删除缓存 5.为了保证第四步删除缓存成功,使用binlog异步删除 6.如果是主从数据库,binglog取自于从库 7.如果是一主多从,每个从库都要采集binlog,然后消费端收到最后一台binlog数据才删除缓存,或者为了简单,收到一次更新log,删除一次缓存

缓存的问题,击穿,穿透和血崩
  • 缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大(不存在的数据)。这时的用户很可能是攻击者,攻击会导致数据库压力过大。 解决:
  1. 接口层增加校验,如用户鉴权校验,id做基础校验,比如 id<=0的直接拦截; 最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。

  2. 另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。通过这个直接设置的默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴。

  • 缓存击穿是指缓存中没有但数据库中有的数据,当一个key非常热点(类似于爆款),在不停的扛着大并发,大并发集中对这一个点进行访问;当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。 解决:
  1. 设置热点数据永远不过期。
  2. 加互斥锁。
  • 缓存雪崩是指缓存中数据大批量到过期时间,大批量数据同一时间过期,导致请求量全部请求到数据库,造成数据库宕机。 解决:
  1. 给缓存失效时间,加上一个随机值,避免大量缓存集体失效。
  2. 双缓存:缓存A和B,比如A的失效时间是20分钟,B不失效。比如从A中没读到,就去B中读,然后异步起一个线程同步到A。
redis的内存淘汰策略

内存淘汰策略

  1. noeviction:当内存使用超过配置的时候会返回错误,不会驱逐任何键

  2. allkeys-lru:加入键的时候,如果过限,首先通过LRU算法驱逐最久没有使用的键

  3. volatile-lru:加入键的时候如果过限,首先从设置了过期时间的键集合中驱逐最久没有使用的键

  4. allkeys-random:加入键的时候如果过限,从所有key随机删除

  5. volatile-random:加入键的时候如果过限,从过期键的集合中随机驱逐

  6. volatile-ttl:从配置了过期时间的键中驱逐马上就要过期的键

  7. volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键

  8. allkeys-lfu:从所有键中驱逐使用频率最少的键

redis过期键的删除策略

其实有三种不同的删除策略: (1):立即删除。在设置键的过期时间时,创建一个回调事件,当过期时间达到时,由时间处理器自动执行键的删除操作。 (2):惰性删除。键过期了就过期了,不管。每次从dict字典中按key取值时,先检查此key是否已经过期,如果过期了就删除它,并返回nil,如果没过期,就返回键值。 (3):定时删除。每隔一段时间,对expires字典进行检查,删除里面的过期键。 可以看到,第二种为被动删除,第一种和第三种为主动删除,且第一种实时性更高。下面对这三种删除策略进行具体分析。