Redis集群搭建方案(Linux)

时间:2023-12-16 21:52:14

Redis简介

  redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、 list(链表)、set(集合)和zset(有序集合)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操 作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的 是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

Redis 是一个高性能的key-value数据库。 redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部 分场合可以对关系数据库起到很好的补充作用。它提供了Python,Ruby,Erlang,PHP客户端,使用很方便。

Redis集群介绍

  Redis 集群是一个提供在多个Redis间节点间共享数据的程序集. Redis集群并不支持处理多个keys的命令,因为这需要在不同的节点间移动数据,从而达不到像Redis那样的性能,在高负载的情况下可能会导致不可预料的错误. Redis 集群通过分区来提供一定程度的可用性,在实际环境中当某个节点宕机或者不可达的情况下继续处理命令.

  • Redis 集群的优势:

自动分割数据到不同的节点上. 整个集群的部分节点失败或者不可达的情况下能够继续处理命令.

  • Redis 集群的数据分片

Redis 集群没有使用一致性hash, 而是引入了哈希槽的概念. Redis 集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽.集群的每个节点负责一部分hash槽,举个例子,比如当前集群有3个节点,那么: 节点 A 包含 0 到 5500号哈希槽. 节点 B 包含5501 到 11000 号哈希槽. 节点 C 包含11001 到 16384号哈希槽. 这种结构很容易添加或者删除节点. 比如如果我想新添加个节点D, 我需要从节点 A, B, C中得部分槽到D上. 如果我像移除节点A,需要将A中得槽移到B和C节点上,然后将没有任何槽的A节点从集群中移除即可. 由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态.

  • Redis 集群的主从复制模型

为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型,每个节点都会有N-1个复制品. 在我们例子中具有A,B,C三个节点的集群,在没有复制模型的情况下,如果节点B失败了,那么整个集群就会以为缺少5501-11000这个范围的槽而不可用. 然而如果在集群创建的时候(或者过一段时间)我们为每个节点添加一个从节点A1,B1,C1,那么整个集群便有三个master节点和三个slave节点组成,这样在节点B失败后,集群便会选举B1为新的主节点继续服务,整个集群便不会因为槽找不到而不可用了 不过当B和B1 都失败后,集群人爱是不可用的.

  • Redis 一致性保证

Redis 并不能保证数据的强一致性. 这意味这在实际中集群在特定的条件下可能会丢失写操作. 第一个原因是因为集群是用了异步复制. 写操作过程: 客户端向主节点B写入一条命令. 主节点B向客户端回复命令状态. 主节点将写操作复制给他得从节点 B1, B2 和 B3. 主节点对命令的复制工作发生在返回命令回复之后, 因为如果每次处理命令请求都需要等待复制操作完成的话, 那么主节点处理命令请求的速度将极大地降低 —— 我们必须在性能和一致性之间做出权衡。

注意:Redis 集群可能会在将来提供同步写的方法。 Redis 集群另外一种可能会丢失命令的情况是集群出现了网络分区, 并且一个客户端与至少包括一个主节点在内的少数实例被孤立。

举个例子 假设集群包含 A 、 B 、 C 、 A1 、 B1 、 C1 六个节点, 其中 A 、B 、C 为主节点, A1 、B1 、C1 为A,B,C的从节点, 还有一个客户端 Z1 假设集群中发生网络分区,那么集群可能会分为两方,大部分的一方包含节点 A 、C 、A1 、B1 和 C1 ,小部分的一方则包含节点 B 和客户端 Z1 .

Z1仍然能够向主节点B中写入, 如果网络分区发生时间较短,那么集群将会继续正常运作,如果分区的时间足够让大部分的一方将B1选举为新的master,那么Z1写入B中得数据便丢失了. 注意, 在网络分裂出现期间, 客户端 Z1 可以向主节点 B 发送写命令的最大时间是有限制的, 这一时间限制称为节点超时时间(node timeout), 是 Redis 集群的一个重要的配置选项。

Redis安装

下载软件包, 登录Linux:

cd /usr/local/

wget http://download.redis.io/releases/redis-3.2.2.tar.gz

