6.Redis
01.Redis 的存储结构有哪些
五大数据类型 1、String,字符串,是 redis 的最基本的类型,一个 key 对应一个 value。是二进制安全的,最大能存储 512MB。 2、Hash,散列,是一个键值(key=>value)对集合。string 类型的 field 和value 的映射表,特别适合用于存储对象。每个 hash 可以存储 232 -1 键值对(40 多亿) 2、List,列表,是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列边或者尾部 (右边)。最多可存储 232 - 1 元素(4294967295, 每个列表可存储 40 亿) 3、Set,集合, 是 string 类型的无序集合,最大的成员数为 232 -1(4294967295, 每个集合可存储 40 多亿个成员)。 4、Sorted set,有序集合,和 set 一样也是 string 类型元素的集合,且不允许重复的成员。 不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。zset 的成员是唯一的,但分数(score)却可以重复。 三种特殊类型【了解】 1、HyperLogLog 基数统计算法 什么是基数?(不重复的数据元素的个数),比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数 2、Geospatial 地理位置 Redis GEO 主要用于存储地理位置信息,并对存储的信息进行操作,该功能在 Redis 3.2 版本新增3、BitMaps (位图) 位图一般由二进制0,1表示,常用来显示两种状态。例如:打卡,未打卡。迟到,未迟到 如果你是 Redis 高端玩家,你可能玩过 Redis Module ,可以再加上下面几种数据结构: BloomFilter RedisSearch Redis-ML JSON 另外,在 Redis 5.0 增加了 Stream 功能,一个新的强大的支持多播的可持久化的消息队列,提供类似 Kafka 的功能
02.为什么要用 Redis
redis优点: 1 因为是纯内存操作,Redis 的性能非常出色,每秒可以处理超过 10 万次读写操作,是已知性能最快的 Key- Value 数据库。Redis 支持事务 、持久化 2、单线程操作,避免了频繁的上下文切换。 3、采用了非阻塞 I/O 多路复用机制。I/O 多路复用就是只有单个线程,通过跟踪每个 I/O 流的状态,来管理 多个 I/O 流。 高性能: 假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问 的数据存在数缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直 接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!Redis使用场景 数据缓存 会话缓存 时效性数据 访问频率 计数器 社交列表 记录用户判定信息 交集、并集和差集 热门列表与排行榜 最新动态 消息队列 分布式锁 高并发: 直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转 移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。
03.redis的持久化(AOF RDB)
redis有两种持久化方式,aof和rdb,aof相当于日志记录操作命令,rdb相当于数据的快照。安全性来讲由于aof的记录能够精确到秒级追加甚至逐条追加,而rdb只能是全量复制,aof明显高于rdb。但是从性能来讲rdb就略胜一筹,rdb是redis性能最大化的体现,它不用每秒监控是否有数据写入,当达到触发条件后就自动fork一个子进程进行全量更新,速度也很快。容灾回复方面rdb更是能够快速的恢复数据,而aof需要读取再写入,相对慢了很多。Redis 提供了两种持久化的方式,分别是 RDB(Redis DataBase)和 AOF(Append Only File)。 RDB,简而言之,就是在不同的时间点,将 redis 存储的数据生成快照并存储到磁盘等介质上。 AOF,则是换了一个角度来实现持久化,那就是将 redis 执行过的所有写指令记录下来,在下次 redis 重新启动时,只要把这些写指令从前到后再重复执行一遍,就可以实现数据恢复了。 RDB 和 AOF 两种方式也可以同时使用,在这种情况下,如果 redis 重启的话,则会优先采用 AOF 方 式来进行数据恢复,这是因为 AOF 方式的数据恢复完整度更高
04.缓存和数据库双写一致性问题
一致性的问题很常见,因为加入了缓存之后,请求是先从 redis中查询,如果 redis 中存在数据就不 会走数据库了,如果不能保证缓存跟数据库的一致性就会导致请求获取到的数据不是最新的数据。 解决方案: 1、编写删除缓存的接口,在更新数据库的同时,调用删除缓存 的接口删除缓存中的数据。这么做会有耦合高以及调用接口失败的情况。 2、消息队列:ActiveMQ,消息通知。
05.缓存雪崩问题
大量的热点 key 设置了相同的过期时间,导在缓存在同一时刻全部失效,造成瞬时数据库请求量大、压力骤增,引起雪崩,甚至导致数据库被打挂。缓存雪崩其实有点像“升级版的缓存击穿”,缓存击穿是一个热点 key,缓存雪崩是一组热点 key。 解决方案: 过期时间打散:缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。缓存分布式部署:如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中。热点数据不过期:设置热点数据永远不过期。加互斥锁:该方式和缓存击穿一样,按 key 维度加锁,对于同一个 key,只允许一个线程去计算,其他线程原地阻塞等待第一个线程的计算结果,然后直接走缓存即可。
06.缓存穿透
缓存穿透是指 缓存和数据库中都没有的数据,而用户不断发起请求,请求会直接打到数据库上,并且查不到数据,没法写缓存,所以下一次同样会打到数据库上。此时,缓存起不到作用,请求每次都会走到数据库,流量大时数据库可能会被打挂。此时缓存就好像被“穿透”了一样,起不到任何作用。在流量大时,可能 DB 就挂掉了,要是有人利用不存在的 key 频繁攻击我们的应用,这就是漏洞。如下 图:有两种方案可以解决: 方案一,缓存空对象:当从 DB 查询数据为空,我们仍然将这个空结果进行缓存,具体的值需要使用特殊 的标识,能和真正缓存的数据区分开。另外,需要设置较短的过期时间,一般建议不要超过 5 分钟。 方案二,BloomFilter 布隆过滤器:在缓存服务的基础上,构建 BloomFilter 数据结构,在 BloomFilter 中存储对应的 KEY 是否存在,如果存在,说明该 KEY 对应的值为空。那么整个逻辑的 如下:1、根据 KEY 查询缓存。如果存在对应的值,直接返回;如果不存在,继续向下执行。 2、根据 KEY 查询在缓存 BloomFilter 的值。如果存在值,说明该 KEY 不存在对应的值,直接 返回空;如果 不存在值,继续向下执行。 3、查询 DB 对应的值,如果存在,则更新到缓存,并返回该值。如果不存在值,更新到 缓存BloomFilter 中,并返回空。 简述:(使用布隆过滤器存储所有可能访问的 key,不存在的 key 直接被过滤,存在的 key 则再进一步查询缓存和数据库。)
07. 缓存击穿
缓存击穿是指:某一个热点key,缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力解决方案:
- 设置热点数据永远不过期:直接将缓存设置为不过期,然后由定时任务去异步加载数据,更新缓存。这种方式适用于比较极端的场景,例如流量特别特别大的场景,使用时需要考虑业务能接受数据不一致的时间,还有就是异常情况的处理,不要到时候缓存刷新不上,一直是脏数据,那就凉了。
- 加互斥锁:该方式和缓存击穿一样,按 key 维度加锁,对于同一个 key,只允许一个线程去计算,其他线程原地阻塞等待第一个线程的计算结果,然后直接走缓存即可。。在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询操作,其他的线程拿不到锁就阻塞等着,等到第一个线程将数据写入缓存后,直接走缓存。
08.缓存预热
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候, 先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
09.缓存更新
缓存更新除了缓存服务器自带的缓存失效策略之外(Redis 默认的有 6 中策略可供选择),我们还可以 根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种: (1)定时去清理过期的缓存; (2)当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数 据并更新缓存
10.缓存降级
当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然 需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开 关实现人工降级。降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的 (如加入购物车、结算)。
11.Redis 有几种数据淘汰策略
Redis 提供了 6 种数据淘汰策略: volatile-lru volatile-ttl volatile-random allkeys-lru allkeys-random 【默认策略】no-enviction 在 Redis 4.0 后,基于 LFU(Least Frequently Used)最近最少使用算法,增加了 2 种淘汰 策略: volatile-lfu allkeys-lfu 1.noeviction 不进行数据淘汰,也是Redis的默认配置。这时,当缓存被写满时,再有写请求进来,Redis不再提供 服务,直接返回错误。 2.volatile-random 缓存满了之后,在设置了过期时间的键值对中进行随机删除。 3.volatile-ttl 缓存满了之后,会针对设置了过期时间的键值对中,根据过期时间的先后顺序进行删除,越早过期的越先 被删除。 4.volatile-lru 缓存满了之后,针对设置了过期时间的键值对,采用LRU算法进行淘汰。 5.volatile-lfu 缓存满了之后,针对设置了过期时间的键值对,采用LFU的算法进行淘汰。 6.allkeys-random 缓存满了之后,从所有键值对中随机选择并删除数据。 7.allkeys-lru 缓存满之后,使用LRU算法在所有的数据中进行筛选删除。 8.allkeys-lfu 缓存满了之后,使用LFU算法在所有的数据中进行筛选删除。
12.Redis 有几种数据过期策略
Redis 的过期策略,就是指当 Redis 中缓存的 key 过期了,Redis 如何处理 Redis 提供了 3 种数据过期策略: 被动删除:当读/写一个已经过期的 key 时,会触发惰性删除策略,直接删除掉这个过期 key 。 主动删除:由于惰性删除策略无法保证冷数据被及时删掉,所以 Redis 会定期主动淘汰一批已过期的 key 。 主动删除:当前已用内存超过 maxmemory 限定时,触发主动清理策略,即 「数据“淘汰”策略」 。 在 Redis 中,同时使用了上述 3 种策略,即它们非互斥的
13.Redis 支持的 Java 客户端都有哪些
使用比较广泛的有三个 Java 客户端 1、Redisson ,是一个高级的分布式协调 Redis 客服端,能帮助用户在分布式环境中轻松实现一些 Java 的对象 (Bloom filter, BitSet, Set, SetMultimap, ScoredSortedSet, SortedSet, Map, ConcurrentMap, List, ListMultimap, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, ReadWriteLock, AtomicLong, CountDownLatch, Publish / Subscribe, HyperLogLog)。 2、Jedis 是 Redis 的 Java 实现的客户端,其 API 提供了比较全面的 Redis 命令的支持。 Redisson 实现了分布式和可扩展的 Java 数据结构,和 Jedis 相比,Jedis 功能较为简单。 Redisson 的宗旨是促进使用者对 Redis 的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。 3、Lettuce 是一个可伸缩线程安全的 Redis 客户端。多个线程可以共享同一个RedisConnection 。它利用优秀 Netty NIO 框架来高效地管理多个连接 Redis 官方推荐使用 Redisson 或 Jedis
14.如何使用 Redis 实现分布式锁
1、正确的获得锁(set 指令附带 nx 参数,保证有且只有一个进程获得到。) 2、正确的释放锁(使用 Lua 脚本,比对锁持有的是不是自己。如果是,则进行删除来释放) 3、超时的自动释放锁(set 指令附带 expire 参数,通过过期机制来实现超时释放) 4、未获得到锁的等待机制(sleep 或者基于 Redis 的订阅 Pub/Sub 机制。一些业务场景,可能需要支持获得不到锁,直接返回 false ,不等待) 5、【可选】锁的重入性 通过 ThreadLocal 记录是第几次获得相同的锁。 1)有且第一次计数为 1 && 获得锁时,才向 Redis 发起获得锁的操作。 2)有且计数为 0 && 释放锁时,才向 Redis 发起释放锁的操作。 6、锁超时的处理(一般情况下,可以考虑告警 + 后台线程自动续锁的超时时间。通过这样的机制,保证 有且仅有一个线程,正在持有锁。)
15.什么是 Redis 事务
在 Redis 中,MULTI / EXEC / DISCARD / WATCH 这四个命令是我们实现事务的基石 1、可以通过 MULTI 命令开启一个事务 2、可以通过执行 EXEC / DISCARD 命令来提交 / 回滚该事务内的所有操作 3、在 Redis 的事务中,WATCH 命令可用于提供 CAS(check-and-set) 功能,假设我们通过 WATCH 命令在事务执行之前监控了多个 keys ,倘若在 WATCH 之后有任何 Key 的值发生了变化, EXEC 命令执行的事务都将被放弃,同时返回 nil 应答以通知调用者事务执行失败。
16.Redis集群有哪些方案
1、Redis Sentinel 2、Redis Cluster 3、Twemproxy 4、Codis 5、客户端分片 目前一般在选型上来说: 体量较小时,选择 Redis Sentinel ,单主 Redis 足以支撑业务。 体量较大时,选择 Redis Cluster ,通过分片,使用更多内存。
17.一个 Redis 实例最多能存放多少的 keys?List、Set、Sorted Set 他们最多能存放多少元素
理论上,Redis 可以处理多达 2^32 的 keys ,并且在实际中进行了测试,每个实例至少存放了 2 亿 5 千万的 keys。 任何 list、set、和 sorted set 都可以放 2^32 个元素
18. redis是多线程还是单线程:
命令执行过程都为单线程,6.0之前:链接,传输使用多路复用的模式命令执行过程都为单线程,6.0之后:链接,传输使用多线程的模式
19. Redis三种集群模式
- redis有三种集群模式,其中主从是最常见的模式。
- Sentinel 哨兵模式是为了弥补主从复制集群中主机宕机后,主备切换的复杂性而演变出来的。哨兵顾名思义,就是用来监控的,主要作用就是监控主从集群,自动切换主备,完成集群故障转移。
- cluster 模式是redis官方提供的集群模式,使用了Sharding 技术,不仅实现了高可用、读写分离、也实现了真正的分布式存储。
20. reids主从模式
在 Redis 主从模式下,主库负责处理读写命令以及向从库同步最新数据,从库只负责处理读命令。
21、redis复制原理
Redis 的复制分为两部分操作 同步(SYNC)和 命令传播(command propagate)
- 同步(SYNC)从服务器主动获取 主服务器的数据。保持数据一致。具体实现是,主服务器收到SYNC命令后,生成RDB快照文件,然后发送给从服务器。
- 命令传播(command propagate)主服务器收到客户端修改数据命令后,数据库数据发生变化,同时将命令缓存起来,然后将缓存命令发送到从服务器,从服务器通过载入缓存命令来达到主从数据一致。
- 为什么需要有同步和命令传播的两种复制操作: 当只有同步操作时候,那么在从服务器向主服务器发送SYNC命令时候,主服务器在生成RDB快照文件时候,仍然会收到客户端的命令修改数据状态,这部分数据如果不能传达给从服务器,那么就会出现主从数据不一致的现象。这时候就出现了命令传播,主服务器收到从服务器的SYNC命令后,生成RDB快照文件同时,将此段时间内收到的命令缓存起来,然后使用命令传播的操作发送从服务器。来达到主从数据一致。
22. Redis主从复制原理与优缺点
主从复制,是指将一台 Redis 服务器的数据,复制到其他的 Redis 服务器。数据的复制是单向的,只能由主节点到从节点。默认情况下,每台 Redis 服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。优点:
- 1、实现读写分离,提高了可用性,解决了单机故障
- 2、主从复制期间master和slave都是非阻塞方式,仍然可用。
缺点:
- 1、master宕机期间,需要手动切换主机,同时会有部分数据不能及时同步从服务器,造成数据不一致(需要人工手动介入)
- 2、slave宕机后,多个slave恢复后,大量的SYNC同步会造成master IO压力倍增(可以手动规避启动时间)
- 3、在线扩容较复杂。
23. Sentinel 哨兵模式 cluster模式 介绍
24. Redisson分布式锁原理
25.Redis的应用场景
顶替部分数据库做热点数据持久化,减轻数据库压力; 字符串数字的原子新增可以做分布式 id; list 可以做消息流; hash 可以做购物车; set 可以做关注,可以做抽奖; zset 可以做排行榜;
redis 高级
1、说说redis,了解redis源码吗?
redis 是什么,有哪些特点,架构是,如何实现高并发的,应用场景是?redis源码宏观分为两大部分。
1.Redis是一种开源的、基于内存的数据库系统,它可以用作数据库、缓存和消息中间件。 2.Redis特点是 **内存中存储:快 , 支持数据持久化:RDB和AOF, 支持事务:原子性, 丰富的数据类型:**字符串、列表、集合、有序集合、散列、位图等。 发布/订阅功能主从复制备份和读写分离, **哨兵系统集群:**数据分片和分布式存储, **单线程:**IO多路
1.简介Redis
Redis是一种高性能的开源key-value内存数据库。Redis的特点是速度快,可以处理非常大的数据集,而且具有很强的可扩展性和数据持久化功能。 Redis采用单线程模型,通过异步IO和事件驱动机制实现高并发。其架构包括网络层、客户端请求处理、命令执行引擎、键空间与过期管理、持久化、复制、Sentinel(哨兵)以及集群等模块。
2.Redis特点:
** 1.内存中存储**:数据存储在内存中,读写速度快。
- 支持数据持久化:通过RDB和AOF机制,可以将内存中的数据保存到磁盘。
- 支持事务:提供事务功能,确保操作的原子性。
- 丰富的数据类型:支持字符串、列表、集合、有序集合、散列、位图等。
- 发布/订阅功能:支持消息发布和订阅机制。
- 主从复制:支持数据的备份和读写分离。
- 哨兵系统:监控主服务器状态,自动故障转移。
- 集群:支持数据分片和分布式存储。
- 单线程模型:尽管是单线程,但通过IO多路复用技术实现高并发。
3.Redis架构:
** 1.网络层**:处理客户端的TCP连接和请求。
- 事件循环:使用epoll或kqueue等IO多路复用技术,处理文件事件和时间事件。
- 命令处理:解析和执行Redis命令。
- 数据存储:内存中的数据结构和持久化到磁盘的机制。
- 复制和集群:实现数据的备份和分布式存储。
- 持久化模块:RDB和AOF持久化机制的实现。
- 哨兵模块:监控和故障转移的实现。
4.Redis如何实现高并发:
- 单线程事件循环:Redis使用单线程模型,通过IO多路复用技术(如epoll)来处理大量的并发连接。
- 内存中操作:数据存储在内存中,减少了磁盘I/O操作,提高了处理速度。
- 高效的数据结构:使用高效的数据结构来优化数据操作的性能。
- 无锁编程:避免了多线程编程中的锁竞争问题,减少了上下文切换的开销。
5.Redis应用
- 缓存:Redis可以作为缓存系统,用于存储热点数据,提高应用程序的响应速度。
- 数据库:Redis可以用作数据库,用于存储数据,支持多种数据结构和事务。
- 消息队列:Redis可以作为消息队列,用于异步消息的传递和处理。
- 分布式系统:Redis支持分布式架构,可以用于实现分布式数据存储和分布式计算。
- 监控和日志:Redis可以用于存储监控数据和日志,支持数据的快速查询和分析。
- 排行榜和计数器:实现实时更新的排行榜。
- 实时分析:进行实时数据统计和分析。
- 全页缓存:缓存整个页面或页面片段。
- 分布式锁:实现跨多个应用实例的锁机制。
- 地理位置服务:存储和查询地理位置数据。
6.Redis的源码 Redis由两大部分构成:服务器和客户端,它们通过TCP协议进行通信。
- Redis服务器是用C语言编写的,负责底层数据存储和处理逻辑。(下面详解)
- Redis客户端可以由不同的编程语言实现,提供了与Redis服务器交互的接口。
Redis源码的架构: 核心代码## 辅助模块## 性能优化## 安全性## 可维护性## 测试## 文档和社区支持
7.Redis服务器源码包括以下几个部分: 核心模块
- 协议栈:处理客户端连接和基于RESP的通信。
- 内存管理:高效的内存分配和释放,内存碎片整理。
- 数据结构:实现多种数据类型,如字符串、列表、集合等。
- 命令执行:解析和执行命令,保证原子性和一致性。
辅助模块 (Auxiliary Modules)核心模块
- 持久化:RDB和AOF两种持久化机制。
- 复制:主从复制机制,支持数据备份和扩展读操作。
- 哨兵:Redis Sentinel监控和自动故障转移。
- 集群:Redis Cluster支持数据分片和分布式存储。
- Lua脚本:服务器端Lua脚本执行。
- 发布/订阅:消息传递和通知的发布/订阅模式。
- 事务:支持批量命令的原子性执行。
- 模块系统:动态加载和卸载模块,便于功能扩展。
Redis服务器源码非常庞大和复杂,如果要完整地研究和理解Redis的源码,需要有较强的编程能力和C/C++语言基础。
2.聊聊:为什么使用redis(阿里一面)
核心概念为:并发+性能
在面试中被问到为什么使用Redis,特别是在涉及并发和性能的话题时,通常有几个关键的理由可以作为回答的核心: ** 1.性能: **我们在碰到需要执行耗时特别久,且结果不频繁变动的SQL,就特别适合将运行结果放入缓存。这样, 后面的请求就去缓存中读取,使得请求能够迅速响应。 ** 2.并发: **在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常。这个时候,就需要使用redis 做一个缓冲操作,让请求先访问到redis,而不是直接访问数据库。
3.聊聊:redis 都有哪些数据类型?分别在哪些场景下使用比较合适?(1-3)
4.redis 的过期策略是啥?
5.Redis为什么快呢?
Redis的速度非常快,单机的Redis可以支撑每秒十几万的并发,相对于MySQL来说,性能是MySQL的几十倍。速度快的原因主要有以下几点:
- 完全基于内存操作
- 使用单线程,避免了线程切换和竞态产生的消耗
- 基于非阻塞的IO多路复用机制
- C语言实现,优化过的数据结构
1.基于内存实现
Redis是基于内存的数据库,与磁盘数据库(如MySQL)相比,其访问速度要快得多。MySQL是关系型数据库,主要用于存放持久化数据,将数据存储在硬盘中,读取速度较慢,每次请求访问数据库时,都存在I/O操作。 如果反复频繁访问数据库,会导致高负载。而Redis作为基于内存的缓存数据库,用于存储频繁使用的数据,从而减少访问数据库的次数,提高运行效率。
2.高效的数据结构
Redis底层数据结构一共有六种:简单动态字符串(SDS)、双向链表、压缩列表、哈希表、跳表和整数数组。它们和数据类型的对应关系如下:
2.1 SDS(简单动态字符串)
Redis使用SDS(Simple Dynamic String)结构体来保存字符串,而不是C语言中的字符串。SDS与C字符串的区别主要有:
- 字符串长度:SDS中保存着字符串的长度,可以在常数时间内获取字符串长度。
- 拒绝缓冲区溢出:SDS在修改时会检查空间是否足够,避免缓冲区溢出。
- 减少字符串修改时的内存重新分配次数:SDS实现了空间预分配和惰性空间释放两种优化策略。
- 二进制安全:SDS可以存储二进制数据,不会受到特殊字符的影响。
2.2 哈希表(字典) Redis作为k-v型内存数据库,所有的键值对是用字典来存储。哈希表的特性使得可以在O(1)时间复杂度内获得对应的值。
2.3 跳表 跳表在链表的基础上增加了多级索引来提升查找效率。跳表可以在O(logN)的时间复杂度里查找到对应的节点。
2.4 双向链表
列表(List)更多是被当作队列或栈来使用的。双向链表支持先进先出和先进后出的特性,链表中的每个节点都带有两个指针,可以在O(1)时间复杂度内获取到前后节点。
2.5 压缩列表 压缩列表是Redis为了节约内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型数据结构。压缩列表的内存是连续分配的,遍历速度很快。
3.IO多路复用模型
** IO多路复用**(I/O Multiplexing)是一种在单个线程内同时监控多个文件描述符(如网络连接、文件等)的方法。 当任何一个文件描述符就绪(例如有数据到达可以读,或者可以写数据)时,程序就可以处理相应的IO操作。常见的IO多路复用机制包括select、poll和epoll等。
4.避免上下文切换
Redis使用单线程模型避免了多线程的上下文切换问题。多线程在执行过程中需要进行CPU的上下文切换,而Redis基于内存操作,没有上下文切换,效率更高。多次读写都在一个CPU上,对于内存来说是最佳方案。
6.Redis为什么最开始被设计成单线程的?
Redis 是一个成熟的分布式缓存框架,由多个模块组成,包括网络请求模块、索引模块、存储模块、高可用集群支撑模块和数据操作模块等。
1.单线程与多线程
在 Redis 中,只有网络请求模块和数据操作模块是单线程的,而其他如持久化存储模块和集群支撑模块是多线程的。单线程意味着网络请求模块使用一个线程处理所有网络请求,不需考虑并发安全性,而其他模块则使用多个线程。
2.为什么网络操作和数据存储模块最初没有使用多线程?
多线程的目的是通过并发提升 I/O 和 CPU 的利用率。 Redis 不需要提升 CPU 利用率,因为它的操作主要基于内存,CPU 资源不是性能瓶颈。因此,通过多线程提升 Redis 的 CPU 利用率是没有必要的。虽然多线程可以提升 I/O 利用率,但也带来了并发问题和性能开销。因此,Redis 选择了多路复用 I/O 技术而非多线程技术来提升 I/O 利用率。
3.官方解释
官方 FAQ 提供了进一步解释。一个程序在执行过程中,主要进行读写操作(I/O 操作,包括网络 I/O 和磁盘 I/O)和计算操作(CPU 操作)。因为 Redis 基于内存操作,CPU 成为瓶颈的情况很少见。Redis 的瓶颈更可能是内存大小或网络限制。如果要最大化利用 CPU,可以在一台机器上 启动多个 Redis 实例。
4.Redis 4.0 之后的多线程支持
在 Redis 4.0 之后,除了主线程外,Redis 还引入了后台线程来处理一些较为缓慢的操作,例如清理脏数据、释放无用连接和删除大 Key 等等。
7.redis 的IO处理线程模型
Redis 内部使用文件事件处理器(file event handler),这是其单线程模型的核心。这种模型与 Netty 的 Reactor 反应器模型类似,它利用了 IO 多路复用机制来同时监听多个 socket。这种机制允许 Redis 将产生事件的 socket 压入内存队列中,然后由事件分派器根据 socket 上的事件类型,选择相应的事件处理器进行处理。
1.文件事件处理器结构
文件事件处理器结构由以下四个主要部分组成:
- 多个 socket:Redis 可以同时监听多个 socket。
- IO 多路复用程序:用于监听这些 socket,并将事件放入队列。
- 文件事件分派器:负责从队列中取出 socket,并根据事件类型进行分派。
- 事件处理器:包括连接应答处理器、命令请求处理器、命令回复处理器等,它们负责具体的事件处理工作。
尽管多个 socket可能会并发产生不同的操作,每个操作对应不同的文件事件,但 IO 多路复用程序能够高效地将这些事件统一管理。事件分派器每次从队列中取出一个 socket,然后根据其事件类型,交由相应的事件处理器进行处理。
8.为什么Redis 6.0 引入多线程
Redis 6.0引入多线程主要是为了解决一些性能瓶颈问题。以下是引入多线程的几个关键原因:
- 网络IO瓶颈:尽管Redis使用单线程模型,但网络IO处理成为了性能瓶颈。在高并发情况下,单线程处理网络请求可能会变得不够高效。
- 多路复用技术的局限:Redis之前使用的IO多路复用技术,如
select
,本质上是同步阻塞型的。这意味着在等待数据时,它们会阻塞线程,限制了处理能力。 - 多核CPU优势未充分利用:现代服务器通常配备多个CPU核心,但Redis的单线程模型并未充分利用这些核心,导致CPU资源没有得到最佳利用。
- 提升性能:通过引入多线程,Redis可以并发处理网络请求,减少网络I/O等待时间,从而提升性能。
- 保持核心操作的单线程:Redis 6.0中,数据的读写命令仍然由单线程处理,以保持操作的原子性和避免并发问题。多线程主要用于网络请求的接收、解析和响应发送。
- 整体性能提升:通过这种方式,Redis 6.0旨在提高网络IO处理效率,同时利用多核CPU的优势,从而整体提升性能。
简而言之,这样做的⽬的是因为Redis的性能瓶颈在于⽹络IO⽽⾮CPU,使⽤多线程能提升IO读写的效率,从⽽整体提⾼Redis的性能。
9.Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如何将它们全部找出来?
使用keys指令可以扫出指定模式的key列表。 如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题? 这个时候你要回答redis关键的一个特性: redis的单线程的。 keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。 这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复 概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。
10.聊聊:Redis如何做持久化的?
Redis通过以下两种方式实现持久化:
- bgsave 做镜像全量持久化(rdb)
- aof 做增量持久化
由于bgsave会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据,因此需要AOF来配合使用。在Redis实例重启时,优先使用AOF来恢复内存的状态,如果没有AOF日志,就会使用RDB文件来恢复。
bgsave做全量持久化
RDB是Redis中数据集的时间点快照,在Redis内完成RDB持久化的方法有rdbSave
和rdbSaveBackground
两个函数方法(源码文件rdb.c中),两者差别如下:
- rdbSave:同步执行的方法,调用后立刻启动持久化流程。由于Redis是单线程模型,持久化过程中会阻塞,Redis无法对外提供服务。
- rdbSaveBackground:异步执行的方法,会fork出子进程,真正的持久化过程在子进程中执行(调用rdbSave),主进程继续提供服务。
RDB持久化的触发方式分为手动和自动。手动触发是指通过Redis客户端发起持久化备份指令,常用的指令有save
和bgsave
。
在整个持久化过程中,主进程不进行任何IO操作,全程由子进程完成,确保了极高的性能。如果需要大规模数据恢复且对数据恢复的完整性要求不高,那么RDB方式比AOF方式更高效。但RDB的缺点是最后一次持久化的数据可能会丢失。
aof做增量持久化
AOF(Append Only File)以日志形式记录每个写操作,将Redis执行的所有写操作指令记录下来(读操作不记录)。文件只许追加,不可改写,Redis重启时会读取该文件重新构建数据。AOF默认保存的文件是appendonly.aof
,该文件具有可读性。
AOF的工作原理类似于MySQL的binlog日志语句复制。AOF文件同步有三种同步策略:
- 修改同步
- 每秒同步
- 不主动调用fsync同步
AOF优缺点:
- 利用appendfsync持久化机制,异步操作每秒记录,数据完整性高于RDB。如果一秒宕机,有可能丢失1秒数据。
- AOF文件要远大于RDB文件,恢复速度要慢于RDB。每秒同步策略效率较好,不同步效率和RDB相同。
在Redis实例重启时,优先使用AOF来恢复内存的状态,如果没有AOF日志,就会使用RDB文件来恢复。
11.聊聊:系统化的介绍一下,Redis持久化的机制?
1.Redis的持久化配置
Redis提供两种持久化机制:RDB和AOF。用于在崩溃后恢复数据。RDB和AOF持久化的区别如下:
- RDB持久化:将数据集生成快照并保存为二进制文件
.rdb
。优点是单个文件便于备份,恢复速度快。缺点是数据实时性低,可能会丢失最后一次持久化的数据。 - AOF持久化:以日志形式记录每个写操作。优点是数据实时性高,通过append模式写文件,确保数据一致性。缺点是文件大,恢复速度慢于RDB。
2.RDB持久化功能
RDB可以通过以下三种方式创建:
- 使用
SAVE
命令手动同步创建RDB文件 - 使用
BGSAVE
命令异步创建RDB文件 - 自动创建RDB文件,通过配置文件设定触发条件
配置文件中的自动持久化配置如下:
save 900 1
save 300 10
save 60 10000
这些配置表示在指定时间内数据发生一定次数的改动时自动执行BGSAVE命令。
3.AOF持久化功能
AOF持久化通过记录每个写操作的日志文件实现。AOF文件同步策略有三种:
appendfsync always
:每次有数据修改时都写入AOF文件,最安全但性能较低。appendfsync everysec
:每秒同步一次,性能适中,最多丢失1秒的数据。appendfsync no
:不主动调用fsync,由操作系统决定何时写入硬盘,性能最好但数据丢失量不确定。
推荐将appendfsync
选项设定为everysec
,在Redis做缓存时,即使数据丢失也不会造成影响。
4.RDB和AOF的选择
一般来说,如果需要数据安全性高,可以同时使用RDB和AOF持久化。在Redis重启时会优先载入AOF文件恢复数据,因为AOF保存的数据集通常比RDB完整。如果可以接受数分钟的数据丢失,可以只使用RDB持久化。
12.Redis 集群了解吗?
数据分区:
数据分区 (或称数据分片) 是集群最核心的功能。 集群将数据分散到多个节点,一方面 突破了 Redis 单机内存大小的限制,存储容量大大增加;另一方面 每个主节点都可以对外提供读服务和写服务,大大提高了集群的响应能力。 数据分片是高扩展的基础。
高可用: 集群支持主从复制和主节点的 自动故障转移 (与哨兵类似),当任一节点发生故障时,集群 仍然可以对外提供服务。
13.数据分片(sharding)的基本类型?大致的原理?
数据分片,也称为数据分区,是一种将全量数据根据特定规则分散存储到多个数据库或表的技术。
数据分片主要有以下几种基本类型:
- 范围分片:根据数据的范围将数据分配到不同的节点。
- Key(ID)取模分片:通过Key的数值对节点数量进行取模来分配数据。
- 哈希取余分片:使用哈希算法后对节点数量取余来分配数据。
- 一致性哈希分片:使用一致性哈希算法来分配数据,减少节点变化时的数据迁移。
- 虚拟槽分片:Redis Cluster使用的分片方式,使用固定的哈希槽来分配数据。
1.为什么要进行数据分片?
在处理大规模数据时,单节点可能无法满足性能和存储需求,数据分片可以提高系统的扩展性和可用性。
2.分片方式详解
2.1 Range 分片
按照连续的数据范围来分配数据,适用于按时间或数据顺序访问的场景,但需注意数据倾斜问题。 2.2 ID(Key)取模分片
常用于关系型数据库,通过数据ID对节点数量取模来均匀分配数据。
2.3 哈希取余分片
通过哈希算法分散数据,然后根据节点数量取余数来分配数据,保证数据分布均匀。
优点:配置简单,易于实现。 缺点:节点伸缩时会引起大量数据迁移。
2.4 一致性哈希分片
使用一致性哈希算法,将数据分布在一个虚拟的token环上,每个节点负责一定范围的数据。
- 优点:节点伸缩时,只影响邻近节点的数据迁移。
- 缺点:在数据规模较小时,可能导致某些节点空闲。
2.5 虚拟槽分片
Redis Cluster使用的一种分片方式,通过预设的哈希槽来分配数据。
映射步骤
- 预设16384个哈希槽,平均分配给各个节点。
- 对每个Key进行CRC16哈希运算。
- 哈希结果对16383取余,得到槽编号。
- 根据槽编号将数据发送到相应节点。
- 节点接收数据,如果在自己的槽编号范围内,则保存数据;否则转发到正确节点。
优势
- 节点扩容或缩容时,只需重新分配哈希槽,数据不会丢失。
选择合适的数据分片策略需要根据应用场景、数据特性和系统需求综合考虑,以实现最优的系统性能和可扩展性。
14..能说说Redis集群的原理吗?
Redis集群通过数据分区来实现数据的分布式存储,通过自动故障转移实现高可用。
集群创建
数据分区是在集群创建的时候完成的。设置节点Redis集群一般由多个节点组成,节点数量至少为6个才能保证组成完整高可用的集群。每个节点需要开启配置cluster-enabled yes,让Redis运行在集群模式下。节点握手节点握手是指一批运行在集群模式下的节点通过Gossip协议彼此通信, 达到感知对方的过程。节点握手是集群彼此通信的第一步,由客户端发起命 令:cluster meet{ip}{port}。完成节点握手之后,一个个的Redis节点就组成了一个多节点的集群。 **分配槽(slot)**Redis集群把所有的数据映射到16384个槽中。每个节点对应若干个槽,只有当节点分配了槽,才能响应和这些槽关联的键命令。通过 cluster addslots命令为节点分配槽。 分配槽
故障转移
Redis集群的故障转移和哨兵的故障转移类似,但是Redis集群中所有的节点都要承担状态维护的任务。 故障发现Redis集群内节点通过ping/pong消息实现节点通信,集群中每个节点都会定期向其他节点发送ping消息,接收节点回复pong 消息作为响应。如果在cluster-node-timeout时间内通信一直失败,则发送节 点会认为接收节点存在故障,把接收节点标记为主观下线(pfail)状态。当某个节点判断另一个节点主观下线后,相应的节点状态会跟随消息在集群内传播。通过Gossip消息传播,集群内节点不断收集到故障节点的下线报告。当 半数以上持有槽的主节点都标记某个节点是主观下线时。触发客观下线流程。故障恢复 故障节点变为客观下线后,如果下线节点是持有槽的主节点则需要在它 的从节点中选出一个替换它,从而保证集群的高可用。 故障恢复流程
- 资格检查 每个从节点都要检查最后与主节点断线时间,判断是否有资格替换故障 的主节点。
- 准备选举时间 当从节点符合故障转移资格后,更新触发故障选举的时间,只有到达该 时间后才能执行后续流程。
- 发起选举 当从节点定时任务检测到达故障选举时间(failover_auth_time)到达后,发起选举流程。
- 选举投票 持有槽的主节点处理故障选举消息。投票过程其实是一个领导者选举的过程,如集群内有N个持有槽的主节 点代表有N张选票。由于在每个配置纪元内持有槽的主节点只能投票给一个 从节点,因此只能有一个从节点获得N/2+1的选票,保证能够找出唯一的从节点。
- 替换主节点 当从节点收集到足够的选票之后,触发替换主节点操作。
部署Redis集群至少需要几个物理节点? 在投票选举的环节,故障主节点也算在投票数内,假设集群内节点规模是3主3从,其中有2 个主节点部署在一台机器上,当这台机器宕机时,由于从节点无法收集到 3/2+1个主节点选票将导致故障转移失败。这个问题也适用于故障发现环节。因此部署集群时所有主节点最少需要部署在3台物理机上才能避免单点问题。
15..说说集群的伸缩?
Redis集群提供了灵活的节点扩容和收缩方案,可以在不影响集群对外服务的情况下,为集群添加节点进行扩容也可以下线部分节点进行缩容。其实,集群扩容和缩容的关键点,就在于槽和节点的对应关系,扩容和缩容就是将一部分槽和数据迁移给新节点。 例如下面一个集群,每个节点对应若干个槽,每个槽对应一定的数据,如果希望加入1个节点希望实现集群扩容时,需要通过相关命令把一部分槽和内容迁移给新节点。缩容也是类似,先把槽和数据迁移到其它节点,再把对应的节点下线。
16.如何保证本地缓存和分布式缓存的一致?
PS:这道题面试很少问,但实际工作中很常见。 在日常的开发中,我们常常采用两级缓存:本地缓存+分布式缓存。 所谓本地缓存,就是对应服务器的内存缓存,比如Caffeine,分布式缓存基本就是采用Redis。 那么问题来了,本地缓存和分布式缓存怎么保持数据一致?Redis缓存,数据库发生更新,直接删除缓存的key即可,因为对于应用系统而言,它是一种中心化的缓存。 但是本地缓存,它是非中心化的,散落在分布式服务的各个节点上,没法通过客户端的请求删除本地缓存的key,所以得想办法通知集群所有节点,删除对应的本地缓存key。 可以采用消息队列的方式:
- 采用Redis本身的Pub/Sub机制,分布式集群的所有节点订阅删除本地缓存频道,删除Redis缓存的节点,同事发布删除本地缓存消息,订阅者们订阅到消息后,删除对应的本地key。但是Redis的发布订阅不是可靠的,不能保证一定删除成功。
- 引入专业的消息队列,比如RocketMQ,保证消息的可靠性,但是增加了系统的复杂度。
- 设置适当的过期时间兜底,本地缓存可以设置相对短一些的过期时间。
17.怎么处理热key?
**什么是热Key?**所谓的热key,就是访问频率比较的key。 比如,热门新闻事件或商品,这类key通常有大流量的访问,对存储这类信息的 Redis来说,是不小的压力。 假如Redis集群部署,热key可能会造成整体流量的不均衡,个别节点出现OPS过大的情况,极端情况下热点key甚至会超过 Redis本身能够承受的OPS。 怎么处理热key? 对热key的处理,最关键的是对热点key的监控,可以从这些端来监控热点key:
- 客户端 客户端其实是距离key“最近”的地方,因为Redis命令就是从客户端发出的,例如在客户端设置全局字典(key和调用次数),每次调用Redis命令时,使用这个字典进行记录。
- 代理端 像Twemproxy、Codis这些基于代理的Redis分布式架构,所有客户端的请求都是通过代理端完成的,可以在代理端进行收集统计。
- Redis服务端 使用monitor命令统计热点key是很多开发和运维人员首先想到,monitor命令可以监控到Redis执行的所有命令。
只要监控到了热key,对热key的处理就简单了:
- 把热key打散到不同的服务器,降低压⼒
- 加⼊⼆级缓存,提前加载热key数据到内存中,如果redis宕机,⾛内存查询
18.缓存预热怎么做呢?
所谓缓存预热,就是提前把数据库里的数据刷到缓存里,通常有这些方法: 1、直接写个缓存刷新页面或者接口,上线时手动操作 2、数据量不大,可以在项目启动的时候自动进行加载 3、定时任务刷新缓存.
19.热点key重建?问题?解决?
开发的时候一般使用“缓存+过期时间”的策略,既可以加速数据读写,又保证数据的定期更新,这种模式基本能够满足绝大部分需求。 但是有两个问题如果同时出现,可能就会出现比较大的问题:
- 当前key是一个热点key(例如一个热门的娱乐新闻),并发量非常大。
- 重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的 SQL、多次IO、多个依赖等。在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃。
怎么处理呢? 要解决这个问题也不是很复杂,解决问题的要点在于:
- 减少重建缓存的次数。
- 数据尽可能一致。
- 较少的潜在危险。
所以一般采用如下方式:
- 互斥锁(mutex key) 这种方法只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可。
- 永远不过期 “永远不过期”包含两层意思:
- 从缓存层面来看,确实没有设置过期时间,所以不会出现热点key过期后产生的问题,也就是“物理”不过期。
- 从功能层面来看,为每个value设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去构建缓存。
20.了解Bigkey 吗?
什么是bigkey? 如何解决bigkey?springboot的使用?
1.什么是BIgKey?
通俗易懂的讲,Big Key就是某个key对应的value很大,占用的redis空间很大,本质上是大value问题。 key往往是程序可以自行设置的,value往往不受程序控制,因此可能导致value很大。 redis中这些Big Key对应的value值很大,在序列化/反序列化过程中花费的时间很大,因此当我们操作Big Key时,通常比较耗时,这就可能导致redis发生阻塞,从而降低redis性能。 BigKey指以Key的大小和Key中成员的数量来综合判定,用几个实际的例子对大Key的特征进行描述:
- Key本身的数据量过大:一个String类型的Key,它的值为5MB
- Key中的成员数过多:一个ZSET类型的Key,它的成员数量为10000个
- Key中成员的数据量过大:一个Hash类型的Key,它的成员数量虽然只有1000个但这些成员的Value值总大小为100MB
在实际业务中,大Key的判定仍然需要根据Redis的实际使用场景、业务场景来进行综合判断。通常都会以数据大小与成员数量来判定。
2.如何解决BigKey
Big Key问题指的是在Redis中单个键(Key)对应的值(Value)过大,这可能导致内存使用效率低下,以及影响Redis的性能和响应速度。以下是几种常见的解决方法:
1. 对大Key进行拆分 将一个Big Key拆分成多个小Key,每个小Key的成员数量或大小都保持在合理范围内。这样做可以降低单个操作的内存和性能负担。
实施步骤:
- 确定合理的拆分标准,例如每个小Key包含的元素数量或大小。
- 重构应用程序逻辑,以支持通过多个小Key进行数据存取。
** 2. 对大Key进行清理** 定期检查并删除不再需要的大Key,释放内存资源。
实施步骤:
- 使用
UNLINK
命令逐步删除大Key,避免阻塞Redis主线程。 - 监控Key的使用情况,及时清理无用或过期的数据。
3. 监控Redis的内存和性能指标 通过监控系统来跟踪Redis的内存使用情况和其他性能指标,以便及时发现潜在的大Key问题。
实施步骤:
- 设置内存使用率和增长速度的警告阈值。
- 使用Redis的监控工具,如
INFO
命令或第三方监控系统。
** 4. 定期清理失效数据** 对于不断增长但具有时效性的数据,定期清理可以防止大量失效数据的堆积。
实施步骤:
- 利用Redis的过期策略,如设置Key的过期时间。
- 通过定时任务清理那些不再需要或过期的数据。
** 5. 压缩value** 使用序列化和压缩技术减小value的大小,但要注意这可能会增加CPU的负担。
实施步骤:
- 选择合适的序列化格式,如JSON、MessagePack等。
- 应用压缩算法,如gzip,但要权衡压缩与解压的性能开销。
** 6. 优化数据结构** 根据数据的使用模式优化数据结构,例如使用更适合的数据类型或结构来减少内存占用。
实施步骤:
- 分析数据访问模式,选择最合适的Redis数据类型。
- 重构数据模型以减少冗余和提高内存效率。
7. 使用更高效的数据编码 利用Redis提供的不同类型的内部编码方式,如使用intset
代替hashtable
存储小整数集合。
实施步骤:
- 根据数据特性选择合适的内部编码。
- 调整Redis配置以优化特定类型的数据存储。
8. 避免过度使用持久化 过度的持久化操作可能会影响性能,特别是在处理大Key时。
实施步骤:
- 根据业务需求调整RDB和AOF的持久化策略。
- 考虑在低峰时段进行持久化操作。
** 结论** 解决Big Key问题需要综合考虑数据模型、访问模式、性能监控和系统配置等多个方面。通过上述方法,可以有效地管理和减小Big Key带来的影响,从而提高Redis的整体性能和稳定性。
3.来个Redis 的String 分析
Redis string 的命令 只能一次设置/查询一个键值对,这样虽然简单,但是效率不高。 为了提高命令的执行效率, Redis 提供了可以批量操作多个字符串的读写命令 MSET/MGET(“M”代表“Many”), 它们允许你一次性设置或查询多个键值对,这样就有效地减少了网络耗时。
Redis 使用标准 C 语言编写,但在存储字符时,Redis 并未使用 C 语言的字符类型, 为了存储字符串,Redis 自定义了一个属于特殊结构 SDS(Simple Dynamic String)即简单动态字符串), SDS 是一个可以修改的内部结构,非常类似于 Java 的 ArrayList。
1.SDS动态字符串 SDS 的结构定义如下:
struct sdshdr{
//记录buf数组中已使用字符的数量,等于 SDS 保存字符串的长度
int len;
//记录 buf 数组中未使用的字符数量
int free;
//字符数组,用于保存字符串
char buf[];
从上述结构体可以看出,Redis string 将字符串存储到字符类型的buf[] 、len、free
2.分配冗余空间 string 采用了预先分配冗余空间的方式来减少内存的频繁分配,如下图所示: 如图 所示,Redis 每次给 string 分配的空间都要大于字符串实际占用的空间,这样就在一定程度上提升了 Redis string 存储的效率,比如当字符串长度变大时,无需再重新申请内存空间。 当字符串所占空间小于 1MB 时,Redis 对字符串存储空间的扩容是以成倍的方式增加的;而当所占空间超过 1MB 时,每次扩容只增加 1MB。Redis 字符串允许的最大值字节数是 512 MB。
4.SpringBoot BigKey的scan扫描实操
SpringBoot 应用中,可是经过用scan,咱们就能够指定有共性的key,并指定一次性查询条件。 演示的代码如下:
@Autowired
private StringRedisTemplate redisTemplate;
public void scanForBigkeys() {
long cursor = ScanOptions.SCAN_POINTER_START;
Set<String> bigkeys = new HashSet<>();
do {
ScanOptions options = ScanOptions.scanOptions()
.match("your_key_pattern*") // 指定key的模式
.count(1000) // 每次迭代扫描的键的数量
.build();
Cursor<String> scanCursor = redisTemplate.executeWithStickyConnection(
(RedisConnection connection, Object[] args) -> connection.scan(cursor, (ScanOptions) args[0])
);
while (scanCursor.hasNext()) {
String key = scanCursor.next();
long sizeInBytes = redisTemplate.getConnectionFactory().getConnection().strlen(key);
if (sizeInBytes > YOUR_BIGKEY_SIZE_THRESHOLD) {
bigkeys.add(key);
}
}
cursor = scanCursor.getPos();
} while (!cursor.equals(0L));
// 处理发现的bigkeys
if (!bigkeys.isEmpty()) {
sendAlert(bigkeys);
}
}
private void sendAlert(Set<String> bigkeys) {
// 发送邮件或钉钉消息
}
这里例子中,是 以 大于 50个字节,就计算为 bigkey. 这个阈值,仅仅是为了演示方便,生产场景,可以设置一个大的阈值,比如,一个String类型的Key,它的阈值为5MB
执行的结果 启动应用,可以得到执行的结果 实验完美成功 生产场景的bigkey 扫描 结合scan + 定时任务的方式, 在 吞吐量的低峰期,进行扫描 发现了bigkey, 可以及时的进行 运维 告警, 发送 邮件通知或者 钉钉企业信息
类似场景,对大量key进行扫描的cluster 在线上有时候,须要对大量key进行扫描(如删除)操做,有几个风险点:
- 一次性查询所指定的key, 如果是使用keys,数量较大可能形成redis服务卡顿,Redis是单线程程序,顺序执行全部指令,其它指令必须等到当前的 keys 指令执行完了才能够继续。
- 从海量的 key 中找出知足特定前缀的 key 上面的场景中,都可以用scan,咱们就能够指定有共性的key,并指定一次性查询条件。
要点是:使用SCAN命令扫描key替代KEYS避免redis服务器阻塞,无坑!
21.说说redis 架构
1.Redis单机: 首先,在使用最简单的单机版 Redis 时,我们遇到了 Redis 故障宕机后数据无法恢复的问题,因此我们引入了「数据持久化」,将内存中的数据保存到磁盘上,以便 Redis 重启后能快速恢复数据。 **2.加入AOF/RDB: **在进行数据持久化时,我们面临如何更高效地将数据保存到磁盘的问题。后来我们发现 Redis 提供了 RDB 和 AOF 两种方案,分别对应数据快照和实时命令记录。当对数据完整性要求不高时,可以选择 RDB 持久化方案;如果对数据完整性要求较高,可以选择 AOF 持久化方案。 **3.混合使用:**但是我们又发现,AOF 文件体积会随着时间增长变得越来越大,此时我们想到的优化方案是,使用 AOF rewrite 的方式对其进行瘦身,减小文件体积,再后来,我们发现可以结合 RDB 和 AOF 各自的优势,在 AOF rewrite 时使用两者结合的「混合持久化」方式,又进一步减小了 AOF 文件体积。 **4.加上副本:**接着,我们发现虽然可以通过数据恢复的方式还原数据,但恢复数据仍需要花费时间,这意味着业务应用仍会受到影响。我们进一步优化,采用「多副本」的方案,让多个实例保持实时同步,当一个实例故障时,可以手动把其他实例提升上来继续提供服务。 **5.哨兵模式:**但是这样也有问题,手动提升实例上来,需要人工介入,人工介入操作也需要时间,我们开始寻找方法使这个流程自动化,因此我们引入了「哨兵」集群。哨兵集群通过互相协商的方式,发现故障节点,并可以自动完成切换,从而大幅降低对业务应用的影响。 **6.分片集群:**最后,我们将关注点放在如何支持更大的写流量上,因此引入了「分片集群」来解决这个问题,让多个 Redis 实例分担写压力。面对更大的流量,我们还可以添加新的实例进行横向扩展,进一步提高集群性能。
通过这些步骤,我们的 Redis 集群能够长期稳定、高性能地为我们的业务提供服务。 在架构演进的过程中, 围绕着「架构设计」的核心思想:
- 高性能:读写分离、分片集群
- 高可用:数据持久化、多副本、故障自动切换
- 易扩展:分片集群、横向扩展
- 高可靠: 写后日志、数据快照
在进行软件架构设计时,您面临的场景是发现问题、分析问题、解决问题,逐步优化和升级您的架构,最终在性能和可靠性方面达到平衡。 尽管各种软件层出不穷,但架构设计的理念不会改变,希望您真正吸收的是这些思想,这样才能做到以不变应万变。
22.redis 优化 了解不,你做过哪些?
Redis 是一个高效的内存数据库,但为了充分发挥其性能和可靠性,进行合理的优化是非常必要的。以下是对 Redis 进行优化的一些关键建议和措施:
1. 使用短的 Key 和精简的 Value
- 短 Key:减少内存占用,缩短哈希表查找时间。虽然要保持 Key 的可读性,但尽量缩短长度。
- 精简 Value:对于可以使用数字或短字符串表示的数据,如性别、状态等,使用数字代替字符串。
2. 避免存储过大的数据
- 大数据分片:避免单个 Key 存储过多数据,可以使用分片技术,将数据分散到多个 Key 中存储。
3. 慎用复杂度高的命令
- Keys *:尽量避免使用 keys * 这种复杂度为 O(n) 的命令。替代方法是使用 SCAN 命令进行迭代查询。
- 其他复杂命令:参考 Redis 命令复杂度](https://redis.io/commands)) 文档,尽量避免使用高复杂度命令。
4. 数据压缩
- 内部编码:Redis 对不同的数据类型有不同的编码方式,自动根据情况调整。可以通过配置参数进行调优,比如
hash-max-ziplist-entries
和hash-max-ziplist-value
。
5. 设置 Key 的有效期
- 过期时间:为临时数据设置过期时间,减少内存占用。避免大量数据同时过期,可以使用不同的过期时间。
6. 内存管理策略
- 回收策略:根据需求选择合适的内存回收策略。常用策略包括:
volatile-lru
:对设置了过期时间的 Key 进行 LRU 回收。allkeys-lru
:对所有 Key 进行 LRU 回收。- 其他策略:
volatile-random
、allkeys-random
、volatile-ttl
、noeviction
。
7. 使用连接池
- 连接池:减少连接创建和释放的开销,提高并发处理能力。
8. 内存优化操作
- Bit 操作:使用 GETRANGE、SETRANGE、GETBIT、SETBIT 进行位级别操作,减少内存占用。
- Hash 操作:尽量使用 Hash 结构进行存储,特别是在有多个字段需要存储时。
9. 关闭持久化(视业务需求)
- 无持久化:在不需要数据持久化的场景下,可以关闭 RDB 和 AOF,获得更高性能。
10. 使用管道(Pipeline)
- 批量操作:使用管道一次性发送多条命令,减少网络开销和 RTT(Round Trip Time)。
@Test public void usePipeline() throws Exception { Jedis jedis = JedisUtil.getJedis(); long start_time = System.currentTimeMillis(); Pipeline pipelined = jedis.pipelined(); for (int i = 0; i < 10000; i++) { pipelined.set("cc_" + i, i + ""); } pipelined.sync(); System.out.println(System.currentTimeMillis() - start_time); }
11. 配置系统参数
- TCP Backlog:增大 /proc/sys/net/core/somaxconn 的值。
echo 511 > /proc/sys/net/core/somaxconn
- 内存分配策略:设置 vm.overcommit_memory = 1。
echo 'vm.overcommit_memory = 1' >> /etc/sysctl.conf sysctl -p
- 关闭透明大页(THP):
echo never > /sys/kernel/mm/transparent_hugepage/enabled
12. 性能分析
- 慢日志:
CONFIG SET slowlog-log-slower-than 10000 CONFIG SET slowlog-max-len 1000 SLOWLOG GET 10
- 基准测试:
redis-cli --intrinsic-latency 120
13. 其他优化措施
- 避免 Big Key:对于大 Key 采用异步删除,防止阻塞主线程。
- 合理配置 AOF:根据业务需要调整 AOF 的同步策略,减少 IO 压力。
- 控制内存占用:避免 Redis 使用 Swap,必要时增加物理内存。
- 多核 CPU 优化:绑定 Redis 进程到物理 CPU 核,提高处理能力。
- 集群配置:在使用主从集群时,控制每个实例的数据量,避免因复制带来的性能问题。
通过这些优化措施,可以显著提升 Redis 的性能和可靠性,确保在高并发和大数据量的场景下仍然能够高效运行。