侧边栏壁纸
博主头像
清如许博主等级

努力成长为一颗大树,一半洒落阴凉,一半沐浴阳光,非常沉默非常骄傲,从不依靠从不寻找

  • 累计撰写 80 篇文章
  • 累计创建 44 个标签
  • 累计收到 5 条评论

目 录CONTENT

文章目录

详解Redis,Redis缓存,Redis分布式锁.md

清如许
2020-04-25 / 0 评论 / 0 点赞 / 250 阅读 / 4,860 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-05-21,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。



1、Redis基本知识

简介

Redis是一个支持网络、基于内存、可选持久性的NoSql数据库,目前在很多的系统中都使用了Redis,尤其是在实现缓存功能的时候应用的尤其广泛(缓存功能也是很多人对Redis的认识),那么Redis到底有哪些优点和缺点,为什么会被广泛应用呢?

Redis的优点

Redis的第一个优点就是速度快,Redis使用C语言实现,基于内存,数据的读写效率非常的高,这也是为什么很多系统的缓存功能使用Redis来实现,但是需要明确的是Redis是一个数据库,缓存只是它的一项应用而已。

Redis的第二个优点是单线程模型,所谓单线程模型就是每一个请求都会有一个全新的线程来进行处理,这一点类似于Struts2,每一个请求都会有一个新的线程来进行处理。这样做的好处就是避免了线程频繁切换带来的系统开销,同时也避免了让人头疼的多线程问题。

Redis的第三个优点就是使用了非阻塞I/O (NIO),不在网络上浪费时间,进一步提高了效率。

Redis的第四个优点就是支持多种的数据类型,并且每一种数据类型都提供了丰富的操作命令,适用于很多特殊的场景,并且支持自定义命令创建个性化的操作命令。

2、redis持久化机制

redis服务器宕机,内存数据是会丢失了,为了保证数据不丢失需要对数据做备份,所备份就是持久化,Redis的持久化即将内存中的数据同步到硬盘,主要包括两种方式RDB、AOF。

RDB持久化机制,(默认使用):做当前内存数据的全本快照,
将内存中的数据以快照的方式写入到二进制文件dump.rdb,
在redis.conf中可以设置发起快照保存的条件。在指定的时间内如果有超过指定数量的key被修改,则会发起快照保存。

这种方式在数据的实时性上不高,在突然断电的情况下,可能会出现部分数据的丢失,即最后一次快照之后在内存中发生修改的数据。

简单来说:RDB就是将redis上的所有数据做个备份,存储的是二进制的数据。

AOF持久化机制,(默认是关闭)

AOF是将Redis内存数据库中更改的数据都记录到指定的文件appendonly.aof。在redis.conf中可以进行写磁盘的相关设置。

在突然断电的情况下,由于在appendonly.aof中保存了最后一次写磁盘之后redis内存发生数据修改的指令,所以在这个Redis重启后,基本不会发生数据丢失,比RDB具有更好的数据安全性。

appendfsync always 接收到更改数据的命令,立即将其记录到appendonly.aof中,能保证数据持久化,数据完全不丢失,但效率相对最低。

appendfsync everysec 每秒钟将redis内存数据修改的命令记录到appendonly.aof中,在性能和持久化上做了折中。因频繁执行磁盘操作,在仅存在单个Master执行写操作时,效率可能存在问题。但在多个Master执行写操作的Redis集群中,效率会提升。

appendfsync no 依赖于操作系统,因不会频繁执行磁盘操作而性能最好,但redis内存数据修改持久化没有保证,无法保证数据可靠性

如何开启AOF持久化:
将redis.conf文件中 appendonly 改成 yes ,自动创建appendonly.aof,该文件存储的客户端执行过增删改操作的命令


3、 Redis的数据结构

redis数据库存储数据使用的key-value,键值对方式存储
key是string类型 value的数据结构支持5个string、set、sorted_set、list、hash
在这里插入图片描述

4、Redis 的常用命令

String(可以存数字)

可以实现原子性的自增(数据安全)

在这里插入图片描述

这是最基本的类型了,没啥可说的,就是普通的set和get,做简单的k-v缓存

hash

