1、缓存带来的好处:
- 高性能
大量相同的请求过来,每次查询mysql耗时600ms,如果用缓存耗时20ms,性能提高30倍. - 高并发
单机mysql一般的最大QPS 2000,超过2000mysql直接死掉;单机redis QPS轻松10万,并发提高50倍.
2、缓存带来的坏处:
- 缓存与数据库的双写不一致
- 缓存雪崩、缓存穿透、缓存击穿
- 缓存并发竞争问题
3、redis高性能的三大核心(为什么单线程还能这么快):
-
纯内存操作:
这时Redis达到每秒万级别访问的重要基础
-
单线程:
保证高并发下数据的一致性,避免了多线程下的并发问题:资源共享、线程切换、死锁、竞态产生的消耗;
-
多路I/O复用,非阻塞I/O:
linux平台下,Redis使用epoll作为I/O多路复用技术的实现,在加上Redis自身的事件处理模型将epoll中的链接、读写、关闭都转换为事件,不在网络I/O上浪费过多的时间;
3.1 源码层面
C语言编写,源代码精简(2万行),C语言实现的程序”距离“操作系统更近,执行速度快;
Redis源码里,包含了多种数据结构的实现:
简单动态字符串(SDS)、双端链表、字典、压缩列表、整数集合、跳跃表等等;
Redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这个系统包含了字符串、列表、哈希、集合、有序集合这五种类型的对象,每种对象都用到了至少一种前面所说的数据结构;
针对不同的使用场景,为对象设置多种不同的数据结构实现,从而优化对象在不同场景下的使用效率;
4、除了缓存,Redis能做些什么特别的事 ?
- 利用redis生成唯一key
- 分布式锁
- 消息队列
5、利用redis实现分布式锁
redis是单线程的,利用setnx命令的原子性(判断指定key是否存在,不存在才能添加成功),实现多线程状态下的分布式锁
SET my_key my_value NX PX milliseconds
6、redis过期策略
Reference:引用
当redis存了非常多数据数据时,过期的数据不是实时删除的,如果每次删除都是扫描全部获取过期数据,代价太大,所以很可能当内存满了时,会先删除没有过期的
定期删除
每个一段时间,从设置了过期的key里面随机挑选,判断是否过期,过期则删除
惰性删除
通过定期删除肯定会遗留很多没删掉的,所以,当在获取某个key时,会判断是否已经过期,过期则删除不返回
8、内存淘汰机制(LRU算法)
当内存不足以容纳新写入数据时,redis会启用内存淘汰机制:
- allkey_LRU:移除最近最少使用的key(最常用)
- allkey_Random:随机移除一个key
- volatile_LRU:在设置了过期时间的键空间中,移除最近最少使用的key
- volatile_Random:在设置了过期时间的键空间中,随机移除key
- volatile_TTL:在设置了过期时间的键空间中,优先移除更早过期的key
9、Redis的I/O多路复用
Reference:引用
下面举一个例子,模拟一个tcp服务器处理30个客户socket。假设你是一个老师,让30个学生解答一道题目,然后检查学生做的是否正确,你有下面几个选择:
- 第一种选择:按顺序逐个检查,先检查A,然后是B,之后是C、D。。。这中间如果有一个学生卡主,全班都会被耽误。这种模式就好比,你用循环挨个处理socket,根本不具有并发能力。
- 第二种选择:你创建30个分身,每个分身检查一个学生的答案是否正确。 这种类似于为每一个用户创建一个进程或者线程处理连接。
- 第三种选择,你站在讲台上等,谁解答完谁举手。这时C、D举手,表示他们解答问题完毕,你下去依次检查C、D的答案,然后继续回到讲台上等。此时E、A又举手,然后去处理E和A。。。
这种就是IO复用模型,Linux下的select、poll和epoll就是干这个的。将用户socket对应的fd注册进epoll,然后epoll帮你监听哪些socket上有消息到达,这样就避免了大量的无用操作。此时的socket应该采用非阻塞模式。这样,整个过程只在调用select、poll、epoll这些调用的时候才会阻塞,收发客户消息是不会阻塞的,整个进程或者线程就被充分利用起来,这就是事件驱动,所谓的reactor模式。
java的NIO 是IO的多路复用; IO多路复用听上去好像是多个数据可以共享一个IO(socket连接),实际上并非如此。IO多路复用不是指多个服务共享一个连接,而仅仅是指多个连接的管理可以在同一进程。
IO多路复用:各个平台的底层IO复用实现各不相同,linux采用select、poll、epoll实现,macOS采用kqueue,windows采用IOCP,netty在此基础上进行了封装,使其更好用,而底层实际上是调用的当前平台的IO复用接口(native方法),而Redis底层也是依赖平台的IO复用
10、redis 集群的三种模式
10.1 主从复制(无法保证高可用)
10.2 哨兵模式(无法保证高性能)
Redis主从模式虽然能做到很好的数据备份,但是他并不是高可用的。一旦主服务器点宕机后,只能通过人工去切换主服务器。因此Redis的哨兵模式也就是为了解决主从模式的高可用方案。
10.3 集群模式
Redis哨兵模式实现了高可用,读写分离,但是其主节点仍然只有一个,即写入操作都是在主节点中,这也成为了性能的瓶颈。
因此Redis在 3.0 后加入了Cluster模式,它采用去无心节点方式实现,集群将会通过分片方式保存数据库中的键值对;
每个分片节点又通过主从方式保证高可用:与哨兵类似,分片中的每个节点会定期向其他节点发送消息,已检测各个节点的状态,如果主节点被半数以上节点检测到下线,会重新选择主节点;
- 集群模式下的扩缩容:
每个分片节点的分片范围维护在配置文件中,当增删节点时,需要将重新将16383个slots分配到集群中的节点上(数据迁移)
11、一致性Hash算法
先说一个误区:Redis的集群模式本身没有使用一致性hash算法,而是使用slots插槽实现了数据分片。
一致性Hash算法主要用于解决分布式缓存集群节点新增,和节点失效的问题;
我们说的一致性hash都不是缓存机器自身的功能,而是集群前置的代理或客户端实现的。比如在redis的jedis客户端jar包就是实现了一致性hash算法(客户端模式)。
12、缓存穿透、缓存雪崩、缓存击穿
缓存穿透(不存在key)
- 概念:访问一个数据库不存在的key,一般情况不会缓存这种数据,所以的请求都命中DB;
- 解决方案
采用布隆过滤器,使用一个足够大的bitmap,用于存储可能访问的key,不存在的key直接被过滤;引用
访问key未在DB查询到值,也将空值写进缓存,但可以设置较短过期时间。
缓存雪崩(同时过期)
- 概念:大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。
- 解决方案
可以给缓存设置过期时间时加上一个随机值时间,使得每个key的过期时间分布开来,不会集中在同一时刻失效。
缓存击穿(热点key过期)
- 概念
一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。 - 解决方案
设置热点缓冲永不过期
缓存预热:每次查出来看看是不是要过期了,快过期了则更新缓存
在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。
13、数据库与缓存双写一致性问题
引用
一般采用:先更新数据库,再删除缓存
14、redis的持久化
14.1 redis持久化的意义
redis服务器宕机的兜底措施,避免宕机时直接访问数据库、降低请求响应速度;
14.2 AOF(Append Only File)日志
写后日志,类似redo log,每执行一次命令,如果成功则保存命令到aof日志文件,在主线程中执行,有阻塞风险;
-
三种写回策略
AOF重写机制
如果一直往AOF文件里写日志,日志文件会爆炸;
当AOF文件的大小超过了配置所设置的阙值时,Redis就会启动AOF文件压缩,只保留可以恢复数据的最小指令集,可以使用命令bgrewriteaof;
- 触发机制:Redis会记录上次重写时的AOF文件大小,默认配置时当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发
auto-aof-rewrite-percentage 100 (一倍)
auto-aof-rewrite-min-size 64mb
AOF重写可避免主线程阻塞:主线程fork一个子线程,同时把主线程的内存通过写时复制的方式拷贝给子线程,然后子线程在不影响主线程的情况下逐一把拷贝的数据写成操作,记入重写日志;
如果在子线程写日志的过程中主线程产生的数据,会放到一个缓存区中,后续写入AOF文件;
14.3 RDB(Redis DataBase)内存快照
默认持久化方式
与AOF对比
AOF记录的是操作命令,不是实际数据,故障恢复的时候缓慢;
RDB能够快速恢复,但是相比于AOF的增量式操作,RDB的全量试操作更耗时耗力;
两个命令来生成RDB
Redis 提供了两个命令来生成 RDB 文件,分别是 save 和 bgsave。
- save:在主线程中执行,会导致阻塞;
- bgsave:创建一个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,这也是 Redis RDB 文件生成的默认配置。
AOF和RDB结合使用
Redis 4.0提出一个混合使用AOF日志和内存快照的方法;内存快照以一定频率执行,两次快照间,使用AOF日志记录这期间的所有命令操作;
这个方法既能享受到 RDB 文件快速恢复的好处,又能享受到 AOF 只记录操作命令的简单优势