Redis

1. Redis的使用

在并发访问量高的情况下,数据库的弊端就会显现

1、随着访问量的增加,数据库的性能逐步降低

2、数据库属于磁盘存储,读写效率相对较低

3、数据库集群,同步数据称为一个问题
在这里插入图片描述
是Redis后,对于获取的请求,就不在请求数据库,直接到Redis获取即可

2. Redis的特点:

1、内存存储数据,读取效率高

2、属于no-sql类型的数据库

3、是key-value的存储形式

4、支持集群,集群内部自动同步数据

5、支持持久化技术

作用:用于数据缓存,提升获取数据的效率

缺点: 数据同步问题,缓存中的数据容易过期

3. 搭建Redis服务

在这里插入图片描述
cmd进入Redis的根目录,输入命令

redis-server.exe redis.windows.conf 回车即可

有三种方式操作Redis服务

1、桌面工具

2、Redis-cli.exe 此工具

3、Jedis java操作Redis

4. Redis的数据类型

4.1 string类型:可以字符串、整形、小数、布尔

在这里插入图片描述
【面试题】redis中的string类型的实现原理?

4.2 list类型:集合、列表类型

一个key,对应多个value,value之间使用逗号分隔

特点:可重复,有序
在这里插入图片描述
在这里插入图片描述

4.3 hash类型

一个key ,对应多个value,每个value又是 属性名-属性值结构,可以用来存储对象

Dept dept = new Dept(“1001”,“软件研发1部”,“设计城901”);

在这里插入图片描述
key-dept1

value

  • deptNo 1001
  • deptName 软件研发1部
  • loc 设计城901

4.4 set

特点:一个key 对应多个值,无序、唯一
在这里插入图片描述

4.5 zset sortedset 有序的set集合

特点:一个key 对应多个值,唯一,但是有序,在添加值时,为每个值指定一个分数,按分数排序

在这里插入图片描述
总结:5种常用命令

  • key-string:最常用的,一般用于存储一个值。

  • key-hash:存储一个对象数据的。

  • key-list:使用list结构实现栈和队列结构。

  • key-set:交集,差集和并集的操作。

  • key-zset:排行榜,积分存储等操作。

另外三种数据结构(了解):

  • HyperLogLog:计算近似值的。
  • GEO:地理位置。
  • BIT:一般存储的也是一个字符串,存储的是一个byte[]。

5. Redis的各种命令

每一个命令在Jedis中都说是对应的一个方法

作用:做数据缓存,用以提升获取数据的效率

特点:内存存储 nosql key-value存储 支持集群、持久化,输入易过期

数据类型:String list set hash zset sortedset

5.1 关于key的相关命令

【说明】Redis内存中存储的数据从存储时间上来分,有两种类型:

永久的:一直存活于内存中

瞬时的:有存活时间(秒 毫秒)

命令含义
DEL删除
EXISTS判断是否存在
EXPIRE以秒为单位设置存活时间
KEYS查看当前库下所有的key
KEYSkeys m? 含义:类似于sql中的模糊查询(查看以m开头的key)
MOVE移走 move mi 2(把当前库中mi移到索引2的库中)
PERSIST设置永久存活有效
PEXPIRE以毫秒为单位设置获取时间
TTL以秒为单位查看剩余时间
PTTL以毫秒为单位查看剩余时间
RANDOMKEY有多个key,随机返回一个key
RENAME重命名key(不建议,会覆盖)
RENAMENX重命名key(推荐,不会覆盖)
TYPE查看key是什么类型

在这里插入图片描述

5.2 String命令

命令含义
SET创建key value(同时可以通过EX秒/PX毫秒设置存活时间),如果创建了另一个set 同样的key会覆盖掉之前的key,所以要在后面加NX判断,如果set的key存在就不会成功
MSET可以设置多个key value
MGET获取多个key
APPEND在原有的字符串后面追加
INCR在原来的值上+1
INCRBY在原来的值上指定+几
INCRBYFLOAT在原来的值上指定+几(小数)
DECR在原来的值上-1
DECRBY在原来的值上指定-几
GETRANGE下标截取
GETSETgetset db mysql(把新值mysql覆盖db)
SETRANGE替换(覆盖)
STRLEN长度

