Loading... # redis集群 ## 为什么要集群 redis是单机、单节点、单实例的,这样就会出现以下问题 1. 单点故障 2. 容量有限 3. I/O和计算的压力大 ## AKF扩展立方体 **AKF扩展立方体(Scalability Cube)**,是《架构即未来》一书中提出的可扩展模型,这个立方体有三个轴线,每个轴线描述扩展性的一个维度,他们分别是产品、流程和团队: X轴 —— 代表无差别的克隆服务和数据,工作可以很均匀的分散在不同的服务实例上; Y轴 —— 关注应用中职责的划分,比如数据类型,交易执行类型的划分; Z轴 —— 关注服务和数据的优先级划分,如分地域划分。 x轴为全量镜像,解决单点故障和部分I/O及计算压力,但是不能解决容量问题,y轴是按照业务功能的不同再进行拆分,类似微服务。z轴是按照优先级逻辑再拆分,比如有1000个用户,通过ID区分,前500个存redis1,后500个存redis2。 ## 主从复制 主从复制是X轴扩展,数据做副本,每个redis都有所有的数据,只要有一个存活,就可以对外提供服务。 节点变多就会出现数据一致性问题,如何解决数据一致性问题? 1、同步阻塞 ![01.png][1] 该方式客户端向某个redis实例写入时,该实例会阻塞,并等待其它实例同步数据,只有所有实例都返回ok了,该实例才会给客户端返回ok。这种方式属于强一致性,但是网络通信毕竟不可靠,假如某个实例同步失败或者网络延时,客户端可能要等待相当长的时间甚至超时返回错误,破坏了可用性,一变多的目的是增加可用性,但是为了保证一致性,又降低了可用性,这种方式显然不可取。 2、异步方式 ![02.png][2] 该方式采用纯异步,客户端访问实例后,该实例会直接返回结果,不用等待其它实例同步完成。这种方式属于弱一致性,可能导致丢失部分数据。 3、最终一致性 ![03.png][3] 该方式是在主节点与从节点之间,加入一个消息队列,主节点与消息队列之间是同步阻塞的,然后其它节点从队列中同步数据,期间可能会取到不一致的数据,但是最终数据会一致。 ### 命令方式 从节点执行命令 redis5.0之前 ``` SLAVEOF host port ``` redis5.0之后 ``` REPLICAOF host port # 尽量用本机ip 而不是127.0.0.1 ``` host和port为要追随的主节点的ip和端口。 取消追随 ``` REPLICAOF no one 取消追随 ``` ### 配置文件方式 ``` vi /etc/redis/6379.conf # 从节点配置文件 ``` ``` replicaof <masterip> <masterport> # 要追随的主节点的ip和端口 replica-serve-stale-data yes # 数据同步期间,是否对外提供服务(从库原有数据会在同步结束后被清除,在此期间是否允许对这些数据进行操作) replica-read-only yes # 从节点是否只读 repl-diskless-sync no # disk方式就是,主节点会先将rdb文件落到磁盘上,然后从节点去主节点的磁盘获取rdb文件,经历两次I/O,但是磁盘上的rdb文件是共享的,从节点可以同时获取,diskless模式也就是socket模式,就是rdb文件不会落到磁盘,而是直接通过网络I/O发送给从节点,经历一次I/O,但是只能按顺序一个一个发送。 repl-backlog-size 1mb # 增量复制缓冲区大小,如果一个从节点短暂下线了,恢复之后需要同步数据,根据偏移量到缓冲区找到对应位置的数据,就可以增量恢复了,但是缓冲区数据会不断被挤出队列,如果从节点发现找不到数据或是数据不完整,就会进行一次全量更新,也就是说,repl-backlog-size的值越大,从节点离线的时间可以更长。 min-replicas-to-write 3 # 如果配置了该选项,如果健康的从节点小于3个,那么master就停止写入服务。 min-replicas-max-lag 10 # 延迟小于10秒的从节点才是健康的从节点(配合min-replicas-to-write选项使用) ``` ## CAP定理 - Consistency(一致性): 对于任何从客户端发送到分布式系统的数据读取请求,要么读到最新的数据,要么失败。 - Availability(可用性):对于任何求从客户端发送到分布式系统的数据读取请求,都一定会收到数据,不会收到错误,但不保证客户端收到的数据一定是最新的数据。 - Partition tolerance(分区容忍性):分布式系统应该一直持续运行,即使在不同节点间同步数据时出现了大量的数据丢失或者数据同步延迟。 对于一个分布式系统而言,P是前提,必须保证,因为只要有网络交互就一定会有延迟和数据丢失,这种状况我们必须接受,必须保证系统不能挂掉。高一致性必然降低可用性,所以CAP只能选其二,不可能三个同时满足。 ## Sentinel(哨兵) - 主备:只有一台主机对外提供服务,备用机不对外提供服务。 - 主从:所有节点都提供服务,但一般主可以读写,从只能读。 但是不管是主备或者主从,都有一个主,这样主自己又是一个单点了,所以,在分布式系统中,通常会对主做高可用,也就是代替人,做自动的故障转移。如何做?肯定是一个技术、程序实现,但是只要是程序,就会有单点故障问题,所以这个程序也要是一个集群。那还要对这个监控程序做高可用?那这样就没完没了了,所以,监控集群有一些不一样的地方。 ![04.png][4] 当多个进程监控一个redis节点的时候,其实可以当做是该redis节点在同时监控这些进程。当某个进程与redis不能正常通信时,该进程就会判定该节点挂了,当超过半数的进程都认为该节点挂了时,就可以判断节点挂了。那么为什么是超过半数?网络毕竟是不稳定的,如果所有进程都通过才算数的话,那就要等待所有人的返回结果,所有人都通过又是一种强一致性方案,肯定会降低可用性。如果不用超过半数,那么就形成不了势力范围,比如,有几个认为挂了,选出了新的主节点,另外的认为还活着,就导致同时出现几个主节点对外提供服务,数据可能不一致,导致网络分区,也就是脑裂。因为超过半数才能做决定,所以集群的数量一般都是奇数台,比如我有5台机器,那么,必须三台才可以形成势力,也就是可以挂两台,如果是6台,必须4台才能形成势力,也是可以挂两台,这样就节省了一台服务器的成本,而且6台中两台挂的概率肯定比5台中两台挂掉的概率高。 redis提供了Sentinel(哨兵)作为高可用方案 ### Sentinel配置 Sentinel完整的配置文件在redis源码目录中,名字叫sentinel.conf,配置项基本和redis一样。 新建一个配置文件 ``` vi 26379.conf ``` ``` port 26379 # 哨兵要占用的端口 sentinel monitor mymaster 192.168.50.16 6381 2 # 主节点 mymaster 组名,随便起,一个哨兵可以监控多组redis主从集群,192.168.50.16 6381 ip和端口,2表示判断失效至少要有两个sentinel同意 尽量用本机ip 而不是127.0.0.1 ``` 通过配置文件启动哨兵 ``` redis-server 26379.conf --sentinel ``` 这样就成功启动了一个哨兵进程,可以继续创建配置文件启动多个哨兵。 当主节点挂掉的时候,哨兵之间会先通过选举,推选出一个“话事人”和一个新的主节点,然后由“话事人”发送命令将选出来的主节点取消追随,再将其他节点(包括原来的主节点)追加到新的主节点上,这样,当之前的主节点上线后,会变成一个从节点。我们配置文件中只简单配置了哨兵的端口和主节点的地址,那哨兵是怎么知道从节点和其它哨兵的呢?从节点比较好理解,因为主节点知道自己有哪些从节点,所以每个哨兵直接从主节点获取从节点信息。哨兵之间互相通信是通过发布/订阅,所有哨兵会订阅一个频道,不断发布消息。 ## Sharding分片 主从复制能解决单点故障问题,但是却不能解决容量有限的问题,要解决容量问题,需要将数据分库存储。 1、按逻辑业务拆分 这种方式比较好理解,客户端根据不同的业务选择不同的库进行写入和读取。 2、modula 通过hash算法计算key的hash值,然后对redis数量取模,这样就可以分散存储到不同的实例中了。这种方式算法比较简单,但是扩展性较低,比如我现在有3个节点,现在我添加一个变成了4个,以前对3取模,现在对4取模了,导致所有节点计算结果的变化,也就是写入和读取变成了另外一个库,想保证一致性,只能所有数据取出来,从新算一遍hash算法,在重新写入,成本太大。 3、random 该方法每次操作都随机到一个库,算法简单,但是客户端自己都不知道存到哪个库里了,看似没什么用,其实可以用作消息队列。 ![05.png][5] 如图,redis每个节点都有一个ooxx list,一个客户端调用LPUSH,另一个客户端调用RPOP。 4、kemata(一致性hash) 一致性hash也是通过hash取模,但是不是对redis数量,而是对2^32取模,也就是取值空间为0~2^32-1。一致性hash将整个hash值空间虚拟成一个hash环。 ![06.png][6] 接下来就可以对每一个redis节点的ip和端口进行一致性hash算法,算出一个数值,并挂到hash环的相应位置上。 ![07.png][7] 当有数据访问的时候,通过对访问的key进行同样的hash运算,就可以在hash环上找到一个位置,然后从此位置顺时针行走,遇到的第一个节点,就是处理这次请求的节点。这样对节点的添加或删除只会影响部分数据,不会造成全局洗牌,比如,之前顺序是a->b->c->d,现在,我增加一台变成a->b->c->e->d,那么受影响的只有对应值在c、e之间的数据,以前到d中操作,现在变成了e。 一致性hash当节点太少时,容易因为服务器分布不均匀,造成数据倾斜,如图: ![08.jpg][8] 此时,必然大量数据集中到A上,所以,一致性hash还可以添加虚拟节点,通过加后缀的方式,分别计算 “Node A# 1”、“Node A# 2”、“Node A# 3”、“Node B# 1”、“Node B# 2”、“Node B# 3”的哈希值,于是形成六个虚拟节点,这样就可以将少量节点虚拟成多个,较均匀的添加到hash环上了。 2,3,4三种集群方式统称为Sharding方式 ## redis集群模式 ### redis-cluster 实际上redis并没有用一致性hash,而是用的modula算法,通过预分区来解决扩展问题。Redis 集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽.集群的每个节点负责一部分hash槽,比如当前集群有3个节点,那么: - 节点 A 包含 0 到 5500号哈希槽. - 节点 B 包含5501 到 11000 号哈希槽. - 节点 C 包含11001 到 16384号哈希槽. redis每个节点都保存一个mapping,记录其他节点包含的哈希槽,这样redis想操作数据,随便访问哪一个节点都可以,该节点会对key进行hash计算出槽位,发现不是自己的,会通过mapping找到对应的节点,返回给客户端,让客户端重定向到该节点进行操作。 在添加节点时,抽取一些原有节点的哈希槽转移到新的节点上连带数据一起复制过来,这样只有新节点需要复制数据,不影响原有节点的服务。 #### 配置 进入redis安装目录下的create-cluster目录 ``` cd redis-5.0.6/utils/create-cluster ``` 编辑脚本 ``` vi create-cluster ``` ``` NODES=6 REPLICAS=1 ``` NODES表示节点的数量,REPLICAS是副本数,也就是三个主,三个从(副本数是1,表示每个主一个副本,总共6个节点,所以是三主三从) 启动 ``` ./create-cluster start ``` 分槽位 ``` ./create-cluster create ``` Can I set the above configuration? (type 'yes' to accept):yes 客户端连接,加-c表示cluster模式,这样可以重定向,否则计hash算出的槽位不在当前端口会报错 ``` redis-cli -c -p 30001 ``` 停止 ``` ./create-cluster stop ``` 清除缓存 ``` ./create-cluster clean ``` 命令启动 ``` redis-cli --cluster create 127.0.0.1:30001 127.0.0.1:30002 127.0.0.1:30003 127.0.0.1:30004 127.0.0.1:30005 127.0.0.1:30006 --cluster-replicas 1 ``` 重新分片(需要给出任意一个存活节点) ``` redis-cli --cluster reshard 127.0.0.1:30001 ``` How many slots do you want to move (from 1 to 16384)? # 要移动多少槽位 What is the receiving node ID? # 移动到哪个节点 节点ID 上边输出的 Source node # 1 : # 从哪台移动,可以输入all,表示所有都拿一些,也可以输入节点ID,回车还可以接着输入,输入完成发送done Do you want to proceed with the proposed reshard plan (yes/no)? yes 查看集群信息(给出一个存活节点,会输出全局) ``` redis-cli --cluster info 127.0.0.1:30001 ``` ``` redis-cli --cluster check 127.0.0.1:30001 ``` 手工搭建需要修改配置 ``` cluster-enable yes cluster-config-file nodes.conf cluster-node-timeout 5000 ``` ### 代理 ![09.png][9] #### Predixy github:https://github.com/joyieldInc/predixy/releases **特性** - 高性能并轻量级 - 支持多线程 - 多平台支持:Linux、OSX、BSD、Windows(Cygwin) - 支持Redis Sentinel,可配置一组或者多组redis - 支持Redis Cluster - 支持redis阻塞型命令,包括blpop、brpop、brpoplpush - 支持scan命令,无论是单个redis还是多个redis实例都支持 - 多key命令支持: mset/msetnx/mget/del/unlink/touch/exists - 支持redis的多数据库,即可以使用select命令 - 支持事务,当前仅限于Redis Sentinel下单一redis组可用 - 支持脚本,包括命令:script load、eval、evalsha - 支持发布订阅机制,也即Pub/Sub系列命令 - 多数据中心支持,读写分离支持 - 扩展的AUTH命令,强大的读、写、管理权限控制机制,健空间限制机制 - 日志可按级别采样输出,异步日志记录避免线程被io阻塞 - 日志文件可以按时间、大小自动切分 - 丰富的统计信息,包括CPU、内存、请求、响应等信息 - 延迟监控信息,可以看到整体延迟,分后端redis实例延迟 **安装** ``` wget https://github.com/joyieldInc/predixy/releases/download/1.0.5/predixy-1.0.5-bin-amd64-linux.tar.gz tar -zxf predixy-1.0.5-bin-amd64-linux.tar.gz cd predixy-* ``` **配置** ``` vi conf/predixy.conf ``` ``` Name PredixyExample # 名称 随便取 Bind 127.0.0.1:7617 # predixy 绑定的ip和端口 WorkerThreads 1 # 工作线程数 ``` ``` ####### ####### ####### ####### ## SERVERS ####### ####### ####### ####### ### # Include cluster.conf # cluster 模式 Include sentinel.conf # 哨兵模式 与cluster二选一 # Include try.conf ``` - 哨兵模式 ``` vi conf/sentinel.conf ``` ``` SentinelServerPool { Databases 16 Hash crc16 # hash算法 HashTag "{}" Distribution modula MasterReadPriority 60 StaticSlaveReadPriority 50 DynamicSlaveReadPriority 50 RefreshInterval 1 ServerTimeout 1 ServerFailureLimit 10 ServerRetryTimeout 1 KeepAlive 120 Sentinels { # 哨兵的ip和端口 + 127.0.0.1:26379 + 127.0.0.1:26380 + 127.0.0.1:26381 } Group ooxx { # 哨兵分组,一个哨兵可以监控n个集群,ooxx为监控的集群名,多个Group之间通过hash算法进行操作 } Group xxoo { } ``` - cluster 模式 ``` vi conf/cluster.conf ``` ``` ClusterServerPool { MasterReadPriority 60 StaticSlaveReadPriority 50 DynamicSlaveReadPriority 50 RefreshInterval 1 ServerTimeout 1 ServerFailureLimit 10 ServerRetryTimeout 1 KeepAlive 120 Servers { # redis 实例 + 192.168.2.107:2211 + 192.168.2.107:2212 } } ``` 启动predixy ``` bin/predixy conf/predixy.conf ``` 因为数据分制,聚合操作和事务控制很难实现,predixy只有在哨兵模式并且只有一个分组的条件下支持事务,与redis-cluster模式一样,开启事务,必须在key前加上{xx}的前缀,花括号中的字符相同,就会放到同一个实例中,就可以做事务控制了,当对没有加{xx}的key进行事务操作时,会报错。 #### twemproxy github:https://github.com/twitter/twemproxy **特性** - 快速 - 轻量级 - 维护持久的服务器连接 - 保持后端缓存服务器的连接计数较低 - 支持对多个服务器进行代理 - 同时支持多个服务器池 - 自动跨多个服务器分片数据 - 实现memcached和redis - 通过 YAML 文件轻松配置 - 支持多种哈希模式,包括一致哈希和散列模式 - 可以配置为在故障时禁用节点 - 通过统计监视端口上公开的统计进行观察 - 适用于 Linux、 * BSD、 OS x 和 SmartOS (Solaris) **弊端** 不支持事务 **安装** ``` git clone https://github.com/twitter/twemproxy.git ``` 如果报错nss版本低 ``` yum update nss ``` ``` cd twemproxy yum install -y automake libtool autoreconf -fvi ./configure --enable-debug=full make ``` 可执行文件 ``` src/nutcracker -h ``` 服务 ``` cp scripts/nutcracker.init /etc/init.d/twemproxy chmod +x /etc/init.d/twemproxy ``` **配置** ``` mkdir -p /etc/nutcracker cp conf/* /etc/nutcracker cp src/nutcracker /usr/bin ``` ``` vi /etc/nutcracker.yml ``` ``` alpha: # 要代理的集群名称 随便取 每一个为一个集群池 listen: 127.0.0.1:22121 # 监听地址 hash: fnv1a_64 # hash算法 distribution: ketama auto_eject_hosts: true redis: true # 代理redis (还可以代理memcache) server_retry_timeout: 2000 server_failure_limit: 1 servers: - 127.0.0.1:6379:1 # redis实例 1表示权重 beta: listen: 127.0.0.1:22122 hash: fnv1a_64 hash_tag: "{}" distribution: ketama auto_eject_hosts: false timeout: 400 redis: true servers: - 127.0.0.1:6380:1 server1 - 127.0.0.1:6381:1 server2 - 127.0.0.1:6382:1 server3 - 127.0.0.1:6383:1 server4 gamma: listen: 127.0.0.1:22123 hash: fnv1a_64 distribution: ketama timeout: 400 backlog: 1024 preconnect: true auto_eject_hosts: true server_retry_timeout: 2000 server_failure_limit: 3 servers: - 127.0.0.1:11212:1 - 127.0.0.1:11213:1 delta: listen: 127.0.0.1:22124 hash: fnv1a_64 distribution: ketama timeout: 100 auto_eject_hosts: true server_retry_timeout: 2000 server_failure_limit: 1 servers: - 127.0.0.1:11214:1 - 127.0.0.1:11215:1 - 127.0.0.1:11216:1 - 127.0.0.1:11217:1 - 127.0.0.1:11218:1 - 127.0.0.1:11219:1 - 127.0.0.1:11220:1 - 127.0.0.1:11221:1 - 127.0.0.1:11222:1 - 127.0.0.1:11223:1 omega: listen: /tmp/gamma 0666 hash: hsieh distribution: ketama auto_eject_hosts: false servers: - 127.0.0.1:11214:100000 - 127.0.0.1:11215:1 ``` 启动 ``` service twemproxy start ``` [1]: https://www.princelei.club/usr/uploads/2019/12/4008473778.png [2]: https://www.princelei.club/usr/uploads/2019/12/1638146498.png [3]: https://www.princelei.club/usr/uploads/2019/12/2415923969.png [4]: https://www.princelei.club/usr/uploads/2019/12/889499374.png [5]: https://www.princelei.club/usr/uploads/2019/12/2327632475.png [6]: https://www.princelei.club/usr/uploads/2019/12/3663476565.png [7]: https://www.princelei.club/usr/uploads/2019/12/1226737097.png [8]: https://www.princelei.club/usr/uploads/2019/12/1367343277.jpg [9]: https://www.princelei.club/usr/uploads/2019/12/3214205456.png Last modification:June 20th, 2020 at 12:30 pm © 允许规范转载
立意高远,以小见大,引发读者对社会/人性的深层共鸣。