Redis(超级无敌认真好用,万字收藏篇!!!!)

Redis

前言

关系型数据库:Sql Server MySql Oracle…

关系:表与表之间的联系

传统的关系型数据库存储在硬盘上,关系是建立在业务的基础上的。

关系型数据库存在的问题:

  1. 关系型数据库存储在硬盘上,磁盘的IO性能低下,不能满足高频访问及实效要求高的应用,特别是海

量用户高并发访问的情况

  1. 关系型数据库表关系复杂,查询效率低,不易扩展 (表连接查询不能超过7张表)

缓存:(高速缓存)可以高速访问的,用于临时存储的数据存储区

缓存使用场合

缓存一般用于在较短时间段对相同数据频繁读取的场合,将读取频度较高的数据放入缓存,直接从缓存中取出数据以提高效率

NOSQL数据库

NOSQL(Not only SQL),即"不仅仅是SQL",泛指非关系型数据库。

NOSQL不依赖于业务逻辑方式存储,而以简单的"key-value"模式存储,大大增加了数据库的扩展能力

  • 不遵循SQL标准

  • 不支持ACID(支持事务但是不支持事务的四大特性<原子性,隔离性,一致性,持久性>)

  • 远超SQL性能

NOSQL使用场景

  1. 数据的高并发读写
  2. 海量数据读写
  3. 对数据的高可扩展性

NOSQL数据库

  1. Redis:数据存储在内存中,支持持久化,以key-value模式存储数据,支持多种数据类型,一般作为缓存数据辅助持久化的数据化

  2. MongoDB:高性能,开源的,文档型数6据库,存储在内存中,也可以存储在硬盘中,以Key-value的模式存储数据,value是json的格式存储,提供了丰富的查询功能

  3. Hbase:HBase是Hadoop项目中的数据库,它主要用于对大量数据进行随机,事实的读写场景,使用列式存储的数据模式存储数据

1 Redis介绍

Redis(Remote Diconary Server)是一个开源的使用ANSI C语言编写,支持网络,可基于内存亦可持久化的日志型,Key-Value数据库,并提供多种语言API。

Redis本身是一个内存数据库,在应用中可以充当缓存,提高系统数据查询性能的非关系型数据库,统称:NOSQL。

1.1 Redis的特征

  1. 数据间没有必然的联系
  2. 内部采用单线程多路IO复用技术
  3. 高性能。(官方测试:50个并发执行十万次请求,读的速度每秒十一万次,写的速度每秒八万次)
  4. 多数据类型支持;
数据类型 存储的值 对存储的值的操作 使用场景
string 可以是字符串、整数或浮点数 对整个字符串或字符串的一部分进行操作;对整数或浮点数进行自增或自减操作; 把常用信息,字符串,图片或者视频等信息放到redis中,redis作为缓存层,mysql做持久化层,降低mysql的读写压力。例如:token
hash 包含键值对的无序散列表 包含方法有添加、获取、删除单个元素 缓存 : 能直观,相比string更节省空间的维护缓存信息,如用户信息,视频信息等
list 一个链表,链表上的每个节点都包含一个字符串 对链表的两端进行push和pop操作,读取单个或多个元素;根据值查找或删除元素 消息队列,任务队列,如秒杀,抢购,抢票等
set 包含字符串的无序集合 字符串的集合,包含基础的方法有看是否存在添加、获取、删除;还包含计算交集、并集、差集等 标签(tag) :给用户添加标签,或者用户给消息添加标签,这样有同一标签或者类似标签的可以给推荐关注的事或者关注的人。
点赞,或点踩,收藏等,可以放到set中实现
z_set 和散列一样,用于存储键值对 字符串成员与浮点数分数之间的有序映射;
元素的排列顺序由分数的大小决定;包含方法有添加、获取、删除单个元素以及根据分值范围或成员来获取元素
排行榜 :有序集合经典使用场景。例如小说视频等网站需要对用户上传的小说视频做排行榜,榜单可以按照用户关注数,更新时间,字数等打分,做排行
  • 注:Redis所有数据都是根据key和value来保存的,key永远都是字符串
  1. 持久化支持,可以进行数据灾难恢复

1.2 Redis应用场景

  1. 热点数据的加速查询,如热点商品,热点新闻,热点资讯等高访问量信息
  2. 任务队列,如秒杀,抢购,抢票等
  3. 即时信息查询,如排行榜,站点访问统计,在线人数统计,公交到站信息,设备信号等
  4. 时效性信息控制,如限时验证码,限时投票等。
  5. 分布式数据共享,如分布式集群架构中的session分离
  6. 消息队列
  7. 分布式锁

1.3 Redis的安装

  1. 下载地址(Windows版)

https://github.com/microsoftarchive/redis/releases

  1. 下载zip版本或者安装版(建议zip版) ,解压
  2. 配置环境变量
  3. 打开一个 cmd 窗口, 运行 redis-server命令,启动redis服务端

