【Redis6】Redis6笔记

文章目录

  • 1、NoSQL数据库简介(非关系型数据库)
      • 1.1在web2.0时代出现了大量用户上网,出现了两个压力
        • 1.1.1解决CPU及内存压力和IO压力:
      • 1.2NoSQL简单概述
        • 1.2.1NoSQL数据库概述
        • 1.2.2NoSQL使用场景
        • 1.2.3NoSQL不适用场景
        • 1.2.4有哪些NoSQL数据库
      • 1.3行列式存储数据库(大数据时代)
  • 2、Redis安装
      • 2.1使用宝塔傻瓜式安装
      • 2.2Redis启动
      • 2.3Redis关闭
  • 3、Redis相关知识介绍
      • 3.1Redis是单线程+多路IO复用技术(可以实现多线程的效果)
      • 3.2Redis中常用5大基本数据类型
        • (1)Redis键(key)
        • (2)字符串String
        • (3)列表(List)
        • (4)集合(Set)
        • (5)哈希(hash)
        • (6)有序集合(ZSet)
  • 4、Redis配置文件讲解
      • 4.1
  • 5、Redis的发布和订阅
      • 5.1什么时发布和订阅?
      • 5.2Redis的发布与订阅。
      • 5.3发布与订阅的命令实现。
  • 6. Redis新数据类型
      • 6.1Bitmaps:新数据类型
      • 6.2HyperLogLog:新数据类型
      • 6.3Geospatial
  • 7、 jedis连接Redis
  • 8、Redis_jedis实例
      • 8.1做一个手机验证码功能:
      • 8.2Springboot整合Redis
  • 9、Redis_事务_锁机制_秒杀
      • 9.1事务:
        • (1)事务定义
        • (2)Multi、Exec、discard 基本命令
        • (3)事务错误处理
      • 9.2事务冲突:
        • (1)什么是事务冲突?
        • (2)悲观锁
        • (3)乐观锁
        • (4)WATCH key[key...] ——Redis的基本命令
        • (5)unwatch
      • 9.3Redis中事务的三个特性
      • 9.4商品秒杀案例
        • (1)三大问题:
        • (2)高并发情况下的超卖问题的解决
        • (3)解决高并场景下连接Redis超时问题
        • (4)解决用Redis的乐观锁带来的库存遗留问题(争抢问题)
        • (5)代码展示
            • (5.1)模拟高并发,调用脚本。
          • (5.2)LUA脚本的编写:
          • (5.3)Redis的连接池编写
  • 10、Redis持久化方式—RDB(Redis DataBase,默认开启)
      • 10.1 RDB是什么?
      • 10.2 备份是如何执行的?如何持久化的
      • 10.3 配置文件中关于持久化的配置的解读
      • 10.4 Redis被关闭之后的数据恢复
      • 10.5 关于RDB的总结:
        • (1)
        • (2)为什么RDB最后一次持久化可能丢失数据?
  • 11、Redis持久化方式—AOF(Append Only File,默认不开启)
      • 11.1 AOF是什么?有什么内容?
        • (1)是什么?
        • (2)内容:
      • 11.2 当RDB和AOF同时开启,那么Redis听谁的?
      • 11.3 执行流程
      • 11.4AOF文件被损坏,如何恢复?
      • 11.5 AOF同步频率
      • 11.6 优点和缺点
      • 11.7 官方推荐
  • 12、主从复制
      • 12.1 主从复制是什么?
        • (1)是什么:
        • (2)图示:
      • 12.2为什么要有主从复制?
      • 12.3怎么玩?:主从复制
        • (1)搭建1主2从
        • (2)测试1主2从
      • 12.4主从复制的复制原理
      • 12.5 常用的3招:
        • (1)1主2仆
        • (2)薪火相传
        • (3)反客为主(手动版)——(自动版:哨兵模式)
      • 12.6 哨兵模式(自动版:反客为主)
        • (1)是什么?
        • (2)怎么玩?
        • (3)复制延时
        • (4)选择从服务器的规则
        • (5)Java实现哨兵模式
  • 13、集群
      • 13.1 需要解决的问题?
        • (1)问题:
        • (2)怎么解决
        • (3)Redis集群的特点:
      • 13.2 如何搭建一个Redis集群(用端口号模拟)
        • (1)搭建三主三从:
        • (2) 让六个节点合成一个集群:
        • (4)测试
      • 13.3Redis 集群怎么分配的节点?
      • 13.4 插槽的概念:
      • 13.5 集群中的命令
      • 13.6 故障恢复
      • 13.7 Jedis连接集群:
      • 13.8 redis集群总结:
        • (1)好处:
        • (2)坏处
  • 14 、Redis应用问题解决
      • 14.1 缓存穿透
        • (1)什么是缓存穿透
        • (2)解决方案
      • 14.2 缓存击穿
        • (1)什么是缓存击穿
        • (2)解决方案
      • 14.3 缓存雪崩
        • (1)什么是缓存雪崩
        • (2)解决方案
      • 14.4 分布式锁
        • (1)什么是分布式锁?
        • (2)主流实现方案:
        • (3)Redis实现分布式锁。
        • (4)Java实现分布式锁。(上锁和去锁)
        • (5)分布式锁中的锁的误删问题。(给锁加UUID解决该问题)
        • (6)lua脚本的使用:保证原子性
        • (7)分布式锁总结
  • 15、Redis6 新特性
      • 15.1 ACL(了解)
        • (1)简介:
      • 15.2 IO多线程
        • (1)简介:
      • 15.3 Redis6新功能

1、NoSQL数据库简介(非关系型数据库)

1.1在web2.0时代出现了大量用户上网,出现了两个压力

【Redis6】Redis6笔记_第1张图片

1.1.1解决CPU及内存压力和IO压力:

【Redis6】Redis6笔记_第2张图片

1.2NoSQL简单概述

1.2.1NoSQL数据库概述

  1. 不支持SQL标准
  2. 不支持ACID。即事务的四大特性原子性、一致性、隔离性、持久性
  3. 远超SQL的性能

1.2.2NoSQL使用场景

  1. 对数据高并发的读写(电商里的秒杀场景)
  2. 海量数据的读写
  3. 对数据的高扩展性

1.2.3NoSQL不适用场景

  1. 需要事务的支持
  2. 基于SQL的结构化查询存储,处理复杂的关系。

1.2.4有哪些NoSQL数据库

  1. Memcache(缺点不支持数据的持久化,数据不能存储到硬盘中)
  2. Redis (支持数据的持久化、支持多种数据结构list\set\hash\zset)
  3. mongoDB(文档型数据库、存储的数据类型更加的多样化)

1.3行列式存储数据库(大数据时代)

【Redis6】Redis6笔记_第3张图片

2、Redis安装

2.1使用宝塔傻瓜式安装

2.2Redis启动

【Redis6】Redis6笔记_第4张图片

  1. ps -ef | grep redis //查看redis启动后的进程
  2. reids-server redis.conf //启动redis服务
  3. redis-cli //设置可以用客户端终端访问
  4. auth 123456 //输入密码
  5. 127.0.0.1:6379> ping //测试验证一下

