3.Redis集群部署和数据分片 - 图3

1.redis 单机部署

1、下载redis

下载地址在:redis.io 首页
3.Redis集群部署和数据分片 - 图4
如果从官网下载慢,可以把链接贴到迅雷下载,再传到虚拟机:

  1. cd /usr/local/soft/
  2. wget https://download.redis.io/releases/redis-6.0.9.tar.gz

2、解压压缩包

  1. tar -zxvf redis-6.0.9.tar.gz

3、安装gcc依赖

Redis是C语言编写的,编译需要GCC。
Redis6.x.x版本支持了多线程,需要gcc的版本大于4.9,但是CentOS7的默认版本是4.8.5。
查看gcc的版本:

  1. gcc -v

升级gcc版本:

  1. yum -y install centos-release-scl
  2. yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
  3. scl enable devtoolset-9 bash
  4. echo "source /opt/rh/devtoolset-9/enable" >>/etc/profile

确认gcc的版本(在同一个窗口中!):

  1. gcc -v

4、编译安装

  1. cd redis-6.0.9/src
  2. make install

安装成功的结果是src目录下面出现服务端和客户端的脚本
redis-server
redis-cli
redis-sentinel

5、修改配置文件

默认的配置文件是/usr/local/soft/redis-6.0.9/redis.conf
后台启动,不然窗口一关服务就挂了

  1. daemonize no

改成

  1. daemonize yes

下面一行必须改成 bind 0.0.0.0 或注释,否则只能在本机访问

  1. bind 127.0.0.1

如果需要密码访问,取消requirepass的注释,在外网(比如阿里云)这个必须要配置!

  1. requirepass yourpassword

6、使用指定配置文件启动Redis

  1. /usr/local/soft/redis-6.0.9/src/redis-server /usr/local/soft/redis-6.0.9/redis.conf

查看端口是否启动成功:

  1. netstat -an|grep 6379

7、进入客户端

  1. /usr/local/soft/redis-6.0.9/src/redis-cli

8、停止redis(在客户端中)

  1. redis> shutdown

  1. ps -aux | grep redis
  2. kill -9 xxxx

9、配置别名的步骤[可选]

  1. vim ~/.bashrc

添加两行:

  1. alias redis='/usr/local/soft/redis-6.0.9/src/redis-server /usr/local/soft/redis-6.0.9/redis.conf'
  2. alias rcli='/usr/local/soft/redis-6.0.9/src/redis-cli'

编译生效:

  1. source ~/.bashrc

这样就可以用redis启动服务,rcli进入客户端了

2.Sentinel集群(读写分离减少读压力)

1.部署

开启哨兵模式,至少需要3个Sentinel实例(奇数个,否则无法选举Leader)。
本例通过3个Sentinel实例监控3个Redis服务(1主2从)。

  1. IP地址 节点角色&端口
  2. 192.168.44.186 Master6379 / Sentinel : 26379
  3. 192.168.44.187 Slave 6379 / Sentinel : 26379
  4. 192.168.44.188 Slave 6379 / Sentinel : 26379

防火墙记得关闭!!!
网络结构图:
3.Redis集群部署和数据分片 - 图5
在187和188的redis.conf配置中找到被注释的这一行

  1. # replicaof <masterip> <masterport>
  1. replicaof 192.168.44.186 6379

在186、187、188创建sentinel配置文件(单例安装后根目录下默认有sentinel.conf,可以先备份默认的配置)

  1. cd /usr/local/soft/redis-6.0.9
  2. mkdir logs
  3. mkdir rdbs
  4. mkdir sentinel-tmp
  5. cp sentinel.conf sentinel.conf.bak
  6. >sentinel.conf
  7. vim sentinel.conf

sentinel.conf配置文件内容,三台机器相同

  1. daemonize yes
  2. port 26379
  3. protected-mode no
  4. dir "/usr/local/soft/redis-6.0.9/sentinel-tmp"
  5. sentinel monitor redis-master 192.168.44.186 6379 2
  6. sentinel down-after-milliseconds redis-master 30000
  7. sentinel failover-timeout redis-master 180000
  8. sentinel parallel-syncs redis-master 1

配置解读:

配置 作用
protected-mode 是否允许外部网络访问,yes不允许
dir sentinel的工作目录
sentinel monitor sentinel监控的redis主节点
down-after-milliseconds(毫秒) master宕机多久,才会被Sentinel主观认为下线
sentinel failover-timeout(毫秒) 1 同一个sentinel对同一个master两次failover之间的间隔时间。2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。3.当想要取消一个正在进行的failover所需要的时间。 4.当进行failover时,配置所有slaves指向新的master所需的最大时间。
parallel-syncs 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,这个数字越小,完成failover所需的时间就越长,但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。

