缓存之Redis(总结篇)

一、什么是Redis

Redis是一种用C语言开发的数据库,Redis的数据是存在内存中的,也叫内存数据库,读写速度非常快,常常用来做缓存。

  • Redis支持丰富的数据类型,除了k/v类型的数据,还支持list、set、hash、zset
  • Redis支持数据的持久化,可以将内存中的数据存在磁盘中,重新加载的时候再读取出来。
  • Redis有灾难恢复机制,因为其支持持久化。
  • Redis在内存快使用完之后,将内存中不用的数据存到磁盘中。
  • Redis支持集群模式。
  • Redis是单线程的IO多路复用模型。(Redis6.0后引入了多线程)
  • Redis支持发布订阅模型、lua脚本、事务等功能。
  • Redis混合使用惰性删除定期删除策略来删除过期的数据。

二、缓存数据的处理流程

在这里插入图片描述
用户请求数据时,先判断缓存中是否存在,存在则直接返回数据,不存在,在数据库中查找,数据库中存在,则更新缓存数据,并返回该数据,不存在,则返回空数据。

三、为什么要使用缓存/Redis

主要有两点:高性能和高并发。

  • 高性能
    用户第一次访问数据时,是从磁盘中读取的,速度比较慢,若这些数据是用户高频率访问的数据,则可以将这些数据放在缓存中,下次用户直接从缓存中获取,操作缓存就是操作内存,速度是非常快的。
  • 高并发
    一般像MySQL这种数据库的QPS(服务器每秒可以执行的查询次数)差不多是1w左右(4核8g),但是RedisQPS可以轻松达到10w左右。最高能达到30w(单机Redis,集群会更高)。
    直接操作缓存能够承受的数据库请求量是远远高于直接操作数据库的。所有可以将一部分数据放到缓存中,这样可以提高系统的并发量。

四、Redis数据结构分析

1、string

string数据结构是简单的k/v类型。常用命令如下

127.0.0.1:6379> set key value #设置 key-value 类型的值
OK
127.0.0.1:6379> get key # 根据 key 获得对应的 value
"value"
127.0.0.1:6379> exists key  # 判断某个 key 是否存在
(integer) 1
127.0.0.1:6379> strlen key # 返回 key 所储存的字符串值的长度。
(integer) 5
127.0.0.1:6379> del key # 删除某个 key 对应的值
(integer) 1
127.0.0.1:6379> get key
(nil)
127.0.0.1:6379> mset key1 value1 key2 value2 # 批量设置 key-value 类型的值
OK
127.0.0.1:6379> mget key1 key2 # 批量获取多个 key 对应的 value
1) "value1"
2) "value2"
127.0.0.1:6379> set number 1
OK
127.0.0.1:6379> incr number # 将 key 中储存的数字值增一
(integer) 2
127.0.0.1:6379> get number
"2"
127.0.0.1:6379> decr number # 将 key 中储存的数字值减一
(integer) 1
127.0.0.1:6379> get number
"1"
127.0.0.1:6379> expire key  60 # 数据在 60s 后过期
(integer) 1
127.0.0.1:6379> setex key 60 value # 数据在 60s 后过期 (setex:[set] + [ex]pire)
OK
127.0.0.1:6379> ttl key # 查看数据还有多久过期
(integer) 56
2、list

list是链表,Redis实现的list是双向链表,支持反向查询和遍历。
常用命令如下:

127.0.0.1:6379> rpush myList value1 # 向 list 的头部(右边)添加元素
(integer) 1
127.0.0.1:6379> rpush myList value2 value3 # 向list的头部(最右边)添加多个元素
(integer) 3
127.0.0.1:6379> lpop myList # 将 list的尾部(最左边)元素取出
"value1"
127.0.0.1:6379> lrange myList 0 1 # 查看对应下标的list列表, 0 为 start,1为 end
1) "value2"
2) "value3"
127.0.0.1:6379> lrange myList 0 -1 # 查看列表中的所有元素,-1表示倒数第一
1) "value2"
2) "value3"
127.0.0.1:6379> rpush myList2 value1 value2 value3
(integer) 3
127.0.0.1:6379> rpop myList2 # 将 list的头部(最右边)元素取出
"value3"
127.0.0.1:6379> llen myList # 查看链表长度
(integer) 3
3、hash

hash是一种string类型的filedvalue的映射表,类似于Java8之前的HashMap,内部实现是数组和链表。适合于用于存储对象。

