Redis原理与应用实践 | 豆包MarsCode AI刷题

53 阅读13分钟

redis

redis简介

redis是什么,为什么需要redis?

redis是一个用于提高数据读写速度的中间层,它在数据量增加和复杂业务场景出现时,由于MySQL等传统数据库无法满足实时查询和高QPS的需求,因此将经常访问的数据存储在内存中以提升性能。redis不仅解决速度问题,还通过其数据结构和工作原理设计,实现了在服务器重启后数据不丢失的功能。

redis的基本工作原理是怎样的?

redis通过将数据存储在内存中,并采用类似EAFP的协议与客户端进行交互。当接收到写入请求时,redis会将数据写入内存同时生成一条日志记录到磁盘文件(AOF文件),这样即使服务器重启,也可以通过重放AOF文件恢复数据,保证数据不丢失。此外,redis还支持RDB文件,用于快照存储所有数据,在启动时加载RDB文件与AOF文件相结合的方式确保了redis启动后的状态一致性。

redis是否支持并发处理?

redis是一个单进程处理系统,这意味着对于同一用户的不同请求,redis会按照接收到的顺序排队执行,先处理get k2命令,完成后才处理get k1命令。这一特性对于数据同步和分布式锁等操作至关重要。

场景案例

如何利用redis实现连续签到功能?

在连续签到场景中,使用redis中的key-value对来记录每个用户的连续签到天数。具体实现时,使用用户ID作为key,value表示连续签到的天数。为了保证连续性,可以设置value的过期时间为24小时后,并利用redis的增加(increase)操作在用户签到时更新其连续签到天数。如果在24小时内未签到,则value会过期重置为0。

在 Redis 中,如何进行字符串的寻址操作?

在 Redis 中寻址字符串时,首先通过 key 指向一个指针,该指针会先向左移动以获取 flags。通过 flags,可以得知当前数据类型以及后续向左移动几位能获得另一个有效信息,如 length(实际使用的内存长度)。然后,根据 length 向右移动指针,直到找到完整的字符串。

消息通知在 Redis 中的使用场景是什么?

消息通知在 Redis 中的应用场景是当有新文章写入并审核完成后,需要将这篇文章推送至 Elastic Search(ES)中建立索引,以便用户能在搜索引擎上搜索到这篇文章。这时就用到了消息通知机制,可以通过消息队列(例如使用 Redis 的 list 数据结构)来实现文章内容推送至 ES 的通知功能。

为什么要在 Redis 中使用消息队列实现文章推送?

在 Redis 中使用消息队列实现文章推送的原因在于,消息队列能够确保数据的先进先出(FIFO),并且可以监听队列头部,一旦有新的文章(例如文章 ID 为 1111)被推送进来,就可以立即读取文章标题和正文,并将这些信息推送到 ES 进行索引更新。

Redis 中的 list 数据结构是如何实现的?

Redis 中的 list 数据结构基于双向链表,并结合了 list pack 技术来提高内存利用率。每个节点不仅存储一个数据项,还可以通过指向下一个节点的指针来串联多个数据项。同时,每个节点内部还包含一个 list pack 结构,其中包含了总长度、元素数量等信息,使得能够在一个节点内高效地存储和操作多个数据项。

Redis 如何利用哈希数据结构优化查询性能?

Redis 利用哈希数据结构存储用户文章被点赞数等信息,每个用户对应一个哈希表,其中每个点赞记录作为哈希表的一个 key-value 对。这样,当用户访问个人主页查看点赞数时,Redis 能够快速从内存中读取相应的哈希表数据,从而极大地提升了查询速度,相比传统的 MySQL 数据库,其响应时间可以从数十毫秒降至两三毫秒。此外,为了进一步提高写入效率,Redis 还支持 pipeline 技术,将多条记录打包在一起发送给服务器,减少网络传输次数。

redis如何通过AOF文件一次性处理多个数据初始化操作?

在实际工作中,当需要一次性设置或获取多个key时,会利用redis的AOF功能减少网络传输。例如,在演示中,通过执行一条命令,将三个用户的ID数据一次性发送到redis服务器上,AOF文件会输出相应的数据记录。