在3台机器上分别启动Redis和Sentinel

  1. cd /usr/local/soft/redis-6.0.9/src
  2. ./redis-server ../redis.conf
  3. ./redis-sentinel ../sentinel.conf

哨兵节点的另一种启动方式:

  1. ./redis-server ../sentinel.conf --sentinel

在3台机器上查看集群状态:

  1. $ /usr/local/soft/redis-6.0.9/src/redis-cli
  2. redis> info replication

3.Redis集群部署和数据分片 - 图6
模拟master宕机,在186执行:

  1. redis> shutdown

注意看sentinel.conf里面的redis-master被修改了,变成了当前master的IP端口。

  1. $ /usr/local/soft/redis-6.0.9/src/redis-cli
  2. redis> info replication

这个时候会有一个slave节点被Sentinel设置为master。
再次启动master,它不一定会被选举为master。
3.Redis集群部署和数据分片 - 图7


2. 哨兵模式的原理和详解

3.Redis集群部署和数据分片 - 图8

1.哨兵集群中的每个节点都会启动三个定时任务

  • 第一个定时任务: 每个sentinel节点每隔1s向所有的master、slaver、别的sentinel节点发送一个PING命令,作用:心跳检测
  • 第二个定时任务: 每个sentinel每隔2s都会向master的sentinel:hello这个channel中发送自己掌握的集群信息和自己的一些信息(比如host,ip,run id),这个是利用redis的pub/sub功能,每个sentinel节点都会订阅这个channel,也就是说,每个sentinel节点都可以知道别的sentinel节点掌握的集群信息,作用:信息交换,了解别的sentinel的信息和他们对于主节点的判断
  • 第三个定时任务: 每个sentinel节点每隔10s都会向master和slaver发送INFO命令,作用:发现最新的集群拓扑结构

    2.哨兵如何判断master宕机

    主观下线

      这个就是上面介绍的第一个定时任务做的事情,当sentinel节点向master发送一个PING命令,如果超过own-after-milliseconds(默认是30s,这个在sentinel的配置文件中可以自己配置)时间都没有收到有效回复,不好意思,我就认为你挂了,就是说为的主观下线(SDOWN),修改其flags状态为SRI_S_DOWN

    客观下线

      要了解什么是客观下线要先了解几个重要参数:

  • quorum:如果要认为master客观下线,最少需要主观下线的sentinel节点个数,举例:如果5个sentinel节点,quorum = 2,那只要2个sentinel主观下线,就可以判断master客观下线

  • majority:如果确定了master客观下线了,就要把其中一个slaver切换成master,做这个事情的并不是整个sentinel集群,而是sentinel集群会选出来一个sentinel节点来做,那怎么选出来的呢,下面会讲,但是有一个原则就是需要大多数节点都同意这个sentinel来做故障转移才可以,这个大多数节点就是这个参数。注意:如果sentinel节点个数5,quorum=2,majority=3,那就是3个节点同意就可以,如果quorum=5,majority=3,这时候majority=3就不管用了,需要5个节点都同意才可以。
  • configuration epoch:这个其实就是version,类似于中国每个皇帝都要有一个年号一样,每个新的master都要生成一个自己的configuration epoch,就是一个编号
    客观下线处理过程
  1. 每个主观下线的sentinel节点都会向其他sentinel节点发送 SENTINEL is-master-down-by-addr ip port current_epoch runid,(ip:主观下线的服务id,port:主观下线的服务端口,current_epoch:sentinel的纪元,runid:*表示检测服务下线状态,如果是sentinel 运行id,表示用来选举领头sentinel(下面会讲选举领头sentinel))来询问其它sentinel是否同意服务下线。
  2. 每个sentinel收到命令之后,会根据发送过来的ip和端口检查自己判断的结果,如果自己也认为下线了,就会回复,回复包含三个参数:down_state(1表示已下线,0表示未下线),leader_runid(领头sentinal id),leader_epoch(领头sentinel纪元)。由于上面发送的runid参数是*,这里后两个参数先忽略。
  3. sentinel收到回复之后,根据quorum的值,判断达到这个值,如果大于或等于,就认为这个master客观下线

    选择领头sentinel的过程

    (raft算法)

