一、redis cluster环境搭建:
1.了解Redis Cluster原理:
详细了解可参考:http://doc.redisfans.com/topic/cluster-tutorial.html#id5
Redis Cluster 是Redis的集群实现,内置数据自动分片机制,集群内部将所有的key映射到16384个Slot中,集群中的每个Redis Instance负责其中的一部分的Slot的读写。集群客户端连接集群中任一Redis Instance即可发送命令,当Redis Instance收到自己不负责的Slot的请求时,会将负责请求Key所在Slot的Redis Instance地址返回给客户端,客户端收到后自动将原请求重新发往这个地址,对外部透明。一个Key到底属于哪个Slot由crc16(key) % 16384 决定。
关于负载均衡,集群的Redis Instance之间可以迁移数据,以Slot为单位,但不是自动的,需要外部命令触发。
关于集群成员管理,集群的节点(Redis Instance)和节点之间两两定期交换集群内节点信息并且更新,从发送节点的角度看,这些信息包括:集群内有哪些节点,IP和PORT是什么,节点名字是什么,节点的状态(比如OK,PFAIL,FAIL等)是什么,包括节点角色(master 或者 slave)等。
关于可用性,集群由N组主从Redis Instance组成。主可以没有从,但是没有从意味着主宕机后主负责的Slot读写服务不可用。一个主可以有多个从,主宕机时,某个从会被提升为主,具体哪个从被提升为主,协议类似于Raft,参见这里。如何检测主宕机?Redis Cluster采用quorum+心跳的机制。从节点的角度看,节点会定期给其他所有的节点发送Ping,cluster-node-timeout(可配置,秒级)时间内没有收到对方的回复,则单方面认为对端节点宕机,将该节点标为PFAIL状态。通过节点之间交换信息收集到quorum个节点都认为这个节点为PFAIL,则将该节点标记为FAIL,并且将其发送给其他所有节点,其他所有节点收到后立即认为该节点宕机。从这里可以看出,主宕机后,至少cluster-node-timeout时间内该主所负责的Slot的读写服务不可用。
2.redis集群搭建(redis cluster需要ruby环境支持)
2.1 redis安装
Redis对于Linux是官方支持的,参考:http://redis.io/download。
Redis官方是不支持windows的,只是 Microsoft Open Tech group 在 GitHub上开发了一个Win64的版本,项目地址:https://github.com/MSOpenTech/redis/releases。
下载某版本下的Redis-x64-3.2.100.zip以及Source code (zip)
2.2 ruby安装(搭建redis cluster是需要ruby环境支持)
ruby下载安装http://rubyinstaller.org/downloads/
2.3 安装 RubyGEM,Redis 的 ruby 支持环境
rubygem下载地址:https://rubygems.org/pages/download
下载下来是一个压缩包,解压运行里面的 setup.rb 安装 rubyGems。
安装ruby依赖,由于墙的原因ruby自带的源有时候很慢,我们换成淘宝的源,不然下面安装redis依赖会失败
cmd下替换ruby自带的源如下:
ruby -v //查看ruby版本 gem -v //查看gem版本 gem sources //查看gem的源 gem source -l //查看gem的源 gem source -a https://ruby.taobao.org //添加淘宝ruby源 gem source -r https://rubygems.org/ //移除ruby自带源 gem install redis //安装redis依赖
2.4 使用redis cluster:
要让redis集群正常运作至少需要三个主节点,因此我们创建6个节点,三个主节点,三个从节点。
2.4.1 创建集群目录,cmd命令:
E:
cd redis
mkdir 7000 7001 7002 7003 7004 7005
将redis解压后文件夹中的redis.windows.conf以及redis-server等内容,分别拷贝到新建的六个文件夹中
2.4.2 更改配置
将六个文件夹下的redis.windows.conf文件中以下属性进行修改:
port 7001(对应文件夹的端口号) appendonly yes cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000
2.4.3 创建redis启动脚本
@echo off cd 7000 (对应文件夹,依次创建) redis-server.exe redis.windows.conf @pause
创建批量启动脚本
@echo on start 7000.bat start 7001.bat start 7002.bat start 7003.bat start 7004.bat start 7005.bat @pause
或者可以:
@echo off start Redis-Server ./7000/redis.windows.conf start Redis-Server ./7001/redis.windows.conf start Redis-Server ./7002/redis.windows.conf start Redis-Server ./7003/redis.windows.conf start Redis-Server ./7004/redis.windows.conf start Redis-Server ./7005/redis.windows.conf @pause
2.4.4 创建集群
解压Redis的Source code (zip)包,copy其中src下的redis-trib.rb(ruby编写,之前的ruby环境就是为这个准备的)到E:Redis
cmd如下:
e: cd redis redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0 .1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005
redis-trib.rb create: 这表示我们希望创建一个新的集群。 选项 --replicas 1 :表示我们希望为集群中的每个主节点创建一个从节点。 之后跟着的其他参数则是实例的地址列表, 我们希望程序使用这些地址所指示的实例来创建新集群。就是让 redis-trib 程序创建一个包含三个主节点和三个从节点的集群。
执行结果如下:
E:Redis>redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0 .1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 >>> Creating cluster >>> Performing hash slots allocation on 6 nodes... Using 3 masters: 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 Adding replica 127.0.0.1:7003 to 127.0.0.1:7000 //这里可以看到主从对应关系7003是7001的副本 Adding replica 127.0.0.1:7004 to 127.0.0.1:7001 Adding replica 127.0.0.1:7005 to 127.0.0.1:7002 M: 7247e10169405fc4b1792d5068902407e8431cc0 127.0.0.1:7000 slots:0-5460 (5461 slots) master M: ddde095bcebb14a734b124cd2bd9aac8985d5a6d 127.0.0.1:7001 slots:5461-10922 (5462 slots) master M: 8583514abc3a324099144dc8bac8f6d4c3c03bd4 127.0.0.1:7002 slots:10923-16383 (5461 slots) master S: 397cd20d54aadb0625e629647244cd556beae174 127.0.0.1:7003 replicates 7247e10169405fc4b1792d5068902407e8431cc0 S: 9869f6f33638f47da17d303ad5c497727839b120 127.0.0.1:7004 replicates ddde095bcebb14a734b124cd2bd9aac8985d5a6d S: d27177a907f27c546dda2693d9c4a723d92be6de 127.0.0.1:7005 replicates 8583514abc3a324099144dc8bac8f6d4c3c03bd4 Can I set the above configuration? (type 'yes' to accept): yes//对配置没问题就输入yes >>> Nodes configuration updated >>> Assign a different config epoch to each node >>> Sending CLUSTER MEET messages to join the cluster Waiting for the cluster to join.. >>> Performing Cluster Check (using node 127.0.0.1:7000) M: 7247e10169405fc4b1792d5068902407e8431cc0 127.0.0.1:7000 slots:0-5460 (5461 slots) master M: ddde095bcebb14a734b124cd2bd9aac8985d5a6d 127.0.0.1:7001 slots:5461-10922 (5462 slots) master M: 8583514abc3a324099144dc8bac8f6d4c3c03bd4 127.0.0.1:7002 slots:10923-16383 (5461 slots) master M: 397cd20d54aadb0625e629647244cd556beae174 127.0.0.1:7003 slots: (0 slots) master replicates 7247e10169405fc4b1792d5068902407e8431cc0 M: 9869f6f33638f47da17d303ad5c497727839b120 127.0.0.1:7004 slots: (0 slots) master replicates ddde095bcebb14a734b124cd2bd9aac8985d5a6d M: d27177a907f27c546dda2693d9c4a723d92be6de 127.0.0.1:7005 slots: (0 slots) master replicates 8583514abc3a324099144dc8bac8f6d4c3c03bd4 [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered.//这表示集群中的 16384 个槽都有至少一个主节点在处理, 集群运作正常。
现在的master是7000 7001 7002这三台机,redis会对key做 CRC16 校验和后分别存储这三台机上,没问题就输入 yes。
例如 7000 这台机 slots:0-5460 的意思是,对key 做 CRC16 校验和后 值在 0-5460范围内都会存到这台机器里。
例如 key=288 对应的CRC16校验和 为 4258,应该存在7000这台机里
PS:使用前应该对业务做梳理,根据系统中key的特点来调整各个机器的slots范围, 不然系统的key基本集中在一两台机器上集群的效果就不大了。
至此,windows下redis cluster搭建成功。
2.4.5 在redis-cli下测试一下:
D:Redis>redis-cli -c -p 7001 127.0.0.1:7001> set lcawen hello OK 127.0.0.1:7001> set hello world -> Redirected to slot [866] located at 127.0.0.1:7000 OK 127.0.0.1:7000> get lcawen -> Redirected to slot [10428] located at 127.0.0.1:7001 "hello" 127.0.0.1:7001> get hello -> Redirected to slot [866] located at 127.0.0.1:7000 "world" 127.0.0.1:7000> keys * 1) "hello" 127.0.0.1:7000> get lcawen -> Redirected to slot [10428] located at 127.0.0.1:7001 "hello" 127.0.0.1:7001> keys * 1) "lcawen"
附Redis配置文件详解:
# 守护进程模式 daemonize yes # pid file pidfile /var/run/redis.pid # 监听端口 port 7003 # TCP接收队列长度,受/proc/sys/net/core/somaxconn和tcp_max_syn_backlog这两个内核参数的影响 tcp-backlog 511 # 一个客户端空闲多少秒后关闭连接(0代表禁用,永不关闭) timeout 0 # 如果非零,则设置SO_KEEPALIVE选项来向空闲连接的客户端发送ACK tcp-keepalive 60 # 指定服务器调试等级 # 可能值: # debug (大量信息,对开发/测试有用) # verbose (很多精简的有用信息,但是不像debug等级那么多) # notice (适量的信息,基本上是你生产环境中需要的) # warning (只有很重要/严重的信息会记录下来) loglevel notice # 指明日志文件名 logfile "./redis7003.log" # 设置数据库个数 databases 16 # 会在指定秒数和数据变化次数之后把数据库写到磁盘上 # 900秒(15分钟)之后,且至少1次变更 # 300秒(5分钟)之后,且至少10次变更 # 60秒之后,且至少10000次变更 save 900 1 save 300 10 save 60 10000 # 默认如果开启RDB快照(至少一条save指令)并且最新的后台保存失败,Redis将会停止接受写操作 # 这将使用户知道数据没有正确的持久化到硬盘,否则可能没人注意到并且造成一些灾难 stop-writes-on-bgsave-error yes # 当导出到 .rdb 数据库时是否用LZF压缩字符串对象 rdbcompression yes # 版本5的RDB有一个CRC64算法的校验和放在了文件的最后。这将使文件格式更加可靠。 rdbchecksum yes # 持久化数据库的文件名 dbfilename dump.rdb # 工作目录 dir ./ # 当master服务设置了密码保护时,slav服务连接master的密码 masterauth 0234kz9*l # 当一个slave失去和master的连接,或者同步正在进行中,slave的行为可以有两种: # # 1) 如果 slave-serve-stale-data 设置为 "yes" (默认值),slave会继续响应客户端请求, # 可能是正常数据,或者是过时了的数据,也可能是还没获得值的空数据。 # 2) 如果 slave-serve-stale-data 设置为 "no",slave会回复"正在从master同步 # (SYNC with master in progress)"来处理各种请求,除了 INFO 和 SLAVEOF 命令。 slave-serve-stale-data yes # 你可以配置salve实例是否接受写操作。可写的slave实例可能对存储临时数据比较有用(因为写入salve # 的数据在同master同步之后将很容易被删除 slave-read-only yes # 是否在slave套接字发送SYNC之后禁用 TCP_NODELAY? # 如果你选择“yes”Redis将使用更少的TCP包和带宽来向slaves发送数据。但是这将使数据传输到slave # 上有延迟,Linux内核的默认配置会达到40毫秒 # 如果你选择了 "no" 数据传输到salve的延迟将会减少但要使用更多的带宽 repl-disable-tcp-nodelay no # slave的优先级是一个整数展示在Redis的Info输出中。如果master不再正常工作了,哨兵将用它来 # 选择一个slave提升=升为master。 # 优先级数字小的salve会优先考虑提升为master,所以例如有三个slave优先级分别为10,100,25, # 哨兵将挑选优先级最小数字为10的slave。 # 0作为一个特殊的优先级,标识这个slave不能作为master,所以一个优先级为0的slave永远不会被 # 哨兵挑选提升为master slave-priority 100 # 密码验证 # 警告:因为Redis太快了,所以外面的人可以尝试每秒150k的密码来试图破解密码。这意味着你需要 # 一个高强度的密码,否则破解太容易了 requirepass 0234kz9*l # redis实例最大占用内存,不要用比设置的上限更多的内存。一旦内存使用达到上限,Redis会根据选定的回收策略(参见: # maxmemmory-policy)删除key maxmemory 3gb # 最大内存策略:如果达到内存限制了,Redis如何选择删除key。你可以在下面五个行为里选: # volatile-lru -> 根据LRU算法删除带有过期时间的key。 # allkeys-lru -> 根据LRU算法删除任何key。 # volatile-random -> 根据过期设置来随机删除key, 具备过期时间的key。 # allkeys->random -> 无差别随机删, 任何一个key。 # volatile-ttl -> 根据最近过期时间来删除(辅以TTL), 这是对于有过期时间的key # noeviction -> 谁也不删,直接在写操作时返回错误。 maxmemory-policy volatile-lru # 默认情况下,Redis是异步的把数据导出到磁盘上。这种模式在很多应用里已经足够好,但Redis进程 # 出问题或断电时可能造成一段时间的写操作丢失(这取决于配置的save指令)。 # # AOF是一种提供了更可靠的替代持久化模式,例如使用默认的数据写入文件策略(参见后面的配置) # 在遇到像服务器断电或单写情况下Redis自身进程出问题但操作系统仍正常运行等突发事件时,Redis # 能只丢失1秒的写操作。 # # AOF和RDB持久化能同时启动并且不会有问题。 # 如果AOF开启,那么在启动时Redis将加载AOF文件,它更能保证数据的可靠性。 appendonly no # aof文件名 appendfilename "appendonly.aof" # fsync() 系统调用告诉操作系统把数据写到磁盘上,而不是等更多的数据进入输出缓冲区。 # 有些操作系统会真的把数据马上刷到磁盘上;有些则会尽快去尝试这么做。 # # Redis支持三种不同的模式: # # no:不要立刻刷,只有在操作系统需要刷的时候再刷。比较快。 # always:每次写操作都立刻写入到aof文件。慢,但是最安全。 # everysec:每秒写一次。折中方案。 appendfsync everysec # 如果AOF的同步策略设置成 "always" 或者 "everysec",并且后台的存储进程(后台存储或写入AOF # 日志)会产生很多磁盘I/O开销。某些Linux的配置下会使Redis因为 fsync()系统调用而阻塞很久。 # 注意,目前对这个情况还没有完美修正,甚至不同线程的 fsync() 会阻塞我们同步的write(2)调用。 # # 为了缓解这个问题,可以用下面这个选项。它可以在 BGSAVE 或 BGREWRITEAOF 处理时阻止主进程进行fsync()。 # # 这就意味着如果有子进程在进行保存操作,那么Redis就处于"不可同步"的状态。 # 这实际上是说,在最差的情况下可能会丢掉30秒钟的日志数据。(默认Linux设定) # # 如果你有延时问题把这个设置成"yes",否则就保持"no",这是保存持久数据的最安全的方式。 no-appendfsync-on-rewrite yes # 自动重写AOF文件 auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb # AOF文件可能在尾部是不完整的(这跟system关闭有问题,尤其是mount ext4文件系统时 # 没有加上data=ordered选项。只会发生在os死时,redis自己死不会不完整)。 # 那redis重启时load进内存的时候就有问题了。 # 发生的时候,可以选择redis启动报错,并且通知用户和写日志,或者load尽量多正常的数据。 # 如果aof-load-truncated是yes,会自动发布一个log给客户端然后load(默认)。 # 如果是no,用户必须手动redis-check-aof修复AOF文件才可以。 # 注意,如果在读取的过程中,发现这个aof是损坏的,服务器也是会退出的, # 这个选项仅仅用于当服务器尝试读取更多的数据但又找不到相应的数据时。 aof-load-truncated yes # Lua 脚本的最大执行时间,毫秒为单位 lua-time-limit 5000 # Redis慢查询日志可以记录超过指定时间的查询 slowlog-log-slower-than 10000 # 这个长度没有限制。只是要主要会消耗内存。你可以通过 SLOWLOG RESET 来回收内存。 slowlog-max-len 128 # redis延时监控系统在运行时会采样一些操作,以便收集可能导致延时的数据根源。 # 通过 LATENCY命令 可以打印一些图样和获取一些报告,方便监控 # 这个系统仅仅记录那个执行时间大于或等于预定时间(毫秒)的操作, # 这个预定时间是通过latency-monitor-threshold配置来指定的, # 当设置为0时,这个监控系统处于停止状态 latency-monitor-threshold 0 # Redis能通知 Pub/Sub 客户端关于键空间发生的事件,默认关闭 notify-keyspace-events "" # 当hash只有少量的entry时,并且最大的entry所占空间没有超过指定的限制时,会用一种节省内存的 # 数据结构来编码。可以通过下面的指令来设定限制 hash-max-ziplist-entries 512 hash-max-ziplist-value 64 # 与hash似,数据元素较少的list,可以用另一种方式来编码从而节省大量空间。 # 这种特殊的方式只有在符合下面限制时才可以用 list-max-ziplist-entries 512 list-max-ziplist-value 64 # set有一种特殊编码的情况:当set数据全是十进制64位有符号整型数字构成的字符串时。 # 下面这个配置项就是用来设置set使用这种编码来节省内存的最大长度。 set-max-intset-entries 512 # 与hash和list相似,有序集合也可以用一种特别的编码方式来节省大量空间。 # 这种编码只适合长度和元素都小于下面限制的有序集合 zset-max-ziplist-entries 128 zset-max-ziplist-value 64 # HyperLogLog稀疏结构表示字节的限制。该限制包括 # 16个字节的头。当HyperLogLog使用稀疏结构表示 # 这些限制,它会被转换成密度表示。 # 值大于16000是完全没用的,因为在该点 # 密集的表示是更多的内存效率。 # 建议值是3000左右,以便具有的内存好处, 减少内存的消耗 hll-sparse-max-bytes 3000 # 启用哈希刷新,每100个CPU毫秒会拿出1个毫秒来刷新Redis的主哈希表(顶级键值映射表) activerehashing yes # 客户端的输出缓冲区的限制,可用于强制断开那些因为某种原因从服务器读取数据的速度不够快的客户端 client-output-buffer-limit normal 0 0 0 client-output-buffer-limit slave 256mb 64mb 60 client-output-buffer-limit pubsub 32mb 8mb 60 # 默认情况下,“hz”的被设定为10。提高该值将在Redis空闲时使用更多的CPU时,但同时当有多个key # 同时到期会使Redis的反应更灵敏,以及超时可以更精确地处理 hz 10 # 当一个子进程重写AOF文件时,如果启用下面的选项,则文件每生成32M数据会被同步 aof-rewrite-incremental-fsync yes
3.Redis容灾部署(哨兵Sentinel)
哨兵的作用
1. 监控:监控主从是否正常
2. 通知:出现问题时,可以通知相关人员
3. 故障迁移:自动主从切换
4. 统一的配置管理:连接者询问sentinel取得主从的地址
Raft分布式算法
1. 主要用途:用于分布式系统,系统容错,以及选出领头羊
2. 作者:Diego Ongaro,毕业于哈佛
3. 目前用到这个算法的项目有:
a. CoreOS : 见下面
b. ectd : a distributed, consistent shared configuration
c. LogCabin : 分布式存储系统
d. redis sentinel : redis 的监控系统
Sentinel使用的Raft算法核心: 原则
1. 所有sentinel都有选举的领头羊的权利
2. 每个sentinel都会要求其他sentinel选举自己为领头羊(主要由发现redis客观下线的sentinel先发起选举)
3. 每个sentinel只有一次选举的机会
4. 采用先到先得的原则
5. 一旦加入到系统了,则不会自动清除(这一点很重要, why?)
6. 每个sentinel都有唯一的uid,不会因为重启而变更
7. 达到领头羊的条件是 N/2 + 1个sentinel选择了自己
8. 采用配置纪元,如果一次选举出现脑裂,则配置纪元会递增,进入下一次选举,所有sentinel都会处于统一配置纪元,以最新的为标准。
Raft分布式算法的应用
coreos:云计算新星 Docker 正在以火箭般的速度发展,与它相关的生态圈也渐入佳境,CoreOS 就是其中之一。CoreOS 是一个全新的、面向数据中心设计的 Linux 操作系统,在2014年7月发布了首个稳定版本,目前已经完成了800万美元的A轮融资。
Sentinel实现Redis容灾部署,Sentinal配置:
# Example sentinel.conf # port <sentinel-port> port 26371 # 守护进程模式 daemonize yes # 指明日志文件名 logfile "./sentinel.log" # 工作路径,sentinel一般指定/tmp比较简单 dir ./ # 哨兵监控这个master,在至少quorum个哨兵实例都认为master down后把master标记为odown # (objective down客观down;相对应的存在sdown,subjective down,主观down)状态。 # slaves是自动发现,所以你没必要明确指定slaves。 sentinel monitor TestMaster 127.0.0.1 7003 1 # master或slave多长时间(默认30秒)不能使用后标记为s_down状态。 sentinel down-after-milliseconds TestMaster 1500 # 若sentinel在该配置值内未能完成failover操作(即故障时master/slave自动切换),则认为本次failover失败。 sentinel failover-timeout TestMaster 10000 # 设置master和slaves验证密码 sentinel auth-pass TestMaster 0234kz9*l sentinel config-epoch TestMaster 15 sentinel leader-epoch TestMaster 8394 # #除了当前哨兵, 还有哪些在监控这个master的哨兵,即其他的哨兵 sentinel known-sentinel TestMaster 127.0.0.1 26372 0aca3a57038e2907c8a07be2b3c0d15171e44da5 sentinel known-sentinel TestMaster 127.0.0.1 26373 ac1ef015411583d4b9f3d81cee830060b2f29862 sentinel current-epoch 8394