Redis 2021最新 超详解 教程(狂神笔记+个人总结+代码+截图)

  1. 文章目录
  • 一.NoSQL 简介
    1. 1.数据库演化
    2. 2.为什么使用NoSQL
    3. 3.NoSQL特点
    4. 4.NoSQL分类
    5. 5.电商的数据存储
      1. 商品的基本信息
      2. 商品的描述、评论(文字较多)
      3. 图片
      4. 商品关键字(搜索)
      5. 商品热门信息
      6. 商品的交易,外部支付接口
  • 二、Redis 简介
    1. 1.简介
    2. 2.为什么需要Redis
    3. 3.Redis帮我们解决什么
    4. 4.学习途径
    5. 5.下载
  • 三、Redis 安装
    1. 1.windows环境
      1. 启动服务端
      2. 启动客户端
  • 2.Linux安装
    1. 下载
    2. 环境
    3. 安装
    4. 配置文件
    5. 启动
    6. 关闭
  • 3.测试性能
    1. 分析结果
  • 四、Redis 基础知识
    1. 基础指令
    2. Redis单线程
  • 1.五大数据类型
    1. Redis-Key
    2. String(字符串)
    3. List(列表)
    4. Set(集合)
    5. Hash(HashMap集合、散列表、链表数组)
    6. Zset(有序集合)
  • 2.三种特殊数据类型
    1. geospatial
    2. Hyperloglogs
    3. Bitmaps
  • 3.Redis 事务
  • 4.Redis实现乐观锁
  • 五、Java 操作 Redis
    1. 1.Jedis 官方原生API
      1. 概念
      2. 项目、依赖
      3. 连接、测试
      4. 常用API
        1. Key操作
        2. String类型
        3. List集合
        4. Set集合
      5. Hash
      6. Jedis 事务
  • 2.SpringBoot 整合 Redis
    1. 项目准备
    2. Redis测试
    3. 序列化问题
    4. 自己写工具类
  • 六、Redis 高级
    1. 1.redis.conf 配置文件
    2. 2.Redis 持久化
      1. RDB(Redis DataBase)
      2. AOF(Append Only File)
      3. Redis 持久化总结
  • 3.Redis 发布订阅
  • 4.Redis 主从复制
    1. 概念
    2. 配置
    3. 启动
    4. 主从链路
  • 5.哨兵模式(重点)
    1. 概念
    2. 模型
    3. 配置
    4. 测试
  • 6.Redis 缓存穿透、击穿、雪崩(面试重点)
    1. 缓存穿透
      1. 概念
      2. 布隆过滤器
      3. 缓存空对象
    2. 缓存击穿
      1. 概念
      2. 设置热点数据永不过期
      3. 加互斥锁
    3. 缓存雪崩
      1. 概念
      2. Redis高可用
      3. 限流降级
      4. 数据预热
  • 文章目录

    一.NoSQL 简介

    1.数据库演化

    早期网站访问量不大,单个数据够用,多数为静态网页,服务器没有太大压力,网站瓶颈主要在

    • 数据量过大,一台机器放不下
    • 数据索引(B+Tree)内存也放不下
    • 访问量读写混合,一个服务器承受不了

    为了解决以上问题,开始使用 Memcached(缓存)+MySQL+垂直拆分(读写分离)

    • 开始没有MyCat中间件,为了确保多个数据库服务器数据同步,所以数据库采用了读写分离的方式
    • 网站的主要消耗在读操作,所以经常使用的数据使用缓存来保证查询效率

    进一步优化结构,采用 分库分表+水平拆分+MySQL集群

    • 核心问题在解决数据库的 读、写

    数据量进一步增长,需要保存较大的文件,如博客、图片等,MySQL开始力不从心,效率低下,到了近期技术需要进一步提升,由转恩的数据库来处理。比如,一个几亿条数据的表,需要加一个列,很难想象。

    2.为什么使用NoSQL

    用户个人信息、社交网络、地理位置、用户自己产生的数据、用户日志等数据量巨大,这些类型数据存储不需要固定的格式,也就是行列,不需要操作就可以横向扩展,

    需要新型的专门数据库出里,随之出现了NoSQL

    NoSQL=Not Only SQL

    3.NoSQL特点

    • 方便扩展,数据之间没有关系,方便扩展
    • 大数据量高性能,读写速度快,是细粒度缓存
    • 数据类型多样,不需要事先设计数据库,随取随用,数据量庞大的表很不好设计

    传统的关系型数据库RDBMS

    • 结构化组织
    • 语言SQL
    • 数据和关系都存在单独的表中
    • 操作,数据定义语言
    • 严格的一致性
    • 基础的事务

    NoSQL

    • 存储不仅仅是数据
    • 没有固定的查询语言
    • 键值对存储,列存储,文档存储,图形数据库(社交关系)
    • 最终一致性(最终一致即可)
    • CAP定理和BASE
    • 高性能,高可用,高可扩展

    4.NoSQL分类

    • 泛指非关系型数据库,为了和关系型数据库做区分,Redis就是一款NoSQL
    • 主流分为四大类:
      1.Key-Value型:Redis Tair memecache
      2.文档型:ElasticSearch Solr MongoDB(非关系型数据库中最像关系型数据库的)
      3.面向列:Hbase Cassandra
      4.图形化:Neo4j,不是存图片,而是存关系图形化的

    简单的理解:关系型数据库以外的都是非关系型数据库,因为它不采用表结构

    5.电商的数据存储

    商品的基本信息

    名称、价格、商家等信息,
    采用关系型数据库,MySQL、Oracle

    商品的描述、评论(文字较多)

    文档型数据库,如MongoDB

    图片

    分布式文件系统,FastDFS

    淘宝自研技术,TFS

    Google:GFS

    Hadoop:HDFS

    阿里云:OSS

    商品关键字(搜索)

    早期搜索引擎:solr elasticsearch

    淘宝使用:ISearch

    商品热门信息

    内存数据库:Redis,Tair,Memache

    商品的交易,外部支付接口

    第三方应用如银行

    二、Redis 简介

    1.简介

    • Redis全称Remote Dictionary Server,即远程字典服务,由C语言编写,所以不需要安装Java环境,是一款基于K-V的NoSQL,使用起来就像map一样,免费,开源,也叫结构化数据库
    • 一个意大利人需要开发一款LLOOGG统计页面,因为MySQL性能不好,所以自己研发一款非关系型数据库,并命名为Redis
    • 基于内存存储数据,读写速度快,性能达到110000次/s读数据,81000次/s写数据,但是内存存储不持久,所以Redis提供了持久化机制
    • Redis还提供了主从、哨兵以及集群的搭建方式,可以更方便的横向扩展以及垂直扩展
    • 新浪有全世界最大的Redis集群,数据量大的服务器基本都需要Redis,他是后台开发人员的必备技能之一

    在这里插入图片描述

    2.为什么需要Redis

    • 由于用户量增大,请求数量增大,数据压力过大,服务器集群能承受住,数据库无法承受
    • 多台服务器之间存在数据不同步,比如,上次登录的服务器有session,这次登录的服务器没有session
    • 多台服务器之间的锁已经不具备互斥性,各论各的

    在这里插入图片描述

    3.Redis帮我们解决什么

    • 针对热点数据添加缓存,存到Redis中,Redis基于内存存储、读取数据,读写效率非常高
    • 将之前存储在session中的共享数据统一存放Redis中
    • Redis基于接收用户请求的是单线程的
    • 可以实现持久化
    • 可以发布订阅,实现消息队列功能
    • 可以地图信息分析
    • 支持计时器、计数器,比如支持浏览量
    • 提供多种语言API

    特性:多样的数据类型、持久化、集群、事务

    4.学习途径

    官网:https://redis.io/

    中文网:http://www.redis.cn/

    论坛

    5.下载

    windows版本在Github上下载,很久没有更新,Redis官方推荐使用Linux版本

    三、Redis 安装

    1.windows环境

    github下载,放在指定的目录下,直接解压文件,Redis很小,只有几兆

    在这里插入图片描述

    • redis-server.exe:启动服务端
    • redis-cli.exe:启动客户端
    • redis-check-aof.pdb:检查持久化文件是否正确
    • redis-benchmark.exe:测试性能

    启动服务端

    双击redis-server,启动服务端,Redis默认端口号6379,

    如果启动服务端发现闪退,可能是重复启动导致的,进入任务管理器关闭旧的即可
    在这里插入图片描述

    启动客户端

    双击redis-cli,默认连接6379端口,使用ping测试是否连接正常

    在这里插入图片描述
    测试存储,设置一个k-v,并获取值,如,

    set name jack get name
    在这里插入图片描述

    window下使用很简单,但是Redis推荐我们使用Linux环境使用!官方已经不维护了,只是微软在维护而已
    在这里插入图片描述
    windows环境下也可以使用RedisDesktopManager来作为客户端界面,
    在这里插入图片描述

    2.Linux安装

    下载

    官网下载:https://redis.io/download

    在这里插入图片描述
    使用VMware启动Linux,MobaXterm连接Linux
    在这里插入图片描述
    将Redis压缩包上传到Linux中,进入到上传目录,
    在这里插入图片描述
    解压Redis压缩包,将程序移动到opt目录下(推荐放在此处),

    tar -zxvf redis-6.2.0.tar.gz
    mv redis-6.2.0 /opt
    

    在这里插入图片描述

    环境

    Redis运行需要C++环境,安装C++,查看C++版本状态

    yum install gcc-c++
    gcc-v
    

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

    安装

    进入Redis解压缩的目录,执行make命令,开始安装Redis(需要一段时间),make insall确认一下

    cd redis-6.2.0/
    make
    make install
    

    在这里插入图片描述

    Redis默认安装目录在/usr/local/bin中,这里的目录就相当于windows中解压缩后进入的目录
    在这里插入图片描述

    配置文件

    在当前目录下新建一个配置文件目录,将Redis解压文件中的redis.con配置文件复制到其中

    以后使用这个复制的配置文件启动Redis

    mkdir rconfig
    cp /opt/redis-6.2.0/redis.conf rconfig/
    

    在这里插入图片描述

    启动

    Redis默认不是后台启动,将其修改为后台启动,编辑配置文件,

    vim命令不好使的,需要在线安装vim,自行百度搜方法

    vim redis.conf
    

    i开始插入编辑,这里修改为yes表示可以后台启动,esc退出编辑,:wq退出配置文件
    在这里插入图片描述
    回到bin目录,通过配置文件启动Redis服务端

    redis-server rconfig/redis.conf
    

    在这里插入图片描述
    启动客户端,连接服务

    redis-cli -p 6379
    

    在这里插入图片描述
    测试连接,测试存储
    在这里插入图片描述
    查看Redis进程命令

    ps -ef|grep redis
    

    在这里插入图片描述

    关闭

    在客户端连接中,使用命令shutdown关闭服务端,退出客户端即可

    shutdown
    exit
    

    在这里插入图片描述

    3.测试性能

    bin目录下的redis-benchmark,官方自带
    在这里插入图片描述
    在这里插入图片描述

    如,测试100个并发,100000个并发请求

    确保客户端连接服务,再开启一个新的连接窗口,进入到bin目录
    在这里插入图片描述
    在这里插入图片描述
    新窗口bin目录中执行测试命令

    redis-benchmark -h localhost -p 6379 -c 100 -n 100000
    

    在这里插入图片描述

    分析结果

    在这里插入图片描述
    写入测试,100000个请求,0.84秒完成,100个并发,每次写入3字节,保持1个连接
    在这里插入图片描述完成多少百分比的请求量所消耗的时间
    在这里插入图片描述
    计算结果,每秒处理119189.52个请求

    四、Redis 基础知识

    Redis默认16个数据库,默认使用的是第一个数据库,select *指令可以切换,配置文件中可以查看到,
    在这里插入图片描述

    基础指令

    以下指令在客户端连接状态下使用

    #切换数据库
    select num(0-15)
    #查看数据库大小
    dbsize
    #查看数据库所有的key
    keys *
    #清空当前数据库
    flushdb
    #清除所有数据库
    flushall
    

    Redis单线程

    Redis速度很快,基于内存操作,性能瓶颈不在CPU,而是内存和网络带宽,我们使用Redis用单线程即可

    多线程的CPU上下文切换比较耗时,所有对于Redis来说,单线程效率最高

    1.五大数据类型

    在这里插入图片描述
    提示:五大基本数据类型的操作使用需要熟练掌握,因为Java操作Redis的API都是基于Redis原生操作来定义的

    Redis-Key

    key泛指键,value泛指值,以实际为准

    查看所有key

    keys *
    

    判断某一个key是否存在

    exist key
    

    将数据移动到指定的数据库中

    move key num(数据库编号)
    

    设置某一条数据的过期时间,t的单位是秒

    expire key t
    

    查看某条数据的剩余生命时长,-2表示没了

    ttl key
    

    查看key的类型

    type key
    

    在这里插入图片描述

    String(字符串)

    赋值、取值、追加、长度

    set key value
    get key
    #向key对应的值后面追加value,如果当前key不存在,则相当于set key value
    append key value
    #查看key对应的值的长度
    strlen key
    

    在这里插入图片描述
    自增减、步长

    #给key的值+1,常用于计数,如网页浏览量
    incr key
    #将key的值-1
    decr key
    #增减时设置增减步长
    incrby key num
    decrby key num
    

    在这里插入图片描述
    截取

    #截取字符串,从num1-num2,0到-1表示查看全部字符串
    getrange key num1 num2
    

    在这里插入图片描述
    替换

    #替换字符串,从offset处开始
    setrange key offset value
    

    在这里插入图片描述
    判断,分布式锁中经常使用

    #如果key存在,则设置过期时间和key的值
    setex key seconds value
    #如果key不存在,则创建新的key和value,如果这个key存在,那么也不会设置成功,值也不会覆盖
    

    在这里插入图片描述
    批量处理,原子性

    #批量设置键值对k1-v1,k2-v2,k3-v3
    mset k1 v1 k2 2 k3 v3
    #批量获取值
    mget k1 k2 k3
    #批量设置,如果不存在,则设置k-v,只要其中有一个是存在的,则整体设置不成功,体现出原子性
    msetnx k1 v1 k2 v2 k3 v3
    

    在这里插入图片描述
    利用Redis批量设置,可以这样保存json格式的对象

    对象名:对象id:属性名 作为Redis的key,属性值 作为Redis的value
    在这里插入图片描述
    先get再set,不存在则返回nil并设置新的值,如果存在则返回旧的值并设置新的值

    #先get再set,返回的是get的值,值会被覆盖
    getset key value
    

    在这里插入图片描述

    • 总结:Redis String类型可以用作计数器、统计多单位数量、浏览量、对象缓存等

    List(列表)

    基本数据类型,列表

    Redis中,可以将list当做栈、队列、阻塞队列来使用,所有的list命令都用l开头

    很多命令都是左右对称可以使用的,我们主要以左侧举例,右侧镜像可以自行测试

    从头部(左侧)操作,赋值、取值,先进后出

    #向key列表赋值,按照入栈顺序
    listpush key element
    #从key列表取值,按照出栈顺序,0到-1表示所有
    lrange key start stop
    

    在这里插入图片描述
    从尾部(右侧)操作

    lrange list start stop
    

    在这里插入图片描述
    当做数组使用,使用下标

    #从数组key中取值,下标为index
    lindex key index
    

    在这里插入图片描述
    获取队列长度,

    在这里插入图片描述
    同一个队列中可以有重复的值

    移除队列中指定的值,如果这个值在队列中只有一个,移除个数就是1,如果有多个,可以指定移除多个这样的值

    #移除队列中的值,可以指定移除个数
    lrem list count element
    

    在这里插入图片描述
    截取指定区间的元素,通过下标,永久性修改了队列

    ltrim list start stop
    

    在这里插入图片描述
    组合命令,可以实现上述功能组合后的效果

    移除列表最后一个元素,或者说从右侧弹出一个元素,放到新的队列中,返回值就是弹出的那个元素

    #移除最后(右侧)的元素,放在新的队列中,左侧入栈
    rpoplpush source destination
    

    在这里插入图片描述
    判断队列是否存在,如果存在则,如果不存在则

    #判断是否存在队列
    exists key
    #如果队列存在,则将指定索引出的值进行更新,如果队列不存在,则没有效果,如果队列存在索引不存在,也报错
    lset list index element
    

    在这里插入图片描述
    在指定的元素(值,不是索引)处插入一个值,指定在前面插入,或在后面插入

    linsert key before|after pivot element
    

    在这里插入图片描述
    总结:

    • list实际上是一个链表,before node after,left,right 都可以插入值
    • 如果key不存在,则创建新链表
    • 如果key存在,则新增内容
    • 如果移除所有值,成为空链表,代表不存在,
    • 对于链表而言,在两端的值操作值效率最高,对中间的值操作效率略低
    • 消息排队,lpush rpop 表示左进右出,相当于消息队列,lpush lpop 表示左进左出,相当于栈

    Set(集合)

    因为无序,所以存储的值不可重复,命令以s开头

    添加集合元素,查看集合元素,判断某个元素是否存在

    #向名为key的集合添加元素member
    sadd key member
    #显示集合key中所有元素
    smembers key
    #判断集合key中是否存在元素member
    sismember key member
    

    在这里插入图片描述
    获取集合中元素个数

    #获取集合key中元素的个数
    scard key
    

    在这里插入图片描述
    移除集合中的指定元素

    #移除集合key中的指定元素member
    srem key member
    
    

    在这里插入图片描述
    随机取出指定个数的元素

    #随机从集合key中抽选指定个数的元素,没有指定个数就是默认1
    srandmember key [count]
    

    在这里插入图片描述
    删除元素

    #随机移除指定个数的元素,没有指定个数就是默认1
    spop key [count]
    

    在这里插入图片描述
    将集合中元素移动到另一个集合中

    #将集合key中的元素member移动到新的集合destination中
    smove key destination member
    

    在这里插入图片描述
    两个集合之间的运算,应用在比如,共同通讯好友,共同关注上

    #集合key1中减去集合key2与集合key1的交集,得到的剩余部分,如果key2没有指定得到的还是完整的key1
    sdiff key1 [key2]
    #集合key1与集合key2做交集,如果key2没有指定,则得到完整的key1
    sinter key1 [key2]
    #集合key1与集合key2做并集,如果key2没有指定,则得到完整的key1
    sunion key1 [key2]
    

    在这里插入图片描述

    Hash(HashMap集合、散列表、链表数组)

    与String类型类似,只不过每个元素变成了一个键值对

    Hash类型的命令以h开头

    赋值、取值、键不可重复,如果键相同则新值覆盖旧值

    #向集合key中存入键值对,键为field,值为value
    hset key field value
    #向集合key中存入多个键值对,field1-value1,field2-value2...
    hmset key field1 value1 field2 value2
    #根据键获取集合key中的多个值
    hmget key field1 field2
    #获取集合Key中的所有的键值对
    hgetall key
    

    在这里插入图片描述
    删除

    #根据键field删除集合key中的指定键值对
    hdel key field
    

    在这里插入图片描述
    长度

    #查看集合key的长度,元素个数
    hlen key
    

    在这里插入图片描述
    判断

    #根据键field判断集合key中是否存在该键值对
    hexists key field
    

    在这里插入图片描述
    只获取键,只获取值

    #只获取集合Key中的键
    hkeys key
    #只获取集合key中的值
    hvals key
    

    在这里插入图片描述
    自增长

    #设置集合key中的键值对中的值增长,并指定步长increment
    hincrby key field increment
    

    在这里插入图片描述
    判断元素如果存在,如果不存在

    #判断指定的键是否存在,如果不存在,则指定值,如果存在,则设置不成功
    hsetnx key field value
    

    存储对象,可变更数据,对象信息的保存,前面String类型也可以存对象,但是推荐使用Hash类型存储对象

    如,将对象名:对象id作为集合,属性名作为键,属性值作为值,可以同时给集合添加多个属性名、属性值

    对象名:id 只是一种规范,自己也可设置其他规范

    在这里插入图片描述

    Zset(有序集合)

    还是集合,在Set类型基础上,增加一个值score,值可以用来实现排序,命令以z开头

    赋值、

    #集合key添加元素member,元素的值为score
    zadd key score member
    #添加多个元素
    zadd key score1 member1 score2 member2
    #获取范围内的值,0到-1为全部的值,这里的范围编号和score不同,注意区分
    zrange key start stop
    #降序
    zrevrange key start stop
    

    在这里插入图片描述
    排序

    #根据score的大小,展示范围内的元素,-inf +inf 表示正无穷、负无穷,这个范围代表集合中的所有元素
    #m默认升序,withscores表示结果中包含scores值
    zrangebyscore key min max [witscores] [limit offset count]
    

    在这里插入图片描述
    移除,

    #移除集合key中的元素member
    zrem key member
    

    在这里插入图片描述

    统计

    #统计集合key总的元素个数
    zcard key
    #根据score的区间范围,统计元素个数,范围含头含尾
    zcount key min max
    

    在这里插入图片描述
    使用场景:需要set排序,如成绩表,薪资表,需要使用权重判断,排行榜等

    2.三种特殊数据类型

    geospatial

    定位、附近人、地理位置推算距离,

    http://www.redis.cn/commands/geoadd.html

    添加地理位置,南北极无法直接添加,通常会下载城市数据直接导入

    地理数据与实际地图吻合不可乱写,否则会计算错误

    • 有效的经度从-180度到180度。
    • 有效的纬度从-85.05112878度到85.05112878度。
    • 当坐标位置超出上述指定范围时,该命令将会返回一个错误。
    #向集合key中添加元素member的经度longitude、纬度latitude,可以一次添加多个位置
    geoadd key longitude latitude member
    #获取地理位置
    geopos key member
    

    在这里插入图片描述
    定位之间距离

    • m 表示单位为米。
    • km 表示单位为千米。
    • mi 表示单位为英里。
    • ft 表示单位为英尺
    #返回两个位置之间的距离
    geodist key member1 member2 [m|km|mi|ft]
    

    在这里插入图片描述
    获取半径内的元素

    #以给定经纬度longitude latitude为中心,找出半径radius内的元素,
    #可以选择是否返回元素到中心的距离、经纬度、限制查询的个数
    georadius key longitude latitude radius [m|km|mi|ft] [withdist] [withcoord] [count count]
    

    在这里插入图片描述
    在这里插入图片描述
    根据已有的元素,筛选范围内其他元素

    #给定一个元素,查找半径内的其他元素
    #可以选择是否返回查找的元素到中心的距离、经纬度、限制查询的个数
    georadiusbymember key member radius [m|km|mi|ft] [withdist] [withcoord] [count count]
    

    在这里插入图片描述
    返回元素的Hash,该命令将返回11个字符的Geohash字符串,不常用

    geohash key member1 member2
    

    在这里插入图片描述
    geo底层实现原理还是zset,所以可也以使用zset命令来操作地理位置,如查看全部元素、移除元素
    在这里插入图片描述

    Hyperloglogs

    基数:指一个集合中不重复的元素的个数

    Redis HyperLogLog 是用来做基数统计的算法

    用途:同一个人访问多次,可以算作一个访问人。

    传统的方式使用set集合,统计id,如果用户数据量很大就会比较麻烦,消耗资源,

    而Redis HyperLogLog 底层数据结构就是去重统计基数,占用内存非常小,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数,但是有0.81%的错误率,统计UV时可以忽略不计

    新增,统计

    #添加元素
    pfadd key member [member]
    #统计集合元素个数
    pfcount key
    #统计多个集合总的元素个数,也会自动去重
    pfcount key [key]
    

    合并,去重

    #合并多个集合,自动去重复元素,合并之后的集合替换掉destkey,sourcekey不变
    pfmerge destkey sourcekey [sourcekey]
    

    在这里插入图片描述

    Bitmaps

    在开发中,可能会遇到这种情况:需要统计用户的某些信息,如活跃或不活跃,登录或者不登录;又如需要记录用户一年的打卡情况,打卡了是1, 没有打卡是0,如果使用普通的 key/value存储,则要记录365条记录,如果用户量很大,需要的空间也会很大,所以 Redis 提供了 Bitmap 位图这中数据结构,Bitmap 就是通过操作二进制位来进行记录,即为 0 和 1;如果要记录 365 天的打卡情况,使用 Bitmap 表示的形式大概如下:0101000111000111…,这样有什么好处呢?当然就是节约内存了,365 天相当于 365 bit,又 1 字节 = 8 bit , 所以相当于使用 46 个字节即可

    通俗理解:

    • 当我们使用标记存储两种状态时,如果用一个变量存一个标记太浪费资源,现在将所有标记统一放在一条二进制数据上,每一位都用0或1对应一个标记,大大节约空间

    bitmap位图这种数据结构,使用二进制操作数据,

    新增、查看、统计

    #向key中存入数据,offet相当于下标,记录位置,从0开始,value只能是0和1,对应我们所需的状态
    setbit key offet value
    #查看指定位置的状态
    getbit key offet
    #统计二进制数据上1的个数,可以加范围,start 和 end 参数的设置和 GETRANGE 命令类似,都可以使用负数值:比如 -1 表示最后一个位,而 -2 表示倒数第二个位,以此类推
    bitcount key [start end]
    

    在这里插入图片描述

    3.Redis 事务

    事物的本质是一组命令一块执行,事务执行是,里面的命令按照顺序执行,一次性,顺序性,排他性

    Redis事务没有隔离级别的概念,因为所有命令在事务中没有直接执行,只有发起命令的时候才会执行exec

    Redis单条命令保证原子性,但Redis整个事务不保证原子性,原因见下面

    • 开启事务 multi
    • 命令入队
    • 执行事务 exec

    在这里插入图片描述

    • 放弃事务 discard

    在这里插入图片描述

    • 编译异常

    开启事务后,当出现代码异常,直至提交事务,这段期间的命令都不会执行,

    这种情况在Java中比较少见,因为Java操作Redis基本不会出现代码异常

    在这里插入图片描述

    • 运行异常

    开启事务后,执行命令,提交事务,如果运行期间出现问题,除了异常命令,其他命令正常执行

    所以说,Redis单条命名保证原子性,但整个Redis事务无法保证原子性
    在这里插入图片描述
    结论:

    • Redis单条命令保证原子性,因为Redis是单线程

    • Redis整个事务不保证原子性,因为开启事务时并没有执行命令,而是提交之后统一执行

    • 这与MySQL截然不同,注意区分

    4.Redis实现乐观锁

    悲观锁:一开始就认为会出现问题,所以什么时候都加锁

    乐观锁:认为不会出现问题,只有在需要的时候才会判断是否有必要加锁

    Redis使用监视命令 watch ,配合Redis事务一起使用

    正常执行成功时

    在这里插入图片描述
    模拟多线程问题

    第一个线程,监视数据,开启事务,但没有提交
    在这里插入图片描述
    打开新窗口,新客户端同样连接6379,模拟多线程操作,修改被监视的数据

    在这里插入图片描述
    然后第一个线程提交事务,发现被监视的元素被篡改,返回nil表示当前线程的整个事务提交失败

    数据按照另一个线程修改为准

    在这里插入图片描述
    Redis的这种监视事务的机制,相当于实现了Redis乐观锁

    当监视出现事务执行失败后,先取消监视unwatch key ,再去重新开始监视,开启事务

    五、Java 操作 Redis

    1.Jedis 官方原生API

    概念

    Redis官方推荐的Java开发工具,实际中可能不常用,但是与Redis原生API关联非常紧密,一定要熟练掌握

    项目、依赖

    在maven项目中导入Jedis依赖,maven镜像搜索坐标,地址:https://mvnrepository.com/

    导入jedis fastjson 依赖

    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>3.3.0</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.70</version>
    </dependency>
    

    连接、测试

    开启redis服务端,默认端口号6379
    在这里插入图片描述
    创建Jedis对象,添加连接参数,测试连接

    注意:如果是连接Linux中的Redis,先将redis.conf配置文件中的IP绑定注释掉,保护模式关闭,Jedis才能正产连接
    在这里插入图片描述
    在这里插入图片描述

    public class demo1 {
        public static void main(String[] args) {
            //创建对象,连接
            Jedis jedis = new Jedis("192.168.126.130", 6379);
            //连接测试
            System.out.println(jedis.ping());
        }
    }
    

    在这里插入图片描述

    常用API

    Jedis的所有命令都是基于Redis原生命令,在这里命令变成了方法

    Key操作
    public class demo2 {
        public static void main(String[] args) {
            Jedis jedis = new Jedis("192.168.126.130", 6379);
            System.out.println("清空当前数据库: "+jedis.flushDB());
            System.out.println("判断: "+jedis.exists("k1"));
            System.out.println("添加: "+jedis.set("k1", "v1"));
            System.out.println("添加: "+jedis.set("k2", "v2"));
            Set<String> keys = jedis.keys("*");
            System.out.println("列出所有key: "+keys);
            System.out.println("删除: "+jedis.del("k1"));
            System.out.println("判断: "+jedis.exists("k1"));
            System.out.println("查看键对应的值的类型: "+jedis.type("k2"));
            System.out.println("随机返回一个key: "+jedis.randomKey());
            System.out.println("重命名key: "+jedis.rename("k2", "k3"));
            System.out.println("获取元素: "+jedis.get("k3"));
            System.out.println("根据索引查询: "+jedis.select(0));
            System.out.println("清空当前数据库: "+jedis.flushDB());
            System.out.println("数据库key数量: "+jedis.dbSize());
            System.out.println("清空所有数据库的key: "+jedis.flushAll());
        }
    }
    

    在这里插入图片描述

    String类型
    public class demo3 {
        public static void main(String[] args) {
            Jedis jedis = new Jedis("192.168.126.130", 6379);
            System.out.println("添加元素");
            System.out.println(jedis.set("k1", "v1"));
            System.out.println(jedis.set("k2", "v2"));
            System.out.println(jedis.set("k3", "v3"));
            System.out.println("删除: "+jedis.del("k2"));
            System.out.println("取值: "+jedis.get("k2"));
            System.out.println("修改值: "+jedis.set("k1", "v11"));
            System.out.println("取值: "+jedis.get("k1"));
            System.out.println("追加值: "+jedis.append("k3", "end"));
            System.out.println("取值: "+jedis.get("k3"));
            System.out.println("添加多个元素: "+jedis.mset("k4","v4","k5","v5"));
            System.out.println("获取多个值: "+jedis.mget("k4","k5","k6"));
            System.out.println("删除多个元素: "+jedis.del("k4","k5"));
            System.out.println("获取多个值: "+jedis.mget("k3","k4","k5"));
            System.out.println("清空: "+jedis.flushDB());
            System.out.println("新增键值对,防止被覆盖");
            System.out.println("第一次赋值: "+jedis.setnx("k1", "v1"));
            System.out.println("第一次赋值: "+jedis.setnx("k2", "v2"));
            System.out.println("覆盖原值: "+jedis.setnx("k2", "v2new"));
            System.out.println("取值: "+jedis.get("k1"));
            System.out.println("取值: "+jedis.get("k2"));
            System.out.println("添加元素设置生命周期: "+jedis.setex("k3", 2, "v3"));
            System.out.println("取值: "+jedis.get("k3"));
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("过期后取值: "+jedis.get("k3"));
            System.out.println("获取原值,并更新: "+jedis.getSet("k2", "v2abc"));
            System.out.println("获取新的值: "+jedis.get("k2"));
            System.out.println("截取部分字符串: "+jedis.getrange("k2", 2, 4));
        }
    }
    

    在这里插入图片描述

    List集合
    public class demo4 {
        public static void main(String[] args) {
            Jedis jedis = new Jedis("192.168.126.130", 6379);
            System.out.println("清空数据: "+jedis.flushDB());
            System.out.println("新增元素");
            System.out.println(jedis.lpush("list", "a","b","c","d"));
            System.out.println(jedis.lpush("list", "d"));
            System.out.println(jedis.lpush("list", "e"));
            System.out.println(jedis.lpush("list", "f"));
            System.out.println("查看所有: "+jedis.lrange("list", 0, -1));
            System.out.println("查看区间数据: "+jedis.lrange("list", 0, 3));
            System.out.println("删除列表中指定的值,第二参数为删除的个数(有重复时),后进先出原则,相当于出栈");
            System.out.println("删除指定值的元素: "+jedis.lrem("list", 2, "d"));
            System.out.println("查看所有: "+jedis.lrange("list", 0, -1));
            System.out.println("删除区间以外的元素: "+jedis.ltrim("list", 0, 3));
            System.out.println("查看所有: "+jedis.lrange("list", 0, -1));
            System.out.println("左侧弹出: "+jedis.lpop("list"));
            System.out.println("查看所有: "+jedis.lrange("list", 0, -1));
            System.out.println("右侧新增: "+jedis.rpush("list", "g"));
            System.out.println("查看所有: "+jedis.lrange("list", 0, -1));
            System.out.println("右侧弹出: "+jedis.rpop("list"));
            System.out.println("查看所有: "+jedis.lrange("list", 0, -1));
            System.out.println("修改指定下标元素: "+jedis.lset("list", 1, "new"));
            System.out.println("查看所有: "+jedis.lrange("list", 0, -1));
            System.out.println("队列长度: "+jedis.llen("list"));
            System.out.println("获取指定下标元素: "+jedis.lindex("list", 2));
            System.out.println("新增元素: "+jedis.lpush("list1", "4","2","0","6","5","8"));
            System.out.println("查看所有: "+jedis.lrange("list1", 0, -1));
            System.out.println("排序: "+jedis.sort("list1"));
            System.out.println("查看所有: "+jedis.lrange("list1", 0, -1));
        }
    }
    

    在这里插入图片描述

    Set集合
    public class demo5 {
        public static void main(String[] args) {
            Jedis jedis = new Jedis("192.168.126.130",6379);
            jedis.flushDB();
            System.out.println("新增元素,不可重复");
            System.out.println(jedis.sadd("set1", "s0","s1","s2","s3","s4","s5","s7","s8"));
            System.out.println(jedis.sadd("set1", "s6"));
            System.out.println(jedis.sadd("set1", "s6"));
            System.out.println("列出所有元素: "+jedis.smembers("set1"));
            System.out.println("删除一个元素: "+jedis.srem("set1", "s0"));
            System.out.println("列出所有元素: "+jedis.smembers("set1"));
            System.out.println("删除元素: "+jedis.srem("set1", "s6","s7"));
            System.out.println("随机移除一个元素: "+jedis.spop("set1"));
            System.out.println("随机移除一个元素: "+jedis.spop("set1"));
            System.out.println("列出所有元素: "+jedis.smembers("set1"));
            System.out.println("查看元素个数: "+jedis.scard("set1"));
            System.out.println("判断是否存在某个元素: "+jedis.sismember("set1", "s3"));
            System.out.println("判断某个元素是否存在: "+jedis.sismember("set1", "s1"));
            System.out.println(jedis.sismember("set1", "s5"));
            System.out.println("================");
            System.out.println("新增: "+jedis.sadd("set2", "s0","s1","s2","s4","s5","s7","s8"));
            System.out.println("新增: "+jedis.sadd("set3", "s0","s1","s2","s4","s8"));
            System.out.println("set2中删除,并存到另一集合上 : "+jedis.smove("set2", "set3", "s1"));
            System.out.println("set2中删除,并存到另一集合上 : "+jedis.smove("set2", "set3", "s1"));
            System.out.println("删除元素并放在新集合中 :"+jedis.smove("set2", "set3", "s2"));
            System.out.println("列出所有元素: "+jedis.smembers("set2"));
            System.out.println("列出所有元素: "+jedis.smembers("set3"));
            System.out.println("交集: "+jedis.sinter("set2","set3"));
            System.out.println("并集: "+jedis.sunion("set2","set3"));
            System.out.println("差集: "+jedis.sdiff("set2","set3"));
            System.out.println("交集保存到集合中: "+jedis.sinterstore("set4", "set2","set3"));
            System.out.println("列出所有元素: "+jedis.smembers("set4"));
        }
    }
    

    在这里插入图片描述

    Hash

    public class demo6 {
        public static void main(String[] args) {
            Jedis jedis = new Jedis("192.168.126.130",6379);
            System.out.println("清空当前数据库");
            jedis.flushDB();
            Map<String,String> map = new HashMap();
            map.put("k1", "v1");
            map.put("k2", "v2");
            map.put("k3", "v3");
            map.put("k4", "v4");
            System.out.println("添加map集合");
            jedis.hmset("hash", map);
            System.out.println("添加元素: "+jedis.hset("hash", "k5","v5"));
            System.out.println("所有键值对: "+jedis.hgetAll("hash"));
            System.out.println("所有键: "+jedis.hkeys("hash"));
            System.out.println("所有值: "+jedis.hvals("hash"));
            System.out.println("给一个键保存整数,如果该键不存在则添加: "+jedis.hincrBy("hash", "k6", 6));
            System.out.println("所有键值对: "+jedis.hgetAll("hash"));
            System.out.println("添加或修改: "+jedis.hincrBy("hash", "k6", 60));
            System.out.println("所有键值对: "+jedis.hgetAll("hash"));
            System.out.println("删除一个或多个元素: "+jedis.hdel("hash", "k2"));
            System.out.println("所有元素: "+jedis.hgetAll("hash"));
            System.out.println("元素个数: "+jedis.hlen("hash"));
            System.out.println("判断元素是否存在: "+jedis.hexists("hash", "k2"));
            System.out.println("判断元素是否存在: "+jedis.hexists("hash", "k3"));
            System.out.println("根据键获取值: "+jedis.hmget("hash", "k3"));
            System.out.println("根据键获取值: "+jedis.hmget("hash", "k3","k4"));
        }
    }
    

    在这里插入图片描述

    Jedis 事务

    使用 multi 方法

    public class demo7 {
        public static void main(String[] args) {
            Jedis jedis = new Jedis("192.168.126.130",6379);
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("hello", "world");
            jsonObject.put("hi", "Java");
            jedis.flushDB();
            System.out.println("开启事务");
            Transaction multi = jedis.multi();
            String result = jsonObject.toJSONString();
            try {
                multi.set("k1", result);
                multi.set("k2", result);
                // 模拟异常
                // int i = 1/0;
                multi.exec();//提交事务
            } catch (Exception e) {
                multi.discard();//回滚事务
                e.printStackTrace();
            } finally {
                System.out.println(jedis.get("k1"));
                System.out.println(jedis.get("k2"));
                jedis.close();//关闭连接
            }
    
        }
    }
    

    成功
    在这里插入图片描述

    失败
    在这里插入图片描述
    如果使用watch方法,可以监控,在try catch中进行判断监控结果,根据情况抛异常

    2.SpringBoot 整合 Redis

    SpringData 用来操作各种数据库的整合

    项目准备

    新建springboot项目模块,勾选Redis依赖,其他根据需要

    在这里插入图片描述
    删除模块里的这些文件,暂时不需要
    在这里插入图片描述
    pom文件依赖

    java web, redis, fastjson

    注意:

    • 有些版本springboot连接redis使用jedis,有些使用lettuce
    • jedis采用直连,多个线程下不安全,使用连接池可以解决
    • 采用netty,实例可以在多个线程中共享,没有现成安全情况,像NIO模式,性能高一些
    • 使用连接池尽量选用lecttuce的,功能更强大,支持的类更多

    主配置文件

    Redis地址,端口号

    Redis测试

    先通过redistemplate对象调出所需的Redis操作对象,再进一步调用数据库方法API
    在这里插入图片描述
    在这里插入图片描述

    进入Redistemplate源码,可以看到所有的操作API

    序列化问题

    对象存入Redis之前需要序列化,否则可能出现中文乱码

    使用jdk自带序列化方案

    @Component
    @AllArgsConstructor
    @NoArgsConstructor
    @Data
    public class User implements Serializable {
        private Integer age;
        private String name;
    }
    

    测试存入Redis

    @Test
        void test1() throws Exception{
            User user = new User(18,"螺蛳粉");
            String jsonUser = new ObjectMapper().writeValueAsString(user);//对象转json字符串
            redisTemplate.opsForValue().set("user", jsonUser);//存入set集合
            Object user1 = redisTemplate.opsForValue().get("user");
            System.out.println(user1);
        }
    

    在这里插入图片描述
    自己配置序列化方案,准备配置类,自己配置bean对象,公司常用这个模板

    @Configuration
    public class RedisConfig {
        //手动修改redisTemplate,常见的固定模板
        @Bean
        public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
            // 连接
            RedisTemplate<String,Object> template = new RedisTemplate<>();
            template.setConnectionFactory(redisConnectionFactory);
            // 创建json的序列化配置
            Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
            ObjectMapper om = new ObjectMapper();
            om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
            jackson2JsonRedisSerializer.setObjectMapper(om);
            // 创建string的序配置
            StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
            // key采用string序列化配置
            template.setKeySerializer(stringRedisSerializer);
            // value采用jackson序列化配置
            template.setValueSerializer(jackson2JsonRedisSerializer);
            // hash key采用string序列化配置
            template.setHashKeySerializer(stringRedisSerializer);
            // hash value采用value序列化方案
            template.setHashValueSerializer(jackson2JsonRedisSerializer);
    
            template.afterPropertiesSet();
    
            return template;
        }
    }
    

    注入RedisTemplate时选择我们配置的Bean对象
    在这里插入图片描述
    运行测试,查看Redis数据库,
    在这里插入图片描述
    这样使用我们自己定义的Redis序列化配置,更好用,如果不配置默认使用jdk自带序列化方案,可能会出现中文乱码或者转义斜杠等问题

    自己写工具类

    • 实际生产中通常不直接使用RedisTemplate原生API,而是自己准备RedisUtils工具类,需要使用时直接注入属性,调用方法

    如,

    @Component
    public class RedisUtil {
    
        @Autowired
        private RedisTemplate<String, Object> redisTemplate;
    
        // 设置失效时间
        public boolean expire(String key, long time) {
            try {
                if (time>0) {
                    redisTemplate.expire(key, time, TimeUnit.SECONDS);
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        // 获取过期时间
        public long getExpire(String key) {
            return redisTemplate.getExpire(key, TimeUnit.SECONDS);
        }
        // 判断key是否存在
        public boolean hasKey(String key) {
            try {
                return redisTemplate.hasKey(key);
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        // 删除缓存
        public void del(String... key) {
            if (key != null && key.length>0) {
                if (key.length == 1) {
                    redisTemplate.delete(key[0]);
                } else {
                    redisTemplate.delete(String.valueOf(CollectionUtils.arrayToList(key)));
                }
            }
        }
        // 添加值
        public boolean set(String key, Object value) {
            try {
                redisTemplate.opsForValue().set(key, value);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        // 取值
        public Object get(String key, Object value) {
            return key == null ? null : redisTemplate.opsForValue().get(key);
        }
        // 存入普通缓存设置时间
        public boolean set(String key, Object value, long time) {
            try {
                if (time > 0) {
                    redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
                } else {
                    set(key, value);
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        // 设置递增
        public long incr(String key, long delta) {
            if (delta < 0) {
                throw new RuntimeException();
            }
            return redisTemplate.opsForValue().increment(key, delta);
        }
        // 设置递减
        public long decr(String key, long delta) {
            if (delta < 0) {
                throw new RuntimeException();
            }
            return redisTemplate.opsForValue().increment(key, -delta);
        }
        // Hash get
        public Object hget(String key, String item) {
            return redisTemplate.opsForHash().get(key, item);
        }
        // Hash get 获取对应的所有值
        public Map<Object,Object> hmget(String key) {
            return redisTemplate.opsForHash().entries(key);
        }
        // Hash set
        public boolean hmset(String key, Map<String, Object> map) {
            try {
                redisTemplate.opsForHash().putAll(key, map);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        // Hash set 并设置时间
        public boolean hmset(String key, Map<String, Object> map, long time) {
            try {
                redisTemplate.opsForHash().putAll(key, map);
                if (time > 0) {
                    expire(key, time);
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        // 存入数据,如果不存在则新增
        public boolean hset(String key, String item, Object value) {
            try {
                redisTemplate.opsForHash().put(key, item, value);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        // 存入数据,设置时间,如果不存在则新增
        public boolean hset(String key, String item, Object value, long time) {
            try {
                redisTemplate.opsForHash().put(key, item, value);
                if (time > 0) {
                    expire(key, time);
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        // 删除
        public void hdel(String key, Object... item){
            redisTemplate.opsForHash().delete(key, item);
        }
        // 判断是否有该值
        public boolean hHasKey(String key, String item) {
            return redisTemplate.opsForHash().hasKey(key, item);
        }
        // 存入值,如果不存在则新增,并设置时间
        public double hincr(String key, String item, double by) {
            return redisTemplate.opsForHash().increment(key, item, by);
        }
        // 设置递减
        public double hdecr(String key, String item, double by) {
            return redisTemplate.opsForHash().increment(key, item, -by);
        }
        // set 获取值
        public Set<Object> sGet(String key) {
            try {
                return redisTemplate.opsForSet().members(key);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
        // 查询是否存在
        public boolean sHasKey(String key, Object value) {
            try {
                return redisTemplate.opsForSet().isMember(key, value);
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        // 存入值
        public long sSet(String key, Object... values) {
            try {
                return redisTemplate.opsForSet().add(key, values);
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
        // 存入职,并设置时间
        public long sSet(String key, long time, Object... values) {
            try {
                long count = redisTemplate.opsForSet().add(key, values);
                if (time > 0) {
                    expire(key, time);
                }
                return count;
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
        // 获取值的数量
        public long sGetSetSize(String key) {
            try {
                return redisTemplate.opsForSet().size(key);
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
        // 移除值
        public long setRemove(String key, Object... values) {
            try {
                long count = redisTemplate.opsForSet().remove(key, values);
                return count;
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
        // list相关API
    }
    

    六、Redis 高级

    1.redis.conf 配置文件

    redis.conf 配置了Redis的详细参数,Redis启动需要使用配置文件

    在这里插入图片描述
    在这里插入图片描述
    进入 redis.conf 编辑状态,分析配置参数,vim redis.conf

    规定单位,单位书写对大小写不敏感
    在这里插入图片描述
    可以包含其他配置文件,将多个配置文件组合起来
    在这里插入图片描述
    NETWORK 网络配置,重点
    在这里插入图片描述
    绑定IP,只能使用指定的IP地址链接

    bind 127.0.0.1
    

    保护模式 yes/no,保护模式下只能本机连接

    protected-mode no
    

    连接端口号

    port 6379
    

    GENERAL 通用配置,
    在这里插入图片描述
    是否后台运行 yes/no

    daemonize yes
    

    管理守护进程的,一般不用动

    # supervised auto
    

    如果选择后台运行,则指定一个pid进程文件

    pidfile /var/run/redis_6379.pid
    

    日志级别,注释里有详解极少各个级别的用途

    loglevel notice
    

    生成日志的位置,空则使用默认

    logfile ""
    

    默认数据库数量 16

    databases 16
    

    是否显示启动logo

    always-show-logo no
    

    SNAPSHOTTING 快照
    在这里插入图片描述
    持久化策略,如,这里表示,

    save 900 1  #900秒内,至少有一个key发生修改,则进行持久化操作
    save 300 10  #300秒内,至少有10个key发生修改,则进行持久化
    save 60 10000  #60秒内,至少有10000个key发生修改,则进行持久化
    

    持久化出现错误,是否继续工作

    stop-writes-on-bgsave-error yes
    

    是否压缩rdb文件,会消耗一定cpu资源

    rdbcompression yes
    

    保存rdb文件时,是否进行校验

    rdbchecksum yes
    

    持久化文件存放目录,默认在当前文件目录

    dir ./
    

    REPLICATION 主从复制,后面详解
    在这里插入图片描述
    SECURITY 安全
    在这里插入图片描述

    设置密码,默认没有

    # requirepass foobared
    

    在Redis客户单,执行命令可以操作当前密码,

    如果设置密码后发现操作无权限,只需要在客户端验证密码即可 auth 密码
    在这里插入图片描述
    CLIENTS 客户端设置
    在这里插入图片描述
    最大客户端连接数,默认10000

    # maxclients 10000
    

    MEMORY MANAGEMENT 内存设置
    在这里插入图片描述
    Redis最大内存

    # maxmemory <bytes>
    

    内存达到上限的处理策略

    • volatile-lru:只对设置了过期时间的key进行LRU(默认值)
    • allkeys-lru : 删除lru算法的key
    • volatile-random:随机删除即将过期key
    • allkeys-random:随机删除
    • volatile-ttl : 删除即将过期的
    • noeviction : 永不过期,返回错误
    # maxmemory-policy noeviction
    

    APPEND ONLY MODE aof的持久化配置,后面详解
    在这里插入图片描述
    默认不开启aof,而是使用了 rdb

    appendonly no
    

    持久化文件名

    appendfilename "appendonly.aof"
    

    同步的频率,always每次修改都同步,比较消耗性能,everysec每秒一次,也可能有数据丢失,no不同步

    # appendfsync always
    appendfsync everysec
    # appendfsync no
    

    2.Redis 持久化

    RDB(Redis DataBase)

    在这里插入图片描述
    根据配置文件的持久化参数,定期将内存数据写入磁盘,即定期拍快照,恢复时将快照读到内存中

    Redis持久化使用一个单独的子进程fork,主进程不进行任何IO操作,确保了Redis的高性能,因此RDB模式效率更高,对于数据完整性不是很敏感的情况下大多使用RDB持久化策略。

    唯一的缺点就是在拍快照的间隙如果出现宕机,可能会有数据丢失

    rdb保存文件,默认名dump.rdb,与redis.conf在同一个目录

    生产环境下,我们会定期备份RDB文件
    在这里插入图片描述

    配置RDB持久化策略
    在这里插入图片描述
    在这里插入图片描述

    触发机制

    • save规则满足条件下,可以触发
    • 执行flushdb,可以触发
    • 退出redis,可以触发

    恢复数据

    • 只需将rdb文件放在redis启动目录下,redis启动时可以自动识别读取
    • 查看启动目录,在客户端执行config get dir

    在这里插入图片描述
    优点

    • 适合大规模数据恢复
    • 对数据完整性要求不高

    缺点

    • 持久化操作有时间间隔,如果redis以外宕机,最后一次操作修改的数据会丢失
    • fork进程运行时,会占用一定内存空间

    AOF(Append Only File)

    在这里插入图片描述

    直译,追加文件,将所有命令记录下来,恢复时将里面的命令全部执行一遍,所以Redis重启就会将该持久化记录里面的指令从到位执行一遍,比较麻烦

    配置文件
    在这里插入图片描述
    默认不开启,手动开启,需要设置

    appendonly yes
    

    默认声明文件名

    appendfilename "appendonly.aof"
    

    追加记录的频率

    # appendfsync always
    appendfsync everysec
    # appendfsync no
    

    其他配置一般使用默认,有需要可以查询

    修改配置文件,开启AOF,重启Redis服务端,即可生效,持久化文件默认保存在Redis启动目录下,

    执行一段Redis数据命令,查看文件目录,打开持久化文件
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述
    如果我们修改了这个日志文件,会影响数据的恢复,可以使用 自动检测修复aof文件,

    执行命令

    redis-check-aof --fix appendonly.aof
    

    在这里插入图片描述在这里插入图片描述
    重写规则,当aof文件超过配置参数的大小(这里是64mb),Redis就会开启一个新的进程,重写一个新的aof文件

    aof配置中默认文件无限追加,会导致文件越来越大

    no-appendfsync-on-rewrite no
    

    如果配置重写,可以指定文件多大触发重写新的aof

    auto-aof-rewrite-min-size 64mb
    

    优点

    • 每次修改都记录,确保文件完整性,注意需要将持久化策略改为always

    缺点

    • 持久化文件远大于RDB文件,修复速度也更慢
    • 开启AOF持久化策略下,Redis运行效率也会比RDB模式慢

    使用策略

    • 实际生产中会开启主从复制,rdb策略放在从机上,用来起到备份作用

    Redis 持久化总结

    1.RDB持久化方式能够在指定的时间间隔内对数据进行快照存储

    2.AOF持久化方式记录每次对服务器写的操作,当服务器重启时会重新执行这些命令来恢复原始数据,AOF命令以Redis协议追加保存每次写的操作到文件末尾,Redis还能对AOF文件进行后台重写,避免AOF文件体积过大

    3.如果只是使用Redis作为服务器运行的缓存,其实也可以不必使用持久化

    4.同时开启两种持久化方式

    • Redis重启会优先载入AOF文件来恢复原始数据,因为保存的数据更完整
    • RDB的数据不实时,但是也推荐开启。因RDB更适合用于备份数据库,AOF不断变化不好备份,RDB重启速度快,没有AOF潜在的bug

    5.性能建议

    • RDB文件通常作为后备,建议只在Slave上持久化RDB文件,而且只需要15分钟备份一次就够了,只保留save 900 1这条规则
    • 如果开启AOF,好处是在最糟糕的情况下,也只会丢失不超过2秒的数据,启动脚本也简单;但是产生了一个持续的IO,还有,在rewrite重写过程中,将新的数据写入新文件不可避免会造成阻塞,所以只要硬盘允许,尽量减小重写频率,默认是64mb,可以设置到5g以上,超过原大小100%触发重写也可以适当提升数值
    • 如果不使用AOF,只靠Master-Slave Replication 时间高可用性也可以,性能节省一大笔IO,减少rewrite带来的波动,代价是,如果主机从机同时宕机,会丢失十几分钟的数据,启动脚本也会比较主机从机中的RDB文件,选择比较新的

    3.Redis 发布订阅

    Redis发布订阅是一种消息通信模式,发送者发送消息,订阅者接收消息

    Redis客户端可以接收任意数量的频道

    要素:消息发送者、频道、消息接收者
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    命令描述
    在这里插入图片描述
    测试使用

    订阅频道,c1
    在这里插入图片描述
    开启一个新的窗口,连接服务端,发送消息
    在这里插入图片描述
    接收端会实时接收消息,并显示
    在这里插入图片描述
    总结

    • 发布者向频道发送消息
    • 接收者订阅频道,实施接收频道消息
    • redis-server底层维护一个字典,字典的键就是每个频道,字典的值是一个链表,链表中保存了所有订阅这个频道的客户端,当字典的一个键上发布消息后,值对应的所有客户端都会接收消息,最明显的用法就是具有实时性,如,即时聊天,群聊、关注等,
    • 进一步复杂的场景一般使用消息中间件 MQ

    4.Redis 主从复制

    概念

    默认情况下,每台Redis服务器都一个主节点

    通常主节点用来读操作,从机用来写操作,

    主从复制作用:

    • 数据冗余,实现了数据双击热备,是持久化之外的一种冗余方式
    • 故障恢复,主节点出现问题,从节节点可以提供服务,快速恢复
    • 负载均衡,多个从节点可以实现负载均衡,分担访问压力,提高Redis服务器的并发量
    • 高可用,主从复制是哨兵模式、Redis集群实施的前提

    结构上,Redis服务器可能发生故障,一台服务器不够用,且访问压力大;

    容量上,单台服务器内存容量有限,也不应当将所有内存用作Redis存储,通常,单台Redis最大使用内存不应超过20G

    配置

    只需要配置从机,主机默认就是,不需要单独配置

    以默认方式启动Redis服务,连接客户端,查看主从复制信息

    在这里插入图片描述
    可得知当前服务身份为主机,从机连接数为0,

    Redis集群的搭建至少需要三台服务器

    Redis的启动需要配置文件,因此我们准备多个不同的配置文件,分别根据各个配置文件启动,就可以得到多个Redis服务器

    准备四个连接窗口,一个观察,三个模拟三台Redis服务器
    在这里插入图片描述
    拷贝redis.conf配置文件,
    在这里插入图片描述

    分别进入各个配置文件,修改配置信息,这里以redis79.conf为例

    端口号
    在这里插入图片描述
    后台运行

    在这里插入图片描述
    pid
    在这里插入图片描述
    日志
    在这里插入图片描述
    rdb
    在这里插入图片描述
    保存退出,80,81根据各自端口号修改

    启动

    修改完配置文件,分别用6379,6380,6381配置文件启动Redis服务端
    在这里插入图片描述
    当前状态每台服务器都是主节点

    三个窗口分别连接6379,6380,6381客户端,查看连接信息
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    通常,从机不用改,只需配置从机,指定从机的主机即可

    给从机指定主机,从机客户端中使用命令,80,81都执行

    slaveof 127.0.0.1 6379
    

    在这里插入图片描述
    查看主机信息,多了两个从机
    在这里插入图片描述
    以上方式通过命令搭建主从,这是暂时的,实际生产中应该通过配置文件搭建主从,这是永久操作,开机即按照主从信息启动

    在这里插入图片描述
    注意事项

    • 主机可以写,从机不能写只能读,写了也会报错
    • 主从复制状态下,主机中的所有信息和数据,都会自动被从机保存

    测试

    • 主机宕机,主从关系没有变化,从机依旧无法写,主机恢复,主从关系不变,从机依旧无法写
    • 从机宕机,重启后变回主机,如果重新指定成刚才的从机,依旧可以获取主机所有数据,包括宕机期间主机的操作

    复制原理

    • slave启动成功连接到master后会发送一个sync同步命令
    • master接到命令,启动后台存盘进程,同时收集所有接收到的修改数据的命令,后台进程执行完毕后,master将传送整个数据文件给slave,并完成一次完全同步
    • 全量复制:slave在接收到数据库文件数据后,将其存盘并加载到内存中
    • 增量复制:master继续将新的所有手机到的修改命令一次传给slave,完成同步
    • 只要是重新连接master,一次完全同步(全量复制)就会自动执行,从机恢复所有数据

    主从链路

    79做主机,80做79的从机,81做80的从机

    此时的80依旧是从机,但是81依旧可以获取全部数据
    在这里插入图片描述
    这个模型下,如果79宕机,可以指定其中一个从机成为主机,进入一个从机客户端,执行命令,这个从机就会成为主机

    slaveof no one
    

    然后,其他从机再指定这个新的主机

    最初的主机重新启动,也失去之前那些从机了

    5.哨兵模式(重点)

    概念

    之前的主从切换,当主机宕机后,需要手动将一台服务器切换为主机,费事费力,服务器也会有一段时间不可用,

    Redis从2.8开始出现哨兵模式,Redis提供了哨兵命令,哨兵是一个独立的进程,会独立运行,哨兵通过发送命令,等待Redis服务器响应,从而监控多个Redis实例

    后台监控主机是否故障,如果宕机,将根据投票自动将从机切换为主机

    模型

    基本模型
    在这里插入图片描述
    为了确保哨兵也高可用,可以使用多哨兵模式
    在这里插入图片描述
    如果主机宕机,哨兵1先发现,系统不会马上进行切换,只是哨兵1主观认为,这个现象叫做主观下线。如果后续其他哨兵也发现主机不可用,且数量达到一定值时,哨兵之间就会进行投票,投票结果由一个哨兵发起,进行故障转移操作,切换成功后,通过发布订阅模式,通知各个哨兵把监控的从机切换为主机,者叫客观下线。

    配置

    当前主从状态,79主机,80,81从机

    在配置文件的目录中,新建哨兵配置文件sentinel.conf

    sentinel monitor不能变,sen1是名字自己取,127.0.0.1 6379 是监控的主机,1表示如果主机宕机,哨兵会进行投票

    这是最基本的配置,完成后,保存退出

    sentinel monitor sen1 127.0.0.1 6379 1
    

    在这里插入图片描述

    启动哨兵

    在这里插入图片描述
    在这里插入图片描述
    哨兵启动,根据配置文件,当前监控的6379成为主机,还有两个从机6380,6381

    测试

    打开新窗口,模拟主机6379宕机,进入6379客户端,shutdown
    在这里插入图片描述
    过一段时间,哨兵会通过心跳检测机制发现主机宕机,开始投票,推选出新的主机,这个例子中6381成了新主机

    在这里插入图片描述
    查看6381服务器信息,也验证了6381是新主机
    在这里插入图片描述
    这里投票的机制,我们暂且不讨论,但是一定会从生下的从机选出主机

    如果之前的主机6379重新连接,发现自己只能作为6381的从机,观察哨兵的投票记录我们可以看到,其实6379下线时,就已经沦为从机的位置了

    登录6379客户端,查看信息
    在这里插入图片描述
    优点:

    • 哨兵集群,基于主从复制模式,拥有其全部优点
    • 可以主从切换,实现故障转移
    • 哨兵模式就是主从模式的升级版,实现自动切换,更加健壮

    缺点

    • Redis不好实现在线扩容,集群容量一旦到达上限,在线扩容十分困难
    • 实现哨兵模式配置的过程其实比较麻烦,我们的举例只是最简单的
    • 真正的全部配置,有很多,如果有哨兵集群更加复杂,

    后续补充哨兵的详细配置

    6.Redis 缓存穿透、击穿、雪崩(面试重点)

    缓存穿透

    概念

    在这里插入图片描述

    用户查询数据,发现Redis内存数据库中没有,也就是缓存没有命中,请求就会想持久层数据库查询,也没有,查询失败;

    当用户量很大,或者恶意攻击,大量的请求进入到持久层数据库,会给数据库造成很大的压力,形成了缓存穿透

    解决方案

    布隆过滤器

    布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先校验,不符合则丢弃,避免对底层数据库直接查询
    在这里插入图片描述
    具体原理可以自行查询

    缓存空对象

    当存储层不命中后,即使返回一个空对象,也可以将其缓存起来,同时设置一个过期时间,在一定时间内也可以将请求拦截在缓存中
    在这里插入图片描述
    缺点

    • 存储空对象,以为这缓存中需要存储更多的键值对,浪费资源
    • 即使空对象缓存有生命周期,到了过期时间,仍然会出现缓存层和存储层数据不一致,对业务也有影响

    缓存击穿

    概念

    某些数据key非常热点,不停抗住大量并发,由于某种原因(宕机或过期),当这个key在失效的瞬间,持续大量的并发会将缓存击穿,直接请求数据库,导致数据库瞬间压力过大

    缓存穿透与缓存击穿区别:穿透是查不到,击穿是本来能查到,突然失效了,发生穿透

    解决方案

    设置热点数据永不过期

    也有缺点,最终还是需要定期处理

    加互斥锁

    分布式锁:使用分布式锁,确保每个key同时只有一个线程去查询后端服务,其他线程没有获取分布式锁的权限,只能等待,这种方式将高并发的压力转移到了分布式锁上面,对分布式锁的考验很大

    缓存雪崩

    概念

    在某一个时间段内,大量缓存集中失效(比如宕机、过期),导致访问查询都落到数据库上,对于数据库而言,产生了周期性的压力波峰,存储层调用量暴增,造成存储层挂掉

    在这里插入图片描述

    解决方案

    Redis高可用

    搭建Redis集群

    大厂比如阿里,在双十一会停掉部分业务,保障主业务

    限流降级

    在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量,如,一个key只允许一个线程查询和写缓存,其他线程等待

    数据预热

    正式部署之前,先把可能的数据访问一遍,可以使一大部分访问的数据加载到缓存中,提前加载不同的数据;

    设置不同的过期时间,让缓存失效的时间均匀分散

    本文转自 https://blog.csdn.net/weixin_47257749/article/details/114044856?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164580136416780261920323%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=164580136416780261920323&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allbaidu_landing_v2~default-1-114044856.pc_search_result_positive&utm_term=redis+2021&spm=1018.2226.3001.4187,如有侵权,请联系删除。


    部分资料来源于网络,版权属其原著者所有,只供学习交流之用。如有侵犯您的权益,请联系【公众号:码农印象】删除,可在下方评论,亦可邮件至ysluckly.520@qq.com。互动交流时请遵守宽容、换位思考的原则。

    ×

    喜欢就点赞,疼爱就打赏

    (function(){ var bp = document.createElement('script'); bp.src = '//push.zhanzhang.baidu.com/push.js'; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })();
    休闲小游戏