redis 为什么这么快

  • 纯内存操作

  • 单线程,避免上下文切换

  • 渐进式rehash、缓存时间戳

渐进式rehash - 数据量变大,涉及到数组的扩容,涉及到元素移动,需要 rehash 所有的key,如果需要一次性操作,那么数据量过大,消耗就比较耗时 - 采用 2 张全局hash表,当需要扩容时,需要将 hash 表1 的数据进行 rehash,拷贝到第二张表中,把一次大量的拷贝的开销,分摊到多次请求中。

缓存时间戳: 经常会使用ttl,如果每次从系统获取,那么比较耗时的,redis有一个定时任务,每毫秒更新一次,获取时间都是从缓存中获取

redis 使用场景

  • 缓存

  • 计数器

  • 分布式会话

  • 排行榜

  • 最新列表

  • 分布式锁

  • 消息队列

redis 为什么不采用多线程

  • cpu 不是瓶颈,受制于内存、网络。

  • 可以通过 pipeline 批量命令,每秒百万级别。

  • 单线程,内部维护比较低。

  • 如果是多线程,线程切换、加解锁,性能消耗,会导致死锁。

redis 6.0 之后为何引入多线程

  • 单线程就够了 数据加载到内存,响应时间 100 纳秒,比较小的数据包,可以到达 8w-19w qps

  • 多线程分摊 io 读写负载,读取还是单线程,为了扛住更多的并发

redis 有哪些高级功能

  • 慢查询

  • pipline 批量处理,节约往返时间

  • watch 可以用于监听某个数据是否被修改

为什么要用redis

  • 高性能 可以將数据库磁盘访问变为内存访问,效率更高

  • 高并发

redis 和 memcached 相对比较有什么优势

  • redis 支持内存、非关系型数据库, memcached 支持内存,支持 key-value

  • redis 支持多种数据类型 String List Set Zset Hash, memcached 支持 文本型、二进制类型

  • redis 支持单个操作、批量操作,支持弱事务、每个类型都有不同的crud,memcached crud以及少量其他命令

  • redis 支持发布、订阅、高可用、单线程操作,memcached支持多线程。

  • redis 支持持久化 RDB AOF,memcached 不支持持久化。

怎么理解 Redis 中的事务

事务表示一组操作,那么全部执行、要么全部不执行。

redis 提供简单事务功能,命令需要放在 multi exec 中间,事务功能很弱,如果存在语法错误,整个事务都无法执行, 如果发生运行时错误,也即使用了错误的命令操作,那么顺序执行的时,已经成功的命令无法回滚。

原理是,在执行multi命令后,redis 会设置成一个特殊的状态,在这个状态下用户执行命令不会被真的执行,而是被缓存起来,直到用户执行 exec 命令为止,才会将缓存中的命令依次执行。

redis 的过期策略以及内存淘汰机制

过期策略 - 定期删除: 采用简单的贪心算法,每秒钟进行10次扫描,不会遍历所有字典的所有key,redis 会将设置了过期时间的key放在一个独立的字典中, 从过期字典中随机 20 个key,删除其中过期的key,如果过期的 key 比率超过1/4,则重复操作。 如果持续扫描字典,直到字典过期 key 变得稀疏,会造成卡顿,所以在开发的过程中需要留意,如果有大批量的 key 过期,需要给过期时间设置一个随机范围,而不能在同一时间过期。 如果有从库,从库不会执行过期扫描,直接接受主库 key 过期,删除指令,因为是异步执行的,存在延迟。 - 惰性删除 访问 key 的时候检查,如果过期了就删除,定期删除会导致 key 没有被及时删除。

淘汰机制 - noeviction: 不执行写,其他正常 - volatile-lru - volatile-ttl - volatile-random - allkeys-lru - allkeys-random

什么是缓存穿透?如何避免?

不正常的用户、黑客,如果缓存查询不到,会直接请求数据库,故意造数据,不在缓存中,直接去访问数据库

解决方案:

Bitmaps:bit 数组,每一个位置只存储 0 和 1, 1 表示存在,0 表示不存在。

布隆过滤器,判断一个元素是否在集合中。二进制数组加一个hash算法。存在误判,不在集合就一定不在集合,

在查询缓存之前,先查询布隆过滤器,判断是否存在,不存在直接返回,存在继续业务操作。

误判问题: - 存在hash冲突,hash值相同,且在数组上,实际不一定存在 - hash值计算不在数组上,那么一定不存在

优化方案: - 增大数组:减少 hash 冲突 - 增加 hash 函数,hash 都存在则存在

什么是缓存雪崩?如何避免?

缓存雪崩:大量的 key 不经过 redis,全部访问数据库

  • redis 宕机,请求全部落到数据库

  • redis 大量 key ttl 失效

解决: - redis 故障:引入集群,保证 redis 高可用,如果并发高,可以限流 hystrix - ttl 尽量分散,加一个随机值

使用redis如何设计分布式锁

锁是为了保证同一时间点,只有一个线程可以处理,在单机情况,可以通过同步机制进行数据加锁,保证同一个时间点只有一个线程可以处理, 但是分布式不再适用,需要借助外部锁进行,所有的服务都可以访问、请求锁,redis 提供了一个互斥能力,setnx, 如果key不存在,则设置值,否则什么也不做。 可以通过这个命令,达到互斥,实现一个分布式锁。

但是这个会操作死锁问题,一个程序拿到这个锁,但是由于各种原因,没有释放这个锁,导致其他客户端永远无法获取到这个锁,造成死锁问题。 通过引入 ttl 过期时间,可以解决这个问题,但是存在拿到锁的客户端,操作还没完成,ttl 过期了,可以通过守护线程,定时运行,检查 key 是否过期,如果快过期了, 就重新设置过期时间,进行一个锁续期,业务逻辑可以正常运行。

怎么使用 redis 实现消息队列?

  • 基于 list 的 LPUSH BRPOP,左边放,右边拿,足够简单,延迟几乎为 0,但是要处理空闲连接问题,如果线程一直阻塞,线程闲置久了,服务器会主动断开连接,后面使用会抛出异常,客户端消费需要注意,还有消费确认 ack 麻烦 不能做广播模式,不能重复消费

  • 基于 sorted-set 实现: 用来实现延迟队列,消费者无法阻塞来获取数据,只有通过轮训,不允许重复消费

  • pub/sub 订阅、发布模式:一个消息可以发布到多个消费者,消息即时发送,不用等待消费者等待获取,消息一旦发布、客户端不在线,消息丢失,不能寻回

  • 基于 Stream 类型实现: 消息队列的雏形,可以考虑在生产环境上使用,借鉴了kafka的设计,提供了很多命令操作,本身是一个链表,可以设置长度,存在消息丢失

什么是 bigkey? 会有什么影响?

指key的value所占内存空间比较大,一个String最大512M。

  • 字符串:一般认为超过 10kb,就是bigkey

  • 非字符串: 体现在元素过多

危害: - 内存空间不均匀 - 超时阻塞,操作bigkey比较耗时 - 网络阻塞,每次获取bigkey产生的网络流量较大 - 并不是完全致命,几乎不访问,那么也不重要。不要是热点key。

发现bigkey - redis-cli bigkey - 能做拆分的就拆分 - 能不使用就不用

redis 如何解决 key 冲突

  • 业务隔离,使用不同的库,或者不同的实例

  • key 的设计,保证 key 的唯一性,比如业务模块+系统名称+关键字

在怎么提高缓存命中率

  • 提前加载

  • 增加缓存存储空间,提高缓存数据量,提高命中率

  • 调整缓存存储类型,存储对象用 hash 结构,可以用其他字段进行搜索,而不只是根据 id

  • 提升缓存更新频次

redis 的持久化

RDB: redis database , 快照,存储数据丢失 AOF: 记录每次操作命令,性能没有RDB高

一般采用混合模式,推荐。