在这里插入图片描述

5.3 Hash命令

命令含义
HSET赋值
HMSET赋多个值
HGET获取值
HMGET获取多个值
HEXISTS判断是否存在
HDEL删除某个值
HGETALL获取所有属性值
HKEYS查看都有哪些key
HLEN一共有几个键值对
HSETNX当你不存在时赋值,否则会覆盖
HVALS获取value值

在这里插入图片描述

5.4 List集合相关命令

命令含义
LSET改值
LPUSH左侧添加 (可重复)
RPUSH右侧添加(可重复)
LPOP删头
RPOP删尾
LLEN集合中有几个值
LINSERT插入(before在谁之前插入,after在谁2之后插入)
LINDEX根据集合的对应索引的元素
LREMlrem hello 0 a(删除hello里所有的a),lrem hello 3 a(正数,从左往右删掉3个a)
LTRIM截取指定范围之内 ltrim dj 3 6(只保留从下标3截取到下标6的元素)
RPOPLPUSH把 group1里的尾部元素删掉,放到group2里元素的头部

在这里插入图片描述

在这里插入图片描述

5.5 Set相关命令

命令含义
SADD添加元素(去重,无序)
SCARD返回集合元素的数量
SDDIF以g4为参考,返回g4和g5不相同的元素(b,a),以g5为参考,返回g5和g4不相同的元素(f, g, n),不是返回两个集合不同的元素
SDIFFSTORE以g4为参考和g5对比不同的元素(a,b)添加到g6当中
SINTER返回的是g4和g5共同的元素
SMOVE把g4里的元素a移动到g5当中
SPOP随机删除

SDDIF
在这里插入图片描述
SDIFFSTORE
在这里插入图片描述
SINTER
在这里插入图片描述
SMOVE
在这里插入图片描述

在这里插入图片描述

5.6 zset sortedset

命令含义
ZADD添加元素(将一个或多个 member 元素及其 score 值加入到有序集 key 当中。)
ZRANGEBYSCORE返回集合中指定的元素(生序排列)

ZADD
在这里插入图片描述
ZRANGEBYSCORE
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

6. java操作Redis

缓存预热:在服务器启动后,把程序的“热点数据”,提前从数据库获取到,存入Redis的过程。

热点数据:

  • 1、访问频率高

  • 2、数据量较大的

1、新建项目

2、添加jedis的依赖

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>

3、操作

3、1:直接存储字符串

package com.qf.redis2204.test;

import redis.clients.jedis.Jedis;

public class Test1 {
    public static void main(String[] args) {
        //1、建立Java程序和Redis的连接
        Jedis jedis=new Jedis("127.0.0.1",6379);
        //2、选择一个库
        jedis.select(2);
        //3、直接存储字符串
        String result=jedis.set("study","redis");
        if(result.equalsIgnoreCase("ok")){
            System.out.println("存储成功!");
        }else{
            System.out.println("存储失败!");
        }
//        if(result.toLowerCase().equals("ok")){
//
//        }
        //获取元素
        String str=jedis.get("study");
        System.out.println("从Redis中获取到的值:"+str);
        //关闭资源
        jedis.close();
    }
}

3、2:从数据库得到的是集合,如何把集合存入Redis?

​ 把对象转成byte[]类型进行存储,使用Redis的string类型

【注意】Redis的string类型最多只允许存储512M数据。

添加一个依赖:能把对象转成byte[]

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.3.18.RELEASE</version>
</dependency>

创建一个实体类:Student

package com.qf.redis2204.test;

