1. Redis原理
为什么Redis是单线程,但却很快?
- 单线程,避免了线程之间的竞争
- 数据都在内存中,内存比硬盘快
- 使用了IO多路复用模型,将连接信息和时间放到了队列中,由时间分派器分配运行结果,类似NIO中的Seletor
2. Redis中的数据类型
- key-String:Redis中字符串长度动态可变;数据结构内部实现类似于ArrayList,采用预分配冗余内存空间的方式来避免内存的频繁分配
- 可缓存结构体信息,可以存放字符,也可以存放jbp图片
- 虽然客户端是多线程的,但是redis内部是单线程也就是线程安全的,可以用incr或者decr来实现计数的功能
- key-Hash:redis的hash相当于HashMap,内部实现和HashMap一致,数据+链表的结构
- 保存结构体信息:hash字典类型也是比较适合保存结构体信息的e,不同于字符串一次序列化整个对象,hash可以存储用户结构中单个字段
- key-List:redis的列表的数据结构与Java中的LinkedList类似,前后插入或者删除数据非常快,但是随即定位数据可能会较慢
- list列表结构常用来做异步队列使用;将需要延后处理的任务结构体序列化成字符串后放入Redis的列表;另一个线程从Redis中获取这个列表的数据进行处理
- list列表可用于秒杀抢购场景:在商品秒杀中最怕的就是商品超卖,通常的解决方式是将库存商品存放到类似MQ的队列中,购买请求都是从队列中取,取完了就是卖完了;但是MQ的处理较为重量级,所以我们可以将商品信息存放到Redis的list队列中,因为pop的操作是原子性的,取完了再取就会抛出异常
- key-Set:redis的set相当于jave中的HashSet,内部的键是无序唯一的
- 用在一些用来去重的场景:例如一个用户只能参加一次活动、一个用户只能中奖一次等等
- key-Zset:Zset类似于java中的SortedSet和HashMap的结合体,一方面它可以保证每个元素的唯一性、另一方面可以给每个value都设置一个score代表这个元素的排序权重
- 各类热门排序场景:例如热门歌曲榜单列表,value值是歌曲ID,score是歌曲播放次数,这样即可对歌曲进行排序
还有类似微博粉丝列表、热门评论列表,value是粉丝ID、评论ID,score是关注时间、点赞数量
3. Redis持久化机制
推荐以下两种方式同时使用,在服务器关闭后建议使用AOF文件来恢复数据
3.1 RDB-Redis DataBase
RDB是redis默认的持久化机制,无需手动开启;RDB持久化文件是一个二进制文件,存储的是内存当中的数据,回复的时候很快
- RDB持久化时机:
- 配置文件当中进行配置
- 关闭服务端的时候,如果没有开启AOF持久化就会默认进行一次RDB持久化
- 主从同步
- 优缺点:
- 优点:RDB持久化文件较小,适合定时备份;重启的时候加载RDB文件很快
- 缺点:可能会丢失部分数据;fork子进程保存数据的时候可能会阻塞服务
3.2 AOF-append only file
默认关闭,需要手动开启;AOF文件是一个文本文件,存放的是命令
- 持久化时机:在配置文件中配置,可选项:每个写操作保存一次、每秒一次、由操作系统决定
- 优缺点:
- 优点:不会阻塞进程、安全,不容易丢失数据
- 缺点:文件很大、恢复的速度较慢、吃服务器的IO性能
4. Redis中淘汰策略
Redis内存满的时候,如果继续往里放数据,则执行淘汰策略;淘汰策略在配置文件中进行配置:maxmemory-policy 具体策略,设置Redis的最大内存:maxmemory 字节大小
- volatile-lru:在内存不足时,Redis会在设置过了生存时间的key中干掉一个最近最少使用的key。
- allkeys-lru:在内存不足时,Redis会再全部的key中干掉一个最近最少使用的key。
- volatile-lfu:在内存不足时,Redis会再设置过了生存时间的key中干掉一个最近最少频次使用的key。
- allkeys-lfu:在内存不足时,Redis会再全部的key中干掉一个最近最少频次使用的key。
- volatile-random:在内存不足时,Redis会再设置过了生存时间的key中随机干掉一个。
- allkeys-random:在内存不足时,Redis会再全部的key中随机干掉一个。
- volatile-ttl:在内存不足时,Redis会在设置过了生存时间的key中干掉一个剩余生存时间最少的key。
- noeviction:(默认)在内存不足时,直接报错。
5. Redis集群方式
5.1 主从模式
单个主机的Redis是有上限的,为了提高性能可以使用主从模式
- 主节点可以读,可以写
- 从节点只能读
- 主从节点数据保持一致,自动将主节点的数据同步到从节点
- 系统运行时如果主节点宕机,可以在从节点上执行命令切换为主节点
缺点
- 主节点宕机后,需要手动切换;且在主节点宕机期间无法进行写操作
- 如果多个从节点宕机,同时重启后由于同步会导致主节点IO暴增,可能宕机
5.2 哨兵模式
为了解决主从手动切换导致服务不可用的问题,可以使用哨兵模式
在主从节点的模式上加了哨兵机制;主节点宕机后从节点自动投票选出主节点;为了选举通常配备奇数的哨兵
缺点
- 每台机器上的数据是一致的,浪费内存
- 难以支持在线扩容,管理复杂
5.3 集群模式
主从解决了读写压力大的问题,哨兵解决了主从手动切换的问题;如果并发量很高且数据量大,可以使用集群模式
集群模式:至少6台服务器,三主三从,但是从节点不干活,只监视对应的主节点和备份数据;如果主节点挂了,就立刻顶上去;如果单个主节点压力过大,可以针对该节点搭建主从或哨兵
6. Redis事务、Redis锁以及实现分布式锁、ZK锁,都有哪写分布式锁?
6.1 Redis事务
MULTI:开启事务
EXEC:执行命令串
DISCARD:取消执行
WATCH:监视key
UNWATCH:取消监视key
redis事务,就是将一系列命令加入到一个队列中,当执行exec的时候按照加入队列的顺序执行命令,当命令有误的时候,分为以下两种情况:
- 语法错误(编译器错误),此时会回滚
- redis类型错误(运行时错误),此时不会回滚,会跳过错误的命令继续向下执行
为什么Redis不支持事务回滚?因为多数事务失败是由于语法错误或者是数据类型错误导致的,语法错误时命令入队时就进行检测的,而类型错误是在命令执行的时候检测的;上述两种类型的错误是在上线之前就应该被发现的,Redis采用这种简单的事务来提高性能
watch和unwatch:
- 可以使用watch来监视一个或者多个key:开启监视后,若在事务执行之前修改了key的值,那么事务将会直接失败,完全进行回滚
- unwatch:取消监视
6.2 Redis实现分布式锁
有两种方案,一种是基于Redis命令;一种是基于Redis Lua脚本
基于Redis命令:
- 加锁:执行setnx,若执行成功再设置超时时间;
- 解锁:执行delete命令
- 优点:实现简单,较为轻便、性能较高
- 缺点:setnx和expire分两步执行,非原子操作,可能会造成死锁;不支持阻塞等待,不可重入
基于redis Lua脚本
- 加锁:执行SET lock_name random_value EX seconds NX 命令
- 解锁:执行脚本
- 优点:逻辑严谨
- 缺点:不支持阻塞等待、不可重入
上面的只是思想,实际项目中我们可以使用封装好的框架,例如Redisson
6.3 其他分布式锁
Zookeeper
7. 布隆过滤器
布隆过滤器可以用来解决Redis中缓存穿透的问题;它实际上是一种算法,主要用于判断一个元素是否在集合中
如果直接来一波冷数据,可能会造成缓存崩掉,这个时候可以用**布隆过滤器当作缓存的索引,**只有在布隆过滤器中才会查询缓存,查不到则穿透到DB;如果不在布隆过滤器中,直接返回
8. 其他缓存问题
8.1 缓存穿透
如果用户查询一些数据库中不存在的数据,那么先去redis中查询,redis中没有,又去数据库,数据库也没有,返回null,且没有将结果放入redis中,导致查询每次都走数据库;也有可能是用户恶意请求进行攻击,这就是缓存穿透
解决办法
- 在redis中存入null
- 使用布隆过滤器
8.2 缓存击穿
redis中某个热点数据因为设置了过期时间,导致在一瞬间失效,大量请求走了数据库,导致数据库压力增加,这就是缓存穿透
解决办法
- 热点数据永不过期
- 使用分布式锁,让请求一个一个进入数据库;
具体实现:请求过来后,先在redis中查,能查到就直接返回并将结果写入Redis,查不到就加分布式锁,限制请求进入数据库,拿到结果后,同样写入Redis并解锁
8.3 缓存雪崩
比缓存击穿更严重,大量热点数据同时失效,导致DB宕机
解决办法
- 热点数据永不过期
- 设置过期时间时采用随机数,避免同时失效
8.4 缓存倾斜
该问题主要针对Redis集群
某个key存放的是一个普通的数据,在一个节点上;突然有一天出了大瓜,导致热度飙升,该节点宕机后大量请求又到了下一个节点,别的节点也顶不住,导致挨个崩掉全部宕机
解决方式:
- 将一些特别热点的key放置到客户端进行存储,设置过期时间,过期后再访问服务器
- 将热点key数据复制出一些子key,这些key有相同的value值,查询的时候使用取模算法,将压力分摊到不同的节点
9. 项目中的应用
9.1 Redis的管道操作
由于redis每次操作都需要等待请求,中间网络耗时最高;我们可以将一些命令打包到一个管道中,一次性全部执行并获取全部返回结果,提高效率
9.3 计数器
- 计算视频播放次数
- 简单计数器,用incr就可以实现;
- 有有效期的计数器,设置key的有效期就可以实现;
- 简单的去重计数器,用set实现;
- 高并发大数去重计数器,用hll实现
- 防止刷单
- 限制登陆失败次数
9.4 排行榜
使用zSet来实现 key score1 value1, score2 value2
9.5 取代定时任务,例如订单超时30分钟后自动取消
通过Redis监听来实现
监听设置了失效时间的Key过期之后,可以设置过期事件;建议使用订单id来作为key
9.6 限流/防高频
与防止刷单类似;在某个时间内,达到多少的次数,就报错
9.7 生成流水号
初始化后,利用自增即可
9.8 作为配置中心
















