本文为博主原创,未经允许不得转载:
目录:
1. 哨兵模式与集群模式对比
2. Redis 集群架构搭建
3. 集群原理分析
4. 集群元数据维护方式对比
5. redis 分布式寻址
6. 集群选举过程
7. spring boot 接入
1. 哨兵模式与集群模式对比
哨兵模式架构通过哨兵 sentinel 工具来监控master节点的状态,如果master节点异常,则会做主从切换,将某一台slave作为master,哨兵的配置略微复杂,并且性能和高可用性等各方面表现一般,特别是在主从切换的瞬间存在访问瞬断的情况,而且哨兵模式只有一个主节点对外提供服务,没法支持很高的并发,且单个主节点内存也不宜设置得过大,否则会导致持久化文件过大,影响数据恢复或主从同步的效率。
集群模式:redis集群是一个由多个主从节点群组成的分布式服务器群,它具有复制、高可用和分片特性。Redis集群不需要sentinel哨兵∙也能完成节点移除和故障转移的功能。需要将每个节点设置成集群模式,这种集群模式没有中心节点,可水平扩展,据官方文档称可以线性扩展到上万个节点(官方推荐不超过1000个节点)。redis集群的性能和高可用性均优于之前版本的哨兵模式,且集群配置非常简单。
2. Redis 集群架构搭建:
redis集群需要至少三个master节点。这里用一台机器搭建三个master节点,并且给每个master再搭建一个slave节点,总共6个redis节点,
2.1. /usr/local下创建文件夹redis‐cluster,然后在其下面分别创建6个文件夹,以端口进行区分:7001,7002,8001,8002,9001,9002
2.2. 把之前的redis.conf配置文件copy到8001下,修改如下内容(并配置每个端口文件夹下的配置)
(1)daemonize yes (2)port 8001(分别对每个机器的端口号进行设置) (3)pidfile /var/run/redis_8001.pid # 把pid进程号写入pidfile配置的文件 (4)dir /usr/local/redis-cluster/8001/(指定数据文件存放位置,必须要指定不同的目录位置,不然会丢失数据) (5)cluster-enabled yes(启动集群模式) (6)cluster-config-file nodes-8001.conf(集群节点信息文件,这里800x最好和port对应上) (7)cluster-node-timeout 10000 (8)# bind 127.0.0.1(bind绑定的是自己机器网卡的ip,如果有多块网卡可以配多个ip,代表允许客户端通过机器的哪些网卡ip去访问,内网一般可以不配置bind,注释掉即可) (9)protected-mode no (关闭保护模式) (10)appendonly yes 如果要设置密码需要增加如下配置: (11)requirepass test (设置redis访问密码) (12)masterauth test (设置集群节点间访问密码,跟上面一致)
2.3. 分别启动每个 redis 实例,并查看是否启动成功:
/usr/local/redis-5.0.3/src/redis-server /usr/local/redis-cluster/700*/redis.conf /usr/local/redis-5.0.3/src/redis-server /usr/local/redis-cluster/800*/redis.conf /usr/local/redis-5.0.3/src/redis-server /usr/local/redis-cluster/900*/redis.conf
2.4. 用redis-cli创建整个redis集群。
如果redis 实例是在不同的服务器之间,需要关闭防火器,关闭防火墙命令:
# systemctl stop firewalld # 临时关闭防火墙
# systemctl disable firewalld # 禁止开机启动
创建集群:
/usr/local/redis-5.0.3/src/redis-cli -a test --cluster create --cluster-replicas 1 112.125.26.68:7001 112.125.26.68:7002 112.125.26.68:8001 112.125.26.68:8002 1112.125.26.68:9001 112.125.26.68:9002
2.5. 集群验证
(1)连接任意一个客户端即可:./redis-cli -c -h -p (-a访问服务端密码,-c表示集群模式,指定ip地址和端口号) 如:/usr/local/redis-5.0.3/src/redis-cli -a test -c -h 112.125.26.68 -p 800* (2)进行验证: cluster info(查看集群信息)、cluster nodes(查看节点列表) (3)进行数据操作验证 (4)关闭集群则需要逐个进行关闭,使用命令: /usr/local/redis-5.0.3/src/redis-cli -a zhuge -c -h 112.125.26.68 -p 800* shutdown
3. 集群原理分析:
4. 集群元数据维护方式对比:
集群元数据的维护有两种方式:集中式、Gossip 协议。Redis cluster 节点间采用 gossip 协议进行通信。
集中式是将集群元数据(节点信息、故障等等)集中存储在某个节点上。集中式元数据集中存储的一个典型代表,就是大数据领域的 storm
。它是分布式的大数据实时计算引擎,是集中式的元数据存储的结构,底层基于 zookeeper(分布式协调的中间件)对所有元数据进行存储维护。
集中式的好处在于,元数据的读取和更新,时效性非常好,一旦元数据出现了变更,就立即更新到集中式的存储中,其它节点读取的时候就可以感知到;不好在于,所有的元数据的更新压力全部集中在一个地方,可能会导致元数据的存储有压力。
Redis 维护集群元数据采用另一个方式, gossip
协议,所有节点都持有一份元数据,不同的节点如果出现了元数据的变更,就不断将元数据发送给其它的节点,让其它节点也进行元数据的变更。
gossip 好处在于,元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续打到所有节点上去更新,降低了压力;不好在于,元数据的更新有延时,可能导致集群中的一些操作会有一些滞后。
5. 分布式寻址算法:
hash 算法, 一致性hash 算法,hash slot 算法
redis 集群使用的是 hash slot 算法:Redis cluster 有固定的 16384
个 hash slot,对每个 key
计算 CRC16
值,然后对 16384
取模,可以获取 key 对应的 hash slot。
Redis cluster 中每个 master 都会持有部分 slot,比如有 3 个 master,那么可能每个 master 持有 5000 多个 hash slot。hash slot 让 node 的增加和移除很简单,增加一个 master,就将其他 master 的 hash slot 移动部分过去,减少一个 master,就将它的 hash slot 移动到其他 master 上去。移动 hash slot 的成本是非常低的。客户端的 api,可以对指定的数据,让他们走同一个 hash slot,通过 hash tag
来实现。
任何一台机器宕机,另外两个节点,不影响的。因为 key 找的是 hash slot,不是机器。
HASH_SLOT = CRC16(key) mod 16384
6. 集群选举过程:
每个从节点,都根据自己对 master 复制数据的 offset,来设置一个选举时间,offset 越大(复制数据越多)的从节点,选举时间越靠前,优先进行选举。所有的 master node 开始 slave 选举投票,给要进行选举的 slave 进行投票,如果大部分 master node (N/2 + 1)
都投票给了某个从节点,那么选举通过,那个从节点可以切换成 master。从节点执行主备切换,从节点切换为主节点。
7. spring boot 接入
7.1 引入pom 依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency>
7.2 配置文件
server: port: 8080 spring: redis: database: 0 timeout: 3000 password: test cluster: nodes: 112.125.26.68:7001 112.125.26.68:7002 112.125.26.68:8001 112.125.26.68:8002 1112.125.26.68:9001 112.125.26.68:9002 lettuce: pool: max-idle: 50 min-idle: 10 max-active: 100 max-wait: 1000
7.3 代码实现:
@RestController public class TestController { private static final Logger logger = LoggerFactory.getLogger(TestController.class); @Autowired private StringRedisTemplate stringRedisTemplate; @RequestMapping("/test_cluster") public void testCluster() throws InterruptedException { stringRedisTemplate.opsForValue().set("test", "666"); System.out.println(stringRedisTemplate.opsForValue().get("test")); } }