import com.qf.redis2204.pojo.Student;
import org.springframework.util.SerializationUtils;
import redis.clients.jedis.Jedis;

import java.util.ArrayList;

public class Test2 {
    public static void main(String[] args) {
        //把对象存入Redis
        Student student=new Student("qf001","张丛鑫",20);
        Student student2=new Student("qf002","钱大野",20);

        ArrayList<Student> list=new ArrayList<Student>();
        list.add(student);
        list.add(student2);

        Jedis jedis=new Jedis("127.0.0.1",6379);
        jedis.select(3);
        //把对象转成byte[],key   value都要转
        String key1="张丛鑫";
        byte[] byteKey1= SerializationUtils.serialize(key1);
        byte[] byteValue1=SerializationUtils.serialize(student);

        //出入redis
        String result=jedis.set(byteKey1,byteValue1);
        if("ok".equals(result.toLowerCase())){
            System.out.println("存入成功!");
        }else{
            System.out.println("存入失败!");
        }

        //获取元素
        byte[] getValue=jedis.get(byteKey1);
        //把byte[]  转成对象类型
        Student getStudent=(Student) SerializationUtils.deserialize(getValue);
        System.out.println("获取到的学生:"+getStudent);

        //关闭资源
        jedis.close();
    }
}

3、3:把对象转成JSON字符串

1、添加转换JSON的jar依赖

<!-- 导入fastJSON -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.47</version>
</dependency>
package com.qf.redis2205.test;

import com.alibaba.fastjson.JSON;
import com.qf.redis2205.pojo.Student;
import com.qf.redis2205.util.RedisUtil;
import redis.clients.jedis.Jedis;

import java.util.ArrayList;
import java.util.List;

public class Test3 {
    public static void main(String[] args) {
        //把对象存入redis
        Student stu1 = new Student("qf001", "aaa", 11);
        Student stu2 = new Student("qf002", "bbb", 12);
        ArrayList<Student> list = new ArrayList();
        list.add(stu1);
        list.add(stu2);

        //创建连接
        //Jedis jedis = new Jedis("127.0.0.1", 6379);
        Jedis jedis = RedisUtil.getJedis();
        //选择库
        jedis.select(4);



        //把集合转成json
        String key = "java2205";
        String json = JSON.toJSONString(list);

        String result = jedis.set(key, json);
        if ("ok".equals(result.toLowerCase())) {
            System.out.println("写入json成功");
        } else {
            System.out.println("写入json失败");
        }

        //读取
        String getJson = jedis.get(key);

        //把json转为对象
        // 只能转成单个对象
        //JSON.parseObject(getJSON,Student.class);

        //把json转为对象
        List<Student> lists = JSON.parseArray(getJson, Student.class);
        for (Student student : lists) {
            System.out.println(student);
        }
    }
}

7. 工具类的使用

为了提升java连接Redis服务的效率,创建Redis的连接池

package com.qf.redis2204.util;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

public class RedisUtil {
    //声明类级别的连接池对象
    private  static JedisPool jedisPool=null;
    //在static代码块中,编写连接池的配置信息
    static {
        //创建连接池的配置对象
        GenericObjectPoolConfig genericObjectPoolConfig=new GenericObjectPoolConfig();
        //设置连接池中的最大连接数量
        genericObjectPoolConfig.setMaxTotal(100);
        //设置连接中的空闲连接的数量
        genericObjectPoolConfig.setMaxIdle(10);
        //设置最小空闲连接数量
        genericObjectPoolConfig.setMinIdle(5);
        //设置等待的超时时间
        genericObjectPoolConfig.setMaxWaitMillis(3000);

        //创建连接池对象
        jedisPool=new JedisPool(genericObjectPoolConfig,"127.0.0.1",6379);
    }
    //编写从连接中获取连接的方法
    public static Jedis getJedis(){
        return jedisPool.getResource();
    }
}

工具类的使用:
在这里插入图片描述

8. 管道技术

