目录
- 为什么使用缓存?
- Redis是什么?
- Redis都有哪些使用场景?
- Redis有哪些功能?
- Redis支持的数据类型有哪些?
- Redis为什么是单线程的?
- Redis真的是单线程的吗?
- Redis同步机制是什么?
- Redis持久化有几种方式?
- Redis和 memecache 有什么区别?
- Redis支持的 java 客户端都有哪些?
- jedis 和 redisson 有哪些区别?
- 怎么保证缓存和数据库数据的一致性?
- 什么是缓存穿透?怎么解决?
- Redis怎么实现分布式锁?
- Redis分布式锁有什么缺陷?
- Redis如何做内存优化?
- Redis6.0后的新特性?
- redis6.0多线程实现机制
- 熟悉哪些 Redis 集群模式?
- 是否使用过 Redis Cluster 集群,集群的原理是什么?
- Redis Cluster 集群方案什么情况下会导致整个集群不可用?
- Redis 集群架构模式有哪几种?
- 如果有大量的 key 需要设置同一时间过期,一般需要注意什么?
- 什么情况下可能会导致 Redis 阻塞?
- 怎么提高缓存命中率?
- Redis 如何解决 key 冲突?
- 了解Redis的事务吗?
- redis的过期策略以及内存淘汰机制?
- 热点数据和冷数据是什么?
- 什么是热Key问题,如何解决热key问题?
- 什么是跳跃表?
- redis的hash冲突怎么办?
- 缓存击穿?
- 缓存雪崩?
- Redis多级缓存?
为什么使用缓存?
- 使用缓存的目的就是提升读写性能。
- 而实际业务场景下,更多的是为了提升读性能,带来更好的性能,带来更高的并发量。
- Redis 的读写性能比 Mysql 好的多,我们就可以把 Mysql 中的热点数据缓存到 Redis 中,提升读取性能,同时也减轻了 Mysql 的读取压力
Redis是什么?
- Redis是一个key-value存储系统,它支持存储的value类型相对更多,包括string、list、set、zset(sorted set --有序集合)和hash。
- 这些数据结构都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。
- 在此基础上,Redis支持各种不同方式的排序。
- 为了保证效率,数据都是缓存在内存中,Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步
Redis都有哪些使用场景?
Redis有以下几个常见的使用场景:
-
缓存:Redis可以将热门的数据缓存到内存中,以提高数据的读取速度。由于Redis的高性能和快速响应时间,它通常用于缓存热门的数据集、查询结果、页面片段等。
-
分布式锁:Redis提供了原子操作和分布式锁的特性,可以用于实现分布式系统中的互斥访问控制。通过使用Redis的SETNX命令,可以轻松地创建一个分布式锁。
-
消息队列:Redis的发布/订阅功能可以用来实现简单的消息队列系统。生产者将消息推送到指定的频道,而消费者则订阅这些频道,并接收到相应的消息。
-
计数器:Redis提供了INCR和INCRBY命令,可以实现高效的计数器功能。这对于实现用户的点赞、分享次数、页面访问次数等功能非常有用。
-
数据存储:Redis支持多种数据结构,如字符串、列表、集合、有序集合和哈希表等。这使得它可以用作轻量级的数据存储工具,适用于需要快速读写的场景。
-
会话管理:Redis可以用于存储和管理用户会话的相关数据。它可以轻松地存储和查询用户的登录状态、用户权限、购物车内容等。
-
分布式缓存:Redis可以作为分布式缓存系统的一部分,用于存储和管理缓存数据。它具有高性能、可扩展性和高可用性,可以有效地减轻后端数据库的负载。
-
实时排行榜:Redis的有序集合功能非常适合用于实现实时的排行榜功能。通过将分数和成员信息存储在有序集合中,可以轻松地进行排名和排行榜的计算。
总结起来,Redis适用于许多场景,包括缓存、分布式锁、消息队列、计数器、数据存储、会话管理、分布式缓存和实时排行榜等。
Redis有哪些功能?
-
基于本机内存的缓存
- 当调用api访问数据库时,假如此过程需要2秒,如果每次请求都要访问数据库,那将对服务器造成巨大的压力,
- 如果将此sql的查询结果存到Redis中,再次请求时,直接从Redis中取得,而不是访问数据库,效率将得到巨大的提升,Redis可以定时去更新数据(比如1分钟)。
-
如果电脑重启,写入内存的数据是不是就失效了呢,这时Redis还提供了持久化的功能。
-
哨兵(Sentinel)和复制
- Sentinel可以管理多个Redis服务器,它提供了监控、提醒以及自动的故障转移功能;
- 复制则是让Redis服务器可以配备备份的服务器;
- Redis也是通过这两个功能保证Redis的高可用;
-
集群(Cluster)
- 单台服务器资源总是有上限的,CPU和IO资源可以通过主从复制,进行读写分离,把一部分CPU和IO的压力转移到从服务器上,但是内存资源怎么办,主从模式只是数据的备份,并不能扩充内存;
- 现在我们可以横向扩展,让每台服务器只负责一部分任务,然后将这些服务器构成一个整体,对外界来说,这一组服务器就像是集群一样。
Redis支持的数据类型有哪些?
Redis支持以下数据类型:
- 字符串(string):最基本的数据类型,可以是字符串类型、整数类型或浮点类型。
- 列表(list):有序的字符串元素列表,可以在列表的两端进行插入或删除操作。
- 集合(set):无序、唯一的字符串元素集合,可以对集合进行交、并、差等操作。
- 有序集合(sorted set):每个元素都关联了一个分数,根据分数进行有序排列。
- 哈希(hash):由键值对组成的无序散列。
- 地理位置(geospatial):存储地理位置信息的特殊数据类型,支持对位置进行距离计算和排序。
- 持久化列表(stream):类似于日志的数据结构,支持多个消费者从不同的位置读取数据。
除了上述数据类型,Redis还提供了一些特殊的操作和数据结构,如位图(bitmap)、超时队列(delayed queue)等。
Redis为什么是单线程的?
- 代码更清晰,处理逻辑更简单;
- 不用考虑各种锁的问题,不存在加锁和释放锁的操作,没有因为可能出现死锁而导致的性能问题;
- 不存在多线程切换而消耗CPU;
- 无法发挥多核CPU的优势,但可以采用多开几个Redis实例来完善;
Redis真的是单线程的吗?
- Redis6.0之前是单线程的,Redis6.0之后开始支持多线程;
- redis内部使用了基于epoll的多路服用,也可以多部署几个redis服务器解决单线程的问题;
- redis主要的性能瓶颈是内存和网络;
- 内存好说,加内存条就行了,而网络才是大麻烦,所以redis6内存好说,加内存条就行了;
- 而网络才是大麻烦,所以redis6.0引入了多线程的概念,
- redis6.0在网络IO处理方面引入了多线程,如网络数据的读写和协议解析等,需要注意的是,执行命令的核心模块还是单线程的。
Redis同步机制是什么?
- Redis 支持主从同步、从从同步。如果是第一次进行主从同步,主节点需要使用 bgsave 命令,再将后续修改操作记录到内存的缓冲区,等 RDB 文件全部同步到复制节点,复制节点接受完成后将RDB 镜像记载到内存中。
- 等加载完成后,复制节点通知主节点将复制期间修改的操作记录同步到复制节点,即可完成同步过程
Redis持久化有几种方式?
redis提供了两种持久化的方式,分别是RDB(Redis DataBase)和AOF(Append Only File)。
-
RDB,简而言之,就是在不同的时间点,将redis存储的数据生成快照并存储到磁盘等介质上;
-
AOF,则是换了一个角度来实现持久化,那就是将redis执行过的所有写指令记录下来,在下次redis重新启动时,只要把这些写指令从前到后再重复执行一遍,就可以实现数据恢复了。
-
其实RDB和AOF两种方式也可以同时使用,在这种情况下,如果redis重启的话,则会优先采用AOF方式来进行数据恢复,这是因为AOF方式的数据恢复完整度更高。
-
如果你没有数据持久化的需求,也完全可以关闭RDB和AOF方式,这样的话,redis将变成一个纯内存数据库,就像memcache一样。
Redis和 memecache 有什么区别?
- Redis相比memecache,拥有更多的数据结构和支持更丰富的数据操作。
- Redis支持key-value,常用的数据类型主要有String、Hash、List、Set、Sorted Set。
- memecache只支持key-value。
- 内存使用率对比,Redis采用hash结构来做key-value存储,由于其组合式的压缩,其内存利用率会高于memecache。
- 性能对比:Redis只使用单核,memecache使用多核。
- Redis支持磁盘持久化,memecache不支持。
Redis可以将一些很久没用到的value通过swap方法交换到磁盘。 - Redis支持分布式集群,memecache不支持。
Redis支持的 java 客户端都有哪些?
Redisson、Jedis、lettuce 等等,官方推荐使用 Redisson。
jedis 和 redisson 有哪些区别?
- Jedis 和 Redisson 都是Java中对Redis操作的封装。
- Jedis 只是简单的封装了 Redis 的API库,可以看作是Redis客户端,它的方法和Redis 的命令很类似。
- Redisson 不仅封装了 redis ,还封装了对更多数据结构的支持,以及锁等功能,相比于Jedis 更加大。
- 但Jedis相比于Redisson 更原生一些,更灵活。
怎么保证缓存和数据库数据的一致性?
在实际项目中,可以采取以下几种方法来保证缓存和数据库数据的一致性:
-
读取缓存前先查询数据库:在读取数据时,先查询缓存,如果缓存中不存在数据,则再到数据库中查询。如果查询到数据,将数据存入缓存,并设置合适的过期时间。这样可以保证缓存中的数据和数据库中的数据保持一致。
-
更新数据库时同时更新缓存:在更新数据库数据时,同时更新缓存中的数据。可以使用触发器、消息队列等机制,将数据变更操作同步到缓存中。这样可以保证数据库和缓存中的数据始终保持一致。
-
缓存数据过期策略:可以设置合适的缓存数据过期策略,确保缓存中的数据是最新的。可以根据业务需求和数据访问频率来设置缓存的过期时间,使得缓存数据在过期前能够及时更新。
-
数据双写策略:在更新数据库数据的同时,更新缓存中的数据。可以采用同步调用或异步调用来更新缓存,确保数据的一致性。可以使用事务或分布式锁来保证数据库和缓存的更新操作是原子性的。
-
缓存雪崩处理:当缓存中的大量数据同时过期时,可能会导致大量请求同时访问数据库,造成数据库压力过大。可以采用预加载、限流、熔断等策略来应对缓存雪崩问题。
-
延时双删策略:在更新数据库数据后,先删除缓存中的数据,再更新数据库。这样可以避免缓存删除失败时,数据库数据已经更新的情况。
-
短暂缓存策略:对于频繁更新的数据,可以采用短暂缓存策略,将缓存时间设置较短,确保数据的实时性和一致性。
以上策略可以根据具体业务场景和需求进行选择和组合,以保证缓存和数据库数据的一致性。
什么是缓存穿透?怎么解决?
缓存穿透指的是大量的请求直接绕过缓存,直接访问数据库,导致缓存无法起到应有的作用,并且给数据库带来了压力。这通常是因为查询一个不存在的数据,每次都会直接请求数据库,而不会命中缓存。
缓存穿透问题可以通过以下几种方法来解决:
-
布隆过滤器(Bloom Filter):布隆过滤器是一种空间效率高、误报率低的数据结构,可以用于判断一个元素是否存在于集合中。可以将所有可能存在的数据哈希到一个足够大的二进制位数组中,并使用多个哈希函数。当一个请求过来时,先通过布隆过滤器判断是否存在,如果不存在则直接返回,不再访问数据库。
-
缓存空对象(Cache Null Object):当数据库中查询某个不存在的数据时,可以将这个空结果(null)放入缓存,并设置一个较短的过期时间。下次再查询同样的数据时,如果缓存中存在空结果,则直接返回,不再查询数据库。这样可以避免频繁查询数据库。
-
热点数据预热(Preheating):在系统启动时或者低峰期,将热点数据加载到缓存中,以提前将数据加载到缓存中,减少请求直接访问数据库的情况。
-
限制请求频率(Rate Limiting):可以使用限流的手段,如令牌桶算法或漏桶算法,限制每秒钟的请求频率。这样可以防止大量的请求同时访问数据库,减轻数据库压力。
-
数据预加载(Cache Loading):在缓存失效前,提前主动从数据库中加载新的数据到缓存中,确保缓存数据的及时更新和有效性。
-
异步缓存更新:在更新数据库数据的同时,异步更新缓存。可以使用消息队列、异步任务等方式来实现异步更新,减少对数据库的直接访问。
使用上述方法可以有效解决缓存穿透问题,提高系统的性能和并发处理能力。
Redis怎么实现分布式锁?
在Redis中,可以使用SET命令来实现分布式锁。具体步骤如下:
- 客户端尝试执行以下命令:
SET lock_key unique_id NX PX lock_time
,其中lock_key
是锁的名称,unique_id
是唯一的标识符,NX
选项表示仅在键不存在时设置键的值,PX
选项表示设置键的过期时间。 - 如果命令执行成功,即返回"OK",表示该客户端成功获得了锁。
- 客户端在执行完业务逻辑后,通过执行以下命令来释放锁:
DEL lock_key
。 - 如果命令执行失败,即返回nil,表示锁已经被其他客户端持有。在此情况下,客户端可以选择等待一段时间后再次尝试获取锁,或者放弃获取锁。
需要注意的是,在释放锁时,客户端只能删除自己所持有的锁,不能意外地删除其他客户端的锁。为了实现这一点,可以为每个锁分配一个唯一的标识符,客户端在释放锁时需要提供该标识符,并且只有匹配的标识符和锁名称同时匹配时,才能够成功释放锁。
使用分布式锁时,还需要考虑一些额外的情况,例如锁的过期时间设置、延迟问题、宕机恢复等。因此,在实际应用中,可以考虑使用Redisson等第三方库来简化分布式锁的实现。
Redis分布式锁有什么缺陷?
- Redis 分布式锁不能解决超时的问题,分布式锁有一个超时时间,程序的执行如果超出了锁的超时时间就会出现问题。
- Redis容易产生的几个问题:
- 锁未被释放
- B锁被A锁释放了
- 数据库事务超时
- 锁过期了,业务还没执行完
- Redis主从复制的问题
Redis如何做内存优化?
-
缩短键值的长度
- 缩短值的长度才是关键,如果值是一个大的业务对象,可以将对象序列化成二进制数组;
- 首先应该在业务上进行精简,去掉不必要的属性,避免存储一些没用的数据;
- 其次是序列化的工具选择上,应该选择更高效的序列化工具来降低字节数组大小;
- 以JAVA为例,内置的序列化方式无论从速度还是压缩比都不尽如人意,这时可以选择更高效的序列化工具,如: protostuff,kryo等
-
共享对象池
- 对象共享池指Redis内部维护[0-9999]的整数对象池。
- 创建大量的整数类型redisObject存在内存开销,每个redisObject内部结构至少占16字节,甚至超过了整数自身空间消耗。
- 所以Redis内存维护一个[0-9999]的整数对象池,用于节约内存。
- 除了整数值对象,其他类型如list,hash,set,zset内部元素也可以使用整数对象池。
- 因此开发中在满足需求的前提下,尽量使用整数对象以节省内存。
-
字符串优化
-
编码优化
-
控制key的数量
Redis6.0后的新特性?
-
增加了多线程Thread I/O
- Redis将所有数据放在内存中,内存的响应时长大约为100纳秒,对于小数据包,Redis服务器可以处理80,000到100,000 QPS,这也是Redis处理的极限了,对于80%的公司来说,单线程的Redis已经足够使用了。
- 但随着越来越复杂的业务场景,有些公司动不动就上亿的交易量,因此需要更大的QPS。
- 常见的解决方案是在分布式架构中对数据进行分区并采用多个服务器,
- 但该方案有非常大的缺点,例如
- 要管理的Redis服务器太多,维护代价大;
- 某些适用于单个Redis服务器的命令不适用于数据分区;
- 数据分区无法解决热点读/写问题;
- 数据偏斜,重新分配和放大/缩小变得更加复杂等等。
- 从Redis自身角度来说,因为读写网络的read/write系统调用占用了Redis执行期间大部分CPU时间,瓶颈主要在于网络的 IO 消耗, 优化主要有两个方向:
- 提高网络 IO 性能,典型的实现比如使用 DPDK 来替代内核网络栈的方式
- 使用多线程充分利用多核,典型的实现比如 Memcached。
- 协议栈优化的这种方式跟 Redis 关系不大,支持多线程是一种最有效最便捷的操作方式。所以总结起来,redis支持多线程主要就是两个原因:
- 可以充分利用服务器 CPU 资源,目前主线程只能利用一个核
- 多线程任务可以分摊 Redis 同步 IO 读写负荷
-
客户端缓存Client Side Cache
- 如果本地没有实现JVM缓存,那么在大并发的情况下对redis服务器也是一种考验,所以redis提出一种客户端缓存方案
- 实现过程是:
- 客户端 1从服务器获取某个key的value,然后服务器缓存下当前key的,并返回value给客户端1。
- 客户端1拿到value之后,将key-value缓存在本地。
- 如果另一个客户端2修改了key的value为value2,则服务端检查之后发现此key已经被缓存到客户端1,则会给客户端1发送一个失效key的value的通知,客户端1收到通知后,失效缓存在本地的key-value
-
ACL细粒度安全控制acces control list
- 可以根据命令和key来控制访问连接
- 在redis6之前,只能通过密码来控制,还有通过rename来调整高危命令flushdb,keys*,shutdown等命令的权限
- redis6之后,提供了更细粒度的权限控制:
- 接入权限:用户名和密码
- 控制可以执行的命令
- 控制可以操作的key
-
增加SSL模块
- 通过增加设置,在传输的时候使用SSL协议,确保传输过程的安全性
当SSL模块开启的时候,不能使用多线程
- 通过增加设置,在传输的时候使用SSL协议,确保传输过程的安全性
-
增加RESP3新的通信协议
- 增加RESP3同行协议,优化服务端和客户端之间的通信
redis6.0多线程实现机制
- 主线程负责接收建立连接请求,获取 socket 放入全局等待读处理队列
- 主线程处理完读事件之后,通过 RR(Round Robin) 将这些连接分配给这些 IO 线程
- 主线程阻塞等待 IO 线程读取 socket 完毕
- 主线程通过单线程的方式执行请求命令,请求数据读取并解析完成,但并不执行
- 主线程阻塞等待 IO 线程将数据回写 socket 完毕
- 解除绑定,清空等待队列
熟悉哪些 Redis 集群模式?
- Redis Sentinel
- 体量较小时,选择 Redis Sentinel ,单主 Redis 足以支撑业务。
- Redis Cluster
- Redis 官方提供的集群化方案,体量较大时,选择 Redis Cluster ,通过分片,使用更多内存。
- Twemprox
- Twemprox 是 Twtter 开源的一个 Redis 和 Memcached 代理服务器,主要用于管理 Redis 和Memcached 集群,减少与Cache 服务器直接连接的数量。
- Codis
- Codis 是一个代理中间件,当客户端向 Codis 发送指令时, Codis 负责将指令转发到后面的Redis 来执行,并将结果返回给客户端。
- 一个 Codis 实例可以连接多个 Redis 实例,也可以启动多个 Codis 实例来支撑,每个 Codis 节点都是对等的,这样可以增加整体的 QPS 需求,还能起到容灾功能。
- 客户端分片
- 在 Redis Cluster 还没出现之前使用较多,现在基本很少热你使用了,在业务代码层实现,起几个毫无关联的 Redis 实例,在代码层,对 Key 进行 hash 计算,然后去对应的 Redis 实例操作数据。
- 这种方式对 hash 层代码要求比较高,考虑部分包括,节点失效后的替代算法方案,数据震荡后的自动脚本恢复,实例的监控,等等。
是否使用过 Redis Cluster 集群,集群的原理是什么?
- 所有的节点相互连接
- 集群消息通信通过集群总线通信,集群总线端口大小为客户端服务端口 + 10000(固定值)
- 节点与节点之间通过二进制协议进行通信
- 客户端和集群节点之间通信和通常一样,通过文本协议进行
- 集群节点不会代理查询
- 数据按照 Slot 存储分布在多个 Redis 实例上
- 集群节点挂掉会自动故障转移
- 可以相对平滑扩/缩容节点
- Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis 先对key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0~16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点
Redis Cluster 集群方案什么情况下会导致整个集群不可用?
Redis 没有使用哈希一致性算法,而是使用哈希槽。Redis 中的哈希槽一共有 16384 个,计算给定密钥的哈希槽,我们只需要对密钥的 CRC16 取摸 16384。假设集群中有 A、B、C 三个集群节点,不存在复制模式下,每个集群的节点包含的哈希槽如下:
- 节点 A 包含从 0 到 5500 的哈希槽;
- 节点 B 包含从 5501 到 11000 的哈希槽;
- 节点 C 包含从 11001 到 16383 的哈希槽;
这时,如果节点 B 出现故障,整个集群就会出现缺少 5501 到 11000 的哈希槽范围而不可用
Redis 集群架构模式有哪几种?
Redis 集群架构是支持单节点单机模式的,也支持一主多从的主从结构,还支持带有哨兵的集群部署模式。
如果有大量的 key 需要设置同一时间过期,一般需要注意什么?
- 如果有大量的 key 在同一时间过期,那么可能同一秒都从数据库获取数据,给数据库造成很大的压力,导致数据库崩溃,系统出现 502 问题。
- 也有可能同时失效,那一刻不用都访问数据库,压力不够大的话,那么 Redis 会出现短暂的卡顿问题。所以为了预防这种问题的发生,最好给数据的过期时间加一个随机值,让过期时间更加分散。
什么情况下可能会导致 Redis 阻塞?
Redis 产生阻塞的原因主要有内部和外部两个原因导致:
- 内部原因
- 如果 Redis 主机的 CPU 负载过高,也会导致系统崩溃;
- 数据持久化占用资源过多;
- 对 Redis 的 API 或指令使用不合理,导致 Redis 出现问题。
- 外部原因
- 外部原因主要是服务器的原因,例如服务器的 CPU 线程在切换过程中竞争过大,内存出现问题、网络问题等
怎么提高缓存命中率?
- 提前加载数据到缓存中;
- 增加缓存的存储空间,提高缓存的数据;
- 调整缓存的存储数据类型;
- 提升缓存的更新频率
Redis 如何解决 key 冲突?
- Redis 如果 key 相同,后一个 key 会覆盖前一个 key。
- 如果要解决 key 冲突,最好给 key 取好名区分开,可以按业务名和参数区分开取名,避免重复 key 导致的冲突
了解Redis的事务吗?
Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的 Redis会将一个事务中的所有命令序列化,然后按顺序执行。
- redis 不支持回滚“Redis 在事务失败时不进行回滚,而是继续执行余下的命令”, 所以 Redis 的内部可以保持简单且快速。
- 如果在一个事务中的命令出现错误,那么所有的命令都不会执行;
- 如果在一个事务中出现运行错误,那么正确的命令会被执行。
- 1)MULTI命令用于开启一个事务,它总是返回OK。 MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。
- 2)EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值 nil 。
- 3)通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出。
WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令
redis的过期策略以及内存淘汰机制?
- redis采用的是定期删除+惰性删除策略。 为什么不用定时删除策略? 定时删除,用一个定时器来负责监视key,过期则自动删除。虽然内存及时释放,但是十分消耗CPU资源。在大并发请求下,CPU要将时间应用在处理请求,而不是删除key,因此没有采用这一策略. 定期删除+惰性删除是如何工作的呢?
- 定期删除,redis默认每个100ms检查,是否有过期的key,有过期key则删除。需要说明的是,redis不是每个100ms将所有的key检查一次,而是随机抽取进行检查(如果每隔100ms,全部key进行检查,redis岂不是卡死)。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。 于是,惰性删除派上用场。也就是说在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除。 采用定期删除+惰性删除就没其他问题了么?
- 不是的,如果定期删除没删除key。然后你也没即时去请求key,也就是说惰性删除也没生效。这样,redis的内存会越来越高。那么就应该采用内存淘汰机制。 在redis.conf中有一行配置
热点数据和冷数据是什么?
- 热点数据,缓存才有价值 对于冷数据而言,大部分数据可能还没有再次访问到就已经被挤出内存,不仅占用内存,而且价值不大。频繁修改的数据,看情况考虑使用缓存 对于上面两个例子,寿星列表、导航信息都存在一个特点,就是信息修改频率不高,读取通常非常高的场景。
- 对于热点数据,比如我们的某IM产品,生日祝福模块,当天的寿星列表,缓存以后可能读取数十万次。再举个例子,某导航产品,我们将导航信息,缓存以后可能读取数百万次。 数据更新前至少读取两次,缓存才有意义。这个是最基本的策略,如果缓存还没有起作用就失效了,那就没有太大价值了。 那存不存在,修改频率很高,但是又不得不考虑缓存的场景呢?
- 有!比如,这个读取接口对数据库的压力很大,但是又是热点数据,这个时候就需要考虑通过缓存手段,减少数据库的压力,比如我们的某助手产品的,点赞数,收藏数,分享数等是非常典型的热点数据,但是又不断变化,此时就需要将数据同步保存到Redis缓存,减少数据库压力。
什么是热Key问题,如何解决热key问题?
- 在Redis中,我们把访问频率高的key,称为热点key
- 如果某一热点key的请求到服务器主机时,由于请求量特别大,可能会导致主机资源不足,甚至宕机,从而影响正常的服务
热点Key是怎么产生的呢?主要原因有两个:
- 用户消费的数据远大于生产的数据,如秒杀、热点新闻等读多写少的场景。
- 请求分片集中,超过单Redi服务器的性能,比如固定名称key,Hash落入同一台服务器,瞬间访问量极大,超过机器瓶颈,产生热点Key问题。
那么在日常开发中,如何识别到热点key呢?
- 凭经验判断哪些是热Key;
- 客户端统计上报;
- 服务代理层上报
如何解决热key问题?
- Redis集群扩容:增加分片副本,均衡读流量;
- 将热key分散到不同的服务器中;
- 使用二级缓存,即JVM本地缓存,减少Redis的读请求。
什么是跳跃表?
- 跳表是可以实现二分查找的有序链表;
- 每个元素插入时随机生成它的level;
- 最底层包含所有的元素;
- 如果一个元素出现在level(x),那么它肯定出现在x以下的level中;
- 每个索引节点包含两个指针,一个向下,一个向右
- 跳表查询、插入、删除的时间复杂度为O(log n),与平衡二叉树接近
redis的hash冲突怎么办?
-
redis通过链式哈希解决冲突,也就是同一个桶里面的元素使用链表保存。但是当链表过长就会导致查找性能变差可能。所以redis为了追求块,使用了两个全局哈希表。用于rehash操作,增加现有的哈希桶数量,减少哈希冲突。
-
开始默认使用【hash表1】保存键值对数据,【hash表2】此刻没有分配空间。当数据越来越多的触发rehash操作,则执行以下操作:
- 给【hash表2】分配更大的空间
- 将【hash表1】的数据重新映射拷贝到【hash表2】中
- 释放【hash表1】的空间
-
值得注意的是,将hash表1的数据重新映射到hash表2的过程并不是一次性的,这样会造成redis阻塞,无法提供服务。
而是采用了渐进式rehash,这样每次处理客户端请求的时候,先从【hash表1】第一个索引开始,将这个位置的所有数据拷贝到【hash表2】中,就这样将 rehash 分散到多次请求过程中,避免耗时阻塞
缓存击穿?
- 缓存击穿是指在使用缓存系统时,一个热门的、经常被访问的数据缓存过期或失效时,大量并发请求同时涌入,直接访问数据库,导致数据库负载剧增,造成系统性能下降甚至崩溃的情况。
解决方案:
-
加锁或互斥机制:在缓存失效时,只允许一个请求访问数据库,并将结果缓存,其他请求等待并从缓存中获取数据。
-
热点数据永远不过期:对于热点数据,可以将其缓存时间设置为永不过期,或者设置一个合理的较长过期时间,确保不会频繁去访问数据库。
-
异步更新缓存:当某个热点数据的缓存过期时,可以使用异步任务来更新缓存,先返回旧的缓存结果给请求,然后在后台异步更新缓存。
-
限流和降级:对于突发的大量请求,可以采取限流策略,限制并发访问的请求数量,或者通过降级策略返回预设的默认值,避免数据库负载过大。
-
前置缓存:在缓存层之前添加一个前置缓存(如CDN等),将请求分摊到多个缓存节点,减轻热点数据的单一缓存节点压。
缓存雪崩?
- 缓存雪崩是指在使用缓存系统时,大量缓存失效或过期,导致原本应该由缓存提供的数据,都需要从数据库中重新加载,从而引发数据库压力剧增、性能下降,甚至系统崩溃的现象。
解决方案:
-
设置随机过期时间:为了避免大量缓存同时失效,可以为不同的缓存设置稍有差异的过期时间,分散缓存过期的可能性。
-
二级缓存机制:使用多级缓存,将数据同时存储到多个缓存层,一级缓存失效时可以从二级缓存中获取数据,避免所有缓存同时失效。
-
并发重建缓存:在缓存失效的时候,通过加锁或者分布式锁的方式,只允许一个请求去加载数据并重新构建缓存,其他请求等待并从缓存中获取数据。
-
缓存预热:在系统低峰期,提前加载热门的缓存数据,避免在高峰期同时加载大量缓存数据。
-
容灾备份:设置多个缓存节点,保证缓存的高可用性,一旦某个缓存节点发生故障,可以快速切换到其他节点。
-
异步更新缓存:对于热点数据,可以使用异步任务来更新缓存,避免大量的请求同时涌入数据库。
Redis多级缓存?
-
传统的缓存策略一般是请求到达Tomcat服务后,先查询Redis,如果未命中则查询数据库,存在下面的问题:
- 请求要经过Tomcat处理,Tomcat的性能成为整个系统的瓶颈。
- Redis缓存失效时,会对数据库产生冲击,比如,春季期间的12306系统,那可是亿级流量,如果只是用Redis,还是有点吃力。所以,就需要多级缓存了
-
多级缓存就是充分利用请求处理的每个环节,分别添加缓存,减轻Tomcat的压力提升性能,
- 首先我们可以先从浏览器客户端进行缓存查询,如果没有则去nginx查询nginx的本地缓存,
- 如果没有则通过Lua脚本编程去查询Redis,
- 如果redis也未命中时则请求到达Tomcat服务器查询进程缓存,
- 如果没有则最后查询数据库,
- 此时大多数的压力都给到了nginx我们需要在nginx内部编程,所以此时我们可以将nginx部署成集群,准备一个单独的nginx做反向代理,代理到多个做缓存的nginx上
总结来说
- 浏览器访问静态资源时候,优先读取浏览器本地缓存;
- 访问非静态资源(ajax查询数据)时候,访问服务端;
- 请求到达Nginx后,优先读取Nginx本地缓存;
- 如果Nginx本地缓存未命中,则去直接查询Redis(不经过Tomcat服务器);
- 如果Redis查询未命中,则查询Tomcat;
- 请求进入Tomcat后,优先查询JVM进程缓存;
- 如果JVM进程缓存未命中,则查询数据库