[redis] redis

本文探讨了Redis的数据类型及其在不同场景的应用,包括字符串、哈希、列表、集合和有序集合,强调了其快速操作和丰富应用场景。此外,深入解析了Redis的持久化机制(RDB和AOF),主从复制、哨兵模式以及集群解决方案,以及如何应对缓存穿透、击穿和雪崩问题。
摘要由CSDN通过智能技术生成

0、引言

数据类型及使用场景

  • String
    这个其实没啥好说的,最常规的set/get操作。博客阅读数计数,对象缓存,分布式锁,共享session
  • hash
    这里value存放的是结构化的对象,比较方便的就是操作其中的某个字段。电商购物车(用户id 商品id 数量)
  • list
    使用List的数据结构,可以做简单的消息队列的功能。另外还有一个就是,可以利用lrange命令,做基于redis的分页功能,性能极佳,用户体验好。
  • set
    因为set堆放的是一堆不重复值的集合。所以可以做全局去重的功能。比如点赞,收藏啊,另外,就是利用交集、并集、差集等操作,可以找共同好友(微信朋友圈评论),可能认识的人。
  • sorted set
    sorted set多了一个权重参数score,集合中的元素能够按score进行排列。可以做排行榜应用,取TOP N操作

好处

  1. 速度快
    • redis是基于内存的,内存的读写速度非常快
    • redis采用hash结构,查找和操作的时间复杂度都是 O(1)
    • redis是单线程的,省去了很多上下文切换线程的时间,和添加各种锁的性能消耗;
    • Redis使用的是非阻塞IO,IO多路复用,使用了单线程来轮询描述符,将数据库的开、关、读、写都转换成了事件,减少了线程切换时上下文的切换和竞争。
  2. 支持丰富数据类型,有着丰富的应用场景
  3. 支持事务,操作都是原子性(因为是单线程)

1、持久化

redis提供了两种持久化的方式,分别是RDB(Redis DataBase)和AOF(Append Only File)。

RDB

简介:

  • RDB方式,是将redis某一时刻的数据持久化到磁盘中,是一种快照式的持久化方法。比如说,你可以在最近的 24 小时内,每小时备份一次 RDB 文件,并且在每个月的每一天,也备份一个 RDB 文件。

  • redis在进行数据持久化的过程中,会先将数据写入到一个临时文件中,待持久化过程都结束了,才会用这个临时文件替换上次持久化好的文件。正是这种特性,让我们可以随时来进行备份,因为快照文件总是完整可用的。

  • 对于RDB方式,redis会单独创建(fork)一个子进程来进行持久化,而主进程是不会进行任何IO操作的,这样就确保了redis极高的性能。

  • 如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。

缺点:

  • 因为 RDB 文件需要保存整个数据集的状态, 所以至少 5 分钟才能保存一次RDB 文件。在这种情况下,一旦发生故障停机,你就可能会丢失好几分钟的数据。所以在同时使用 AOF 和 RDB 时, Redis 重启会优先使用 AOF 文件来还原数据集

  • 每次保存 RDB的时候,Redis 都要 fork() 出一个子进程,并由子进程来进行实际的持久化工作。 在数据集比较庞大时, fork() 可能会非常耗时,造成服务器在某某毫秒内停止处理客户端。

AOF

简介:

  • AOF,英文是Append Only File,是将执行过的写指令记录下来,在数据恢复时按照从前到后的顺序再将指令都执行一遍,就这么简单。。

  • 我们通过配置redis.conf中的appendonly yes就可以打开AOF功能。如果有写操作(如SET等),redis就会被追加到AOF文件的末尾。

  • 你可以通过appendfsync配置来设置不同的 fsync策略:

    • always:每个Redis写命令都要同步写入硬盘.这样做会严重降低Redis的速度,还会降低固态硬盘的使用寿命(写入在SSD中的数据是不可以直接更新的,只能通过扇区覆盖重写,在覆盖重写之前需要先擦除,而且擦除操作又是不能在扇区上做的,只能在磁盘的块上来完成,擦除块之前需要将原有的还有效的数据先读出,然后在与新来的数据一起写入,而闪存的擦除次数是有限的,这些重复的操作会减少闪存的寿命)
    • everysec:每秒执行一次同步, AOF 的默认策略
    • no 让操作系统来决定应该何时进行同步
  • 如果在追加日志时,恰好遇到磁盘空间满、inode满或断电等情况导致日志写入不完整,也没有关系,redis提供了redis-check-aof工具,可以用来进行日志修复。

  • 因为采用了追加方式,如果不做任何处理的话,AOF文件会变得越来越大,为此,redis提供了AOF文件重写(rewrite)机制,即当AOF文件的大小超过所设定的阈值时,redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集。举个例子或许更形象,假如我们调用了100次INCR指令,在AOF文件中就要存储100条指令,但这明显是很低效的,完全可以把这100条指令合并成一条SET指令,这就是重写机制的原理。

  • 在进行AOF重写时,仍然是采用先写临时文件,全部完成后再替换的流程,所以断电、磁盘满等问题都不会影响AOF文件的可用性。

  • 如果不小心执行了FLUSHALL,导致redis内存中的数据全部被清空了,只要redis配置了AOF持久化方式,且AOF文件还没有被重写(rewrite),我们就可以用最快的速度暂停redis并编辑AOF文件,将最后一行的FLUSHALL命令删除,然后重启redis,就可以恢复redis的所有数据到FLUSHALL之前的状态了。