tar -zxvf redis-3.0.3.tar.gz

mv redis-3.0.3  redis

cd redis

make && make install

集群安装

1)172.20.13.229(server)

登录Server:

cd /usr/local/

mkdir redis_cluster  //创建集群目录

mkdir 7000 7001 7002  //分别代表三个节点    其对应端口 7000 7001 7002

创建7000节点为例,

cd ./7000

cp /usr/local/redis/redis.conf  ./    //拷贝到当前7000目录

vi redis.conf    //编辑配置  主要修改一下几个参数

daemonize    yes                          //redis后台运行
pidfile /var/run/redis_7000.pid //pidfile文件对应7000
port 7000 //端口7000
cluster-enabled yes //开启集群 把注释#去掉
cluster-config-file nodes.conf //集群的配置 配置文件首次启动自动生成
cluster-node-timeout 5000 //请求超时 设置5秒够了
appendonly yes //aof日志开启 有需要就开启,它会每次写操作都记录一条日志 配置好了,就相应地把这个修改后的配置文件拷贝到 7001 7002目录,注意要修改监听端口port 7001 7002. 接下来,启动服务,进入节点目录 依次执行 redis-server redis.conf 可以看到生成了appendonly.aof nodes.conf ps -ef | grep redis 查看是否启动成功
```
[root@mobancentos70 ~]# ps -ef|grep redis
root 20099 1 0 7月29 ? 00:09:04 redis-server 172.20.13.229:7002 [cluster]
root 20106 1 0 7月29 ? 00:09:09 redis-server 172.20.13.229:7000 [cluster]
root 20121 1 0 7月29 ? 00:09:10 redis-server 172.20.13.229:7001 [cluster]
``` netstat -tnlp | grep redis 可以看到redis监听端口
[root@mobancentos70 ~]# netstat -tnlp | grep redis
tcp 0 0 172.20.13.229:17000 0.0.0.0:* LISTEN 20106/redis-server
tcp 0 0 172.20.13.229:17001 0.0.0.0:* LISTEN 20121/redis-server
tcp 0 0 172.20.13.229:17002 0.0.0.0:* LISTEN 20099/redis-server
tcp 0 0 172.20.13.229:7000 0.0.0.0:* LISTEN 20106/redis-server
tcp 0 0 172.20.13.229:7001 0.0.0.0:* LISTEN 20121/redis-server
tcp 0 0 172.20.13.229:7002 0.0.0.0:* LISTEN 20099/redis-server
``` 我们除了看到 配置文件中设置的端口700* 还有700*+10000 (1700*), 前者是客户端访问的, 后者是集群内部节点之间访问的. 设置iptables开放上面所有端口

/sbin/iptables -I INPUT -p tcp --dport 7000 -j ACCEPT /sbin/iptables -I INPUT -p tcp --dport 7001 -j ACCEPT /sbin/iptables -I INPUT -p tcp --dport 7002 -j ACCEPT /sbin/iptables -I INPUT -p tcp --dport 17000 -j ACCEPT /sbin/iptables -I INPUT -p tcp --dport 17001 -j ACCEPT /sbin/iptables -I INPUT -p tcp --dport 17002 -j ACCEPT


2)172.20.13.230(server)
同上, 设置端口 7003 7004 7005

创建集群

官方提供了一个工具:redis-trib.rb  (/usr/local/redis/src/redis-trib.rb)

要运行这个我们首先要安装一下 Ruby:

yum -y install ruby ruby-devel rubygems rpm-build

再用 gem 这个命令来安装 redis接口:

 gem install redis    //等一会儿

确认所有的节点都启动后,接下来使用参数create 创建:

/usr/local/redis/src/redis-trib.rb  create  --replicas  1  172.20.13.229:7000  172.20.13.229:7001  172.20.13.230:7003  172.20.13.230:7004  172.20.13.230:7005  172.20.13.229:7002

解释下, --replicas 1 表示 自动为每一个master节点分配一个slave节点 上面有6个节点,程序会按照一定规则生成 3个master(主)3个slave(从)

前面已经提醒过的 防火墙一定要开放监听的端口,否则会创建失败。

运行中,提示Can I set the above configuration? (type 'yes' to accept): yes //输入yes

创建过程中的显示:
[root@mobancentos70 redis]# /usr/local/redis/src/redis-trib.rb  create  --replicas  1  172.20.13.229:7000  172.20.13.229:7001  172.20.13.230:7003  172.20.13.230:7004  172.20.13.230:7005  172.20.13.229:7002
>>> Creating cluster
>>> Performing hash slots allocation on 6 nodes...
Using 3 masters:
172.20.13.229:7000
172.20.13.230:7003
172.20.13.229:7001
Adding replica 172.20.13.230:7004 to 172.20.13.229:7000
Adding replica 172.20.13.229:7002 to 172.20.13.230:7003
Adding replica 172.20.13.230:7005 to 172.20.13.229:7001
M: 239e1e08b782005f7c9f73f64446159124e0f73a 172.20.13.229:7000
slots:0-5460 (5461 slots) master
M: bc55587ed472eca63b901576abadc17b6b98b3dd 172.20.13.229:7001
slots:10923-16383 (5461 slots) master
M: 5e2c320238a41ee28f82d835d3fc96d6a0e15ce9 172.20.13.230:7003
slots:5461-10922 (5462 slots) master
S: a0cef1fa6ac79cad9a69791d3f4dea9bd8ea0d92 172.20.13.230:7004
replicates 239e1e08b782005f7c9f73f64446159124e0f73a
S: c8e54ee24b91b71ae698af0f961eeac99139e502 172.20.13.230:7005
replicates bc55587ed472eca63b901576abadc17b6b98b3dd
S: 7414943a4513eee521fc0b7e37985f7d07fb5a4e 172.20.13.229:7002
replicates 5e2c320238a41ee28f82d835d3fc96d6a0e15ce9
Can I set the above configuration? (type 'yes' to accept): 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 172.20.13.229:7000)
M: 239e1e08b782005f7c9f73f64446159124e0f73a 172.20.13.229:7000
slots:0-5460 (5461 slots) master
M: bc55587ed472eca63b901576abadc17b6b98b3dd 172.20.13.229:7001
slots:10923-16383 (5461 slots) master
M: 5e2c320238a41ee28f82d835d3fc96d6a0e15ce9 172.20.13.230:7003
slots:5461-10922 (5462 slots) master
M: a0cef1fa6ac79cad9a69791d3f4dea9bd8ea0d92 172.20.13.230:7004
slots: (0 slots) master
replicates 239e1e08b782005f7c9f73f64446159124e0f73a
M: c8e54ee24b91b71ae698af0f961eeac99139e502 172.20.13.230:7005
slots: (0 slots) master
replicates bc55587ed472eca63b901576abadc17b6b98b3dd
M: 7414943a4513eee521fc0b7e37985f7d07fb5a4e 172.20.13.229:7002
slots: (0 slots) master
replicates 5e2c320238a41ee28f82d835d3fc96d6a0e15ce9
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

检查集群状态:

/usr/local/redis/src/redis-trib.rb check 172.20.13.229:7000

[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.```