b4qjEDXcqQ.gif
  到现在为止,已经知道了master客观下线,那就需要一个sentinel来负责故障转移,那到底是哪个sentinel节点来做这件事呢?需要通过选举实现,具体的选举过程如下:

  1. 判断客观下线的sentinel节点向其他节点发送SENTINEL is-master-down-by-addr ip port current_epoch runid(注意:这时的runid是自己的run id,每个sentinel节点都有一个自己运行时id)
  2. 目标sentinel回复,由于这个选择领头sentinel的过程符合先到先得的原则,举例:sentinel1判断了客观下线,向sentinel2发送了第一步中的命令,sentinel2回复了sentinel1,说选你为领头,这时候sentinel3也向sentinel2发送第一步的命令,sentinel2会直接拒绝回复
  3. 当sentinel发现选自己的节点个数超过majority(注意上面写的一种特殊情况quorum>majority)的个数的时候,自己就是领头节点
  4. 如果没有一个sentinel达到了majority的数量,等一段时间,重新选举

    故障转移过程

      通过上面的介绍,已经有了领头sentinel,下面就是要做故障转移了,故障转移的一个主要问题和选择领头sentinel问题差不多,到底要选择哪一个slaver节点来作为master呢?按照我们一般的常识,我们会认为哪个slaver中的数据和master中的数据相识度高哪个slaver就是master了,其实哨兵模式也差不多是这样判断的,不过还有别的判断条件,详细介绍如下:
      在进行选择之前需要先剔除掉一些不满足条件的slaver,这些slaver不会作为变成master的备选
  • 剔除列表中已经下线的从服务
  • 剔除有5s没有回复sentinel的info命令的slaver
  • 剔除与已经下线的主服务连接断开时间超过 down-after-milliseconds*10+master宕机时长的slaver

    选主过程

  1. 选择优先级最高的节点,通过sentinel配置文件中的replica-priority配置项,这个参数越小,表示优先级越高
  2. 如果第一步中的优先级相同,选择offset最大的,offset表示主节点向从节点同步数据的偏移量,越大表示同步的数据越多
  3. 如果第二步offset也相同,选择run id较小的

    后续事项

      新的主节点已经选择出来了,并不是到这里就完事了,后续还需要做一些事情,如下

  4. 领头sentinel向别的slaver发送slaveof命令,告诉他们新的master是谁谁谁,你们向这个master复制数据

  5. 如果之前的master重新上线时,领头sentinel同样会给起发送slaveof命令,将其变成从节点

1. 哨兵模式是结点如何相互发现的?

非常神奇的是我们部署哨兵的时候之配置了 sentinel monitor redis-master 192.168.44.186 6379 2 也就是只配置了master的地址 为什么其他哨兵上线的时候会再配置文件中发现并配置其他哨兵节点和slave节点的信息呢?

  1. daemonize yes
  2. port 26379
  3. protected-mode no
  4. dir "/root/redis/redis-6.0.9/sentinel-tmp"
  5. sentinel myid e55acfb38d611736dfcca8b2651b04def5b680cb
  6. sentinel deny-scripts-reconfig yes
  7. sentinel monitor redis-master 192.168.122.1 6381 2
  8. sentinel auth-pass redis-master 123456
  9. # Generated by CONFIG REWRITE
  10. pidfile "/var/run/redis.pid"
  11. user default on nopass ~* +@all
  12. sentinel config-epoch redis-master 2
  13. sentinel leader-epoch redis-master 2
  14. sentinel known-replica redis-master 192.168.122.1 6380
  15. sentinel known-replica redis-master 192.168.122.1 6379
  16. sentinel known-sentinel redis-master 192.168.122.1 26381 d27c0b3d3688372e8beca9827a1b5baf1c994808
  17. sentinel known-sentinel redis-master 192.168.122.1 26380 b3a7356236aa9c8af67e0d8b000ea916da6a2cb4
  18. sentinel current-epoch 2

实际上sentinel利用了master的发布/订阅机制,去自动发现其它也监控了同一个master的sentinel节点。
因为Sentinel是一个特殊状态的Redis节点,它也有发布订阅的功能。
哨兵上线时,给所有的Reids节点(master/slave)的名字为sentinel:hello的channle发送消息。
每个哨兵都订阅了所有Reids节点名字为sentinel:hello的channle,所以能互相感知对方的存在,而进行监控。

2. 主从拷贝原理和细节

3.Redis集群部署和数据分片 - 图10

Talk is cheap. Show me the code

