精讲Redis服务架构分析与搭建

基于内存的Redis应该是目前各类web开发业务中最为经常使用的key-value数据库了,咱们常常在业务中用其存储用户登录态(Session存储),加速一些热数据的查询(相比较mysql而言,速度有数量级的提高),作简单的消息队列(LPUSH和BRPOP)、订阅发布(PUB/SUB)系统等等。规模比较大的互联网公司,通常都会有专门的团队,将Redis存储以基础服务的形式提供给各个业务调用。能够阅读前两篇文章精讲redis内存模型精讲redis持久化mysql

不过任何一个基础服务的提供方,都会被调用方问起的一个问题是:你的服务是否具备高可用性?最好不要由于你的服务常常出问题,致使我这边的业务跟着遭殃。最近我所在的项目中也本身搭了一套小型的“高可用”Redis服务,在此作一下本身的总结和思考。web

首先咱们要定义一下对于Redis服务来讲怎样才算是高可用,即在各类出现异常的状况下,依然能够正常提供服务。或者宽松一些,出现异常的状况下,只通过很短暂的时间便可恢复正常服务。所谓异常,应该至少包含了如下几种可能性:redis

【异常1】某个节点服务器的某个进程忽然down掉(例如某开发手残,把一台服务器的redis-server进程kill了)sql

【异常2】某台节点服务器down掉,至关于这个节点上全部进程都停了(例如某运维手残,把一个服务器的电源拔了;例如一些老旧机器出现硬件故障)数据库

【异常3】任意两个节点服务器之间的通讯中断了(例如某临时工手残,把用于两个机房通讯的光缆挖断了)服务器

其实以上任意一种异常都是小几率事件,而作到高可用性的基本指导思想就是:多个小几率事件同时发生的几率能够忽略不计。只要咱们设计的系统能够容忍短期内的单点故障,便可实现高可用性。网络

对于搭建高可用Redis服务,网上已有了不少方案,例如Keepalived,Codis,Twemproxy,Redis Sentinel。其中Codis和Twemproxy主要是用于大规模的Redis集群中,也是在Redis官方发布Redis Sentinel以前twitter和豌豆荚提供的开源解决方案。个人业务中数据量并不大,因此搞集群服务反而是浪费机器了。最终在Keepalived和Redis Sentinel之间作了个选择,选择了官方的解决方案Redis Sentinel。架构

Redis Sentinel能够理解为一个监控Redis Server服务是否正常的进程,而且一旦检测到不正常,能够自动地将备份(slave)Redis Server启用,使得外部用户对Redis服务内部出现的异常无感知。咱们按照由简至繁的步骤,搭建一个最小型的高可用的Redis服务。运维

方案1:单机版Redis Server,无Sentinel分布式

通常状况下,咱们搭的我的网站,或者平时作开发时,会起一个单实例的Redis Server。调用方直接链接Redis服务便可,甚至Client和Redis自己就处于同一台服务器上。这种搭配仅适合我的学习娱乐,毕竟这种配置总会有单点故障的问题没法解决。一旦Redis服务进程挂了,或者服务器1停机了,那么服务就不可用了。而且若是没有配置Redis数据持久化的话,Redis内部已经存储的数据也会丢失。

方案2:主从同步Redis Server,单实例Sentinel

为了实现高可用,解决方案1中所述的单点故障问题,咱们必须增长一个备份服务,即在两台服务器上分别各启动一个Redis Server进程,通常状况下由master提供服务,slave只负责同步和备份。与此同时,在额外启动一个Sentinel进程,监控两个Redis Server实例的可用性,以便在master挂掉的时候,及时把slave提高到master的角色继续提供服务,这样就实现了Redis Server的高可用。这基于一个高可用服务设计的依据,即单点故障自己就是个小几率事件,而多个单点同时故障(即master和slave同时挂掉),能够认为是(基本)不可能发生的事件。

对于Redis服务的调用方来讲,如今要链接的是Redis Sentinel服务,而不是Redis Server了。常见的调用过程是,client先链接Redis Sentinel并询问目前Redis Server中哪一个服务是master,哪些是slave,而后再去链接相应的Redis Server进行操做。固然目前的第三方库通常都已经实现了这一调用过程,再也不须要咱们手动去实现(例如Nodejs的ioredis,PHP的predis,Golang的go-redis/redis,JAVA的jedis等)。

然而,咱们实现了Redis Server服务的主从切换以后,又引入了一个新的问题,即Redis Sentinel自己也是个单点服务,一旦Sentinel进程挂了,那么客户端就没办法连接Sentinel了。因此说,方案2的配置并没有法实现高可用性。

方案3:主从同步Redis Server,双实例Sentinel

为了解决方案2的问题,咱们把Redis Sentinel进程也额外启动一份,两个Sentinel进程同时为客户端提供服务发现的功能。对于客户端来讲,它能够链接任何一个Redis Sentinel服务,来获取当前Redis Server实例的基本信息。一般状况下,咱们会在Client端配置多个Redis Sentinel的连接地址,Client一旦发现某个地址链接不上,会去试图链接其余的Sentinel实例,这固然也不须要咱们手动实现,各个开发语言中比较热门的redis链接库都帮咱们实现了这个功能。咱们预期是:即便其中一个Redis Sentinel挂掉了,还有另一个Sentinel能够提供服务。

