C++(week15): C++提高:(五)Redis数据库

零、Redis的安装、API

1.redis、hiredis、redis-plus-plus安装

(1)redis安装

1.解压,make

tar zxvf redis--7.2.4.tar.gz
cd redis-7.2.4
make
sudo make install   #拷贝头文件和库文件到系统目录
sudo ldconfig       #更新库缓存

2.修改utils目录下的配置文件

cd utils
vim install_server.sh

将下面的内容进行注释 (大约在77行,有8行)
在这里插入图片描述

3.注释之后,保存退出并且执行下面命令

sudo ./install_server.sh

正确填写select的默认值

edward22@ed22-vm:~/cpp58/Redis/redis-7.2.4/utils$ sudo ./install_server.sh 
Welcome to the redis service installer
This script will help you easily set up a running redis server

Please select the redis port for this instance: [6379] 6379
Please select the redis config file name [/etc/redis/6379.conf] /etc/redis/6379.conf
Please select the redis log file name [/var/log/redis_6379.log] /var/log/redis_6379.log
Please select the data directory for this instance [/var/lib/redis/6379] /var/lib/redis/6379
Please select the redis executable path [/usr/local/bin/redis-server] /usr/local/bin/redis-server
Selected config:
Port           : 6379
Config file    : /etc/redis/6379.conf
Log file       : /var/log/redis_6379.log
Data dir       : /var/lib/redis/6379
Executable     : /usr/local/bin/redis-server
Cli Executable : /usr/local/bin/redis-cli
Is this ok? Then press ENTER to go on or Ctrl-C to abort.
Copied /tmp/6379.conf => /etc/init.d/redis_6379
Installing service...
Success!
Starting Redis server...
Installation successful!

(2)hiredis安装

git clone https://github.com/redis/hiredis.git
unzip hiredis-master.zip
cd hiredis-master
make
sudo make install
sudo ldconfig      #更新库缓存

(3)redis-plus-plus安装:cpp有自己的redis,就不需要hiredis了

git clone https://github.com/sewenew/redis-plus-plus.git
unzip redis-plus-plus-master.zip
cd redis-plus-plus
mkdir build
cd build
cmake ..
make
sudo make install
sudo ldconfig      #更新库缓存

(4)测试

//test.cpp

#include <sw/redis++/redis++.h>
#include <iostream>

using namespace std;
using namespace sw::redis;

int main()
{
    auto redis = Redis("tcp://127.0.0.1:6379");

    redis.set("key", "val");
    auto val = redis.get("key");   
    if (val) {
        std::cout << *val << std::endl;
    }
  
    std::vector<std::string> vec = {"a", "b", "c"};
    redis.rpush("list", vec.begin(), vec.end());
    redis.rpush("list", {"a", "b", "c"});

    vec.clear();
    redis.lrange("list", 0, -1, std::back_inserter(vec));
    
    return 0;
}

编译链接:

//如果版本是2204的,直接这样编译没有问题  但是版本比较老的可以带上-std=c++17
g++ test.cpp -lredis++ -lhiredis -lpthread

g++ test.cpp -lredis++ -lhiredis -lpthread -std=c++17

若打印 val,并且redis数据库中有 list (abcabc) 和 key(value) 两个数据,则证明安装成功

redis-cli   # 启动redis数据库
keys *      # 展示所有key
lrange list 0 -1  
get key

2.HiRedis的API

在这里插入图片描述

在这里插入图片描述



一、Redis数据库的基本概念

1.关系型数据库与非关系型数据库的区别

MySQL:属于关系型数据库的一种,存储数据的时候使用的是表结构。
Redis:属于非关系型数据库中的一种,存储数据的时候,使用键值对的形式。


2.非关系型数据库的分离

①基于键值对 key-value类型:Redis,memcached
②列存储数据库 Column-oriented Graph:HBase
③图形数据库 Graphs based:Neo4j
④文档型数据库: MongoDB
⑤MongoDB是一个基于分布式文件存储的数据库,主要用来处理大量的文档。


3.Redis的概念

Redis (Remote Dictionary Service),远程字典服务器。
Redis是用C语言编写的,开源的,高性能的数据结构存储系统。
它可以用作数据库、缓存和消息中间件。基于内存并支持持久化。
它基于内存运行并支持持久化的NoSQL数据库,是当前最热门的NoSQL数据库之一。

相关网站:
①redis中文官网:https://redis.com.cn/
②redis英文官网:https://redis.io/