集群测试

测试 Redis 集群比较简单的办法就是使用 redis-rb-cluster 或者 redis-cli , 接下来我们将使用 redis-cli 为例来进行演示:

[root@mobancentos70 redis_cluster]# redis-cli -c -h 172.20.13.229 -p 7000
172.20.13.229:7000> set foo bar
-> Redirected to slot [12182] located at 172.20.13.229:7001
OK
172.20.13.229:7001> set hello world
-> Redirected to slot [866] located at 172.20.13.229:7000
OK
172.20.13.229:7000> get foo
-> Redirected to slot [12182] located at 172.20.13.229:7001
"bar"
172.20.13.229:7001> get hello
-> Redirected to slot [866] located at 172.20.13.229:7000
"world"
172.20.13.229:7000>``` redis-cli 对集群的支持是非常基本的, 所以它总是依靠 Redis 集群节点来将它转向(redirect)至正确的节点。一个真正的(serious)集群客户端应该做得比这更好:

Jedis操作集群

Maven依赖:

<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.7.2</version>
<type>jar</type>
<scope>compile</scope>
</dependency>

测试1:

 public static void main(String[] args) {
String key = "foo";
// 这东西 可以直接看到key 的分片数,就能知道放哪个 节点
System.out.println(JedisClusterCRC16.getSlot(key));
Set<HostAndPort> jedisClusterNodes = new HashSet<HostAndPort>();
jedisClusterNodes.add(new HostAndPort("172.20.13.229", 7000));
jedisClusterNodes.add(new HostAndPort("172.20.13.229", 7001));
jedisClusterNodes.add(new HostAndPort("172.20.13.230", 7003));
// 3个master 节点
JedisCluster jc = new JedisCluster(jedisClusterNodes);
System.out.println(jc.get(key));
jc.setnx(key, "bar");
String value = jc.get(key);
System.out.println(value);
}
console:
12182
bar
bar

  

测试2:

public static void main(String[] args) {
Set<HostAndPort> jedisClusterNodes = new HashSet<HostAndPort>();
jedisClusterNodes.add(new HostAndPort("172.20.13.229", 7000));
jedisClusterNodes.add(new HostAndPort("172.20.13.229", 7001));
jedisClusterNodes.add(new HostAndPort("172.20.13.230", 7003));
JedisCluster jc = new JedisCluster(jedisClusterNodes);
for (int i = 1; i <= 10000; i++) {
long start = System.currentTimeMillis();
jc.set("k:" + i, "v" + i);
System.out.print("set " + i +"th value in " + (System.currentTimeMillis() - start) + " ms");
start = System.currentTimeMillis();
jc.get("k:" + i);
System.out.println(", get " + i +"th value in " + (System.currentTimeMillis() - start)
+ " ms");
}
}
console:
set 9976th value in 1 ms, get 9976th value in 1 ms
set 9977th value in 1 ms, get 9977th value in 1 ms
set 9978th value in 0 ms, get 9978th value in 1 ms
set 9979th value in 1 ms, get 9979th value in 1 ms
set 9980th value in 1 ms, get 9980th value in 1 ms
set 9981th value in 1 ms, get 9981th value in 0 ms
set 9982th value in 1 ms, get 9982th value in 1 ms
set 9983th value in 1 ms, get 9983th value in 0 ms
set 9984th value in 1 ms, get 9984th value in 1 ms
set 9985th value in 1 ms, get 9985th value in 0 ms
set 9986th value in 1 ms, get 9986th value in 1 ms
set 9987th value in 1 ms, get 9987th value in 0 ms
set 9988th value in 1 ms, get 9988th value in 1 ms
set 9989th value in 1 ms, get 9989th value in 1 ms
set 9990th value in 1 ms, get 9990th value in 1 ms
set 9991th value in 1 ms, get 9991th value in 1 ms
set 9992th value in 1 ms, get 9992th value in 1 ms
set 9993th value in 0 ms, get 9993th value in 1 ms
set 9994th value in 1 ms, get 9994th value in 1 ms
set 9995th value in 0 ms, get 9995th value in 1 ms
set 9996th value in 2 ms, get 9996th value in 0 ms
set 9997th value in 1 ms, get 9997th value in 1 ms
set 9998th value in 1 ms, get 9998th value in 1 ms
set 9999th value in 1 ms, get 9999th value in 1 ms
set 10000th value in 0 ms, get 10000th value in 1 ms

  

测试反馈:

1.集群正常,基本操作都OK

2.如果某个master 挂了,比如7001, 集群依然可用,会存放到对应的salve 7004 上去。

3.如果master -slave 都挂了,会导致 整个集群不可用,异常,因此最好配有M-S 的结构

4.我默认配置,有rdb 和 aof 持久化,因此master 挂了,重启,数据可以从salve 上恢复

5.存放的key 会根据返回的位置,放在不同的slot 上,实现均衡

如果觉得有疑问或者对你有帮助 欢迎评论。

原文地址:http://www.cnblogs.com/owenma/p/7383959.html

作者:森林木马

如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,但是未经作者本人同意

转载文章之后必须在 文章页面明显位置给出作者和原文连接否则保留追究法律责任的权利。