如何在redis中增加某个字段(如收藏数)的值?

在redis中,可以使用“hincrby”命令来增加哈希结构中某个字段的值。比如,若要增加收藏数,只需指定哈希表名、字段名和要增加的数值即可。

redis的哈希数据结构是如何工作的,以及其读取逻辑是什么?

redis的哈希数据结构基于槽位和单向链表拉链原理。当计算出key对应的槽位后,会在该槽位下的链表中查找value。如果槽位只有一个元素,则直接返回;若存在冲突,则会在同一个槽位下形成链表。当链表过长导致性能下降时,redis会进行扩容,通过迁移数据到新的槽位实现平滑过渡。

redis如何在不阻塞用户请求的情况下进行数据迁移和扩容操作?

redis采用渐进式哈希策略,在进行数据迁移的过程中,首先创建一个新的、未被用户访问的结构(HT1),然后在每次用户访问时顺道将访问的数据迁移到新结构中。当迁移过程完成时,再将HT0指针切换至HT1,实现无缝扩容,保证了在扩容过程中用户的正常访问不受影响。

redis中的zset数据结构如何应用于排行榜场景,以及它的优势是什么?

在redis中,zset数据结构非常适合用来实现排行榜功能,比如游戏中的战力排名。相比使用MySQL存储千万级别的数据并进行排序,redis的zset命令可以高效地对积分变更进行操作,并按得分倒序排列用户。这样,在高并发场景下,redis能有效避免数据库服务器因大量数据排序操作而失去响应,极大地提高了性能和稳定性。

redis是否直接使用跳跃表来存储数据?

redis并没有直接使用跳跃表,而是一个跳跃表加哈希(DICT字典)的组合结构。

哈希在redis中的作用是什么?redis中的跳跃表有哪些特点和限制?

哈希在redis中起到的作用是当我们通过跳跃表找到一个元素时,需要知道这个元素对应的分值或其他属性,通过哈希结构可以快速获取到这些信息。redis中的跳跃表具有层级结构,一般情况下查找一个元素不会超过四层。同时,它是一个双向链表,可以实现按score倒排等功能。此外,redis中还使用了keep list来记录跳跃表的节点信息以及层数,确保查找效率。

在跳跃表中,如何通过层级结构查找特定元素,例如数字7?

在跳跃表中,查找数字7的过程是这样的:首先从头节点开始查找,通过层级结构逐层向下查找。当找到一个大于当前值(比如3)的节点时,会查看其右侧是否有链,如果有则继续沿着这条链向下查找。如果右侧链不存在,则回溯到上一层链,并继续查找。最终,可以在某一层链上找到值为7的元素。

什么是限流功能,以及其在实际场景中的应用?

限流是一种保护网站或服务器免受恶意攻击或过高请求量影响的功能。例如,在掘金网站中,为了防止用户脚本批量发布沸点,可以设定每秒只允许10个请求通过限流机制进行保护。当超过设定的QPS(每秒请求数量),后续请求会被拦截并返回友好的提示信息。

如何利用redis实现简单的限流策略?

通过设置一个key来记录当前一秒内的访问次数,每次请求时更新这个key的值,并检查是否达到预设的限流阈值。如果未达到,则放行请求;若达到或超过,则拦截请求,并等待下一秒生成新的key。

redis如何应用于分布式锁场景,以解决并发抢购商品的问题?

在分布式锁场景下,redis使用set not exist命令(即setNX)来实现分布式锁。多个用户同时尝试获取锁时,只有第一个成功设置key的用户能获取锁并执行业务逻辑,其余用户则会失败并等待一段时间后重新尝试。这样可以保证并发抢购不会导致商品超卖等问题。但需要注意的是,setNX命令存在单线程执行的问题,可能导致在某些异常情况下无法及时释放锁,从而影响高可用性。

注意事项

在字节跳动中使用redis时,有哪些需要注意的事项?

在字节跳动这样大规模的环境中使用redis时,需要注意避免因不当操作导致redis集群崩溃。尤其是要关注“大key”问题,即当set操作中的key或其value大小超过10KB时,视为大P。在使用像哈希、set或list等数据结构时,若元素个数或整体大小大于10兆也会被认为是大key。大key的存在会严重影响redis的处理效率,甚至可能造成整个服务不可用。

