分布式缓存
分布式缓存常见的解决方案?你们采用哪种?
答:Memcached集群和Redis集群(RedisCluster)。我们采用的是RedisCluster。
分布式缓存带来的复杂度问题
常见的问题主要包括如下几点:
·数据一致性
·缓存穿透
·缓存雪崩
·缓存击穿
·缓存高可用
下面逐一介绍分析这些问题以及相应的解决方案。
数据一致性
本地数据库中的数据与缓存数据库中的数据不一致的现象。
解决方案:缓存同步。每次对本地数据库中的热点数据进行更新后,都要将对应的缓存数据库中的数据删除掉。下一次访问时,先访问缓存数据库,发现没有,就去访问本地数据库,然后会将数据再次缓存到缓存数据库中。
缓存穿透
客户端请求的流程是这样的:先访问缓存数据库,如果缓存数据库中没有,就去访问本地数据库,并将查询结果放到缓存数据库中,下一次再访问的时候,就直接从缓存数据库中取了。但但是如果请求的数据根本不存在呢?先到缓存数据库中找,没有,又到本地数据库中找,也没有,查询结果是null,因为是null,所以也就不会再放到缓存数据库中。这将导致每次请求这个根本不存在的数据都要到本地数据库中去查询,缓存就失去了意义,这就是缓存穿透。缓存穿透增加了本地数据库的压力,当流量特别大时,DB就挂掉了。如果有人专门利用不存在的key来攻击我们,这就是漏洞。
解决方案:有很多种方案,最常见的是采用布隆过滤器(我不太懂这个)。我采用的是一种简单粗暴的方法,如果在本地数据库中查询的数据为空,我们仍然将这个空数据进行缓存,但是我们会将这种数据的过期时间设置的很短,最长不超过5分钟。
缓存雪崩
我们缓存的数据一般都会设置过期时间,当过期时间一到,缓存就会失效,在访问这个数据的时候,就会再到DB中去查。缓存雪崩指的是我们将所有缓存的数据设置了相同的过期时间,这就导致在某一个时刻,所有缓存的数据同时失效,所有访问数据的请求都去访问DB数据库,DB瞬间压力过大,造成雪崩。
解决方案:将缓存数据的过期时间分散开,可以在原过期时间的基础上再加上一个随机值,比如1-5分钟随机。这样不会出现缓存数据同一时间集体失效的情况了。
缓存击穿
缓存击穿和缓存雪崩比较像,区别是缓存雪崩针对的是多个key,而缓存击穿指的是一个key。就是在某个key的过期时间到了的时候,同一时间,有特别大的并发同时访问该key,因为缓存中的该key已经过期,所以这些请求会同时去访问本地数据库DB,造成DB数据库一瞬间压力过大,被压垮。
解决方案:能造成缓存击穿的数据一定是特别热点的,我们将这些特别热点的数据设置为“永远不过期”,就能解决缓存击穿的问题。怎么让数据“永远不过期呢?”,有人说,对这些数据不要设置过期时间不就好了,但是这样一来,这些数据就变成静态的了,不好。我们采用的方法是为这些数据加守护线程。如果设置该key的过期时间为半个小时,则快要半个小时的时候由守护线程再将过期时间追加半个小时,循环往复。
缓存高可用
缓存系统必须是高可用的,即当分布式缓存系统中的某个结点挂掉之后,整个系统还是可用的。
解决方案:为分布式缓存系统中的每一个节点搭建备份服务器,当该节点挂掉后,备份节点就要顶上来,维持系统的运行。
RedisCluster中的知识点
- RedisCluster是如何确定数据要存在集群中的哪个节点的?
答:Redis集群中内置了16384个哈希槽,当需要在Redis集群中放置一个key-value时,redis先对key使用crc16算法算出一个结果,然后把结果对16384求余数,这样每个key都会对应一个编号在0-16383之间的哈希槽,redis会根据节点数量大致均等的将哈希槽映射到不同的节点。
Redis Cluster将所有数据划分为16384个槽,每个节点负责其中一部分槽位。槽位的信息存储于每个节点中,当Redis Cluster的客户端来连接集群时,它也会得到一份集群的槽位配置信息。这样当客户端要查找某个key时,可以直接定位到目标节点。
槽位定位算法
Cluster默认会对key值使用crc16算法进行hash得到一个整数值,然后用这个整数值对16384进行取模来得到具体槽位。
Cluster还允许用户强制某个key挂在特定槽位上,通过在key字符串里面嵌入tag标记,这就可以强制key所挂在的槽位等于tag所在的槽位。
槽如何进行分配呢?
答:这是可以由人为设置的。一般情况下会平均分配。为了保证Redis集群的性能,要看Redis集群中有几个结点,还要看每个结点的性能怎么样。假如有3个结点,每个结点的性能都是完全一样的,那么我们就可以把这16384个槽平均分到3个结点上。
0~5000个槽分到第一个结点上
5001~10000个槽分到第二个结点上
10001~16383个槽分到第三个结点上(为了好计算,这样划分)
- 如何判断Redis Cluster集群中的节点是否挂掉?
答:Redis集群中有一个投票机制。大家都知道,在选举的时候,是少数服从多数的原则,要判断Redis集群中的某个结点是否挂掉了,需要我们集群中超过半数的节点进行投票,半数以上的节点认为它挂了,它就挂了。假如集群中有5个结点,有三个认为某个结点已经挂了,那么集群就认为这个结点真挂了。这个时候就要看有没有备份结点,如果没有备份结点顶上来,那么集群就会宕机。如果有备份结点,备份结点顶上来,继续维持整个集群的工作,然后管理人员就需要赶快把那个挂掉的节点修理好。那么,集群中最少有几个结点呢?3个!3个结点就可以搭建起一个Redis集群。而在实际开发中,为了保证集群的高可用,还要保证每个结点都有一个备份机,所以,实际中,最小的集群会搭建6个结点。
- 一个RedisCluster集群至少有几个结点?至多有几个节点?为什么?
答:至少有3个结点,至多有16384个结点。
至少有3个结点是由RedisCluster的投票机制决定的。投票机制指的是超过半数的节点投票认为某个节点挂掉就挂掉了!
至多有16384个结点是因为RedisCluster总共就有16384个槽。
- RedisCluster集群中没有统一管理器,那么各个节点之间是如何进行通信的呢?
答:是通过PING-PONG机制(一种特殊的二进制协议)来实现各个节点之间的通信。
(面试题)你知道哪些分布式缓存,如果要你设计一个分布式缓存,你会怎么去设计?
答:主要有Memcached和Redis。我使用Redis来做分布式缓存。
刚开始对Redis的操作都是单机版,虽然Redis的速度很快,但是在特别高的并发下,Redis也有性能瓶颈。Redis中的数据都放在内存里面,内存能有多大呢?64G,已经很大了,64G都放满了呢?还能放吗?可以,内存放满了会放在硬盘中的虚拟内存中,一旦用到虚拟内存了,性能就很低了,所以我们尽可能的不要超出内存的容量。如果存不下了,但是数据还是很多,还需要往缓存中放,那怎么办呢?通过搭Redis集群来扩展内存空间。官方给出的Redis集群名称为Redis-Cluster。
Redis-Cluster架构图如下:
集群一般都会有一个入口,有一个集群管理工具,但Redis集群没有入口,即没有代理层,集群中的节点都是相互连接的,通过PING-PONG机制来实现各个节点之间的通信,以及判断各个节点的状态,客户端想要连接集群,只需要连接到集群中的任意一个节点即可。
集群中有那么多个结点,结点中保存的数据一样吗?不一样。如果是一样的,那叫主备。既然是集群,就应该是可以扩容的,如果存储空间不足了,可以加结点,加一个服务器进入,存储空间就会变大。所有结点的内存容量加起来才是整个集群内存的总容量,如果每个结点存储的数据都一样,那总容量就只是一个Redis的内存容量了。
Redis集群中,每个结点保存的数据是不一样的。如果不一样,那么当一个结点挂了,那整个集群就不完整了,不完整了就不能用了,所以,要想保证Redis集群的高可用(长时间可使用,而不会宕机),每一个节点都需要加一个备份机,如果这个结点挂了,必须要有备份结点顶上来,来保证集群可以继续提供服务。
Redis集群中有一个投票:容错机制,我们前面说集群中一般都会有一个集群管理工具,但在Redis集群中并没有,那么,我们怎么才能知道集群中哪一个结点挂了呢?Redis集群中有一个投票机制。大家都知道,在选举的时候,是少数服从多数的原则,要判断Redis集群中的某个结点是否挂掉了,需要我们集群中超过半数的节点进行投票,半数以上的节点认为它挂了,它就挂了。假如集群中有5个结点,有三个认为某个结点已经挂了,那么集群就认为这个结点真挂了。这个时候就要看有没有备份结点,如果没有备份结点顶上来,那么集群就会宕机。如果有备份结点,备份结点顶上来,继续维持整个集群的工作,然后管理人员就需要赶快把那个挂掉的节点修理好。那么,集群中最少有几个结点呢?3个!3个结点就可以搭建起一个Redis集群。而在实际开发中,为了保证集群的高可用,还要保证每个结点都有一个备份机,所以,实际中,最小的集群会搭建6个结点。那如果面试官问你:
一个Redis集群至少有几个结点?为什么?
答:有3个结点。这是由Redis集群中的投票:容错机制决定的。Redis集群中,要判断一个结点是否挂掉了,是通过集群中的其他结点投票决定的。当集群中有一半以上的节点都投票认为该节点挂掉了,Redis集群才会认为该节点挂掉了,这就导致一个Redis集群中最少要有3个结点。
当我们使用单机版的Redis做缓存时,操作很简单,当单机版的Redis变成Redis集群后,操作是不是就会变得异常复杂?
答:并不会变多复杂。只需要连接上Redis集群中的任意一个结点,就能连接上整个Redis集群。只是在使用Jedis连接单机版Redis和连接Redis集群时,会有所不同,但对Redis的操作都是一样的。
Redis集群中,每个结点保存的数据是不一样的,那就会有一个问题,如何把数据分散到不同的节点进行存储呢?为解决这个问题,Redis集群中引入了一个概念,叫slot(槽,哈希槽)。Redis集群中一共有16384个槽(0~16383),这是固定的。这些槽有什么作用呢?
当要在Redis集群中放置一个key-value对时,先要对key使用crc16算法得出一个数,然后再用这个数对16384求余数,肯定会得到一个0~16383之间的数,这样每一个key值都会对应一个0~16383之间的哈希槽,然后将key-value键值对放在这个槽对应的Redis结点上就可以了。
槽如何进行分配呢?
答:这是可以由人为设置的。一般情况下会平均分配。为了保证Redis集群的性能,要看Redis集群中有几个结点,还要看每个结点的性能怎么样。假如有3个结点,每个结点的性能都是完全一样的,那么我们就可以把这16384个槽平均分到3个结点上。
0~5000个槽分到第一个结点上
5001~10000个槽分到第二个结点上
10001~16383个槽分到第三个结点上(为了好计算,这样划分)
Redis集群中最多有多少个结点?为什么?
答:最多有16384个结点(这里不考虑备份机的问题)。这是由Redis集群中哈希槽的数量决定的,极限情况下,每个结点有一个哈希槽。
架构细节:
(1)所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.
(2)节点的fail是通过集群中超过半数的节点检测失效时才生效.
(3)客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
(4)redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster负责维护node<->slot<->value
Redis集群中内置了16384个哈希槽,当需要在Redis集群中放置一个key-value时,redis先对key使用crc16算法算出一个结果,然后把结果对16384求余数,这样每个key都会对应一个编号在0-16383之间的哈希槽,redis会根据节点数量大致均等的将哈希槽映射到不同的节点。