Redis相关问题汇总

1.Redis为什么那么快

1.Redis是完全基于内存的,绝大部分请求是纯粹的内存操作,非常快速。数据都存在内存中,类似HashMap,查找和操作的时间复杂度都是O(1)。
2.数据结构简单,对数据操作也简单,Redis的数据结构是专门进行设计的
3.采用单线程,避免了不必要的线程上下文切换和竞争条件,不存在多线程或多线程导致的切换消耗CPU,不用去考虑锁的问题,没有可能出现死锁导致的性能消耗
4.使用多路I/O复用模型,非阻塞IO
5.Redis自有的通讯协议,RESP是redis客户端与服务器之间的一种通讯协议,特点:实现简单、快速解析、可读性好

多路I/O复用模型:
多路I/O复用模型是利用select、poll、epoll可以同时监察多个IO事件的能力(epoll性能最高),在空闲的时候,会把当前线程阻塞掉,当有一个或者多个流有IO事件时,就从阻塞状态中唤醒,程序轮询一遍所有的流(epoll只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的IO流,这种做法就避免的大量的无用操作
多路指的是多个网络连接,复用指的是复用同一个线程。采用多路IO复用技术可以让单个线程高效的处理多个连接请求(减少网络IO的时间消耗),且Redis在内存中的操作速度非常快,也就是说内存中的操作不会成为Redis的性能瓶颈。由以上几点原因造就了Redis具有很高的吞吐量。

2.Redis为什么要设计成单线程的

官方FAQ表示,因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。在linux平台,已经能够支持100万QPS(大家测试出来10W,官方给的100W)。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章的采用单线程的方案了。官方解释到,在Redis4.0以后,开始让Redis的一些操作来支持多线程,下一个Release版本,计划让Redis支持多线程。

3.Redis如何保证高可用

(1)主从复制主从复制

优点:解决了数据备份和单例可能存在的性能问题,但是其也引入了新的问题。由于主从模式配置了三个redis实例,并且每个实例都使用不同的ip(如果在不同的机器上)和端口号,根据前面所述,主从模式下可以将读写操作分配给不同的实例进行从而达到提高系统吞吐量的目的
缺点:但也正是因为这种方式造成了使用上的不便,因为每个客户端连接redis实例的时候都是指定了ip和端口号的,如果所连接的redis实例因为故障下线了,而主从模式也没有提供一定的手段通知客户端另外可连接的客户端地址,因而需要手动更改客户端配置重新连接。另外,主从模式下,如果主节点由于故障下线了,那么从节点因为没有主节点而同步中断,因而需要人工进行故障转移工作
(2)Sentinel
哨兵
对于一组主从节点,sentinel只是在其外部额外添加的一组用于监控作用的redis实例。在主从节点和sentinel节点集合配置好之后,sentinel节点之间会相互发送消息,以检测其余sentinel节点是否正常工作,并且sentinel节点也会向主从节点发送消息,以检测监控的主从节点是否正常工作。。前面讲到,sentinel架构的主要作用是解决主从模式下主节点的故障转移工作的。这里如果主节点因为故障下线,那么某个sentinel节点发送检测消息给主节点时,如果在指定时间内收不到回复,那么该sentinel就会主观的判断该主节点已经下线,那么其会发送消息给其余的sentinel节点,询问其是否“认为”该主节点已下线,其余的sentinel收到消息后也会发送检测消息给主节点,如果其认为该主节点已经下线,那么其会回复向其询问的sentinel节点,告知其也认为主节点已经下线,当该sentinel节点最先收到超过指定数目(配置文件中配置的数目和当前sentinel节点集合数的一半,这里两个数目的较大值)的sentinel节点回复说当前主节点已下线,那么其就会对主节点进行故障转移工作,故障转移的基本思路是在从节点中选取某个从节点向其发送slaveof no one(假设选取的从节点为127.0.0.1:6380),使其称为独立的节点(也就是新的主节点),然后sentinel向其余的从节点发送slaveof 127.0.0.1 6380命令使它们重新成为新的主节点的从节点。重新分配之后sentinel节点集合还会继续监控已经下线的主节点(假设为127.0.0.1:6379),如果其重新上线,那么sentinel会向其发送slaveof命令,使其成为新的主机点的从节点,如此故障转移工作完成。
优点:解决了数据备份和单例存在的性能问题,有效的解决了故障转移的问题,也解决了主节点下线客户端无法识别新的可用节点的问题
缺点:如果是从节点下线了,sentinel是不会对其进行故障转移的,并且连接从节点的客户端也无法获取到新的可用从节点
(3)Redis-cluster
redis集群是在redis 3.0版本推出的一个功能,其有效的解决了redis在分布式方面的需求。当遇到单机内存,并发和流量瓶颈等问题时,可采用Cluster方案达到负载均衡的目的。并且从另一方面讲,redis中sentinel有效的解决了故障转移的问题,也解决了主节点下线客户端无法识别新的可用节点的问题,但是如果是从节点下线了,sentinel是不会对其进行故障转移的,并且连接从节点的客户端也无法获取到新的可用从节点,而这些问题在Cluster中都得到了有效的解决。
优点: 解决了数据备份和单例存在的性能问题,有效的解决了故障转移的问题,也解决了主节点下线客户端无法识别新的可用节点的问题,解决了从节点故障转移问题,可以从连接从节点的客户端也无法获取到新的可用从节点
缺点:

无中心化架构
Gossip消息的开销
不停机升级困难
无法根据统计区分冷热数据

客户端的挑战
Cluster协议支持
连接和路由表的维护开销
MultiOp和Pipeline支持有限

Redis实现问题
不能自动发现
不能自动Resharding
无监控管理UI
最终一致性和“脑裂”问题
数据迁移以Key为单位,速度较慢
数据迁移没有保存进度,故障时不能恢复
Slave“冷备”,不能缓解读压力
具体如下【Redis Cluster 架构优化】

Redis可靠性方案
redis单例、主从模式、sentinel以及集群的配置方式及优缺点对比
Redis Cluster 架构优化

4.什么是一致性哈希算法和哈希槽

一致性哈希算法
我们在使用n台存储设备存储数据的时候,常规做法有将数据根据key%n这样计算放在哪台服务器,但是在扩容的时候就会遇到数据迁移的问题,比如扩容m台服务器,以前是key%n,现在是key%(n+m),导致数据存储的位置需要变化,数据迁移的成本比较大,这个时候我们就引用了一种叫一致性hash的算法 。
一致性hash的原理

一致性Hash算法也使用取模的方法,刚才描述的取模法是对服务器的数量进行取模,而一致性Hash算法是对2^32取模
首先,我们把2^32 想象成一个圆,圆上一共有2^32 个点,编号0-2^32-1,这个圆称为hash环,
1.将服务器的ip或者编号进行hash算法计算取模hash(sever)%2^32 ,计算出服务器处于环上的位置
2.在存取数据的时候,根据hash算法计算数据在环中的位置hash(data)%2^32 ,再计算离数据最近顺时针方向最近的节点getNodeIndex(data) ,查找到数据存放的节点
3.遇到故障的时候,假如有服务节点遇到故障,则只需要将该服务节点的数据移入路径上的下一个节点,对原有的节点没有影响,转移到的节点内存使用率骤增,可能导致节点挂掉,挂掉之后再转移到其他节点,造成所有节点崩溃,出现缓存雪崩
4.扩容的时候,假如服务需要扩容的时候,则根据扩容的节点的位置,只需要将该位置路径下一个节点的部分数据移入新节点即可。如,新增一个node x,则只需要将C节点中,从b到x段的数据移入x即可,极大的减轻了扩容时对整个系统的影响
5.虚拟节点,在服务节点较少的时候,容易出现hash环倾斜的情况,即大量数据分布在少部分节点上。为了解决哈希环倾斜和缓存雪崩的问题,引入了虚拟节点。一个实际节点对于多个虚拟节点,hash环上的节点越多,数据被均匀分布的概率就越大。数据分配给了多个节点,避免出现缓存雪崩的问题。

真实节点不放置到哈希环上,只有虚拟节点才会放上去

哈希槽:

Redis集群(Cluster)并没有选用上面一致性哈希,而是采用了哈希槽(SLOT)的这种概念。主要的原因就是上面所说的,一致性哈希算法对于数据分布、节点位置的控制并不是很友好。

首先哈希槽其实是两个概念,第一个是哈希算法。Redis Cluster的hash算法不是简单的hash(),而是crc16算法,一种校验算法。

另外一个就是槽位的概念,空间分配的规则。其实哈希槽的本质和一致性哈希算法非常相似,不同点就是对于哈希空间的定义。一致性哈希的空间是一个圆环,节点分布是基于圆环的,无法很好的控制数据分布。而Redis Cluster的槽位空间是自定义分配的,类似于Windows盘分区的概念。这种分区是可以自定义大小,自定义位置的。

Redis Cluster包含了16384个哈希槽,每个Key通过计算后都会落在具体一个槽位上,而这个槽位是属于哪个存储节点的,则由用户自己定义分配。例如机器硬盘小的,可以分配少一点槽位,硬盘大的可以分配多一点。如果节点硬盘都差不多则可以平均分配。所以哈希槽这种概念很好地解决了一致性哈希的弊端。

另外在容错性和扩展性上,表象与一致性哈希一样,都是对受影响的数据进行转移。而哈希槽本质上是对槽位的转移,把故障节点负责的槽位转移到其他正常的节点上。扩展节点也是一样,把其他节点上的槽位转移到新的节点上。

但一定要注意的是,对于槽位的转移和分派,Redis集群是不会自动进行的,而是需要人工配置的。所以Redis集群的高可用是依赖于节点的主从复制与主从间的自动故障转移。

和一致性哈希相比

它并不是闭合的,key的定位规则是根据CRC-16(key)%16384的值来判断属于哪个槽区,从而判断该key属于哪个节点,而一致性哈希是根据hash(key)的值来顺时针找第一个hash(ip)的节点,从而确定key存储在哪个节点。
一致性哈希是创建虚拟节点来实现节点宕机后的数据转移并保证数据的安全性和集群的可用性的。redis cluster是采用master节点有多个slave节点机制来保证数据的完整性的,master节点写入数据,slave节点同步数据。当master节点挂机后,slave节点会通过选举机制选举出一个节点变成master节点,实现高可用。但是这里有一点需要考虑,如果master节点存在热点缓存,某一个时刻某个key的访问急剧增高,这时该mater节点可能操劳过度而死,随后从节点选举为主节点后,同样宕机,一次类推,造成缓存雪崩。

解决方案:
1.将热点缓存存到JVM内存中,可以使用Ehcache或者HashMap
2.限流熔断保护,每个系统实例内部,加入限流熔断保护机制,控制并发访问的线程数。

5.缓存穿透、缓存击穿、缓存雪崩

缓存穿透:是指查询一个数据库一定不存在的数据。正常使用缓存的流程是,数据查询先查询缓存如果key不存在或者key已经过期,再对数据库进行查询,再把查询结果放入缓存。如果查询对象为空,则不放入缓存。
解决方法:1.过滤明显不合理的请求
2.缓存空值,设置一个较短时间,比如缓存30S。

缓存击穿:是指一个key非常热点,不停地扛着大量的并发请求,高并发量集中对着一个点进行访问,那么在这个key失效的一瞬间,大量的并发就会穿破缓存,直接请求数据库,极有可能搞挂数据库。
解决方法:1.设置热点数据为永不过期
2.使用互斥锁,防止多个线程访问数据库重复更新缓存的情况出现

缓存雪崩:指在某一时间段内,缓存集中过期失效。造成原因一般是一波数据在一个时间段内集中放入了缓存,并且设置的缓存失效时间是一样的,这样就导致缓存时间到了之后,这一波数据的缓存集体失效,失效后大量的请求直接冲向数据库,数据库访问压力骤升。

或是缓存服务器的宕机或者断网,更是灾难性的,所以需要保证缓存服务的高可用
解决办法:将数据根据分类,每个类目设置不同的缓存周期,再对同一分类的数据,设置缓存过期时间时加一个随机因子,分散开缓存时间,是缓存失效时间呈现一种离散的状态。比如,热门数据缓存时间久一点,冷门数据缓存时间短一点。

6.Redis如何实现分布式锁

先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。若是setnx与expire指令之间服务器挂掉,expire得不到执行,依然存在坑无法释放的情况。实际的问题是setnx与expire两个指令不是原子指令,不能一起执行。为了解决这个问题redis 2.8版本以后作者对set指令进行了扩展,是的setnx与expire指令一起执行。set lock true ex 10 nx。

即便这样,以上操作还是可能会引起超时问题,即超时时间到了,锁释放,A线程逻辑还未执行完成,B线程池有锁,AB线程同时对资源进行操作,就会有问题。为了避免这个问题,Redis 分布式锁不要用于较长时间的任务。如果真的偶尔出现了,数据出现的小波错乱可能需要人工介入解决。

7.先更新数据库还是先删除缓存

首先,先讲清楚一件事,无论你采用哪种策略或者说设计模式,你总是在性能和一致性之间取舍,就像算法里面的时间换空间,空间换时间一样。完美的事物总是镜花水月。

1.先删除缓存再更新数据库—— 写操作删除缓存后去写库,此时读操作也访问了数据库,然后将数据放入缓存,此时写操作写数据库完成,缓存中存放的是旧数据。产生脏读(可以在写库完成再让缓存失效。没意义,还不如直接用下面这种)
2.先更新数据库再删缓存—— 写操作进来,写入数据库,此时读操作访问的是缓存,直接返回,如果此时缓存已经失效,读操作进入数据库,读到数据后,更新缓存,写操作完成,然后缓存失效。数据依然正确。

个人:首先确定一件事,读请求来的时候,如果缓存中不存在,则去访问数据库,将结果存至缓存中。
读:
1.读请求访问缓存,缓存中存在,返回数据
2.不存在,访问数据库,更新缓存
写:
1.写数据库,不操作缓存
2.写库完成后,缓存失效
这种更新缓存的方式几乎不会出现问题。
出现问题的情况:在读缓存时缓存失效,而且并发着有一个写操作。而实际上数据库的写操作会比读操作慢得多,而且还要锁表,而读操作必需在写操作前进入数据库操作,而又要晚于写操作更新缓存,所有的这些条件都具备的概率基本并不大。

参考文章:缓存更新的套路
欢迎留言,互相交流,思想碰撞,取得进步,哈哈!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值