本文的面试题以下:程序员
Redis是一个支持持久化的内存数据库,经过持久化机制把内存中的数据同步到硬盘文件来保证数据持久化。当Redis重启后经过把硬盘文件从新加载到内存,就能达到恢复数据的目的。面试
实现:单首创建fork()一个子进程,将当前父进程的数据库数据复制到子进程的内存中,而后由子进程写入到临时文件中,持久化的过程结束了,再用这个临时文件替换上次的快照文件,而后子进程退出,内存释放。redis
RDB是Redis默认的持久化方式。按照必定的时间周期策略把内存的数据以快照的形式保存到硬盘的二进制文件。即Snapshot快照存储,对应产生的数据文件为dump.rdb,经过配置文件中的save参数来定义快照的周期。( 快照能够是其所表示的数据的一个副本,也能够是数据的一个复制品。) AOF:Redis会将每个收到的写命令都经过Write函数追加到文件最后,相似于MySQL的binlog。当Redis重启是会经过从新执行文件中保存的写命令来在内存中重建整个数据库的内容。算法
当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复。spring
缓存雪崩咱们能够简单的理解为:因为原有缓存失效,新缓存未到期间数据库
(例如:咱们设置缓存时采用了相同的过时时间,在同一时刻出现大面积的缓存过时),全部本来应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存形成巨大压力,严重的会形成数据库宕机。从而造成一系列连锁反应,形成整个系统崩溃。设计模式
大多数系统设计者考虑用加锁( 最多的解决方案)或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。还有一个简单方案就时讲缓存失效时间分散开。缓存
缓存穿透是指用户查询数据,在数据库没有,天然在缓存中也不会有。这样就致使用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,而后返回空(至关于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是常常提的缓存命中率问题。安全
最多见的则是采用布隆过滤器,将全部可能存在的数据哈希到一个足够大的bitmap中,一个必定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。服务器
另外也有一个更为简单粗暴的方法,若是一个查询返回的数据为空(无论是数据不存在,仍是系统故障),咱们仍然把这个空结果进行缓存,但它的过时时间会很短,最长不超过五分钟。经过这个直接设置的默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴。
5TB的硬盘上放满了数据,请写一个算法将这些数据进行排重。若是这些数据是一些32bit大小的数据该如何解决?若是是64bit的呢?
对于空间的利用到达了一种极致,那就是Bitmap和布隆过滤器(Bloom Filter)。
Bitmap:典型的就是哈希表
缺点是,Bitmap对于每一个元素只能记录1bit信息,若是还想完成额外的功能,恐怕只能靠牺牲更多的空间、时间来完成了。
布隆过滤器(推荐)
就是引入了k(k>1)k(k>1)个相互独立的哈希函数,保证在给定的空间、误判率下,完成元素判重的过程。
它的优势是空间效率和查询时间都远远超过通常的算法,缺点是有必定的误识别率和删除困难。
Bloom-Filter算法的核心思想就是利用多个不一样的Hash函数来解决“冲突”。
Hash存在一个冲突(碰撞)的问题,用同一个Hash获得的两个URL的值有可能相同。为了减小冲突,咱们能够多引入几个Hash,若是经过其中的一个Hash值咱们得出某元素不在集合中,那么该元素确定不在集合中。只有在全部的Hash函数告诉咱们该元素在集合中时,才能肯定该元素存在于集合中。这即是Bloom-Filter的基本思想。
Bloom-Filter通常用于在大数据量的集合中断定某元素是否存在。
缓存击穿:是指一个key很是热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据。
解决方案:在访问key以前,采用SETNX(set if not exists)来设置另外一个短时间key来锁住当前key的访问,访问结束再删除该短时间key。
给一个我公司处理的案例:背景双机拿token,token在存一份到redis,保证系统在token过时时都只有一个线程去获取token;线上环境有两台机器,故使用分布式锁实现。
缓存预热这个应该是一个比较常见的概念,相信不少小伙伴都应该能够很容易的理解,缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就能够避免在用户请求的时候,先查询数据库,而后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
解决思路:
除了缓存服务器自带的缓存失效策略以外(Redis默认的有6中策略可供选择),咱们还能够根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:
二者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!具体用哪一种方案,你们能够根据本身的应用场景来权衡。
当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然须要保证服务仍是可用的,即便是有损服务。系统能够根据一些关键数据进行自动降级,也能够配置开关实现人工降级。
降级的最终目的是保证核心服务可用,即便是有损的。并且有些服务是没法降级的(如加入购物车、结算)。
以参考日志级别设置预案:
服务降级的目的,是为了防止Redis服务故障,致使数据库跟着一块儿发生雪崩问题。所以,对于不重要的缓存数据,能够采起服务降级策略,例如一个比较常见的作法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。
热点数据,缓存才有价值
对于冷数据而言,大部分数据可能尚未再次访问到就已经被挤出内存,不只占用内存,并且价值不大。频繁修改的数据,看状况考虑使用缓存
对于上面两个例子,寿星列表、导航信息都存在一个特色,就是信息修改频率不高,读取一般很是高的场景。
对于热点数据,好比咱们的某IM产品,生日祝福模块,当天的寿星列表,缓存之后可能读取数十万次。再举个例子,某导航产品,咱们将导航信息,缓存之后可能读取数百万次。
数据更新前至少读取两次, 缓存才有意义。这个是最基本的策略,若是缓存尚未起做用就失效了,那就没有太大价值了。
那存不存在,修改频率很高,可是又不得不考虑缓存的场景呢?有!好比,这个读取接口对数据库的压力很大,可是又是热点数据,这个时候就须要考虑经过缓存手段,减小数据库的压力,好比咱们的某助手产品的,点赞数,收藏数,分享数等是很是典型的热点数据,可是又不断变化,此时就须要将数据同步保存到Redis缓存,减小数据库压力。
1)、存储方式 Memecache把数据所有存在内存之中,断电后会挂掉,数据不能超过内存大小。Redis有部份存在硬盘上,redis能够持久化其数据
2)、数据支持类型 memcached全部的值均是简单的字符串,redis做为其替代者,支持更为丰富的数据类型 ,提供list,set,zset,hash等数据结构的存储
3)、使用底层模型不一样 它们之间底层实现方式 以及与客户端之间通讯的应用协议不同。Redis直接本身构建了VM 机制 ,由于通常的系统调用系统函数的话,会浪费必定的时间去移动和请求。
4). value 值大小不一样:Redis 最大能够达到 512M;memcache 只有 1mb。
5)redis的速度比memcached快不少
6)Redis支持数据的备份,即master-slave模式的数据备份。
(一)纯内存操做
(二)单线程操做,避免了频繁的上下文切换
(三)采用了非阻塞I/O多路复用机制
回答:一共五种
这个其实没啥好说的,最常规的set/get操做,value能够是String也能够是数字。通常作一些复杂的计数功能的缓存。
这里value存放的是结构化的对象,比较方便的就是操做其中的某个字段。博主在作单点登陆的时候,就是用这种数据结构存储用户信息,以cookieId做为key,设置30分钟为缓存过时时间,能很好的模拟出相似session的效果。
使用List的数据结构,能够作简单的消息队列的功能。另外还有一个就是,能够利用lrange命令,作基于redis的分页功能,性能极佳,用户体验好。本人还用一个场景,很合适—取行情信息。就也是个生产者和消费者的场景。LIST能够很好的完成排队,先进先出的原则。
由于set堆放的是一堆不重复值的集合。因此能够作全局去重的功能。为何不用JVM自带的Set进行去重?由于咱们的系统通常都是集群部署,使用JVM自带的Set,比较麻烦,难道为了一个作一个全局去重,再起一个公共服务,太麻烦了。
另外,就是利用交集、并集、差集等操做,能够计算共同喜爱,所有的喜爱,本身独有的喜爱等功能。
sorted set多了一个权重参数score,集合中的元素可以按score进行排列。能够作排行榜应用,取TOP N操做。
redis采用的是按期删除+惰性删除策略。
为何不用定时删除策略?
定时删除,用一个定时器来负责监视key,过时则自动删除。虽然内存及时释放,可是十分消耗CPU资源。在大并发请求下,CPU要将时间应用在处理请求,而不是删除key,所以没有采用这一策略.
按期删除+惰性删除是如何工做的呢?
按期删除,redis默认每一个100ms检查,是否有过时的key,有过时key则删除。须要说明的是,redis不是每一个100ms将全部的key检查一次,而是随机抽取进行检查(若是每隔100ms,所有key进行检查,redis岂不是卡死)。所以,若是只采用按期删除策略,会致使不少key到时间没有删除。
因而,惰性删除派上用场。也就是说在你获取某个key的时候,redis会检查一下,这个key若是设置了过时时间那么是否过时了?若是过时了此时就会删除。
采用按期删除+惰性删除就没其余问题了么?
不是的,若是按期删除没删除key。而后你也没即时去请求key,也就是说惰性删除也没生效。这样,redis的内存会愈来愈高。那么就应该采用内存淘汰机制。
在redis.conf中有一行配置
maxmemory-policy volatile-lru1
该配置就是配内存淘汰策略的(什么,你没配过?好好检讨一下本身)
ps:若是没有设置 expire 的key, 不知足先决条件(prerequisites); 那么 volatile-lru, volatile-random 和 volatile-ttl 策略的行为, 和 noeviction(不删除) 基本上一致。
官方FAQ表示,由于Redis是基于内存的操做,CPU不是Redis的瓶颈,Redis的瓶颈最有多是机器内存的大小或者网络带宽。
既然单线程容易实现,并且CPU不会成为瓶颈,那就瓜熟蒂落地采用单线程的方案了(毕竟采用多线程会有不少麻烦!)Redis利用队列技术将并发访问变为串行访问
1)绝大部分请求是纯粹的内存操做(很是快速)
2)采用单线程,避免了没必要要的上下文切换和竞争条件
3)非阻塞IO优势:
同时有多个子系统去set一个key。这个时候要注意什么呢?
不推荐使用redis的事务机制。由于咱们的生产环境,基本都是redis集群环境,作了数据分片操做。你一个事务中有涉及到多个key操做的时候,这多个key不必定都存储在同一个redis-server上。所以,redis的事务机制,十分鸡肋。
对redis的操做都是具备原子性的,是线程安全的操做,你不用考虑并发问题,redis内部已经帮你处理好并发的问题了。
1.twemproxy,大概概念是,它相似于一个代理方式, 使用时在本须要链接 redis 的地方改成链接 twemproxy, 它会以一个代理的身份接收请求并使用一致性 hash 算法,将请求转接到具体 redis,将结果再返回 twemproxy。
缺点:twemproxy 自身单端口实例的压力,使用一致性 hash 后,对 redis 节点数量改变时候的计算值的改变,数据没法自动移动到新的节点。
2.codis,目前用的最多的集群方案,基本和 twemproxy 一致的效果,但它支持在 节点数量改变状况下,旧节点数据可恢复到新 hash 节点
3.redis cluster3.0 自带的集群,特色在于他的分布式算法不是一致性 hash,而是 hash 槽的概念,以及自身支持节点设置从节点。具体看官方文档介绍。
主从复制,读写分离
一类是主数据库(master)一类是从数据库(slave),主数据库能够进行读写操做,当发生写操做的时候自动将数据同步到从数据库,而从数据库通常是只读的,并接收主数据库同步过来的数据,一个主数据库能够有多个从数据库,而一个从数据库只能有一个主数据库。
redis是一个单线程程序,也就说同一时刻它只能处理一个客户端请求;
redis是经过IO多路复用(select,epoll, kqueue,依据不一样的平台,采起不一样的实现)来处理多个客户端请求的
(1) Master 最好不要作任何持久化工做,如 RDB 内存快照和 AOF 日志文件
(2) 若是数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一次
(3) 为了主从复制的速度和链接的稳定性, Master 和 Slave 最好在同一个局域网内
(4) 尽可能避免在压力很大的主库上增长从库
(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…
文件事件处理器包括分别是套接字、 I/O 多路复用程序、 文件事件分派器(dispatcher)、 以及事件处理器。使用 I/O 多路复用程序来同时监听多个套接字, 并根据套接字目前执行的任务来为套接字关联不一样的事件处理器。
当被监听的套接字准备好执行链接应答(accept)、读取(read)、写入(write)、关闭(close)等操做时, 与操做相对应的文件事件就会产生, 这时文件事件处理器就会调用套接字以前关联好的事件处理器来处理这些事件。
I/O 多路复用程序负责监听多个套接字, 并向文件事件分派器传送那些产生了事件的套接字。
工做原理:
I/O 多路复用程序负责监听多个套接字, 并向文件事件分派器传送那些产生了事件的套接字。
尽管多个文件事件可能会并发地出现, 但 I/O 多路复用程序老是会将全部产生事件的套接字都入队到一个队列里面, 而后经过这个队列, 以有序(sequentially)、同步(synchronously)、每次一个套接字的方式向文件事件分派器传送套接字:
当上一个套接字产生的事件被处理完毕以后(该套接字为事件所关联的事件处理器执行完毕), I/O 多路复用程序才会继续向文件事件分派器传送下一个套接字。若是一个套接字又可读又可写的话, 那么服务器将先读套接字, 后写套接字.
对于Redis而言,命令的原子性指的是:一个操做的不能够再分,操做要么执行,要么不执行。
Redis的操做之因此是原子性的,是由于Redis是单线程的。(Redis新版本已经引入多线程,这里基于旧版本的Redis)
Redis自己提供的全部API都是原子操做,Redis中的事务实际上是要保证批量操做的原子性。
多个命令在并发中也是原子性的吗?
不必定, 将get和set改为单命令操做,incr 。使用Redis的事务,或者使用Redis+Lua==的方式实现.
Redis事务功能是经过MULTI、EXEC、DISCARD和WATCH 四个原语实现的
Redis会将一个事务中的全部命令序列化,而后按顺序执行。
注:redis的discard只是结束本次事务,正确命令形成的影响仍然存在.
1)MULTI命令用于开启一个事务,它老是返回OK。MULTI执行以后,客户端能够继续向服务器发送任意多条命令,这些命令不会当即被执行,而是被放到一个队列中,当EXEC命令被调用时,全部队列中的命令才会被执行。
2)EXEC:执行全部事务块内的命令。返回事务块内全部命令的返回值,按命令执行的前后顺序排列。当操做被打断时,返回空值 nil 。
3)经过调用DISCARD,客户端能够清空事务队列,并放弃执行事务, 而且客户端会从事务状态中退出。
4)WATCH 命令能够为 Redis 事务提供 check-and-set (CAS)行为。能够监控一个或多个键,一旦其中有一个键被修改(或删除),以后的事务就不会执行,监控一直持续到EXEC命令。
Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的链接并不存在竞争关系Redis中可使用SETNX命令实现分布式锁。
将 key 的值设为 value ,当且仅当 key 不存在。若给定的 key 已经存在,则 SETNX 不作任何动做
解锁:使用 del key 命令就能释放锁
解决死锁:
欢迎关注公众号:程序员白楠楠 ,领取pdf文档的Java核心知识点总结!
这些资料的内容都是面试时面试官必问的知识点,篇章包括了不少知识点,其中包括了有基础知识、Java集合、JVM、多线程并发、spring原理、微服务、Netty 与RPC 、Kafka、日记、设计模式、Java算法、数据库、Zookeeper、分布式缓存、数据结构等等。
你们一块儿交流,喜欢文章记得关注我点个赞哟,感谢支持!