2.3Redis关闭

  1. 第一:用宝塔关闭(简单)
  2. 第二:单实例关闭:redis-cli shutdown
  3. 第三:可以进入终端后关闭

在这里插入图片描述

  1. 第四:知道redis进程号后用kill命令杀掉redis进程

3、Redis相关知识介绍

3.1Redis是单线程+多路IO复用技术(可以实现多线程的效果)

  1. 如下图描述:单线程+多路IO复用
    【Redis6】Redis6笔记_第5张图片
  2. Redis读取是原子操作,因为在黄牛和售票厅之间是单线程的,不像多线程里面需要加锁

3.2Redis中常用5大基本数据类型

(1)Redis键(key)

  1. keys * :查看当前库中的所有key
  2. set :设置key-value
  3. exists key1 :判断key1是否存在——返回1:存在,2:不存在
  4. type key2 :查看key2所对应值的类型
  5. del key2 :删除key2
  6. unlink key2 :删除key2(它会先返回消息说已经删掉了key2,其实还没有删除,真正的删除是在以后慢慢删除,异步删除)
  7. expire key1 10 :设置过期时间,为key1设置10秒后过期
  8. ttl key1:查看key1是否已经过期——返回: -1:永不过期,-2:已经过期
  9. select :切换库(Redis一共有16个库,默认用0号库)
  10. dbsize :查看当前数据库的key的数量
  11. flushdb:清空当前库
  12. flushall :通杀全部库

