提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
作者最近学习了redis,故写作一篇博客加强记忆,如有错误之处,敬请指出
一、Redis简介
1.Redis是什么
Redis (官网)全称 Remote Dictionary Server(即远程字典服务),它是一个基于内存实现的键值型非关系(NoSQL)数据库
2.Redis可以用来干什么
Redis 用来缓存一些经常被访问的热点数据、或者需要耗费大量资源的内容,通过把这些内容放到 Redis 中,可以让应用程序快速地读取它们。例如,网站的首页需要经常被访问,并且在创建首页的过程中会消耗的较多的资源,此时就可以使用 Redis 将整个首页缓存起来,从而降低网站的压力,减少页面访问的延迟时间。
1.Redis最常用来做缓存,是实现分布式缓存的首先中间件;
2.Redis可以作为数据库,实现诸如点赞、关注、排行等对性能要求极高的互联网需求;
3.Redis可以作为计算工具,能用很小的代价,统计诸如PV/UV、用户在线天数等数据;
4.Redis还有很多其他的使用场景,例如:可以实现分布式锁,可以作为消息队列使用。
3.Redis是单线程的,为什么还能这么快?
1.对服务端程序来说,线程切换和锁通常是性能杀手,而单线程避免了线程切换和竞争所产生的消耗;
2.Redis的大部分操作是在内存上完成的,这是它实现高性能的一个重要原因;
3.Redis采用了IO多路复用机制,使其在网络IO操作中能并发处理大量的客户端请求,实现高吞吐率。
二、key和Redis五大数据类型
1.key
1)key的类型
key 的类型对应着 value 的类型,同样也有五种(string、list、hash、set、zset)。如果 key 指向的是一个字符串类型的值,那么 key 的类型就是字符串。我们可以通过TYPE命令来查看 key 的类型
2)key过期时间
redis的默认过期时间是-1,也就是不过期;我们可以使用expire命令来设置key的过期时间
Redis支持如下两种过期策略:
惰性删除:客户端访问一个key的时候,Redis会先检查它的过期时间,如果发现过期就立刻删除这个key。
定期删除:Redis会将设置了过期时间的key放到一个独立的字典中,并对该字典进行每秒10次的过期扫描,
过期扫描不会遍历字典中所有的key,而是采用了一种简单的贪心策略。该策略的删除逻辑如下:
1.从过期字典中随机选择20个key;
2.删除这20个key中已过期的key;
3.如果已过期key的比例超过25%,则重复步骤1。
3)key的主要命令
keys #*查看当前库所有key (匹配:keys *1)
exists key #判断某个key是否存在
type key #查看你的key是什么类型
del key #删除指定的key数据
unlink key #根据value选择非阻塞删除
#仅将keys从keyspace元数据中删除,真正的删除会在后续异步操作。
expire key 10 #10秒钟:为给定的key设置过期时间
ttl key #查看还有多少秒过期,-1表示永不过期,-2表示已过期
select #命令切换数据库
dbsize #查看当前数据库的key的数量
flushdb #清空当前库
flushall #通杀全部库
2.字符串(String)
1)常用命令
set <key> <value> [EX seconds|PX milliseconds] [NX|XX] #添加键值对
#其中[]内代表可选参数,其含义如下所示:
#EX seconds:设置指定的过期时间,以秒为单位;
#PX milliseconds:设置指定的过期时间,以毫秒为单位;
#NX:先判断 key 是否存在,如果 key 不存在,则设置 key 与 value;
#XX:先判断 key 是否存在,如果 key 存在,则重新设置 value。
get <key> #查询对应键值
append <key><value> #将给定的<value> #追加到原值的末尾
strlen <key> #获得值的长度
setnx <key><value> #只有在key不存在时设置 key 的值
incr <key> #将key中储存的数字值增1 只能对数字值操作,如果为空,新增值为1
decr <key> #将key中储存的数字值减1 只能对数字值操作,如果为空,新增值为-1
incrby/decrby <key><步长> #将 key 中储存的数字值增减。自定义步长。
3.列表(List)
1)介绍
Redis list(列表)相当于 Java 语言中的 LinkedList 结构,是一个链表而非数组,其插入、删除元素的时间复杂度为 O(1),但是查询速度欠佳,时间复杂度为 O(n)。
Redis 列表的底层存储结构,其实是一个被称为快速链表(quicklist)的结构。当列表中存储的元素较少时,Redis 会使用一块连续的内存来存储这些元素,这个连续的结构被称为 ziplist(压缩列表),它将所有的元素紧挨着一起存储。
而当数据量较大时,Redis 列表就会是用 quicklist(快速链表)存储元素。Redis 之所以采用两种方法相结合的方式来存储元素。这是因为单独使用普通链表存储元素时,所需的空间较大,会造成存储空间的浪费。因此采用了链表和压缩列表相结合的方式,也就是 quicklist + ziplist
2)常用命令
lpush/rpush <key><value1><value2><value3> .... #从左边/右边插入一个或多个值。
lpop/rpop <key> #从左边/右边吐出一个值。值在键在,值光键亡。
rpoplpush <key1><key2>从<key1> #列表右边吐出一个值,插到<key2>列表左边。
lrange <key><start><stop> #按照索引下标获得元素(从左到右)
lrange mylist 0 -1 #0左边第一个,-1右边第一个,(0-1表示获取所有)
lindex <key><index> #按照索引下标获得元素(从左到右)
llen <key> #获得列表长度
linsert <key> before <value><newvalue> #在<value>的后面插入<newvalue>插入值
lrem <key><n><value> #从左边删除n个value(从左到右)
lset<key><index><value> #将列表key下标为index的值替换成value
4.集合(Set)
1)介绍
Redis set (集合)遵循无序排列的规则,集合中的每一个成员(也就是元素,叫法不同而已)都是字符串类型,并且不可重复。Redis set 是通过哈希映射表实现的(它底层其实是一个value为null的hash表),所以它的添加、删除、查找操作的时间复杂度为 O(1)。集合中最多可容纳 2^32 - 1 个成员(40 多亿个)
2)常用命令
sadd <key><value1><value2> ..... #将一个或多个 member 元素加入到集合 key 中,已经存在的 member 元素将被忽略
smembers <key> #取出该集合的所有值。
sismember <key><value> #判断集合<key>是否为含有该<value>值,有1,没有0
scard<key> #返回该集合的元素个数。
srem <key><value1><value2> .... #删除集合中的某个元素。
spop <key> #随机从该集合中吐出一个值。
srandmember <key><n> #随机从该集合中取出n个值。不会从集合中删除 。
smove <source><destination> #value把集合中一个值从一个集合移动到另一个集合
sinter <key1><key2> #返回两个集合的交集元素。
sunion <key1><key2> #返回两个集合的并集元素。
sdiff <key1><key2> #返回两个集合的差集元素(key1中的,不包含key2中的)
5.哈希(Hash)
1)介绍
Redis hash(哈希散列)是由字符类型的 field(字段)和 value 组成的哈希映射表结构(也称散列表),它非常类似于表格结构。在 hash 类型中,field 与 value 一一对应,且不允许重复。
2)常用命令
hset <key><field><value> #给<key>集合中的 <field>键赋值<value>
hget <key1><field> #从<key1>集合<field>取出 value
hmset <key1><field1><value1><field2><value2>... #批量设置hash的值
hexists<key1><field> #查看哈希表 key 中,给定域 field 是否存在。
hkeys <key> #列出该hash集合的所有field
hvals <key> #列出该hash集合的所有value
hincrby <key><field><increment> #为哈希表 key 中的域 field 的值加上增量 1 -1
hsetnx <key><field><value> #将哈希表 key 中的域 field 的值设置为 value ,当且仅当域 field 不存在 .
6.有序集合(Zset)
1)介绍
zset(有序集合)中的成员是有序排列的,它和 set 集合的相同之处在于,集合中的每一个成员都是字符串类型,并且不允许重复;而它们最大区别是,有序集合是有序的,set 是无序的,这是因为有序集合中每个成员都会关联一个 double(双精度浮点数)类型的 score (分数值),Redis 正是通过 score 实现了对集合成员的排序。
zset 是 Redis 常用数据类型之一,它适用于排行榜类型的业务场景,比如 QQ 音乐排行榜、用户贡献榜等。在音乐排行榜单中,我们可以将歌曲的点击次数作为 score 值,把歌曲的名字作为 value 值,通过对 score 排序就可以得出歌曲“热度榜单”。
2)常用命令
zadd <key><score1><value1><score2><value2>… #将一个或多个 member 元素及其 score 值加入到有序集 key 当中。
z**加粗样式**range <key><start><stop> [WITHSCORES] #返回有序集 key 中,下标在<start><stop>之间的元素带WITHSCORES,可以让分数一起和值返回到结果集。
zrangebyscore key minmax [withscores] [limit offset count] #返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列。
zrevrangebyscore key maxmin [withscores] [limit offset count] #同上,改为从大到小排列。
zincrby <key><increment><value> #为元素的score加上增量
zrem <key><value> #删除该集合下,指定值的元素
zcount <key><min><max> #统计该集合,分数区间内的元素个数
zrank <key><value> #返回该值在集合中的排名,从0开始。
三、Redis事务
1.Redis事务是什么?
Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。执行 Redis 事务可分为三个阶段:
1.开始事务
2.命令入队
3.执行事务
2.Redis事务的特性
1) 单独的隔离操作
事务中的所有命令都会被序列化,它们将按照顺序执行,并且在执行过的程中,不会被其他客户端发送来的命令打断。
2) 不保证原子性
在 Redis 的事务中,如果存在命令执行失败的情况,那么其他命令依然会被执行,不支持事务回滚机制。
3.Redis事务命令
multi #开启事务
exec #执行事务
discard #放弃事务
watch key [key ...] #在开启事务之前用来监视一个或多个key 。如果事务执行时这些 key 被改动过,那么事务将被打断。
unwatch #取消watch命令对 key 的监控。
关于watch命令:
很多时候,要确保事务中的数据没有被其他客户端修改才执行该事务。Redis提供了watch命令来解决这类问题,这是一种乐观锁的机制。客户端通过watch命令,要求服务器对一个或多个key进行监视,如果在客户端执行事务之前,这些key发生了变化,则服务器将拒绝执行客户端提交的事务,并向它返回一个空值。
如果在执行 WATCH 命令之后,EXEC 命令或DISCARD 命令先被执行了的话,那么就不需要再执行UNWATCH 了。
例如:在执行事务exec前在另一个会话中修改name的值会导致事务提交失败
这里引出两个概念:悲观锁和乐观锁
悲观锁:
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
乐观锁:
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。
四、Redis持久化
1.Redis持久化是什么?
Redis 是一款基于内存的非关系型数据库,它会将数据全部存储在内存中。但是如果 Redis 服务器出现某些意外情况,比如宕机或者断电等,那么内存中的数据就会全部丢失。因此必须有一种机制能够保证 Redis 储存的数据不会因故障而丢失,这就是 Redis 的数据持久化机制。
数据的持久化存储是 Redis 的重要特性之一,它能够将内存中的数据保存到本地磁盘中,实现对数据的持久存储。这样即使在服务器发生故障之后,也能通过本地磁盘对数据进行恢复。
Redis 提供了两种持久化机制:第一种是 RDB,又称快照(snapshot)模式,第二种是 AOF 日志,也就追加模式。
2.RDB
1)RDB是什么?
RDB(Redis Database)是Redis默认采用的持久化方式,它以快照的形式将进程数据持久化到硬盘中。RDB会创建一个经过压缩的二进制文件,文件以“.rdb”结尾,内部存储了各个数据库的键值对数据等信息。RDB持久化的触发方式有两种:
手动触发:通过SAVE或BGSAVE命令触发RDB持久化操作,创建“.rdb”文件;
自动触发:通过配置选项,让服务器在满足指定条件时自动执行BGSAVE命令。
自动触发策略,是指 Redis 在指定的时间内,数据发生了多少次变化时,会自动执行BGSAVE命令。自动触发的条件包含在了 Redis 的配置文件中,如下所示:
下图所示, save m n 的含义是在时间 m 秒内,如果 Redis 数据至少发生了 n 次变化,那么就自动执行BGSAVE命令。配置策略说明如下:
save 900 1 表示在 900 秒内,至少更新了 1 条数据,Redis 自动触发 BGSAVE 命令,将数据保存到硬盘。
只要上述三个条件任意满足一个,服务器就会自动执行BGSAVE命令。
注意:每次创建 RDB 文件之后,Redis 服务器为实现自动持久化而设置的时间计数和次数计数就会被清零,并重新开始计数,因此多个策略的效果不会叠加。
1)BGSAVE命令原理
BGSAVE命令是非阻塞式的,所谓非阻塞式,指的是在该命令执行的过程中,并不影响 Redis 服务器处理客户端的其他请求。这是因为 Redis 服务器会 fork() 一个子进程来进行持久化操作(比如创建新的 dunp.rdb 文件),而父进程则继续处理客户端请求。当子进程处理完后会向父进程发送一个信号,通知它已经处理完毕。此时,父进程会用新的 dump.rdb 文件覆盖掉原来的旧文件。
2)RDB优劣势
在 RDB 持久化的过程中,子进程会把 Redis 的所有数据都保存到新建的 dump.rdb 文件中,这是一个既消耗资源又浪费时间的操作。因此 Redis 服务器不能过于频繁地创建 rdb 文件,否则会严重影响服务器的性能。
RDB 持久化的最大不足之处在于,最后一次持久化的数据可能会出现丢失的情况。我们可以这样理解,在 持久化进行过程中,服务器突然宕机了,这时存储的数据可能并不完整,比如子进程已经生成了 rdb 文件,但是主进程还没来得及用它覆盖掉原来的旧 rdb 文件,这样就把最后一次持久化的数据丢失了。
RDB 数据持久化适合于大规模的数据恢复,并且还原速度快,如果对数据的完整性不是特别敏感(可能存在最后一次丢失的情况),那么 RDB 持久化方式非常合适。
3.AOF
1)AOF是什么
AOF 被称为追加模式,或日志模式,是 Redis 提供的另一种持久化策略,它能够存储 Redis 服务器已经执行过的的命令,并且只记录对内存有过修改的命令,这种数据记录方法,被叫做“增量复制”,其默认存储文件为appendonly.aof。
AOF 机制默认处于未开启状态,可以通过修改 Redis 配置文件开启 AOF:
#修改配置文件,把no改为 yes
appendonly yes
2)AOF的写入机制
为了提高程序的写入性能,现代操作系统会把针对硬盘的多次写操作优化为一次写操作。
当程序调用write对文件写入时,系统不会直接把数据写入硬盘,而是先将数据写入内存的缓冲区中;
当达到特定的时间周期或缓冲区写满时,系统才会执行flush操作,将缓冲区中的数据冲洗至硬盘中;
1.这种优化机制虽然提高了性能,但也给程序的写入操作带来了不确定性。
2.对于AOF这样的持久化功能来说,冲洗机制将直接影响AOF持久化的安全性;
3.为了消除上述机制的不确定性,Redis向用户提供了appendfsync选项,来控制系统冲洗AOF的频率;
Linux的glibc提供了fsync函数,可以将指定文件强制从缓冲区刷到硬盘,上述选项正是基于此函数。
appendfsync选项的取值和含义如下:
2)AOF的重写机制
Redis 在长期运行的过程中,aof 文件会越变越长。如果机器宕机重启,“重演”整个 aof 文件会非常耗时,导致长时间 Redis 无法对外提供服务。因此就需要对 aof 文件做一下“瘦身”运动。
为了让 aof 文件的大小控制在合理的范围内,Redis 提供了 AOF 重写机制,手动执行BGREWRITEAOF命令,开始重写 aof 文件,如下所示:
127.0.0.1:6379> BGREWRITEAOF
Background append only file rewriting started
通过上述操作后,服务器会生成一个新的 aof 文件,该文件具有以下特点:
新的 aof 文件记录的数据库数据和原 aof 文件记录的数据库数据完全一致;
新的 aof 文件会使用尽可能少的命令来记录数据库数据,因此新的 aof 文件的体积会小很多;
AOF 重写期间,服务器不会被阻塞,它可以正常处理客户端发送的命令。
下表对原有 aof 文件和新生成的 aof 文件做了对比,如下所示:
4.AOF和RDB对比
五、Redis主从复制
1.Redis主从复制是什么
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点。Master以写为主,Slave 以读为主。
主要作用:
①数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
②故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
③负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
④高可用(集群)基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
如图 所示,Redis 主机会一直将自己的数据复制给 Redis 从机,从而实现主从同步。在这个过程中,只有 master 主机可执行写命令,其他 salve 从机只能只能执行读命令,这种读写分离的模式可以大大减轻 Redis 主机的数据读取压力,从而提高了Redis 的效率,并同时提供了多个数据备份。主从模式是搭建 Redis Cluster 集群最简单的一种方式。
2.设置主从复制
为了方便演示,我们只从一台机器上搭建主从模式。
首先我们启动三台redis服务
在从库中使用slaveof 命令
之后用info replication查看信息,可以看到已经变成了从机(role:slave)
同理,查看主机:主机下有两台从机
3.主从模式不足
主从模式并不完美,它也存在许多不足之处,下面做了简单地总结:
1) Redis 主从模式不具备自动容错和恢复功能,如果主节点宕机,Redis 集群将无法工作,此时需要人为干预,将从节点提升为主节点。
2) 如果主机宕机前有一部分数据未能及时同步到从机,即使切换主机后也会造成数据不一致的问题,从而降低了系统的可用性。
3) 因为只有一个主节点,所以其写入能力和存储能力都受到一定程度地限制。
4) 在进行数据全量同步时,若同步的数据量较大可能会造卡顿的现象。
六、哨兵模式
1.哨兵模式是什么
在 Redis 主从复制模式中,因为系统不具备自动恢复的功能,所以当主服务器(master)宕机后,需要手动把一台从服务器(slave)切换为主服务器。在这个过程中,不仅需要人为干预,而且还会造成一段时间内服务器处于不可用状态,同时数据安全性也得不到保障,因此主从模式的可用性较低,不适用于线上生产环境。
Redis 官方推荐一种高可用方案,也就是 Redis Sentinel 哨兵模式,它弥补了主从模式的不足。Sentinel 通过监控的方式获取主机的工作状态是否正常,当主机发生故障时, Sentinel 会自动进行 Failover(即故障转移),并将其监控的从机提升主服务器(master),从而保证了系统的高可用性。
2.哨兵模式特征
哨兵节点包含如下的特征:
1.哨兵节点会定期监控数据节点,其他哨兵节点是否可达;
2.哨兵节点会将故障转移的结果通知给应用方;
3.哨兵节点可以将从节点晋升为主节点,并维护后续正确的主从关系;
4.哨兵模式下,客户端连接的是哨兵节点集合,从中获取主节点信息;
5.节点的故障判断是由多个哨兵节点共同完成的,可有效地防止误判;
6.哨兵节点集合是由多个哨兵节点组成的,即使个别哨兵节点不可用,整个集合依然是健壮的;
7.哨兵节点也是独立的Redis节点,是特殊的Redis节点,它们不存储数据,只支持部分命令。
在上图过程中,哨兵主要有两个重要作用:
第一:哨兵节点会以每秒一次的频率对每个 Redis 节点发送PING命令,并通过 Redis 节点的回复来判断其运行状态。
第二:当哨兵监测到主服务器发生故障时,会自动在从节点中选择一台将机器,并其提升为主服务器,然后使用 PubSub 发布订阅模式,通知其他的从节点,修改配置文件,跟随新的主服务器。
七、Redis集群
1.Redis集群是什么
Redis 集群实现了对Redis的水平扩容,即启动N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N。Redis 集群通过分区(partition)来提供一定程度的可用性(availability): 即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求。
2.所需环境
Redis集群至少需要3个节点,因为投票容错机制要求超过半数节点认为某个节点挂了该节点才是挂了,所以2个节点无法构成集群。
要保证集群的高可用,需要每个节点都有从节点,也就是备份节点,所以Redis集群至少需要6台服务器。因为我没有那么多服务器,也启动不了那么多虚拟机,所在这里搭建的是伪分布式集群,即一台服务器虚拟运行6个redis实例
安装ruby
3.搭建集群
可以打开以下链接
搭建集群
八、Redis分布式锁
1.分布式锁是什么
在分布式系统中,当不同进程或线程一起访问共享资源时,会造成资源争抢,如果不加以控制的话,就会引发程序错乱。此时使用分布式锁能够非常有效的解决这个问题,它采用了一种互斥机制来防止线程或进程间相互干扰,从而保证了数据的一致性。
2.分布式锁的特点
Redis 分布式锁主要有以下特点:
第一:互斥性是分布式锁的重要特点,在任意时刻,只有一个线程能够持有锁;
第二:锁的超时时间,一个线程在持锁期间挂掉了而没主动释放锁,此时通过超时时间来保证该线程在超时后可以释放锁,这样其他线程才可以继续获取锁;
第三:加锁和解锁必须是由同一个线程来设置;
第四:Redis 是缓存型数据库,拥有很高的性能,因此加锁和释放锁开销较小,并且能够很轻易地实现分布式锁。
注意:一个线程代表一个客户端。
3.Redis分布式锁命令
我们主要可以使用SETNX命令来完成分布式锁的需求
第一版:
setnx key value
问题:setnx刚好获取到锁,业务逻辑出现异常,导致锁无法释放
解决:设置过期时间,自动释放锁
第二版:给锁增加了过期时间,避免出现死锁
1.setnx key value 2.expire key seconds
问题:这两个命令不是原子的,第二步可能会失败,依然无法避免死锁问题
解决:将两个命令合到一块
第三版:通过“set…nx…”命令,将加锁、过期命令编排到一起,它们是原子操作了,可以避免死锁。
问题:如下图。进程A在任务没有执行完毕时,锁已经到期被释放了。等进程A的任务执行结束后,它依然会尝试释放锁,因为它的代码逻辑就是任务结束后释放锁。但是,它的锁早已自动释放过了,它此时释放的可能是其他线程的锁
set key value nx ex seconds
第四版:在加锁时就要给锁设置一个标识,例如使用uuid
set key uuid nx ex seconds
问题:删除操作缺乏原子性
1.进程A进入,拿到属于自己uuid的锁key:uuid1
2.A执行业务逻辑
3.在执行完后,进入if语句里面,在下图语句执行前,锁过期了
4.此时进程B在此瞬间,因为没有锁了,它拿到了锁,如下图
5.进程A执行delete语句,删除锁;此时删的就是别的进程的锁
第5版:这就需要采用Lua脚本,通过Lua脚本将两个命令编排在一起,而整个Lua脚本的执行是原子的
九、Redis缓存问题
1.问题描述
在实际的业务场景中,Redis 一般和其他数据库搭配使用,用来减轻后端数据库的压力,比如和关系型数据库 MySQL 配合使用。
Redis 会把 MySQL 中经常被查询的数据缓存起来,比如热点数据,这样当用户来访问的时候,就不需要到 MySQL 中去查询了,而是直接获取 Redis 中的缓存数据,从而降低了后端数据库的读取压力。如果说用户查询的数据 Redis 没有,此时用户的查询请求就会转到 MySQL 数据库,当 MySQL 将数据返回给客户端时,同时会将数据缓存到 Redis 中,这样用户再次读取时,就可以直接从 Redis 中获取数据。流程图如下所示:
在使用 Redis 作为缓存数据库的过程中,有时也会遇到一些棘手问题,比如常见缓存穿透、缓存击穿和缓存雪崩等问题
2.缓存穿透
1)问题描述
key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会压到数据源,从而可能压垮数据源。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。
2)解决方案
(1) 对空值缓存:如果一个查询返回的数据为空(不管是数据是否不存在),我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过五分钟
(2) 设置可访问的名单(白名单):
使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。
(3) 采用布隆过滤器:(布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。
布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。)
将所有可能存在的数据哈希到一个足够大的bitmaps中,一个一定不存在的数据会被 这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力。
(4) 进行实时监控:当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务
2.缓存击穿
1)问题描述
key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮
2)解决方案
(1)永不过期:热点数据不设置过期时间,所以不会出现上述问题,这是“物理”上的永不过期。或者为每个数据设置逻辑过期时间,当发现该数据逻辑过期时,使用单独的线程重建缓存。
(2)加互斥锁:对数据的访问加互斥锁,当一个线程访问该数据时,其他线程只能等待。这个线程访问过后,缓存中的数据将被重建,届时其他线程就可以直接从缓存中取值。
3.缓存雪崩
1)问题描述
在某一时刻,缓存层无法继续提供服务,导致所有的请求直达存储层,造成数据库宕机。可能是缓存中有大量数据同时过期,也可能是Redis节点发生故障,导致大量请求无法得到处理。
2)解决方案
(1)避免数据同时过期:设置过期时间时,附加一个随机数,避免大量的key同时过期。
(2)启用降级和熔断措施:在发生雪崩时,若应用访问的不是核心数据,则直接返回预定义信息/空值/错误信息。或者在发生雪崩时,对于访问缓存接口的请求,客户端并不会把请求发给Redis,而是直接返回。
(3)构建高可用的Redis服务:采用哨兵或集群模式,部署多个Redis实例,个别节点宕机,依然可以保持服务的整体可用。
总结
但愿吉祥