尚硅谷Java技术之深圳高频面试题
版本:V1.0
尚硅谷Java技术中心
Redis一般来说在项目中有几方面的应用
1. 作为缓存,将热点数据进行缓存,减少和数据库的交互,提高系统的效率
2. 作为分布式锁的解决方案,解决缓存击穿等问题
3. 作为消息队列,使用Redis的发布订阅功能进行消息的发布和订阅
具体的使用场景要结合项目去说,比如说项目中有哪些场景用到Redis来作为缓存,以及分布式锁等等。。
Redis常用的5种数据类型,string、hash、list、set、zset(sorted set);
String类型是最简单的类型,一个key对应一个value,项目中我们主要利用单点登录中的用户信息用string类型来存储;
Hash类型中的key是string类型,value又相当于一个map(key-value),针对这种数据特性,比较适合存储对象,在我们项目中由于购物车是用redis来存储的,因为选择redis的散列(hash)来存储;{key:{filed:value}{filed:value}{filed:value}}
List类型是按照插入顺序的字符串链表(双向链表),主要命令是LPUSH和RPUSH,能够支持反向查找和遍历,如果使用的话主要存储商品评论列表,key是该商品的ID,value是商品评论信息列表,秒杀的使用;
Set类型是用哈希表类型的字符串序列,没有顺序,集合成员是唯一的,没有重复数据。比如微博应用中,可以将一个用户所有关注的对象放在一个集合中,将其所有粉丝存在一个集合,这样我们就可以实现两个人的共同好友、共同关注等需求;
zset(sorted
set)类型和set类型基本是一致的,不同的是zset这种类型会给每个元素关联一个**double**类型的分数(score),这样就可以为成员排序,并且插入是有序的。这种数据类型如果使用的话主要用来统计商品的销售排行榜,比如:items:sellsort
10 1001 20 1002
这个代表编号是1001的商品销售数量为10,编号为1002的商品销售数量为20。
Redis是内存型数据库,同时它也可以持久化到硬盘中,redis的持久化方式有两种:
(1)RDB(半持久化方式):
按照配置不定期的通过异步的方式、快照的形式直接把内存中的数据持久化到磁盘的一个dump.rdb文件(二进制文件)中;
这种方式是redis默认的持久化方式,它在配置文件(redis.conf)中的格式是:save
N M,表示的是在N秒之内发生M次修改,则redis抓快照到磁盘中;
优点:只包含一个文件,对于文件备份、灾难恢复而言,比较实用。因为我们可以轻松的将一个单独的文件转移到其他存储媒介上;性能最大化,因为对于这种半持久化方式,使用的是写时拷贝技术,可以极大的避免服务进程执行IO操作;相对于AOF来说,如果数据集很大,RDB的启动效率就会很高
缺点:如果想保证数据的高可用(最大限度的包装数据丢失),那么RDB这种半持久化方式不是一个很好的选择,因为系统一旦在持久化策略之前出现宕机现象,此前没有来得及持久化的数据将会产生丢失;rdb是通过fork进程来协助完成持久化的,因此当数据集较大的时候,我们就需要等待服务器停止几百毫秒甚至一秒;
(2)AOF(全持久化的方式) 写后日志
把每一次数据变化都通过write()函数将你所执行的命令追加到一个appendonly.aof文件里面;
Redis默认是不支持这种全持久化方式的,需要进行手动开启
实现文件刷新的三种方式:
no:不会自动同步到磁盘上,需要依靠OS(操作系统)进行刷新,效率快,但是安全性就比较差;
always:每提交一个命令都调用同步刷新到aof文件,非常慢,但是安全;
everysec:每秒钟都调用fsync刷新到aof文件中,很快,但是可能丢失一秒内的数据,推荐使用,兼顾了速度和安全;
优点:
数据安全性高
该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机问题,也不会破坏日志文件中已经存在的内容;
缺点:
对于数量相同的数据集来说,aof文件通常要比rdb文件大,因此rdb在恢复大数据集时的速度大于AOF;
根据同步策略的不同,AOF在运行效率上往往慢于RDB,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效;
针对以上两种不同的持久化方式,如果缓存数据安全性要求比较高的话,用aof这种持久化方式(比如项目中的购物车);如果对于大数据集要求效率高的话,就可以使用默认的。而且这两种持久化方式可以同时使用。
1.完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
2. 数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;
3.采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗
CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
4. 使用多路I/O复用模型,非阻塞IO;
5.使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM
机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
一、 延时双删策略
在写库前后都进行redis.del(key)操作,并且设定合理的超时时间。具体步骤是:
1)先删除缓存
2)再写数据库
3)休眠500毫秒(根据具体的业务时间来定)
4)再次删除缓存。
那么,这个500毫秒怎么确定的,具体该休眠多久呢?
需要评估自己的项目的读数据业务逻辑的耗时。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。
当然,这种策略还要考虑 redis
和数据库主从同步的耗时。最后的写数据的休眠时间:则在读数据业务逻辑的耗时的基础上,加上几百ms即可。比如:休眠1秒。
二、设置缓存的过期时间
从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。所有的写操作以数据库为准,只要到达缓存过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存
结合双删策略+缓存超时设置,这样最差的情况就是在超时时间内数据存在不一致,而且又增加了写请求的耗时。
三、如何写完数据库后,再次删除缓存成功?
上述的方案有一个缺点,那就是操作完数据库后,由于种种原因删除缓存失败,这时,可能就会出现数据不一致的情况。这里,我们需要提供一个保障重试的方案。
1、方案一具体流程
(1)更新数据库数据;
(2)缓存因为种种问题删除失败;
(3)将需要删除的key发送至消息队列;
(4)自己消费消息,获得需要删除的key;
(5)继续重试删除操作,直到成功。
然而,该方案有一个缺点,对业务线代码造成大量的侵入。于是有了方案二,在方案二中,启动一个订阅程序去订阅数据库的binlog,获得需要操作的数据。在应用程序中,另起一段程序,获得这个订阅程序传来的信息,进行删除缓存操作。
2、方案二具体流程
(1)更新数据库数据;
(2)数据库会将操作信息写入binlog日志当中;
(3)订阅程序提取出所需要的数据以及key;
(4)另起一段非业务代码,获得该信息;
(5)尝试删除缓存操作,发现删除失败;
(6)将这些信息发送至消息队列;
(7)重新从消息队列中获得该数据,重试操作。
1. 缓存穿透:
是指查询一个不存在的数据,由于缓存无法命中,将去查询数据库,但是数据库也无此记录,并且出于容错考虑,我们没有将这次查询的null写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。
解决方案:空结果也进行缓存,可以设置一个空对象,但它的过期时间会很短,最长不超过五分钟。
或者用Bloom过滤器也可以解决
2. 缓存雪崩:
是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。
解决方案:原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
3. 缓存击穿
是指对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常"热点"的数据。这个时候,需要考虑一个问题:如果这个key在大量请求同时进来之前正好失效,那么所有对这个key的数据查询都落到db,我们称为缓存击穿。
解决方案:在分布式的环境下,应使用分布式锁来解决,分布式锁的实现方案有多种,比如使用Redis的setnx、使用Zookeeper的临时顺序节点等来实现
相关文章:
第一章 Java基础
第二章 MySQL相关
第三章 框架相关
第四章 Redis相关
第五章 JVM相关
第六章 消息队列相关
第七章 项目相关
真诚点赞 诚不我欺~
{{ praiseUserVoList.length }}人点赞
内容
"{{ child.parent.content }}"