(2)字符串String

  1. String类型是二进制安全的。意味着Redis的String可任意包含任何数据。比如jpg图片,或者序列化对象,通俗点,只要你的内容能用字符串表示,我们都能存到Redis去。
  2. String类型是Redis最基本的数据类型,一个Redis中字符串value最多可以达到512MB。
  3. 关于String的命令:
  1. set:添加key—value (设置相同key的数据会把前面的覆盖掉)
  2. get:取值
  3. append:将指定的value追加到原来值的末尾
    【Redis6】Redis6笔记_第6张图片
  4. strlen:获取指定值的长度;
  5. setnx:当库中没有对应的key时,设置key-value才能成功。(以防覆盖原来的);
  6. incr key:将key中存储的数值加1,如果为空,则新增值1;
  7. decr key:将key中存储的数值减1;
  8. incrby/decrby key 步长:给数值加减指定的数。
  9. mset :同时设置多个key-value(不保证原子性)
  10. mget :同时获取多个key分别对应的值(不保证原子性)
  11. msetnx:同时设置多个key-value,但是key不能和之前有重复,如果有一个重复则这次设置的key-value都失败;(体现原子性
  12. getrange key 起始位置 结束位置:获取key对应值的
  13. setrange key 起始位置 value : 用value覆盖掉从起始位置开始的值
    【Redis6】Redis6笔记_第7张图片
  14. setex key 过期时间 value 设置key-value时设置过期时间;
  15. getset key1 value:设置新的key1-value;同时输出原来的key1对应的value
    在这里插入图片描述
  1. String的数据结构
  1. String的内部结构为简单的动态字符串(Simple Dynamic String),是可以修改的字符串(Java的字符串是不可变的),其内部结构类似于Java中的ArrayList,可以伸缩。

【Redis6】Redis6笔记_第8张图片

(3)列表(List)

  1. List是简单的字符串列表,里面存储的都是一个一个的字符串;
  2. List底层实现为一个循环的双向链表,对两端的操作时它性能很高,当需要通过下标操作中间件时性能较差。
  3. 常用命令:
  1. lpush/rpush key value1 value2 value3:从左/右边边放入多个值;
    【Redis6】Redis6笔记_第9张图片
  2. lrange key1 start stop: 按照索引下标获取元素(从左到右)
    如:lrange key1 0 -1: 从左到右取出所有元素,0代表第一个位置,因为是链表List底层是双向链表,所以-1代表左后一个位置。
  3. lpop/rpop key count: 从key对应的链表的左边或者从右边取出count个值; 每取出一个值该链表少一个值;
  4. rpoplpush key1 key2 :把key1最右边的一个value放到key2最左边去。
    【Redis6】Redis6笔记_第10张图片
  5. lindex key index :按照索引下标获得元素;
  6. linsert key before value newvalue: 在旧的值的后面插入一个新的值;
  7. lrem key N value11:从左边到右边删除N个一样的value;
  8. lset key index value :将key对应列表中下标为index的值替换为value
  1. List列表数据结构:
  1. List会在数据量较少时寻找一块连续的空间,结构为zipList.也就是压缩列表。只有数据量比较多时才会改成多个zipList首尾相连的quickList(链表实现)
    【Redis6】Redis6笔记_第11张图片
  2. 使用普通链表,如果存放的只是int型数据,还需要在两边加上指针(prev,next),极大浪费空间。

(4)集合(Set)

  1. Redis Set和List的功能不同在于List可以有重复项,而Set没有重复项。Set可以自动排重
  2. Redis Set集合内提供了查看一个成员是否在Set中的接口,而List当中就没有这样的就功能。
  3. Redis Set是String类型的无序集合,底层就是一个值为null的hash表,所以添加删除查找复杂度都是O(1).
  4. 常用命令:

【Redis6】Redis6笔记_第12张图片

【Redis6】Redis6笔记_第13张图片

  1. Redis Set数据结构:
  1. Set数据结构是dict字典,字典就是哈希表实现;
  2. Java中HashSet就用HashMap实现,只不过所有的value指向同一个对象。
  3. Redis的set结构也是一样,它内部也使用hash结构,所有的value都指向同一个内部的值。

(5)哈希(hash)

  1. Redis hash的由来:Redis hash是一张String类型的field和value的映射表,hash特别适合存储对象,类似于Java中的Map.

【Redis6】Redis6笔记_第14张图片
【Redis6】Redis6笔记_第15张图片

【Redis6】Redis6笔记_第16张图片

  1. hash常用命令:
    【Redis6】Redis6笔记_第17张图片
  2. hash数据结构:

hash类型对应的数据类型是2种:zipList(压缩列表),hashTable(哈希表)。当field-value长度较短并且个数较少时,使用zipList,否则使用hashTable。

(6)有序集合(ZSet)

  1. 简介:Redis有序集合Zset与普通集合Set很类似,没有一个重复元素的字符串集合。
  2. 不同之处在于Zset的每个成员有关联一个评分,该评分(score),集合中的成员按照自己关联的评分高低排先后顺序。集合成员是唯一的,但是成员关联的评分可以相同。
  3. Zset的元素是有序的,而且没有重复项。
  4. 常用命令:
    【Redis6】Redis6笔记_第18张图片
    【Redis6】Redis6笔记_第19张图片
  5. 案例:利用Zset实现一个文章访问量排行榜。

【Redis6】Redis6笔记_第20张图片

  1. Zset数据结构:
  1. Zset数据结构很特殊,一方面,value内部等价于Java中的Map
    【Redis6】Redis6笔记_第21张图片
  2. 另一方面,类似于TreeSet,内部的元素会按照权重score来排名,还可以用过score范围来获取范围内的元素。
  3. Zset底层使用了两个数据结构
    (1)hash,hash的作用是的作用是关联元素的值与score,保证元素的唯一性,可任通过value找到相应的score值。
    (2)跳跃表(跳表),跳跃表的目的:在于给元素value排序,根据score来查找元素。
    【Redis6】Redis6笔记_第22张图片
    例如:【Redis6】Redis6笔记_第23张图片

4、Redis配置文件讲解

4.1

5、Redis的发布和订阅

5.1什么时发布和订阅?

  1. Redis发布和订阅是一种通信模式:发送者发送消息,订阅者接收消息
  2. Redis客户端可以订阅任意数量的频道。
    【Redis6】Redis6笔记_第24张图片

5.2Redis的发布与订阅。

【Redis6】Redis6笔记_第25张图片

5.3发布与订阅的命令实现。

  1. 打开一台客户端订阅频道1、2;
  1. SUBSCRIBE channel1 chanel2————订阅频道1 和频道2
  1. 打开另一台客户端分别向频道1和频道2发送“nihao”;
  1. publish channel1 nihao ————向频道1发送“nihao”
  2. publish channel2 nihao————向频道2发送“nihao”

6. Redis新数据类型

6.1Bitmaps:新数据类型

(1)Redis提供了Bitmaps这个数据类型可以实现对位操作

  1. Bitmaps本身不是数据类型,实际上它就是字符串(相当于字符类型和字符串的差别)。
  2. Bitmaps可以被想象成一个以bit(位)为单位的数组(这个数组中只能存“0”或者“1”,如果存默认数组里面全是0),数组的下标叫做偏移量(offset)
  3. 基本命令:
  1. setbit key offset value :往一个key对应的数组(以位为单位的数组)中的offset(偏移量)的这个位置存一个值,value值只能为“0”或“1”;
  2. getbit key offset:取出该offset(偏移量)对应的值
  3. bitcount key :统计key对应的Bitmaps中为“1”的个数。
  4. bitcount key start end bit :以bit为单位在start和end中有多少个“1”;
  5. bitcount key start end byte :以byte为单位在start和end中有多少个“1”;
    【Redis6】Redis6笔记_第26张图片
  6. bitop and(or/not/xor) destkey key[key…],对多个Bitmaps求,and(交集)、or(并集)、not(非)、xor(异或)操作,并将结果保存在目标Bitmaps(deskey)中。
  1. 举例1:一个班的有10位同学,学号分别为1 2 3 4 5 6 7 8 9 10,如果他们看了青年大学习就用”1”表示,如果没看就用“0”表示;把他们的学号作为offset(偏移量),value就是“0”或者“1”;

在这里插入图片描述

  1. 举例2:统计一个有1亿用户数量的网站的某一天的活跃用户数量,若1亿用户里有5000万用户活跃。

方案一:用集合Set存储活跃用户的id,每个人的id占64位,那么存储id总共花费:64bit x 5000万=32亿bit
方案二:用Bitmaps记录每一位用户是否活跃,那么花费:1bit x 1亿=1亿bit
结论:很明显当活跃用户多时,方案二很好;但是活跃用户少时,方案一好;

  1. Bitmaps用来表示一个团体的人做一件事,哪些人做了,哪些人没做很合适(或者像统计一个网站的活跃用户有哪些)。比如一个班看青年大学习,有一部分同学看了,一部分同学还没看,怎么记录看了的同学?就把看了的同学的学号作为偏移量或者数组下标,以1作为value存到Bitmaps中。

6.2HyperLogLog:新数据类型

  1. 主要用来解决基数运算。什么是基数?就是像一个集合中去了重复项之后的总的个数。
  2. 如:统计一个网站今天有多少个ip地址访问过。

解决像这样的问题有很多种解决方案:

  1. 方案一:如果数据存储在Mysql中,使用distinct count统计不重复的值;
  2. 方案二:使用Redis中的hash、set、bitmaps、这些结构都能处理
  3. 为什么不用这些结构:因为以上数据结构如果对于大的数据集,将要浪费大量空间,不切实际。
  4. 方案三:使用hyperLogLog来统计
  1. HyperLogLog是用来统计基数的算法。
  2. 优点:HyperLogLog计算基数所需的空间是固定大小的,并且很小。
  3. HyperLogLog算法,不像Zset、hash这些数据结构内部的算法,后者在统计量很大的数据集时占用空间也会很大,不划算。
  4. HyperLogLog只会根据输入的元素计算出基数,而不会像Zset、hash存储元素。
  5. 基本命令:
  1. pfadd key value[values]:把value添加,并不是存储起来,而是用来计算基数
  2. pfcount key :计算基数;

6.3Geospatial

Geo:Geographic:地理学的
spatial:空间的

  1. Redis3.2增加了地理信息类型的支持,该类型的元素就是二维坐标。经度和纬度。(经度范围:-180_180;维度范围:-85_85)
  2. 基本命令:
  1. 添加多个个地点
    【Redis6】Redis6笔记_第27张图片
  2. geopos key shanghai:取一个地点的经纬度:
    在这里插入图片描述
  3. geodist key member1 member2 [m米/km千米/ft英尺/mi英里]:得到两个地点之间的直线距离
  4. georadius key 经度 维度 半径 [m/km/ft/mi] 以一个经纬度为中心,得到周围半径以内的距离的地点有哪些。

7、 jedis连接Redis

8、Redis_jedis实例

8.1做一个手机验证码功能:

简介:

  1. 生成随机6位数验证码,把这个验证码存在Redis中,然后再把这个数发送个用户;
  2. 生成的验证码2分钟失效,并且一天内只能生成3次验证码。
  3. 用户输入收到的验证码,并且输入
  4. 把用户输入的验证码与Redis中调出的验证码做等价判断。
package com.yiheng.jedis;

import redis.clients.jedis.Jedis;

import java.util.Random;
import java.util.Scanner;

//案例需求:
//输入手机号之后,我们点击一下,能够获取随机6位的验证码。
public class PhoneCode {

    public static void main(String[] args) {
        //1、用户点击获取验证码后:向Redis保存生成的验证码,并且向用户发送该验证码;
        int i = verifyCode("18728089510");

        if(i==1){//达到了获取3次的界限
            System.out.println("程序结束!");
        }else {
            //2、用户填写验证码,该验证码再两分钟以内有效
            Scanner scanner = new Scanner(System.in);
            System.out.println("请输入验证码!!");
            String code = scanner.next();
            System.out.println(code);
            //3、调用验证码校验,
            verify("18728089510",code);
        }
    }

    //三、验证码校验:
    public static void verify(String phone,String code){

        //1.连接redis
        Jedis jedis = new Jedis("8.142.89.219", 6379);
        jedis.auth("123456");

        //2.设置改手机号获取的验证码在Redis中的key
        String Verify_code="Code"+phone+":code";
        //3.从redis中取该用户已获取到的验证码
        String s = jedis.get(Verify_code);
        jedis.close();
        //4.判断用户输入的验证码:code与从Redis中取出的验证码是否一致;
        if (s.equals(code)){
            System.out.println("验证成功");
        }else {
            System.out.println("验证失败");
        }
    }
    
    //二、让每个手机每天只能生成3次验证码,把生成的验证码保存到redis中,并且返回给用户该验证码
    public static int verifyCode(String phone){

        //1.连接redis
        Jedis jedis = new Jedis("8.142.89.219", 6379);
        jedis.auth("123456");

        //2.设置改手机号获取的验证码在Redis中的key
        String Verify_code="Code"+phone+":code";

        //3.设置该手机号获取验证码次数在Redis中的key
        String Verify_count="Count"+phone+":count";

        //4.取出redis中保存的向该手机发送验证码发送的次数;
        String count = jedis.get(Verify_count);

        //5.判断获取次数
        if(count==null){//第一次发送
            jedis.setex(Verify_count,24*60*60,"1");//设置过期时间
        }else if(Integer.valueOf(count)<=2){
            jedis.incr(Verify_count);
        }else if(Integer.valueOf(count)>2){

            //回去次数大于等于3次,直接回应给用户,并且关闭资源链接
            System.out.println("今日获取已经达到3次,24小时内无法再此发送");
            jedis.close();
            return 1;
        }

        //6.把随机生成的验证码保存到Redis中;
        String s = String.valueOf(getCode());
        jedis.setex(Verify_code,2*60,s);
        //7.从redis中获取,24小时内发送给用户验证码的  总次数
        String a =jedis.get(Verify_count);
        //8.把生成的验证码发送到用户的手机;
        System.out.println("今天第"+a+"次获取验证码;"+"验证码为:"+s+",两分钟内有效。");
        jedis.close();
        return 0;

    }


    //一、随机生成6位验证码
    public static StringBuilder  getCode(){
        Random random = new Random();
        StringBuilder code = new StringBuilder("");
        for (int i = 0; i < 6; i++) {
            int c = random.nextInt(10);
            code=code.append(c);
        }
        return code;
    }
}

8.2Springboot整合Redis

  1. pom.xml
    <dependencies>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-redisartifactId>
        dependency>
        <dependency>
            <groupId>org.apache.commonsgroupId>
            <artifactId>commons-pool2artifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>

    dependencies>
  1. application.yml
spring:
  redis:
    host: 8.142.89.219
    port: 6379
    database: 0 #数据库索引默认0;
    connect-timeout: 1800000 #连接超时时间
    lettuce:
      pool:
        max-active: 20 #最大连接数
        max-idle: 5 #连接池最大空闲连接
        max-wait: -1 #最大阻塞等待时间
        min-idle: 0 #连接池最小空闲连接
    password: 123456 #Redis连接密码

  1. RedisConfig.java
package com.yiheng.redis_config;


@EnableCaching       //配置缓存
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        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);
        template.setConnectionFactory(factory);
        //key序列化方式
        template.setKeySerializer(redisSerializer);
        //value序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //value hashmap序列化
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        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);
        // 配置序列化(解决乱码的问题),过期时间600秒
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(600))  //设置数据过期时间600秒
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                .disableCachingNullValues();
        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }

}
  1. Controller.java
