Redis基础


介绍

Redis的数据是存在内存中,是个内存数据库,所以读写速度非常快,一般用于缓存方向,另外也可以做分布式锁,甚至消息队列。

缓存处理的流程

  1. 如果用户请求的数据在缓存中就直接返回;
  2. 缓存中不存在就查看数据库是否存在;
  3. 数据库存在就更新缓存的数据;
  4. 数据库不存在直接返回空数据

为什么要使用缓存

主要是“高并发”与“高性能”

  • 高并发 一般Mysql这类的数据库QPS大概1W左右,使用Redis缓存之后很容易达到10W

    QPS:服务器每秒可以执行的查询次数

  • 高性能
  • 操作缓存就是操作内存,速度相当快

Redis常见的数据结构以及使用场景分析

  • string
    1. 是简单key-value类型;
    2. 一般用于需要计数的场景,比如访问的次数,点赞和转发的数量等;
    3. 常用命令set,get,strlen,exists,dect,setex
  • list
    1. list即链表,Redis的list为双向链表,可以支持反向查询和遍历;
    2. 一般用于发布订阅或者消息队列;
    3. 常用命令rpush,lpop,lpush,rpop,llen
  • hash
    1. 类似于HashMap,内部实现(数组+链表)
    2. 一般用于存储对象数据
    3. 常用命令hset,hmset,hexists,hget
  • set
    1. 类似于HashSet,无序集合,自动去重
    2. 一般用于存放数据不重复的集合,比如共同好友
    3. 常用命令sadd,spop,smembers,scard
  • sorted set
    1. 有序集合自动去重
    2. 一般用于存放数据不重复排序的集合,比如排行榜
    3. 常用命令zadd,zscore,zrange,zcard

衍生问题

  • 为什么list和set使用跳表?跳表原理

    1.跳表操作时间复杂度和红黑树相同
    2.跳表代码实现更易读
    3.跳表区间查找效率更高

为什么这么快

  1. 基于内存,绝大部分是存粹的内存操作
  2. 采用单线程,避免了不必要的上下文切换与竞争,也不存在多进程切换而消耗cpu,不用考虑加锁释放锁的问题;
  3. IO多路复用(非阻塞IO),IO多路复用程序会监听多个Socket,将socket放到一个队列中进行排队,每次从队列中取出一个socket给事件分派器,再把socket给对应的事件处理器

    每次我们一个socket请求过来 和 redis中的 server socket建立连接后,通过IO多路复用程序,就会往队列中插入一个socket,文件事件分派器就是将队列中的socket取出来,分派到对应的处理器,在处理器处理完成后,才会从队列中在取出一个
    这里也就是用一个线程,监听了客户端的所有请求,被称为Redis的单线程模型。

衍生问题:

  • Redis大key问题排查以及解决方案? 大key指的是Redis的字符串类型过大,非字符串类型元素过多会产生以下问题:
    1. Redis阻塞:因为Redis单线程特性,如果操作某个Bigkey耗时比较久,则后面请求会被阻塞。
    2. 内存空间不足
    3. 过期时可能阻塞:如果大key设置了过期时间,当过期后,这个key被删除,如果没有过期异步删除,会存在阻塞Redis的可能性
  • 如何排查? redis 可使用 redis-cli 的 “–bigkeys” 选项查找大Key

  • 如何处理
    1. 删除大key:4.0之前使用SCAN命令读取数据再进行删除,避免线程阻塞
    2. 拆分:对于string类型的,可以拆分成多个key-vlaue;hash或者list可以拆分成多个hash或list

删除策略

惰性删除

主动查询是否过期,过期就删除不返回,没过期不管。

定期删除

默认100ms随机抽取一定数量设置了过期的key,检查是否过期,过期就删除;

Redis采用的定期删除+惰性删除

Redis内存相关

Redis内存默认多少?哪里查看?如何设置修改?

  1. 打开配置文件redis.conf,找到关键字maxmemory,如果没有配置或者设置最大内存大小为0.在64位操作系统下是不限制内存大小,在32位操作系统下最多使用3GB内存;maxmemory是bytes字节类型,注意单位转换。

    生产上如何配置?

  2. 一般推荐Redis设置内存为最大物理内存的四分之三

    如何设置Redis的内存大小

  3. 直配置文件接在对应的maxmemory,后面加上大小;或者命令config set maxmemory [大小]

    如何查看Redis内存使用情况

  4. info memory命令

    如果Redis打满了怎么玩办?

  5. 故意把Redis最大值改为1个byte试试,会报OOM异常错误

    如果Redis内存使用超出了设置的最大值会怎么样?

  6. 引出淘汰策略