4.Redis的特性

Redis与其他key-value 缓存产品有以下三个特点:
①Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
②Redis不仅仅支持简单的key-value类型的数据,同时还提供string,list,set,sort set,hash等数据结构的存储。
③Redis支持数据的备份,即master-slave模式的数据备份。也就是主从复制。


5.Redis的优点

①性能极高:Redies每秒可读11万次,每秒可写8.1万次。
②支持丰富的数据类型:Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
③原子性:Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
④丰富的特性:Redis还支持 发布(publish)、订阅(subscribe)、 通知、key过期 等等特性。 [类似观察者模式]


二、Redis常用命令

0.启动Redis数据库

redis-cli

1.切换数据库
Redis默认有16个数据库,MySQL默认有4个数据库。

SELECT 0   //0-15

2.设置key、查看key、删除key

set k1 100    //设置key
set k2 hello
set k33 666

get k1   //查看key

keys *   //查看key
keys k?

del k1  //删除key,大小写不敏感

3.查看数据库大小

dbsize

4.清空数据库

flushdb  //清空当前数据库

flushall //清空所有数据库

5.设置过期时间、查看过期时间

EXPIRE k2 20  //k2存在时间为20秒
ttl k2     //查看过期时间, -1代表永不过期, -2代表已过期。其他数字是剩余秒数

三、Redis的五大数据类型及其命令

Redis支持五种数据类型:string(字符串),list(列表),set(集合),sort set/zset(有序集合),hash(哈希)。

在这里插入图片描述

1.string (字符串)

1.设置与获取:set、get、mset、mget

//设置与获取单个
set key value
get key

//设置与获取多个
mset key1 value1 key2 value2 ...
mget key1 key2 ...

2.获取子串:getrange

getrange key 起始下标 终止下标
getrange k33 0 -1 //获取全部

3.设置子串(覆盖子串):setrange

setrange key 起始下标 新的子串

4.设置新值

getset key newvalue  //返回value,并将key值设为newvalue

5.设置过期时间

setx 

6.累加

INCR key  	       //值加1, key要求是整型,不能是字符串
INCRYBY key value  //值加value

2.list (列表)

1.首尾插入

lpush mylist 9 7 5 3 1 //逆序头插

rpush mylist 2 4 6 8 10 //顺序尾插

2.在某个元素的前面或者后面插入一个新元素

linsert mylist before 9 1000  #表明在mylist中,元素为9的前面插入一个值1000

3.遍历

lrange mylist 0 -1

4.在头部或者尾部进行删除

lpop mylist   //队头出队
rpop mylist	  //队尾出队

5.下标访问

lindex key index 	   #通过下标获取列表中的元素
lset key index value   #将下标为index的值设置为value

6.删除

lrem key count value  //从队头开始移除count个值为value的列表元素
ltrim key start stop  //对一个列表进行修剪(trim),仅让列表保留指定闭区间内的元素。区间外的元素全部删除

3.set (集合)

1.添加元素

sadd key value1 value2 ..  //自动去重

2.遍历元素

smembers key  //返回集合中的所有成员

3.获取集合的成员数

scard myset

4.删除集合中值为value的元素

SREM key value

5.在集合中随机选出num个元素

srandmember key num

6.随机删除 (举例:抽奖)

spop key [num]  //随机移除并返回集合中的1个/num个元素

7.取交集

sinter key1 key2

8.取并集

sunion key1 key2

4.sort set (zset) (有序集合)

为了将元素进行排序,可以给每个元素一个double类型的 分数 (score,即权重)
sort set的所有命令都是以z开头,所以又称为zset。

Redis的set是String类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
Redis中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。

1.插入

zadd key1 权重1 value1 [key2 权重2 value2 ...] 

2.遍历

zrange myset 0 -1 [withsocres]

5.hash (字典):hset、hget

Key-value模式不变,但value是一个键值对
map<key, map<key1, value>>

1.插入和获取
(1)一次插入一个

hset key field value    //插入一个
hset user Edward 2204   //举例

(2)获取一个

hget key field  //获取value
hget user Edward

(3)一次插入多个

hset key field value [field2 value2..]     //插入多个
hset user Edward 2204 Amber 2106 Sam 2406  //举例

(4)一次获取多个

hget key field1 field2 ... 
hget user Edward Amber Sam

2.遍历
(1)遍历所有的key

hkeys key
hkeys user  //举例

(2)遍历所有的value