@RestController
public class RedisController {
    @Autowired
    RedisTemplate redisTemplate;
    @RequestMapping("/RedisTest1")
    public String test1(){

        //设置key-String
        redisTemplate.opsForValue().set("k1","yiheng");
        //取key对应的String
        return (String)redisTemplate.opsForValue().get("k1");
    }

}

9、Redis_事务_锁机制_秒杀

9.1事务:

(1)事务定义

  1. Redis事务是一个单独的隔离操作:事务中所有的命令都会被序列化、按顺序的执行。在事务的执行过程中不会被客户端发送来的其他命令打断。

(2)Multi、Exec、discard 基本命令

  1. Multi:可以用来开启事务,开启事务后,我们可以输入命令,命令会进入队列准备执行。

  2. Exec:执行操作,命令会依次执行

【Redis6】Redis6笔记_第28张图片

  1. discard:在组队(进入队列的过程)中,可以使用discard命令放弃组队。

(3)事务错误处理

  1. 命令入对阶段,如果需要被执行的命令出现了被报告出来的任务,则整个队列中所有命令都会被取消。
  2. 命令执行阶段,如果队列中有命令发生错误,则不会影响其他命令执行。
  3. 结论:这种就很像Java中的编译时错误,和运行时错误。编译错误整个程序都没法运行,运行时错误则不会影响其他命令的执行。

9.2事务冲突:

(1)什么是事务冲突?

  1. 举个例子,1万块钱帐户,3个人同时得知账户还有一万,第一个人买8000块钱东西,第二个人买5000块钱东西,第3个人买2000块钱的东西,那么账户余额:1万-1万5=-5000。
  2. 为了解决上述问题,悲观锁乐观锁诞生了

(2)悲观锁

  1. 悲观锁,顾名思义就是很悲观,每次操作数据时都怕别人修改这个数据,所以你每次操作这个数据你都上锁,当你持有锁时,别人进入阻塞,当你操作完时,你就释放锁,下一个人再持有这个锁。传统关系型数据库中的行锁表锁读写锁写锁,等都用这种机制。就像Java里面的同步机制,效率低下

(3)乐观锁

  1. 乐观锁,顾名思义就是很乐观,认为当前我修改数据时别人不会修改该数据,所以不会上锁,也是因为没有上锁,所以所有人都可以同时拿到该数据,但是当我拿到数据将要修改该数据时,我会判断一下,我拿到的数据是否是最新的数据,这个可以利用版本号机制实现。乐观锁可以用在多读的应用类型(读:从数据库中拿数据,写:把数据保存在数据库中),这样可以提高吞吐量。Redis就是利用check-and-set机制实现事务
  2. 乐观锁图解(版本号是实现):

【Redis6】Redis6笔记_第29张图片

(4)WATCH key[key…] ——Redis的基本命令

  1. 作用:再执行Multi之前先执行 watch key1[key…],可以用来监视一个或者多个key,如果,在输入Exec命令之前,如果你操作的key发生改变过(被其他命令改变过),则事务执行会被打断。

(5)unwatch

  1. 作用:在使用watch key1[key…]后,可使用unwatch取消对key的监视。

9.3Redis中事务的三个特性

  1. 单独的隔离操作:事务中的所有操作都会被序列化,按顺序的执行,在执行事务中,不会被客户端发送来其他的命令请求打断。
  2. 没有个隔离级别的概念,使用multi命令开启事务后,然后添加命令,如果最后没有提交该事务,则事务中的命令都不会执行。
  3. 不保证原子性:如果过再事务执行过程中,发现有执行失败的命令,则该事务其他命令继续执行。不像Mysql数据库中原子性,一个败全败。

9.4商品秒杀案例

(1)三大问题:

  1. 秒杀时超卖问题:在秒杀结束后,商品库存为负数
  2. 连接Redis超时问题:再高并发场景下,jedis连接Redis超时
  3. 库存遗留问题:用Redis的乐观锁机制解决了第1问后,出现了库存有遗留的情况。争抢问题

(2)高并发情况下的超卖问题的解决

  1. 使用Redis提供的乐观锁机制。
  2. 具体过程为:1、watch命令监视库存的key,2、使用mutli开启事务,把库存-1和添加用户id的两个命令添加到事务队列中。3、Exec命令来执行事务。
  3. 超卖问题解决!

(3)解决高并场景下连接Redis超时问题

  1. 用连接池解决!