redis 主从拷贝的代码实现主要是replication.c

  1. /* ---------------------------------- MASTER -------------------------------- */
  2. void createReplicationBacklog(void) /* 创建backlog的buffer */
  3. void resizeReplicationBacklog(long long newsize) /* 调整复制备份日志的大小,当replication backlog被修改的时候 */
  4. void freeReplicationBacklog(void) /* 释放备份日志 */
  5. void feedReplicationBacklog(void *ptr, size_t len) /* 往备份日志中添加添加数据操作,会引起master_repl_offset偏移量的增加 */
  6. void feedReplicationBacklogWithObject(robj *o) /* 往backlog添加数据,以Redis 字符串对象作为参数 */
  7. void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) /* 将主数据库复制到从数据库 */
  8. void replicationFeedMonitors(redisClient *c, list *monitors, int dictid, robj **argv, int argc) /* 发送数据给monitor监听者客户端 */
  9. long long addReplyReplicationBacklog(redisClient *c, long long offset) /* slave从客户单添加备份日志 */
  10. int masterTryPartialResynchronization(redisClient *c) /* 主数据库尝试分区同步 */
  11. void syncCommand(redisClient *c) /* 同步命令函数 */
  12. void replconfCommand(redisClient *c) /* 此函数用于从客户端进行配置复制进程中的执行参数设置 */
  13. void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) /* 给slave客户端发送BULK数据 */
  14. void updateSlavesWaitingBgsave(int bgsaveerr) /* 此方法将用于后台保存进程快结束时调用,更新slave从客户端 */
  15. /* ----------------------------------- SLAVE -------------------------------- */
  16. void replicationAbortSyncTransfer(void) /* 中止与master主数据的同步操作 */
  17. void replicationSendNewlineToMaster(void) /* 从客户端发送空行给主客户端,破坏了原本的协议格式,避免让主客户端检测出从客户端超时的情况 */
  18. void replicationEmptyDbCallback(void *privdata) /* 清空数据库后的回调方法,当老数据被刷新出去之后等待加载新数据的时候调用 */
  19. void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) /* 从客户端读取同步的Sync的BULK数据 */
  20. char *sendSynchronousCommand(int fd, ...) /* 从客户端发送给主客户端同步数据的命令,附上验证信息,和一些参数配置信息 */
  21. int slaveTryPartialResynchronization(int fd) /* 从客户端尝试分区同步操作 */
  22. void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) /* 与主客户端保持同步,期间包括端口号等的确认,socket连接 */
  23. int connectWithMaster(void) /* 连接主客户端 */
  24. void undoConnectWithMaster(void) /* 撤销连接主客户端 */
  25. int cancelReplicationHandshake(void) /* 当已经存在一个复制进程时,中止一个非阻塞的replication复制的尝试 */
  26. void replicationSetMaster(char *ip, int port) /* 设定主客户端的ip地址和端口号 */
  27. void replicationUnsetMaster(void)
  28. void slaveofCommand(redisClient *c)
  29. void roleCommand(redisClient *c)
  30. void replicationSendAck(void) /* 发送ACK包给主客户端 ,告知当前的进程偏移量 */
  31. /* ---------------------- MASTER CACHING FOR PSYNC -------------------------- */
  32. void replicationCacheMaster(redisClient *c) /* 缓存客户端信息 */
  33. void replicationDiscardCachedMaster(void) /* 当某个客户端将不会再回复的时候,可以释放掉缓存的主客户端 */
  34. void replicationResurrectCachedMaster(int newfd) /* 将缓存客户端复活 */
  35. /* ------------------------- MIN-SLAVES-TO-WRITE --------------------------- */
  36. void refreshGoodSlavesCount(void) /* 更新slave从客户端数量 */
  37. void replicationScriptCacheInit(void)
  38. void replicationScriptCacheFlush(void)
  39. void replicationScriptCacheAdd(sds sha1)
  40. int replicationScriptCacheExists(sds sha1)
  41. void replicationCron(void)
  1. void disklessLoadRestoreBackups(redisDb *backup, int restore, int empty_db_flags)
  2. {
  3. if (restore) {
  4. /* Restore. */
  5. //emptyDbGeneric 会 Remove all keys from all the databases in a Redis server.
  6. emptyDbGeneric(server.db,-1,empty_db_flags,replicationEmptyDbCallback);
  7. for (int i=0; i<server.dbnum; i++) {
  8. dictRelease(server.db[i].dict);
  9. dictRelease(server.db[i].expires);
  10. server.db[i] = backup[i];
  11. }
  12. } else {
  13. /* Delete (Pass EMPTYDB_BACKUP in order to avoid firing module events) . */
  14. emptyDbGeneric(backup,-1,empty_db_flags|EMPTYDB_BACKUP,replicationEmptyDbCallback);
  15. for (int i=0; i<server.dbnum; i++) {
  16. dictRelease(backup[i].dict);
  17. dictRelease(backup[i].expires);
  18. }
  19. }
  20. zfree(backup);
  21. }

可以看到slave收到回复 会移除本地全部的key从master再从 master服务器中接受快照。
全量同步
Redis全量复制一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份。具体步骤如下:
- 从服务器连接主服务器,发送SYNC命令;
- 主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令;
- 主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;
- 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
- 主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
- 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;

增量同步
MobaXterm_IiWszT2gDP.png
slave 和 master 维护一个偏移量 如果两个偏移一致就说明数据是一致性的 如果slave的便宜量小于master说明slave的数据落后于slave(slave是只读的所以不会超过master)
Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。
增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。

Redis主从同步策略
主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。

哨兵模式解决的问题和缺点