hvals key
hvals user  //举例

四、Redis配置文件:启动和关闭redis服务器

Redis默认端口号:6379
MySQL默认端口号:3306

1.配置文件的路径

vim /etc/redis/6379.conf

2.关闭redis服务器

shutdown  然后crtl + d退出
也可以使用kill -9 杀死进程

3.启动Redis服务器:redis-server

sudo redis-server /etc/redis/6379.conf
sudo redis-server + 配置文件的路径

4.与运行中的Redis服务器进行交互的命令行工具

redis-cli

5.每次修改配置文件,都要重启服务器
在这里插入图片描述

6.其他
在这里插入图片描述
在这里插入图片描述


五、Redis持久化方式:单机备份问题

1.概念:
redis是内存数据库,而内存中的数据具有易失性。为了防止数据的丢失,需要将数据从内存保存到硬盘。下一次重启之后,就可以将数据从磁盘加载到内存中。

每次关闭Redis服务器后,如果没有启用持久化机制,内存中的数据确实会丢失。为了避免这种情况,可以使用Redis的持久化功能,即RDB(Redis Database)和AOF(Append Only File)。


2.两种形式
(1)RDB持久化
将当前数据保存到硬盘(原理是将Reids在内存中的数据库记录定时dump到磁盘上的RDB持久化)【默认持久化方式,快照

(2)AOF持久化
将每次执行的写命令保存到硬盘(原理是将Reids的操作日志以追加的方式写入文件,类似于MySQL的binlog)【主流持久化方式,日志

由于AOF持久化的实时性更好,即当进程意外退出时丢失的数据更少,因此AOF是目前主流的持久化方式,不过RDB持久化仍然有其用武之地。


1.RDB持久化

RDB(Redis Database)

1.基本概念
指定的时间间隔内,执行指定次数的写操作则会将内存中的数据写入到磁盘中。即在指定目录下生成一个快照文件 dump.rdb。Redis重启会通过加载dump.rdb文件恢复数据。(/var/lib/redis/6379)

快照dump.rdb文件所在的目录:

/var/lib/redis/6379

当Redis服务器关闭并重启后,数据仍然存在,这表明Redis的持久化功能已经正确配置并工作了。这是预期的行为。如果启用了AOF或RDB持久化机制,Redis在重启时会从持久化文件中恢复数据。


2.触发快照的方式
①执行shutdown命令,让服务器关闭,意义不大
②执行flushall命令,清空数据库,意义也不大
③手动执行save (bgsave) 触发快照
④在指定的时间间隔内,执行指定次数的写操作 (两个条件要同时满足)

save 3600 1 300 100 60 10000   //秒数、写操作次数为一对,两个条件要同时满足

3.优点
①适合大规模数据的恢复,恢复速率是比较
②如果对数据的完整性与一致性要求不高,可以使用RDB
③数据恢复的文件是二进制的

4.缺点
①对数据的完整性与一致性不高,有可能丢失更多的数据
②备份时占用内存,因为Redis 在备份时会独立创建一个fork子进程,将数据写入到一个临时文件(此时内存中的数据是原来的两倍哦),最后再将临时文件替换之前的备份文件。所以Redis 的持久化和数据的恢复要选择在夜深人静的时候执行是比较合理的。


5.验证RDB和AOF持久化是否已经关闭
(1)验证RDB:

redis-cli
127.0.0.1:6379> CONFIG GET save
1) "save"
2) ""

(2)验证AOF

redis-cli
127.0.0.1:6379> CONFIG GET appendonly
1) "appendonly"
2) "no"

在这里插入图片描述


2.AOF持久化

1.基本概念
AOF (Append Only File) 方式是将执行过的写指令记录下来,在数据恢复时按照从前到后的顺序再将指令都执行一遍。


2.开启/禁用 AOF方式
6379.conf 第1390行:追加写
在这里插入图片描述
开启AOF:appendonly yes
禁用AOF:appendonly no


3.写命令的日志文件所在的目录:

/var/lib/redis/6379/appendir?

4.重写
redis会把文件里的命令简化和等价。
在这里插入图片描述


5.AOF重写的条件:
(1)被动重写:两个条件同时满足

auto-aof-rewrite-percentage 100  //文件大小增长率超过100%
auto-aof-rewrite-min-size 64mb   //文件大小超过64mb

(2)主动重写:
主动触发重写的命令:BGREWRITEAOF命令
默认每1秒存储一次缓存中的写操作,最多丢失一秒的数据。


