【Redis7】2023保姆文章,哨兵、主从、雪崩、穿透


【提示】学习本文需要先掌握Linux、MySQL、SpringBoot应用


一、Redis入门

1.SQL与NoSQL的区别

(1).具体区别

SQL: 关系型数据库 ; NoSQL: 关系型数据库
那么它们两者之间究竟有什么区别呢?
SQL
①: 结构化(Structured)
SQL的数据有固定的表格规定,如主键、外键、类型、大小。而这些内容,在创建表之初就必须设计好。
当表中存储上百万、千万级别的数据时,我们是几乎无法修改的。而恰恰就是这里显得很鸡肋。

②: 关联(Relational)
SQL当中的外键能够将两张表格关联起来、而关联起来的表格无法随意地对其作出修改
不过与此同时也有一个好处,节省了许多空间
在这里插入图片描述
③: SQL查询
固定的SQL语法

SELECT id,name,age FROM tbl_user WHERE id = 1;

④: ACID 事务四大特性


NoSQL
①: 非结构化(Unstructured)
NoSQL没有要求、也能够随时去修改"字段名"
在这里插入图片描述
②: 非关联(Unrelated)
以Json格式来存储信息、可是没有了外键,每一条记录都必须记录下详细的内容。
操作起来很自由,但却要牺牲不少存储空间
在这里插入图片描述
③: 非SQL
主打一个简单快捷方便随性

get user:1

④: BASE 无事务、主打一个叛逆 不同的库采用不同的语法

(2).表格版

SQLNoSQL
数据结构结构化非结构化
数据关联关联的无关联的
查询方式SQL查询非SQL
事务特性ACIDBase
存储方式磁盘内存
扩展性垂直水平
使用场景数据结构稳定,相关业务对数据安全性、一致性要求较高数据结构不稳定、安全性要求不高。对性能要求高

2.认识Redis

特征:
1. 键值 (key-value)型,value支持多种不同数据结构,功能丰富
2. 单线程,每个命令具备原子性
3. 低延迟,速度快(基于内存、IO多路复用、良好的编码)
4. 支持数据持久化 (定期将数据存入磁盘、断电也不怕)
5. 支持主从集群、分片集群
6. 支持多语言客户端 (java \ c …)

Redis 本身就是针对于 Linux 开发的、网上Windows版本是微软后来发布的,所以本文也是针对Linux进行记录

(1).Redis安装

我使用的是VMware虚拟机、使用的是 centOS7、FinalShell
①: 下载Redis、 在FinalShell中输入

//Redis是基于C语言编写,需要先安装Redis所需的gcc依赖:
yum install -y gcc tcl
//下方目录通常用来放我们的安装文件
cd /usr/local/src/
//上传Redis官网下载来的压缩包然后解压
tar -zxvf redis-7.0.11.tar.gz 
//进入Redis目录
cd redis-7.0.11/
//运行编译命令
make && make install
//等待安装完成之后进入下方文件就能看见文件
cd /usr/local/bin/

安装完成图、此后你可以在任意目录下运行
redis-server : 这种启动我们称之为前台启动,会阻塞整个窗口无法动弹,需要关闭窗口或Ctrl + c 停止Redis
在这里插入图片描述

(2).Redis指定文件启动

想要Redis以后台方式启动,就必须修改Redis的配置文件、就要回到我们之前解压redis安装包下
/usr/local/src/redis-7.0.11 名为 redis.conf,现将这个文件备份以防万一修改出错

我个人认为直接打开文件修改更方便也更快捷,而且finalshell同样可以直接ctrl f 查找

cp redis.conf redis.conf.bck
//备份后来修改
vi redis.conf
//监听的地址,默认是127.0.0.1,会导致只能在本地访问
//修改为0.0.00则可以在任意IP访问,生产环境不要设置为0.0.0.0
bind 0.0.0.0
//守护进程,修改为yes后即可后台运行
daemonize yes
//密码,设置后访问Redis必须输入密码
requirepass 123321 

Redis常见的其他配置

//监听的端口
port 6379
//工作目录,默认是当前目录,也就是运行redis-server时的命今,日志、持久化等文件会保存在这个目录
dir .
//数据库数量,设置为1,代表只使用1个库,默认有16个库,编号0~15databases 1
//设置redis能够使用的最大内存
maxmemory 512mb
//日志文件,默认为空,不记录日志,可以指定日志文件名
logfile "redis.log"