内存淘汰机制

Redis提供6种数据淘汰策略

  1. volatile-lru:从设置了过期时间的数据集中选出最少使用数据淘汰
  2. volatile-ttl:从设置了过期时间的数据集中选出要过期的数据淘汰
  3. volatile-random:从设置了过期时间的数据集中任意选择数据淘汰
  4. allkeys-lru:当内存不足写入新数据,移除最少使用的key(常用)
  5. allkeys-random:从数据中任意选择数据淘汰
  6. no-eviction:内存不足,写入数据报错

4.0后增加两种

  1. volatile-lfu:从设置了过期时间的数据集中选出不常用的数据淘汰
  2. allkeys-lfu:当内存不足写入新数据,移除最不常用使用的key

Redis的持久化

Redis支持持久化,且有两种不同的持久化操作,一般会RDB+AOF一起使用,RDB做冷备

RDB(快照)

它将生成数据集的时间点快照

原理
使用多进程COW(copy on write)。Redis在持久化会fork一个子进程,相当于在当前进程复制了一个进程;主进程和子进程会共享内存的代码块与数据段。所以快照持久化完全可以交给子进程处理,父进程继续处理客户端请求。子进程做数据持久化,不会改变现有的内存数据结构,只是对数据结构进行遍历读取并序列化到磁盘中。

  • 可能产生的问题
    1. redis是一个单线程的程序,意味着不仅要响应用户的请求,还需要内存快照,内存快照必须进行IO操作,拖慢服务器的性能
    2. 持久化的同时,内存数据接口可能还在变化,结果一个请求把它删除,但是持久化才刚结束

AOF(只追加文件)

每次修改内存的操作都会被记录,类似于记录日志 配置文件./redis-server --appendonly yes

原理
aof的synv属性,在写每条指令sync磁盘,就不会丢失数据,一般使用定时sync,1s一次,这个时候最多丢失1s的数据

-可能产生的问题

  1. redis长期运行过程中,AOF的日志会越来越多,如果重放AOF日志非常耗时,导致Redis无法立即对外提供服务,需要对日志进行瘦身。
  2. Redis提供了bgwriteaof指令对aof进行日志瘦身,原理就是开辟了一个子进程对日志进行遍历转为一系列的操作指令,序列化到一个新的AOF中,序列化完成后再将操作期间的增量AOF追加到新的AOF中,追加完后立即代替旧的AOF文件

实际场景操作

重启Redis,一般很少使用RDB恢复,因为会丢失大量数据,一般使用AOF日志重放,只是AOF的性能比RDB慢很多,启动需要花费比较长的时间

Redis4.0带来了一个新的持久化选项-混合持久化。将RDB文件的内容与AOF日志放在一起,这个时候AOF不再是全量日志,而是持久化开始到持久化结束这段时间的增量日志(这部分AOF日志比较小)。即先加载RDB内容,然后重放增量aof日志。

如何保证数据库和缓存的一致性

  1. 先更新数据库,后删除缓存 更新数据库成功,如果删除缓存失败或者还没有来的及删除,那么,其他线程从缓存中读取到的就是旧值,还是会发生不一致。

    利用消息队列,更新数据库,成功后往队列发送消息,消息达到后删除缓存,可以利用消息队列的重试机制达到最终一致性(建议使用)
    缺点:需要引入第三方中间件,维护成本高,且引入消息队列需要考虑消息丢失、顺序等问题

  1. 先删除缓存,后更新数据库 请求A先删除Redis中的数据,然后去数据库进行更新操作,此时请求B看到Redis中的数据是空的,就会去数据库中查询,补录到缓存,但是此时请求A并没有更新成功,或者事务还未提交。

    延时双删:
    1.先删除Redis缓存;
    2.更新数据,确保事务提交;
    3.延迟删除redis缓存

集群方案

Redis Cluster (数据量大的情况)

Redis Cluster详解 可以帮助我们解决 Redis 大数据量缓存的问题,并且,也方便我们进行横向拓展(增加 Redis 机器)

Redis Cluster 通过分片(sharding)来进行数据管理,并提供复制和故障转移等功能。Redis Cluster 并没有使用一致性哈希,每一个键值对都属于一个被称作 hash slot 的东西。

通常 Redis Cluster 有 16384 hash slots ,要计算给定 key 的 hash slot ,我们只需要通过对每个 key 计算 CRC16 值,然后对 16384(hash slot的数量) 取模。