然而,愿景是美好的,现实倒是很残酷的。如此架构下,依然没法实现Redis服务的高可用。方案3示意图中,红线部分是两台服务器之间的通讯,而咱们所设想的异常场景(【异常2】)是,某台服务器总体down机,不妨假设服务器1停机,此时,只剩下服务器2上面的Redis Sentinel和slave Redis Server进程。这时,Sentinel实际上是不会将仅剩的slave切换成master继续服务的,也就致使Redis服务不可用,由于Redis的设定是只有当超过50%的Sentinel进程能够连通并投票选取新的master时,才会真正发生主从切换。本例中两个Sentinel只有一个能够连通,等于50%并不在能够主从切换的场景中。

你可能会问,为何Redis要有这个50%的设定?假设咱们容许小于等于50%的Sentinel连通的场景下也能够进行主从切换。试想一下【异常3】,即服务器1和服务器2之间的网络中断,可是服务器自己是能够运行的。以下图所示:

实际上对于服务器2来讲,服务器1直接down掉和服务器1网络连不通是同样的效果,反正都是忽然就没法进行任何通讯了。假设网络中断时咱们容许服务器2的Sentinel把slave切换为master,结果就是你如今拥有了两个能够对外提供服务的Redis Server。Client作任何的增删改操做,有可能落在服务器1的Redis上,也有可能落在服务器2的Redis上(取决于Client到底连通的是哪一个Sentinel),形成数据混乱。即便后面服务器1和服务器2之间的网络又恢复了,那咱们也没法把数据统一了(两份不同的数据,到底该信任谁呢?),数据一致性彻底被破坏。

方案4:主从同步Redis Server,三实例Sentinel

鉴于方案3并无办法作到高可用,咱们最终的版本就是上图所示的方案4了。实际上这就是咱们最终搭建的架构。咱们引入了服务器3,而且在3上面又搭建起一个Redis Sentinel进程,如今由三个Sentinel进程来管理两个Redis Server实例。这种场景下,无论是单一进程故障、仍是单个机器故障、仍是某两个机器网络通讯故障,均可以继续对外提供Redis服务。

实际上,若是你的机器比较空闲,固然也能够把服务器3上面也开启一个Redis Server,造成1 master + 2 slave的架构,每一个数据都有两个备份,可用性会提高一些。固然也并非slave越多越好,毕竟主从同步也是须要时间成本的。

在方案4中,一旦服务器1和其余服务器的通讯彻底中断,那么服务器2和3会将slave切换为master。对于客户端来讲,在这么一瞬间会有2个master提供服务,而且一旦网络恢复了,那么全部在中断期间落在服务器1上的新数据都会丢失。若是想要部分解决这个问题,能够配置Redis Server进程,让其在检测到本身网络有问题的时候,当即中止服务,避免在网络故障期间还有新数据进来(能够参考Redis的min-slaves-to-write和min-slaves-max-lag这两个配置项)。

至此,咱们就用3台机器搭建了一个高可用的Redis服务。其实网上还有更加节省机器的办法,就是把一个Sentinel进程放在Client机器上,而不是服务提供方的机器上。只不过在公司里面,通常服务的提供方和调用方并不来自同一个团队。两个团队共同操做同一个机器,很容易由于沟通问题致使一些误操做,因此出于这种人为因素的考虑,咱们仍是采用了方案4的架构。而且因为服务器3上面只跑了一个Sentinel进程,对服务器资源消耗并很少,还能够用服务器3来跑一些其余的服务。

易用性:像使用单机版Redis同样使用Redis Sentinel

做为服务的提供方,咱们老是会讲到用户体验问题。在上述方案当中始终有一个让Client端用的不是那么舒服的地方。对于单机版Redis,Client端直接链接Redis Server,咱们只须要给一个ip和port,Client就可使用咱们的服务了。而改形成Sentinel模式以后,Client不得不采用一些支持Sentinel模式的外部依赖包,而且还要修改本身的Redis链接配置,这对于“矫情”的用户来说显然是不能接收的。有没有办法仍是像在使用单机版的Redis那样,只给Client一个固定的ip和port就能够提供服务呢?

答案固然是确定的。这可能就要引入虚拟IP(Virtual IP,VIP),如上图所示。咱们能够把虚拟IP指向Redis Server master所在的服务器,在发生Redis主从切换的时候,会触发一个回调脚本,回调脚本中将VIP切换至slave所在的服务器。这样对于Client端来讲,他仿佛在使用的依然是一个单机版的高可用Redis服务。

结语

搭建任何一个服务,作到“能用”实际上是很是简单的,就像咱们运行一个单机版的Redis。不过一旦要作到“高可用”,事情就会变得复杂起来。业务中使用了额外的两台服务器,3个Sentinel进程+1个Slave进程,只是为了保证在那小几率的事故中依然作到服务可用。在实际业务中咱们还启用了supervisor作进程监控,一旦进程意外退出,会自动尝试从新启动。分布式高可用架构的学习视频能够在个人群619881427免费获取。

相关文章
相关标签/搜索