这个是类似map的一种结构,这个一般就是可以将结构化的数据,比如一个对象(前提是这个对象没嵌套其他的对象)给缓存在redis里,然后每次读写缓存的时候,可以就操作hash里的某个字段。

key=150



value={

 “id”: 150,

 “name”: “zhangsan”,

 “age”: 20

}

hash类的数据结构,主要是用来存放一些对象,把一些简单的对象给缓存起来,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值

value={

 “id”: 150,

 “name”: “zhangsan”,

 “age”: 21

}

list

有序列表,这个是可以玩儿出很多花样的

微博,某个大v的粉丝,就可以以list的格式放在redis里去缓存

key=某大v

value=[zhangsan, lisi, wangwu]

key=书名

value=[评论1, 评论2, 评论3]

比如可以通过list存储一些列表型的数据结构,类似粉丝列表了、文章的评论列表了之类的东西

比如可以通过lrange命令,就是从某个元素开始读取多少个元素,可以基于list实现分页查询,这个很棒的一个功能,基于redis实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西,性能高,就一页一页走

比如可以搞个简单的消息队列,从list头怼进去,从list尾巴那里弄出来

set

无序集合,自动去重

直接基于set将系统里需要去重的数据扔进去,自动就给去重了,如果你需要对一些数据进行快速的全局去重,你当然也可以基于jvm内存里的HashSet进行去重,但是如果你的某个系统部署在多台机器上呢?

得基于redis进行全局的set去重

可以基于set玩儿交集、并集、差集的操作,比如交集吧,可以把两个人的粉丝列表整一个交集,看看俩人的共同好友是谁?对吧

把两个大v的粉丝都放在两个set中,对两个set做交集

sorted set 跳表

排序的set,去重但是可以排序,写进去的时候给一个分数,自动根据分数排序,这个可以玩儿很多的花样,最大的特点是有个分数可以自定义排序规则

比如说你要是想根据时间对数据排序,那么可以写入进去的时候用某个时间作为分数,人家自动给你按照时间排序

排行榜:将每个用户以及其对应的什么分数写入进去,zadd board score username,接着zrevrange board 0 99,就可以获取排名前100的用户;zrank board username,可以看到用户在排行榜里的排名

总结:优先掌握 String 即可

5、数据失效时间

场景:

手机验证码登录 手机验证码注册

验证码后台生成 Redis

  1. 集中存储
  2. 可以设置过期时间
验证码  

key = 手机号 
value = 验证码  

设置3分钟过期

Redis中可以设置数据的存活时间

命令

expire key 存活时间的秒
ttl key 查看key对应的数据的存活时间.
pexipre key 存活时间的毫秒
pttl key 查看key对应的数据的存活时间,毫秒单位

expire key 存活时间的秒

失效的原理

  1. 定期随机删除+惰性删除

    key  
    1    1分钟
    2    1分钟  
    3    1分钟
    4    1分钟
    5
    6
    
    redis 每过100ms 随机抽取一定数量的设置了失效时间的key 将过期的删除
    
    有些key过期了 每次都没有随机到  就一直删不掉  怎么办?
    
    惰性删除 get key 的时候 先判断 key是否过期 如果过期 返回数据为空
    
    

    定期随机删除 例如100ms

    查询的时候 先检查key

  2. 内存淘汰机制

    如果redis的内存占用过多的时候,此时会进行内存淘汰,有如下一些策略:

    redis 10个key,现在已经满了,redis需要删除掉5个key

    1个key,最近1分钟被查询了100次

    1个key,最近10分钟被查询了50次

    1个key,最近1个小时被查询了1次

    1. noeviction:当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了
    2. allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)
    3. allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的key给干掉啊
    4. volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key(这个一般不太合适)
    5. volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key
    6. volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除

    LRU算法 扩展视野


6、Redis的基本应用!!!

二级缓存
  1. 为什么要做缓存

  2. 缓存的选择(二级缓存)

  3. 二级缓存的基本概念

    1. 是什么
    2. 如何开启
  4. 二级缓存的缺点

  5. 使用Redis集成二级缓存的步骤

    1. 实现cache接口

缓存的作用

  1. 数据从内存获取,提升数据获取速度.
  2. 减轻了数据库读操作的访问压力(数据基本不变)

