redis
1. redis为什么适合做缓存?而不使用mysql等数据库,特点
- 执行速度快 查询快、写入快
- 数据结构丰富
- 支持持久化、支持集群、支持主从
相比来说:
mysql关系性数据库查询慢(redis keyvalue结构)
mysql更新慢,为维护数据一致性付出巨大代价
原因如下:存储方式(导致维护困难)、查询方式(SQL结构化语言,采用索引,redis通过nosql更精简,无sql的解析)、事务、性能(redis数据都存储再内存中)
2.redis为什么这么快
- 单线程(存取是单线程的)不需要进行上下文的切换,cpu利用率高 (cpu一般不是瓶颈,往往是 网络带宽和机器的内存大小。单线程编程容易实现 )
- 多路复用io机制
- 数据结构接单,节省时间
- 运行在内存中
- C语言编写,操作系统相关,速度快
3. 什么样的数据适合做缓存
判断数据是否适合缓存到Redis中,可以从几个方面考虑:会经常查询么?命中率如何?写操作多么?数据大小?
4. redis 数据结构
- String [int raw emstr]
- set集合
- Zset有序集合
- hash 散列表
- List 列表
5. String 底层数据结构
-
int整形
-
embstr
redisobject和对应的sds(简单字符串)一同分配;适合短字符串
-
raw
字符串对象使用sds 两用两次分配内存
6. List对象
- ziplist
- linkedList
7. Hash对象
- ziplist
- hashtable
8. Zet对象
- intset 实际上就是个数组
- hashtable
9. Zset有序集合 底层数据结构
- ziplist
- skiplist
10. 使用场景
-
缓存(读多写少、命中率高、数据量少)
-
分布式锁
-
排行榜
-
计数器(点赞、评论)
-
秒杀
-
简单消息队列(以及延迟队列)
-
session
-
好友关系(交集并集差集sinter sunion sdiff)
11. redis 缓存一致性问题
场景:当对一条数据进行了修改,但是缓存中的数据不会自动同步更新。出现数据不一致
在这里,我们讨论三种更新策略:
- 先更新数据库,再更新缓存
- 先删除缓存,再更新数据库
- 先更新数据库,再删除缓存
-
先更新数据库,再更新缓存
这种策略是不能使用的
原因一:线程问题 (多个线程更新不一定谁更新的快,如果先更新库的最后更新的缓存就会出现问题)
原因二:业务问题(写多读少,浪费时间)
-
先删除缓存,再更新数据库
考虑这样一种场景:更新数据m->m’
A线程 B线程 淘汰缓存中数据m 获取数据m,缓存中不存在 从mysql中获取数据m 更新数据库中数据m->m’ 设置缓存为m (两个线程的最后这一步无论什么顺序,都会出现缓存不一致)
这时缓存中数据是m,但是实际数据是m’,因此存在缓存不一致问题
可考虑升级为延时双删
在更新数据库之后间隔一定时间再次删除在这段时间内的一个脏数据。
那为什么要延时进行删除呢?直接进行删除不行吗?
考虑这种场景
A线程 B线程 淘汰缓存中数据m 获取数据m,缓存中不存在 从mysql中获取数据m 更新数据库中数据m->m’ 淘汰缓存中数据 设置缓存为m 最后我们发现如果我们不延时进行删除仍然会产生问题(几率很小,具体看下面)。但是延时再删除呢?
实际上延时删除只是尽可能的使得线程B最后一步发生在线程A进行删除之前,这是一种折中策略。特殊情况下仍然会发生缓存不一致。至于延时的时间我们可以根据我们的业务进行调整
比如如果我们正常的业务只需要50ms就能处理完,那我们就将其设为100ms,但是如果我们的mysql是读写分离架构呢,那就在此基础上再增加一个数据同步的时间。
这是我们不得不面对另外一个问题:延时需要时间,吞吐量就会下降,这是我们就可以通过异步删除来解决吞吐量低下问题
-
先更新数据库,再删缓存
实际上这种情况也会出现问题,同样出现脏数据。但是几率很小
出现脏数据的条件在于:读数据据库的速度比更新数据库的速度慢。 可是,大家想想,数据库的读操作的速度远快于写操作的 。
如何必须解决问题?
首先,**给缓存设有效时间是一种方案。**其次,采用给出的异步延时删除策略,保证读请求完成以后,再进行删除操作。
除此之外还有一个问题,就是第二个和第三个策略可能出现的问题,如果最后删除缓存失败了该怎么办呢?
**方案一:使用消息队列 **
(1)更新数据库数据;
(2)缓存因为种种问题删除失败
(3)将需要删除的key发送至消息队列
(4)自己消费消息,获得需要删除的key
(5)继续重试删除操作,直到成功
不使用消息队列也可以另起一个线程隔段时间进行删除
缺点:对业务代码出现大量的代码入侵
方案二:使用数据库 中间件
比如在mysql中存在binlog日志,我们可以另起一套程序通过中间件如canal,订阅binlog日志,专门用来删除缓存,如果再删除失败再像方案一一样发送到消息队列自消费或者另起一个线程循环删除直到删除成功
12. 数据淘汰机制
-
当内存不足时
volatile-lru 从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
volatile-ttl 从已设置过期时间的数据集中挑选将要过期的数据淘汰
volatile-random从已设置过期时间的数据集中任意选择数据淘汰
allkeys-lru从所有数据集中挑选最近最少使用的数据淘汰
allkeys-random从所有数据集中任意选择数据进行淘汰
noeviction禁止驱逐数据 -
正常情况下过期数据的一个淘汰
- 定时删除:设置一个定时器,定时删除,对CPU不友好
- 惰性删除:浪费内存
- 定期删除:定期扫描过期键,淘汰已过期键值对
当前使用的惰性删除和定期删除
13. redis持久化
-
RDB 持久化
将某个时间点的所有数据都存放到硬盘上。
可以将快照复制到其它服务器从而创建具有相同数据的服务器副本。缺点:
-
如果系统发生故障,将会丢失最后一次创建快照之后的数据。
-
如果数据量很大,保存快照的时间会很长。
-
-
AOF 持久化
将写命令添加到 AOF 文件(Append Only File)的末尾。
使用 AOF 持久化需要设置同步选项,从而确保写命令同步到磁盘文件上的时机。这是因为对文件进行写入并不会马上将内容同步到磁盘上,而是先存储到缓冲区,然后由操作系统决定什么时候同步到磁盘。
有以下同步选项:
选项同步频率always每个写命令都同步everysec每秒同步一次no让操作系统来决定何时同步
- always 选项会严重减低服务器的性能;
- everysec 选项比较合适,可以保证系统崩溃时只会丢失一秒左右的数据,并且 Redis 每秒执行一次同步对服务器性能几乎没有任何影响;
- no 选项并不能给服务器性能带来多大的提升,而且也会增加系统崩溃时数据丢失的数量
随着服务器写请求的增多,AOF 文件会越来越大。Redis 提供了一种将 AOF 重写的特性,能够去除 AOF 文件中的冗余写命令。
14. 缓存穿透,缓存雪崩以及缓存击穿吧
缓存穿透:就是客户持续向服务器发起对不存在服务器中数据的请求。客户先在Redis中查询,查询不到后去数据库中查询。
缓存击穿:就是一个很热门的数据,突然失效,大量请求到服务器数据库中
缓存雪崩:就是大量数据同一时间失效。
- 缓存穿透:
- 1.接口层增加校验,对传参进行个校验,比如说我们的id是从1开始的,那么id<=0的直接拦截;
- 2.缓存中取不到的数据,在数据库中也没有取到,这时可以将key-value对写为key-null,这样可以防止攻击用户反复用同一个id暴力攻击
- 缓存击穿:
最好的办法就是设置热点数据永不过期,拿到刚才的比方里,那就是你买腾讯一个永久会员- 缓存雪崩:
1.缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
2.如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。**