1.分片集群结构
哨兵模式只有一个主节点,如果写操作频率过高,那么就会导致主节点出现宕机问题,就需要使用分片集群模式
分片集群结构图:这些主从都会存在哨兵模式
使用了分槽技术,默认集群槽的数量为16384个。而每个槽可以存放若干个数据。如果搭建redis集群模式会为主节点平均分配这些槽。
原理:Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。
当你往Redis Cluster中加入一个Key时,会根据crc16(key) mod 16384计算这个key应该分布到哪个hash slot中,一个hash slot中会有很多key和value。你可以理解成表的分区,使用单节点时的redis时只有一个表,所有的key都放在这个表里;改用Redis Cluster以后会自动为你生成16384个分区表,你insert数据时会根据上面的简单算法来决定你的key应该存在哪个分区,每个分区里有很多key。
2.如何搭建redis分片集群
准备6台redis服务:3台主节点和3台从节点,这里为了操作就在一台虚拟机上启动6个redis服务,修改端口号分别为为7001、7002、7003、7004、7005、7006
修改redis集群中6台配置文件:如果是多台虚拟机的话就不必要修改名称,这里是为了分辨
1.修改端口号:
2.修改快照.rdb文件的名称防止一样
3.必须开启aof模式并修改.aof文件的名称
4.开启redis集群模式 cluster-enabled yes
5.修改集群文件名 cluster-config-file nodes-7001.conf
6.设置允许任意ip访问 bind * -::*
启动6台redis服务
为上面6台redis设置主从关系并分配槽:
redis-cli --cluster create --cluster-replicas 1 192.168.61.223:7001 192.168.61.223:7002 192.168.61.223:7003 192.168.61.223:7004 192.168.61.223:7005 192.168.61.223:7006
中途输入yes
完成
分配了三主三从:
启动redis客户端:redis-cli -c -h 192.168.61.223 -p 7001 这个ip和端口输入开启6台的任意一台都可以
测试:可以看到通过crc16算法进入了12706所在的分区进入了7003
测试关掉7001主节点看7001的从节点(7006)会不会上位
发现7006接替了7001的主节点
3.java连接redis
可以使用jedis完成java与redis之间的连接
依赖:
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.6.0</version>
</dependency>
</dependencies>
3.1连接单机版:
public class Test01 {
public static void main(String[] args) {
//1.创建连接对象
Jedis jedis = new Jedis("192.168.61.223",6379);
//查看所有key
Set<String> keys = jedis.keys("*");
System.out.println("所有的key"+keys);
//设置key的值为字符串类型
jedis.set("k1","v1");
//设置有效时间
jedis.expire("k1",20L);
//查看有效时间
Long time = jedis.ttl("k1");
System.out.println("有效时间"+time);
//删除key为k1
// Long flag = jedis.del("k1");
// System.out.println(flag);
//判断当前key是否存在
System.out.println(jedis.exists("k1"));
System.out.println("-----String类型-----");
//设置key的值为字符串类型的value
jedis.set("k1","v1");
//根据key获取对应的value值
String k1 = jedis.get("k1");
System.out.println(k1);//v1
//设置多个key-value
jedis.mset("k1","v1","k2","v2");
//获取多个key对应的value值
List<String> mget = jedis.mget("k1", "k2", "k3", "k4"); //[v1, v2, null, null]
System.out.println(mget);
//如果指定的key存在则不存入,如果不存在才存入
jedis.setnx("k1","v4");
//为指定的key递增 必须为整数
jedis.set("k3","2");
jedis.incr("k3");
//为指定的key递减 必须为整数
jedis.decr("k3");
//为指定的key递增指定的值
jedis.incrBy("k3",2L);
//为指定的key递减指定的值
jedis.decrBy("k3",2L);
System.out.println("-----hash类型-----");
//存放hash类型的数据
jedis.hset("k4","name","zs");
jedis.hset("k4","age","20");
//或者
HashMap<String, String> map = new HashMap<>();
map.put("name","ls");
map.put("age","20");
jedis.hset("k5",map);
//获取相应key中field对应的数据
System.out.println(jedis.hget("k5", "name")); //ls
//获取key对应hash数据内容
System.out.println(jedis.hgetAll("k4")); //{name=zs, age=20}
//获取hash中所有field字段
System.out.println(jedis.hkeys("k4"));//[name, age]
//获取hash中所有value字段
System.out.println(jedis.hvals("k4"));//[zs, 20]
System.out.println("-----list列表类型-----");
//从左边存放列表数据
jedis.lpush("k6","v1","v2","v3","v4","v5");
//从左边取出元素 (指最后存入的)
String k6 = jedis.lpop("k6");//v5
System.out.println(k6);
// 从左边获取指定范围的元素
System.out.println(jedis.lrange("k6", 0, -1)); //[v4, v3, v2, v1]
System.out.println("-----set集合类型");
//存放set类型的元素
jedis.sadd("k7","v1","v2","v3","v4","v5");
//获取指定key对应的所有元素。
System.out.println(jedis.smembers("k7"));//[v1, v2, v3, v4, v5]
//随机获取集合中一个或多个元素
System.out.println(jedis.srandmember("k7"));//v2
//求多个集合的交集
jedis.sadd("k8","v3","v4","v5","v6","v7");
System.out.println(jedis.sinter("k7","k8"));
//随机移除一个或多个元素
System.out.println(jedis.spop("k8"));//v7
System.out.println("-----sorted set集合类型-----");
// 添加有序集合
jedis.zadd("k9",10,"v1");
HashMap<String, Double> map1 = new HashMap<>();
map1.put("v1",10.);
map1.put("v2",20.);
map1.put("v3",30.);
map1.put("v4",40.);
map1.put("v5",50.);
jedis.zadd("k10",map1);
//从小到大的顺序获取集合中的元素
System.out.println(jedis.zrange("k10", 0, -1));//[v1, v2, v3, v4, v5]
//从大到小的顺序获取集合中的元素
Set<String> k10 = jedis.zrevrange("k10", 0, -1);//[v5, v4, v3, v2, v1]
System.out.println(k10);
//从小到大的顺序获取集合中的元素和分数
Set<Tuple> k101 = jedis.zrevrangeWithScores("k10", 0, -1);//[[v5,50.0], [v4,40.0], [v3,30.0], [v2,20.0], [v1,10.0]]
System.out.println(k101);
}
}
3.2连接集群版:
public class Test02 {
public static void main(String[] args) {
//java连接redis集群
HashSet<HostAndPort> hostAndPorts = new HashSet<>();
hostAndPorts.add(new HostAndPort("192.168.61.223",7001));
hostAndPorts.add(new HostAndPort("192.168.61.223",7002));
hostAndPorts.add(new HostAndPort("192.168.61.223",7003));
hostAndPorts.add(new HostAndPort("192.168.61.223",7004));
hostAndPorts.add(new HostAndPort("192.168.61.223",7005));
hostAndPorts.add(new HostAndPort("192.168.61.223",7006));
//创建一个Jedis集群对象---需要传入redis集群服务的地址信息
JedisCluster jedisCluster = new JedisCluster(hostAndPorts);
jedisCluster.set("k1","v1");
System.out.println(jedisCluster.get("k1"));
}
}
4.使用springboot连接redis
springboot在整合redis时会自动封装了两个类:RedisTemplate和StringRedisTemplate. StringRedisTemplate它是RedisTemplate的子类,StringRedisTemplate它里面存储的key和value都是字符串类型。
依赖:springboot版本为:2.3.2.RELEASE
<dependencies>
<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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
4.1单机版
修改配置文件:
spring.redis.port=6379
spring.redis.host=192.168.61.223
4.1.1使用StringRedisTemplate
@SpringBootTest
class SpringbootRedis01ApplicationTests {
@Autowired
private StringRedisTemplate redisTemplate; //starter自动完成了redis的装配
@Test
void contextLoads() {
//获取所有的key
System.out.println(redisTemplate.keys("*")); //[]
//是否存在指定的key
System.out.println(redisTemplate.hasKey("k1"));//false
//删除指定的key
System.out.println(redisTemplate.delete("k1"));//false
//设置有效时间
// redisTemplate.expire("k1",20, TimeUnit.SECONDS);
//查看有效时间
System.out.println(redisTemplate.getExpire("k1"));//-2
System.out.println("--------------操作String字符串类型---------------");
ValueOperations<String, String> string = redisTemplate.opsForValue();
//设置value为string的
string.set("k1", "v1");
//获取key为k1
System.out.println(string.get("k1"));//v1
//设置多个key-value
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("k1","v1");
hashMap.put("k2","v2");
hashMap.put("k3","v3");
string.multiSet(hashMap);
//获取多个key对应的value值
ArrayList<String> list1 = new ArrayList<>();
list1.add("k1");
list1.add("k2");
list1.add("k3");
list1.add("k4");
System.out.println(string.multiGet(list1));//[v1, v2, v3, null]
//如果指定的key不存在存入,存在则不存入
System.out.println(string.setIfAbsent("k1", "v1"));//false
//为指定的key递增 必须为整数
string.set("k2","1");
System.out.println(string.increment("k2"));//2
//为指定的key递增指定的数 必须为整数
System.out.println(string.increment("k2", 3));//5
//为指定的key递减 必须为整数
System.out.println(string.decrement("k2"));//4
//为指定的key递减指定的数 必须为整数
System.out.println(string.decrement("k2",2));//2
System.out.println("-------------------操作hash类型--------------------");
HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
//存放hash类型的数据
hash.put("k4","name","张三");
//存放多个
HashMap<String, String> k4 = new HashMap<>();
k4.put("age","20");
k4.put("address","河南");
hash.putAll("k4",k4);
//获取相应key中field对应的数据
System.out.println(hash.get("k4", "name"));//张三
//获取hash中key对应的所有数据内容
Map<Object, Object> k41 = hash.entries("k4");
System.out.println(k41);//{name=张三, address=河南, age=20}
//获取key对应的所有field
System.out.println(hash.keys("k4"));//[name, address, age]
//获取key对应的所有value字段
System.out.println(hash.values("k4"));//[张三, 河南, 20]
System.out.println("-------------------list列表类型-------------------");
ListOperations<String, String> list = redisTemplate.opsForList();
//从左边存放列表数据
list.leftPushAll("k5","v1","v2");
ArrayList<String> list2 = new ArrayList<>();
list2.add("v2");
list2.add("v3");
list2.add("v4");
//或者
list.leftPushAll("k6",list2);
//从左边取出一个元素 (最后存入的)
System.out.println(list.leftPop("k5"));//v2
//从左边获取指定范围的元素
System.out.println(list.range("k6", 0, -1));//[v4, v3, v2]
System.out.println("-----------------set集合类型-------------------");
SetOperations<String, String> set = redisTemplate.opsForSet();
//存放set类型的元素
set.add("k7","v1","v2","v3","v4");
//获取key对应的所有元素
System.out.println(set.members("k7"));//[v1, v2, v3, v4]
//随机获取集合中一个或多个元素
System.out.println(set.randomMember("k7"));//v3
//求多个集合的交集
set.add("k8","v3","v4","v5","v6");//[v3, v4]
System.out.println(set.intersect("k7", "k8"));
//随机移除一个元素
System.out.println(set.pop("k8"));//v6
System.out.println("--------------------sorted set集合类型----------------------");
ZSetOperations<String, String> zSet = redisTemplate.opsForZSet();
//添加有序集合
zSet.add("k9","v1",10);
//添加多个
Set<ZSetOperations.TypedTuple<String>> tuples = new HashSet<>();
tuples.add(new DefaultTypedTuple<>("v1",10.));
tuples.add(new DefaultTypedTuple<>("v2",20.));
tuples.add(new DefaultTypedTuple<>("v3",30.));
zSet.add("k9",tuples);
//从小到大的顺序获取集合中的元素
System.out.println(zSet.range("k9", 0, -1));//[v1, v2, v3]
//从大到小的顺序获取集合中的元素
System.out.println(zSet.reverseRange("k9",0,-1));//[v3, v2, v1]
//从小到大的顺序获取元素和分数
System.out.println(zSet.rangeWithScores("k9", 0, -1));//[org.springframework.data.redis.core.DefaultTypedTuple@c45c123c, org.springframework.data.redis.core.DefaultTypedTuple@c64c123d, org.springframework.data.redis.core.DefaultTypedTuple@c782123e]
for (ZSetOperations.TypedTuple<String> k9 : zSet.rangeWithScores("k9", 0, -1)) {
System.out.println(k9.getScore());
System.out.println(k9.getValue());
}
//10.0
//v1
//20.0
//v2
//30.0
//v3
}
}
4.1.2使用RedisTemplate
@SpringBootTest
public class SpringbootApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test01(){
ValueOperations string = redisTemplate.opsForValue();
string.set("k1","v1");
}
}
在运行时发现存入的key和value都为乱码
这是因为RedisTemplate默认使用的是jdk序列化,而StringRedisTemplate使用的是StringRedisSerializer()序列化,所以我们需要设置序列化方式
因为不知道以后value要传入什么类型。所以value指定为json类型,Jackson2JsonRedisSerializer(Object.class));
hash中key和value也需要设置序列化:
@SpringBootTest
public class SpringbootApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test01(){
//指定了key的序列化
redisTemplate.setKeySerializer(new StringRedisSerializer());
//指定了value的序列化
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(Object.class));
//指定hash的key的序列化
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//制定了hash的value的序列化
redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer(Object.class));
ValueOperations string = redisTemplate.opsForValue();
string.set("k1","v1");
//hash操作
HashOperations hashOperations = redisTemplate.opsForHash();
hashOperations.put("k55","name","张三");
}
}
每次使用redistemplate时都需要配置这些序列化,所以我们可以将这些创建为一个配置类交于spring管理
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//hashmap序列化key
template.setHashKeySerializer(redisSerializer);
//hashmap序列化value
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
}
4.2集群版
# 集群redis
spring.redis.cluster.nodes=192.168.61.223:7001,\
192.168.61.223:7002,\
192.168.61.223:7003,\
192.168.61.223:7004,\
192.168.61.223:7005,\
192.168.61.223:7006