6.优点:
①恢复的数据的完整性与一致性高
②数据的丢失比较小


7.缺点:
①不适合大规模数据的恢复
②数据的恢复速率比较慢
③写命令的日志文件的大小比较大


8.两种模式的选择:
(1)要求数据恢复速率快,选RDB。要求数据恢复完整,用AOF
(2)两种持久化方式可以同时存在。
(3)单独使用任何一种持久化的方式都可以启动服务器
(4)如果仅使用AOF作为唯一的持久化方式 (关闭了RDB),且AOF文件损坏,则服务器无法启动

edward@ed-vm:~/Redis$ redis-cli
Could not connect to Redis at 127.0.0.1:6379: Connection refused //服务器未启动

可以修复aof文件:

sudo redis-check-aof --fix 文件名

在这里插入图片描述



六、Redis事务

1.概念:事务是一组命令的集合

1.Redis 事务 可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序的串行化执行,而不会被其他命令插入,不许加塞。


2.Redis事务有以下三个重要的保证:
(1)批量操作在发送 EXEC 命令前被放入队列缓存。
(2)收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
(3)在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。


3.一个事务从开始到执行会经历以下三个阶段:
①开始事务:MULTI
②命令入队:QUEUED
③执行事务:EXEC


2.事务命令

1.开启事务

MULTI / multi

2.执行事务

EXEC  /exec

3.取消事务,放弃执行事务块内的所有命令

DISCARD

4.监控键值:监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断

WATCH key [key ...]

5.取消监视:取消 WATCH 命令对所有 key 的监视

UNWATCH

3.事务执行

1.正常执行 MULTI+EXEC 分三步:开始,加入队列,执行
2.放弃事务 DISCARD 类似mysql中的rollback
3.事务执行中有错误的命令:部分执行成功or全部不执行?
如果输入命令后显示的是QUEUED,表示入队成功,部分执行。
如果提示ERROR,事务被放弃,全部不执行。
总结:编译时出错和运行时出错的区别。
4.WATCH监控 如果watch监控的keys发生了变化,EXEC执行的事务将被放弃
5.UNWATCH取消监控
6.一旦执行EXEC,WATCH监控会被取消。


4.事务特性

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

2.没有隔离级别的概念:
队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际的执行,也就是不存在“事务内的查询要看到事务里面的更新,在事务外查询不能看到 ”这个是让人万分头痛的问题。

3.Redis事务没有原子性
(1)Redis同一个事务中如果有一条命令执行失败(但语法本身没问题),其后的命令仍然会被执行,没有回滚。【类似运行时错误,如对string类型进行INCR】
(2)某一条命令本身的语法是错误的,会导致整个事务执行失败。
【类似编译错误】

在这里插入图片描述


5.乐观锁与悲观锁、CAS机制

1.悲观锁
每次去拿数据的时候都认为别人会修改,因此每次在取数据时都会上锁。


2.乐观锁
每次去拿数据都认为别人不会修改,所以不会上锁。
但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用 版本号机制CAS机制

在这里插入图片描述


3.CAS机制
(1)CAS (Check And Set,先检查再设置)
(2)拿预期值和原值进行比较,如果相等,就更新为新值。若不一致,则证明被别人修改了,则不能更新值。
(3)CAS操作方式:即 Compare And Swap,CAS是乐观锁技术,涉及到三个操作数,数据所在的内存值V,预期值A,新值B。当需要更新时,判断当前内存值V与之前取到的值A是否相等,若相等,则用新值更新,若失败则重试,一般情况下是一个自旋操作,即不断的重试。


4.乐观锁与悲观锁的优缺点
两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。



七、Redis主从复制:多机备份

Redis持久化解决单机备份,主从复制解决多机备份问题

持久化侧重解决的是Redis数据的单机备份问题(从内存到硬盘的备份);而主从复制则侧重解决数据的多机热备。此外,主从复制还可以实现负载均衡和故障恢复。

1.概念

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。

默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。

Redis的主从复制是单向的,MySQL的主从复制是双向的。


2.作用

主从复制的作用主要包括:
①数据冗余:
主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。

②故障恢复:
当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。

③负载均衡:
在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。

④高可用基石:
除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。


3.配置步骤

1.拷贝etc/redis/6379.conf拷贝两份,6380.conf、6381.conf

2.6380.conf、6381.conf文件中的port、pidfile、logfile、dbfilename、appendfilename、

