23 Dec 2019
redis-cluster提供了一种方式,在多个redis节点中进行数据分片。同时,也在分区期间提供了一定程度的高可用性,当某个节点无法通信时,可以继续执行读写操作。
每一个redis节点需要监听两个端口,其中一个端口是用于提供客户端服务,例如6379,另外一个端口是给前一个端口加上10000,例如16379.第二个大端口号用于集群总线通信的,在上面节点与节点之间使用二进制协议进行通信。集群总线被节点用于失败检测、配置更新、failover授权和其他集群功能。
redis-cluster目前不支持nat网络或端口重定向过的网络。而docker恰好使用了端口转发的方式,来实现了同一台主机上的多个容器可以在容器内部使用同一个端口的方式。所以,如果要使用docker来部署redis-cluster,我们必须给容器使用host网络的方式。
Redis Cluster不使用一致的哈希,而是使用一种不同形式的分片,其中每个键从概念上讲都是我们称为哈希槽的一部分。每一个redis-cluster中有16384个哈希槽,计算给定密钥的哈希槽是多少,我们只需对密钥的CRC16取模16384。
每一个redis节点负责哈希槽的一个子区间,例如我们有一个3节点的redis-cluster,那么:
因为将哈希槽从一个节点移动到另一个节点不需要停止操作,所以添加和删除节点或更改节点持有的哈希槽的百分比不需要任何停机时间。
redis-cluster支持针对多个key的批量操作,只需要目标keys存在在同一个哈希槽中。要达成将多个keys放在同一个哈希槽中,可以使用hash tags来手动分类(关键字就是{},具体文档可以谷歌)。
当redis-cluster部分子节点无法和其他大部分节点通信或者发生错误时使整个集群依然可用,redis-cluster使用了主从模型,使每一个哈希槽存在多个副本。
在我们举得例子中,三个节点ABC,假设B发生了问题,那么5501-11000区间的哈希槽将无法被访问,但是因为我们给B提供了一个从节点B1,那么A和C将B1提升为主节点,此时就可以继续对5501-11000区间的哈希槽提供访问和操作。
但是需要注意的是,如果B和B1同时发生错误,那么整个集群会发生失败
redis-cluster不保证强一致性。在实际使用中,这意味着在某些特定条件下,redis-cluster会丢失丢失系统已确认给客户端的写入。
Redis Cluster可能会丢失写入的第一个原因是因为它使用异步复制。
基本上,我们需要在性能和一致性之间进行权衡。当然,如果你希望改变这种默认行为,而使用同步复制的话,可以使用WAIT命令。这样可以大大降低写操作的丢失,但是依然没有达到强一致性。在更复杂的故障情况下,总是有可能将无法接收写操作的从设备选为主设备。
还有一种值得注意的情况,Redis Cluster将丢失写操作,这种情况发生在网络分区期间,在该分区中,客户端与少数实例(至少包括主实例)隔离。
假设我们有三主三从ABCA1B1C1,然后有个客户端Z1,客户端Z1和B,被网络分区隔离出来,然后ACA1B1C1是在另外一个网络分区。Z1依然可以向B中写入,如果在很短的时间内集群恢复,然而时间已经长到足够B1被提升为主节点。此时Z1写入到B的数据会丢失。
请注意,Z1将能够发送到B的最大写入量有一个最大窗口,如果已经有足够的时间让分区的多数方选举一个从属方为主,则少数方中的每个主节点将停止接受写入。该时间量是Redis Cluster的一个非常重要的配置指令,称为node timeout。当node timeout超时后,主节点会进入失败状态,然后被其他从节点代替。类似地,在没有主节点能够感知大多数其他主节点的节点超时之后,它进入错误状态并停止接受写入。
mkdir redis-cluster-test cd redis-cluster-test # 创建配置文件 # 基本上就是默认配置,然后修改了以下内容 # - port 7000(对应的端口组合的路径啥的一起改了) # - protect-mode no # - bind 0.0.0.0 # - maxmemory 1024mb # - appendonly yes # - cluster-enabled yes # - cluster-config-file nodes-7000.conf # - cluster-node-timeout 5000 # - cluster-replica-validity-factor 10 cat << EOF > 7000.conf bind 0.0.0.0 protected-mode no port 7000 tcp-backlog 511 timeout 0 tcp-keepalive 300 daemonize no supervised no pidfile /var/run/redis_7000.pid loglevel notice logfile "" databases 16 always-show-logo yes save 900 1 save 300 10 save 60 10000 stop-writes-on-bgsave-error yes rdbcompression yes rdbchecksum yes dbfilename dump.rdb dir ./ replica-serve-stale-data yes replica-read-only yes repl-diskless-sync no repl-diskless-sync-delay 5 repl-disable-tcp-nodelay no replica-priority 100 requirepass myredis123 maxmemory 1024mb lazyfree-lazy-eviction no lazyfree-lazy-expire no lazyfree-lazy-server-del no replica-lazy-flush no appendonly yes appendfilename "appendonly.aof" appendfsync everysec no-appendfsync-on-rewrite no auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb aof-load-truncated yes aof-use-rdb-preamble yes lua-time-limit 5000 cluster-enabled yes cluster-config-file nodes-7000.conf cluster-node-timeout 5000 cluster-replica-validity-factor 10 slowlog-log-slower-than 10000 slowlog-max-len 128 latency-monitor-threshold 0 notify-keyspace-events "" hash-max-ziplist-entries 512 hash-max-ziplist-value 64 list-max-ziplist-size -2 list-compress-depth 0 set-max-intset-entries 512 zset-max-ziplist-entries 128 zset-max-ziplist-value 64 hll-sparse-max-bytes 3000 stream-node-max-bytes 4096 stream-node-max-entries 100 activerehashing yes client-output-buffer-limit normal 0 0 0 client-output-buffer-limit replica 256mb 64mb 60 client-output-buffer-limit pubsub 32mb 8mb 60 hz 10 dynamic-hz yes aof-rewrite-incremental-fsync yes rdb-save-incremental-fsync yes EOF # 其他的7001.conf,7002.conf,7003.conf,7004.conf,7005.conf保持配置一致,然后端口改掉即可 # 创建docker-compose文件 # 重点注意: # - 网络模式使用host # - 生产环境中可以把rdb的目录(/data)给映射出来 cat << EOF > redis-cluster-test.yml version: '3' services: redis-7000: container_name: redis-7000 image: redis:5 network_mode: "host" command: ["redis-server", "/usr/local/etc/redis/redis.conf"] volumes: - ./7000.conf:/usr/local/etc/redis/redis.conf redis-7001: container_name: redis-7001 image: redis:5 network_mode: "host" command: ["redis-server", "/usr/local/etc/redis/redis.conf"] volumes: - ./7001.conf:/usr/local/etc/redis/redis.conf redis-7002: container_name: redis-7002 image: redis:5 network_mode: "host" command: ["redis-server", "/usr/local/etc/redis/redis.conf"] volumes: - ./7002.conf:/usr/local/etc/redis/redis.conf redis-7003: container_name: redis-7003 image: redis:5 network_mode: "host" command: ["redis-server", "/usr/local/etc/redis/redis.conf"] volumes: - ./7003.conf:/usr/local/etc/redis/redis.conf redis-7004: container_name: redis-7004 image: redis:5 network_mode: "host" command: ["redis-server", "/usr/local/etc/redis/redis.conf"] volumes: - ./7004.conf:/usr/local/etc/redis/redis.conf redis-7005: container_name: redis-7005 image: redis:5 network_mode: "host" command: ["redis-server", "/usr/local/etc/redis/redis.conf"] volumes: - ./7005.conf:/usr/local/etc/redis/redis.conf EOF # 启动redis-cluster集群 docker-compose -f redis-cluster-test.yml up -d # 因为是host网络启动,会直接占用宿主机端口 netstat -lnpt |grep redis tcp 0 0 0.0.0.0:7000 0.0.0.0:* LISTEN 9090/redis-server 0 tcp 0 0 0.0.0.0:7001 0.0.0.0:* LISTEN 9218/redis-server 0 tcp 0 0 0.0.0.0:7002 0.0.0.0:* LISTEN 9297/redis-server 0 tcp 0 0 0.0.0.0:7003 0.0.0.0:* LISTEN 9323/redis-server 0 tcp 0 0 0.0.0.0:7004 0.0.0.0:* LISTEN 9143/redis-server 0 tcp 0 0 0.0.0.0:7005 0.0.0.0:* LISTEN 9192/redis-server 0 tcp 0 0 0.0.0.0:17000 0.0.0.0:* LISTEN 9090/redis-server 0 tcp 0 0 0.0.0.0:17001 0.0.0.0:* LISTEN 9218/redis-server 0 tcp 0 0 0.0.0.0:17002 0.0.0.0:* LISTEN 9297/redis-server 0 tcp 0 0 0.0.0.0:17003 0.0.0.0:* LISTEN 9323/redis-server 0 tcp 0 0 0.0.0.0:17004 0.0.0.0:* LISTEN 9143/redis-server 0 tcp 0 0 0.0.0.0:17005 0.0.0.0:* LISTEN 9192/redis-server 0 # 初始化集群 redis-cli -a password --cluster create \ 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 \ --cluster-replicas 1 # 会输出建议的master和slave分布,同意的话,输入yes即可 # 会提示redis集群创建完毕
在实际上生产的时候遇到了一个问题,因为机房是nat网络结构,导致了如果我们用内网ip把redis-cluster启动起来,机房外部的客户端A访问这个集群碰到redirect的情况会有问题。因为当连接上node01的时,碰到需要redirect的情况,node01给出的redirect的ip是内网ip,而客户端A是使用外网才能访问到redis-cluster,此时就是有报错。所以此时需要以下几个配置
详细配置的官方解释看这里
########################## CLUSTER DOCKER/NAT support ######################## # In certain deployments, Redis Cluster nodes address discovery fails, because # addresses are NAT-ted or because ports are forwarded (the typical case is # Docker and other containers). # # In order to make Redis Cluster working in such environments, a static # configuration where each node knows its public address is needed. The # following two options are used for this scope, and are: # # * cluster-announce-ip # * cluster-announce-port # * cluster-announce-bus-port # # Each instruct the node about its address, client port, and cluster message # bus port. The information is then published in the header of the bus packets # so that other nodes will be able to correctly map the address of the node # publishing the information. # # If the above options are not used, the normal Redis Cluster auto-detection # will be used instead. # # Note that when remapped, the bus port may not be at the fixed offset of # clients port + 10000, so you can specify any port and bus-port depending # on how they get remapped. If the bus-port is not set, a fixed offset of # 10000 will be used as usually. # # Example: # # cluster-announce-ip 10.1.1.5 # cluster-announce-port 6379 # cluster-announce-bus-port 6380