Redis 大厂应用场景探究 |青训营

226 阅读14分钟

1. Redis简介

Redis是一个开源的高性能键值存储系统,以内存作为数据存储介质,具备快速读写和高并发的特性。Redis支持多种数据结构,包括字符串、列表、哈希表、集合和有序集合,可以灵活应对各种场景的数据存储需求。

Redis的应用场景非常广泛。

  • Redis可以作为缓存使用,将经常访问到的数据存储在内存中,加速读取操作,减轻后端数据库的压力。这在高并发的Web应用中特别有用,可以显著提升系统的响应速度。
  • Redis可以用作消息队列,支持发布/订阅模式和队列模式。它可以实现异步消息传递,解耦系统组件,提高系统的可伸缩性和可靠性。
  • Redis还可以用于实时统计和计数器,统计例如网站的UV、在线用户数、点赞等。
  • Redis还可以用于实现分布式锁、会话管理等。高性能和原子操作保证了可靠性和一致性。

1.1 Redis使用背景

随着互联网的发展,数据逐渐成为海量的形势。

  • 在数据存储上,数据库由单表发展为分库分表,从单机发展为集群。海量数据量增长,使数据库读写数据压力的不断增加,对架构和数据库设计提出了更高的要求。
  • 在数据读取上,可以将数据冷热分离。热数据即为经常访问的数据,将其存放到内存数据库中,可以极大提高读取速度。

1.2 Redis基本工作原理

首先是数据都在内存中读写,redis-client客户端通过RESP协议传入到redis-server服务端的服务进程,之后进行读写,同时会产生日志(AOF文件)到磁盘上。

  • 增量数据保存到AOF文件。若服务机宕机,Redis重启后会读取AOF文件,进行对比,若存在未执行的操作命令,Redis会执行该命令,实现数据不丢失。
  • 全量数据RDB文件。Redis启动后会先去读取RDB文件,再与AOF文件对比。
  • 单进程处理所有操作命令

2. Redis应用场景及常用数据结构

2.1 连续签到 数据结构-sds

  • 可以存储字符串、数字、二进制数据(存储图片、视频)
  • 可以根据实际存储的数据长度进行动态扩容
  • 提供了高效的字符串操作函数,如拼接、截取、比较等
  • 通常和expire配合使用
  • 使用场景: 存储数、Session -SDS的特点有以下几点:

2.2 消息通知 数据结构-list

  • 有序性,列表中的元素按照插入的顺序进行存储,都有索引来表示位置。
  • 允许重复元素,没有唯一性限制。
  • 列表支持在列表的两端进行插入和删除操作。使用LPUSHRPUSH命令在列表的头部和尾部插入元素,使用LPOPRPOP命令从列表的头部和尾部删除元素。
  • 可以使用LINDEX命令按索引获取列表中的元素。如果需要获取多个元素,可以使用LRANGE命令按范围获取。
  • Quicklist双向链表listpack实现。listpack实现一个节点存储多个数据
  • 列表的灵活性和高效性使得它在各种场景下都能发挥作用,例如消息通知、实时排行榜等
  • 使用场景: 消息通知。例如当用户文章更新时,将更新后的文章推送到ES,用户就能搜索到最新的文章数据。

2.3 计数 数据结构-字典dict。

Redis中,字典dict是一种键值对的数据结构,也被称为哈希表-Hash。字典是Redis中最常用的数据结构之一,可以用来存储对象的属性以及其他需要快速获取、修改和删除的数据。若有多项计数需求,即可通过该结构存储。

  • 字典中的数据以键值对的形式进行存储,每个键都对应一个值。键和值都是字符串类型
  • 字典使用了哈希表的实现,可以快速的查找和修改。通过键,可以快速地获取对应的值,也可以修改键对应的值。
  • 字典会根据实际存储的数据量动态扩容。
  • 使用HSET设置键值对,使用HGET获取键对应的值,使用HDEL删除键值对。
  • 字典支持批量操作,可以一次性设置、获取或删除多个键值对。可以使用HMSET一次性设置多个键值对,使用HMGET一次性获取多个键对应的值。
  • 可以使用HKEYS获取字典中的所有键,使用HVALS获取字典中的所有值。
  • 可以使用HLEN命令获取字典中键值对的数量。
  • 字典还提供了其他操作,如判断键是否存在、增加或减少值等。字典的灵活性和高效性可以应用在存储对象的属性、缓存数据等场景。
  • 扩容-rehash: rehash操作是将ht[0]中的数据全部迁移到ht[1]中。数据量小的场景下直接将数据从ht[O]拷贝到ht[1]速度较快。但在例如存有上百万KV的数据量大的场景下,迁移过程将会明显阻塞用户请求。
  • 改善-渐进式rehash: 为避免这种情况,使用rehash方案,每次用户访问时都会迁移少量数据。迁移平摊

2.4 排行榜 数据结构-zset-zskiplist

结合dict,可以通过key操作表