当频繁操作Redis是,如果逐个命令去发送、执行,这样效率会很低,把要执行的命令进行“积攒”,然后分批发送到Redis服务器,进行执行,这样来提升执行效率

管道技术:用于积攒命令

package com.qf.redis2204.test;

import com.qf.redis2204.util.RedisUtil;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;

public class Test4 {
    public static void main(String[] args) {
        //执行100万个命令
        //不使用管道
        Jedis jedis= RedisUtil.getJedis();
        jedis.select(5);
        jedis.set("num","1");
        //获取时间
        long start1=System.currentTimeMillis();
        for (int i=1; i<=1000000; i++){
            //+1操作
            jedis.incr("num");
        }
        //获取结束时间
        long end1=System.currentTimeMillis();

        jedis.select(6);
        jedis.set("num","1");
        //获取管道
        Pipeline pipeline=jedis.pipelined();
        //再次获取开始时间
        long start2=System.currentTimeMillis();
        for(int i=1; i<=1000000; i++){
            //把命令积攒到管道中
            pipeline.incr("num");
        }
        //把管道中的命令提交到Redis中,并且执行
        pipeline.syncAndReturnAll();
        //获取结束时间
        long end2=System.currentTimeMillis();

        System.out.println("不使用管道用时:"+(end1-start1));
        System.out.println("使用管道技术用时:"+(end2-start2));
    }
}

效果:
在这里插入图片描述

9. Redis的数据持久化机制-重要

使用Jedis操作Redis

常用String类型,只能存储512M

热点数据

存储方式有3种:

1、普通字符串

2、对象,转成byte[]

3、对象,转成JSON

连接池-凡在工具类中:

管道:提升工作效率

9.1 为什么要持久化?

Redis使用内存来存储数据,当Redis服务器重启、宕机了,内存的数据就被清除,当服务启动后,如果进行缓存预热,自行查取数据,存入Redis,效率低。

有持久化后,自行按照条件存储内存中的数据,当Redis启动后,自动加载持久化的数据,无需进行缓存预热,提升效率。

9.2 Redis支持的持久化方式有3种:

方式1:RDB快照方式持久化-默认的

1、当符合持久化条件时,Redis就fork出一个子进程,进行持久化

2、新创建一个指定名字的持久化文件 dump.rdb

3、把内存中的所有数据存储于持久化文件中

4、删除掉原有的持久化文件,用新的持久化文件代替

在这里插入图片描述
分析:

1、优点:由于是fork出的子进程进行的持久化,所以不影响Redis的运行

2、优点:使用新的文件替代旧的持久化文件,效率相对AOF方式更高

3、缺点:可能会丢失一部分数据,在本次持久化后,下次持久化之前,Redis中变化的数据会被丢失

注意

实际使用中,save 1 X 最多只允许丢失1秒的数据

方式2:AOF只追加文件方式持久化

Append only file 只追加文件方式持久化,需要开启

在这里插入图片描述

AOF方式持久化的条件也称为刷写模式,共有3种:

在这里插入图片描述
1、Redis自身进行持久化,影响Redis的性能

2、是一直在原有的持久化文件的基础上进行追加,导致此文件越来越大

3、性能比rdb快照方式慢

方式3:两种方式一起使用

如果两种方式同时开启:那么优先使用AOF方式的持久化文件进行加载

如果先开启了RDB,后开启AOF: AOF持久化文件的内容要覆盖掉RDB持久化的内容

10. 数据淘汰策略

数据淘汰:Redis删除内存中的数据的过程

Redis自身删除过期的数据,并不是立刻删除:

方式1:定期删除,Redis每隔一段时间,删除已经过期的数据,默认是每隔100毫秒,只随机删除其中3个过期的key。

方式:惰性删除,即使过期,也不删除,当获取数据的时候,判断你是否过期,如果过期则删除,获取到的就是nil。

Redis从4开始是多线程模型:

多线程:是接收命令、响应命令的执行结果是多线程的

单线程:核心的执行存储、读取数据的业务,依然是单线程

当Redis内存,存满了数据,还需要进行存储时,就需要Redis自身删除掉一部分数据,Redis的删除依据就是数据淘汰策略:

在Redis内存已经满的时候,添加了一个新的数据,执行淘汰机制。

  • volatile-lru:在内存不足时,Redis会再设置过了生存时间的key中干掉一个最近最少使用的key。
  • allkeys-lru:在内存不足时,Redis会再全部的key中干掉一个最近最少使用的key。
  • volatile-lfu:在内存不足时,Redis会再设置过了生存时间的key中干掉一个最近最少频次使用的key。
  • allkeys-lfu:在内存不足时,Redis会再全部的key中干掉一个最近最少频次使用的key。
  • volatile-random:在内存不足时,Redis会再设置过了生存时间的key中随机干掉一个。
  • allkeys-random:在内存不足时,Redis会再全部的key中随机干掉一个。
  • volatile-ttl:在内存不足时,Redis会再设置过了生存时间的key中干掉一个剩余生存时间最少的key。
  • noeviction:(默认)在内存不足时,直接报错。

指定淘汰机制的方式:maxmemory-policy 具体策略,设置Redis的最大内存:maxmemory 字节大小

设置数据淘汰策略:

在这里插入图片描述
设置Redis服务器能使用的内存的大小

在这里插入图片描述

11. Redis的集群

为了安全,给Redis设置访问密码
在这里插入图片描述
在这里插入图片描述
集群的作用:

  • 1、提升了并发请求数据的处理能力

  • 2、防止单点故障

Redis的集群通常有3种:

1、主从集群: 一台主服务器,多台从服务器

​ 属于中心化的集群,生产环境中不会使用,由于主服务器只有一台,不高可用,容易发生单点故障

​ 主从集群进行了读写分离:

​ 主服务器:写入数据通过Redis

​ 从服务器:自动从主服务器同步、拉取数据,负责读取数据使用

在这里插入图片描述
2、哨兵集群: 是以主从集群为基础,每个服务器,配置一个哨兵,用于监控主服务器,当主服务器宕机后,会进行投票选举,选举新的主服务器。

在这里插入图片描述
3、cluster集群: 只有Linux系统支持,去中心化的集群

至少有3对节点

当某个主服务器宕机后,对应的从服务器,自动升级为主服务器,当原来的主服务器启动后,就降级为从服务器

在这里插入图片描述

由于有多个主服务器,那么写数据时,通过哪台服务器写入呢?

cluster提供了hash槽0~16383 之间,共16384个值,如果有3个主服务器,每个主服务器平均分配hash槽的值

当出入数据时,先对key值进行hash计算,得到一个0~16383之间的一个值,计算的值在哪个主服务器对应的范围内,就通过哪个主服务器写入数据,其他主服务器拉取、同步数据

11.1 主从集群的搭建

一主三从

主服务器:端口 6379

从服务器:改端口号 6380

在这里插入图片描述
服从于主服务器的信息

先启动主服务器,然后启动从服务器

通过从服务器写数据会失败,从服务器是只读的,只能读取数据,可以通过主服务器写入数据

在这里插入图片描述
【说明】通过主服务器写入数据后,从服务器会自动从主服务器拉取数据。

11.2 哨兵集群

在主从集群的基础上搭建的,为每个Redis服务器,配置一个哨兵,都去监控主服务器

主观宕机:如果某个哨兵发送ping给主服务器,在规定的内,没接收到主服务器返回的pong,认为主服务器器主观宕机

【注意】

主观宕机后,并不进行投票选举,有可能是网络延迟造成

客观宕机:监控到主服务器宕机的哨兵的数量达到了阈值,认知客观宕机

【注意】

客观宕机后,才进行投票选举

投票协议:每个哨兵都可以投票给其他服务器,也可以征求其他服务器的投票

