问题四:缓存Redis

1、Redis和Memcache对比

memcache:
(1)是异步多线程io,可以发挥cpu的多核优势
(2)只支持key,value的结构
(3)不支持持久化和主从同步
(4)容量满了会对过期的数据进行清除,除了过期策略以外还有淘汰机制
(5)key不能超过250字节,value不能超过1MB字节,key的最大时效时间是30天

redis:
(1)单线程模式,原因是采用非阻塞异步实现处理机制,缓存数据都是内存操作IO,时间不会太长,单线程会避免线程上下文切换产生的代价。
(2)redis不仅可以用来做缓存,也可以用来做nosql数据库。
(3)数据结构格式比较多。
(4)提供主从同步的部署机制

2、redis的数据结构及场景

string,hash,list,set,zset
string:sds(simple dynamic string)类似arrayList,通过预分配内存空间,减少内存的重复分配
list:zipList(一段连续的内存上,适合插入操作)和linkList,在redis3.2加入了quickList,是一个双向无环的链表,每一个节点都是zipList
hash:zipList和hashtable两种实现
set:intset和hashtable
zset:zipList和skipList

3、redis的持久化方式及区别

(1)RDB(Redis DataBase)
在指定的时间间隔内会将内存中的数据集快照写入磁盘中,也就是snapshot快照,默认保存到dump.rdb文件中,他恢复时将快照文件直接读到内存中。

Redis会fork一个子进程来进行持久化,也就是复制一个与当前一模一样的进程。先将数据写入到一个临时文件中,待持久化过程结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,fork的进程不进行任何的IO操作,这就确保了极高的性能。如果需要进行大规模数据的恢复,而且对于数据恢复的完成性不是非常敏感,那RDB方式要比AOF方式更加高效。RDB备份可能是五分钟恢复一次,这样的话如果在最后五分钟宕机,那么就有可能会丢失最后一次的数据。
RDB保存的是dump.rdb文件

让rdb保存即可生效的命令,save,就不用等15分钟1次,5分钟10,1分钟一万次了。但是save时只管保存,其他不管,全部阻塞。bgsave:redis会在后台异步进行快照操作,快照同时还可以响应客户端的请求,可以通过lastsave获取最后一次保存快照的时间。

image.png

二、AOF(Append Only File)
以日志的形式来记录每个写操作,将Redis执行过的所有的指令记录下来(读操作不记录),只允许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据得恢复工作。

aof和rdb的持久化策略可以同时存在,appendonly.aof文件比dump.rdb文件先生效,如果aof文件损坏(比如说正在编码时突然断电可能会导致)会导致redis启动失败,这时可以用

redis-check-aof --fix appendonly.aof 进行修复

aof采用文件追加的方式,文件会变得越来越大(比如说对某一个key连续减一加一100次)为了避免出现这种情况新增了重写机制,当aof文件的大小超过设定的阈值时,Redis就会启动对文件的压缩,只保留可以恢复数据的最小指令值。
aof会fork出一条新进程来将文件重写(也是先写临时文件最后rename)遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,和快照是有点像的。Redis会记录上次重写的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M是触发。也可以使用命令bgwriteaof 进行手动重写。参考出自redis.conf文件以下:

rewrite的配置

AOF的优势和劣势:
优势:redis可以实现每秒同步,甚至每修改同步,数据丢失的可能性低。劣势:相同数据集的数据而言aof文件远大于rdb文件,恢复速度慢于rdb,aof运行效率要慢于rdb,每秒同步策略效率较好,不同步效率和rdb相同。


image.png

(3)RDB和AOF的选择
如果对持久话要求恢复快,占用的内存空间小,而对数据的敏感度要求没那么高,那么选择RDB,如果对数据敏感度要求极高那么选择AOF。只做缓存的时候:如果只希望数据在服务器运行时存在,可以不使用任何的持久化方式。

同时开启两种持久化方式时,在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据。因为在通常情况下AOF文件保存的数据要比RDB文件保存的数据集要完整。因为RDB文件只用作后备用途,(主从策略)建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则。

如果Enalbe AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了。代价一是带来了持续的IO,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上。默认超过原大小100%大小时重写可以改到适当的数值。 如果不Enable AOF ,仅靠Master-Slave Replication 实现高可用性也可以。能省掉一大笔IO也减少了rewrite时带来的系统波动。代价是如果Master/Slave同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载入较新的那个。新浪微博就选用了这种架构。

4、redis的过期策略和淘汰机制

在Redis中过期的key不会立刻从内存中删除,而是会同时以下面两种策略进行删除:
(1)定期删除:每隔一段时间,随机检查设置了过期的key并删除已过期的key;维护定时器消耗CPU资源;
(2)惰性删除:当key被访问时检查该key的过期时间,若已过期则删除;已过期未被访问的数据仍保持在内存中,消耗内存资源;

定期删除:
Redis每 100ms 进行一次过期扫描:
1.随机取20个设置了过期策略的key;
2.检查20个key中过期时间中已过期的key并删除;
3.如果有超过25%的key已过期则重复第一步;
这种循环随机操作会持续到过期key可能仅占全部key的25%以下时,并且为了保证不会出现循环过多的情况,默认扫描时间不会超过25ms;