ZINCRBY myzset 2 “ALEX”
ZSCORE myzset “ALEX”

在Redis中,有序跳跃表是实现有序集合的核心数据结构之一。有序跳跃表-ZSkipList是一种有序集合的数据结构,用于实现有序集合-Sorted Set。有序跳跃表是一种基于链表的数据结构,通过使用多级索引来提高查找元素的效率。

  • 有序跳跃表中的元素是有序的,每个元素都有一个分数-score与之关联,根据分数进行排序。分数可以是浮点数或整数。
  • 有序跳跃表通过使用多级索引,可以在O(log N)的时间复杂度内完成插入、删除和查找操作,其中N是有序跳跃表中元素的数量。
  • 有序跳跃表可以动态地调整索引层级,以适应元素的增加或删除,具有较好的扩展性和灵活性。
  • 有序跳跃表可以根据分数范围进行操作,例如获取指定分数范围内的元素,或者获取排名在指定范围内的元素。
  • 支持按排名获取元素,有序跳跃表可以根据元素的排名(即在有序集合中的位置)来获取元素。
  • 支持按成员获取元素:有序跳跃表可以根据成员来获取元素。可以获取指定成员的分数,也可以获取指定成员的排名。
  • 有序跳跃表可以进行合并操作,将多个有序跳跃表合并成一个有序跳跃表。
  • 可以利用有序跳跃表来存储和处理如排行榜、计分系统等应用场景数据。 image.png

2.5 限流

限流-要求1秒内放行的请求为N, 超过N则禁止访问

eg:Key: comment freq_imit time_stamp

对Key调用incr,超过限制N则禁止访问

2.6 分布式锁

主要应用于并发场景,例如秒杀。

要求一次只能有一个协程执行,执行完成后,其它等待中的协程才能执行

Redis的分布式锁通常使用以下两种方式实现:

  • 基于SETNX命令:使用Redis的SETNX可以将一个键设置为对应的值,但只有在该键不存在时才会设置成功。分布式锁可以通过使用SETNX命令来实现,将一个特定的键作为锁,当SETNX命令返回1时,表示获取到了锁;当SETNX命令返回0时,表示锁已经被其他进程或线程持有,获取锁失败。
    • 可以使用redis的setnx实现:原因为:

      • Redis是单线程执行命令

      • setnx只有未设置才能执行成功

  • 基于RedLock算法:RedLock是Redis提供的一种分布式锁算法,使用多个Redis实例来提供更高的可靠性和安全性。RedLock算法通过在不同的Redis实例上创建相同的键,并设置相同的值和相同的过期时间,然后使用大多数原则来判断锁是否获取成功。

分布式锁的实现还需要考虑以下几个问题:

  • 锁的持有和释放:获取锁的进程或线程需要在操作完成后及时释放锁,以允许其他进程或线程获取锁。使用DEL命令来删除锁键。
  • 锁的过期时间:为了防止锁被持有进程或线程长时间占用,需要为锁设置一个合适的过期时间。使用EXPIRE命令为锁键设置过期时间,或者在创建锁时设置一个自动释放锁的机制。
  • 锁的可重入性:有些场景下,同一个进程或线程可能需要多次获取同一个锁,可以使用计数器来记录锁的持有次数,当计数器为0时释放锁。
  • 锁的安全性:分布式锁需要保证在并发环境下的安全性,避免出现死锁等问题。使用Redis的事务来保证锁的原子性操作,以及使用心跳机制来防止锁的过期时间过短导致的问题。

3. Redis注意事项

3.1 大Key、热Key

  • 大Key:
数据类型大Key标准
String类型value的字节数大于10KB即为大Key
Hash/Set/Zset/list等复杂数据结构类型元素个数大于5000个或总value字节数大于10MB即为大Key
  • 大Key危害
    • 读取成本高
    • 容易导致慢查询 (过期、删除)
    • 主从复制异常,服务阻塞
    • 无法正常响应请求
  • 业务侧使用大Key的表现
    • 请求Redis超时报错
  • 处理方法
    • 拆分。将大Key查分成小Key。例如_1,_2,_3
    • 压缩。将value压缩后写入redis,读取时解压后再使用。压缩算法可以是gzip、snappy、Iz4等。【注意】通常情况下一个压缩算法压缩率高、则解压耗时就长。需要对实际数据进行测试后,选择一个合适的算法。 如果存储的是JSON字符串,可以考虑使用MessagePack进行序列化。
    • 集合类结构hash、list、set、set
      • 拆分: 可以用hash取余、位掩码的方式决定放在哪个key中
      • 区分冷热: 如榜单列表场景使用zset,只缓存前10页数据,后续数据走数据库
  • 热Ley:

用户访问的Key的QPS特别高,导致Server实例出现CPU负载突增或者不均的情况 热key没有明确的标准,QPS 超过500就有可能被识别为热Key

  • 处理方法
    • 设置Localcache。
      • 在访问Redis前,在业务服务侧设置Localcache,降低访问Redis的QPS。LocalCache中缓存过期或未命中,则从Redis中将数据更新到LocalCache。Java的Guava、 Golang的Biacache就是LocalCache。
    • 拆分。
      • 将key:value这一个热Key复制写入多份,例如key1:value,key2:value,访问的时候访问多个key,但value是相同的,将qps分散到不同实例上,降低负载。但更新时需要更新多个key,存在数据短暂不一致的风险。
    • 使用Redis代理的热Key承载能力。
      • 字节跳动公司的Redis访问代理具备热Key承载能力。本质结合了“热Key发现”、"LocalCache"两个功能。

3.2 慢查询

造成慢查询的场景:

  • 批量操作。一次性传入过多的key/value,如mset/hmset/sadd/zadd等O(n)操作。
    • 建议单批次不要超过100, 超过100之后性能下降明显。
  • zset大部分命令都是O(log(n),当大小超过5k以上时,zadd/zrem也可能导致慢查询。
  • 操作的单个value过大,超过10KB。避免使用大Key。
  • 对大key的delete/expire操作也可能导致慢查询,Redis4.0之前不支持异步删除unlink, 大key删除会阻塞Redis。

3.3 缓存穿透

缓存穿透: 热点数据查询绕过缓存,直接查询数据库。

缓存穿透的危害:

  • 查询一个一定不存在的数据。通常不会缓存不存在的数据。这类查询请求都会直接到数据库。如果有系统bug或人为攻击,那么容易导致数据库无响应慢甚至宕机。
    • 缓存过期时。在高并发场景下,一个热key如果过期,会有大量请求同时击穿数据库,容易影响数据库性能和稳定。同一时间有大量key集中过期时,也会导致大量请求落到数据库上,导致查询变慢,甚至出现数据库无法响应新的查询。

减少缓存穿透:

  • 缓存空值
    • 一个不存在的useriD。这个id在缓存和数据库中都不存在。则可以缓存一个空值,再查缓存直接返回空值。
  • 布隆过滤器
    • 通过bloom filter算法来存储合法Key,得益于该算法超高的压缩率,只需占用极小的空间就能存储大量key值。

3.4 缓存雪崩

缓存雪崩: 大量缓存同时过期。

缓存雪崩的危害:

  • 性能下降。当大量的请求直接访问后端系统时,系统的负载会急剧增加,导致系统的响应时间延长,甚至出现请求超时的情况。这会导致用户体验下降,系统的吞吐量下降,甚至无法正常提供服务。
  • 数据不一致。当缓存中的数据同时失效或者被清除时,后续的请求会直接访问后端系统获取数据。如果后端系统无法承受瞬时的大量请求,可能会出现数据读取失败或者数据丢失的情况。这会导致系统中的数据不一致,可能会给业务逻辑带来严重的问题。
  • 系统崩溃。当缓存雪崩发生时,大量的请求直接访问后端系统,系统的负载会急剧增加,可能会超出系统的承载能力,导致系统崩溃。系统崩溃会导致业务中断,影响用户的正常使用。

避免缓存穿透:

  • 缓存空值
    • 将缓存失效时间分散开,在原有的失效时间基础上增加一个随机值。例如不同Key过期时间可以设置为 10分1秒过期,10分23秒过期,10分8秒过期。单位秒部分是随机时间
    • 对于热点数据,过期时间尽量设置更长,冷门的数据可以相对设置过期时间更短。
  • 用缓存集群,避免单机宕机造成的缓存雪崩。

4. 结语

在学习Redis大厂使用场景和注意事项的过程中,我深刻认识到Redis作为一款高性能的缓存和存储系统,其在大规模应用中的重要性和价值。无论是在电商平台的秒杀场景中,还是在社交网络的实时消息推送中,Redis都展现出了卓越的性能和可靠性。

首先,Redis的应用场景广泛而丰富。它不仅可以作为缓存层,提升系统的读取性能和响应速度,还可以作为消息队列、计数器、分布式锁等多种用途。这种多功能的设计使得Redis能够满足不同场景下的需求,为业务的高效运行提供了有力支持。

其次,学习Redis的注意事项也给我留下了深刻的印象。在使用Redis时,我们需要注意数据一致性的问题,合理设置缓存的过期时间,避免缓存雪崩和缓存击穿的风险。此外,合理设计数据结构和使用管道批量操作等技巧,也能够有效提升Redis的性能和效率。

最重要的是,Redis的成功应用离不开大厂的经验总结和不断的优化。大厂们在Redis的使用过程中,积累了丰富的经验和技巧,通过不断的实践和优化,使得Redis能够更好地满足业务需求,并保持高可用性和可扩展性。

最后,我深刻认识到了Redis的重要性和价值。它不仅是一个高性能的缓存和存储系统,更是支撑大规模应用的关键组件之一。在今后的学习和实践中,我将更加注重细节和注意事项,充分发挥Redis的优势,为业务的高效运行贡献自己的力量。