哨兵模式实现了mastr -slave 主节点下线后进行故障转移的功能。
mastr -slave 模式 进行了读写分离 slave 节点进行读mastr 进行写 这大大减少了读压力大的问题。但是依然没有解决写压力大的问题。如果频繁进行写的话依然会导致mastr节点挂掉

3.数据分片

1.一致性hash算法 hash环

一致性Hash算法也是使用取模的方法,只是,刚才描述的取模法是对服务器的数量进行取模,而一致性Hash算法是对2^ 32-1取模,什么意思呢简单来说,一致性Hash算法将整个Hash值控件组织成一个虚拟的圆环,如假设某哈希函数H的值空间为0-2^32-1取模(即哈希值是一个32位无符号整型),整个哈希环如下:
3.Redis集群部署和数据分片 - 图12
整个空间按顺时针方向组织,圆环的正上方的点代表0,0点右侧的第一个点代表1,以此类推,2、3、4、5、6……直到2^ 32-1,也就是说0点左侧的第一个点代表2^ 32-1, 0和2^ 32-1在零点中方向重合,我们把这个由2^32个点组成的圆环称为Hash环。
下一步将各个服务器使用Hash进行一个哈希,具体可以选择服务器的主机名(考虑到ip变动,不要使用ip)作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位置,这里假设将上文中三个master节点的IP地址哈希后在环空间的位置如下:
3.Redis集群部署和数据分片 - 图13
下面将三条key-value数据也放到环上:将数据key使用相同的函数Hash计算出哈希值,并确定此数据在环上的置。将数据从所在位置顺时针找第一台遇到的服务器节点,这个节点就是该key存储的服务器!
例如我们有a、b、c三个key,经过哈希计算后,在环空间上的位置如下:key-a存储在node1,key-b存储在node2,key-c存储在node3
3.Redis集群部署和数据分片 - 图14

容错性和可扩展性

现假设Node 2不幸宕机,可以看到此时对象key-a和key-c不会受到影响,只有key-b被重定位到Node 3。一般的,在一致性Hash算法中,如果一台服务器不可用,则受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器,如下图中Node 2与Node 1之间的数据,图中受影响的是key-2)之间数据,其它不会受到影响。
同样的,如果集群中新增一个node 4,受影响的数据是node 1和node 4之间的数据,其他的数据是不受影响的。

3.Redis集群部署和数据分片 - 图15

综上所述,一致性Hash算法对于节点的增减都只需重定位环空间中的一小部分数据,具有较好的容错性和可扩展性。

数据倾斜(添加虚拟节点)

一致性Hash算法在服务节点太少时,容易因为节点分部不均匀而造成数据倾斜(被缓存的对象大部分集中缓存在某一台服务器上)问题,例如系统中只有两台服务器,此时必然造成大量数据集中到Node 2上,而只有极少量会定位到Node 1上。其环分布如下:
3.Redis集群部署和数据分片 - 图16

为了解决数据倾斜问题,一致性Hash算法引入了虚拟节点机制,即对每一个服务节点计算多个哈希,每个计算结果位置都放置一个此服务节点,称为虚拟节点。具体做法可以在主机名的后面增加编号来实现。例如上面的情况,可以为每台服务器计算三个虚拟节点,于是可以分别计算 “Node 1#1”、“Node 1#2”、“Node 1#3”、“Node 2#1”、“Node 2#2”、“Node 2#3”的哈希值,于是形成六个虚拟节点:
3.Redis集群部署和数据分片 - 图17

上图中虚拟节点node 1#1,node 1#2,node 1#3都属于真实节点node 1;虚拟节点node 2#1,node 2#2,node 2#3都属于真实节点node 2。

2.Jedis中一致性哈希算法

