SpringBoot整合Redis(端口号7000)
Spring Boot Data Redis中提供了RedisTemplate和StringRedisTemplate,其中StringRedisTemplate是
RedisTemplate的子类,两个类中成员方法基本一致,不同之处主要体现在操作的数据类型不同,RedisTemplate中的两个泛型都是Object,意味着存储的key和value都可以是一个对象,而
StringRedisTemplate的两个泛型都是String,意味着StringRedisTemplate的key和value都只能是字符串。注意:使用RedisTemplate默认是将对象序列化到Redis中,所以放入的对象必须实现序列化接口
环境准备
引入依赖(spring2.0以后不适用jedis了,使用lettuce)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置application.yml文件
spring.redis.host=192.168.80.100
spring.redis.port=6379
spring.redis.database=0
创建SpringBoot项目,把以下两个勾选上
配置依赖
测试连接
创建五个包(controller,entity,dao,service,utils),
入口类
1 package com.shujia.xiao;
2
3 import org.springframework.boot.SpringApplication;
4 import org.springframework.boot.autoconfigure.SpringBootApplication;
5
6 @SpringBootApplication
7 public class RedisDemoApplication {
8
9 public static void main(String[] args) {
10 SpringApplication.run(RedisDemoApplication.class, args);
11 }
12
13 }
练习类型代码
StringRedisTemplate中的key和value都是string类型
package com.shujia.xiao.controller;
import com.shujia.xiao.entity.MytypedTuple;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.time.Duration;
import java.util.*;
@Controller
@ResponseBody
@RequestMapping("redistest")//RequestMapping不仅可以在方法上,也可以在类上,在类上时属于联级定义,有分类的作用
public class RedisApiController {
//springboot操作redis可以使用自己提供的redis依赖,父工程中已经写好了两个类供我们使用
//RedisTemplate和StringRedisTemplate
//通过对象注入的方式,使用该类的对象
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("test")
public String test(){
return "测试模版springboot";
}
/**
* 对redis中数据类型做操作
* 1.字符类型(String)
*/
@RequestMapping("testString")
public String testString(){
//链式调用
//opsForValue操作值是String类型的
//set方法是没有返回值的
stringRedisTemplate.opsForValue().set("name","xiaoxiao");
stringRedisTemplate.opsForValue().set("name1","panpan");
stringRedisTemplate.opsForValue().set("name2","tangtang");
//设置过期时间
stringRedisTemplate.opsForValue().set("delect","sansan",Duration.ofSeconds(10L));
//获取值,通过key获取
String name = stringRedisTemplate.opsForValue().get("name");
System.out.println(name);
//获取多个值
//将要获取的值的key组成一个集合
ArrayList<String> arrayList=new ArrayList<>();
arrayList.add("name");
arrayList.add("name1");
arrayList.add("name2");
List<String> strings = stringRedisTemplate.opsForValue().multiGet(arrayList);
for (String string : strings) {
System.out.println(string);
}
//一次性插入多个键值对
HashMap<String,String> map=new HashMap<>();
map.put("age","18");
map.put("age1","19");
stringRedisTemplate.opsForValue().multiSet(map);
Long l = stringRedisTemplate.opsForValue().size("name");
System.out.println(l);
//追加
stringRedisTemplate.opsForValue().append("name","yu");
return "测试String类型成功";
}
/**
* 对List类型操作
*/
@RequestMapping("testList")
public String testList(){
//插入
// stringRedisTemplate.opsForList().leftPush("id","宋亚轩");
// stringRedisTemplate.opsForList().leftPush("id","张极");
// stringRedisTemplate.opsForList().leftPush("id","张泽禹");
// stringRedisTemplate.opsForList().leftPush("id","马嘉祺");
// stringRedisTemplate.opsForList().leftPush("id","刘耀文");
//插入多个
// ArrayList<String> arrayList = new ArrayList<>();
// arrayList.add("xyg.九月");
// arrayList.add("xyg.秀豆");
// arrayList.add("xyg.灵梦");
// arrayList.add("xyg.酷偕");
// arrayList.add("xyg.羲和");
// stringRedisTemplate.opsForList().leftPushAll("list1", arrayList);
List<String> list1 = stringRedisTemplate.opsForList().range("list1", 0, -1);
//断言 assert list1 !=null;
if(list1!=null){
for (String s : list1) {
System.out.println(s);
}
}
//在某个值的左边插入
// stringRedisTemplate.opsForList().leftPush("list1","xyg.九月","xyg.破绽");
//右边
// stringRedisTemplate.opsForList().rightPush("id","宋亚轩","ss");
//保留区间元素
stringRedisTemplate.opsForList().trim("id",0,4);
//返回和移除指定位置的值
// List<String> list11 = stringRedisTemplate.opsForList().leftPop("list1", 1);
// System.out.println(list11);
//查看指定位置的索引
Long s1 = stringRedisTemplate.opsForList().leftPushIfPresent("id", "宋亚轩");
System.out.println(s1);
//最后一次出现指定值的位置
Long l1 = stringRedisTemplate.opsForList().lastIndexOf("id", "宋亚轩");
System.out.println(l1);
return "测试List类型";
}
/**
*
* 对set类型做操作
*/
//元素唯一并无序
@RequestMapping("testSet")
public String testSet(){
// //添加数据
// stringRedisTemplate.opsForSet().add("set1","ss","yy","xx","jiyu");
// //读取数据
Set<String> set1 = stringRedisTemplate.opsForSet().members("set1");
// for (String s : set1) {
// System.out.println(s);
// }
//是否存在
Boolean member = stringRedisTemplate.opsForSet().isMember("set1", "ss");
System.out.println(member);
Set<String> difference = stringRedisTemplate.opsForSet().difference(set1);
System.out.println(difference);
return "测试set类型成功";
}
/**
*
* 对Zset类型做操作
*/
//ZSet是可排序的
@RequestMapping("testZSet")
public String testZSet(){
//添加元素
HashSet<ZSetOperations.TypedTuple<String>> myTypedTuples = new HashSet<>();
//封装自己的值和分数
myTypedTuples.add(new MytypedTuple("11",100.0));
myTypedTuples.add(new MytypedTuple("22",110.0));
myTypedTuples.add(new MytypedTuple("33",120.0));
myTypedTuples.add(new MytypedTuple("44",130.0));
stringRedisTemplate.opsForZSet().add("zset1",myTypedTuples);
//获取ZSet的数据
Set<String> zset1 = stringRedisTemplate.opsForZSet().range("zset1", 0, -1);
for (String s1 : zset1) {
System.out.println(s1);
}
System.out.println("=======================");
//获取元素的同时获取分数
Set<ZSetOperations.TypedTuple<String>> zset11 = stringRedisTemplate.opsForZSet().rangeByScoreWithScores("zset1", 100, 120);
for (ZSetOperations.TypedTuple<String> s2 : zset11) {
String value = s2.getValue();
Double score = s2.getScore();
System.out.println(value+":"+score);
}
System.out.println("=======================");
//倒序遍历
Set<String> zset12 = stringRedisTemplate.opsForZSet().reverseRangeByScore("zset1", 100.0, 130.0);
for (String s3 : zset12) {
System.out.println(s3);
}
System.out.println("=======================");
//倒序遍历并且输出分数
Set<ZSetOperations.TypedTuple<String>> zset13 = stringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores("zset1", 0, 10000);
for (ZSetOperations.TypedTuple<String> s4 : zset13) {
String value = s4.getValue();
Double score = s4.getScore();
System.out.println(value+":"+score);
}
return "测试ZSet类型成功";
}
/**
*
* 对hash类型进行操作
*/
//无序
@RequestMapping("testHash")
public String testHash(){
//添加多个值 排序TreeMap 不排HashMap
// HashMap<String,String> map=new HashMap<>();
//// map.put("area","37000");
// map.put("area1","36000");
// map.put("area2","35000");
// stringRedisTemplate.opsForHash().putAll("map1",map);
// System.out.println("======================");
// //取值
// ArrayList<Object> arrayList = new ArrayList<>();
// arrayList.add("area");
// arrayList.add("area1");
// arrayList.add("area2");
// List<Object> map1 = stringRedisTemplate.opsForHash().multiGet("map1", arrayList);
// for (Object o : map1) {
// System.out.println(o);
// }
//判断是否存在
Boolean aBoolean = stringRedisTemplate.opsForHash().hasKey("map1", "area");
System.out.println(aBoolean);
return "测试hash类型成功";
}
@RequestMapping("testKey")
public String testKey(){
//获取当前库中所有的键
Set<String> keys = stringRedisTemplate.keys("*");
if(keys!=null){
for (String key : keys) {
System.out.println(key);
}
}else{
System.out.println("该库中没有键存在");
}
//给键设置过期时间
//设置分钟
Boolean fr = stringRedisTemplate.expire("fr", Duration.ofMinutes(1L));
System.out.println(fr);
//设置秒级别
Boolean a1 = stringRedisTemplate.expire("a1", Duration.ofSeconds(10L));
System.out.println(a1);
//判断key是否存在
Boolean k1 = stringRedisTemplate.hasKey("name");
System.out.println(k1);
//移动
stringRedisTemplate.move("name",1);
// //删除一个键
// stringRedisTemplate.delete("name");
// //删除多个键
// ArrayList<String> list1=new ArrayList<>();
// list1.add("name");
// list1.add("age");
// stringRedisTemplate.delete(list1);
//查看键对应的类型
//自学的方法:
//1、查看方法的名字
//2、查看方法需要传的参数类型与个数
//3、查看返回值类型
//4、查看源码的实现(需要你掌握java的面向对象开发的基础)
DataType set1 = stringRedisTemplate.type("zset1");
DataType age1 = stringRedisTemplate.type("age");
System.out.println(age1.code());
return "测试与redis键相关的操作";
}
}
在entity创建MyTypedTuple实现接口类TypedTuple
package com.shujia.xiao.entity;
import org.springframework.data.redis.core.ZSetOperations;
public class MytypedTuple<String> implements ZSetOperations.TypedTuple<String> {
private String value;
private Double score;
public MytypedTuple(String value, double score) {
this.value=value;
this.score=score;
}
@Override
public String getValue() {
return value;
}
@Override
public Double getScore() {
return score;
}
@Override
public int compareTo(ZSetOperations.TypedTuple<String> o) {
return 0;
}
}
练习RedisTemplate
* RedisTemplate:Key和Object类型都是Object类型,将key和value当作对象来看待, 既然将key和value当作对象来看待,那么存储的时候,就是存一个对象,对象存储到数据库中,是一个持久化的过程,对象持久化到数据库中,需要对对象进行序列化
- 回忆一下java中的序列化,对象的类必须要实现serializable接口,其次生成一个序列化ID
- 在java中String类实现了serializable接口,可能在使用RedisTemplate对象的时候能够偶读取到对应的数据
- 一定是可以的嘛?
由于StringRedisTemplate是RedisTemplate子类,大部分的方法,两个都差不多。
@Controller
@ResponseBody
public class RedisController {
@Autowired
private RedisTemplate<Object,Object> redisTemplate;
@RequestMapping("test1")
public String test1(){
/**
* 分析为什么是null?
* 首先,name我们使用StringRedisTemplate,而我们现在用RedisTemplate去读,两个类都不一样,可能读不到
* 我们回想一下刚刚提到的一点,RedisTemplate会将key和value当作对象来看待,所以需要对key和value做序列化
* 那么RedisTemplate就应该把我们传进去的name做序列化,”name“ --->"namexxxx",然后拿着序列化后的值去redis中找,
* 而redis中并没有namexxxx,所以结果为null
*
* String类刚刚才看的,已经实现了serializable接口,为什么不行呢?
* 所以这里,用的应该不是 jdk自己序列化,而是redis中特有的序列化方式
*
* 我们使用了redis特有的序列化方式后,结果却依旧没有出来。
* 序列化ID,再次插入一次,让两次序列化ID保持一直就可以了。
*
*/
/*
如果插入的数据是字符串,用StringRedisSerializer做序列化;如果插入的
是对象,用JdkSerializationRedisSerializer做序列化
*/
// Object name=redisTemplate.opsForValue().get("name");
// System.out.println(name);
//先设置序列化方式
// redisTemplate.setKeySerializer(new JdkSerializationRedisSerializer());
redisTemplate.setKeySerializer(new StringRedisSerializer());
//再插入
// redisTemplate.opsForValue().set("sname","xsee");
//查询
// Object name=redisTemplate.opsForValue().get("sname");
// System.out.println(name);
//创建学生对象,插入到redis中
// Student s = new Student("ss", 18);
// redisTemplate.opsForValue().set("1001",s);
Object o = redisTemplate.opsForValue().get("1001");
System.out.println(o);
//对key序列化
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//对value序列化
redisTemplate.setHashValueSerializer(new StringRedisSerializer());
redisTemplate.opsForHash().put("1002","cname","wqy");
return "测试使用RedisTemplate成功";
}
}
连接池
使用它是为了避免重复的创建和销毁链接对象而消耗资源
引入依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
线程池代码
//构建redis资源连接池配置
JedisPoolConfig poolConfig = new JedisPoolConfig();
//给定个数
poolConfig.setMaxTotal(100);
//根据这个配置来创建redis连接池
JedisPool jedisPool = new JedisPool(poolConfig, "192.168.80.100", 6379);
//在连接池中构建redis连接
Jedis jedis = jedisPool.getResource();
//拿着这个连接操作redis
String name1 = jedis.get("name");
System.out.println(name1);
//操作完后将连接归还给连接池
jedis.close();
Redis学习高级部分(重要)
Redis主从复制
主从复制
主从复制架构仅仅用来解决数据的冗余备份,从节点仅仅用来同步数据
不能解决master节点出现故障的自动故障转移
主从复制架构图
master接受用户请求并存储数据,从节点只存储数据,所以主节点坏了之后数据不会丢
搭建主从配置
1.在/usr/local/soft/redis-install/下创建三个目录代表三台机器,master,node1,node2
2.把redis.conf文件分别拷贝到master,node1,node2中
[root@master redis-install]# cp redis-7.0.0/redis.conf ./master/
[root@master redis-install]# cp redis-7.0.0/redis.conf ./node1/
[root@master redis-install]# cp redis-7.0.0/redis.conf ./node2/
# 1.准备3台机器并修改配置,修改端口号,开启远程连接,配置主节点是谁
- master
port 6379
protected-mode no
- node1
port 6380
protected-mode no
replicaof <masterip> <masterport> 改成下面:
replicaof 192.168.80.100 6379
- node2
port 6381
protected-mode no
replicaof <masterip> <masterport> 改成下面:
replicaof 192.168.80.100 6379
# 2.启动3台机器进行测试
- cd /usr/local/soft/bigdata19/redis-install
- redis-server ./master/redis.conf
- redis-server ./node1/redis.conf
- redis-server ./node2/redis.conf
node1和node2启动服务后,会在master中显示同步,在master中设置值,在node1和node2中也会接收到
查看运行进程 :ps aux|grep redis
杀死进程:kill -9 进程号(进程号就是第一个四字数字)
杀死主进程后,其它子进程会停止连接
Redis哨兵机制
哨兵Sentine机制
Sentinel(哨兵)是Redis 的高可用性解决方案:由一个或多个Sentinel 实例 组成的Sentinel 系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器。简单的说哨兵就是带有自动故障转移功能的主从架构。
不能解决 单节点并发压力问题和单节点内存和磁盘物理上限
哨兵架构原理
在主节点被恢复之后它就不是主节点了
Redis集群(在开始之前拍摄快照)
Redis在3.0后开始支持Cluster(模式)模式,目前redis的集群支持节点的自动发现,支持slave-master选举和容错,支持在线分片(sharding shard )等特性。reshard
PING PONG协议(心跳机制)
集群架构
集群细节
- 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.
- 节点的fail是通过集群中超过半数的节点检测失效时才生效.(半数机制,后面hadoop也会说到 )
- 客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
- redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->value
--如果key是一样的节点,则后面需要加一个符号来区分,加了不同符号之后的物理节点是不同的
例如:name*和mena@
集群搭建
判断一个是集群中的节点是否可用,是集群中的所用主节点选举过程,如果半数以上的节点认为当前节点挂掉,那么当前节点就是挂掉了,所以搭建redis集群时建议节点数最好为奇数,搭建集群至少需要三个主节点,三个从节点,至少需要6个节点
# 1.准备环境安装ruby以及redis集群依赖
- yum install -y ruby rubygems
# https://rubygems.org/gems/redis/versions
- gem install redis-3.3.5.gem(在d:soft/redis中)
#2.在、usr/local/soft/redis-install目录下创建redis-cluster文件
#在redis-cluster文件下创建7个目录
# 3.每个目录复制一份配置文件(把/usr/local/soft/redis.install/redis-7.0.0下的redis.conf文件复制给七个目录)
[root@master redis-cluster]# cp ../redis-7.0.0/redis.conf ./7000/
[root@master redis-cluster]# cp ../redis-7.0.0/redis.conf ./7001/
[root@master redis-cluster]# cp ../redis-7.0.0/redis.conf ./7002/
[root@master redis-cluster]# cp ../redis-7.0.0/redis.conf ./7003/
[root@master redis-cluster]# cp ../redis-7.0.0/redis.conf ./7004/
[root@master redis-cluster]# cp ../redis-7.0.0/redis.conf ./7005/
[root@master redis-cluster]# cp ../redis-7.0.0/redis.conf ./7006/
# 4.修改不同目录配置文件
- port 7000 ..... //修改端口
- # bind 127.0.0.1 -::1 //开启远程连接
- protected-mode no
- daemonize yes //开启守护进程
- dbfilename dump-7000.rdb //每台机器的文件不能一样
- cluster-enabled yes //开启集群模式
- cluster-config-file nodes-7000.conf //集群节点配置文件
- cluster-node-timeout 5000 //集群节点超时时间
- appendonly yes //开启AOF持久化
- appendfilename "appendonly-7000.aof" //修改aof文件名
- appenddirname "appendonlydir-7000"
# 5.指定不同目录配置文件启动七个节点(7003,7004的忘记修改了守护进程)(在d盘soft中的redis里面有redis-cluster文件上传到redis-install目录下)
[root@master redis-cluster]# redis-server 7000/redis.conf
[root@master redis-cluster]# redis-server 7001/redis.conf
[root@master redis-cluster]# redis-server 7002/redis.conf
[root@master redis-cluster]# redis-server 7003/redis.conf
[root@master redis-cluster]# redis-server 7004/redis.conf
[root@master redis-cluster]# redis-server 7005/redis.conf
[root@master redis-cluster]# redis-server 7006/redis.conf
在启动之前保证没有其它的redis服务进程
此时集群并没有创建成功
创建集群
# 1.复制集群操作脚本到bin目录中(可以不复制)
[root@master redis-cluster]# cp /usr/local/soft/redis-install/redis-7.0.0/src/redis-trib.rb /usr/local/soft/redis/bin/
# 2.创建集群
redis7.0.0之后的命令:redis-cli --cluster create 192.168.80.100:7000 192.168.80.100:7001 192.168.80.100:7002 192.168.80.100:7003 192.168.80.100:7004 192.168.80.100:7005 --cluster-replicas 1
查看集群状态
# 1.查看集群状态 check [原始集群中任意节点] [无]
redis-cli --cluster check 192.168.80.100:7000
# 2.集群节点状态说明
- 主节点
主节点存在hash slots,且主节点的hash slots 没有交叉
主节点不能删除
一个主节点可以有多个从节点
主节点宕机时多个副本之间自动选举主节点
- 从节点
从节点没有hash slots
从节点可以删除
从节点不负责数据的写,只负责数据的同步
演示其中一个主节点宕机的状态,然后由从节点接管
kill -9 进程号
五秒之后,现在的7005变成了主节点,再次启动7000这个节点就是从节点
设置的值要在对应的槽里存储
添加主节点
redis-cli --cluster add-node 192.168.80.100:7006 192.168.80.100:7001
- 注意:
1.该节点必须以集群模式启动
2.默认情况下该节点就是以master节点形式添加
添加从节点
redis-cli --cluster add-node 192.168.80.100:7006 192.168.80.100:7001 --cluster-slave
- 注意:
当添加副本节点时没有指定主节点,redis会随机给副本节点较少的主节点添加当前副本节点
删除副本节点
# 1.删除节点 del-node [集群中任意节点] [删除节点id]
redis-cli --cluster del-node
redis需要连接可视化工具