惰性删除:
定期删除可能会导致很多过期 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的内存淘汰策略的选取并不会影响过期的key的处理。内存淘汰策略用于处理内存不足时的需要申请额外空间的数据;过期策略用于处理过期的缓存数据。

淘汰机制的三种算法:FIFO、LRU、LFU

image.png

redis的主从机制

redis的主从机制

5、如何实现延时队列、分布式锁:

订单处于未支付状态可以使用延时队列,检查订单退款状态跟订单是否已退款成功等业务场景。可以采用mq来开启延时队列,也可以使用redis的zset,list来实现延时队列。使用redis的分布式锁可以使用以下:

(1)加锁:执行setnx,若成功再执行expire添加过期时间
(2)解锁:执行delete命令 实现简单,相比数据库和分布式系统的实现,该方案最轻,性能最好
(3)缺点:

  • 1.setnx和expire分2步执行,非原子操作;若setnx执行成功,但expire执行失败,就可能出现死锁
  • 2.delete命令存在误删除非当前线程持有的锁的可能
  • 3.不支持阻塞等待、不可重入

分布式锁学习
https://blog.csdn.net/w372426096/article/details/103761286?spm=1001.2101.3001.6650.3&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-3.no_search_link&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-3.no_search_link

image.png

6、缓存击穿、缓存穿透、缓存雪崩:

缓存击穿:指缓存中没有但数据库中有的数据(一般是热点数据缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去查,引起数据库压力瞬间增大,线上系统卡住。

解决方案:
  • (1)可以用redis锁的方式来解决,用setnx互斥锁进行判断,让其他的线程处于等待状态,保证不会有大量的并发去操作数据库;
  • (2)根据实际业务情况,在Redis中维护一个热点数据表,批量设为永不过期(如top1000),并定时更新top1000数据。

缓存穿透:指缓存和数据库中都没有的数据,导致所有的请求都打到数据库上,然后数据库还查不到(如null),造成数据库短时间线程数被打满而导致其他服务阻塞,最终导致线上服务不可用,这种情况一般来自黑客。

解决方案:
  • (1)针对查询结果为空的情况也进行缓存,缓存时间设置的短一点,然后在该key对应的数据插入之后清理缓存。但是这时会导致缓存太多空值占用了空间
  • (2)采用布隆过滤器,在缓存之前先查询过滤器key是不是存在,如果不存在就直接返回,如果存在再去查询缓存和数据库。

缓存雪崩:指缓存同一时间大面积的失效,缓存击穿升级版,突然之间大量的请求访问数据库,数据库崩溃。

解决方案:
  • (1)key的失效期设置的分散开,不同的key设置不同失效期
  • (2)设置一个二级缓存,但是数据可能达不到强一致,主要还是为了防止数据库崩溃
  • (3)允许数据的脏读,也是要分项目,对数据要求的实时性不是很高,可以这样操作。

6、大key和热key

大key指的是存储的值特别的大,会占用很多的内存,在主动删除和过期删除的时候操作时间过长,可能会导致redis的阻塞,可以通过将redis的csv文件导入到数据库中进行分析。通过string减少字符串长度,list、set减少成员数量,可以将对象拆分开,拆分成多个对象。删除大key时不要采用delete命令,因为是一个阻塞的命令,使用lazy delete,如果key不存在就会被跳过,因为是非阻塞的。

热key指的是有大量的请求,例如说几十万的访问某一个key,导致redis崩溃。热key可以进行预估,比如说秒杀的商品,火爆的新闻,利用大数据技术进行统计。
(1)可以将热key加入到本地的缓存当中,这样访问的时候就可以直接访问本地的缓存。
(2)redis的节点上都备份热点数据,将redis的访问压力负载到每一台服务器上去。
(3)利用限流熔断操作,例如使用sentinel进行热点限流,一般可以设置每秒的访问次数不超过400次,超过就可以熔断。

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

不一致的根源是数据源不一样,强一致性是比较难的,追求一个最终一致性,互联网行业高吞吐量,低延时,数据敏感性低于金融业这些行业。时序控制也就是先更新数据库再更新缓存,或者先更新缓存再更新数据库,但是都不是一个原子操作,高并发情况下都会产生不一致。可以采用延时双删策略。

  • (1)更新数据库的同时删除缓存项,读取的时候再填充缓存项
  • (2)2s后再删一次缓存项
  • (3)设置缓存过期时间,尽量短一些
  • (4)将缓存删除失败的记录下来,再次进行删除,可以发给mq,通过ack机制来确认删除了缓存。

7、数据并发竞争

指的是多个客户端同时并发写一个key

  • (1)可以采用分布式锁+时间戳
  • (2)采用消息队列,把对redis队列的操作进行串行化,解决数据并发竞争导致数据错乱的问题。

8、单线程的redis为什么这么快

image.png

redis是在内存中进行操作的,内存和硬盘不会进行频繁的交换,最大的内存设置加上淘汰策略,单线程没有锁,没有多线程的调度和切换,使用了IO的多路复用器模型,进行持久化的时候开启子进程进行执行。

你可能感兴趣的:(问题四:缓存Redis)