(4)解决用Redis的乐观锁带来的库存遗留问题(争抢问题)

  1. 原因:在高并发场景下,如果有500个商品,所以人都看到了500个商品,那么所有人中一瞬间里只有一个人能抢到1件商品,这个人抢了一件衣服后(抢了一件衣服以后,库存因为乐观锁,导致库存的版本号改变了),那么其他所有人在这一瞬间都秒杀失败了,这些人的发的请求都失败了。只有点击发送下一次请求,下一次请求和这一次一样,其中只有一个人成功,其他人都失败。所以乐观锁会导致库存遗留问题
  2. 解决方法:使用LUA:洛脚本语言。我们把在Redis执行的步骤写为一个脚本,然后一次提交给Redis执行,减少反复连接Redis的次数。

【Redis6】Redis6笔记_第30张图片

(5)代码展示

(5.1)模拟高并发,调用脚本。
public class TestSecondKill implements Runnable {

    public static void main(String[] args) {
        TestSecondKill testSecondKill = new TestSecondKill();
        for (int i = 0; i < 200; i++) {//生成200个线程,模拟高并发。
            new Thread(testSecondKill).start();
        }

    }

    @Override
    public void run() {
        //随机生成用户id
        String userid=String.valueOf(new Random().nextInt(5000));
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
       // boolean result = secKill(userid,"0101");

        try {
            SecKill_redisByScript.doSecKill(userid,"0101");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
  
}
(5.2)LUA脚本的编写:
public class SecKill_redisByScript {

    private static final  org.slf4j.Logger logger =LoggerFactory.getLogger(SecKill_redisByScript.class) ;

//主方法用来测试LUA脚本对不对
    public static void main(String[] args) {
        JedisPool jedispool =  JedisPoolUtil.getJedisPoolInstance();

        Jedis jedis=jedispool.getResource();
        System.out.println(jedis.ping());

        Set<HostAndPort> set=new HashSet<HostAndPort>();

        //	doSecKill("201","sk:0101");
    }

    //LUA:洛脚本语言:我们把在Redis执行的步骤写为一个脚本,然后一次提交给Redis执行,减少反复连接Redis的次数。提升性能。
    static String secKillScript ="local userid=KEYS[1];\r\n" +
            "local prodid=KEYS[2];\r\n" +
            "local qtkey='sk:'..prodid..\":qt\";\r\n" +
            "local usersKey='sk:'..prodid..\":usr\";\r\n" +
            "local userExists=redis.call(\"sismember\",usersKey,userid);\r\n" +
            "if tonumber(userExists)==1 then \r\n" +
            "   return 2;\r\n" +
            "end\r\n" +
            "local num= redis.call(\"get\" ,qtkey);\r\n" +
            "if tonumber(num)<=0 then \r\n" +
            "   return 0;\r\n" +
            "else \r\n" +
            "   redis.call(\"decr\",qtkey);\r\n" +
            "   redis.call(\"sadd\",usersKey,userid);\r\n" +
            "end\r\n" +
            "return 1" ;

    static String secKillScript2 =
            "local userExists=redis.call(\"sismember\",\"{sk}:0101:usr\",userid);\r\n" +
                    " return 1";

    public static boolean doSecKill(String uid,String prodid) throws IOException {

        JedisPool jedispool =  JedisPoolUtil.getJedisPoolInstance();
        Jedis jedis=jedispool.getResource();

        //String sha1=  .secKillScript;
        String sha1=  jedis.scriptLoad(secKillScript);//加载脚本

        Object result= jedis.evalsha(sha1, 2, uid,prodid);

        String reString=String.valueOf(result);
        if ("0".equals( reString )  ) {
            System.err.println("已抢空!!");
        }else if("1".equals( reString )  )  {
            System.out.println("抢购成功!!!!");
        }else if("2".equals( reString )  )  {
            System.err.println("该用户已抢过!!");
        }else{
            System.err.println("抢购异常!!");
        }
        jedis.close();
        return true;
    }
}
(5.3)Redis的连接池编写
package com.yiheng.controller;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * Jedis连接池工具类,解决并发问题下的连接偶尔出现的Redis超时问题。
 */
public class JedisPoolUtil {

    private static volatile JedisPool jedisPool=null;
    private JedisPoolUtil(){
    }

    public static JedisPool getJedisPoolInstance(){

        if(null==jedisPool){
            synchronized (JedisPoolUtil.class){
                if(null==jedisPool){
                    JedisPoolConfig poolConfig = new JedisPoolConfig();
                    //MaxTotal:控制一个pool可分配多少个jedis实例,通过pool.getResource()来获取;如果赋值为-1,则表示不限制;如果pool已经分配了MaxTotal个jedis实例,则此时pool的状态为exhausted。
                    //maxIdle:控制一个pool最多有多少个状态为idle(空闲)的jedis实例;
                    //MaxWaitMillis:表示当borrow(借)一个jedis实例时,最大的等待毫秒数,如果超过等待时间,则直接抛JedisConnectionException;
                    poolConfig.setMaxTotal(200);//最大连接数
                    poolConfig.setMaxIdle(32);
                    poolConfig.setMaxWaitMillis(100*1000);//等待时间
                    poolConfig.setBlockWhenExhausted(true);//连接耗尽是否进行等待
                    poolConfig.setTestOnBorrow(true);//从连接池获取连接,先测试一下连接是否是连通状态。
                    jedisPool = new JedisPool(poolConfig, "8.142.89.219", 6379, 60000, "123456");//60000超时时间,超过这个时间就不能提供服务了。
                }
            }
        }
        return jedisPool;
    }

    //释放Jedis资源
    public static void release(JedisPool jedisPool, Jedis jedis){
        if(null!=jedis){
            jedisPool.returnResource(jedis);
        }
    }
}

10、Redis持久化方式—RDB(Redis DataBase,默认开启)

10.1 RDB是什么?

  1. 就是每隔指定的时间,就把Redis中的那一个快照的数据保存在磁盘中。

10.2 备份是如何执行的?如何持久化的

【Redis6】Redis6笔记_第31张图片

  1. fork出的子进程会利用 写时复制 进行持久化
    【Redis6】Redis6笔记_第32张图片

Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等) 数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程

10.3 配置文件中关于持久化的配置的解读

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

10.4 Redis被关闭之后的数据恢复

是根据保存在磁盘上面的dump.rdb文件,来恢复关闭Redis关之前的状态。

10.5 关于RDB的总结:

(1)

【Redis6】Redis6笔记_第33张图片

(2)为什么RDB最后一次持久化可能丢失数据?

如果在最后一次进行持久化操作中,服务器挂掉了,那么Redis就会终止。那么导致最后一次持久化时的数据丢失。

11、Redis持久化方式—AOF(Append Only File,默认不开启)

11.1 AOF是什么?有什么内容?

(1)是什么?

【Redis6】Redis6笔记_第34张图片

(2)内容:

  1. 一些写操作的命令
  2. Redis中保存的数据

11.2 当RDB和AOF同时开启,那么Redis听谁的?

AOF的,AOF>RDB优先级。再关闭Redis,再重启Redis,恢复Redis关闭前的状态中的key会根据AOF文件来恢复,而不是根据RDB文件恢复。

11.3 执行流程

【Redis6】Redis6笔记_第35张图片

11.4AOF文件被损坏,如何恢复?

【Redis6】Redis6笔记_第36张图片

11.5 AOF同步频率

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

11.6 优点和缺点

