主要看的这篇文章 http://mt.sohu.com/20160523/n451048025.shtml
edis Partitioning即Redis分区,简单的说就是将数据分布到不同的redis实例中,因此对于每个redis实例所存储的内容仅仅是所有内容的一个子集。
分区(Partitioning)不仅仅是Redis的概念,几乎是所有数据存储系统都会涉及到的概念,在理解分区基本概念的基础之上进一步讨论Redis对分区的支持。
主要讨论两方面:分区(Partitioning)和多实例(Pre-sharding)
Redis分区基础
范围分区
最简单的一种方式是范围分区(range partitioning)
就是将一个范围内的key都映射到同一个Redis实例中,不过还是有问题:
如果我们想要存储的数据的key并不能按照范围划分怎么办。所以需要以下的哈希分区。
哈希分区
但是需要在增删节点时,需要能做到一致性哈希分区(查看这篇文章:link)
实现1-客户端实现
实现2-代理实现
实现3-查询路由
查询路由是Redis Cluster实现的一种Redis分区方式:
可以将查询请求随机的发送到任意一个Redis实例,这个Redis实例负责将请求转发至正确的Redis实例中。Redis集群实现了一个通过和客户端协作的hybrid来做查询路由。
致命的缺点
多键操作是不被支持的,比如我们将要批量操作的键被映射到了不同的Redis实例中。
多键的Redis事务是不被支持的。
分区的最小粒度是键,因此我们不能将关联到一个键的很大的数据集映射到不同的实例。
当应用分区的时候,数据的处理是非常复杂的,比如我们需要处理多个rdb/aof文件,将分布在不同实例的文件聚集到一起备份。
添加和删除机器是很复杂的。其中Redis集群支持几乎运行时透明的因为增加或减少机器而需要做的rebalancing;然而像客户端和代理分区这种方式是不支持这种功能的。
持久存储用还是缓存
这是必须要考虑的问题,影响很多方案的决策。
Consistent hashing实现通常使得当一个key被映射到的实例不能用的时候将这个key映射到其他实例成为可能。类似,如果增加了一台机器,一部分的key将会被映射到这台新的机器上,我们需要了解的两点如下:
如果Redis被用来当做缓存,且要求容易增加或删除机器,使用consistent hashing是非常简单的。
如果Redis被用来当做(持久)存储,一个固定的key到实例的映射是需要的,因此我们不能够再灵活的添加或删除机器。否则,我们需要在增加或删除机器的时候系统能够rebalace,当前Redis Cluster已经支持。
(上面两句没有理解透彻)
Pre-Sharding
除非我们只是使用Redis当做缓存,否则对于增加机器或删除机器是非常麻烦的。
然而,通常我们Redis容量变动在实际应用中是非常常见的,比如今天我需要10台Redis机器,明天可能就需要50台机器了。
对于上面的问题一种简单的解决办法是 Pre-Sharding(其实是Redis作者提出的):
Redis是很轻量级的服务(每个实例仅仅占用1M)。 我们可以开启多个Redis实例,尽管是一台物理机器,我们在刚开始的时候也可以开启多个实例。我们可以从中选择一些实例,比如32或64个实例来作为我们的工作集群。
当一台物理机器存储不够的时候,我们可以将一般的实例移动到我们的第二台物理机上,
依次类对,我们可以保证集群中Redis的实例数不变,又可以达到扩充机器的目的。
将Redis实例移动到独立的机器上的时候,我们可以通过下面步骤实现:
在新的物理机上启动一个新的Redis实例。 将新的物理机作为要移动的那台的slave机器。 停止客户端。 更新将要被移动的那台Redis实例的IP地址。 对于slave机器发送SLAVEOF ON ONE命令。 使用新的IP启动Redis客户端。 关闭不再使用的那个Redis实例。
利用『 SLAVEOF NO ONE 不会丢弃同步所得数据集』这个特性,可以在主服务器失败的时候,将从属服务器用作新的主服务器,从而实现无间断运行。
由于Pre-sharding是Redis作者提出的,通常流程是:
Redis的作者提出了一种名为Pre-Sharding的方式: Pre-Sharding方法是将每一个台物理机上,运行多个不同断口的Redis实例,假如有三个物理机,每个物理机运行三个Redis实际,
那么我们的分片列表中实际有9个Redis实例,当我们需要扩容时,增加一台物理机,步骤如下: A. 在新的物理机上运行Redis-Server; B. 该Redis-Server从属于(slaveof)分片列表中的某一Redis-Server(假设叫RedisA); C. 等主从复制(Replication)完成后,将客户端分片列表中RedisA的IP和端口改为新物理机上Redis-Server的IP和端口; D. 停止RedisA。 这样相当于将某一Redis-Server转移到了一台新机器上。Prd-Sharding实际上是一种在线扩容的办法,但还是很依赖Redis本身的复制功能的,
如果主库快照数据文件过大,这个复制的过程也会很久,同时会给主库带来压力。所以做这个拆分的过程最好选择为业务访问低峰时段进行。
微博的这篇文章对于分布式Redis讲的很好,比上面的深入很多:
http://blog.nosqlfan.com/html/3153.html
《Redis复制与可扩展集群搭建》
Redis的主从复制策略是通过其持久化的rdb文件来实现的,其过程是先dump出rdb文件,将rdb文件全量传输给slave,然后再将dump后的操作实时同步到slave中。
Redis复制流程概述
Redis的复制功能是完全建立在之前我们讨论过的基于内存快照的持久化策略基础上的,也就是说无论你的持久化策略选择的是什么,只要用到了Redis的复制功能,就一定会有内存快照发生,那么首先要注意你的系统内存容量规划。
Redis复制流程在Slave和Master端各自是一套状态机流转,涉及的状态信息是:
Slave 端:
REDIS_REPL_NONE
REDIS_REPL_CONNECT
REDIS_REPL_CONNECTED
Master端:
REDIS_REPL_WAIT_BGSAVE_START
REDIS_REPL_WAIT_BGSAVE_END
REDIS_REPL_SEND_BULK
REDIS_REPL_ONLINE
整个状态机流程过程如下:
- 1.Slave端在配置文件中添加了slave of指令,于是Slave启动时读取配置文件,初始状态为REDIS_REPL_CONNECT。
- 2.Slave端在定时任务serverCron(Redis内部的定时器触发事件)中连接Master,发送sync命令,然后阻塞等待master发送回其内存快照文件(最新版的Redis已经不需要让Slave阻塞)。
- 3.Master端收到sync命令简单判断是否有正在进行的内存快照子进程,没有则立即开始内存快照,有则等待其结束,当快照完成后会将该文件发送给Slave端。
- 4.Slave端接收Master发来的内存快照文件,保存到本地,待接收完成后,清空内存表,重新读取Master发来的内存快照文件,重建整个内存表数据结构,并最终状态置位为 REDIS_REPL_CONNECTED状态,Slave状态机流转完成。
- 5.Master端在发送快照文件过程中,接收的任何会改变数据集的命令都会暂时先保存在Slave网络连接的发送缓存队列里(list数据结构),待快照完成后,依次发给Slave,之后收到的命令相同处理,并将状态置位为 REDIS_REPL_ONLINE。
整个复制过程完成,流程如下图所示:
Redis复制机制的缺陷
没有象MySQL那样有复制位置的概念,即无增量复制,这会给整个集群搭建带来非常多的问题。
比如一台线上正在运行的Master主库配置了一台从库进行简单读写分离,这时Slave由于网络或者其它原因与Master断开了连接,
那么当Slave进行重新连接时,需要重新获取整个Master的内存快照,Slave所有数据跟着全部清除,然后重新建立整个内存表,
一方面Slave恢复的时间会非常慢,另一方面也会给主库带来压力。 所以基于上述原因,如果你的Redis集群需要主从复制,那么最好事先配置好所有的从库,避免中途再去增加从库。
Cache还是Storage
实际上Redis目前发布的版本还都是一个单机版的思路,主要的问题集中在,持久化方式不够成熟,复制机制存在比较大的缺陷。
这时我们又开始重新思考Redis的定位:Cache还是Storage?
如果作为Cache的话,似乎除了有些非常特殊的业务场景,必须要使用Redis的某种数据结构之外,可以用其他缓存服务比如memcached。 如果是作为存储Storage的话,我们面临的最大的问题是无论是持久化还是复制都没有办法解决Redis单点问题,即一台Redis挂掉了,没有太好的办法能够快速的恢复,
通常几十G的持久化数据,Redis重启加载需要几个小时的时间,而复制又有缺陷,如何解决呢?
Redis可扩展集群搭建
1. 主动复制避开Redis复制缺陷。
既然Redis的复制功能有缺陷,那么我们不妨放弃Redis本身提供的复制功能,我们可以采用主动复制的方式来搭建我们的集群环境。 所谓主动复制是指由业务端或者通过代理中间件对Redis存储的数据进行双写或多写,通过数据的多份存储来达到与复制相同的目的,
主动复制不仅限于用在Redis集群上,目前很多公司采用主动复制的技术来解决MySQL主从之间复制的延迟问题,
比如Twitter还专门开发了用于复制和分区的中间件gizzard(https://github.com/twitter/gizzard) 。 主动复制虽然解决了被动复制的延迟问题,但也带来了新的问题,就是数据的一致性问题,数据写2次或多次,如何保证多份数据的一致性呢?
如果你的应用对数据一致性要求不高,允许最终一致性的话,
那么通常简单的解决方案是可以通过时间戳或者vector clock等方式,让客户端同时取到多份数据并进行校验,
如果你的应用对数据一致性要求非常高,那么就需要引入一些复杂的一致性算法比如Paxos来保证数据的一致性,但是写入性能也会相应下降很多。
(Paxos算法准备再开一篇文章来研究) 通过主动复制,数据多份存储我们也就不再担心Redis单点故障的问题了,如果一组Redis集群挂掉,我们可以让业务快速切换到另一组Redis上,降低业务风险。
2. 通过presharding进行Redis在线扩容。
通过主动复制我们解决了Redis单点故障问题,那么还有一个重要的问题需要解决:容量规划与在线扩容问题。 我们前面分析过Redis的适用场景是全部数据存储在内存中,而内存容量有限,那么首先需要根据业务数据量进行初步的容量规划,
比如你的业务数据需要100G存储空间,假设服务器内存是48G,那么根据上一篇我们讨论的Redis磁盘IO的问题,我们大约需要3~4台服务器来存储。
这个实际是对现有业务情况所做的一个容量规划,假如业务增长很快,很快就会发现当前的容量已经不够了,Redis里面存储的数据很快就会超过物理内存大小,
那么如何进行Redis的在线扩容呢? Redis的作者提出了一种叫做presharding的方案来解决动态扩容和数据分区的问题,
实际就是在同一台机器上部署多个Redis实例的方式,当容量不够时将多个实例拆分到不同的机器上,这样实际就达到了扩容的效果。
拆分过程如下:
.在新机器上启动好对应端口的Redis实例。
.配置新端口为待迁移端口的从库。
.待复制完成,与主库完成同步后,切换所有客户端配置到新的从库的端口。
.配置从库为新的主库。
.移除老的端口实例。
.重复上述过程迁移好所有的端口到指定服务器上。
以上拆分流程是Redis作者提出的一个平滑迁移的过程,不过该拆分方法还是很依赖Redis本身的复制功能的,
如果主库快照数据文件过大,这个复制的过程也会很久,同时会给主库带来压力。所以做这个拆分的过程最好选择为业务访问低峰时段进行。
Redis复制的改进思路
线上的系统使用了我们自己改进版的Redis,主要解决了Redis没有增量复制的缺陷,能够完成类似Mysql Binlog那样可以通过从库请求日志位置进行增量复制。 持久化方案是首先写Redis的AOF文件,并对这个AOF文件按文件大小进行自动分割滚动,同时关闭Redis的Rewrite命令,
然后会在业务低峰时间进行内存快照存储,并把当前的AOF文件位置一起写入到快照文件中,这样我们可以使快照文件与AOF文件的位置保持一致性,
这样我们得到了系统某一时刻的内存快照,并且同时也能知道这一时刻对应的AOF文件的位置,那么当从库发送同步命令时,
我们首先会把快照文件发送给从库,然后从库会取出该快照文件中存储的AOF文件位置,并将该位置发给主库,主库会随后发送该位置之后的所有命令,
以后的复制就都是这个位置之后的增量信息了。 注:我感觉,就是使用了Aof机制做了一些折中,没什么太大的意思。
Redis与MySQL的结合
目前大部分互联网公司使用MySQL作为数据的主要持久化存储,那么如何让Redis与MySQL很好的结合在一起呢?
我们主要使用了一种基于MySQL作为主库,Redis作为高速数据查询从库的异构读写分离的方案。
为此我们专门开发了自己的MySQL复制工具,可以方便的实时同步MySQL中的数据到Redis上。
总结:
- 1.Redis的复制功能没有增量复制,每次重连都会把主库整个内存快照发给从库,所以需要避免向在线服务的压力较大的主库上增加从库。
- 2.Redis的复制由于会使用快照持久化方式,所以如果你的Redis持久化方式选择的是日志追加方式(aof),那么系统有可能在同一时刻既做aof日志文件的同步刷写磁盘,又做快照写磁盘操作,这个时候Redis的响应能力会受到影响。所以如果选用aof持久化,则加从库需要更加谨慎。
- 3.可以使用主动复制和presharding方法进行Redis集群搭建与在线扩容。
完。