(3).配置完文件之后就可以启动Redis

//进入redis安装目录
cd /usr/local/src/redis-7.0.11
//后台启动!
redis-server redis.conf

(4).如何后台关闭Redis

在这里插入图片描述
学习过SpringBoot的朋友应该知道,在Linux后台部署项目,也是通过这种方式杀进程

(5).Redis如何开机自启?

创建下方文件: vi /etc/systemd/system/redis.service
在新文件中写入下方配置,注意自己的Redis版本

[Unit]
Description=redis-server
After=network.target

[Service]
Type=forking
ExecStart=/usr/local/bin/redis-server /usr/local/src/redis-7.0.11/redis.conf
PrivateTmp=true

[Install]
WantedBy=multi-user.target

在这里插入图片描述
一条关键的指令: systemctl enable redis 开机自启


(6).Redis客户端

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

3.认识Redis命令

(1).Redis数据结构介绍

Redis是一个key-value的数据库,key一般是String类,不过value的类型多种多样
指令全部记住是不可能的,语法查文档
在这里插入图片描述

(2).Redis通用命令

KEYS: 查看符合模板的所有key,不建议在生产环境设备上使用0
DEL: 删除一个指定的key
EXISTS: 判断key是否存在
EXPIRE: 给一个key设置有效期,有效期到期时该key会被自动删除
TTL: 查看一个KEY的剩余有效期

Redis命令大全

(3).List命令

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

(4).Set命令

元素不能重复、无序集合、底层通过哈希表实现
在这里插入图片描述

(5).ZSet命令

Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。
在这里插入图片描述

(6).hash命令

在这里插入图片描述

(7).geo地理坐标命令

通常来用开发“附近的人”,计算坐标
在这里插入图片描述
在这里插入图片描述

(8).HyperlogLog基数统计命令

(9).bitmap位图统计命令

通常用来统计签到次数,由于签到只需要“是”与“否”两种情况,而一个位图只需要1bit。所以用来统计签到这类数据极其节省空间
在这里插入图片描述

3.Redis的Java客户端

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

4.Redis持久化

(1).为什么需要持久化?

持久化: “把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)”
Redis是基于内存的数据库、一旦服务宕机,内存中的数据会全部丢失。因为内存不能够进行持久化,我们只能持久化在磁盘上。
所以我们的方案是,服务器重启之后,在数据库重新读取我们的数据塞到Redis里面,同时也存在以下问题:
1.大量数据会对数据库产生压力

(2).持久化的方案?

RDB: 也就是主线程分裂出一个子线程来保存当前的nosql内容,保存到一个rdb文件中。当我重启redis,它会自动加载最新版本RDB来恢复数据。
RDB的优缺点
优点:
①由于rdb文件是二进制存储的,节省磁盘空间。
②由于rdb保存的是数据库状态快照,恢复速度快。
缺点:
①虽然redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能
②由于rdb的save策略,如果redis服务器意外宕机,会导致最后一次保存快照后的修改数据丢失。
AOF
类似于游戏啥时候存档的策略
在这里插入图片描述
两种持久化方式,优先使用AOF 如果AOF文件不存在就是用RDB

(3).Redis高可用之主从复制

主从库的配置
在这里插入图片描述
缺点:如果主库数据为空,也会把从库当中的数据置空

(4).Redis高可用之哨兵机制

所有的从库都只能提供读服务、并不提供写服务。所有的写操作都只能在主库上面进行,如果主库挂了,那么整个系统就失去了写的能力。因此出现了哨兵机制

哨兵机制会时刻检查所有主从服务器,一旦主服务器挂了,哨兵机制会立刻将从服务器升级为主服务器,以此来保证系统的高可用性
在这里插入图片描述
在这里插入图片描述

主服务器宕机、从服务器升级为主服务器。此时原先的主服务器就算重启,也只是从服务器了。

(5).Redis高可用之主集群分片

配置主集群分片为了解决两个问题,一个是主服务器容量有限、另一个是关于一个系统只有主服务器能够进行写操作。配置了主集群分片之后从服务器也能够进行读写

集群是指多台计算机协同工作,共同完成一个任务。集群可以提高系统的可靠性、可用性和扩展性。