127.0.0.1:6379> hset userInfoKey name "guide" description "dev" age "24"
OK
127.0.0.1:6379> hexists userInfoKey name # 查看 key 对应的 value中指定的字段是否存在。
(integer) 1
127.0.0.1:6379> hget userInfoKey name # 获取存储在哈希表中指定字段的值。
"guide"
127.0.0.1:6379> hget userInfoKey age
"24"
127.0.0.1:6379> hgetall userInfoKey # 获取在哈希表中指定 key 的所有字段和值
1) "name"
2) "guide"
3) "description"
4) "dev"
5) "age"
6) "24"
127.0.0.1:6379> hkeys userInfoKey # 获取 key 列表
1) "name"
2) "description"
3) "age"
127.0.0.1:6379> hvals userInfoKey # 获取 value 列表
1) "guide"
2) "dev"
3) "24"
127.0.0.1:6379> hset userInfoKey name "GuideGeGe" # 修改某个字段对应的值
127.0.0.1:6379> hget userInfoKey name
"GuideGeGe"
4、set

set类似于Java中的HashSet,是一种无序不重复的集合。同时set提供了一个判断某个值是否存在于集合的接口。set还可以实现交集、并集、差集的操作。

127.0.0.1:6379> sadd mySet value1 value2 # 添加元素进去
(integer) 2
127.0.0.1:6379> sadd mySet value1 # 不允许有重复元素
(integer) 0
127.0.0.1:6379> smembers mySet # 查看 set 中所有的元素
1) "value1"
2) "value2"
127.0.0.1:6379> scard mySet # 查看 set 的长度
(integer) 2
127.0.0.1:6379> sismember mySet value1 # 检查某个元素是否存在set 中,只能接收单个元素
(integer) 1
127.0.0.1:6379> sadd mySet2 value2 value3
(integer) 2
127.0.0.1:6379> sinterstore mySet3 mySet mySet2 # 获取 mySet 和 mySet2 的交集并存放在 mySet3 中
(integer) 1
127.0.0.1:6379> smembers mySet3
1) "value2"
5、sorted set

set相比,sorted set提供了一个权重参数score,让集合中的元素可以按照score来排序。

127.0.0.1:6379> zadd myZset 3.0 value1 # 添加元素到 sorted set 中 3.0 为权重
(integer) 1
127.0.0.1:6379> zadd myZset 2.0 value2 1.0 value3 # 一次添加多个元素
(integer) 2
127.0.0.1:6379> zcard myZset # 查看 sorted set 中的元素数量
(integer) 3
127.0.0.1:6379> zscore myZset value1 # 查看某个 value 的权重
"3"
127.0.0.1:6379> zrange  myZset 0 -1 # 顺序输出某个范围区间的元素,0 -1 表示输出所有元素
1) "value3"
2) "value2"
3) "value1"
127.0.0.1:6379> zrange  myZset 0 1 # 顺序输出某个范围区间的元素,0 为 start  1 为 stop
1) "value3"
2) "value2"
127.0.0.1:6379> zrevrange  myZset 0 1 # 逆序输出某个范围区间的元素,0 为 start  1 为 stop
1) "value1"
2) "value2"
6、bitmap

bitmap存储的是连续的二进制数字(0 和 1),通过bitmap, 只需要一个bit位来表示某个元素对应的值或者状态,key就是对应元素本身 。

# SETBIT 会返回之前位的值(默认是 0)这里会生成 7 个位
127.0.0.1:6379> setbit mykey 7 1
(integer) 0
127.0.0.1:6379> setbit mykey 7 0
(integer) 1
127.0.0.1:6379> getbit mykey 7
(integer) 0
127.0.0.1:6379> setbit mykey 6 1
(integer) 0
127.0.0.1:6379> setbit mykey 8 1
(integer) 0
# 通过 bitcount 统计被被设置为 1 的位的数量。
127.0.0.1:6379> bitcount mykey
(integer) 2

五、Redis缓存数据过期时间的设置

1、为什么要设置过期时间

因为内存空间是有限的,如果缓存中的数据是一直保存的话,会导致out of menory
其次是业务场景需要,比如短信登陆验证码,是1分钟内有效的,就需要设置过期时间为1分钟。就不用我们自己在业务代码中来出来过期问题。

127.0.0.1:6379> exp key  60 # 数据在 60s 后过期
(integer) 1
127.0.0.1:6379> setex key 60 value # 数据在 60s 后过期 (setex:[set] + [ex]pire)
OK
127.0.0.1:6379> ttl key # 查看数据还有多久过期
(integer) 56

其中string类型的数据有自己独特的setex方法来设置过期时间,其他类型的数据都需要通过expire来设置过期时间。

2、Redis是怎么判断数据过期的

Redis是通过过期字典来判断的(可以看作hash表),过期字典中的key值指向Redis数据库中某一个key值(键),过期字典中的value则是存储的long型数据,记录的是所指向key(键)的过期时间。过期字典是存储在redisDb中。

3、过期数据的删除策略

假如你给一个key设置的过期时间是1分钟,那么1分钟后,Redis是怎么处理这个数据的了?

  • 惰性删除
    只有在读取key的时候才对key进行过期检查删除。对cpu友好,但是会导致有过多的过期数据没有删除。
  • 定期删除
    每隔一段时间抽取一批key来执行过期key删除操作。Redis会限制删除操作的时长和频率来减少对cpu的影响。
    Redis采用的策略是定期删除+惰性删除
    但是仅仅给key设置过期时间还是不够的,即使采用了定期删除+惰性删除策略,还是可能会有一些数据没有被删除掉,导致out of memory。为了解决这个问题,Redis使用了内存淘汰机制
