本文所用的Redis是5.0.8所以和旧版的4.0.X不同的地方在于创建集群的方式的变化
所用系统:Centos7.7 64位
本文设置的统一密码为:shunleite
Spring Boot 2.0+
在Redis集群中,所有的Redis节点彼此互联,节点内部使用的二进制协议优化传输速度和带宽。
当一个节点挂掉后,集群中超过半数的节点坚持失效时才认为该节点已失效。Redis集群中的任意
节点都以和Java客户端连接。Redis集群上的数据分配是才用哈希槽(HASH SLOT),Redis集群内置
了16384个哈希槽,当有数据需要储存时,Redis会首先使用CRC16算法对Key进行计算,将计算获得
的结果对16384取余,这样每一个key都会对应一个取值在0~16383之间的哈希槽,Redis则根据这个
余数将该条数据储存到对应的Redis节点上,程序猿们就可根据每个Redis实例的性能来调整每个Redis
实例上哈希槽的分布范围
需要了解一些概念:
一个master(主)可以拥有多个slave(从),而一个slave(从)只能拥有一个master(主)
另外官方说明每个集群中至少需要三个主master才能正常运行
本案例采取每个服务器上对应的每个端口来区别每一个不同的Redis服务器
主节点:111.67.194.61:6661,111.67.194.60:6662,111.67.194.63:6663
从节点:111.67.194.64:6664,111.67.194.65:6665,111.67.202.206:6666
两个用来测试添加主从数据库的节点: 111.67.194.70:6667(主),111.67.194.108:6668(从)
因为Redis集群工具依赖Ruby环境,首先需要安装Ruby环境,又因为centos7 yum库中
的默认的Ruby版本太低,所以就搞个便捷的RVM工具(以下步骤是下载配置rvm公钥)
gpg2 --keyserver hkp://keys.gnupg.net --recv-keys D39DC0E3
#上面个木有成功就下面那个
gpg2 --keyserver hkp://pool.sks-keyservers.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
curl -L get.rvm.io | bash -s stable
source /usr/local/rvm/scripts/rvm
看下RVM是否安装成功
rvm list known
如图所示出现了绿色那就成功了
选个比较稳定的版本安(教程所出时间为2020/4/5)
rvm install 2.6.5
gem install redis
①下载Redis
wget http://download.redis.io/releases/redis-5.0.8.tar.gz
没有wget命令的话就需要安装,然后再执行上面命令
yum install wget
②安装Redis
先创建个redisCluster文件夹,将下载的文件移动过去然后再安装
mkdir redisCluster
cp -f ./redis-5.0.8.tar.gz ./redisCluster
cd redisCluster
tar -zxvf redis-5.0.8.tar.gz
cd redis-5.0.8
make MALLOC=libc
make install
③其它每台各进行第②小步(该操作是每个服务器都必须要的操作)
然后在每台服务器(包括老大哥服务器111.67.194.61)的redisCluster下创建一个文件夹(以便于生产后面在单台上添加)
分别为文件夹6661 6662 6663 6664 6665 6666 (与前面规划部分一致)
另外每个文件夹下面都需要把上层目录中redis-5.0.8目录下的redis.conf文件复制一份到该目录
cp -f ../redis-5.0.8/redis.conf ./
接着对每台服务器666x文件夹下的redis.conf的配置进行修改,所修改的配置如下(是修改不是添加)
port 6661
#bind 127.0.0.1
cluster-enabled yes
cluster-config-file nodes-6661.conf
protected no
daemonize yes
requirepass shunleite
masterrauth shunleite
提一下:如果使用vi的话那你就需要/字符串模式慢慢找然后修改
第一为该节点端口与文件夹名一一对应
第二行是将bind部分注释掉,因为默认是本地乱接,注释掉就可以外网连接Redis了
第三行表示开启集群
第四行表示该集群节点的配置文件(注意nodes-666x.conf格式配置方便管理)
第五行就是关闭保护毕竟你第七行用了密码
第六行表示允许Redis后台运行
第七行就是设置密码了
第八行因为第七行设置了密码所以他就需要个密码认证
④开启Redis实例
redis-server /root/redisCluster/6661/redis.conf
redis-server /root/redisCluster/6662/redis.conf
redis-server /root/redisCluster/6663/redis.conf
redis-server /root/redisCluster/6664/redis.conf
redis-server /root/redisCluster/6665/redis.conf
redis-server /root/redisCluster/6666/redis.conf
⑤为了能够远程连接上Redis,还需要关闭防火墙(或者开启相应端口)
systemctl stop firewalld.service
systemctl disable firewalld.service
回到老大哥服务器(111.67.194.61)后切到redisCluster目录下
提一下:因为设置了密码,一般来说,Redis5.0.x以下的版本是需要将redis-trib.rb文件复制到redisCluster目录下的
然而我们用的不是(嘻嘻)如果是就需要移
而且还需修改复制后的redis-trib.rb文件,将@r = Redis.new(:host => @info[:host], :port => @info[:port], :timeout => 60)这行
修改成@r = Redis.new(:host => @info[:host], :port => @info[:port], :timeout => 60, :password=>"shunleite")
然后操作就把后面的redis-cli -a shunleite --cluster的部分改成./redis-trib.rb(在你redisCluster目录下)即可(虽然有些细微的不一样比如删除啥
子,但大多一样,毕竟官方只是进行移植集成操作)
创建集群
redis-cli -a shunleite --cluster create 111.67.194.61:6661 111.67.194.60:6662 111.67.194.63:6663 111.67.194.64:6664 111.67.194.65:6665 111.67.202.206:6666 --cluster-replicas 1
replicas表示每个主节点的salve数量。集群在创建过程中都将分配到一个唯一的id并分配一段slot
成功效果如下:M代码主节点,S代码从节点,slots就是分配的slot段,replicates后面的Id代表所对应的主节点Id
-h 表示实例主机地址 -p表示登陆的集群的端口,-a表示要登陆的集群的密码,-c则便是以集群的方式登陆,登陆成功后随便登陆一个节点
使用cluster nodes命令查询集群节点的信息,cluster info命令查询集群状态信息,此时你会发现已经自动配置好了slot
redis-cli -h 111.67.194.61 -p 6661 -a shunleite -c
cluster info
cluster nodes
业务增长的情况下,我们避免不了增加主节点,增加主节点也就是构建主节点实例
①按Redis集群配置环节中的第2个步骤的②与③,④,⑤小步骤的模子在 111.67.194.70的节点上创建文件夹6667(配置
和上面步骤一样,这里就不演示了)
②开启Redis也一样模子:redis-server /root/redisCluster/6667/redis.conf
③接着登陆到老大哥服务器(111.67.194.61)执行添加节点操作,add-node就是添加节点的操作,中间的参数是要添加的Redis
实例地址,最后一个参数是集群中的实例地址
redis-cli -a shunleite --cluster add-node 111.67.194.70:6667 111.67.194.61:6661
登陆任意集群中的实例,执行cluster nodes就会发现实例地址已经添加成功,但是slot没有分配,注意节点的6667的node ID。
此新节点的ID为:d35877f5646c96fdfce71bdf66f588e8c90fd520
④重新分配下slot(本案例给新增主节点分配1000个slot)
因为之前的操作已经将slot分配完了,所以导致没有slot分配给新节点,也就是书新节点没有储存数据的机会,所以我们可以
另外三个主节点中拿出一部分分配给新节点实例,我这里总共分配1000个slot给新节点
如果是把n个的slot分配给谁,输入接收这n个slot的Redis实例的ID,这个ID添加的时候就可以看到,或者登陆进集群控制器后
使用上面的clustern nodes命令也可以看到。例如从端口为6661的实例拿出n个slot分给6667的实例,那么输入6661的id后按回
车键,在输入done按返回键即可
登陆老大哥服务器(111.67.194.61)执行如下代码重新分配,all表示均摊原来原有的实例
redis-cli -a shunleite --cluster reshard 111.67.194.61:6661
#接着填入1000
#在填写节点的ID
#填入all
从节点的增加就容易很多了
①还是按Redis集群配置环节中的第2个步骤的②与③,④,⑤小步骤的模子在 111.67.194.108的节点上创建文件夹6668然后
启动它
②登陆老大哥服务器(111.67.194.61)添加从节点需要指定该解答你的master id,–cluster-master-id后面的参数依次表示:
从节点master(主节点)的id(这里是以6667为例子),从节点的地址,集群中任意一个实例的地址。代码如下:
redis-cli -a shunleite --cluster add-node --cluster-slave --cluster-master-id d35877f5646c96fdfce71bdf66f588e8c90fd520 111.67.194.108:6668 111.67.194.61:6661
登陆集群控制器后输入cluster nodes便可以看到了
5.0.x以下版本的Redis增加新从节点需要在redisCluster目录下使用如下命令增加子节点(命令意思和上面基本一致):
./redis-trib.rb add-node --slave --master-id d35877f5646c96fdfce71bdf66f588e8c90fd520 111.67.194.108:6668 111.67.194.61:6661
删除节点登录到老大哥服务器(111.67.194.61)使用如下命令模子删除即可:
del-node代表删除节点,后面的参数就是要删除的节点,如果删除的节点占有slot,则会删除失败,那么你按照第四大步骤
Redis集群主节点和从节点的增加与删除第1步骤中第④小步将要删除节点的全部slot分配出去,然后执行该命令即可,从节点
直接该命令即可删
redis-cli -a shunleite --cluster del-node 111.67.194.61:6661
不同于单实例的Redis,Redis集群整合Spring Boot需要程序猿们手动配置
jedis连接池是基于apache.commons-pool2实现的,在侯建连接池对象时,需要提供连接池对象的配置对象,即JedisPoolConfig,
JedisPoolConfig继承自GenericObjectPoolConfig,我们可以通过这个配置对象对连接池进行相关参数的配置(如最大连接数,
最大空闲数等)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
此时使用application.yml就方便很多,所以就直接删除原来的application.properties,新建它了
因为使用的host很多,所以就创建两个一一对应的数组即可,也方便生产中后续增删节点的操作
max-total代表连接池最大连接数,max-idle代表连接池中最大空闲连接数, max-wait-millis表示最大阻塞等待时间,默认为-1
表示没有限制,min-idle表示连接池最小连接数
spring:
redis:
cluster:
ports:
- 6661
- 6662
- 6663
- 6664
- 6665
- 6666
- 6667
- 6668
hosts:
- 111.67.194.61
- 111.67.194.60
- 111.67.194.63
- 111.67.194.64
- 111.67.194.65
- 111.67.202.206
- 111.67.194.70
- 111.67.194.108
poolConfig:
max-total: 8
max-idle: 8
max-wait-millis: -1
min-idle: 0
我们来分析一下
在SpringBoot的自动配置类中提供了RedisAutoConfiguration进行Redis的配置,源码如下:
这一段源码可以看出,默认的application.properties中的配置信息将被注入RedisProperties中,如果没有提供RedisTemplate或者
StringRedisTemplate实例,那么Spring Boot默认会提供这两个实例,RedisTemplate和StringRedisTemplate都提供了Redis的
基本操作
由于本案例使用的是Jedis,再加上JedisConnectionFactory继承自RedisConnection
所以我们需要手动配置与提供Redis集群配置,JedisConnectionFactory,RedisTemplate,StringRedisTemplate。
@Configuration
//通过ConfigurationProperties注解声明配置文件前缀,配置文件中定义的ports,hosts数组及连接配置信息都将被注入port、host、poolConfig三个属性中
@ConfigurationProperties("spring.redis.cluster")
public class RedisConfig {
List<Integer> ports;
List<String> hosts;
JedisPoolConfig poolConfig;
//配置RedisClusterConfiguration实例,设置Redis登陆密码以及Redis节点信息
@Bean
RedisClusterConfiguration redisClusterConfiguration(){
RedisClusterConfiguration configuration = new RedisClusterConfiguration();
List<RedisNode> nodes = new ArrayList<>();
for(int i = 0;i<ports.size();i++){
nodes.add(new RedisNode(hosts.get(i),ports.get(i)));
}
//配置RedisClusterConfiguration实例,摄制Redis登陆密码以及Redis节点信息
configuration.setPassword(RedisPassword.of("shunleite"));
configuration.setClusterNodes(nodes);
return configuration;
}
//根据RedisClusterConfiguration实例以及连接池配置信息Jedis连接工厂JedisConnectionFactory
@Bean
JedisConnectionFactory jedisConnectionFactory(){
JedisConnectionFactory factory = new JedisConnectionFactory(redisClusterConfiguration(),poolConfig);
return factory;
}
//根据RedisConnectionFactory创建RedisTemplate和StringRedisTemplate,同时配置key和value的系列化方式。
// 有了RedisTemplate和StringRedisTemplate,剩下的用法就和单一使用Redis用法一致
@Bean
RedisTemplate redisTemplate(){
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(jedisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
return redisTemplate;
}
@Bean
StringRedisTemplate stringRedisTemplate(){
StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(jedisConnectionFactory());
stringRedisTemplate.setKeySerializer(new StringRedisSerializer());
stringRedisTemplate.setValueSerializer(new StringRedisSerializer());
return stringRedisTemplate;
}
@Override
public String toString() {
return "RedisConfig{" +
"ports=" + ports +
", hosts=" + hosts +
", poolConfig=" + poolConfig +
'}';
}
public List<Integer> getPorts() {
return ports;
}
public void setPorts(List<Integer> ports) {
this.ports = ports;
}
public List<String> getHosts() {
return hosts;
}
public void setHosts(List<String> hosts) {
this.hosts = hosts;
}
public JedisPoolConfig getPoolConfig() {
return poolConfig;
}
public void setPoolConfig(JedisPoolConfig poolConfig) {
this.poolConfig = poolConfig;
}
}
4.创建实体类
public class Customer implements Serializable {
private String name;
private String email;
@Override
public String toString() {
return "Customer{" +
"name='" + name + '\'' +
", email='" + email + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
StringRedisTemplate是RedisTemplate子类,StringRedisTemplate中的key和value都是字符串,采用的
系列化方案是StringRedisSerializer,而RedisTemplate则可以用来操作对象,RedisTemplate采用的序列化
方案是JdkSerializationRedisSerializer。无论是StringRedisTemplate还是RedisTemplater,操作Redis的方法都一样
StringRedisTemplate和RedisTemplate都是通过opsForValue,opsForZSet或者opsForSet等方法首先获取一个操作对象,
再使用该操作对象完成数据的读写
@RestController
public class CustomerController {
@Autowired
RedisTemplate redisTemplate;
@Autowired
StringRedisTemplate stringRedisTemplate;
@GetMapping("/test1")
public void test1(){
ValueOperations ops = redisTemplate.opsForValue();
Customer customer = new Customer();
customer.setName("shunzi");
customer.setEmail("[email protected]");
ops.set("customer",customer);
System.out.println("customer = " + ops.get("customer"));
ValueOperations<String,String> ops2 = stringRedisTemplate.opsForValue();
ops2.set("ceshi","数据正确");
System.out.println("ceshi = " + ops2.get("ceshi"));
}
}
get customer
get ceshi
RedisCluster会很负责的将查询请求Redirected到相应的实例上去。
教程很长,细节地方很多,所以耐心看完后再实践一遍。☺
差点忘了,下面提供有案例源码
百度网盘:https://pan.baidu.com/s/17pNKWjfu_Fb70_x12m5ckQ (snet)
普通的下载:http://t.cn/A6Z1r6tG
蓝奏云:https://lanzous.com/ib1ip9e