假设集群有 3 个 Redis 节点组成,每个节点负责整个集群的一部分数据:

  • Node 1 : 0 - 5500 的 hash slot.
  • Node 2 : 5501 - 11000 的 hash slot. .
  • Node 3 : 11001 - 16383 的 hash slot..

Redis Cluster 是去中心化的,任何一台机器宕机,另外两个节点,不影响的。因为 key 找的是 hash slot,不是机器。并且,我们想要添加新的节点比如 Node4 进入 Redis Cluster 也非常方便,只需要将一些 hash slot 从 节点 Node1,Node2,Node3 移动到 Node4 即可。

主从复制(数据量小的情况)

Redis主从架构 一主(master)多从(slave)来提高可用性和读吞吐量,slave的多少取决于你的读吞吐量

  • 同步方式 启动一台slave的时候,会发送一个psync命令给master,如果slave第一次连接到master,会触发一个全量复制,master会启动一个线程,生成RDB快照,会把新的请求都缓存到内存中,RDVB生成之后,master会把这个快照发送给slave,slave拿到后写进本地磁盘,然后加载进内存,master之后会把内存的缓存都发给slave

如何保证高可用

Redis如何做到高可用

哨兵模式

Sentinel 是 Redis 的一种运行模式 ,它主要的作用就是对 Redis 运行节点进行监控。当 master 节点出现故障的时候, Sentinel 会帮助我们实现故障转移,确保整个 Redis 系统的可用性。整个过程完全自动,不需要人工介入。

  1. 集群监控
  2. 消息通知
  3. 故障转移
  4. 配置中心
  • 定时监控任务
    1. 每隔10s,slave节点向主节点发送info命令获取最新的拓扑结构;
    2. 每隔2s,slave节点向某频道发送slave节点对于主节点的判断以及当前salve节点信息,每个哨兵节点会订阅频道了解其他slave节点对主节点的判断;
    3. 每隔1s,每个slave会向主从节点发送一条ping命令,做心跳检查,确认节点是否可达
  • 主客观下线
    1. 主观下线,根据三个定时任务对没有回复的节点做主观下线;
    2. 客观下线,如果主观下线的是主节点,会咨询其他slave节点对该主节点的判断,如果超过半数,主节点客观下线;
    3. 选举某一哨兵节点偶为领导进行故障转移,raft算法,每个slave有一票同意权,哪个slave做出主观下线,会询问其他slave节点是否同意其为领导者,超过半数则为领导者,一般来说,谁先做出客观下线,谁就成为领导者。

Redis常见问题

缓存穿透

数据库中不存在的数据,导致每次请求都直接打到数据库,会把数据库打崩

  • 解决方案
    1. 缓存无效的key,不存在的值,默认设置value为null,并设置失效时间(不推荐),每次请求可能会导致Redis内存打满,还有就是会缓存大量无效的key或者value
    2. 利用布隆过滤器,可以设置预估大小,并设置误差率,把数据放到布隆过滤器中

布隆过滤器,原理是把key值的3次hash放在hash数组上
1.一个元素如果判断结果不存在,则一定不存在;如果判断存在,元素不一定存在;
2.布隆过滤器可以添加元素,但是不能删除元素,删除元素会导致误判率增加;
3.hash数组大小越小或者数据量越大,则误差率越大(即增加数组长度 )

布窿过滤器可能存在问题,是内存占用过大,所以可以使用布窿算法降低内存占用

布隆过滤器的实现

  • 衍生问题

    2个文件,里面有100亿个url,最高效率找出这两个文件的交集,分别给出精准算法和模糊算法

    1. 精准算法:使用分治,拆分成小文件,比如第一个A文件里面其中的url1放到第一个小文件,依次类推,B文件也如此,只需要比较A的第一个文件与B的第一个文件,依次比较;
    2. 模糊算法:利用布隆过滤器,A文件的信息利用hash函数放到数组里,之后B文件也进行hash,判断放到的位置为1还是为0,为1说明碰撞,为0说明不同,但是有一定的误判率

缓存雪崩

大量的hot key同时失效,导致数据全打在数据库上

  • 解决方案
    1. 往Redis存数据的时候可以把key的失效时间加上随机值,保证数据不在同一时间大面积失效setRedis(Key,Value,time+math.random()*1000)
    2. 热点数据设置永不失效
    3. 如果Redis集群部署,那么将热点数据分布在不同的库,但是最好还是要加上失效时间随机