port 6380
pidfile "/var/run/redis_6380.pid"
logfile "/var/log/redis_6380.log"
dbfilename "dump6380.rdb"
appendfilename "appendonly6380.aof"

3.分别启动三个配置文件(启动三台服务器)
先关闭已经连接的:shutdown

sudo redie-server /etc/redis/6379.conf
sudo redie-server /etc/redis/6380.conf
sudo redie-server /etc/redis/6381.conf

4.查看服务器状态:ps -elLf | grep redis

5.进入redis服务器

redis-cli  #进入默认的6379端口的redis服务器
redis-cli -p 6380
redis-cli -p 6381

6.查看配置状态

info replication  #在对应的客户端下执行该命令,发现每台服务器都是主机 role: master

7.配置主从复制

SLAVEOF 127.0.0.1 6379  #将该服务器作为6379的从机

在这里插入图片描述

8.测试,从机只读,不能写数据 (数据的同步复制是主机到从机)
在这里插入图片描述

9.从机shutdown后,再次重启从机,就会以主机身份开启

10.主机shutdown后,从机还是role:slave,但是master_link_status:up变为master_link_status:down。
但是若此时有写数据,则无法写入redis服务器。
然后重启主机,则恢复主从复制的关系。从机的master_link_status重新变为up,感知到主机上线。

在这里插入图片描述

在这里插入图片描述

11.常见问题

从机对主机数据的同步,是全量的还是增量的?
配置了主从复制之后,主机上已有的数据会更新到从机上(全量)
主机新增的数据也会更新到从机上(增量)
作为主机的程序结束,从机的角色不会变化(角色还是slave)。
主机重新启动,从机可以继续与主机保持连接(从机原地待命)。
从机的程序重启后,角色变成了master,从机每次与master断开连接后,都需要重新连接,除非把slaveof 配置到配置文件里。
只要重新连接master,一次完全同步(全量复制)将被自动执行。


4.常用模式

1.一主多从 (常见)
中央集权,所有的从服务器都要从主服务器哪里更新数据,对主服务器压力较大。

在这里插入图片描述


2.击鼓传花
只依赖前面的节点
在这里插入图片描述


3.反客为主,自己变为主机(主节点)

Slaveof  no one



八、哨兵模式 (sentinel)

为了弥补传统的redis主从复制,主机挂掉后,无法写入的问题。提出了哨兵模式。

Redis主从复制的作用有数据热备、负载均衡、故障恢复等;但主从复制存在的一个问题是故障恢复无法自动化。接下来要介绍的哨兵模式,它基于Redis主从复制,主要作用便是解决主节点故障恢复的自动化问题,进一步提高系统的高可用性。


1.基本概念

哨兵(sentinel)会监视(monitor)主机的状态,当主机断开之后,会执行流言协议(造谣)与投票协议(少数服从多数),将自己的票数投给从机,票高者为新的主机。
【流言协议:在网络结点中互传信息,类似流言的传播。会通知从机,主机是否已经挂掉。】


2.配置方法

1.在/etc/redis下面创建一个文件 sentinel.conf,并添加如下命令

sudo vim sentinel.conf
#哨兵      监视     主机名     主机ip   端口号 票数
sentinel monitor master6379 127.0.0.1 6379  1

2.启动哨兵模式 (启动哨兵的配置文件)

sudo redis-sentinel /etc/redis/sentinel.conf

3.哨兵启动后,会监视主机。当主机挂掉后,会将自己的票数随机投递给6380或6381从机。谁得到这一票,谁就会成为新的主机。



九、Redis常见问题

缓存机制,一般用redis作为cache。

缓存机制的应用,能够极大的提升程序的性能和效率,但是也会遇到一些问题。
一些典型的问题如下:

在这里插入图片描述

1.缓存雪崩

1.问题 (概念)
大量数据在同一时间失效了。本来可以在缓存中命中的数据,现在必须要在底层数据库上进行查找。就会导致底层数据库的压力较大,甚至可能将底层数据库压垮。

一般热点数据都会去做缓存,一般缓存都是定时任务去刷新,定时刷新就有一个问题:
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样缓存在失效的时候,原本访问缓存就能得到的数据,现在失效了,只能到数据库中取访问,这样会给后端系统(比如DB)带来很大压力。

总结:缓存中大量的数据在同一时间失效,此时相当于没有缓存,所有对数据的请求直接走到数据库,会带来很大压力。


