Redis数据库 专题

Redis是一种内存型数据库。传统的数据库储存在硬盘中,而Redis数据库存在内存中,所以读写速度非常快。因此redis广泛用于缓存方向,除此之外也经常用于实现分布式锁。redis提供了多种数据类型来支持不同的业务场景。

除此之外,redis支持事务、持久化、LUA脚本、LRU驱动事件、多种集群方案。

为什么要用Redis/为什么要用缓存

高性能高并发

高性能:从内存读取数据比从硬盘读取要快很多。如果数据库中对应的数据改变之后,同步改变缓存中相应的数据即可。

高并发:直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以可以考虑将数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。

Redis为什么那么快

  1. 纯内存操作
  2. 单线程操作,避免了频繁的上下文切换造成的开销
  3. 采用了非阻塞I/O多路复用机制

Redis常见数据结构

  • String
  • Hash
  • List
  • Set
  • Sorted Set

Redis内存淘汰机制

Redis采用的是定期清除+惰性删除的策略。

为什么不使用定时删除策略?

定时删除,需要用一个定时器来负责监视key,过期自动和三处。虽然内存可以及时释放,但是这十分消耗CPU资源。在大并发请求下,CPU要将时间应用在处理请求上,而不是删除key上。

定期删除,redis默认每隔100ms检查一下,是否有过期的key,有过期的key则删除。这里需要强调一下的是,redis并不会检查所有的key,而是会随机抽取。如果只采用定期删除策略,会导致很多key到时间而没有删除,于是就需要惰性删除。惰性删除,并不是直接删除,而是你在获取某个key的时候,redis会检查一下是否过期,过期了才删除。

如果定期删除没有删除key,而且你也没有即时去请求key,惰性删除没有起效果。Redis的内存会越来越高,这时候就需要采用一些内存淘汰机制。

redis提供6中数据淘汰策略:

  1. volatile-lru:从设置的过期时间数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰。
  2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰。
  3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰。
  4. allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。
  5. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰。
  6. no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。

4.0版本后增加以下两种:

  1. volatile-lfu:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰。
  2. allkeys-lfu:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用key。

总结:两种操作对象:已设置过期时间的数据集(server.db[i].expires)和数据集(server.db[i].dict),四种机制lrulfurandomttl(仅针对过期时间数据集)加no-eviction

Redis持久化机制 (重要)

持久化:将内存中的数据写入到硬盘里面。主要是为了之后重用数据(比如重启、机器故障之后恢复数据),或者为了防止系统故障而将数据备份到一个远程位置。

Redis不同于memcache很重要的一点在于Redis支持持久化,提供了两种不同的持久化方式快照(snapshotting, RDB),另一种方式是只追加文件(append-only file, AOF)

快照

Redis通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器创建具有相同数据的服务器副本(Redis主从结构,主要用来提高Redis性能),还可以将快照留在原地以便重启服务器的时候使用。

AOF(append-only file)持久化

默认没有开启,可以通过下面的参数开启:

appendonly yes

开启AOF持久化后每执行一条会更改Redis中的数据的命令。Redis就会将该命令写入硬盘中那个的AOF文件。AOF文件和RDB文件位置相同,可以通过dir参数设置。

appendfsync always # 每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
appendfsync everysec # 每秒钟同步一次,显式地将多个写命令同步到硬盘
appendfsync no # 让操作系统决定何时进行同步 

为了兼顾数据和写入性能,用户可以考虑appendfsync everysec选项,让Redis每秒同步一次AOF文件,Redis性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只丢失一秒之内产生的数据。当硬盘忙于写入操作的时候,Redis还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。

Redis 4.0开始支持RDB和AOF的混合持久化(默认关闭,可以通过配置项aof-use-rdb-preamble开启)

开启混合持久化之后,AOF重写的时候就直接把RDB的内容写到AOF文件开头,这样做的好处是可以结合RDB和AOF的优点,快速加载的同时避免丢失过多的数据,当然缺点也是有的,AOF里面RDB部分的压缩格式不再是AOF格式,可读性较差。

Redis 主从架构

单机的redis,能够承载的QPS大概在上万或者几万不等。对于缓存来说,一般都是用来支持读高并发的。因此Redis架构实现上会采用主从架构(master-slave)一主多从主节点(master mode)负责,并且将数据复制到其他从属节点,从节点(slave node)负责。即所有的读请求全部走从节点,这样可以轻松实现水平扩容。
在这里插入图片描述
redis replication → \rightarrow 主从架构 → \rightarrow 读写分离 → \rightarrow 水平扩容支撑高并发

