重点
redis是单线程还是多线程
无论什么版本,工作线程是单线程
Redis6.0之前是单线程的,Redis6.0之后在网络IO处理方面引入了多线程;
redis内部使用了基于epoll的多路服用,也可以多部署几个redis服务器解决单线程的问题;
redis主要的性能瓶颈是内存和网络;
内存好说,加内存条就行了,而网络才是大麻烦,所以redis6内存好说,加内存条就行了;
而网络才是大麻烦,所以redis6.0引入了多线程的概念,
redis6.0在网络IO处理方面引入了多线程,如网络数据的读写和协议解析等,需要注意的是,执行命令的核心模块还是单线程的。
Redis为什么是单线程的?
- 代码更清晰,处理逻辑更简单;
- 不用考虑各种锁的问题,不存在加锁和释放锁的操作,没有因为可能出现死锁而导致的性能问题;
- 不存在多线程切换而消耗CPU;
什么是缓存穿透?怎么解决
缓存穿透是指查询一个在缓存中和数据库中都不存在的数据,导致每次请求都要查询数据库的问题。
如何解决:
-
使用布隆过滤器:布隆过滤器是一种数据结构,利用极小的内存可以判断大量的数据“一定不存在或者可能存在”。通过哈希函数将数据映射到布隆过滤器中,用户发送的请求会先被布隆过滤器拦截,一定不存在的数据就直接拦截返回,从而避免对数据库进行无效查询。可以使用bitmap做布隆过滤器。
-
空缓存处理:当数据库中不存在该数据时,将空结果(如null)缓存一段时间,避免缓存被频繁访问而导致数据库压力增大。这种方法可以减少对数据库的直接访问,但需要注意不要无限期地缓存空结果,以免浪费缓存空间。
什么是缓存击穿?怎么解决?
当前key是一个热点key(例如一个秒杀活动),并发量非常大。 重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的SQL、多次IO、多个依赖等。 在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃。
为了解决缓存击穿问题,可以采取以下策略:
- 使用互斥锁:在获取数据时,使用分布式锁(如Redis的分布式锁)来控制同时只有一个请求去后端获取数据,其他请求需要等待锁释放。这样可以防止多个请求同时穿透到后端存储系统。
- 设置热点数据永不过期:对于一些热点数据,可以将其永不过期,确保即使数据过期后,仍然可以从缓存中获取。
- 使用缓存预热:在系统启动或低峰时段,预先加载热点数据到缓存中,以减少因缓存过期导致的击穿问题。
什么是缓存雪崩?怎么解决?
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力,造成数据库后端故障,从而引起应用服务器雪崩。
解决:
1、避免缓存集中失效,不同的key设置不同的超时时间
2、增加互斥锁,控制数据库请求,重建缓存。
数据库与缓存不一致如何解决
数据库和缓存之间的不一致通常发生在以下几种情况:
缓存更新滞后:当数据在数据库中被修改后,如果缓存没有及时更新或清除,则会导致后续请求读取到过期的缓存数据。
并发写操作:多个客户端同时对同一数据进行写操作时,可能导致一部分更新丢失或覆盖。
网络延迟:在网络不稳定的情况下,更新命令可能未能及时到达目标系统。
故障恢复:系统出现故障后重启,如果没有正确的恢复机制,可能会导致数据状态不一致。
解决策略
- 延迟双删策略:先删缓存,再更新数据库,然后经过一段时间后再删除缓存中的数据,以避免因网络延迟等原因造成的不一致问题。
-
同步删除:更新请求进来,我们先更新数据库再删除缓存
基础
redis是怎么删除过期key
Redis 使用两种方式来删除过期的 key:
-
惰性删除:当 key 过期时,并不立即删除它,而是等到下次访问这个 key 时再检查它的过期时间,如果已经过期则删除它。
-
定时删除:Redis 默认每 100 毫秒会随机抽查一些设置了过期时间的 key,检查并删除其中已经过期的 key。
这两种方式结合使用,确保过期的 key 最终会被删除,但不会在访问 key 时阻塞当前操作。
缓存是如何淘汰的
Redis缓存淘汰是指当缓存数据超过最大内存限制时,Redis 会根据一定的淘汰策略来删除一些不常用或者不重要的数据,以便为新的数据腾出空间。
Redis 提供了以下几种淘汰策略:
-
noeviction
: 不进行任何淘汰,当内存不足时,会返回错误给客户端。 -
allkeys-lru
: 当内存不足以容纳更多数据时,使用最近最少使用算法(LRU)来淘汰键。 -
allkeys-random
: 随机淘汰键,不考虑使用频率。 -
volatile-lru
: 只对设置了过期时间的键进行LRU淘汰。 -
volatile-random
: 随机淘汰设置了过期时间的键。 -
volatile-ttl
: 淘汰设置了过期时间的键,优先淘汰生存时间(TTL)短的键。
可以通过配置文件或者 CONFIG SET
命令动态设置淘汰策略。
描述一下持久化原理
Redis的持久化主要有两种方式:RDB(Redis DataBase)和AOF(Append Only File)。
-
RDB:定时将内存中的数据快照保存到磁盘的一个压缩二进制文件中。可以配置定时任务来控制快照的频率。当Redis需要持久化数据时,Redis会fork()一个子进程来处理数据保存工作,父进程继续处理客户端请求。
-
AOF:每个写命令都通过append()操作保存到文件中。在服务重启时,通过重放这些命令来恢复数据。AOF文件中的命令通常是不可变的,因此可以确保数据的持久性
AOF
系统提供了 fync 和 fdatasync 两个同步函数,它们可以强制让操作系统立即将缓冲区中的数据写入到硬盘,以及三种策略:
always:同步写回,每个写命令执行完立刻同步地将日志写回磁盘。(性能最差,最多丢失一个写指令的数据)
everysec(默认):每秒执行一次。(是性能和数据安全性的折中方案,最多也就丢一秒的数据)
no:根据操作系统和资源的情况,一定时间执行一次,时间不确定。(性能最好,可能会丢失上次同步AOF文件之后的所有写命令数据)
Redis都有哪些使用场景
- 缓存:Redis可以用作应用程序的缓存层,减少对数据库的读取压力,提高数据访问速度。例如,热点数据缓存(如报表、明星出轨等)、对象缓存、全页缓存等。
- 会话存储:在Web应用中,Redis可以用来存储用户的会话信息,如登录状态、购物车内容等。通过将会话数据存储在Redis中,可以实现无状态的服务器之间共享用户相关的状态数据。
- 分布式锁:在分布式系统中,Redis可以用于实现分布式锁,确保在多个节点之间共享资源的一致性。通过使用Redis的原子操作命令,如SETNX,可以实现分布式锁的功能。
- 排行榜和计数器:Redis支持原子操作,非常适合实现实时排行榜、点赞数、访问计数等功能。利用Redis的INCR命令,可以轻松实现计数器的功能。
- 消息队列:Redis可以用作消息队列系统,用于处理异步任务,例如邮件发送、后台任务处理等。通过使用Redis的列表(List)数据结构,可以实现消息的发布和订阅。
- 实时分析:Redis可以用于实时分析,如用户行为分析、实时统计信息等。利用Redis的高速性能,可以实时处理和分析大量数据。
- 此外,Redis还用于实现全局ID生成、限流、位统计等场景。例如,利用Redis的INCR命令可以实现计数器的功能,用于生成全局唯一的ID;利用限流功能可以控制对资源的访问频率,防止过载;位统计则利用Redis的Bitmap数据结构,以节省空间的方式实现大数据量的统计。
Redis数据类型
- string(字符串): 基本的数据存储单元,可以存储字符串、整数或者浮点数。
- hash(哈希):一个键值对集合,可以存储多个字段。
- list(列表):一个简单的列表,可以存储一系列的字符串元素。
- set(集合):一个无序集合,可以存储不重复的字符串元素。
- zset(sorted set:有序集合): 类似于集合,但是每个元素都有一个分数(score)与之关联。
Redis 有事务吗?
Redis是有事务的,redis中的事务是一组命令的集合,这组命令要么都执行,要不都不执行,
redis事务的实现,需要用到MULTI(事务的开始)和EXEC(事务的结束)命令 ;discard(取消事务)
Redis的事务除了保证所有命令要不全部执行,要不全部不执行外,还能保证一个事务中的命令依次执行而不被其他命令插入。同时,redis的事务是不支持回滚操作的。若在事务队列中存在命令性错误(类似于java编译性错误),则执行EXEC命令时,所有命令都不会执行
扩展
如何进行缓存预热
1、应用启动时预热
- 使用启动监听事件实现缓存预热。
- 使用 @PostConstruct 注解实现缓存预热。
- 使用 CommandLineRunner 或 ApplicationRunner 实现缓存预热。
- 通过实现 InitializingBean 接口,并重写 afterPropertiesSet 方法实现缓存预热。
2、定时任务预热
3、使用消息队列提高预热效率
Redis怎么实现分布式锁
1、使用Lua脚本(包含SETNX + EXPIRE两条指令)
2、使用set 的扩展命令 set id value NX EX 100
- NX :表示key不存在的时候,才能set成功,也即保证只有第一个客户端请求才能获得锁,而其他客户端请求只能等其释放锁,才能获取。
- EX seconds :设定key的过期时间,时间单位是秒。
- PX milliseconds: 设定key的过期时间,单位为毫秒
- XX: 仅当key存在时设置值
3、使用Redisson框架
分布式锁容易出现的问题
- 锁未被释放
- B锁被A锁释放了
- 数据库事务超时
- 锁过期了,业务还没执行完
- Redis主从复制的问题
Redis如何做内存优化?
1、缩短键值的长度
缩短值的长度才是关键,如果值是一个大的业务对象,可以将对象序列化成二进制数组;
首先应该在业务上进行精简,去掉不必要的属性,避免存储一些没用的数据;
其次是序列化的工具选择上,应该选择更高效的序列化工具来降低字节数组大小;
以JAVA为例,内置的序列化方式无论从速度还是压缩比都不尽如人意,这时可以选择更高效的序列化工具,如: protostuff,kryo等
2、共享对象池
对象共享池指Redis内部维护[0-9999]的整数对象池。创建大量的整数类型redisObject存在内存开销,每个redisObject内部结构至少占16字节,甚至超过了整数自身空间消耗。所以Redis内存维护一个[0-9999]的整数对象池,用于节约内存。 除了整数值对象,其他类型如list,hash,set,zset内部元素也可以使用整数对象池。因此开发中在满足需求的前提下,尽量使用整数对象以节省内存。
3、字符串优化
4、编码优化
5、控制key的数量