在这里插入图片描述

  1. 另打开一个cmd窗口,运行redis-cli命令,进入客户端

在这里插入图片描述

  1. 安装成功。

1.4 Redis不同版本的线程问题

Redis不同版本中所使用的线程处理方式不同,以下就三个版本进行简要说明

  • Redis4以前的版本使用单线程多路IO复用技术,也就是说Redis 4之前的版本使用的是单线程

  • Redis4.0开始支持多线程,但仅仅是针对删除操作使用多线程处理,也就是在Redis4.0版本中引入

    的惰性删除(异步删除)的方式,将删除操作交给后台线程处理,以解决主线程卡顿的问题

  • Redis6.0的版本才开始真正的使用多线程进行I/O操作,但Redis的命令依旧是主线程串行执行的,

    Redis6.0以后的版本虽然支持多线程,但默认情况下是禁用的,需要开发者在配置文件中开启并指

    定所使用的线程数量

    # So for instance if you have a four cores boxes, try to use 2 or 3 I/O
    # threads, if you have a 8 cores, try to use 6 threads. In order to
    # enable I/O threads use the following configuration directive:
    #
    # io-threads 4 设置线程的数量
    #
    # Setting io-threads to 1 will just use the main thread as usual.
    # When I/O threads are enabled, we only use threads for writes, that is
    # to thread the write(2) syscall and transfer the client buffers to the
    # socket. However it is also possible to enable threading of reads and
    # protocol parsing using the following configuration directive, by setting
    # it to yes:
    #
    # io-threads-do-reads no yes为启动多线程
    

2 Redis常用命令

传统的关系型数据库是使用表来存储数据,在Redis中存储数据使用类似于JavaMap集合的方式存储,使 用key-value存储,key不允许重复,值可以重复

  • 在Redis中存储的数据都依赖于传统数据库(如MySQL/Oracle等)
  • 对Redis内存数据库的操作不在使用SQL语句,在Redis中主要使用各种命令
具体命令 功能
select index 切换数据库(默认15个数据库,编号为0-15)
clear 清屏命令
set key value 添加一个数据value对应数据类型为string
get key 根据key获得value
lpush key value.... 添加一个数据value对应数据类型为list
lrange key 0 -1 遍历集合
keys pattern
keys *
keys j*
keys *zhong
keys ?ava
keys u[se]r
获取当前数据库中的key
获取当前数据库所有key
获取所有以j开头的key
获取所有以中结尾的key
获取前面有一个任意字符,并且以ava结尾的key
获得第一个字符为u,第二个字符为,s或者e,结尾字符为r的key
ps:"*"匹配任意字符 "?"匹配一个字符 "[]"匹配其中任意字符
exists key 判断key是否存在
type key 判断key的类型
del key 删除指定 key
fulshdb 清除库中所有数据
fulshall 清除所有数据命令
dbsize 查看库中key的数量