Redis Replication的核心机制
  • redis采用异步方式复制数据到slave节点。(redis2.8开始,slave node会周期性地确认自己每次复制的数据量)
  • 一个master node配置多个slave节点
  • slave node也可以连接其他slave node
  • slave node在做复制的时候,不会block master node的工作
  • slave node在做复制的时候,也不会block对自己的查询操作,它会使用旧的数据集来提供服务;但在复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务
  • slave node主要用来进行横向扩容,做读写分离,扩容的slave node可以提高读的吞吐量。

如果采用了主从架构,那么建议必须开启master node的持久化机制,不建议使用slave node作为master node的数据热备,因为那样的话,如果你关掉master的持久化,可能在master宕机重启的时候数据是空的,然后一经复制,slave node的数据也丢失了。

另外,master的各种备份方法,也需要做。万一本地的所有文件丢失了,从备份中挑选一份rdb去恢复master,这样才能保证重新启动的时候,是有数据的。slave node可以自动接管master node,但又有可能sentinel还没有检测到master failure,master node就自动重启了,仍有可能导致上述slave node的数据被清空。

master的持久化和多种备份方案都是为了防止重启是数据不为空从而导致slave结点数据清空。

Redis主从复制核心原理

启动一个slave node时,它会发送一个PSYNC命令给master node。如果slave node和master node是初次连接,则会触发一次full resynchronization全量复制。此时master会启动一个后台线程,开始生成一份RDB快照文件,同时还将从客户端client新收到的所有写命令缓存在内存中。RDB文件生成完毕后,master会将这个RDB发送给slave,slave会先写入本地磁盘,然后从本地磁盘加载到内存中。接着master会将内存中缓存的写命令发送到slave,slave也会同步这些数据。slave如果跟master之间发生了网络故障,断开了连接,会自动重连,连接之后master node仅会复制给slave部分缺少的数据。
在这里插入图片描述

1.主从复制的断点续传。
mastet node和slave node会在内存中维护一个backlog,同时在backlog中保存一个replica offsetmaster run id。如果master和slave之间的网络连接断掉了,slave会让master从上次replica offset开始复制,如果没有找到对应的offset,那么就会执行一次resynchronization

master run id的作用:
根据host+ip来定位master,是不太靠谱的。因为如果master node重启或者数据发生了变化,那么slave node根据run id仍能做出正确区分。

2.无磁盘化复制
master在内存中直接创建RDB,然后发送给slave,不会在自己本地落地磁盘。只需要在配置文件中开启repl-diskless-sync yes即可。

3.过期key处理
slave不会过期key,只会等待master过期key。如果一个master过期了key,那么会模拟一条del命令发送给slave。

Redis事务

Redis通过MULTIEXECWATCH等命令来实现事务(transaction)功能。事务提供了一种将多个请求打包,然后一次性的,按顺序的执行多个命令的机制,并且在事务执行期间,服务不会中断事务而改去执行其他客户端的命令请求,它会将事务中的所有命令都执行完毕。

Redis中,事务总是具有原子性(Atomicity)一致性(Consistency)隔离性(Isolation),并且当Redis运行在某种特定的持久化模式下时,事务也具有持久性(Durability)

缓存雪崩和缓存穿透

缓存雪崩

缓存同一时间大面积的失效,所以,后面的请求全都落到数据库上,造成数据库短时间内承受大量请求而崩掉。

解决方法:

  • 事前:尽量保证redis集群的高可用性,发现机器宕机尽快补上。选择合适的内存淘汰策略。
  • 事中:本地ehcache缓存+hystrix限流&降级,避免MySQL崩掉。
  • 事后:利用redis持久化机制保存的数据尽快恢复缓存。

缓存穿透

大量请求的key根本不在缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。

解决方法:

  • 缓存无效key:如果缓存和数据库都查不到某个key就写一个到redis中并设置过期时间。并不能从根本上解决问题,尽量要将无效的key的过期时间设置短一点。

  • 布隆过滤器:把所有可能请求的值都存放在布隆过滤器中,当用户请求过来,先判断用户发来请求的值是否存在于布隆过滤器中,存在的话才会走正常流程,不存在的话直接返回请求错误信息。

如何解决Redis并发竞争key问题

并发竞争key的问题指的是多个系统同时对一个key进行操作,但最后执行的顺序与我们期望的不同,这样导致了结果的不同。

分布式锁(zookeeper和redis都可以实现分布式锁)

基于zookeeper临时有序节点可以实现分布式锁。大致思想为:每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。判断是否获取锁的方式很简单,只需要判断有序节点序号中的最小的一个。当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。完成业务流程后,删除对应的子节点释放锁。

如何保证缓存与数据库双写时的数据一致性

读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况。

最好不要使用这个方案,串行之后系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的一个请求。

参考

来自Java Guide面试突击版,百度可得最新版本,这里有删减和修正以及扩充。

Redis 主从架构

2020年11个Redis系列高频面试题,哪些你还不会?

posted @ 2020-09-24 09:56  从流域到海域  阅读(50)  评论(0编辑  收藏  举报