为什么 redis 需要把所有的数据放到内存中?

  • 内存中读取数据比磁盘读取快

  • cpu 始终要操作内存,将数据放在内存中,没有上下文切换,能避免再去磁盘读取数据带来的性能消耗

如何保证缓存与数据库双写数据一致性?

双写新增没有问题

  • 先更新缓存、在更新数据库 不考虑 数据不一致

  • 先更新数据库,再更新缓存 不考虑 数据不一致

  • 先删除缓存,后更新数据库 多线程操作也会导致数据不一致 延迟双删、更新数据库、休眠、再删除,休眠时间评估项目耗时,确保再次删除休眠期间产生的脏数据缓存

  • 先更新数据库,后删除缓存

一般情况,删除缓存更新缓存比数据库操作要快,一般先更新数据库,后删除缓存。这种情况导致的不一致只有可能是查询比删除慢,而这种情况很少,同时配合延时双删,可以有效避免缓存不一致情况。

评估项目耗时,加上几百毫秒

redis 集群方案应该怎么做

redis 官方提供了集群方案 redis-cluster,采用虚拟槽分区。范围为 0 - 16383, 采用虚拟一致性 hash 分区,可以把所有的数据映射到一个固定范围的集合中, 槽是集群类数据管理和迁移的基本单位,采用大范围槽的主要目的是为了方便数据拆分和扩展,每个节点会负责一定数量的槽。

  • 16383 控制集群数量,节点数量在1000内,足够了,如果更大,节点之间会 ping/pong 命令,如果节点过多,网络开销比较大

限制 - 批量操作支持有限 - 事务支持有限 - 只能使用一个库 0 - 复制结构只支持 1 层,不支持树型

  • 可以根据 redis 命令手动搭建,不推荐命令

  • 5.0 之前使用 ruby语言编写的脚本

  • 5.0之后放弃ruby脚本,合并到了 redis-cli 中

  • 集群中至少应该有奇数个节点,所以至少3个节点,官方推荐3主3从

启动、配置,核心是分配槽到那些机器上,加入减少会涉及到数据迁移,槽的变化。还有故障恢复、选举等功能。

redis 集群方案中什么情况导致整个集群不可用?

为保证集群的完整新,在所有的槽中任何一个没有指定节点,那么整个集群都不可用。

主节点故障,没有替代方案,整个集群不可用。

可以设置参数 cluster-require-full-coverage 为 no,这样只能是故障节点不可用,其他还是可用的。 特殊情况,设置参数后,半数主机都宕机了,根据分布式的一个经验,集群也不可用。

说一说 redis 哈希槽的概念

分布式数据库需要将整个数据集按照分区规则映射到多个节点,每个节点负责一部分数据。重点是分区规则,常见的有 hash 分区和顺序分区。

hash 分区离散度好,数据分布业务无关,无法顺序访问,顺序分区则刚好相反。

  • 简单hash

  • 一致性hash分区, 每个节点分配一个token,加入节点、删除节点,只会影响相关的节点

  • 虚拟一致性hash分区

  • 虚拟槽分区: 固定范围是 16383

redis 集群会有写操作丢失嘛?为什么?

会,redis 复制中,主节点负责写,然后通过异步写入到从节点,如果主节点突然宕机了,导致命令无法同步到从节点。

158 redis 常见的性能和解决方案有哪些?

  • 持久化问题,设置主从,主节点不做持久化,从节点做持久化

  • 数据比较重要,slave 开启 Aof,每秒同步一次

  • 主从复制,在同一个局域网

  • 尽量避免主库压力很大,增加从库

  • 主从复制 不要采用网状结构,采用线性结构

什么是热点数据,什么是冷数据?

热数据:经常被访问的数据、不断变化的数据(点赞、收藏、排行等等) 冷数据:

什么情况会导致 Redis 阻塞?

  • 客户端阻塞 命令 keys * Hgetall smemebers,时间复杂度On

  • bigkey 删除,释放内存

  • 清空库

  • 日志同步 aof 同步写

  • 从库加载RDB文件