通常来说,单线程的处理能力是要比多线程差的。综合分析有两个方面,第一,Redis大部分都是操作在内存上完成的,再加上采用高效的数据结构,比如哈希表和跳表,来实现高性能的一个重要原因。第二,采用了多路复用机制,使其在网络IO操作中能并发处理大量的客户端请求,实现高吞吐率。使用单线程其实也是避免多线程高并发控制的问题。
单线程编程容易并且更容易维护;Redis的性能瓶颈不是CPU而是内存和网络;多线程情况下会出现并发控制问题以及线程上下文切换问题会影响性能的?
虽然Redis6.0 是支持多线程的,但只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程的,不需要担心线程安全问题。多线程默认关闭的
相关面试题:Redis 内存淘汰机制了解吗?Redis是内存数据库,它的缓存过期策略你是如何理解和配置的?
Redis提供6种数据淘汰机制,在4.0 版本增加两种,官方连接
- eviction:是默认淘汰策略,不进行移除,针对写操作,当达到内存限制只是返回错误信息(实际开发需要更改的)
- volatile-lru:使用LRU算法从设置过期时间的数据,挑出最近最少的数据优先淘汰掉。
- volatile-ttl:过期时间的数据越小越优先淘汰
- volatile-random:随机淘汰设置过期时间的key
- allkeys-lru:该策略和volatile-lru区别就是淘汰key对象是全体的key集合,而不像volatile-lru只淘汰设置过期时间的key。
- allkeys-random:淘汰的策略是随机的key
- volatile-lfu:当达到内存限制时,移除最近最少使用设置过期时间的key
- allkeys-lfu:达到内存限制时,移除最近不经常使用的key
怎么保证 Redis 宕机后重启数据可用进行恢复?宕机如何避免数据丢失?
使用 AOF 和 快照(RDB) 两种持久化机制完成的。
什么是RDB?原理是什么?
RDB是在指定的时间间隔内将内存的数据集快照写入磁盘,恢复时是将快照文件直接读到内存里。
原理:在保存RDB文件时,Redis会单独创建(fork)的一个子进程来进行持久化,此时父进程不需要做其他IO操作。fork是指redis通过创建子进程来进行RDB操作,cow指的是copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。
RDB的缺点:最后一次持久化后的数据可能丢失,所以一般我们都是用RDB做镜像全量持久化,AOF做增量持久化,进行这样一个配合使用。
fork:作用就是复制一个与当前进程一样的进程。新的进程的所有数据都是和原进程一致的,但是是一个全新的进程,并作为原进程的子进程。
什么时候出发RDB?
默认有三种出发方式:
- 1小时内,key改动一次,就进行触发快照机制
- 5分钟内,key改动100次,则触发快照机制
- 1分钟内,key改动10000次,则触发RDB快照机制
RDB 优缺点?
优点:适合大规模的数据恢复;对数据完整性和一致性要求不高
缺点:会丢失最后一次快照后的所有修改;Fork时,内存中的数据被克隆一份,2倍的膨胀性能。
什么是AOF?
与快照持久化相比,AOF持久化实时性更好,它是通过保存Redis服务器所执行的写命令来记录数据库状态,只允许追加文件但不可以修改文件。redis重启后根据保存的记录指令执行完成数据的恢复。
如何配置AOF?
默认关闭的,需要appendonly yes开启,广商默认触发方式有三种
- appendfsync always # 同步持久化每次发生数据变更会被立即立即到磁盘,这种方式性能较差但保证数据的完整性
- appendfsync everysec # 出厂默认推荐,异步操作,每秒记录,如果一秒内宕机,有数据丢失
- appendfsync no
导致缓存和数据库不一致的原因有两种:
- 删除缓存值或更新数据库失败而导致不一致;解决方案:重试机制
- 在删除缓存值、更新数据库时,有其他线程并发读操作,会导致其他线程读到旧值;解决方案:延迟双删
如何保证缓存和数据库数据的⼀致性?
使用Cache Aside pattern(旁路缓存模式),也就是Cache服务当前不可使用导致缓存删除失败,那么隔一段时间再重试,如果多次重试还是失败,那么我们把当前更新失败的Key放入队列,等缓存服务可用之后,在将缓存中对应Key删除即可。
Redis 可以通过 MULTI,EXEC,DISCARD 和 WATCH 等命令来实现事务(transaction)功能。
1、什么是缓存穿透?
大量请求查询缓存,但是缓存没有数据,然后会去数据库请求,数据库也没有数据,这就是缓存穿透问题。解决思路,我们要想办法识别哪些请求的数据,是数据库没有的,然后进行过滤跳。比如黑客攻击。我们可以只将Key存储在缓存,这样就减少内存的浪费,使用bitmap,每次访问数据库前先判断key是否存在,不存在就过滤掉。你也会想到使用HashMap,但是要考虑哈希冲突问题。
2、解决方案
(1)对空值缓存:如果一个查返回的数据为空,不管该数据具体是否存在,都要把这个空值结果进行缓存,设置空结果的过期时间会很短。
(2)设置可访问的名单:使用Bitmaps类型定义一个访问名单,名单的ID作为bitmaps的偏移量,每次访问和bitmap里面的ID进行比较,如果访问不在bitmaps里面,进行拦截。
(3)采用布隆过滤器:Bloom filter,是1970年是布隆提出的,它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)
(4)进行实时监控:当发现Redis命中率急速下降,则进行排查。
1、什么缓存击穿?什么时候会出现缓存击穿?
什么是缓存击穿?某个热点数据在某一时刻失效,然后大量请求直接打到数据库,导致数据库压力增大。(缓存没有,但数据有)
2、解决方案
(1)预先设置热门数据:在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据的key的时长。
(2)实时调整:现场监控那些数据热门,实时调整key的过期时长
(3)使用分布式锁
1、什么事缓存雪崩?
缓存大面积的key同时失效,导致大量请求过来的直接落到数据库上,造成数据库短时间内承受大量请求。因为是key同时失效,导致很多key的并发一起加起来才会是的数据库并发压力过大,解决思路就将并发分散开来。
2、解决方案
(1)构建多级缓存架构:nginx缓存+redis缓存+其他缓存等
(2)使用锁或者队列:用锁或队列方式来保证不会有大量的线程对数据库一次性进行读写操作,但是这种效率低,不适合搞并发。
(3)设置过期标志更新缓存:记录缓存是否有过期,如果过期会触发通知另外线程在后台去更新实际key的缓存
(4)将缓存失效时间分散开:
布隆过滤器(Bloom Filter)是1970年由布隆的提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
使用场景:解决缓存穿透/去重问题,比如推荐系统,每次推荐时都要去掉那些看过的内容的。当用户量很大,系统并发量很高时,如果记录都存在关系数据库,那么数据库抗压肯定有问题的,你会想到用缓存,但是历史记录非常龙大,那就非常浪费存储空间,历史记录是随着时间线性增长的,缓存也撑不了多久。此时布隆过滤器(Bloom Filter)登场。
布隆过滤器是什么?
你可以理解为布隆过滤器是一个可以去重的,但是不怎么精确的set结构,误判概率很小的。
命令:bf.add (添加1个元素) 和 bf.madd (批量添加元素)、bf.exists(查询1个元素是否存在)、bf.mexists批量查询。
原理:
布隆过滤器的原理是,当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。这就是布隆过滤器的基本思想。