分片是指将数据分成多个部分,存储在不同的节点上。这样,每个节点只需要存储一部分数据,可以提高系统的扩展性和性能。

举个例子,假设我们有一个用户数据库,包含1000万条记录。如果我们将这些记录平均分成10份,每份存储在一个节点上,那么每个节点只需要存储100万条记录。当我们查询某个用户的信息时,只需要在对应的节点上查询即可,而不需要在所有节点上都进行查询。这样就大大提高了查询效率。

(6).Redis底层数据结构分析之SDS

简单动态字符串
用于存储二进制数据的一种结构、具有动态扩容的特点


二、Redis实战

1.JVM级别锁与分布式锁

在这里插入图片描述
在这里插入图片描述
单纯使用以上代码还有一个隐患 : 死锁
当某个请求运行到一半异常了。lockkey无法删除、这样永远判断都是true
所以我们修改为 try catch finally,无论如何都需要删除掉lockkey不能影响买票。

还有一种可能就是运行到一般宕机了、如果宕机请求也会终止,try catch依然不能解决问题。
因此我们的对应方案是设置一个过期时间
在这里插入图片描述
在上一个版本中,我们发现由于每条请求执行时间的不确定性,很有可能被redis自动删除影响到。
因此为了避免第二条线程删除掉第一条线程的lockkey,我们还需要设定一条UUID.randomUUID().toString,以此来限制其他请求不能互相删除。谁创建的key谁来自己删除
尽管如此还是存在bug,如下图中描述。虽然概率小、但是大型公司的用户还是有很大隐患
在这里插入图片描述

看到这里实际上还是会有一些并发BUG、看似逻辑完美但是还做不到万无一失,因此我们引入 Redisson分布式锁,它其中就包含了 "续命锁"

2.Redisson分布式锁

添加依赖
在这里插入图片描述
只需要这么一写,底层就能够自动替代我们刚才写的一对内容,并且更加完善。
在这里插入图片描述
redisson底层的逻辑。
在这里插入图片描述

(1).可重入锁的重入特性

class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...
   public void m() {
     lock.lock();  // block until condition holds
     try {
       n();
       // ... method body
     } finally {
       lock.unlock()
     }
   }

   public void n() {
     lock.lock();
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
}

在这个例子中,方法 m 获取了锁,然后调用了方法 n。由于 n 方法也试图获取相同的锁,如果这个锁不是可重入的,那么程序将会死锁。但由于 ReentrantLock 是可重入的,所以当线程已经持有锁时,它可以再次获取该锁而不会被阻塞。

(2).Lua脚本语法

Lua 是一种简单易学的脚本语言,它的语法类似于 C 语言。下面是一些常用的 Lua 命令和语法:

  • 变量:Lua 使用 local 关键字来声明局部变量,例如 local x = 1。如果不使用 local 关键字,变量将被视为全局变量。
  • 数据类型:Lua 支持多种数据类型,包括 nil、boolean、number、string、table、function、userdata 和 thread。
  • 控制结构:Lua 支持常见的控制结构,如 if、while、for 和 repeat。例如:
if x > 0 then
  print("x is positive")
elseif x < 0 then
  print("x is negative")
else
  print("x is zero")
end
  • 函数:Lua 使用 function 关键字来定义函数,例如:
function add(x, y)
  return x + y
end
  • 表(table):表是 Lua 中唯一的数据结构,它可以用来表示数组、集合和字典等数据类型。例如:
-- 创建一个空表
local t = {}

-- 添加元素
t["key"] = "value"

-- 访问元素
print(t["key"])  -- 输出 "value"

(3).Redisson高并发的问题所在

redisson底层通过重入锁和Lua脚本构成分布式锁,本质上其实就是串行化。串行化的本质其实又是与高并发相违背的。因此我们可能出现一种情况:

  目前大多数公司采用的都是哨兵、主从、集群等等形式。此时有一种可能,加入线程一拿到锁开始进行业务处理
  而恰好执行到一半的时候,主服务器宕机了。那么此时新竞选的主服务器就没有这个key。因为我们知道主从服务器中,都是写入主服务器,主服务器再同步信息给从服务器。此时线程二很快创建一个key到新主服务器。此时线程一与线程二就处于并行状态。还是有可能出现超卖之类的问题。

