Redis(单线程的)

Redis 教程_redis教程,只有一个主进程

任务列表:

  • redis集群介绍和散列插槽原理
  • redis分布式锁介绍和原理
  • redis主从介绍和主从同步原理
  • redis哨兵作用和工作原理
  • redis持久化

image-20230202153810399

应用场景

(21条消息) Redis使用场景_redis 用在哪些方面_Leigel_java的博客-CSDN博客

  • 缓存

  • 任务队列

  • 消息队列

  • 分布式锁

  • 代替集群的session共享 补充:session是tomcat的内存空间,存入session的数据般是基本信息不是全部信息

    image-20230225135917250

    image-20230225140802547

    image-20230225140734149

数据类型

指的是value的值,键的值统一是字符类型

写的很详细:Redis 的五种基本数据类型redis的五种数据类型喵先森爱吃鱼的博客-CSDN博客

image-20230203165848147

ZSet(有序集合)是 Redis 数据库提供的一种数据结构,它是一种有序的字符串集合,每个成员都关联着一个浮点数类型的分值(score),用于进行成员之间的排序。ZSet 的特点是能够保持成员的插入顺序和根据分值范围或成员获取数据,同时还支持快速的插入、删除和更新操作。

Stream是Redis5.0引入的一种新的数据类型,可以实现一个完善的消息队列

image-20230225140416857

用于缓存对象时可以采用Hash的结构进行保存,便于做CRUD

常用命令

image-20230203170613866

image-20230203171256045

image-20230203190129205

image-20230203190706958

image-20230225141539149

image-20230203191713959

image-20230206160407552

在Redis中,当我们尝试获取某个键对应的值时,如果该键不存在,Redis会返回一个特殊的值nil。本文将介绍如何使用Redis来实现输出nil的功能。

Java中操作redis

image-20230203191903126

image-20230206155300251

Springboot框架下的redis

image-20230206155609687

image-20230206160535244

RedisTemplate默认会将key、value序列化

通过配置类重新定义序列化方法,不过value不需要再重新定义,因为程序中get出来还会反序列化

image-20230206161633566

image-20230206161526334

缓存问题

image-20230225142416756

image-20230225142554493

更新缓存操作

image-20230225142935761

主动更新的三种方法

image-20230225144017132

image-20230225143451401

不论先操作缓存还是先操作数据库都有问题要具体情况具体分析,但先操作数据库在删除缓存出错的概率小

两类线程安全问题:

image-20231003141302165

image-20231003141318016

为什么一般是先更新数据库再删缓存?

  • 首先,删除缓存再更新数据库,由于数据库的更新操作所需时间较长,期间进行查询容易查到原来的数据
  • 其次同样由于数据库更新相对于缓存读取要慢很多,因此在写入缓存时发送生数据库更新完成的概率很低

缓存穿透

缓存、数据库中都不存在查询对象

危害

image-20230225151318864

image-20230225151650627

image-20230225152034083

缓存雪崩

image-20230225152732321

多级缓存:

缓存击穿

image-20230225153019420

image-20230225153548273

image-20230225153611470

通过参考一致性和可用性的需求来进行方案选择

解决方法的区别:

  • 互斥锁:获取锁失败后会继续等待,会消耗性能

    • 自己写互斥锁时注意封装类型转基本类型时若直接转可能会导致拆箱后存在空指针
  • 逻辑过期:发现数据过期会开一个新线程去更新数据,其他线程获取锁失败后直接返回过期数据

Rdis持久化

RDB(数据量较小)

RDB存放的是数据

image-20230225154132787

bgsave:异步存储

image-20230225154250840

image-20230225154432988

image-20230225160522210

image-20230225160547818

AOF(安全性更好)

AOF存放的是命令,所以所占内存会比RDB大很多

image-20230225160731369

image-20230225160857232

image-20230225161449825

image-20230225161800530

redis主从介绍和主从同步原理

处理redis高并发读的问题

如何搭建主从集群:

同过在从节点执行:slaveof 主节点ip 主节点端口 实现主从设置

①:slave节点请求增量同步;

②:master节点判断replid,发现不一致,拒绝增量同步,进行全量同步,将自己的repliid和offset发送给slave;

③:master将完整的内存数据生产RDB,发送RDB给slave;

④:slave清空本地数据并加载master的RDB;

⑤:master将RDB期间的命令记录在repl_baklog,并持续将log中的命令发送给slave;

⑥:slave执行接收到的命令,保持与master之间的同步。

那么如何进行增量同步呢?

image-20231126204816352

数据同步优化:

image-20231126205146973

redis哨兵作用和工作原理

实现主从集群的自动故障恢复

监控原理:sentinel基于心跳机制监测服务的状态,每隔1秒向集群的每个实例发送ping命令;

主观下线(单个哨兵)与客观下线(多个哨兵)

流程

① 监控

setinel会不断检查集群中master和slave是否按预期工作(Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令);

ps:哨兵一般配置中是监控主节点的,通过主节点可以获取其他节点的信息

image-20231126205756171

② 自动故障恢复

如果master故障了,sentinel会将一个slave提升为master。当故障实例恢复后,也以新的master为主,自己则作为slave节点加入到集群中;如果slave故障了,sentinel会自动重启slave服务;

