Redis3.0之前一般是通过哨兵工具来监测master节点的状态,若master节点宕机,则哨兵集群会进行主从切换,从其他slave节点中选举出新的master节点。
相较于高可用集群模式而言,哨兵模式有如下不足:
Redis高可用Cluster模式是由多个主从节点群组成的分布式服务器群,具有复制,高可用和分片特性;
相较于哨兵模式,其配置简单并且在性能和高可用方面更有一筹,不需要哨兵也能实现主从切换、故障转移功能,并且每个节点支持水平扩展(官方最大扩展1000个节点)。
Redis Cluster将数据划分为16384个槽位,每个节点负责其中一部分槽位,这部分槽位信息保存在对应负责的节点中。
当Client端连接集群时会得到一份集群的槽位配置信息缓存在客户端本地,在client查找某个key时可以根据缓存的槽位配置直接定位到对应的集群目标节点,另外当client端和server端的槽位信息不一致时,通过槽位纠正机制实现槽位的校验调整。
当slave发现自己的master变为FAIL状态时,slave期望成为新的master会尝试进行Failover,由于挂掉的master
可能会有多个slave,从而存在多个slave竞争成为master节点的过程。 其过程如下:
从节点并不是一发现主节点FAIL 状态就马上尝试发起选举,而是有一定延迟,一定的延迟确保FAIL状态在集群中传播,slave如果立即尝试选举,其它masters或许尚未意识到FAIL状态,可能会拒绝投票
•延迟计算公式:DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms
•SLAVE_RANK表示此slave已经从master复制数据的总量的rank。Rank越小代表已复制的数据越新。该方式理论上持有最新数据的slave将会先发起选举
前提:已编译安装完毕Redis(安装目录为 /usr/local/redis-5.0.3)
Redis Cluster集群至少要有3个master节点,本次搭建部署三个主从小集群即三个一主一从6个节点作为演示;
一般情况下,这三个主从小集群分别部署到三个不同的服务器节点,每台机器一主一从,但由于资源有限,本次演示的6个Redis节点均部署到同一台虚拟机。
部署步骤:
mkdir -p /usr/local/redis-cluster/node1/8001
mkdir -p /usr/local/redis-cluster/node1/8004
mkdir -p /usr/local/redis-cluster/node2/8002
mkdir -p /usr/local/redis-cluster/node2/8005
mkdir -p /usr/local/redis-cluster/node3/8003
mkdir -p /usr/local/redis-cluster/node3/8006
mkdir -p /usr/local/redis-cluster/8001
mkdir -p /usr/local/redis-cluster/8002
mkdir -p /usr/local/redis-cluster/8003
mkdir -p /usr/local/redis-cluster/8004
mkdir -p /usr/local/redis-cluster/8005
mkdir -p /usr/local/redis-cluster/8006
cd /usr/local/redis-cluster && ll
cp /usr/local/redis-5.0.3/redis.conf ./node1/8001/redis.conf
vi node1/8001/redis.conf
#设置端口
port 8001
#设置后台启动
daemonize yes
#将PID进程号写入pidfile文件
pidfile “/var/run/redis_8001.pid”
#指定数据存放目录
dir “/usr/local/redis-cluster/8001”
#启动集群模式
cluster-enabled yes
#设置集群节点配置文件
cluster-config-file nodes-8001.conf
#设置集群节点超时时间(ms)
cluster-node-timeout 10000
#注释bind
#bind 127.0.0.1
#关闭保护模式
protected-mode no
#开启AOF
appendonly yes
#密码设置如下
#设置redis访问密码
requirepass admin
#设置集群节点间访问密码
masterauth admin(与访问密码一致即可)
:wq
cp node1/8001/redis.conf node1/8004/redis.conf
vi node1/8004/redis.conf
:%s/8001/8004/g
:wq
/usr/local/redis-5.0.3/src/redis-server /usr/local/redis-cluster/node1/8001/redis.conf
/usr/local/redis-5.0.3/src/redis-server /usr/local/redis-cluster/node1/8004/redis.conf
/usr/local/redis-5.0.3/src/redis-server /usr/local/redis-cluster/node2/8002/redis.conf
/usr/local/redis-5.0.3/src/redis-server /usr/local/redis-cluster/node2/8005/redis.conf
/usr/local/redis-5.0.3/src/redis-server /usr/local/redis-cluster/node3/8003/redis.conf
/usr/local/redis-5.0.3/src/redis-server /usr/local/redis-cluster/node3/8006/redis.conf
ps -ef|grep redis
/usr/local/redis-5.0.3/src/redis-cli -a admin --cluster create --cluster-replicas 1 192.168.126.130:8001 192.168.126.130:8002 192.168.126.130:8003 192.168.126.130:8004 192.168.126.130:8005 192.168.126.130:8006
#连接集群节点8001
/usr/local/redis-5.0.3/src/redis-cli -a admin -c -h 192.168.126.130 -p 8001
#查看集群信息
cluster info
#查看集群节点列表
cluster nodes
<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>
server:
port: 8080
#redis cluster config
spring:
application:
name: redis-demo
redis:
database: 0
timeout: 3000
password: admin
cluster:
nodes: 192.168.126.130:8001,192.168.126.130:8002,192.168.126.130:8003,192.168.126.130:8004,192.168.126.130:8005,192.168.126.130:8006
lettuce:
pool:
max-idle: 50
min-idle: 10
max-active: 100
max-wait: 1000
package com.itjeffrey.redis.test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* @From: Jeffrey
* @Date: 2022/11/12
*/
@EnableScheduling
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
package com.itjeffrey.redis.test.service;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
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.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
/**
* Redis配置
* @From: Jeffrey
*/
@Slf4j
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
//string serializer
private RedisSerializer<String> redisSerializer = new StringRedisSerializer();
//object serializer
private Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
public RedisConfig(){
setObjectMapper(jackson2JsonRedisSerializer);
}
//LettuceConnectionFactory实例化过程中会自动从spring.cache.redis中读取配置信息
@Autowired
private LettuceConnectionFactory lettuceConnectionFactory;
/**
* config redisTemplate---manually add caches
* @return RedisTemplate
*/
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 设置ObjectMapper(解决查询缓存转换异常问题)
setObjectMapper(jackson2JsonRedisSerializer);
// 设置连接工厂
template.setConnectionFactory(lettuceConnectionFactory);
// 配置key, value, hashValue序列化
template.setKeySerializer(redisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
/**
* 解决查询缓存转换异常问题
*/
private void setObjectMapper(Jackson2JsonRedisSerializer jackson2JsonRedisSerializer){
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
}
}
package com.itjeffrey.redis.test.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Redis操作工具类
* @From: Jeffrey
*/
@Component
public class RedisUtil {
@Autowired
private RedisTemplate redisTemplate;
/**
* 写入缓存
*
* @param key
* @param value
* @return
*/
public boolean set(final String key, Object value) {
boolean result = false;
try {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
operations.set(key, value);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 写入缓存设置时效时间
*
* @param key
* @param value
* @return
*/
public boolean set(final String key, Object value, Long expireTime, TimeUnit timeUnit) {
boolean result = false;
try {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
operations.set(key, value);
redisTemplate.expire(key, expireTime, timeUnit);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 批量删除对应的value
*
* @param keys
*/
public void remove(final String... keys) {
for (String key : keys) {
remove(key);
}
}
/**
* 批量删除key
*
* @param pattern
*/
public void removePattern(final String pattern) {
Set<Serializable> keys = redisTemplate.keys(pattern);
if (keys.size() > 0) {
redisTemplate.delete(keys);
}
}
/**
* 删除对应的value
*
* @param key
*/
public void remove(final String key) {
if (exists(key)) {
redisTemplate.delete(key);
}
}
/**
* 判断缓存中是否有对应的value
*
* @param key
* @return
*/
public boolean exists(final String key) {
return redisTemplate.hasKey(key);
}
/**
* 读取缓存
*
* @param key
* @return
*/
public Object get(final String key) {
Object result = null;
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
result = operations.get(key);
return result;
}
/**
* 哈希 添加
*
* @param key
* @param hashKey
* @param value
*/
public void hmSet(String key, Object hashKey, Object value) {
HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
hash.put(key, hashKey, value);
}
/**
* 哈希获取数据
*
* @param key
* @param hashKey
* @return
*/
public Object hmGet(String key, Object hashKey) {
HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
return hash.get(key, hashKey);
}
/**
* 列表添加
*
* @param k
* @param v
*/
public void lPush(String k, Object v) {
ListOperations<String, Object> list = redisTemplate.opsForList();
list.rightPush(k, v);
}
/**
* 列表获取
*
* @param k
* @param l
* @param l1
* @return
*/
public List<Object> lRange(String k, long l, long l1) {
ListOperations<String, Object> list = redisTemplate.opsForList();
return list.range(k, l, l1);
}
/**
* 集合添加
*
* @param key
* @param value
*/
public void add(String key, Object value) {
SetOperations<String, Object> set = redisTemplate.opsForSet();
set.add(key, value);
}
/**
* 集合获取
*
* @param key
* @return
*/
public Set<Object> setMembers(String key) {
SetOperations<String, Object> set = redisTemplate.opsForSet();
return set.members(key);
}
/**
* 有序集合添加
*
* @param key
* @param value
* @param scoure
*/
public void zAdd(String key, Object value, double scoure) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
zset.add(key, value, scoure);
}
/**
* 有序集合获取
*
* @param key
* @param scoure
* @param scoure1
* @return
*/
public Set<Object> rangeByScore(String key, double scoure, double scoure1) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
return zset.rangeByScore(key, scoure, scoure1);
}
}
package com.itjeffrey.redis.test.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
/**
* @From: Jeffrey
* @Date: 2022/11/12
*/
@Service
public class TestService {
private int count;
@Autowired
private RedisUtil redisUtil;
@Scheduled(cron = "0 0/2 * * * ?")
public void test(){
String key = "user" + count;
redisUtil.set(key, "jeffrey-" + count);
System.out.println("set redis cache, key:" + key + " - value:" + redisUtil.get(key));
count++;
}
}