大key带来的具体危害有哪些?

大key会导致读取成本增加,引发慢查询问题;同时,由于redis设置了过期时间,过期的key需要被删除,这一操作也可能因数据量过大而变得耗时,进一步加重服务响应延迟。此外,大key还会导致主从切换等高成本事件的发生,甚至可能使整个redis节点不可用。

redis单线程处理命令的过程是怎样的?

redis作为单线程服务器,处理命令的过程包括:读取命令、解析命令、执行命令以及将结果写回给客户端。对于多条命令,这个过程会逐条执行。例如,当接收到get t1和get t2请求时,每个操作都需要消耗一定的时间。如果key很大,会导致处理一条命令所需时间变长,从而影响服务性能。

面对大key时,业务侧会出现哪些表现及应对策略?

业务侧会表现为redis服务调用出现超时现象。应对大key问题,首先应尽量避免在业务设计中产生大key,如果无法避免,可以考虑拆分或压缩数据。拆分逻辑是将大key拆分为多个小key,通过解析字符串构建多个小key来获取完整数据;而压缩则是利用算法(如gzip、snappy或message pack)对value进行压缩存储,以减少空间占用和读取时间,但需要根据实际情况选择合适的压缩算法并进行压力测试以平衡压缩和解压的时间成本。

如何解决热 key 问题以提高系统稳定性?

热key是指用户访问量极高,导致特定key频繁被访问,从而造成某个redis实例压力过大。为解决此问题,可以采用local cache设计方案,在客户端建立局部缓存,将经常访问的热key缓存在本地,以减轻中心化redis服务器的压力。这种设计思想在互联网开发中广泛应用,有助于提升整体系统的可用性和响应速度。

Local Cache的设计原理是什么?

Local Cache是指在当前服务器内存中存储数据,以加快数据读取速度。当接收到请求时,首先从本地内存(即GVM或服务器内存)中获取数据返回给用户,而不是通过网络请求从数据库中取数。为了保证数据一致性,需要设置缓存过期管理机制,一旦缓存数据过期,就应及时从数据库更新。例如,可以将Local Cache设置为2秒过期,从而极大地降低对数据库的压力。

使用Local Cache的优点有哪些?

使用Local Cache后,可以显著减少从数据库读取数据的次数,降低数据库压力,同时通过设置合适的缓存过期时间,能够在短暂时间内保证数据的有效性。此外,一些语言如Java和Go已经提供了现成的本地缓存实现,如Java中的缓存机制和Go的bigcache,开发者可以直接利用这些工具简化实现过程。

第二个解决方案是什么?

第二个解决方案是采用数据拆分技术,即将数据分散存储在不同的slot中,这样请求会落到不同的redis集群上,可以并发执行多个请求,从而提升速度。但这也带来了风险,即更新key时可能需要写入多个redis实例,若其中一个写入失败,会导致数据不完整。因此,在实现数据拆分时,必须确保写入成功,否则会出现数据和实际数据不一致的问题。

如何解决不同业务线使用相同Local Cache的问题?

为了解决每个业务线都需要独立编写Local Cache的问题,字节跳动通过开发redis代理来实现统一的Local Cache能力。这个代理具备路由和热启发现能力,能够根据需求自动将请求转发至对应的redis实例,并在代理自身缓存中快速返回结果,避免了每个业务线都需要自行实现Local Cache的复杂性。

如何处理redis中的慢查询以及批量操作带来的问题?

在使用redis过程中,需要注意慢查询可能导致的性能瓶颈甚至集群崩溃。应限制批量操作的数量,例如使用pipeline批量发送多个key-value对时,建议不超过100个,并严格控制单次操作的大小不超过512K。另外,大量过期或删除操作也可能阻塞redis,因此需要合理设置缓存过期时间和策略,以减少缓存穿透和缓存雪崩的发生,并采取如使用空值缓存、设置不存过滤器等方式避免缓存穿透。同时,利用缓存过期时间分散和redis集群架构来应对缓存雪崩,确保服务持续性。

总结