  1. 优点:

1数据不易丢失

  1. 缺点:
  1. 空间开销大:因为AOF文件保存了“命令和Redis数据库中的数据,所以空间开销比RDB开销大,RDB过程产生的dump.rdb文件之保存了数据,没有命令
  2. 会影响系统吞吐量,AOF在同步操作命令时,会有额外的性能开销。

11.7 官方推荐

【Redis6】Redis6笔记_第37张图片

12、主从复制

12.1 主从复制是什么?

(1)是什么:

主机数据更新后根据配置和策略, 自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主

(2)图示:

【Redis6】Redis6笔记_第38张图片

12.2为什么要有主从复制?

  1. 读写分离,性能提升
  2. 容灾快速回复:当你如果有一台从服务器挂了,其他服务器继续工作。

12.3怎么玩?:主从复制

(1)搭建1主2从

1、创建/myredis目录
2、把redis.conf配置文件复制3份到myredis文件夹(1主2从)
3、再把redis.conf里面的配置修改:
——①修改pidfile 路径为:pidfile /www/server/redis/redis_6379.pid
——②修改 port 端口号为:6379
——③修改 dbfilename为dump6379.rdb
——④方式一配主从服务器:修该 replicaof IP地址 端口号 :用来表明该服务器作为谁的从服务器
——⑤方式一配主从服务器:修改 masterauth 密码 :主机密码。
——④如果开启了AOF持久化,则需要改动aof文件的路径名。
4、启动3个Redis服务。redis-server redis6379.conf
——注意:安装redis时一定要再Redis安装目录下用make install,不然会报错:redis-server: command not found
5、查看进程: ps -ef | grep redis
在这里插入图片描述
6、使用redis-cli -p 端口号 :开启三个客户端连接分别连接3个redis服务。
7、①输入密码②使用info replication查看主机信息
8、方式二配主从服务器:使用 slaveof ip port 把该机器变成这个IP的这个端口的从机

(2)测试1主2从

  1. 主服务器写操作:set k1 v1

  2. 主服务器读操作:get k1——v1

  3. 从服务器读操作:get k1—— v1

  4. 从服务器写操作:报错:你只能进行读操作

  5. 结论:主服务器既能写也能读,而从服务器只能读

12.4主从复制的复制原理

  1. 第一步:当从服务器连接到主服务器之后,从服务器主动向主服务器发送一个同步消息,
  2. 第二步:主服务器收到同步消息后,会把自己的数据持久化到RDB文件中,然后把RDB文件发送个从服务器。从服务器根据rdb文件进行读取。(全量复制)
  3. 当主服务器每次进行写操作时,主服务器主动把数据同步到从服务器。(增量复制)

12.5 常用的3招:

(1)1主2仆

  1. 如果从服务器挂掉,那么从服务器重启以后,它会把主服务器里面的所有数据复制一份。
  2. 如果主服务器挂掉,那么从服务器还是从服务器,等主服务器重启之后,还是2仆的主服务器。挂掉之前的所有数据还是有。

(2)薪火相传

  1. 图示:
    【Redis6】Redis6笔记_第39张图片
  2. 怎么配置:只需要再一个服务器的redis配置文件修改
  1. 方法一:服务器的redis配置文件修改 replicaof IP地址 端口号 指定父从服务器
  2. 方法二:在服务器运行中使用命令: slaveof ip port 指定自己的父从服务器
  1. 总结:
  1. 优点:再从服务器的数量比较多时,有利于给主服务器分摊压力
  2. 缺点:当如果有中间的一台从服务器挂掉了,那么该从服务器的子从服务器,无法进行同步主服务器的数据了(中间的服务器挂了)

(3)反客为主(手动版)——(自动版:哨兵模式)

  1. 当主机挂掉时,我们可以让一台从机变成主机
  2. 命令:slaveof no one

12.6 哨兵模式(自动版:反客为主)

(1)是什么?

  1. 自动版反客为主,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库

【Redis6】Redis6笔记_第40张图片

(2)怎么玩?

  1. 创建1主服务器,2从服务器
  2. 再myredis下创建sentinel.conf文件。
    ————内容①:sentinel monitor mymaster 127.0.0.1 6379 1。监控127.0.0.1 6379的主机,并为主机取别名为:mymaster ——1:至少有1个哨兵同意迁移。
    ————内容②:sentinel auth-pass mymaster 123456 -如果你主服务器密码,你不设置的话不能动态切换主从机
  3. 打开一个窗口启动一个哨兵(命令):redis-sentinel sentinel.conf
  4. 哨兵的端口和进程号
    在这里插入图片描述
  5. 把主服务器的redis进程关闭。
  6. 哨兵会再从服务器里面选择一个当主服务器,如果启动原来的主服务器,那么原来挂掉的主服务器将变为从服务器。

(3)复制延时

  1. 由于所有的写操作都是先在Master上操作然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。

(4)选择从服务器的规则

【Redis6】Redis6笔记_第41张图片

  1. 优先级——配置文件里
    在这里插入图片描述
  2. 当优先级相同,那么就选择偏移量最大的。偏移量是指和主服务器数据相差最小的。可以省复制延时的时间
  3. 每个redis实例在启动后都会生成一个随机的runid,那么就选择runid最小的。

(5)Java实现哨兵模式

13、集群

13.1 需要解决的问题?

(1)问题:

  1. 容量不够,redis如何进行扩容?
  2. 并发写操作, redis如何分摊?
  3. 另外,主从模式,薪火相传模式,主机宕机,导致ip地址发生变化,应用程序中配置需要修改对应的主机地址、端口等信息。

(2)怎么解决

解决方案1:redis3之前使用代理主机。缺点:耗用的服务器数量太多了。在实际的开发中,一个redis服务器肯定对应一台Linux系统。
在这里插入图片描述
解决方案二:使用无中心化集群配置
【Redis6】Redis6笔记_第42张图片

(3)Redis集群的特点:

  1. 实现了对Redis的水平扩容,启动N个Redis节点,那么每个节点存储总数据的1/N.
  2. 如果集群中某一个节点出现问题。那么其他节点可以继续提供服务。

13.2 如何搭建一个Redis集群(用端口号模拟)

(1)搭建三主三从:

  1. 创建/myredis目录
  2. 把redis.conf文件名改为redis6379.conf
  3. 再把redis6379.conf里面的配置修改:
    ——①修改pidfile 路径为:pidfile /www/server/redis/redis_6379.pid
    ——②修改 port 端口号为:6379
    ——③修改 dbfilename为dump6379.rdb
    ——④关闭aof持久化
    ——⑤cluster-enabled yes 打开集群模式
    ——⑥cluster-config-file nodes-6379.conf 设定节点配置文件名
    ——⑦cluster-node-timeout 15000 设定节点失联时间,超过该时间(毫秒),集群自动进行主从切换。
  4. 把redis6379.conf文件复制五份。
  5. 效果为:3主3从
    【Redis6】Redis6笔记_第43张图片
  6. 启动6个redis服务

(2) 让六个节点合成一个集群:

