1. 为什么Redis性能这么强
支持数十万的并发(32G可以Set操作达到30W的QPS, Get操作40W的QPS),pipline则更高x2
内存存储:Redis是使用内存(in-memeroy)存储,没有磁盘IO上的开销
单线程实现:Redis使用单个线程处理请求,避免了多个线程之间线程切换和锁资源争用的开销
非阻塞IO:Redis使用多路复用IO技术,在poll,epolll,kqueue选择最优IO实现(参考netty篇章)
优化的数据结构:Redis有诸多可以直接应用的优化数据结构的实现,应用层可以直接使用原生的数据结构提升性能
2. 为什么用Redis
高性能:mysql数据存储磁盘,访问较慢。 redis存内存,访问快。50ms,10ms
大并发:mysql并发能力有限(8C32G的mysql,8000的最大连接数,可以达到3W的QPS,1.5K的TPS),在大并发情况下需要引入redis来缓冲数据访问(4 50W的QPS),避免击穿mysql。
3. Redis数据结构
String
List
Hash
Set
Sorted Set
-
Bitmap
boomfilter
Redis为什么单线程
https://draveness.me/whys-the-design-redis-single-thread/
单线程模型,基于多路复用IO模型监听多个连接。
-
使用单线程模型能带来更好的可维护性,方便开发和调试;
不需要引入锁之类机制来控制同步。(比如变量被并发读写了,那么需要加锁之类,不然访问结果无法确定)
-
使用单线程模型也能并发的处理客户端的请求;
多路复用IO也能同时监听多个FD,当监听到有相关事件发生时就调用相应的事件处理器进行处理。https://draveness.me/redis-io-multiplexing/
-
Redis 服务中运行的绝大多数操作的性能瓶颈都不是 CPU;
Redis 并不是 CPU 密集型的服务,如果不开启 AOF 备份,所有 Redis 的操作都会在内存中完成不会涉及任何的 I/O 操作,这些数据的读写由于只发生在内存中,所以处理速度是非常快的。
整个服务的瓶颈在于网络传输带来的延迟和等待客户端的数据传输,也就是网络 I/O。
在普通的linux上,redis也可以在1S内处理100W的请求。 如果这种还不够的话就做集群。
Redis引入多线程
redis4.0之后引入了一些命令,例如 UNLINK
、FLUSHALL ASYNC
、FLUSHDB ASYNC
等非阻塞的删除操作,这些命令会使用主线程外的线程执行。
删除操作如果删除的KV对占用比较小,那么同步也没什么问题。但是如果是几十几百MB的那么会消耗较多时间,不能在几MS内处理完,影响Redis的可用性。UNLINK
命令将键从元数据中删除,无法再被访问到,真正的删除操作会在后台异步执行。
Redis持久化机制
RDB 快照持久化
创建快照后获得某个时间点上完整的数据副本,快照可以用来复制到其他Redis从服务器来创建相同数据,或者用来重启服务时候进行恢复数据。
快照持久化是 Redis 默认采用的持久化方式,在 Redis.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命令创建快照。
AOF(append-only file)持久化
AOF模式实时性更强,默认没有开启,需要配置appendonly yes
来开启。每执行一条修改数据的命令都会将命令写入硬盘的AOF文件。
appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘 推荐
appendfsync no #让操作系统决定何时进行同步
AOF重写:
执行BGREWRITEAOF
会创建一个子进程进行AOF重写,但是其实不会读取当前的AOF文件,是通过读取现在数据库中KV数据生成新的AOF文件。新的AOF文件体积更加小。
Redis事务
Redis 过期机制
Redis集群模式
Redis pipeline、script、mass insertion
Redis配置修改
config set
可以不重启redis,实时生效配置
CONFIG SET protected-mode no
缓存相关
https://mp.weixin.qq.com/s/TBCEwLVAXdsTszRVpXhVug
https://segmentfault.com/a/1190000008931971
缓存穿透
指查询不存在的数据,缓存层没有命中,导致每次都查询数据库层,使得缓存层失去了保护存储层的意义,产生原因可能如下:
业务自身代码或者数据出现问题
一些恶意攻击、爬虫等造成大量空命中
可以进行分析统计方法调用次数,缓存命中次数,储存层命中次数,判断是否出现了缓存穿透的情况。
解决方案1. 缓存空对象
在存储层也返回空时,把空对象也保存到缓存,这样下次查询就会命中缓存。
问题:占用更多存储空间,而且如果是恶意攻击则会更加严重。
解决:
- 添加一个较小的TTL ,使其自动过期删除
2. 空对象缓存放到另外单独的一个缓存服务,避免空间不够回收时LRU算法剔除了正常的KV而没有过期空对象
问题: 数据不一致,如果缓存了空对象,然后业务操作导致存储层有把数据添加上了,那么在空对象缓存过期之前两边数据产生了不一致。
解决:
1. 如果是redis缓存,则更新数据后直接删除缓存
- 如果是本地缓存,则需要引入消息中间件之类通知机制,通知删除
解决方案2. 布隆过滤器
在访问缓存层之前,先访问布隆过滤器,如果miss,那就是不存在直接返回空对象。如果命中再继续查询缓存和存储层。
boomfilter命中可能误判,但是不命中必定是真的不命中。
缺点:需要额外维护boomfilter,删除数据时候需要重建,添加数据时候需要通知插入。如果是本地缓存构建的boomfilter那也还需要引入消息通知机制。如果是使用redis bitmap实现的那还简单点。
缓存雪崩
缓存正常起着承担大量请求,保护存储层的作用,但是由于某些情况导致缓存层无法提供服务,那么所有请求到达了存储层,导致存储层挂掉宕机。
产生的原因:
- 缓存层服务可用性不高,重启,或者网络等各种故障导致一段时间内无法提供服务
- 热点key,大量缓存同时失效过期
解决:
- 针对可用性问题可以构建集群+主备来提高可用性
- 大量缓存同时失效问题可以在存储层读写时候进行队列,加锁,信号量等控制并发数量。(hystrix对访问进行熔断限流)
- 大量缓存同时失效问题也可以对key设置分散的过期时间
- 针对热点key过期,可以设置永不过期,然后异步单独的线程来更新重建热点key数据
- 针对热点key过期,也可以在过期重建数据时候加锁,只允许一个线程进行重建,其他线程进行等待重建完成。分布式环境下可以用redis setnx锁。