Redis单机问题
随着互联网的急速发展,单机的redis已经不足以应对日常的需求,单机的redis主要存在以下问题:
1.单点故障,在单机环境下,一旦redis宕机,那么整个系统将会崩溃。
2. 容量有限,redis是一个内存型数据库,而一台机器的内存是非常有限的,一台机器只能将内存的一部分分配给redis用,故而单机的redis存在容量问题。
3. 访问压力,当访问量急速增加时,单机的redis将会出现瓶颈,无法为所有的用户提供服务。
问题解决方案
对于上述存在的问题,业界一般采用AKF原则来进行解决,AKF简单理解即从X轴、Y轴、Z轴三个维度去增加机器来解决出现的单点、容量、性能问题,它的一个理念就是没有什么问题是不能通过增加机器来解决的(没有什么事是不能通过一顿烧烤解决的,如果有,那就两顿)。
在redis的单机问题上,我们可以在X轴方向上增加机器作为备机或者从机,解决单点故障;对于容量问题和访问压力,我们可以拆分系统,将不同的功能独立为不同的服务,每个服务单独运行在一台机器上,从Y轴方向增加机器实现;在X和Y方向拓展后,我们每一个服务都是主备或主存结构,如果还存在问题,可以做Z轴方向的拓展,将一个服务一个主备或主存拓展为多个主备或主存。
注:
主备与主从的区别,备机一般不提供服务,只有当主机挂掉之后它将作为主机出现,从机一般会提供读操作,不接收写操作
一变多后带来的新问题
一变多后带来的问题就是数据一致性问题,主机提供读写操作,备机不提供任何操作,从机提供读操作,当主机挂掉后,备机或从机要接替主机,那么它们的数据就需要保持一致。即在主机写入后要将数据传入到备机或从机中,传输又需要一定的时间,这又会带来性能、可用性问题,选择什么样的传输方式就显得特别重要,常见的方式有:同步阻塞式和异步非阻塞式,它们又各有优缺点,选择什么样的方式就需要看实际的情况来定了,redis为了满足高可用、快的特点,选择了异步的方式。
Redis中的主从复制(哨兵模式)
在redis的主从结构中,要保证高可用那么当主机挂掉后就需要立即有从机站出来作为新的主机来提供服务,Redis通过Redis哨兵(sentinel)的方式来进行这一过程,这一过程也可叫做自动故障转移,当主机出问题后,马上从从机中选出一台作为新主机来提供服务。
在Redis的主从结构中,我们可以用sentinel来做对主机的监控,sentinel是一个应用程序,它设计出来的目的就是实时的进行对主机的监控,当发现主机出问题后由它来做出判断,决定哪台机器将成为新的主机。
如果是一台sentinel,那么它还存在单点问题,同时它也不可靠,如果只是它与主机的网络出问题了,那么它的消息就是错误的,因此,一般情况下sentinel也是集群存在的,集群后每台sentinel都会得到主机的信息,但新的问题就是我们应该听谁的,多台sentinel拿到的结果或许有差别,面对这种情况我们一般会采用少数服从多数原则,在redis中也称之为势力范围(拿到形同结果的为一伙),只有势力范围足够,才能做出决定,选出新的主机。
主从数据传输
当有从机连入主机时,主机会落一个rdb文件到磁盘,然后传到从机上,从机会先清库然后读入传入的rdb文件。
当从机挂掉重启后,会以增量的方式来重新获得主机的数据(读入自己的rdb,然后从主机获得偏移量内的数据即可),主机一般会有一个消息对列,用来保存新增的数据,如果偏移量大于主机消息队列的大小,那么就会触发全量传输。
注:这些东西都可以通过配置文件来改变
偏移量:新数据与从机旧数据的差值
Redis集群
主存复制的模式是在X轴方向的拓展,它并不能解决容量问题,对于容量问题我们可以在Y轴方向做对应的处理,加主机。但加机器后引入的一个新问题就是数据分发问题,该怎么将数据分发到不同的库中。
我们采用的办法一般为对于数据能拆分的我们可以根据业务功能将数据划分,不同的功能采用不同的数据库,对于数据不能拆分的我们采用一些算法来做统一化处理,无差别的将它们做存取,这样我们的容量问题就得以解决。
分发策略
hash取模(module)
根据数据库的个数来做hash取模运算,将数据分发。它有一个显著的缺点就是扩展性问题。只要增加机器,那么它的数据就会全部失效。
随机存取(random)
对于这种方式,随机的将数据分发到不同的库中,这种方式的问题就是无法取出,但它却存在一种非常好的应用场景,做消息队列,客户端只需要将数据存入一个集合中,获取消息的一端就可以将数据取出。
一致性hash(kemata)
这种方式是对module的改进,采用一致性hash算法将数据分发到不同的库里,当增加了机器后只有一小部分数据会失效,这种方式的redis只适合做缓存,需要进数据库拿数据,具体的一致性hash算法这不做过多解释。
数据分治后的缺点及Redis的解决方式(tag)
我们将数据分到不同的库中后会出现无法进行聚合、事务等的操作;redis为了保持快速、高可用的特点拒绝支持这些操作,但它给出了一种解决方式添加标记的方式,它会将同一个标记下的数据放入同一个库中,举个简单的例子:
set {ttoo}key1 abc
set {ttoo}key2 bdg
因为key1与key2有相同的标记,因此redis会将这两个数据放到同一个库中。
分库后的连接模型
分库后如果各客户端直接连到不同的redis,会给redis数据库造成很大的压力,使redis的性能有所下降,因此在集群下的redis连接模型有了一些变化,引入了代理来提升性能(redis数据库只需要和有限的几台代理通信即可,同时我们的分发逻辑也在代理服务器上)与用户体验(后面的redis集群对于用户来说是透明的,它们只需要连到代理服务器就可以)。
Redis 集群的数据分片
Redis 集群没有使用一致性hash, 而是引入了 哈希槽的概念,相当于对预分配的概念。
Redis 集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,相当于最大支持16384个redis集群,集群的每个节点负责一部分hash槽,举个例子,比如当前集群有3个节点,那么:
节点 A 包含 0 到 5500号哈希槽.
节点 B 包含5501 到 11000 号哈希槽.
节点 C 包含11001 到 16384号哈希槽.
这种结构很容易添加或者删除节点. 比如如果我想新添加个节点D, 我需要从节点 A, B, C中得部分槽到D上. 如果我想移除节点A,需要将A中的槽移到B和C节点上,然后将没有任何槽的A节点从集群中移除即可. 由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态。
Redis集群的几种方式
redis自身提供的一种实现就是cluster,它需要客户端支持;其他的第三方也有其他的实现,twemproxy就是一种,它是 Twtter 开源的一个 Redis 和 Memcache 代理服务器,主要用于管理 Redis 和 Memcached 集群;还有一个是predixy代理服务器。具体的使用可参考对应的GitHub,各种各样的实现其实都使用了上边介绍的理论,它们的主要目的就是实现集群的管理,使redis集群能够提供高可用的服务。