image-20231126205455795

③ 通知

sentinel充当Redis客户端的服务发现来源,当集群中发生了故障转移时,会将最新的信息推送给Redis的客户端

image-20230916162803773

从slave选取master规则

①:首先会判断slave节点与master节点断开时间长短,如果超过指定值(down-after-milliseconds * 10)则会被排除掉的;

②:然后判断slave节点的slave-priority值,越小优先级越高。如果是0则表示永远不参与选举;

③:如果slave-priority一样,则判断slave节点的offset值,越大说明数据越新,优先级越高;

④:最后判断slave节点的运行id大小,越小优先级越高。

记录:先排除,再比较priority值,接着比较offset,最后比较runid。

新master如何切换(故障转移)

当选出一个新的master后,该如何切换呢?

流程如下:

①:sentinel给备选的slave节点发送 slaveof no one 命令,让该节点成为master;

②:sentinel给所有其他的slave节点发送slaveof xxx.xxx.xx.xx 端口(比如我们新master节点是192.168.1.11 7022 那么命令就是 slaveof 192.168.1.11 7002)命令,让这些slave成为新master的从节点,开始从新master上同步数据;

③:最后,sentinel将故障节点标记为slave,当故障节点恢复后自动成为新master的slave节点。

redis集群介绍和散列插槽原理

处理redis高并发写的问题

集群中如何读取key:Redis集群中数据的key与插槽绑定

当读取key时,会先计算插槽点再重定向到该节点去获取数据

如何根据key计算插槽

  • key中包含”{}”,且“{}”中至少包含1个字符,“{}”中的部分是有效部分
  • key中不包含“0”,整个key都是有效部分

例如:key是num,那么就根据num计算,如果是{itcast)num,则根据itcast计算。计算方式是利用CRC16算法得到一个hash值,然后对16384取余,得到的结果就是slot值。

如何将同一类数据固定的保存在同一个Redis实例?
这一类数据使用相同的有效部分,例如key都以{typeld}为前缀

Redis分布式锁

分布式锁的问题

  • 锁的误删问题
  • 锁的原子性问题

单体模式下,锁监视器是在一个jvm中唯一的,但在分布式模式下有多个jvm进程,在此情况下需要实现同步,此时的锁需要满足分布式系统或集群模式下,多进程可见的一把互斥锁,即分布式锁。

基于Redis实现分布式锁:

获取锁:SETNX lock thread1 释放锁:DEL lock(为防止服务器宕机导致释放锁失败,需要设置过期时间:ecpire lock 5)

为防止SETNX lock thread1执行后服务器宕机,无法继续执行ecpire lock 5的问题,可以通过 set lock thread1 nx ex 10来一次性实现两种操作,这个需要对redis的操作命令有一定的熟悉

为防止发生线程1因业务阻塞超时而误删线程二的锁,在释放锁的时候要查看锁的value值,只有当线程标识(一般推荐用uuid)与value值相同时才能够进行锁的释放。

image-20230723101336565

image-20230723101309506

进一步考虑,由于判断锁标识和释放锁是两个动作,因此即使加了释放锁时判断锁是否为自己的也会引发并发问题,如下图所示

image-20230723102431125

利用Lua脚本语言在一条脚本中编写多条Redis命令,来确保多条命令执行时的原子性

Lua语言介绍

image-20230723103549819

分布式锁的优化Redission

官网地址: https://redisson.org

官网提供了快速开始的方法,流程和mybaits差不多,就是引依赖、编写配置类等

GitHub地址: https://github.com/redisson/redisson

解决问题

可重入

image-20230723111952594

image-20231005171720938

可重试及超时问题

可重试及解决锁超时问题

image-20231005174156473

image-20231005174246651

主从一致性问题

为了防止主从同步时主节点宕机导致锁失效,向每一个redis节点都设置锁(连锁,每个redis节点中的锁的标识是不同的),这样当其中一台挂点后,只要其他几台中获取锁失败,就不能够获取锁。

image-20231005174733991

消息队列

消息队列和阻塞队列的区别:

  1. 消息队列存储在jvm之外,不会受jvm内存影响
  2. 消息队列除了接受消息外还会保证数据的安全性(list方式、),会对消息队列中的数据做持久化(JVM宕机后其中的数据会消失)
  3. 在消息投递给消费者后需要消费者确认,若消费者未确认,则消息还会存在于消息队列中

image-20230916161504560

list结构

利用list结构模拟消息队列

redis的list是通过双向链表实现的,基于list的消息队列消息的出口和入口不在同一处。一般是左进右出

注意:使用RPOP或LPOP会返回null,无法实现阻塞,要实现阻塞需使用BRPOP或BLPOP

PubSub

基本的点对点模型

Stream

image-20230916154323989

image-20230916154635734

消息永久存在于队列中

缺点:会存在漏读消息的情况

image-20230916154840649

消费者组

  • 消息分流(队列中的消息会分给组内不同的消费者)
  • 消息标示(消费者组会记录一个消息标示,记录一个最后被处理的消息)
  • 消息确认(会将消息从pendinglist中移除)

image-20230916155616273