缺点:

  • 在同样数据规模的情况下,AOF文件要比RDB文件的体积大。而且,AOF方式的恢复速度也要慢于RDB方式。

  • 如果运气比较差,AOF文件出现了被写坏的情况,也不必过分担忧,redis并不会贸然加载这个有问题的AOF文件,而是报错退出。这时可以通过以下步骤来修复出错的文件:

    • 备份被写坏的AOF文件
    • 运行redis-check-aof –fix进行修复
    • 用diff -u来看下两个文件的差异,确认问题点
    • 重启redis,加载修复后的AOF文件

2、主从复制

一个主节点Master有多个从节点slave,将Master数据,复制到Slave上,数据是单向的,只能从主节点到从节点,Master以写为主,Slave以读为主

默认情况下,每台redis服务器都是主节点,一个Master可以有多少Slave或没有从节点,一个从节点只能有一个主节点

作用

  • 数据冗余/故障恢复
    实现了数据的热备份,是持久化之外的一种数据冗余方式,主节点出现问题,从节点可以提供服务
  • 读写分离,负载均衡
    主节点提供写服务,从节点提供读服务,分担服务器负载

复制原理

全量同步

Redis全量复制一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份。具体步骤如下:

  1. 从服务器连接主服务器,发送SYNC命令;
  2. 主服务器接收到SYNC命名后,开始执行bgsave命令生成dump.rdb文件并使用缓冲区记录此后执行的所有写命令;
  3. 主服务器bgsave执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;
  4. 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
  5. 主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
  6. 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;

增量同步

Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。
增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。

主从同步策略

主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。

3、集群方式

哨兵模式

主机崩溃,从机能读取,还是不能写,必须手动把一台从机切换为主机,费事,更多时候选择优先考虑是哨兵模式,哨兵能够监控后台的主机是否故障,根据投票自动将从机切换为主机
在这里插入图片描述

假设master宕机,哨兵1先检测到这个结果,系统并不会马上进行failover(故障切换、失效备援)这个现象称为主观下线,当后面的哨兵2 3也检测到主服务器不可用,哨兵之间会发起一次投票,投票的结果由随机一个哨兵发起,进行failover操作,得到票数多的slave能成功切换为master,切换成功后,通过发布订阅模式,让各个哨兵把自己监控的服务器实现切换主机,这个过程称为客观下线

集群方式

Redis集群中有16384个hash slots,为了计算给定的key应该在哪个hash slot上,我们简单地用这个key的CRC16值来对16384取模。(即:key的CRC16 % 16384)

Redis集群中的每个节点负责一部分hash slots

允许添加和删除集群节点。比如,如果你想增加一个新的节点D,那么久需要从A、B、C节点上删除一些hash slot给到D。同样地,如果你想从集群中删除节点A,那么会将A上面的hash slots移动到B和C,当节点A上是空的时候就可以将其从集群中完全删除。

因为将hash slots从一个节点移动到另一个节点并不需要停止其它的操作,添加、删除节点以及更改节点所维护的hash slots的百分比都不需要任何停机时间。也就是说,移动hash slots是并行的,移动hash slots不会影响其它操作。

4、缓存穿透、击穿、雪崩

缓存穿透

查询一个不存在的数据,由于缓存无法命中,将去查询数据库,但是数据库也无此记录。当这种查询很多的时候,会给数据库造成巨大压力

布隆过滤器

由k(k>1)个相互独立的哈希函数,和一个很长的bitmap组成,保证在给定的空间、误判率下,判断元素是否存在。每个哈希函数都能够将传入的数据生成一个哈希值,k个函数能够生成k个哈希值,在bitmap中将这k个哈希值的位置的比特位置为1。如果查找时发现其中的一个Hash值对应的比特位为0,那么该元素肯定不在集合中。只有在所有的Hash函数告诉我们该元素在集合中时,才能确定该元素存在于集合中。