4、内存淘汰机制

(1)olatile-lru(least recently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
(2)volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
(3)volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
(4)allkeys-lru(least recently used):当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)
(5)allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
(6)no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。
(7)volatile-lfu(least frequently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰。
(8)allkeys-lfu(least frequently used):当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key

六、Redis持久化机制

持久化就是将数据从内存中写入到磁盘数据库中的,主要是为了重用数据(如重启服务,Redis挂了等)。
Redis实现持久化有两种操作方式:快照(RDB)只追加文件(AOF)

1、快照持久化RDB

Redis可以通过创建快照来存储内存中的数据在某个时间节点的副本。该方式是Redis默认的持久化机制,Reids.conf中默认如下配置:

save 900 1           #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令创建快照。

save 300 10          #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照。

save 60 10000        #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
2、AOF持久化

于快照持久化相比,AOF的实时性更好,目前是主流的持久化方案。通过appendonly参数开启。

appendonly yes

开启AOF持久化后每执行一条会更改Redis中数据的命令,Redis都会将该命令写入到磁盘中的AOF文件中。
Redis配置文件中存在三种不同的AOF持久化方案:

appendfsync always    #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
appendfsync everysec  #每秒钟同步一次,显示地将多个写命令同步到硬盘
appendfsync no        #让操作系统决定何时进行同步

比较常用的是everysec。兼顾了数据和写入性能,用户最多会损失1秒中之内产生的数据。

七、Redis事务

Redis通过MULTI、EXEC、DISCARD、WATCH等命令来实现事务功能。

> MULTI
OK
> INCR foo
QUEUED
> INCR bar
QUEUED
> EXEC
1) (integer) 1
2) (integer) 1

使用MULTI来开启事务,Redis会将接下来的命令放入队列中,当调用EXEC命令后会执行队列中的命令。调用DISCARD命令会清除队列中的命令。
一般事务具有四大特性:原子性、隔离性、持久性、一致性
但是Redis是不支持回滚的,所有Redis事务不满足原子性(也不满足持久性)。

八、Redis缓存穿透

缓存穿透就是有大量的请求key不在缓存中,直接请求到了数据库,相当于越过了缓存,直接访问数据库服务。
有两种解决方法:

1、缓存无效key

将缓存和数据库中都查不到的key设置一个空值存在缓存中,并设置过期时间。这种方式是针对请求的key变化不频繁的时候。若大量请求的key值都不一样,则会导致大量无效的空值key存在缓存中。

2、使用布隆过滤器

什么是布隆过滤器:布隆过滤器
把所有可能存在的请求的值存放在布隆过滤器中,当用户请求时,先判断该请求是否存在于布隆过滤器,若不存在,则直接返回错误,则接着向下走缓存的流程。

九、Redis缓存雪崩

缓存雪崩是指缓存在同一时间大面积的失效,导致大量的请求直接交给数据库处理。

  • 一种情况是Redis服务宕机了,导致所有的请求直接走数据库服务。
    (1)可以采用Redis集群,避免单机Redis宕机而影响整个缓存服务。
    (2)限流,避免同时处理大量的请求。
  • 另一种情况是大量的热点数据在同一时间失效了(过期了),导致大量的请求访问数据库。
    (1)给热点数据设置不同的失效时间。
    (2)缓存永不失效。

十、Redis缓存和数据库一致性问题

如何保证缓存中的数据和数据库的数据一致了?
目前有三种常用的缓存读写策略

1、旁路缓存模式(Cache Aside Pattern)

旁路缓存模式是比较常用的一种缓存读写模式,适用于大量读请求的业务。
旁路缓存模式中服务端需要同时维护数据库和缓存,并且以数据库中的数据为准。


  • (1)直接更新数据库。
    (2)删除对应缓存。

  • (1)先读缓存,缓存中存在则直接返回数据。
    (2)缓存不存在,则读取数据库返回值。
    (3)将值更新到缓存中。
2、读写穿透模型(Read/Write Through Pattern)

读写穿透模型是以缓存中的数据为主,从中读取数据和写入数据,缓存服务之后再将数据同步到数据库。


  • (1)先查缓存,缓存中不存在,则直接更新数据库。
    (2)缓存中存在,则更新缓存,之后缓存服务自己将数据同步到数据库。

  • (1)从缓存中读取,存在的话直接返回。
    (2)不存在,从数据库中加载,更新到缓存中后返回值。
3、异步缓存写入模型(Write Behind Pattern)

异步缓存写入模型和读写穿透模型相似,都是由缓存服务来负责缓存和数据库的读写。不过异步缓存写入模型只更新缓存,数据库在之后由缓存服务异步批量执行更新。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值