2.原理图
大量缓存中的数据失效了,需要直接访问数据库。造成数据库压力。

大量的key值过期,导致客户端的访问必须到MySQL数据库中,增加数据库的负担

在这里插入图片描述


3.解决方案
(1)可以让数据不失效。 (但是这会让缓存越来越大)
(2)分散缓存失效的时间 (将key失效的时间分散)

缓存雪崩解决方法
缓存失效时的雪崩效应对底层系统的冲击非常可怕!大多数系统设计者考虑用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量。这是个治标不治本的方法!因此很少使用。

还有一个简单方案就是将缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

还有一种方法是不设置缓存的过期时间,有更新操作时就把热点的缓存全部更新,比如首页上的商品,当首页更新时,就把对应的数据替换掉。


2.缓存击穿

1.问题
某个热点数据,从缓存中失效了。则大量的访问同时请求这个数据,就会将查询下沉到底层数据库。此时数据库层的负载压力会骤增,这种现象称为”缓存击穿“。

缓存击穿:
缓存击穿跟缓存雪崩类似,但是又不一样。
缓存雪崩是大量的缓存失效,对这些数据的访问量全都转移到数据库上了。
而缓存击穿是一个key可能会在某些时间点被超高并发地访问,属于“热点”数据,在不停的扛着大并发的访问量,当这个热点数据在缓存中过期而失效的时候,大量的并发访问就会穿破缓存,转移到数据库上面,就像在缓存上开了一个洞,所以叫击穿。

总结:当热点数据key从缓存内失效时,大量访问同时请求这个数据,就会将查询下沉到数据库层,此时数据库层的负载压力会骤增,我们称这种现象为"缓存击穿"。


2.原理图
热点的key值过期,导致客户端的访问必须到MySQL数据库中,增加数据库的负担
在这里插入图片描述


3.解决 (缓存击穿解决方法):
(1)可以让热点数据不失效或者延迟过期时间
(2)让客户端互斥访问数据库,即对数据库加锁 (但用户体验差)

(1)延长热点key的过期时间或者设置永不过期,如排行榜,首页等;
(2)利用互斥锁保证同一时刻只有一个客户端可以查询底层数据库的这个数据,一旦查到数据就缓存至Redis内,避免其他大量请求同时穿过Redis访问底层数据库;


3.缓存穿透

1.问题
要访问的数据不存在,在缓存和数据库中都没有。那么多个客户端的请求会访问缓存,再访问数据库,也会导致底层数据库的压力较大。(比如黑客调用大量用户电脑,恶意攻击数据库)

缓存穿透:
假如用户要访问的数据并不存在(缓存和数据库中都没有),这样每次先到缓存中查找,再到数据库中查找,由于数据并不存在,也就无法将该数据写入到缓存中,那么每次对该数据的查询都会去查询一次数据库。如果用户频繁请求这样的数据,比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,会导致数据库压力过大,若黑客利用此漏洞进行攻击可能压垮数据库。

总结:要查询的数据不存在,缓存无法命中所以需要查询完数据库,但是数据是不存在的,此时数据库肯定会返回空,也就不会记录到缓存中,这样每次对该数据的查询都会穿过缓存去查询一次数据库。

缓存击穿和缓存穿透从名词上可能很难区分开来,它们的区别是:穿透表示底层数据库没有数据且缓存内也没有数据,击穿表示底层数据库有数据而缓存内没有数据。


2.原理图
数据在缓存中本身不存在,所以在Redis缓存中查不到,在数据库中也查不到,这样会导致数据库压力很大。
在这里插入图片描述


3.解决 (缓存穿透解决方法):
(1)构建空对象:
对于不存在的数据key,查不到key对应的value,则构建空对象,即使用键值对的形式<key,null>,直接返回空对象,不将无效的查询下沉到底层数据库。

(1)查询时做一些校验和过滤(权限校验,参数校验等等),判断这是一次正常的查询,还是异常的查询或者是攻击,如果是不合法的参数或者查询,直接返回

(2)缓存空对象,如果数据库中不存在这个数据,我们也在缓存中保存这个key,只是把val值记录为“不存在”,“空”这样的数据,下次再访问这个key时,就不会到数据库中做无用的查找了。

(3)我们可以预先将数据库里面所有的key全部存到一个大的map里面,然后在过滤器中过滤掉那些不存在的key。但是需要考虑数据库的key是会更新的,此时需要考虑数据库 --> map的更新频率问题。类似于位图。


在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员爱德华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值