MyBatis二级缓存机制(开启)

机制:

  1. Java本地缓存空间.(jar)

  2. mybaits事务提交后操作缓存

  3. Mybatis根据Mapper文件的namespace划分多个缓存空间.

  4. mybatis会将查询语句执行结果,缓存在 sql所在mapper文件对应的namespace对应的缓存空间中.

    会将执行的查询sql(对sql处理后产生的对象)作为key.

  5. MyBatis执行DML,在事务提交之后,默认清空当前sql所在的mapper文件对应的namespace对应的缓存空间中

    A Mapper User表 脏读

    B Mapper User表 删除 缓存清空

    缓存空间融合

    1. 所有关于User表的操作都写在一个Mapper中
    2. Mapper配置 融合缓存空间(基本没有人使用)
    3. 第三方的缓存空间
      1. ehcache
      2. Redis

在这里插入图片描述

MyBatis缓存实现原理(源码)

org.apache.ibatis.cache.impl.PerpetualCache.class

  1. 根据namepace划分缓存空间(id)

  2. MyBatis二级缓存本质是一个Map结构

    key :和执行的sql先关

    value:查询结果相关

  3. 存放数据的功能: select语句(key)----查询结果(value)

  4. 获得数据的功能: 根据key

  5. 清空缓存的功能: clear

  6. MyBatis管理每个缓存,使用Map管理 key:id(namespace) value:PerpetualCache

在这里插入图片描述

MyBatis缓存的问题?(缓存数据量不能太多)

  1. mybatis缓存在tomcat的jvm内部分配的缓存空间.
  2. 缓存数据过多,挤占java运行期间需要的内存.

解决办法:

将Mybatis的二级缓存空间转移到Redis数据库中

在这里插入图片描述

Mybatis二级缓存空间划分

在这里插入图片描述

Redis缓存空间的划分设计

核心:

  1. 每个缓存空间是一个map
  2. 每个缓存空间对应一个namespace.(管理多个cache空间)

方案:

  1. 将mybatis的namespace作为redis的key
  2. 将key对应的value作为hash数据结构使用.(替换PerpetualCache)

在这里插入图片描述

自定义Redis缓存实现

自定义缓存实现类

  1. 自定义MyBatis二级缓存

    1. 自定缓存类实现Cache接口
    2. 导入redis操作相关的工具(jar,JedisUtil,jedis.properties)
    3. 必须具备如下功能:
  2. 根据namepace划分缓存空间(id)

  3. MyBatis二级缓存本质是一个Map结构

    key :和执行的sql先关

    value:查询结果相关

  4. 存放数据的功能: select语句(key)----查询结果(value)

  5. 获得数据的功能: 根据key

  6. 清空缓存的功能: clear

在这里插入图片描述

使用自定义的缓存

在这里插入图片描述

Session共享

为何要实现session共享?

nginx负载均衡,希望兼顾权重的按照硬件性能分配访问压力的优势,又想保证多个tomcat使用同一个session应该怎么解决?
在这里插入图片描述

  1. ip_hash
  2. session复制
  3. Redis

解决方案

使用redis管理负载均衡中多个tomcat的session.(Redis共享session)

在这里插入图片描述

如何实现Redis管理Session

在这里插入图片描述

配置步骤

  1. tomcat使用redis管理session的jar

将jar拷贝tomcat中lib

  1. 配置tomcat的session管理方式为RedisSessionManager
1. tomcat 配置文件context.xml

<!--注册session管理工具-->
<Manager className="session管理工具全类名"
         host="redis的ip地址"
         port="端口"
         maxInactiveInterval="session存活时间 秒 1800" 秒
         />

<!--将session管理工具使用在tomcat操作过程中-->
<Valve className="RedisSessionHandlerValve在tomcat中使用session管理工具"/>

重启两个tomcat

缓存问题

  1. 缓存穿透

  2. 缓存雪崩

    User
    
    根据主键查询 
    
    key
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    
    1W  -id  缓存中都没有 
    
    2000个查询  每秒 可以认为是安全的
    
缓存穿透

缓存击穿 大量不存在的key攻击