expire key 1
pexpire key 1
设置key的过期时间
以秒为单位
以毫秒为单位
ttl key 查看过期剩余时间(-1表示永不过期,-2表示已过期)
persist key 设置永不过期
rename key newkey 修改key的名字
renamex key newkey 修改key的名字,如果newkey不存在才修改,否则修改失败
sort key
`sort list[asc
desc]<br/>sort list[limit 0 3] [asc
quit exit ESC键 退出命令
ping 检查与服务器连通命令
echo message 控制台打印命令
move key db 数据在不同的库中移动
当前库存在key数据且1号库不存在同名数据
info 或得当前Redis属性值

3 Redis的数据类型

redis自身是一个Map,其中所有的数据都是采用 key : value 的形式存储

key:是一个字符串

value:是具有具体类型的数据

Redis中包含5种基本数据类型和3中特殊类型

3.1 string(字符串String)

string是Redis最基本数据类型,String二进制是安全的,redis的string可以存储任何数据,如图片,对象等

  • 单个数据,一个存储空间保存一个数据,如果其中存储的数字则可以当数字来用

    redis中最大可以存储512M大小的字符串,如果是数字,取值范围为java总的lang类型取值范围

命令 规则 功能
set key value 如果key存在替换原有value,如果不存在则创建新的数据 存储单个数据
mset key1 value1 key2 value2 ... 同时存储多个数据
get key 获取数据时返回值为nil表示空,没有数据 获取一个数据
mget key1 key2 ... 同时获取多个数据
strlen 获得字符串长度
append key value 如果数据存在追加数据,如果不存在新建数据 向value中增加新数据
getrange key startIndex endIndex startIndex:开始截取,索引从0开始
endIndex:终止截取
截取字符串
setrang key offset value offset:从下标为多少的字符开始替换 字符串某字符替换
setnx key value 如果key存在存值失败,如果不存在则存值 存储一个数据
incr key 如果字符串中存储的是数字则可以进行数值运算,但其数据类型还是string
必须确保值为数字格式,否则会报错,在redis中incr具有原子性.
对key的值自加1,等同于i++
incrby key number 对key的值加上指定的数值(负值进行减法运算),等同于i=i+n
incrbyfloat key number 对key的值加上或减去浮点数
decr key 对key的值自减1,等同于i++
incrby key number 对可以的值减去指定的数值
setex key seconds value 以秒为单位设置存活时间
psetex key millseconds value 以毫秒为单位设置存活时间
set key value [ex seconds][px milliseconds] 使用set方式设置key的存活期,如果使用nx选项则表示setnx+过期时间
set key value [ex seconds][px milliseconds][nx]
incrby key number 对可以的值减去指定的数值
expire key seconds 单独设置过期时间 以秒为单位
set key value [ex seconds][px milliseconds][nx] 以毫秒为单位

存储对象

我们可以使用字符串存储一个对象,但在使用时不方便,所以存储对象一般不使用字符串

string中key的命名规范

redis中常用于存储表中的一条数据(对象),所以key命名规则体现唯一原则:

表名:主键名:主键值:字段名

set user:user_id:1001{username:admin,password}

3.2 hash (哈希HashMap)

hash类型主要用于存储对象,以key field value 的形式存储

hash中的value只能为string不能在value中嵌套hash或list等

在这里插入图片描述

  • 常用指令
命令 规则 功能
hset key field value 设置数据 设置单个数据
hmset key field value 同时设置多个数据
hget key 获取数据 获得key对应的整体数据
hget key filed 获得hash单个字段数据
hmget key1 filed1 filed2 ... 同时获取多个数据
hdel key filed 删除数据 删除指定字段
hlen key 获取hash中的字段数量
hexists key filed 存在返回1,不存在返回0 获取hash中是否包含指定字段
hexists key filed 获取hash中的所有字段名和字段值 获取所有字段值
hkeys key 获取所有字段名
hincrby key filed number 设置指定字段数值增加或减少 增加或减少整数(负数为减少)
hincrbyfloat key filed number 增加或减少浮点数(负数为减少)

3.3 list (列表LinkedList)

在Redis中可以把list用作栈、队列、阻塞队列

list中允许存放重复数据

list中存储的数据有序(指进入顺序<分左右>)

  • 常用指令

    ① 向列表添加数据

    • 左边开始添加:lpush key value
    • 右边开始添加:rpush key value

    ② 从list中获取元素

    • 根据起始和结束下标获取此范围内的列表元素:lrange key startIndex endIndex

    ③ 从list中弹出元素

    • 获得列表头部的第一个元素并从列表中移除:lpop key
    • 获得列表尾部的第一个元素并从列表中移除:rpop key

    ④ 通过下标获取list中的某个元素

    lindex key index

    ⑤ 获得列表中元素的数量(长度)

    llen key

    ⑥ 根据元素值从列表中移除指定数量的元素

    lrem key count value

    ⑦ 截取子list(截断子元素)

    • 截断后list中剩余的数据为截断数据 :ltrim key start stop

    ⑧ 将原列表中最后一个元素移到新列表中

    source:列表

    destination:新列表

    rpoplpush source destination

    ⑨ 根据下标重置list中的一个元素(根据下标修改list中的一个元素)

    lset key index value

    ⑩ 向某个元素前或后插入一个元素

    linsert key BEFORE|AFTER pivot value

3.4 set (集合HashSet)

不保证顺序,集合不能存放重复元素

  • 常用指令

    ① 向set集合中添加一个元素

    sadd key value1 value2...

    ② 查看set集合中所有元素

    smembers key

    ③判断一个元素是否存在于set集合中

    0表示不存在 1表示存在

    sismembers key value

    ④获取set集合元素个数

    scard key

    ⑤ 移除一个元素

    srem key value

    ⑥ 随机抽取一个元素

    • 随机抽取一个或者多个元素:srandmember key [count]

    ⑦ 随机删除元素

    • 随机删除一个或者多个元素:spop key [count]

    ⑧将一个特定的值,移动到另一个set集合中

    destination:另外一个集合

    smove source destination member

    ⑨ 集合操作

    • 差集:sdiff key [key ...]
    • 交集:sinter key [key...]
    • 并集:sunion key [key..]

3.5 sorted_set又称z_set(有序集合TreeSet)

该集合是对set集合的改造,在set集合中加入了一个字段值,用于存储排序规则数据,该数据只负责排序不起其他作用

  • zset存储结构

在这里插入图片描述

  • 常用指令

    ① 向z_set集合中添加元素

    zadd key score1 value1 score2 value2...

    ② 获取z_set集合中的元素

    • 从小到大的顺序显示元素信息,withscores显示排序规则字段:zrange key start stop [WITHSCORES]
    • 从大到小的顺序显示元素信息,withscores显示排序规则字段:zrevrangekey start end [WITHSCORES]

    ③ 按条件获取z_set中的元素

    • 获取min-max之间的元素可用limit限制:zrevrangebyscore key min max [limit] [withscores]
    • 降序:zrevrangebyscore key max min [limit] [withscores]

    ④ 增加或减少z_set中元素score

    • zincrby key increment value

    ⑤ 删除z_set中的元素

    • 根据元素名删除一个或多个元素 :zrem key member1 member2...
    • 删除指定下标范围的元素:zremrangebyrank key start stop
    • 根据socres指定范围删除元素 :zremrangebyscore key min max

    ⑥ 获得元素在集合中的排名

    • 升序排名:zrank key member
    • 降序排名:zrevrank key member

    ⑦ 获得集合中元素数量

    • 获得集合中元素数量:zcard key
    • 获得指定范围的元素数量:zcard key min max

    ⑧ 集合交集和并集

    • 集合交集操作,将多个集合的交集存 入到newset集合中,相交集合的数量个setcount指定的数量要一致,默认对交集数据进行求和运算, 也可以获得最大值或最小值等运算

      zinterstore newset setcount set1 set2 ...

    • 集合并集操作:zunionstore newset setcount set1 set2 ...

3.6 特殊类型

1.geospatial :地理位置

城市经纬度查询: 经纬度查询

  • 两极无法直接添加,我们一般会下载城市数据,直接通过java程序一次性导入!
  • 有效的经度从-180度到180度。
  • 有效的纬度从-85.05112878度到85.05112878度。
  • m 为米。km 为千米。mi 为英里。ft 为英尺。

2.hyperloglog : 基数

数学层面上可以说是:两个数据集中不重复的元素
但是再Redis中,可能会有一定的误差性。 官方给出的误差率是0.81%。
Hyperloglog的优点: 占用的内存是固定的,2^64个元素,相当于只需要12kb的内存即可。效率极高!

3.bitmap 位图

都是操作二进制位来进行记录,只有0 和 1 两个状态

4 Jedis

4.1 Jedis简介

jedis是Java操作redis的一个工具,等同于jdbc,可用使用jedis实现java操作redis的目的。

Spring中操作Redis的技术是Spring Data Redis

4.2 Jedis操作Redis

  1. 引入Jedis的依赖包
<dependency>
	<groupId>redis.clients</groupId>
	<artifactId>jedis</artifactId>
	<version>4.3.1</version>
</dependency>
  1. 连接Redis
		/**
         * 创建jedis对象并指定服务器ip地址及端口号
         * 参数1:Redis服务器地址
		 * 参数2:Redis服务器端口号
         */
        Jedis jedis=new Jedis("127.0.0.1",6379);
  1. 操作Redis
        jedis.set("num1","0");
        String value = jedis.get("num1");//根据key获得对应的value
  1. 关闭连接
//关闭连接
jedis.close();
  • Jedis操作Sting

    1. 向redis添加数据

      //添加一条数据
      jedis.set("num1","0");
      //添加多条数据
      jedis.mset("num3","2","num4","3");
      
    2. 获得redis中元素

      ①根据key获得对应的value

           //获取一个数据
               String value = jedis.get("num1");
           System.out.println(value);
           //获取多个数据
           List<String> mget = jedis.mget("num1", "num2", "num3");
              System.out.println(mget);
      

      ②获取redis所有元素的key

           //获取redis所有元素的key
              Set<String> keys = jedis.keys("*");
              System.out.println(keys);
      
    3. 设置时效

      jedis.expire("num1",1000);
      //获得剩余时效
      long num11 = jedis.ttl("num1");
      System.out.println(num11);
      
  • Jedis操作List

        public static void jedisForList(){
          
          
            /**
             * 创建jedis对象并指定服务器ip地址及端口号
             */
            Jedis jedis = new Jedis("127.0.0.1", 6379);
            //向Redis中添加list数据
            jedis.lpush("list","a","b","c");
            jedis.rpush("list","x");
            //从Redis中将key为list1的数据获取,返回java.util.List集合
            List<String> list1 = jedis.lrange("list1", 0, -1);
            //遍历List集合
            for(String str : list1){
          
          
                System.out.println(str);
            }
            //关闭Redis的连接
            jedis.close();
        }
    
  • Jedis操作set集合数据

    	    public static void jedisForSet(){
          
          
            /**
             * 创建jedis对象并指定服务器ip地址及端口号
             */
            Jedis jedis = new Jedis("127.0.0.1", 6379);
            //向Redis中添加set集合数据
            jedis.sadd("set1","v1","v2","v3");
            //从Redis中获取key为set1的数据,返回值自动封装到java.util.Set集合中
            Set<String> set1 = jedis.smembers("set1");
            for(String set : set1){
          
          
                System.out.println(set);
            }
            //关闭与Redis的连接
            jedis.close();
        	}
    
  • Jedis操作Map

        public static void jedisForMap(){
          
          
            /**
             * 创建jedis对象并指定服务器ip地址及端口号
             */
            Jedis jedis = new Jedis("127.0.0.1", 6379);
            //向hash中添加数据
            Map valMap = new HashMap<String,Object>();
            valMap.put("id","001");
            valMap.put("name","小明");
            valMap.put("age","20");
            /**
             * 参数1:key
             * 参数2:value对应的是map集合
             */
            jedis.hmset("user",valMap);
            Map<String,String> user = jedis.hgetAll("user");
            System.out.println(user);
            //关闭与Redis的连接
            jedis.close();
        }
    

5 Redis基本配置

  • redis启动时会读取相应配置文件,我们可以通过修改redis的配置文件对redis进行配置,同时也

    可以设置多个不同的配置文件,启动多个redis服务,如redis-6379.conf,redis-6380.conf

    ① Redis服务端启动方式

    • 默认启动方式: redis-server 读取的配置文件为默认的件redis.windows-service.conf

    • 指定配置文件启动方式:redis-server (xxx/redis-6380.conf)

      在redis目录里: redis-server redis-6380.conf

      配置文件在指定文件夹里(此处为redis目录下的config文件夹):redis-server config/redis-6381.conf

    ② Redis客户端启动方式

    • 默认启动方式:redis-cli访问默认服务器
    • 访问指定端口的服务:redis-cli -p 6380
    • 访问指定服务器上特定端口的redis服务:redis-cli -h 127.0.0.1 -p 6381

    基本配置

    #服务器绑定的ip地址
    bind 127.0.0.1
    
    #Redis服务器绑定的服务器端口号
    port 6380
    
    #超时时间  0表示不超时
    timeout 0
    
    # redis是否使用守护线程,设置为no表示不使用启动时会在控制台上打印启动信息
    # 设置为yes表示使用守护线程,后台启动,不打印启动信息(该配置在windows上不支持)
    # daemonize no
    
    #日志文件,该文件配置后,redis相关的日志信息会输出到指定的配置文件中,便于查看
    logfile "E:/Redis/Redis-x64-3.2.100/config/log/redis-6380.log"
    
    #Redis服务器数据库的数量
    databases 16
    
    # 指定存储到本地的数据库文件时是否对数据进行压缩,默认为yes,采用LZF压缩(一般开启)
    rdbcompression yes
    
    # 设置是否对rdb文件格式进行校验,该校验在写文件和读文件时均会进行,默认为yes(一般开启,如
    果设置为no读写性能会提升但存在数据损坏风险)
    rdbchecksum yes
    
    # 当后台保存数据时如果出现错误是否停止保存操作
    stop-writes-on-bgsave-error yes
    

6 Redis持久化

  • redis是基于内存的数据库,它是存储在内存上,如果突然断电或遇到其他意外情况把内存中的数

    据清除,就会造成redis中数据的丢失,为避免这样的情况出现而操作损失,在redis中提供了持久

    化机制。

  • redis持久化指将redis中的数据保存到可以永久存储的存储介质中,在需要时将保存的数据进行恢复。

Redis中提供了两种持久化方案:

(1) RDB方式(快照方式):将当前数据状态以快照的形式进行保存,存储格式简单,关注点在数据

(2) AOF方式(日志方式):将操作数据的过程以日志的方式记录下来,存储格式复杂,关注点在操作

数据的过程。

7.1 RDB实现持久化

RDB方式实现持久化,使用save命令可以将当前的redis中的数据存储到磁盘上,存储文件的默认名称为dump.rdb

  • 当redis服务启动时,会自动读取rdb文件,将已存储的备份进行恢复

  • save指令是指示redis立即将数据存储到磁盘上进行备份,但如果存储的数据量过大,而且同时又

    有其他客户端也要访问redis时就会造成堵塞状态,影响性能(不建议使用),redis提供了后台执行命

    bgsave来解决这个问题

  • savebgsave两个命令都会调用 rdbSave 函数将当前Redis中的数据存入到rdb文件中,但两者调

    用的方式不同

    • save直接调用rdbSave,会阻塞Redis主进程,直到保存完成为止。在主进程阻塞期间,服务器不能处 理客户端的任何请求。
    • bgsave则调用fork函数生成一个子进程,子进程负责调用rdbSave,并在保存完成之后向主进程发送 信号,通知保存已完成。Redis服务器在bgsave执行期间仍然可以继续处理客户端的请求。
  • 在配置文件里配置rdb的路径和文件名

    #指定持久化文件存储路径
    dir E:/Redis/Redis-x64-3.2.100/config/data

    #指定持久化文件文件名
    dbfilename dump-6380.rdb

ps:为什么在配置文件里持久化文件名不在dir(持久化文件存储路径)里配置?

答:因为持久化方式不止一种

  • 手动保存过于繁琐,Redis里面提供了一种"自动保存快照"的方式,当达到存储Redis要求时,Redis就会自动调用bgsave指令,自动保存当前数据,我们需要在配置文件里配置自动保存的时间及数据变化量(不配置不会自动保存)
    #该配置表示开启Redis的自动备份功能
    #配置rdb快照方式自动保存时间及数据变化量
    #每隔900s内有一个数据发送改变则自动保存
    save 900 1 
    #每隔300s内有10个数据发送改变则自动保存
    save 300 10
    #每隔60s内有10000个数据发送改变则自动保存
    save 60 10000

PS:

  • 根据使用场景进行设置,频率过高和过低都会使性能受到影响

  • 调用debug reload 指令重启服务器时会自动保存数据

  • 服务器关闭时(shutdown指令)会自动保存数据

  • RDB持久化方式的优点

    RDB是一个紧凑压缩的二进制文件,存储效率高

    RDB内部存储的是Redis在某个时间点的数据快照,非常适用于数据备份,数据整体复制等场景

    RDB恢复数据的速度要比AOF快

  • RDB持久化方式的缺点

    RDB方式基于快照思想,每次读写全部数据,当数据量巨大时效率低、IO读写性能低下

    RDB方式无法实时备份数据,数据丢失的可能性大

    bgsave指令每次执行要创建子进程,内存会产生额外的消耗

7.2 AOF实现持久化

  • AOF持久化是以独立日志方式记录每次写命令,重启时在重新执行AOF文件中的命令以恢复数据

  • 与RDB相比AOF不是存储所有数据而是存储操作数据的过程,实时性更高

  • AOF解决了数据持久化的持久性,目前AOF已经是数据持久化的主流方式

  • AOF持久化方式写数据的过程:

    • Redis将写命令写入AOF写缓冲区
    • 达到一定程度,将AOF缓冲区中数据一次性写入AOF文件中
  • AOF写数据的三种策略:

    • always(每次写都存入)

      每次写都存入,虽然保证了数据存储的实时性,准确性,但是当写入数据过多,每次写入都需要IO操作,就会产生大量IO操作,效率低下(不建议使用)

    • everysec(每秒写入)

      每秒将缓冲区中的写操作同步到AOF文件中,数据实时性,准确性,性能较高(建议使用,Redis默认使用方式)

    • no(系统写入)

      由操作系统控制每次同步到AOF的周期

  • AOF持久化的使用

    • AOF默认在Redis中是禁用的,需要在配置文件中打开,设置为yes,开启后服务器会自动生产一个aof文件,用于备份数据

      #配置是否使用AOF存储数据,默认为no
      appendonly no|yes
      
    • 在配置文件里指定aof文件名

      #配置aof文件名
      appendfilename "appendonly-6380.aof"
      
    • 在配置文件中配置AOF存储所使用的策略,默认为everysec策略

      # 配置AOF存储的写入策略
      # appendfsync always
      appendfsync everysec
      # appendfsync no
      
  • AOF重写

    随着不断向aof写入数据,aof文件会越来越大,且操作中会有大量重复且无效的操作,替换过程,Redis引入了AOF重写机制,将对AOF压缩,将AOF中原有指令进行重组,将无效,多余的指令移除,多条追加指令进行合并,转换为最终数据对应的指令进行记录。以实现压缩aof文件的目的

    • AOF重写的作用

      ① 降低磁盘占用量,提高磁盘使用率

      ② 提高持久化效率,降低持久化时间,提高IO性能

      ③ 提高数据恢复效率

    • AOF重写规则

      ① 超时数据不再写入文件

      ② 忽略无效指令,重写时使用进程内的数据直接生成,这样新的aof文件只保存最终数据的写入命令

      ③ 对同一数据的多条命令进行合并

      • 如:lpush list a,lpush list b,lpush list c转换为l push a,b,c
    • AOF重写的使用

      ① 使用bgrewriteaof手动重写

      127.0.0.1:6380> set name xiaowang
      OK
      127.0.0.1:6380> set age 20
      OK
      127.0.0.1:6380> set sex 1
      OK
      127.0.0.1:6380> set num01 10
      OK
      127.0.0.1:6380> set num02 10
      OK
      127.0.0.1:6380> delete num01
      (error) ERR unknown command 'delete'
      127.0.0.1:6380> del num01
      (integer) 1
      127.0.0.1:6380> set num02 20
      OK
      #查看aof文件
      127.0.0.1:6380> bgrewriteaof
      Background append only file rewriting started
      127.0.0.1:6380>
      #查看重写的aof文件
      

      ② 在配置文件里进行配置实现自动重写

      Redis中配置好AOF自动重写的触发条件后,当条件满足自动触发,在Redis中有两个触发条件配置,当达到两个条件就会自动触发AOF重写

      # AOF自动触发百分比
      auto-aof-rewrite-percentage 100
      # AOF自动触发最小尺寸
      auto-aof-rewrite-min-size 64mb
      

      当Reids中自动触发条件大于触发参考值时,执行AOF重写(参考条件可根据info指令查看,Redis中已配置好)

      # AOF自动触发参考值
      aof_current_size:60 #当AOF触发条件的最小尺寸大于该值自动执行AOF重写
      aof_base_size:60 #AOF触发基础尺寸
      # 当前使用百分比大于或等于自动触发百分比时会自动执行AOF重写
      # 公式:aof_current_size-auto-aof-rewrite-min-size/aof_base_size>=autoaof-rewrite-percentage
      

7 Redis中的事务

传统的事务:事务对应一个sql集,其中包含sql语句,这些sql语句要么全部执行,要么不执行,必须符合事务的(ACID)特性

Redis中的事务:不需要符合所谓(ACID)四大特性。

  • Redis中事务是为了保证多条执行的指令在执行过程中不被打断
  • 开启事务:multi指令,该指令用于设置事务的开始位置,该指令后的所有指令都会加入Redis的事务中,形成一个指令队列

  • 执行事务:exec指令,该指令用于执行事务,表示事务的结束和指令队列的执行,与multi成对使用

    在这里插入图片描述

  • 取消事务:discard指令,该指令必须在multi之后,在exec之前,该指令用于取消当前事务

PS:

Redis事务中如果出现指令语法错误,则会自动取消当前事务

在事务执行过程中,即使出现错误,事务也会继续执行

8 Redis中锁的概念

  • watch监控锁

    当同一个数据需要改变时,可能会出现多个人同时修改该数据,数据可能发生错乱,为了避免发生数据修改错乱,此时加入watch锁,只允许数据修改一次数据,当一个人修改后其他人则不能改变,其他人修改该数据,则终止事务的执行

    • 加入watch锁,执行事务前,如果数据发生变化,则终止事务执行

      watch key[key1 key2....]

    • 关闭watch锁

      unwatch

  • 分布式锁

    当多个线程同时操作一个数据数,可能出现数据错乱,为避免该情况,我们需要将该数据锁起来,当一个线程操作完成,才允许另外一个线程操作该数据,Redis使用分布式锁实现此场景

    Redis中没有分布式锁,我们可以使用setnx设计一个分布式锁

    • 添加一个key,该key为分布式锁,我们知道setnx在设置数据时如果数据存在则返回0

    • 设置该数据为锁,其他客户端要操作数据前先通过该指令的返回值检测如果返回值为0
      则表示当前数据已被锁定不能操作,如果返回值为1表示加锁,然后操作。

      setnx lock-num 1

    • 对加锁的数据使用后要解锁,通过del lock-num移除数据的方式实现解锁过程
      del lock-num

  • 死锁

    通过分布式锁的机制可以实现对数据操作的排他性,但数据使用结束后需要解锁(谁加锁,谁解锁),但有时可能会忘记解锁或发送意外无法解锁,就成了死锁。。。

    设计分布式锁时不允许出现死锁

    在设置分布式锁就需要添加时效,在一定时间后自动解锁

    set key value [ex seconds][px milliseconds][nx]:设置分布式锁

9 Springboot整合Redis

Spring使用Spring Data与Redis进行整合,在SpringData中提供了一个模板类RedisTemplate来实现redis的相关操作

9.1 添加相关依赖和设置配置

  • 添加相关依赖

    • 引入springBoot父项目
       <!--引入springBoot父项目
           springboot对spring相关版本进行规定
    	   也对spring基础配置进行了约定
         -->
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.7.9</version>
        </parent>
    
    • 引入spring-boot-start-web
    <!-- spring-boot-starter-web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-web</artifactId>
        <version>2.7.9</version>
    </dependency>
    
    • 引入spring-boot-start-data-redis
    <!-- spring-boot-starter-data-redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
        <version>2.7.9</version>
    </dependency>
    
  • 设置配置

    server.context:
      port: 80
    
    
    spring:
      redis:
        host : 127.0.0.1 #配置ip地址
        port : 6380 #配置端口号
        jedis:
          #配置连接池
          pool:
            max-active: 10 # 最大活动连接
            min-idle: 2 # 最小空闲连接
            max-idle: 5 # 最大空闲连接
            max-wait: 1000 # 最大等待时间
    
  • 创建Bean

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
          
          
       private Integer userId;
       private String username;
       private String password;
    }
    

9.2 SpringDataRedis对象的序列化

要将程序中的数据进行外部存储,都必须将数据进行序列化。

数据从外部存储到程序中,也需要将数据进行反序列化

  • SpringDataRedis默认使用JDK方式将程序中的数据进行序列化,要求类需要实现序列化接口

    该种方法传到redis中会有富余数据,所以我们使用下列方式设置序列化

    private RedisTemplate redisTemplate;
    @Autowired
    public ApplicationTest(RedisTemplate redisTemplate) {
          
          
        this.redisTemplate = redisTemplate;
          /**
           * 设置redisTemplate中key和value的序列化
           * SpringDateRedis默认使用JDKSerializationRedisSerializer对key和value进行序列化
           * GenericJackson2JsonRedisSerializer:将任意类型的数据转化为json串进行序列化到redis中
           * Jackson2JsonRedisSerializer<User>(User.class):将指定类型转化json并序列化到redis中
           * GenericToStringSerializer<User>(User.class):将任意类型数据转化为字符串
           */
    //设置key序列化
    //StringRedisSerializer:只支持字符串
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    //设置value序列化
    redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
    //redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<User>(User.class));
    //redisTemplate.setValueSerializer(new GenericToStringSerializer<User>(User.class));
    }
    

9.3 SpringBoot整合Redis

  • 操作字符串

        @Test
        void operateString() {
          
          
            //获得字符串操作对象
            ValueOperations valueOperations = redisTemplate.opsForValue();
    //        valueOperations.set("user",new User());//添加对象
    //        valueOperations.set("num",100);
    //        Integer num = (Integer) valueOperations.get("num");
    //        Long num = valueOperations.increment("num");//incr命令
    //        Long num = valueOperations.increment("num", 10);//incrby命令
    //        User user = (User) valueOperations.get("user");
    //        valueOperations.set("username","admin",100, TimeUnit.SECONDS);//添加字符串并设置过期时间
    //        valueOperations.set("age",18, Duration.ofSeconds(10));
    //        Map<String,Object> map=new HashMap<>();
    //        map.put("username","admin");
    //        map.put("age",88);
    //        valueOperations.multiSet(map);//mset命令
    //        ArrayList arrayList=new ArrayList();
    //        Collections.addAll(arrayList,"username","age");
    //        List list = valueOperations.multiGet(arrayList);//mget命令
    //         valueOperations.setIfAbsent("age",18);//setnx命令
    //        valueOperations.setIfAbsent("age1",18);
    //        System.out.println(user);
        }
    
  • 操作list

        @Test
        void operateList() {
          
          
            ListOperations listOperations = redisTemplate.opsForList();
    //        listOperations.leftPush("list","aaa");//lpush
    //        listOperations.leftPush("list","bbb");//lpush
    //        listOperations.rightPush("list","ccc");//rpush
    
    //        listOperations.leftPushAll("list","ddd","eee","fff");
    //        listOperations.leftPop("list");//lpop
    //         List list = listOperations.range("list", 0, -1); //lrange key start end
    //        list.forEach(System.out::println );
            
        }
    
  • 操作hash

        @Test
        void operateHash() {
          
          
            HashOperations hashOperations = redisTemplate.opsForHash();
    //        hashOperations.put("hash","hashKey","value");//hset
            Map<String,Object> map=new HashMap<>();
            map.put("hashKey01","value01");
            map.put("hashKey02","value02");
            map.put("hashKey03","value03");
            map.put("hashKey04","value04");
            hashOperations.putAll("hash",map);//hmset
    //        System.out.println(hashOperations.get("hash", "hashKey"));//hget
            System.out.println(hashOperations.entries("hash"));//hmget
        }
    
  • 操作set

        @Test
        void operateSet() {
          
          
            SetOperations setOperations = redisTemplate.opsForSet();
    //        setOperations.add("key1","value1","value2","value1");//sadd
    
    //        System.out.println(setOperations.isMember("key1", "value"));//sismenmbers
            setOperations.add("key1",new User());
            System.out.println(setOperations.members("key1"));//smembers
        }
    
  • 操作Z_set

        @Test
        void operateZSet() {
          
          
            ZSetOperations zSetOperations = redisTemplate.opsForZSet();
    //        zSetOperations.add("scores","小明",90.9);//zadd
    //        zSetOperations.add("scores","花花",88.6);
    //        Set scores = zSetOperations.range("scores", 0, -1);//zrange
    //        Set <ZSetOperations.TypedTuple> scoreSet=new HashSet<>();
    //        scoreSet.add(ZSetOperations.TypedTuple.of("张三",100d));
    //        scoreSet.add(ZSetOperations.TypedTuple.of("李四",60.55));
    //        zSetOperations.add("scores",scoreSet);
            /**
             * 根据z_set的key获得value和filed,filed和value被封装在TypedTuple中
             */
    //        Set<ZSetOperations.TypedTuple> scores = zSetOperations.reverseRange("scores", 0, -1);//zrevrange
    //        scores.forEach(System.out::println);
    //        for (ZSetOperations.TypedTuple typedTuple : scores) {
          
          
    //            System.out.println(typedTuple.getScore() + ":" + typedTuple.getValue());
    //        }
    
            //获得前三名
    //        Set<ZSetOperations.TypedTuple> scores = zSetOperations.reverseRangeByScoreWithScores("scores", 0, 100, 0, 3);
    //        for (ZSetOperations.TypedTuple typedTuple:scores){
          
          
    //            System.out.println(typedTuple.getScore()+":"+typedTuple.getValue());
    //        }
    //        Long rank = zSetOperations.rank("scores", "小明");//获得该值得排名
    //        System.out.println(rank+1);
            Long scores = zSetOperations.zCard("scores");//获得元素数量
            System.out.println(scores);
        }
    

  • 学习来自于西安加中实训

猜你喜欢

转载自blog.csdn.net/woschengxuyuan/article/details/126994785