它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。误识别是因为随着增加的值越来越多,被置为 1 的 bit 位也会越来越多,这样某个值即使没有被存储过,但是也有可能哈希函数返回的k个 bit 位都被其他值置位了 1 ,那么程序还是会判断这个值存在。
在这里插入图片描述

缓存空对象

当持久化层不命中后,将返回的空对象存储起来,同时设置一个过期时间,之后再访问这个数据就从缓存中获取,保护持久层数据源

需要面临的问题

  • 存储空的key也需要空间
  • 对空值设置了过期时间,如果这个时间内数据库里有值了,对于需要保持一致性的业务会有影响

缓存击穿

  • 一个key非常热点,不停扛着大并发,集中对一个点进行访问,当个key失效的瞬间,持续大并发导致穿破缓存,直接请求数据库

  • 某个key在过期的瞬间,大量的访问会同时访问数据库来查询最新的数据,并且回写缓存,导致数据库瞬间压力过大

解决方案

  • 设置热点数据不过期
    • 一直缓存也会浪费空间
  • 加互斥锁
    • 分布式锁:使用分布式锁,保证对于每个key同时只有一个线程查询后端服务,其他线程没有获得分布式锁的权限,只需要等待即可,这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大

热点数据

就是信息修改频率不高,读取通常非常高的数据

数据更新前至少读取两次,缓存才有意义。这个是最基本的策略,如果缓存还没有起作用就失效了,那就没有太大价值了

那存不存在,修改频率很高,但是又不得不考虑缓存的场景呢?有!比如我们的博客点赞数,收藏数,分享数等是非常典型的热点数据,但是又不断变化,此时就需要将数据同步保存到Redis缓存,减少数据库压力

缓存雪崩

在某一个时间段,缓存集中过期失效,redis宕机

产生雪崩的原因之一,设置缓存的存活时间较短,大并发访问时刚好都过期,直接访问了数据库,对数据库而言,会产生周期性压力波峰,暴增时数据库可能会宕机

例如双十一时会停掉一些服务,保证主要的一些服务可用

解决方案:

  • 增加集群中服务器数量
    • 异地多活
  • 限流降级
    • 缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量,对某个key只允许一个线程查询数据和写缓存,其他线程等待
  • 数据预热
    • 正式部署之前,把可能的数据提前访问一遍,可能大量访问的数据就会加载到缓存中,加载不同的key,设置不同的过期时间,让缓存时间尽量均匀

5、事务

Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的
Redis会将一个事务中的所有命令序列化,然后按顺序执行。

  1. redis 不支持回滚“Redis 在事务失败时不进行回滚,而是继续执行余下的命令”, 所以 Redis 的内部可以保持简单且快速。
  2. 如果在一个事务中的命令出现错误,那么所有的命令都不会执行;
  3. 如果在一个事务中出现运行错误,那么正确的命令会被执行。
    • MULTI命令用于开启一个事务,它总是返回OK。 MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。
    • EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。当操作被打断时,返回空值 nil 。
    • 通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出。
    • WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令

6、缓存过期和缓存淘汰

缓存过期

常见的策略有两种:

  • 定期删除:定时去清理过期的缓存;
  • 惰性删除:当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。

两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!

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

定期删除,redis默认每个100ms检查,是否有过期的key,有过期key则删除。需要说明的是,redis不是每个100ms将所有的key检查一次,而是随机抽取进行检查(如果每隔100ms,全部key进行检查,redis岂不是卡死)。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。于是,惰性删除派上用场。也就是说在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除。

采用定期删除+惰性删除就没其他问题了么?
不是的,如果定期删除没删除key。然后你也没即时去请求key,也就是说惰性删除也没生效。这样,redis的内存会越来越高。那么就应该采用内存淘汰机制。

缓存淘汰

Redis的内存淘汰策略是指在Redis的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据。redis总共有如下几种内存淘汰策略:

  • noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。
  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。
  • allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。(不推荐使用)
  • volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。
  • volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。
  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。

redis 4.x 后支持LFU策略

  • allkeys-lfu

  • volatile-lfu

LRU(Least recently used)
LRU算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”(首先淘汰最长时间未被使用的数据)。

LFU(Least Frequently Used)
它是基于“如果一个数据在最近一段时间内使用次数很少,那么在将来一段时间内被使用的可能性也很小”的思路。(淘汰一定时期内被访问次数最少的数据)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值