1.如何使用?

  1. public void test3() {
  2. //设置连接池的相关配置
  3. JedisPoolConfig poolConfig = new JedisPoolConfig();
  4. poolConfig.setMaxTotal(2);
  5. poolConfig.setMaxIdle(1);
  6. poolConfig.setMaxWaitMillis(2000);
  7. poolConfig.setTestOnBorrow(false);
  8. poolConfig.setTestOnReturn(false);
  9. //定义redis的多个节点机器
  10. List<JedisShardInfo> list = new ArrayList<JedisShardInfo>() {{
  11. add(new JedisShardInfo("127.0.0.1", 6379));
  12. add(new JedisShardInfo("127.0.0.1", 6380));
  13. add(new JedisShardInfo("127.0.0.1", 6381));
  14. }};
  15. //定义redis分片连接池
  16. ShardedJedisPool jedisPool = new ShardedJedisPool(poolConfig, list);
  17. //获取连接操作redis 进行查询等其他操作
  18. //使用后一定关闭,还给连接池
  19. try (ShardedJedis jedis = jedisPool.getResource()) {
  20. //向redis中添加20个记录查看分片结果
  21. for (int i = 0; i < 10; i++) {
  22. //增加的记录格式为 key=NUM_i value=i
  23. jedis.set("NUM_" + i, "" + i);
  24. jedis.get("NUM_" + i);
  25. }
  26. }

2.如何实现?

主要代码在 https://github.com/redis/jedis/blob/818dc9db08f87004dca5ab7e4a8e8cf06c0ea15a/src/main/java/redis/clients/jedis/util/Sharded.java
jedis 用 treemap实现了一个hash环算法。 利用treemap红黑树提高查找速度。

3.twemproxy +redis (不建议)


3.Redis集群部署和数据分片 - 图18

2.数据插槽 redis slot

image.png

4.redis cluster

1.架构图

3.Redis集群部署和数据分片 - 图20

2.简单的搭建

为了节省机器,我们直接把6个Redis实例安装在同一台机器上(3主3从),只是使用不同的端口号。
机器IP 192.168.44.181
可以跟单机的redis安装在同一台机器上,因为数据目录不同,没有影响。

  1. cd /usr/local/soft/redis-6.0.9
  2. mkdir redis-cluster
  3. cd redis-cluster
  4. mkdir 7291 7292 7293 7294 7295 7296

复制redis配置文件到7291目录

  1. cp /usr/local/soft/redis-6.0.9/redis.conf /usr/local/soft/redis-6.0.9/redis-cluster/7291

修改7291的redis.conf配置文件,内容:

  1. cd /usr/local/soft/redis-6.0.9/redis-cluster/7291
  2. >redis.conf
  3. vim redis.conf

内容如下:

  1. port 7291
  2. daemonize yes
  3. protected-mode no
  4. dir /usr/local/soft/redis-6.0.9/redis-cluster/7291/
  5. cluster-enabled yes
  6. cluster-config-file nodes-7291.conf
  7. cluster-node-timeout 5000
  8. appendonly yes
  9. pidfile /var/run/redis_7291.pid

把7291下的redis.conf复制到其他5个目录。

  1. cd /usr/local/soft/redis-6.0.9/redis-cluster/7291
  2. cp redis.conf ../7292
  3. cp redis.conf ../7293
  4. cp redis.conf ../7294
  5. cp redis.conf ../7295
  6. cp redis.conf ../7296

批量替换内容

  1. cd /usr/local/soft/redis-6.0.9/redis-cluster
  2. sed -i 's/7291/7292/g' 7292/redis.conf
  3. sed -i 's/7291/7293/g' 7293/redis.conf
  4. sed -i 's/7291/7294/g' 7294/redis.conf
  5. sed -i 's/7291/7295/g' 7295/redis.conf
  6. sed -i 's/7291/7296/g' 7296/redis.conf

启动6个Redis节点

  1. cd /usr/local/soft/redis-6.0.9/
  2. ./src/redis-server redis-cluster/7291/redis.conf
  3. ./src/redis-server redis-cluster/7292/redis.conf
  4. ./src/redis-server redis-cluster/7293/redis.conf
  5. ./src/redis-server redis-cluster/7294/redis.conf
  6. ./src/redis-server redis-cluster/7295/redis.conf
  7. ./src/redis-server redis-cluster/7296/redis.conf

是否启动了6个进程

  1. ps -ef|grep redis

3.Redis集群部署和数据分片 - 图21
创建集群
注意用绝对IP,不要用127.0.0.1

  1. cd /usr/local/soft/redis-6.0.9/src/
  2. redis-cli --cluster create 192.168.44.181:7291 192.168.44.181:7292 192.168.44.181:7293 192.168.44.181:7294 192.168.44.181:7295 192.168.44.181:7296 --cluster-replicas 1

Redis会给出一个预计的方案,对6个节点分配3主3从,如果认为没有问题,输入yes确认

  1. >>> Performing hash slots allocation on 6 nodes...
  2. Master[0] -> Slots 0 - 5460
  3. Master[1] -> Slots 5461 - 10922
  4. Master[2] -> Slots 10923 - 16383
  5. Adding replica 192.168.44.181:7295 to 192.168.44.181:7291
  6. Adding replica 192.168.44.181:7296 to 192.168.44.181:7292
  7. Adding replica 192.168.44.181:7294 to 192.168.44.181:7293
  8. >>> Trying to optimize slaves allocation for anti-affinity
  9. [WARNING] Some slaves are in the same host as their master
  10. M: 2058bd8fc0def0abe746816221c5b87f616e78ae 192.168.44.181:7291
  11. slots:[0-5460] (5461 slots) master
  12. M: 4e41266f2fb7944420d66235475318f5f9526cd8 192.168.44.181:7292
  13. slots:[5461-10922] (5462 slots) master
  14. M: 9ff8ac86b5faf3c0eca149f090800efea3b142e0 192.168.44.181:7293
  15. slots:[10923-16383] (5461 slots) master
  16. S: 8383088d3ce75732fc9acb31ca4bce68833028f7 192.168.44.181:7294
  17. replicates 9ff8ac86b5faf3c0eca149f090800efea3b142e0
  18. S: d185adbfa62133e30cee291b028eff451502ecca 192.168.44.181:7295
  19. replicates 2058bd8fc0def0abe746816221c5b87f616e78ae
  20. S: 634709cf14809976ea40b65615f816e31d424748 192.168.44.181:7296
  21. replicates 4e41266f2fb7944420d66235475318f5f9526cd8
  22. Can I set the above configuration? (type 'yes' to accept):

注意看slot的分布:

  1. 7291 [0-5460] (5461个槽)
  2. 7292 [5461-10922] (5462个槽)
  3. 7293 [10923-16383] (5461个槽)

输入yes确认,集群创建完成

  1. >>> Performing Cluster Check (using node 192.168.44.181:7291)
  2. M: 2058bd8fc0def0abe746816221c5b87f616e78ae 192.168.44.181:7291
  3. slots:[0-5460] (5461 slots) master
  4. 1 additional replica(s)
  5. S: 8383088d3ce75732fc9acb31ca4bce68833028f7 192.168.44.181:7294
  6. slots: (0 slots) slave
  7. replicates 9ff8ac86b5faf3c0eca149f090800efea3b142e0
  8. S: d185adbfa62133e30cee291b028eff451502ecca 192.168.44.181:7295
  9. slots: (0 slots) slave
  10. replicates 2058bd8fc0def0abe746816221c5b87f616e78ae
  11. M: 9ff8ac86b5faf3c0eca149f090800efea3b142e0 192.168.44.181:7293
  12. slots:[10923-16383] (5461 slots) master
  13. 1 additional replica(s)
  14. M: 4e41266f2fb7944420d66235475318f5f9526cd8 192.168.44.181:7292
  15. slots:[5461-10922] (5462 slots) master
  16. 1 additional replica(s)
  17. S: 634709cf14809976ea40b65615f816e31d424748 192.168.44.181:7296
  18. slots: (0 slots) slave
  19. replicates 4e41266f2fb7944420d66235475318f5f9526cd8
  20. [OK] All nodes agree about slots configuration.
  21. >>> Check for open slots...
  22. >>> Check slots coverage...
  23. [OK] All 16384 slots covered.

重置集群的方式是在每个节点上个执行cluster reset,然后重新创建集群
批量写入值

  1. cd /usr/local/soft/redis-6.0.9/redis-cluster/
  2. vim setkey.sh

脚本内容

  1. #!/bin/bash
  2. for ((i=0;i<20000;i++))
  3. do
  4. echo -en "helloworld" | redis-cli -h 192.168.44.181 -p 7291 -c -x set name$i >>redis.log
  5. done
  1. chmod +x setkey.sh
  2. ./setkey.sh

连接到客户端

  1. redis-cli -p 7291
  2. redis-cli -p 7292
  3. redis-cli -p 7293

每个节点分布的数据

  1. 127.0.0.1:7291> dbsize
  2. (integer) 6652
  3. 127.0.0.1:7292> dbsize
  4. (integer) 6683
  5. 127.0.0.1:7293> dbsize
  6. (integer) 6665

新增节点如何重新分片:
一个新节点add-node加入集群后,是没有slots的。

  1. redis-cli --cluster reshard 目标节点(IP端口)

这时会要求你输入分配的槽位,生成reshard计划,确定就会迁移数据

cluster管理命令

其他命令,比如添加节点、删除节点,重新分布数据:

  1. redis-cli --cluster help
  1. Cluster Manager Commands:
  2. create host1:port1 ... hostN:portN
  3. --cluster-replicas <arg>
  4. check host:port
  5. --cluster-search-multiple-owners
  6. info host:port
  7. fix host:port
  8. --cluster-search-multiple-owners
  9. reshard host:port
  10. --cluster-from <arg>
  11. --cluster-to <arg>
  12. --cluster-slots <arg>
  13. --cluster-yes
  14. --cluster-timeout <arg>
  15. --cluster-pipeline <arg>
  16. --cluster-replace
  17. rebalance host:port
  18. --cluster-weight <node1=w1...nodeN=wN>
  19. --cluster-use-empty-masters
  20. --cluster-timeout <arg>
  21. --cluster-simulate
  22. --cluster-pipeline <arg>
  23. --cluster-threshold <arg>
  24. --cluster-replace
  25. add-node new_host:new_port existing_host:existing_port
  26. --cluster-slave
  27. --cluster-master-id <arg>
  28. del-node host:port node_id
  29. call host:port command arg arg .. arg
  30. set-timeout host:port milliseconds
  31. import host:port
  32. --cluster-from <arg>
  33. --cluster-copy
  34. --cluster-replace
  35. help
  36. For check, fix, reshard, del-node, set-timeout you can specify the host and port of any working node in the cluster.

附录:

集群命令

cluster info :打印集群的信息
cluster nodes :列出集群当前已知的所有节点(node),以及这些节点的相关信息。
cluster meet :将 ip 和 port 所指定的节点添加到集群当中,让它成为集群的一份子。
cluster forget :从集群中移除 node_id 指定的节点(保证空槽道)。
cluster replicate :将当前节点设置为 node_id 指定的节点的从节点。
cluster saveconfig :将节点的配置文件保存到硬盘里面。

槽slot命令

cluster addslots [slot …] :将一个或多个槽(slot)指派(assign)给当前节点。
cluster delslots [slot …] :移除一个或多个槽对当前节点的指派。
cluster flushslots :移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点。
cluster setslot node :将槽 slot 指派给 node_id 指定的节点,如果槽已经指派给另一个节点,那么先让另一个节点删除该槽>,然后再进行指派。
cluster setslot migrating :将本节点的槽 slot 迁移到 node_id 指定的节点中。
cluster setslot importing :从 node_id 指定的节点中导入槽 slot 到本节点。
cluster setslot stable :取消对槽 slot 的导入(import)或者迁移(migrate)。

键命令

cluster keyslot :计算键 key 应该被放置在哪个槽上。
cluster countkeysinslot :返回槽 slot 目前包含的键值对数量。
cluster getkeysinslot :返回 count 个 slot 槽中的键

3.分片算法 redis slot

wps_eXXCLq9KIo.png

4.故障转移

1.slave发现自己的master变成了FAIL
2.将自己记录的集群currerntEpoch加一 (这是一个集群状态相关的概念,可以当做记录集群状态变更的递增版本号。每个集群节点,都会通过 server.cluster->currentEpoch 记录当前的 currentEpoch。
集群节点创建时,不管是 master 还是 slave,都置 currentEpoch 为 0。当前节点接收到来自其他节点的包时,如果发送者的 currentEpoch(消息头部会包含发送者的 currentEpoch)大于当前节点的currentEpoch,那么当前节点会更新 currentEpoch 为发送者的 currentEpoch。因此,集群中所有节点的 currentEpoch 最终会达成一致,相当于对集群状态的认知达成了一致)并广播 FAILOVER_AUTH_REQUEST(gossip协议 在集群中无论添加几个节点复杂度是恒定的)
3.其它节点收到该信息,只有master响应,判断请求这合法性,并发生FAILER_AUTH_ACK,对每个Epoch只发送一次ack
4.尝试failover的slave收集FAILER_AUTH_ACK,
5.超过半数确认变成新的Master
6.广播pong通知全部节点 篡位成功

5.特点

1.无中心架构
2.数据按照Slot存储分布在多个节点,节点间数据共享,可动态调节数据分布
3.可扩展,可线性扩展到1000个节点 官方不推荐超过1000个,节点可动态添加或者删除
4.降低运维成本部署容易
5.高可用,部分节点不可用时集群依然可用,可用通过增加slave做stanby做数据副本,能够实现自动故障转移,节点之间通过gossip协议交换状态,用投票机制完成slave到master的提升。

gossip协议3.Redis集群部署和数据分片 - 图23

举个例子
一个村庄(集群) 有 3户人家(redis group) 每户人家都有 爸爸(master) 和儿子(slave)两个人。
每个人都知道 整个村人名 和谁是哪户人家的爸爸 有一份对应关系的家谱(Cluster中的每个节点都维护一份在自己看来当前整个集群的状态
一天小明爸爸死了,他当户主了 先把自己手上的家谱升级一个版本再 就随机 告诉3个人 【小明爸爸死了 2203房是小明当Master了】这3个人受到消息,再各自告诉三个人直到所有人都知道这个消息,但是 master收到到消息还会写信给小明 【我知道了你爸爸死了,同意你当家主!】 当一半的 家庭回信收到了 小明就是2203的master了。

Cluster中的每个节点都维护一份在自己看来当前整个集群的状态,主要包括:

  1. 当前集群状态
  2. 集群中各节点所负责的slots信息,及其migrate状态
  3. 集群中各节点的master-slave状态
  4. 集群中各节点的存活状态及不可达投票

MobaXterm_8BLdS17ud9.png

因为任意节点都有整个集群的拓扑结构,所以jedis集群只需要传入任意一个节点就可以缓存全部数据的哈希槽了。