在这里插入图片描述

那么既然如此,我们就不用ZooKeeper不行了吗?又不是每个公司都用zookeeper。所以
我们还可以使用redis当中的redlock,它的逻辑是,首先拥有几台对等的、没有任何关联的服务器
没有主从关系、没有各种关系。当线程发送key的时候,要发给每一台服务器。必须接收到半数以上的反馈才能继续执行业务方面。

不过redlock也是存在争议的
千万不能为了高可用给redlock的服务器加从服务器。这样的话又回到老问题了。

(4).如何优化锁

尽量减少锁的粒度、尽量把没有高并发的代码放到锁外面去

还有一种优化思想称之为高并发分段式锁
假设我电商秒杀有 200 库存、我直接在redis中设置一个key。这样的话压力比较大
所以我在redis中分成10个key、每个key存放20个。这样在10条线程并行的情况下我都不需要加锁。
如果有20条线程,那我就加锁、也能提升我们的性能

3.Redis缓存问题

(1).缓存失效(击穿)

由于大批量缓存在同一时间失效可能导致大量请求同时穿透缓存直达数据库,可能会造成数据库瞬间压力过大甚至挂掉,对于这种情况我们在批量增加缓存时最好将这一批数据的缓存过期时间设置为一个时间段内的不同时间。

比方京东的商家客户端,一次性使用excel导入了十万商品信息。由于没有设置好缓存过期时间,所以所有信息统一都是24小时失效。24小时之后,缓存信息全部消失。此时有大量的请求访问这批商品信息,由于缓存没有,所以全部向数据库索取,导致数据库压力过大直接挂了。

如何避免击穿问题?
可以在设置缓存时间的时候,在24小时的基础上加一个随机时间(几分钟也可以几十秒也可以),我们不让这批商品在同一时间过期,那么有的访问就能够在缓存中找到。有的可以到数据库去找,以此来减轻压力和被击穿的可能。

(2).穿透

比方京东的有一个秒杀商品、此时非常多用户在等待。但是一位后端人员手抖把该商品给删掉了。
然而此时大量的并发请求进来,在缓存中找不到商品、在数据库中也找不到商品。等于请求把缓存与数据库都穿透了
虽然没有找到、但是大批量的请求还是发生了,数据库依然会去查询。这样也极大的影响了性能

(3).突发性热点导致数据库压力过大

抖音的直播带货,一上链接就有非常大的访问量。依然是靠一把分布式锁

在这里插入图片描述

(4).缓存数据库双写不一致

在这里插入图片描述
由此我们引入 读写锁
读锁可以同时让多条线程并行读取数据、但是一旦出现写锁,就必须等写锁线程更新完内容。读锁才能继续读取数据
在这里插入图片描述
但是!这个时间是不好预估的、具体还是看情况。比较没有哪个方案是十全十美,只能根据业务场景需求来决定

(5).缓存雪崩

比方一个几千万粉丝的博主发送一条微博,这条微博在发布时就被更新到redis了
紧接着几十万上百万的访问量进来查看这条微博,此时redis肯定是扛不住的,redis最多几万到十万的并发撑死了
就算是主从好多台也没用,因为一条微博就放在一台redis服务器。一台崩了之后可能导致整个redis缓存出现问题
大多数web系统又依赖于redis,最终接二连三的引起服务器的瘫痪。称之为缓存雪崩

由此引入 多级缓存(JVM缓存)
我们创建一个Map集合,在将数据存入redis的同时也存入Map集合。在查询的时候优先查询Map集合,
如果不为空就直接返回了。
那么JVM与redis有何不同呢?
由于分布式环境、我们使用有很多台机器的,每台机器都有JVM。可以把几百万条请求都负载均衡。
但是redis就不行了捏。

由此又引出两个问题

(6).JVM缓存如何在分布式环境同步信息、JVM缓存的溢出问题

①: 我们在做更新操作的时候同样需要更新缓存内容,redis更新、JVM缓存也同样需要更新,在分布式环境下要如何使得每一台机器的JVM缓存都得到更新呢?
②: 几万条信息都更新到JVM缓存中,我们知道JVM缓存存放在堆内存当中。内存又怎么能扛得住呢?所以导致的信息溢出问题该如何解决呢?

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值