当某个Redis的投票数达到一半以上,此服务就成为新的主服务器,其他服务器从该服务器同步、拉取数据

自动故障迁移:投票、选举主服务器、同步、拉取数据的过程。

1、在每个Redis服务器下创建一个sentinel.conf配置文件

在这里插入图片描述
2、在每个服务下创建sentinel_startup.bat文件,用于启动哨兵
在这里插入图片描述
3、把以上两个文件分别复制到各个从服务器中,修改端口号及title对应的值

4、启动

5、关闭掉主服务器后,就会自动故障迁移
在这里插入图片描述

12. Redis的常见问题及解决方案

12.1 缓存穿透

发出大量的请求,请求Redis,请求的key都是Redis不存在的,导致大量的请求到达数据库,从而把数据库请求宕机,造成数据库不可用

【说明】先到Redis获取,如果获取到,则直接返回,如果获取不到,就到数据库获取

在这里插入图片描述
解决方案:

1、缓存空值: 当到数据库中获取不到数据后,把请求的key和null值进行Redis缓存,下次再请求此key时,直接返回空值

缺点:Redis缓存并没有拦截住请求,大量的请求还是到达数据库

​ 缓存大量的无效的空值

2、布隆过滤: 把Redis中的所有的key,存储于web服务器的一个集合中

  • 每当请求Redis时,判断请求的key是否在集合中

  • 如果在集合中,则进行Redis获取

  • 如果不在,说明是无效的key,则直接返回

缺点: 对Redis进行增加、删除key操作的同时,还需要对集合进行更新

12.2 缓存击穿

key是合理的,但是Redis中大量热点数据同时过期,导致即使合理的key值,但是到Redis中依然获取不到数据,进而大量的请求到达了数据库,降低数据库的性能

在这里插入图片描述
解决方案:

1、分散设置热点数据的过期时间,避免大量的热点数据同时过期-最优

2、去掉生存时间

3、给数据库加锁,限制数据库的并发访问量

12.3 缓存雪崩

缓存雪崩:缓存服务器不可用

原因:

1、所有的 百万级别的热点数据同时过期,导致Redis失去作用

2、Redis发生单点故障

在这里插入图片描述
解决方案:

1、使用集群

2、对key的过期时间设置一个范围的随机值,避免大量的key同时过期

12.4 缓存倾斜

有多台Redis服务器,每个Redis服务器缓存了不同的热点数据,会造成请求会集中在某个Redis服务器上,最终导致Redis宕机不可用。

解决方案:

1、对热点数据的Redis服务合理配置集群中服务的数量。

【说明】声明类,存储Redis的各个key值

package com.qf.springbootvue.util;

import java.util.ArrayList;

public class RedisKeys {
    //声明集合
    public static ArrayList<String> listKeys=new ArrayList<String>();
    static {
        //把所有的Redis中的key存入集合
        listKeys.add(RedisKeys.BOOT_DEPT);
    }
    //声明key
    public static String BOOT_DEPT="depts";
}

具体内容

@Override
public List<Dept> findDept() {
    List<Dept> deptList=null;
    String key="dpets";
    //布隆过滤
    boolean flag=false;
    for (String strKey: RedisKeys.listKeys
         ) {
        if(key.equals(strKey)){
            flag=true;
            break;
        }
    }
    if(flag){
        //先到Redis中获取部门
        Jedis jedis= RedisUtil.getJedis();
        //选择库
        jedis.select(1);
        String json=jedis.get(key);

        //判断是否获取到
        if(json!=null){
            //如果获取到数据,直接返回
            deptList=JSON.parseArray(json,Dept.class);
        }else {
            //如果获取不到数据,则到数据中获取
            deptList=deptDao.findDept();
            //存入Redis
            String strJson= JSON.toJSONString(deptList);
            jedis.set(key,strJson);
        }
    }
    return deptList;
}

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值