为啥Redis那么快?
首先看下关系数据库和Redis本质上的区别。
从图中可以看出,关系数据库涉及到硬盘操作,而Redis直接是内存操作。
Redis采用的是基于内存的采用的是单进程单线程模型的 KV 数据库,由C语言编写,官方提供的数据是可以达到100000+的QPS(每秒内查询次数)。
-
完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。它的,数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
-
数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;
-
采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
-
使用多路I/O复用模型,非阻塞IO;
-
使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
Redis是单线程的,我们现在服务器都是多核的,那不是很浪费?
是的他是单线程的,但是,我们可以通过在单机开多个Redis实例嘛。
既然提到了单机会有瓶颈,那你们是怎么解决这个瓶颈的?
我们用到了集群的部署方式也就是Redis cluster,并且是主从同步读写分离,
类似Mysql的主从同步,Redis cluster 支撑 N 个 Redis master node,每个master node都可以挂载多个 slave node。
这样整个 Redis 就可以横向扩容了。如果你要支撑更大数据量的缓存,那就横向扩容更多的 master 节点,每个 master 节点就能存放更多的数据了。
哦?那问题就来了,他们之间是怎么进行数据交互的?以及Redis是怎么进行持久化的?Redis数据都在内存中,一断电或者重启不就木有了嘛?
是的,持久化的话是Redis高可用中比较重要的一个环节,因为Redis数据在内存的特性,持久化必须得有,我了解到的持久化是有两种方式的。
-
RDB:RDB 持久化机制,是对 Redis 中的数据执行周期性的持久化。
-
AOF:AOF 机制对每条写入命令作为日志,以 append-only 的模式写入一个日志文件中,因为这个模式是只追加的方式,所以没有任何磁盘寻址的开销,所以很快,有点像Mysql中的binlog。
两种方式都可以把Redis内存中的数据持久化到磁盘上,然后再将这些数据备份到别的地方去,RDB更适合做冷备,AOF更适合做热备,
比如我杭州的某电商公司有这两个数据,我备份一份到我杭州的节点,再备份一个到上海的,就算发生无法避免的自然灾害,也不会两个地方都一起挂吧,这灾备也就是异地容灾,地球毁灭他没办法。
tip:两种机制全部开启的时候,Redis在重启的时候会默认使用AOF去重新构建数据,因为AOF的数据是比RDB更完整的。
那这两种机制各自优缺点是啥?
先说RDB吧
优点:
他会生成多个数据文件,每个数据文件分别都代表了某一时刻Redis里面的数据,
这种方式,有没有觉得很适合做冷备,完整的数据运维设置定时任务,定时同步到远端的服务器,比如阿里的云服务,这样一旦线上挂了,你想恢复多少分钟之前的数据,就去远端拷贝一份之前的数据就好了。
RDB对Redis的性能影响非常小,是因为在同步数据的时候他只是fork了一个子进程去做持久化的,而且他在数据恢复的时候速度比AOF来的快。
缺点:
RDB都是快照文件,都是默认五分钟甚至更久的时间才会生成一次,这意味着你这次同步到下次同步这中间五分钟的数据都很可能全部丢失掉。
AOF则最多丢一秒的数据,数据完整性上高下立判。
还有就是RDB在生成数据快照的时候,如果文件很大,客户端可能会暂停几毫秒甚至几秒,你公司在做秒杀的时候他刚好在这个时候fork了一个子进程去生成一个大快照,哦豁,出大问题。
我们再来说说AOF
优点:
上面提到了,RDB五分钟一次生成快照,但是AOF是一秒一次去通过一个后台的线程fsync
操作,那最多丢这一秒的数据。
AOF在对日志文件进行操作的时候是以append-only
的方式去写的,他只是追加的方式写数据,自然就少了很多磁盘寻址的开销了,写入性能惊人,文件也不容易破损。
AOF的日志是通过一个叫非常可读的方式记录的,这样的特性就适合做灾难性数据误删除的紧急恢复了,比如公司的实习生通过flushall清空了所有的数据,只要这个时候后台重写还没发生,你马上拷贝一份AOF日志文件,把最后一条flushall命令删了就完事了。
缺点:
一样的数据,AOF文件比RDB还要大。
AOF开启后,Redis支持写的QPS会比RDB支持写的要低,他不是每秒都要去异步刷新一次日志嘛fsync,当然即使这样性能还是很高,
我记得ElasticSearch也是这样的,异步刷新缓存区的数据去持久化,为啥这么做呢,不直接来一条怼一条呢,那我会告诉你这样性能可能低到没办法用的,大家可以思考下为啥哟。
那两者怎么选择?
可以两个一起用。
你单独用RDB你会丢失很多数据,你单独用AOF,你数据恢复没RDB来的快,真出什么时候第一时间用RDB恢复,然后AOF做数据补全,
真香!冷备热备一起上,才是互联网时代一个高健壮性系统的王道。
关于高可用,Redis还有其他保证集群高可用的方式么?
Redis除了可以用主从的方式保证高可用,还可以使用哨兵集群。
还有哨兵集群sentinel。???Redis的哨兵集群?
哨兵必须用三个实例去保证自己的健壮性的,哨兵+主从并不能保证数据不丢失,但是可以保证集群的高可用。
总结下哨兵组件的主要功能:
-
集群监控:负责监控 Redis master 和 slave 进程是否正常工作。
-
消息通知:如果某个 Redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
-
故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
-
配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。
关于主从同步,能说一下主从之间的数据怎么同步的么?
如上,关于主从读写分离。
回归正题,他们数据怎么同步的呢?
你启动一台slave 的时候,他会发送一个psync命令给master ,如果是这个slave第一次连接到master,他会触发一个全量复制。
master就会启动一个线程,生成RDB快照,还会把新的写请求都缓存在内存中,
RDB文件生成后,master会将这个RDB发送给slave的,slave拿到之后做的第一件事情就是写进本地的磁盘,
然后加载进内存,然后master会把内存里面缓存的那些新命名都发给slave。
数据传输的时候断网了或者服务器挂了怎么办啊?
传输过程中有什么网络问题啥的,会自动重连的,并且连接之后会把缺少的数据补上的。
大家需要记得的就是,RDB快照的数据生成的时候,缓存区也必须同时开始接受新请求,不然你旧的数据过去了,你在同步期间的增量数据咋办?是吧?
能说一下他的内存淘汰机制么,来手写一下LRU代码?
Redis的过期策略,是有定期删除+惰性删除两种。
定期好理解,默认100ms就随机抽一些设置了过期时间的key,去检查是否过期,过期了就删了。
为啥不扫描全部设置了过期时间的key呢?
假如Redis里面所有的key都有过期时间,都扫描一遍?
那太恐怖了,而且我们线上基本上也都是会设置一定的过期时间的。
全扫描跟你去查数据库不带where条件不走索引全表扫描一样,100ms一次,Redis累都累死了。
如果一直没随机到很多key,里面不就存在大量的无效key了?
好问题,惰性删除,见名知意,惰性嘛,我不主动删,我懒,我等你来查询了我看看你过期没,过期就删了还不给你返回,没过期该怎么样就怎么样。
最后就是如果的如果,定期没删,我也没查询,那可咋整?
内存淘汰机制!
官网上给到的内存淘汰机制是以下几个:
-
noeviction:返回错误,当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
-
allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
-
volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
-
allkeys-random: 回收随机的键使得新添加的数据有空间存放。
-
volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
-
volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
如果没有键满足回收的前提条件的话,策略volatile-lru, volatile-random以及volatile-ttl就和noeviction 差不多了。
tip:Redis为什么不使用真实的LRU实现是因为这需要太多的内存。
不过近似的LRU算法对于应用而言应该是等价的。使用真实的LRU算法与近似的算法可以通过下面的图像对比。
注意LRU只是个预测键将如何被访问的模型。另外,如果你的数据访问模式非常接近幂定律,大部分的访问将集中在一个键的集合中,LRU的近似算法将处理得很好。
其实在大家熟悉的LinkedHashMap中也实现了Lru算法的,实现如下:
当容量超过100时,开始执行LRU策略:将最近最少未使用的 TimeoutInfoHolder 对象 evict 掉。
真实面试中会让你写LUR算法,你可别搞原始的那个,那真TM多,写不完的,你要么怼上面这个,要么怼下面这个,找一个数据结构实现下Java版本的LRU还是比较容易的,知道啥原理就好了。
参考:https://mp.weixin.qq.com/s/EjDeypra_d9Tfsn-WkJZdw