缓存雪崩事前事中事后解决方案

  • 事前:Redis高可用,主从+哨兵,Redis cluster,避免全盘崩溃
  • 事中:本地ehcache缓存 + hystrix 限流&降级,避免MySQL被打死。
  • 事后:Redis持久化,一旦重启,自动从磁盘上加载数据,加速恢复缓存数据。

用户发送一个请求,A系统收到请求后,先查本地ehcache缓存,如果没有再去查Redis。如果两个都没有,再去查数据库,将数据库的结果写入echache与Redis中。 限流组件,可以设置每秒的请求,有多少能通过组建,剩余没通过的请求,可以走降级,返回一些默认值、友情提示或者空值。

优点

  • 数据库绝对不会挂,组件确保每秒的请求数
  • 只要数据库不挂,一半的请求都可以被处理
  • 只要一半的请求可以被处理,意味着系统没有挂,可能只是影响了用户的体验。

缓存击穿

hot key 缓存突然失效,全部打中数据库

  • 解决方案
    1. 给hot key设置永不失效,并加上互斥锁

脑裂问题

就是在主从集群中,同时有两个主节点,它们都能够接收写请求。而脑裂最直接的影响就是客户端不知道应该往那个主节点写入数据,结果就是不同的客户端往不同的主节点写入数据,严重的话,脑裂会导致数据丢失。

在主从集群机制配置项中查找是否有限制主库接收请求的设置 redis 提供了2个配置项限制主库的请求处理

  • min-slaves-to-write:这个配置项设置了主库能进行数据同步的最少从库数量
  • min-slaves-max-lag:这个配置项设置了主从库间进行数据复制时,从库给主库发送ACK消息的最大延迟

常用配置:
假设从库有K个,可以将min-slaves-to-write设置为K/2+1(如果K为1,就设为1),将min-slaves-max-lag设置为十几秒(如10s~20s),这个配置下,如果有一半以上的从库和主库消息延迟超过十几秒,就禁止住哭接收客户端写请求

分布式锁

setNX+Lua脚本

Redis实现的分布式锁

  1. 可以通过香Redis发送[set key value NX 过期时间]命令实现原子的加锁操作;比如某个客户端想要设置一个key为 “SU”的锁,此时需要执行set SU random_value NX PX 30000(random_value最好设置为随机数,为了更加安全释放锁,释放锁需要检测key是否存在,并且key对应的value是否相同,相同才能释放)
  2. 如果setNX命令返回OK,说明拿到了锁,就可以做一些业务逻辑操作,完成之后释放锁调用redis的del命令删除
  3. 但是使用随机值无法保证原子性操作,多线程依旧有问题;比如A执行完业务逻辑,在获取锁的同时判断value是否一致,此时A的锁突然过了过期时间,导致B获得锁,还没执行的时候,A执行完后执行del方法删除B的锁,这时可以利用Lua脚本

Redission

  1. Redission所有指令通过lua脚本执行,Redission设置一个key的默认过期时间为30s,如果客户端持有一个锁超时,Redission有一个watchdog(看门狗),它会在获取锁之后,每隔10s把key的超时时间设为30s,这样就算一直持有锁也不会出现锁过期
  2. 当超过一般数量获取锁成功才算是真正的成功

zookeeper

每个客户端对方法加锁的时候,在zookeeper上的方法对于的指定节点目录下,生成唯一一个瞬时有序的节点。判断是否获取锁的方式,只需要判断是否有序节点最小的那个,当释放的时候,只需要删除这个瞬时节点即可

生产环境的部署

Redis cluster,10台机器,5台部署了Redis主实例,另外5台机器部署了Redis的从实例,每个主实例挂了一个从实例,5个节点对外提供读写服务,每个节点读写高峰QPS可以达到每秒5W,5台机器最多是每秒25W读写请求。任何一个主实例宕机,都会自动故障转移,从实例会自动变成主实例提供读写服务。分配给每一台Redis的内存是10g。

Redis给缓存数据设置过期时间的作用

因为内存是有限的,如果缓存数据一直保持,很容易OOM

Redis如何判断数据是否过期

Redis通过一个叫做过期字典(可以看作是hash表)保持数据过期时间。过期字典的键只想Redis数据库中的某个key,过期字典的值是一个long 类型的整数,保存了key所指向的数据库key的过期时间(毫秒的时间戳)




Enjoy Reading This Article?

Here are some more articles you might like to read next:

  • 2379. Minimum Recolors to Get K Consecutive Black Blocks
  • 2471. Minimum Number of Operations to Sort a Binary Tree by Level
  • 1387. Sort Integers by The Power Value
  • 2090. K Radius Subarray Averages
  • 2545. Sort the Students by Their Kth Score