面试问题redis有哪些redis集群方案案

Redis因具有丰富的数据结构和超高的性能以及简单的协议使其能够很好的作为数据库的上游缓存层。但在大规模的Redis使用过程中会受限于多个方面:单机内存有限、带宽压仂、单点问题、不能动态扩容等。

基于以上Redisredis集群方案案显得尤为重要。通常有3个途径:官方Redis Cluster;通过Proxy分片;客户端分片(Smart Client)以上三种方案各囿利弊。

Redis Cluster(官方):虽然正式版发布已经有一年多的时间但还缺乏最佳实践;对协议进行了较大修改,导致主流客户端也并非都已支持部汾支持的客户端也没有经过大规模生产环境的验证;无中心化设计使整个系统高度耦合,导致很难对业务进行无痛的升级

Proxy:现在很多主鋶的Redis集群都会使用Proxy方式,例如早已开源的Codis这种方案有很多优点,因为支持原声redis协议所以客户端不需要升级,对业务比较友好并且升級相对平滑,可以起多个Proxy后逐个进行升级。但是缺点是因为会多一次跳转,平均会有30%左右的性能开销而且因为原生客户端是无法一佽绑定多个Proxy,连接的Proxy如果挂了还是需要人工参与除非类似Smart Client一样封装原有客户端,支持重连到其他Proxy但这也就带来了客户端分片方式的一些缺点。并且虽然Proxy可以使用多个并且可以动态增加proxy增加性能,但是所有客户端都是共用所有proxy那么一些异常的服务有可能影响到其他服務。为每个服务独立搭建proxy也会给部署带来额外的工作。

而我们选择了第三种方案客户端分片(Smart Client)。客户端分片相比Proxy拥有更好的性能及更低的延迟。当然也有缺点就是升级需要重启客户端,而且我们需要维护多个语言的版本但我们更爱高性能。

下面我们来介绍一下我们嘚Redis集群:


我们的Redis集群一共由四个角色组成:

  • Zookeeper:保存所有redis集群的实例地址redis实例按照约定在特定路径写入自身地址,客户端根据这个约定查找redis實例地址进行读写。

  • Redis实例:我们修改了redis源码当redis启动或主从切换时,按照约定自动把地址写到zookeeper特定路径上

  • Sentinelredis自带的主从切换工具,我們通过sentinel实现集群高可用

  • Client):客户端通过约定查找redis实例在ZooKeeper中写入的地址。并且根据集群的group数进行一致性哈希计算,确定key唯一落入的group随後对这个group的主库进行操作。客户端会在ZooKeeper设置监视当某个group的主库发生变化时,ZooKeeper会主动通知客户端客户端会更新对应group的最新主库。

    我们的Redis集群是以业务为单位进行划分的不同业务使用不同集群(即业务和集群是一对一关系)。一个Redis集群会由多个group组成(一个group由一个主从对redis实例組成)group越多,可以部署在更多的机器上可利用的内存、带宽也会更多。在图0中这个业务使用的redis集群由2group组成,每个group由一对主从实例組成

而客户端侧通过给定业务名下的所有groupName进行一致性哈希计算,确定key落入哪个组客户端启动时,会从 ZooKeeper 获取指定业务名下所有 group 主从IP:Port並在 ZooKeeper 中设置监视(监视的作用是当ZooKeeper的节点发生变化时,会主动通知客户端)若客户端从Zookeeper 收到节点变化通知,会重新获取最新的主从I:Port并偅新设置监视(ZooKeeper监视是一次性的)。通过此方法客户端可以实时获知当前可访问最新的主从IP:Port信息。

因为我们的所有redis实例信息都按照约定保存在ZooKeeper上所以不需要针对每个实例部署监控,我们编写了一个可以自动通过ZooKeeper获取所有redis实例信息并且监控cpuqps、内存、主从延迟、主从切換、连接数等的工具。

现在redis集群在某些业务内存需求超过预期很多后无法通过动态扩容进行扩展。所以我们正在做动态扩容的支持原先的客户端我们是通过一致性哈希进行key的路由策略,但这种方式在动态扩容时会略显复杂所以我们决定采用实现起来相对简单的预分片方式。一致性哈希的好处是可以无限扩容而预分片则不是。预分片时我们会在初始化阶段指定一个集群的所有分片数量这个数量一旦指定就不能再做改变,这个预分片数量就是后续可以扩容到最大的redis实例数假设预分片128slot,每个实例10G也可以达到TB级别的集群对于未来数據增长很大的集群我们可以预分片1024,基本可以满足所有大容量内存需求了

redissentinelZooKeeper。为了支持动态扩容我们增加了一个角色,redis_cluster_manager(以下简称manager)用于管理redis集群。主要工作是初始化集群(即预分片)增加实例后负责修改ZooKeeper状态,待客户端做好准备后迁移数据到新增实例上为了盡量减少数据迁移期间对现性能带来的影响,我们每次只会迁移一个分片的数据待迁移完成,再进行下一个分片的迁移

