1、为什么用redis?
主要是从:性能和并发两方面考虑
(1)性能:我们在碰到需要执行耗时特别久,且结果不频繁变动的SQL,就特别适合将运行结果放入缓存。这样,后面的请求就去缓存中读取,使得请求能够迅速响应。
(2)并发:在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常。这个时候,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问数据库。
2、redis为什么快?
(1)纯内存操作
(2)单线程操作,避免了频繁的上下文切换
(3)采用了非阻塞I/O多路复用机制
3、使用redis有什么缺点?
(1)缓存和数据库双写一致性问题
(2)缓存雪崩问题
(3)缓存击穿问题
(4)缓存的并发竞争问题
4、redis的数据类型
Redis 支持 5 种数据类型:string(字符串),hash(哈希),list(列表),set(集合),zset(sorted set:有序集合)
string:
string 是 redis 最基本的类型,一个 key 对应一个 value,string 类型的值最大能存储 512MB。
应用:
- (1)比如说记录每个用户博文的访问量
- (2)缓存用户的基本信息(数据源在 MySQL中),信息被序列化存放在value中
- (3)分布式id生成器(因为Redis单线程的特点,一次只执行一条指令,保证了id值的唯一。)
hash:
hash 是一个键值对(key - value)集合,特别适合存储对象。
应用:
- (1)记录网站每个用户个人主页的访问量
- (2)缓存用户信息
list:
list是一个简单的字符串列表,按照插入顺序排序。
应用:
- (1)时间轴
- (2)简易的消息队列(pop的时候要sleep,防止没消息一直pop)
set:
set 是字符串类型的无序集合,集合元素不能重复。
应用:
- (1)csdn的点赞状态
- (2)csdn发表博文的时候,给文章打标签,标签就可以存储在set集合中
- (3)set 结构可以⽤来存储活动中奖的⽤户 ID,因为有去重功能,可以保证同⼀个⽤户不会中奖两次
zset:
zset 和 set 一样都是 字符串类型元素的集合,并且集合内的元素不能重复。不同的是 zset 每个元素都会关联一个 double 类型的分数。redis 通过分数来为集合中的成员进行从小到大的排序。
zset 的元素是唯一的,但是分数(score)却可以重复。
它的内部是用叫跳表的数据结构来实现的
应用:
- (1)排行榜
- (2)存储学生的成绩
- (3)存储粉丝列表
5、跳跃表
增加了向前指针的链表叫作跳跃表。跳跃表是一个随机化的数据结构,实质就是一种可以进行二分查找的有序链表。跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找。跳表不仅能提高搜索性能,同时也可以提高插入和删除操作的性能。
插入元素:
(1)新节点和各层索引节点逐一比较,确定原链表的插入位置。O(logN)
(2)把索引插入到原链表。O(1)
(3)利用抛硬币的随机方式,决定新节点是否提升为上一级索引。结果为“正”则提升并继续抛硬币,结果为“负”则停止。O(logN)
总体上,跳跃表插入操作的时间复杂度是O(logN),而这种数据结构所占空间是2N,既空间复杂度是 O(N)。
删除元素:
(1)自上而下,查找第一次出现节点的索引,并逐层找到每一层对应的节点。O(logN)
(2)删除每一层查找到的节点,如果该层只剩下1个节点,删除整个一层(原链表除外)。O(logN)
总体上,跳跃表删除操作的时间复杂度是O(logN)。
跳跃表性质:
(1)跳跃表的每一层都是一条有序的链表.
(2) 跳跃表的查找次数近似于层数,时间复杂度为O(logn),插入、删除也为 O(logn)。
(3) 最底层的链表包含所有元素。
(4) 跳跃表是一种随机化的数据结构(通过抛硬币来决定层数)。
(5) 跳跃表的空间复杂度为 O(n)。
为什么用抛硬币方法?
(1)因为跳跃表删除和添加的节点是不可预测的,很难用一种有效的算法来保证跳表的索引分布始终均匀。
(2)随机抛硬币的方法虽然不能保证索引绝对均匀分布,却可以让大体趋于均匀。
6、发布订阅
Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。
Redis 客户端可以订阅任意数量的频道。
subcribe helloChat 在客户端创建并订阅了名称为helloChat的频道
publish helloChat 'hello world' 在另一个客户端向helloChat频道发送消息,订阅者就能收到消息
7、Redis 事务
Redis 事务可以一次执行多个命令, 它先以 MULTI 开始一个事务, 然后将多个命令入队到事务中, 最后由 EXEC 命令触发事务, 一并执行事务中的所有命令。
虽然单个 Redis 命令的执行是原子性的,但 Redis 事务的执行并不是原子性的。中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。
8、持久化
由于Redis的数据都存放在内存中,如果没有配置持久化,redis重启后数据就全丢失了,于是需要开启redis的持久化功能,将数据保存到磁盘上,当redis重启后,可以从磁盘中恢复数据。
Redis持久化分为RDB持久化和AOF持久化。
(1)RDB 持久化:快照形式是当需要做持久化时,Redis 会 fork 一个子进程,子进程将数据写到磁盘上一个临时 RDB 文件中。当子进程完成写临时文件后,新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件。
触发方式:
RDB有save、bgsave和自动化触发方式三种方式
- save命令:执行save命令会阻塞当前Redis服务器,直到RDB过程完成,对于内存比较大的实例会造成长时间阻塞,线上环境不建议使用。
- bgsave命令:Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责.阻塞只发生在fork阶段,一般时间很短。
(2)AOF 持久化: AOF持久化就是把每一个写命令都通过 write 函数追加到 appendonly.aof 文件中。
使用 AOF 持久化需要设置同步选项,从而确保写命令同步到磁盘文件上的时机。这是因为对文件进行写入并不会马上将内容同步到磁盘上,而是先存储到缓冲区,然后由操作系统决定什么时候同步到磁盘。
触发方式(选项同步频率):
always:每个写命令都同步。会严重减低服务器的性能
eyerysec:每秒同步一次。可以保证系统崩溃时只会丢失一秒左右的数据,并且 Redis 每秒执行一次同步对服务器几乎没有任何影响。
no:让操作系统来决定何时同步。选项并不能给服务器性能带来多大的提升,而且会增加系统崩溃时数据丢失的数量。
AOF重写
随着服务器写请求的增多,AOF 文件会越来越大,Redis会生成新的AOF文件替换旧的AOF文件,新的AOF文件没有冗余的写命令。注意:AOF重写并不是读取旧的AOF文件写命令,而是通过读取服务器当前数据库状态实现该功能的。
触发方式:
可以设置自动触发,或者执行BGREWRITEAOF命令手动触发。
Redis通过子进程后台重写会造成新命令在新旧AOF文件不同步问题,为解决这个问题,Redis增加了一个AOF重写缓存,这个缓存在fork出子进程之后开始启用。
9、redis 的数据过期策略?
- 惰性删除:key过期的时候不删除,每次从数据库获取key的时候去检查是否过期,若过期,则删除,返回null。
- 定期删除:每隔一段时间执行一次删除
redis 中数据过期策略是采用定期删除+惰性删除策略。如果还未删除就使用内存淘汰机制。
10、mysql里面有2000w条数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据
redis内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。
11、Redis的数据淘汰策略
(1)volatile-lru:从设置了过期时间的数据集中,移除最近最少使用的key
(2)volatile-random:从设置了过期时间的数据集中,随机移除一个key
(3)volatile-ttl:移除即将过期的key
(4)allkeys-lru:从所有key中移除最近最少使用的key
(5)allkeys-random:从所有key中随机移除一个key
(6)noeviction:不移除任何key,只是返回一个写错误 。默认选项,一般不会选用
记忆方式:3个volatile开头的,2个all_keys开头,最后加一个noeviction
LRU:最近最少使用 Least Recently Used
12、redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如何将它们全部找出来?
使用keys指令可以扫出指定模式的key列表。
如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?
redis是单线程的,keys命令会导致线程阻塞一段时间,这个时候可以使用scan指令,scan指令可以无阻塞的提取指定模式的key列表,但是会有一定的重复率,需要在客户端去重,但是整体话费时间比keys指令长。
13、如何保证redis和mysql的一致性?
缓存和数据库间的数据一致性问题:
(1)如果删除了缓存Redis,还没有来得及写库MySQL,另一个线程就来读取,发现缓存为空,则去数据库中读取数据写入缓存,此时缓存中为脏数据。
(2)如果先写了库,在删除缓存前,写库的线程宕机了,没有删除掉缓存,则也会出现数据不一致情况。
方案:
(1)采用延时双删策略
- 先删除缓存;
- 再写数据库;
- 休眠500毫秒(根据具体的业务时间来定);
- 再次删除缓存。
(2)异步更新缓存
14、什么是缓存雪崩,如何解决?
缓存雪崩:
- (1)Redis挂掉了,请求全部走数据库。
- (2)对缓存数据设置相同的过期时间,导致某段时间内缓存失效,请求全部走数据库。
解决办法:
- 针对缓存同时失效:给缓存的过期时间加上一个随机值,这样就会大幅度的减少缓存在同一时间过期。
- 对于“Redis挂掉了,请求全部走数据库”这种情况,我们可以有以下的思路:
(1)事发前:实现Redis的高可用(主从架构+Sentinel 或者Redis Cluster),尽量避免Redis挂掉这种情况发生
(2)事发中:万一Redis真的挂了,我们可以设置本地缓存(ehcache)+限流(hystrix),尽量避免我们的数据库被干掉
(3)事发后:redis持久化,重启后自动从磁盘上加载数据,快速恢复缓存数据
15、什么是缓存穿透,如何解决?
缓存穿透:指查询一个一定不存在的数据,导致每次请求都要到数据库去查询,失去了缓存的意义。
(1)提前拦截
(2)把null设置到缓存里面去
(3)给空对象设置一个较短的过期时间。
16、分布式锁?
分布式锁本质上要实现的⽬标就是在 Redis ⾥⾯占⼀个“位置”,当别的进程也要来占时,发现“位置”被占了,就只好放弃或者稍后再试。
占位置⼀般是使⽤ setnx(set if not exists) 指令,只允许被⼀个客户端占据。⽤完了,再调⽤ del 指令释放位置。
如果逻辑执⾏到中间出现异常了,可能会导致 del指令没有被调⽤,这样就会陷⼊死锁,锁永远得不到释放。于是我们在拿到锁之后,再给锁加上⼀个过期时间。
但如果在 setnx 和 expire 之间服务器进程突然挂掉了,可能是因为机器掉电或者是被⼈为杀掉的,就会导致expire 得不到执⾏,也会造成死锁。
setex 是一个原子(atomic)操作, 它可以在同一时间内完成设置值和设置过期时间这两个操作
上面就是分布式锁的基本思想。但是在真正投入使用的时候,还会面临一个常见的问题:超时问题
Redis 的分布式锁不能解决超时问题,如果在加锁和释放锁之间的逻辑执⾏的时间太⻓,超出了锁的超时限制,就会出现问题。这时候第⼀个线程持有的锁过期了,临界区的逻辑没有执⾏完,而第⼆个线程就提前重新持有了这把锁,导致临界区代码不能严格地串⾏执⾏。
为了避免这个问题,Redis 分布式锁不要⽤于较⻓时间的任务。
17、redis做异步队列
一般用list结构作为队列,rpush生产消息,lpop消费消息,当lpop没有消息的时候,要适当sleep一会再重试。
16、说说redis哈希槽的概念?
redis集群没有使用一致性hash,而是引入了哈希槽的概念,redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。
redis集群最大节点数?
16384个
一个字符串类型的值能存储最大容量是多少?
512M
18、是否使用过redis集群,集群的原理是什么?
redis sentinal着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。
redis cluster着眼于扩展性,在单个redis内存不足时,使用cluster进行分片存储。
解决了上面提到的两个问题。