转载请注明出处
SpringBoot+Redis-Cluster集群搭建及调优
集群配置
3主3从,本文以3主3从为例子讲解,但最好大家在搭建用于公司生产环境的集群时,搭建3主6从,这样不会一个节点宕机导致全部宕机,可靠性更高,会搭3主3从就会搭3主6从。
服务器配置
六台(14~19)Linux服务器简易版,8G运行内存
具体搭建步骤
安装所必须的一些工具包
如果已经安装过则无需安装
yum install gcc
yum -y install wget
yum -y install vim*
安装redis
在14服务器上进行以下操作
#进入安装目录,下载redis包并安装
cd /usr/local/
wget https://download.redis.io/releases/redis-5.0.8.tar.gz
tar xzf redis-5.0.8.tar.gz
cd redis‐5.0.8
make
#启动并指定配置文件
src/redis‐server redis.conf(使用后台启动,修改redis.conf里的daemonize改为yes)
#验证启动是否成功
ps -ef | grep redis
#进入redis客户端
src/redis-cli -p 6379 (默认6379端口)
#退出客户端
quit
到目前位置,已经成功搭建了一个单击redis,下面介绍如何使其成为集群
集群搭建
安装其他redis节点
在另外5台服务器上(15~19),同样地按以上步骤安装redis
修改14上面的redis.conf配置文件
(1)daemonize yes
(2)port 8001(分别对每个机器的端口号进行设置)
(3)pidfile /var/run/redis_8001.pid
(4)logfile “8001.log”
(5)dir /usr/local/redis-cluster/8001/(指定数据文件存放位置,必须要指定不同的目录位置,不然会丢失数据)
(6)cluster-enabled yes(启动集群模式)
(7)cluster-config-file nodes-8001.conf(集群节点信息文件,这里800x好和port对应上)
(8)cluster-node-timeout 5000
(9)# bind 127.0.0.1(去掉bind绑定访问ip信息)
(10) protected-mode no (关闭保护模式)
(11) appendonly yes
#如果要设置密码需要增加如下配置:
(12) requirepass 123456 (设置redis访问密码)
(13) masterauth 123456 (设置集群节点间访问密码,跟上面一致)
复制修改后的配置文件至其他节点并替换
替换后,将配置文件的端口改为相应节点的端口,使用命令批量替换
:%s/源字符串/目的字符串/g
分别启动所有redis节点并验证启动成功
使用上面安装redis的命令启动即可,不再赘述。
设置防火墙开放指定节点,保证节点间正常通信
如果未开放端口,会出现创建集群命令超时的现象
firewall-cmd --zone=public --add-port=此处填写端口/tcp --permanent
firewall-cmd --reload
使用cli创建整体的redis集群
进入我们第一个搭建的redis节点14,执行如下命令:
注意:cli命令在redis5.0之后才会有(之前是使用meet命令已被淘汰);–cluster-replicas 1代表一个主节点对应一个从节点,也就是三主三从,默认哈希槽平均分配。
/usr/local/redis-5.0.8/src/redis-cli -a 123456 --cluster create --cluster-replicas 1 192.168.2.14:8001 192.168.2.15:8002 192.168.2.16:8003 192.168.2.17:8004 192.168.2.18:8005 192.168.2.19:8006
创建成功后会有如下提示
RedisCluster帮助命令
吃透这个命令里面的东西蛮重要,玩转cluster这是第一步
/usr/local/redis-5.0.8/src/redis-cli -a 123456 --cluster help
验证集群
cluster info(查看集群信息)、cluster nodes(查看节点列表)
./redis-cli -c -h -p(连接任意一个客户端即连接集群,‐a访问服务端密码,‐c表示集群模式,指定ip地址和端口号)
如果不加-c,倒是也可以连上,但如果连节点1并执行set操作,此时算出来的哈希槽命中了节点二,就会报错
如/usr/local/redis-5.0.8/src/redis-cli -a 123456 -h 192.168.2.101 -p 8001 -c
使用程序或可视化工具进行数据操作
关闭集群
/usr/local/redis-5.0.8/src/redis-cli -a 123456 -c -h 192.168.2.102 -p 8002 shutdown
集群扩容
新增一对主从节点,其中主节点要先加入,加入后通过cluster nodes命令复制其ID备用
/usr/local/redis-5.0.8/src/redis-cli --cluster add-node 新增主节点IP:端口 原任意一节点IP:端口
/usr/local/redis-5.0.8/src/redis-cli --cluster add-node 新增从节点IP:端口 原任意一节点IP:端口 --cluster-slave --cluster-master-id 主节点ID
分配哈希槽
/usr/local/redis-5.0.8/src/redis-cli --cluster reshard 原任意一节点IP:端口
How many slots do you want to move(from 1 to 16384) 此处填写希望分配的哈希槽数
What is the reciving node ID 此处填写新加入的主节点ID
Please enter all the source node IDs.
Type 'all' to use all tho nodes as sourco nodos for the hash slots.
Type 'done' once you entered all the source nodes IDs .
Source node #1:此处写all,就是从别的节点平均取出哈希槽分配给新节点,也可以写节点ID来限制只有哪些节点分配哈希槽给新节点。
例如:
//平均分
Source node #1:all
//只从节点1分哈希槽出去
Source node #1:节点1的ID
//从节点1和节点2平均分哈希槽出去
Source node #1:节点1的ID
Source node #2:节点2的ID
注意:
新分配的哈希槽不一定是连续的
槽位迁移了,数据也会被迁移到新的节点上
如果其中一对主从节点挂掉了,整个集群将不可用,更改一条配置可将其变为可用,但这样就会存在丢失数据的风险:
//改为no是可用
cluster-replica-no-coverage no
SpringBoot整合redis-cluster
依赖
<!--默认是lettuce客户端-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- redis依赖commons-pool 这个依赖一定要添加 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
yml或properties文件
两种配置文件二选一即可,推荐yml,因为优雅并且社区文档多,现在很少人用properties了
yml
spring:
redis:
password: 123456 #密码
lettuce: #lettuce连接池配置
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: 1000
shutdown-timeout: 100
cluster: #集群配置
nodes:
- 192.168.2.14:8001
- 192.168.2.15:8002
- 192.168.2.16:8003
- 192.168.2.17:8004
- 192.168.2.18:8005
- 192.168.2.19:8006
max-redirects: 3
properties
spring.redis.password=123456
spring.redis.lettuce.pool.max-active=1000
spring.redis.lettuce.pool.min-idle=10
spring.redis.lettuce.pool.max-idle=50
spring.redis.lettuce.pool.max-wait=1000
spring.redis.timeout=3000
# springboot2.3 以后加上下面配置可自动刷新拓扑,某节点挂掉后,会有新的主从关系,如果不配置,调用接口时程序会报错
spring.redis.lettuce.cluster.refresh.adaptive=true
spring.redis.lettuce.cluster.refresh.period=5000
# spring.redis.sentinel.master=mymaster
# spring.redis.sentinel.nodes=192.168.2.14:26379,192.168.2.15:26380,192.168.2.16:26381
spring.redis.cluster.nodes=192.168.2.14:8001,192.168.2.15:8002,192.168.2.16:8003,192.168.2.17:8004,192.168.2.18:8005,192.168.2.19:8006
redis集群配置类,配置RedissionClient
package com.nwd.pressuretestutil.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.lang.reflect.Method;
/**
* @ClassName RedisConfig
* @Description TODO
* @Author n
* @Version 1.0
*/
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
/**
* 自定义缓存key的生成策略。默认的生成策略是看不懂的(乱码内容) 通过Spring 的依赖注入特性进行自定义的配置注入并且此类是一个配置类可以更多程度的自定义配置
*
* @return
*/
@Bean
@Override
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
/**
* 缓存配置管理器
*/
@Bean
public CacheManager cacheManager(LettuceConnectionFactory factory) {
//以锁写入的方式创建RedisCacheWriter对象
RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(factory);
//创建默认缓存配置对象
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
RedisCacheManager cacheManager = new RedisCacheManager(writer, config);
return cacheManager;
}
/**
* redissionClient配置
*/
@Bean
public RedissonClient redissonClient(){
Config config = new Config();
if (redisProperties.getCluster() != null) {
//集群模式配置
List<String> nodes = redisProperties.getCluster().getNodes();
List<String> clusterNodes = new ArrayList<>();
for (int i = 0; i < nodes.size(); i++) {
clusterNodes.add("redis://" + nodes.get(i));
}
ClusterServersConfig clusterServersConfig = config.useClusterServers()
.addNodeAddress(clusterNodes.toArray(new String[clusterNodes.size()]));
if (!StringUtils.isEmpty(redisProperties.getPassword())) {
clusterServersConfig.setPassword(redisProperties.getPassword());
}
} else {
//单节点配置
String address = "redis://" + redisProperties.getHost() + ":" + redisProperties.getPort();
SingleServerConfig serverConfig = config.useSingleServer();
serverConfig.setAddress(address);
if (!StringUtils.isEmpty(redisProperties.getPassword())) {
serverConfig.setPassword(redisProperties.getPassword());
}
serverConfig.setDatabase(redisProperties.getDatabase());
}
//看门狗的锁续期时间,默认30000ms,这里配置成15000ms
config.setLockWatchdogTimeout(15000);
return Redisson.create(config);
}
@Bean
public RedisTemplate<String,Object> redisTemplate(LettuceConnectionFactory factory){
RedisTemplate<String,Object> template = new RedisTemplate <>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// 在使用注解@Bean返回RedisTemplate的时候,同时配置hashKey与hashValue的序列化方式。
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
测试接口
/**
* 测试redis集群连接
* */
@GetMapping("/saveRedisCluster")
public String saveRedisCluster() {
try{
Object x = new Object();
redisTemplate.opsForValue().set("1","123");
}catch (Exception e){
e.printStackTrace();
return "err";
}
return "ok";
}
Redis-Cluster集群调优
后续还会更新宕机反演、水平拓展节点等