缓存有哪些类型
缓存的类型分为:本地缓存、分布式缓存和多级缓存
本地缓存:
本地缓存就是在进程的内存中进行缓存,本地缓存是内存访问,没有远程交互开销,性能最好,但是受限于单机容量,一般缓存较小且无法扩展
分布式缓存:
分布式缓存一般都具有良好的水平扩展能力,对较大数据量的场景也能应付自如。缺点就是需要进行远程请求,性能不如本地缓存
多级缓存:
实际业务中一般采用多级缓存,本地缓存只保存访问频率最高的部分热点数据,其他的热点数据放在分布式缓存中
Redis和Memcache比较
- 与 MC 不同的是,Redis 采用单线程模式处理请求。这样做的原因有 2 个:一个是因为采用了非阻塞的异步事件处理机制;另一个是缓存数据都是内存操作 IO 时间不会太长,单线程可以避免线程上下文切换产生的代价。
- Redis 支持持久化,所以 Redis 不仅仅可以用作缓存,也可以用作 NoSQL 数据库。
- 相比 MC,Redis 还有一个非常大的优势,就是除了 K-V 之外,还支持多种数据格式,例如 list、set、sorted set、hash 等。
- Redis 提供主从同步机制,以及 Cluster 集群部署能力,能够提供高可用服务。
Redis字符串存储为什么使用SDS,而不是C字符串,它有哪些优点?
SDS数据定义
struct sdshdr {
int len; // 记录 buf 数组中已使用字节的数量,等于 SDS 所保存字符串的长度
int free; // 记录 buf 数组中未使用字节的数量
char buf[]; // 字节数组,用于保存字符串
};
- O(1)复杂度获取字符串长度
- 杜绝缓冲区溢出
- 减少修改字符串长度时所需的内存重分配次数
- 二进制安全
- 兼容部分C字符串函数
redis的缺点
- 由于是内存数据库,所以,单台机器,存储的数据量,跟机器本身的内存大小。虽然redis本身有key过期策略,但是还是需要提前预估和节约内存。如果内存增长过快,需要定期删除数据。
- 如果进行完整重同步,由于需要生成rdb文件,并进行传输,会占用主机的CPU,并会消耗现网的带宽。不过redis2.8版本,已经有部分重同步的功能,但是还是有可能有完整重同步的。比如,新上线的备机。
- 修改配置文件,进行重启,将硬盘中的数据加载进内存,时间比较久。在这个过程中,redis不能提供服务。
什么是缓存预热,如何实现
缓存预热这个应该是一个比较常见的概念,相信很多小伙伴都应该可以很容易的理解,缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
解决思路:
- 直接写个缓存刷新页面,上线时手工操作下;
- 数据量不大,可以在项目启动的时候自动进行加载;
- 定时刷新缓存;
Redis 为什么是单线程的
因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。
单线程的redis为什么这么快
- 纯内存操作
- 单线程操作,避免了频繁的上下文切换
- 采用了非阻塞I/O多路复用机制
redis的hashtable为什么需要渐进式rehash呢?
在元素数量较少时,rehash会非常快的进行,但是当元素数量达到几百、甚至几个亿时进行rehash将会是一个非常耗时的操作。如果一次性将成万上亿的元素的键值对rehash到ht[1],庞大的计算量可能会导致服务器在一段时间内停止服务,这是非常危险的!所以,rehash这个动作不能一次性、集中式的完成,而是分多次、渐进式地完成。
Redis列表list 底层原理
- 在版本3.2之前,Redis 列表list使用两种数据结构作为底层实现:压缩列表ziplist和双向链表linkedlist;因为双向链表占用的内存比压缩列表要多, 所以当创建新的列表键时,列表会优先考虑使用压缩列表, 并且在有需要的时候,才从压缩列表实现转换到双向链表实现。
- Redis3.2+ list的新实现quickList,可以认为quickList,是ziplist和linkedlist二者的结合;quickList将二者的优点结合起来。
这两种存储方式的优缺点
- 双向链表linkedlist便于在表的两端进行push和pop操作,在插入节点上复杂度很低,但是它的内存开销比较大。首先,它在每个节点上除了要保存数据之外,还要额外保存两个指针;其次,双向链表的各个节点是单独的内存块,地址不连续,节点多了容易产生内存碎片。
- ziplist存储在一段连续的内存上,所以存储效率很高。但是,它不利于修改操作,插入和删除操作需要频繁的申请和释放内存。特别是当ziplist长度很长的时候,一次realloc可能会导致大批量的数据拷贝。
Redis Set的底层原理
redis的集合对象set的底层存储结构特别神奇,我估计一般人想象不到,底层使用了intset和hashtable两种数据结构存储的,intset我们可以理解为数组,hashtable就是普通的哈希表(key为set的值,value为null)。
使用intset存储必须满足下面两个条件
- 结合对象保存的所有元素都是整数值
- 集合对象保存的元素数量不超过512个
Redis Zset的底层原理
采用ziplist和skiplist两种方案,满足如下两个条件使用ziplist.
- 元素数量少于128的时候
- 每个元素的长度小于64字节
skiplist优势
skiplist本质上是并行的有序链表,但它克服了有序链表插入和查找性能不高的问题。它的插入和查询的时间复杂度都是O(logN)
Redis的LRU
算法
如果按照HashMap和双向链表实现,需要额外的存储存放 next 和 prev 指针,牺牲比较大的存储空间,显然是不划算的。Redis初始的实现算法很简单,随机从dict中取出五个key,淘汰一个lru字段值最小的。(随机选取的key是个可配置的参数maxmemory-samples,默认值为5).在3.0的时候,又改进了一版算法,首先第一次随机选取的key都会放入一个pool中(pool的大小为16),pool中的key是按lru大小顺序排列的。接下来每次随机选取的keylru值必须小于pool中最小的lru才会继续放入,直到将pool放满。放满之后,每次如果有新的key需要放入,需要将pool中lru最大的一个key取出。淘汰的时候,直接从pool中选取一个lru最小的值然后将其淘汰。
策略(举例)
- volatile-lru 设置了过期时间的key参与近似的lru淘汰策略
-
allkeys-lru 所有的key均参与近似的lru淘汰策略
Redis事务原理和特点
- 对于Redis的事务功能来说,事务队列中的命令要么就全部执行,要么就一个都不执行,但是Redis的事务是不支持回滚操作的,即使事务队列中的某个命令在执行期间出现错误,整个事务也会继续执行下去,直到将事务队列中的所有命令都执行完毕为止。在EXEC的时候发现事务队列里有命令存在错误,所以事务里的命令就全都不执行
- Redis事务不像InnoDB那样复杂的多版本控制机制,而是将所有的命令放在一个事务队列中,收到EXEC命令后按照顺序一一执行。从而事务间就不会导致数据脏读、不可重复读、幻读的问题,因此就没有隔离级别
Redis 6.0 为什么要引入多线程呢?
- 可以充分利用服务器 CPU 资源,目前主线程只能利用一个核
- 多线程任务可以分摊 Redis 同步 IO 读写负荷
Redis 6.0 多线程的优化方向如下:
- 提高网络 IO 性能,典型的实现比如使用 DPDK 来替代内核网络栈的方式。
- 使用多线程充分利用多核,典型的实现比如 Memcached
开启多线程后,是否会存在线程并发安全问题?
不会,Redis 的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程顺序执行。