redis day3
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机制
不能解决 单节点并发压力问题和单节点内存和磁盘物理上限
哨兵架构原理

在主节点被恢复之后它就不是主节点了
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@
集群搭建
# 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 192.168.80.100:7002 be89e999371cdcded083d3abe1a74ba48b4d5b2f
- 注意: 1.被删除的节点必须是从节点或没有被分配hash slots的节点
redis需要连接可视化工具
浙公网安备 33010602011771号