  1. 重要:再阿里云和宝塔把12个端口放行(Redis服务端口+每个服务对应的集群总线端口)

  2. 进入www/server/redis/src目录:

  3. 配置集群的命令:redis-cli --cluster create --cluster-replicas 1 8.142.89.219:6379 8.142.89.219:6380 8.142.89.219:6381 8.142.89.219:6389 8.142.89.219:6390 8.142.89.219:6391
    —— ①–cluster-replicas 1 ——表示1台主机1个从服务器。
    ——②写实际IP地址和端口号。不能用127.0.0.1。

  4. 成功:
    在这里插入图片描述

(4)测试

  1. 使用命令:redis-cli -c -p 6379 连接到集群,集群的任意一台主机都是集群的入口
  2. 使用命令:cluster nodes 查看所有节点
    在这里插入图片描述

13.3Redis 集群怎么分配的节点?

  1. 一个集群至少要有3台主服务器。
  2. –cluster-replicas 1:配置集群的命令中的一句话:意思是:希望为集群中的每个主节点创建一个从节点。
  3. 集群配置的原则:尽量保证所有主机和所有从机都在不同点iP上。

13.4 插槽的概念:

  1. 一个Redis数据库都有16384个槽(hash slot),数据库中的每个键都属于这些槽中的一个。可以几个键存放在一个槽内。就像hashcode一样。
  2. 集群中的每个主节点负责处理一部分插槽。可以使用命令:cluster nodes 查看所有节点可以看出。比如:6379处理0~5640节点。

13.5 集群中的命令

  1. set k1 v1
  2. mset k1{user} v1 k2{user} v2 k3{user} v3:一次插入3个key-value,则是根据user来计算槽的位置的。原理是把k1 k2 k3 归为一个组。
  3. cluster keyslot k1:查询k1属于哪个槽。
  4. cluster countkeysinslot 5474 :查看5474这个槽有多少个键。必须要在对应的IP端口查看才能效果,5474属于8080端口管的。

13.6 故障恢复

  1. 一段插槽的主机挂掉:如果一个主机挂掉那么它的一个从机就顶替他作为主机,挂掉的主机如果恢复,那么该挂掉的主机就变成了从机。
  2. 一段插槽的主机和从机都挂掉:要看配置文件。
    在这里插入图片描述

13.7 Jedis连接集群:

/**
 * jedis连接集群测试.txt
 */
public class JedisClusterTest {

    public static void main(String[] args) {
        //1、可以创建一个集合,但没必要

//        HashSet hostAndPorts  = new HashSet<>();
//        hostAndPorts.add(new HostAndPort("8.142.89.219",6379));
//        hostAndPorts.add(new HostAndPort("8.142.89.219",6380));

        //1、可以直接创建hostAndPort:因为搭建的是集群,集群中任意一台服务器都是入口。
        HostAndPort hostAndPort = new HostAndPort("8.142.89.219", 6380);

        //2、
        JedisCluster jedisCluster = new JedisCluster(hostAndPort);

        jedisCluster.mset("k1{user}","v1","k2{user}","v2");//集群存入一次存多个值,则需要分组。这里k1,k2都放在user组
        System.out.println(jedisCluster.get("k2{user}"));
    }
}

13.8 redis集群总结:

(1)好处:

  1. 实现了扩容
  2. 分摊了压力
  3. 无中心化配置操作方便

(2)坏处

  1. 多键操作麻烦。需要分组
  2. 多键的Redis事务是不被支持的。lua脚本不被支持
  3. 由于集群方案出现较晚,很多公司已经采用了其他的集群方案,而代理或者客户端分片的方案想要迁移至redis cluster,需要整体迁移而不是逐步过渡,复杂度较大。

14 、Redis应用问题解决

【Redis6】Redis6笔记_第44张图片

14.1 缓存穿透

(1)什么是缓存穿透

  1. 在获取redis缓存中的key,如果缓存中没有找到,那么从而会向数据库(MySQL)进行查找,有可能压倒数据源。比如黑客一直请求不存在的id,那么就容易引起数据库的崩溃。

(2)解决方案

  1. 对空值缓存:如果一个数据的查询结果返回为空,那么我们将对这个空值进行缓存,让key=null;空值最多持续5分钟。(不方便)
  2. 设置可访问名单(白名单)。使用bitmaps数据结构,把可访问的id都存放到里面,id作为bitmaps的偏移量。(缺点:每次的请求id都需要和bitmaps里面的值比较,看id在bitmaps存在不,耗时长)
  3. 使用布隆过滤器(最优):优点:查找速度比一般的算法来的快。缺点:误判率比较高,删除困难。底层就是把数据hash到一个bitmaps中,一定不存在的数据会被bitmaps拦截掉。从而减小数据库的访问压力。
  4. 使用监控,监控到如果某一台主机经常访问某个不存在的id,就把它加入黑名单。

14.2 缓存击穿

(1)什么是缓存击穿

  1. 一个key在Redis中刚好过期,这时候这个key访问量突然增大,使数据库压力过大,后端DB压垮。

(2)解决方案

  1. 预先设置热门数据。
  2. 实时调整,如果发现某几个key热度突然增加,则增加它的过期时间。
  3. 加锁方式:由高并发转低并发(一般使用分布式锁)。

14.3 缓存雪崩

(1)什么是缓存雪崩

  1. 当缓存有大量的key过期,这时就会造成数据库访问量过大。缓存失效时的雪崩效应对底层系统的冲击非常可怕!

(2)解决方案

  1. 构架多级缓存架构:nginx缓存+Redis缓存+其他缓存。缺点:实现起来很复杂
  2. 使用锁或者队列:能保证DB不down机,但不适用于高并发场景
  3. 设置过期标志:当缓存中key快过期了,则通知其他线程去延长过期时间。
  4. 将失效时间分散开来:让key的有效时间随机延迟1~5分钟。就不会造成集体失效现象。

14.4 分布式锁

(1)什么是分布式锁?

【Redis6】Redis6笔记_第45张图片

(2)主流实现方案:

  1. 基于数据库实现分布式锁
  2. 基于缓存(redis)实现 :性能高
  3. 基于zookeeper实现 :可靠性好

(3)Redis实现分布式锁。

  1. 设置锁:setnx key value :设置key-value并加锁
  2. 释放锁:①删除键(手动释放):del key;②设置锁的过期时间:expire key 时间
  3. 设置锁和释放锁(设置过期时间)的原子操作:set key value nx ex 加秒。
    【Redis6】Redis6笔记_第46张图片

(4)Java实现分布式锁。(上锁和去锁)

  1. SpringBoot实现:
/**
 * 分布式锁Java代码实现.txt
 */
@RestController
public class Test {

    @Autowired
    RedisTemplate redisTemplate;

