一、Redis为什么快
1.1 基于内存实现
数据都存储在内存里,相比磁盘IO操作快百倍,操作速率很快。
1.2 高效的数据结构
Redis底层多种数据结构支持不同的数据类型,并且Redis对这些数据结构进行了一些优化,比如存储字符串类型的SDS数据结构。
1.3 丰富而合理的编码
Redis底层提供了 丰富而合理的编码 ,五种数据类型根据长度及元素的个数适配不同的编码格式。
- String:自动存储int类型,非int类型用raw编码。
- List:字符串长度且元素个数小于一定范围使用 ziplist 编码,否则转化为 linkedlist 编码。
- Hash:hash 对象保存的键值对内的键和值字符串长度小于一定值及键值对。
- Set:保存元素为整数及元素个数小于一定范围使用 intset 编码,任意条件不满足,则使用 hashtable 编码。
- Zset:保存的元素个数小于定值且成员长度小于定值使用 ziplist 编码,任意条件不满足,则使用 skiplist 编码。
1.4 合适的线程模型
I/O 多路复用模型同时监听客户端连接,多线程是需要上下文切换的,对于内存数据库来说这点很致命。
1.5 Redis6.0后引入多线程
网络IO耗时要远大于 Redis 运行执行耗时,Redis的瓶颈主要在于网络的 IO 消耗。
Redis的优化方向:
- 提高网络 IO 性能,典型的实现比如使用 DPDK 来替代内核网络栈的方式
- 使用多线程充分利用多核,典型的实现比如 Memcached。
注:协议栈优化的方式与 Redis 关系不大,支持多线程是一种最有效最便捷的操作方式。
Redis支持多线程主要原因:
- 可以充分利用服务器 CPU 资源,目前主线程只能利用一个核;
- 多线程任务可以分摊 Redis 同步 IO 读写负荷;
关于多线程须知:
- Redis 6.0 版本 默认多线程是关闭的 io-threads-do-reads no;
- Redis 6.0 版本 开启多线程后线程数也要谨慎设置;
- 多线程可以使得性能翻倍,但是多线程只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程顺序执行。
Redis多线程的实现机制:
- 主线程负责接收建立连接请求,获取 socket 放入全局等待读处理队列
- 主线程处理完读事件之后,通过 RR(Round Robin) 将这些连接分配给这些 IO 线程
- 主线程阻塞等待 IO 线程读取 socket 完毕
- 主线程通过单线程的方式执行请求命令,请求数据读取并解析完成,但并不执行
- 主线程阻塞等待 IO 线程将数据回写 socket 完毕
- 解除绑定,清空等待队列
该设计的特点:
- IO 线程要么同时在读 socket,要么同时在写,不会同时读或写;
- IO 线程只负责读写 socket 解析命令,不负责命令处理。
Redis多线程是否考虑线程安全问题: Redis的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程顺序执行。所以不需要去考虑控制 key、lua、事务,LPUSH/LPOP 等等的并发及线程安全问题。
二、缓存雪崩
雪崩定义: 大量请求访问缓存,缓存扛不住,导致大量请求直接访问数据库,而数据库也扛不住导致大面积崩塌。
解决方案:
- 保证缓存层服务高可用,比如使用哨兵模式或者集群;
- 依赖隔离组件为后端服务限流熔断并降级;
- 提前演练,在项目上线前,演练缓存层宕掉后,应用以及后端的负载情况以及可能出现的问题,在此基础做一些预案设定;
- 使用JVM内存,使用二级缓存,JVM内存可以至少支持每秒百万并发,Redis每秒最多10万并发。
使用JVM二级缓存的缺点:
- 不然缓存数量太大,JVM压力比较大,可以使用ESCache,Guava进行缓存的淘汰。
- JVM是进程级别的缓存,如果多台服务,需要使用Redis的发布订阅、MQ等保证多个服务的数据一致性。
- 即使保证了数据一致性,但也会出现短时数据不一致的现象。
三、缓存穿透
穿透定义: 缓存穿透是指缓存和数据库中都没有的数据,比如ID默认>0,黑客一直 请求ID= -12的数据那么就会导致数据库压力过大,严重会击垮数据库。
解决方案:
- 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null 失效时间可以为15秒防止恶意攻击。
- 后端接口层增加用户鉴权校验,参数做校验等。
- 单个IP每秒访问次数超过阈值直接拉黑IP,关进小黑屋1天。
- 用Redis提供的 Bloom Filter 。
四、缓存击穿
击穿定义: 大批量key在同一时间同时失效导致所有请求都打到数据库,直接请求数据库,数据库中还有数据,导致数据库压力比较大,出现数据库抖动。 解决方案:
- 设置热点数据永远不过期;
- 缓存数据的过期时间加上个随机值,防止同一时间大量数据过期现象发生。
- 使用互斥锁;
五、简单冷热分离
方案:在读到缓存数据后重新设置缓存数据的存活时间,实现读延期。可以使得用户经常访问的数据(热数据)一直在缓存中,不经常访问的数据(冷数据)不在缓存中,实现冷热分离。
六、突发性的热点缓存重建
方案:双重检测。先查一遍缓存,如果查不到,再查一遍,不过第二次查询要加分布式锁。