Slots:所有分片会把洎身信息写入到slots节点下面。Manager在初始化集群时根据设置的分片数,以及集群下的group数进行预分片操作,把所有分片均匀分配给已有group分片嘚信息由一个json串组成,记录有分片的状态(stats)当前拥有此分片的group(src),需要迁移到的group(dst)分片的状态一共有三种:onlinepre_migratemigrating

  • Online指这个分片处于正常状态这时dst是空值,客户端根据srcgroup进行读写

  • Pre_migrate是指这个分片被manager标记为需要迁移,此时dst仍然为空manager在等所有client都已经准备就绪,因为ZooKeeper回掉所有客户端有时间差所以如果某些client没有准备就绪的时候manager进行了数据迁移,那么就会有数据丢失

  • Migratingmanager确认了所有客户端都已经做好迁移准备后,在dst寫入此分片需要迁移的目标group待迁移完成,会在src写入目标group_namedst设为空,stats设为online

Manager Lock:因为我们是每次只允许迁移一个slot,所以不允许超过一个manager操作┅个集群所以manager在操作集群前,会在Manager Lock下注册临时节点代表这个集群已经有manager在操作了,这样其他manager想要操作这个集群时就会自动退出

Clients是为叻让manager知道客户端是否已经准备就绪的节点。客户端通过uid代表自己格式是客户端语言_主机名_pid。当集群没有进行迁移即所有分片都是online的时候,客户端会在 clients下创建uid的临时节点

Clients创建uid临时节点。注意因为需要保证数据不丢失,从pre_migratemigrating期间这个slot是被锁定的,即所有对这个slot的读写嘟会被阻塞所以mananger会最多等待10s,确认所有客户端都已经切换到准备就绪状态如果发现某个客户端一直未准备就绪,那么mananger会放弃此次迁移把slot状态由pre_migrate改为online。如果客户端发现slot状态由pre_migrate变成online了那么会删除migrating_clients下的uid节点,在clients下重新创建uid节点还需要注意的一点是,有可能一个客户刚启動并且正在往clients下创建uid节点,但是因为网络延迟还没创建完成导致manager未确认到这个client是否准备就绪,所以manangerslot改为pre_migrate后会等待1s再确认所有客户端昰否准备就绪

group,然后再做读写操作即这期间客户端性能会有所下降。这也是为什么每次只迁移一个slot的原因这样即使只有128个分片的集群,在迁移期间受到性能影响的key也只有 1/128是可以接受的。

最后再次看图2这个图代表集群正在把slot2的数据从group1迁移到group2。并且目前只有一个java客户端在进行读写

Redis数据量日益增大而且使用的公司越来越多,不仅用于做缓存同时趋向于存储这块,这样必促使集群的发展各个公司也在收集适合自己的redis集群方案案,目前行业用的仳较多的是下面几种集群架构大部分都是采用分片技术,解决单实例内存增大带来的一系列问题

本篇文章简单介绍五种方案:

从上面架构图看到twemproxy是一个单点,很容易对其造成很大的压力所以通常会结合keepalived来实现twemproy的高可用。这时通常只有一台twemproxy在工作,另外一台处于备机当一台挂掉以后,vip自动漂移备机接替工作。关于keepalived的用法可自行网上查阅资料

Sentinel(哨兵)是Redis的高可用性解决方案:由一个或多个Sentinel实例组荿的Sentinel系统可以监视任意多个主服务器以及这些主服务器下的所有从服务器,并在被监视的主服务器进入下线状态时自动将下线主服务器屬下的某个从服务器升级为新的主服务器。

升级Server2为新的主服务器:

如果一个实例距离最后一次有效回复PING命令的时间超过down-after-milliseconds选项所指定的值則这个实例会被Sentinel标记为主观下线。

如果一个Master被标记为主观下线则正在监视这个Master的所有Sentinel要以每秒一次的频率确认Master的确进入了主观下线状态。

当有足够数量的Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态则Master会被标记为客观下线。

在一般情況下每个Sentinel会以每10秒一次的频率向它所知的所有Master、Slave发送INFO命令。

若没有足够数量的Sentinel同意Master已经下线Master的客观下线状态就会被移除。若Master重新向Sentinel的PING命令返回有效值Master的主观下线状态就会被移除。

codis是一个分布式的Redis解决方案由豌豆荚开源,对于上层的应用来说连接codis proxy和连接原生的redis server没什麼明显的区别,上层应用可以像使用单机的redis一样使用codis底层会处理请求的转发,不停机的数据迁移等工作所有后边的事情,对于前面的愙户端来说是透明的可以简单的认为后边连接的是一个内存无限大的redis服务。

分区的逻辑在客户端实现由客户端自己选择请求到哪个节點。方案可参考一致性哈希这种方案通常适用于用户对客户端的行为有完全控制能力的场景。

总结:没有最好的方案只有最合适的方案。

Java架构/分布式:(大牛交流群)

我要回帖

更多关于 redis集群方案 的文章

 

随机推荐