    @GetMapping("/test")
    public void testLock(){
        //1 获取锁,要要给锁加过期时间;如果业务逻辑出现问题锁才会自动释放:这里为11秒过期
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111",11, TimeUnit.SECONDS);
        //2 获取锁成功、查询num值
        if(lock){
            Object value = redisTemplate.opsForValue().get("num");//redis中set num 0;
            //2.1判断num为空return
            if(StringUtils.isEmpty(value)){
                return;
            }
            //2.2有值就转换为int
            int num = Integer.parseInt(value + "");
            //2.3把redis的num加1
            redisTemplate.opsForValue().set("num",++num);
            //2.4释放锁,手动释放
            redisTemplate.delete("lock");
        }else {
            //3.获取锁失败就隔0.1秒在获取
            try {
                Thread.sleep(100);
                testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

上述代码的业务逻辑为:
【Redis6】Redis6笔记_第47张图片

(5)分布式锁中的锁的误删问题。(给锁加UUID解决该问题)

【Redis6】Redis6笔记_第48张图片

改进:给每个锁加了uuid

/**
 * 分布式锁Java代码实现.txt
 */
@RestController
public class Test {

    @Autowired
    RedisTemplate redisTemplate;

    @GetMapping("/test")
    public void testLock(){

        //为每个拿到锁的线程都生成一个uuid.
        String uuid = UUID.randomUUID().toString();

        //1 获取锁,要要给锁加过期时间;如果业务逻辑出现问题锁才会自动释放:这里为11秒过期
                                                                //key:lock——————value:uuid+分界线+实际的value。
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid+"uuid与value的分界线"+111,11, TimeUnit.SECONDS);
        //2 获取锁成功、查询num值
        if(lock){
            Object value = redisTemplate.opsForValue().get("num");//redis中set num 0;
            //2.1判断num为空return
            if(StringUtils.isEmpty(value)){
                return;
            }
            //2.2有值就转换为int
            int num = Integer.parseInt(value + "");
            //2.3把redis的num加1
            redisTemplate.opsForValue().set("num",++num);

            //2.4释放锁,手动释放——判断_当前线程持有的锁的uuid_是否与_Redis缓存中该key的锁的uuid_相等
            String lock1 = (String) redisTemplate.opsForValue().get("lock");

            String[] s = lock1.split("uuid与value的分界线");//把从Redis中取出的value以“分界线”,分解 成uuid和实际value值
            String redis_uuid1=s[0];
            System.out.println("redis_uuid:"+s[0]);
            System.out.println("value:"+s[1]);

            if(uuid.equals(redis_uuid1)){
                redisTemplate.delete("lock");
            }else {
                return;
            }

        }else {
            //3.获取锁失败就隔0.1秒在获取
            try {
                Thread.sleep(100);
                testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

  1. 现在的逻辑为:
    【Redis6】Redis6笔记_第49张图片

(6)lua脚本的使用:保证原子性

  1. 使用原因:
    【Redis6】Redis6笔记_第50张图片

  2. Lua脚本怎么写可以参考菜鸟教程

@RestController
public class TestLockLua {

    @Autowired
    RedisTemplate redisTemplate;

    @GetMapping("/testLockLua")
    public void testLockLua() {
        //为每个拿到锁的线程都生成一个uuid.
        String uuid = UUID.randomUUID().toString();

        //定义一个锁:lua脚本可以使用同意把锁来实现删除
        String skuId = "25"; // 访问skuId 为25号的商品 100008348542
        String locKey = "lock:" + skuId; // 锁住的是每个商品的数据

        //1 获取锁,要要给锁加过期时间;如果业务逻辑出现问题锁才会自动释放:这里为11秒过期
        //key:lock——————value:uuid+分界线+实际的value。
        String value=uuid + "uuid与value的分界线" + 111;
        Boolean lock = redisTemplate.opsForValue().setIfAbsent(locKey, value, 11, TimeUnit.SECONDS);
        //2 获取锁成功、查询num值
        if (lock) {
            Object num = redisTemplate.opsForValue().get("num");
            //2.1判断num为空return
            if (StringUtils.isEmpty(num)) {
                return;
            }
            //2.2有值就转换为int
            int num1 = Integer.parseInt(num + "");
            //2.3把redis的num1加1
            redisTemplate.opsForValue().set("num", ++num1);

            //2.4释放锁,手动释放——判断_当前线程持有的锁的uuid_是否与_Redis缓存中该key的锁的uuid_相等
            String lock1 = (String) redisTemplate.opsForValue().get(locKey);
            //使用lua脚本
          String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            //使用redis执行lua
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            redisScript.setScriptText(script);
            //设置一下返回值的类型 为 Long
            //因为删除判断时,返回的0时String类型,需要封装成Long来判断;
            redisScript.setResultType(Long.class);
            redisTemplate.execute(redisScript,Arrays.asList(locKey),value);

        } else {
            //3.获取锁失败就隔0.1秒在获取
            try {
                Thread.sleep(100);
                testLockLua();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

(7)分布式锁总结

  1. 为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:
  • 互斥性。在任意时刻,只有一个客户端能持有锁。
  • 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。(设置过期时间)
  • 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。(uuid的判断和使用LUA脚本保证原子性)
  • 加锁和解锁必须具有原子性。

15、Redis6 新特性

15.1 ACL(了解)

(1)简介:

  1. Redis ACL是Access Control List(访问控制列表)的缩写,该功能允许根据可以执行的命令和可以访问的键来限制某些连接。
  2. 在Redis 5版本之前,Redis 安全规则只有密码控制 还有通过rename 来调整高危命令比如 flushdb , KEYS* , shutdown 等。Redis 6 则提供ACL的功能对用户进行更细粒度的权限控制 :
    (1)接入权限:用户名和密码
    (2)可以执行的命令
    (3)可以操作的 KEY
    参考官网: https://redis.io/topics/acl

15.2 IO多线程

(1)简介:

  1. Redis6终于支撑多线程了,告别单线程了吗?

  2. IO多线程其实指客户端交互部分的网络IO交互处理模块多线程,而非执行命令多线程。Redis6执行命令依然是单线程。
    【Redis6】Redis6笔记_第51张图片

  3. redis6多路IO默认不开启,配置文件修改,redis7好像默认启用了。

io-threads-do-reads yes
io-threads 4

15.3 Redis6新功能

  1. RESP3新的 Redis 通信协议:优化服务端与客户端之间通信
  2. Client side caching客户端缓存:基于 RESP3 协议实现的客户端缓存功能。为了进一步提升缓存的性能,将客户端经常访问的数据cache到客户端。减少TCP网络交互。
  3. 集群代理(Redis Cluster Proxy): 将集群抽象为单实例,客户端不需要知道集群中的具体节点个数和主从身份,通过代理访问集群,就像访问单机。Redis一样不过需要注意的是代理不改变 Cluster 的功能限制,不支持的命令还是不会支持,比如跨 slot 的多Key操作。
  1. 自动化路由:每个查询被自动路由到集群的正确节点。
  2. 多线程,每个线程都有自己与集群的连接
  3. 顺序性,只执行命令时时串行的
  4. 把2、3点总结为:单线程+多路IO复用技术
  1. Modules API:
    Redis 6中模块API开发进展非常大,因为Redis Labs为了开发复杂的功能,从一开始就用上Redis模块。Redis可以变成一个框架,利用Modules来构建不同系统,而不需要从头开始写然后还要BSD许可。Redis一开始就是一个向编写各种系统开放的平台。

你可能感兴趣的:(Redis,JVM,redis,nosql,数据库)