只需要极少的空间就可以判断一个元素是不是在一个集合之内,这正好是我们所需要的场景啊:判断key是否存在
在这里插入图片描述
解决方案

  1. 空值缓存 (非恶意攻击)

    key  value
    -1   null
    
  2. 布隆过滤器

    可以判断key是否在数据库中存在

    缺点:可能会判断出错 概率不高 但是会

缓存雪崩

微博

key = 鹿晗微博1  value = 相关信息 评论  追评 点赞 等
key = 鹿晗微博2  value = 相关信息 评论  追评 点赞 等
key = 鹿晗微博3  value = 相关信息 评论  追评 点赞 等
key = 鹿晗微博4  value = 相关信息 评论  追评 点赞 等
key = 鹿晗微博5  value = 相关信息 评论  追评 点赞 等
key = 吃瓜群众1 
key = 吃瓜群众2 
key = 吃瓜群众3 
key = 吃瓜群众4 
key = 吃瓜群众5

上千万key 这些key一定会设置失效 失效时间设置的不合理  同一时间大量key过期了(500W)  如果发生在平时 无所谓
不巧的是  上热搜了 突然间 大量的用户来访问 相关的信息

大量key同一时间失效 将导致不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储的意义

在这里插入图片描述

大量流量 且数据失效 导致不存在的数据每次请求都要到存储层去查询 一模一样的SQL 数据库崩溃

  1. 合理的设置过期时间 单体架构
  2. 分布式锁
  3. 多级缓存

分布式的锁,谁获得了这把锁,谁就可以访问数据库

在这里插入图片描述

大型项目中

在这里插入图片描述

如果说用户查不到数据 降级服务

  1. 先等着 两三秒
  2. 能直接 返回固定数据
  3. 等等

7、Redis 分布式锁

什么是分布式锁

一种逻辑处理

在这里插入图片描述

Redis 分布式锁

分布式锁本质上要实现的目标就是在 Redis 里面占一个“茅坑”,当别的进程也要来占时,发现已经有人蹲在那里了,就只好放弃或者稍后再试。

占坑一般是使用 setnx(set if not exists) 指令,只允许被一个客户端占坑。先来先占, 用完了,再调用 del 指令释放茅坑。

// 这里的冒号:就是一个普通的字符,没特别含义,它可以是任意其它字符,不要误解
> setnx lock:codehole true
OK
... do something critical ...
> del lock:codehole
(integer) 1

但是有个问题,如果逻辑执行到中间出现异常了,可能会导致 del 指令没有被调用,这样就会陷入死锁,锁永远得不到释放。

于是我们在拿到锁之后,再给锁加上一个过期时间,比如 5s,这样即使中间出现异常也可以保证 5 秒之后锁会自动释放。

> setnx lock:codehole true
OK
> expire lock:codehole 5
... do something critical ...
> del lock:codehole
(integer) 1

但是以上逻辑还有问题。如果在 setnx 和 expire 之间服务器进程突然挂掉了,可能是因为机器掉电或者是被人为杀掉的,就会导致 expire 得不到执行,也会造成死锁。

这种问题的根源就在于 setnx 和 expire 是两条指令而不是原子指令。如果这两条指令可以一起执行就不会出现问题。也许你会想到用 Redis 事务来解决。但是这里不行,因为 expire 是依赖于 setnx 的执行结果的,如果 setnx 没抢到锁,expire 是不应该执行的。事务里没有 if-else 分支逻辑,事务的特点是一口气执行,要么全部执行要么一个都不执行。

为了解决这个疑难,Redis 开源社区涌现了一堆分布式锁的 library,专门用来解决这个问题。实现方法极为复杂,小白用户一般要费很大的精力才可以搞懂。如果你需要使用分布式锁,意味着你不能仅仅使用 Jedis 或者 redis-py 就行了,还得引入分布式锁的 library。

为了治理这个乱象,Redis 2.8 版本中作者加入了 set 指令的扩展参数,使得 setnx 和 expire 指令可以一起执行,彻底解决了分布式锁的乱象。从此以后所有的第三方分布式锁 library 可以休息了。

> set lock:codehole true ex 5 nx
OK
... do something critical ...
> del lock:codehole

上面这个指令就是 setnx 和 expire 组合在一起的原子指令,它就是分布式锁的奥义所在。

0

评论区