系统设计高频率元件之 Redis
2025年10月29日
在系统设计中有许多“高频率会用上”的要件。所谓的高频率会用上,是指不论哪类系统,都会有使用这些要件的需求。
其中键值对资料库是指由键值对 (key-value pair) 组成的存储机制。这种资料库在存资料时,会同时存一个独特的 id,然后要取资料时,再根据这个 id 来取。
常见的键值对资料库有 DynamoDB、MongoDB,或是 etcd、Zookeeper,以及 Redis 等等,上面这些都是键值对资料库,但都有不同的特性。这篇会着重在 Redis 这类把资料存在记忆体的键值对资料库。目前社群中相似解决方案有很多,先前 Redis 开源授权风波后,各大厂都有推出相对应的解决方案。不过由于 Redis 在过去被广泛使用,以下都仍以 Redis 这个词来代表相似类型的解决方案。
Redis 的基本介绍与特性
如上面提到,Redis 是一种键值对资料库,是由键值对的方式来存资料,而 Redis 的每个键可以对应的资料结构很多元,基本上最常见的资料结构都有被支援。举例来说,字串 (string)、列表 (lists)、集合 (Sets),甚至是 布隆过滤器 (Bloom Filters) 以及地理空间索引 (Geospatial Indexes)
因为是键值对的操作,所以可以简单地用 SET 来为某个键设定对应的值,以及用 GET 来取得相对应键的值。举例来说,我们可以这样
SET explainThis 1
GET explainThis # 拿到 1
当然除了上面做基本的存跟取之外,Redis 还提供许多方法可以直接用。有兴趣的人可以参考相关文件 [连结],这边就不展开。下面我们将会聚焦谈几个 Redis 常在系统设计会被用的地方。
做为键值对资料库,Redis 的一大特性是把资料存在记忆体,这让 Redis 的速度很快。同时,Redis 的可扩展性很好。Redis 主要透过一种叫 hash slot 的分片方式,把键分到集群中的不同节点,让集群要添加节点变很容易,一个集群每秒处理百万个读取请求都很容易 (AWS 的 ElastiCache for Redis 先前号称做到每个集群能一秒处理五百万个读取请求)。
在初步了解完 Redis 与其特性后,接着让我们来谈一些实际可以应用上 Redis 的地方。
Redis 作为快取 (caching)
上面提到,Redis 又快又能处理大量请求,基于这个特性,Redis 很常会被作为快取来用。服务先查看 Redis,如果有快取且没过期,就直接用快取,就不用跟资料库拿。如果没有,就跟资料库拿,然后再放到快取,并设定快取的存活时间 (TTL)
在上面的流程中,如果快取还没有过期,直接跟 Redis 拿,会比跟资料库拿快很多;而有 Redis 做为快取,除了能够降低延迟外,也可以降低后端资料库的压力。如果在系统设计中遇到有低延迟要求的系统,Redis 可能是解决方案之一。
Redis 作为分布式锁 (distributed locking)
除了快取外,Redis 也很常会被拿来当分布式锁。在一个分散式系统中,如果要保持节点之间的一致性 (consistency),或是要确保同样的操作不会被重复做,这时就可以用 Redis 来做为分布式锁。
让我们用抢票系统作为例子来进一步说明。一般来说,抢票系统对于一致性要求很高,例如,使用者 A 抢到的票,不能同时被其他使用者抢,不然会出现一张票卖两个人,这就麻烦了。这时候就需要一个机制来避免这种重复的问题,而锁 (lock) 是很常会用到的方法。
当提到锁,你可能会想到用资料库的悲观锁 (pessmistic lock),但在抢票系统的情境下,这不是好的做法。因为通常抢票系统要让抢到票的人,保留时间来付款 (例如抢到后保留五分钟,没付款的话就释放掉);而悲观锁适合短时间上锁,要锁五到十分钟不适合用这种方式。
而分布式锁在这种情况就会更适合,具体来说,可以用 Redis 搭配存活时间 (TTL) 来实现。 以抢票系统来说,当使用者 A 抢到票,会用票的 id 以及存活时间 (TTL) 拿到 Redis 的锁。如果 A 有在锁的存活时间内完成付款,则会更新到资料库,确保其他人不会重复购票;假如时间到了没付款,锁就可以自动释放,让票能被其他人抢。
用于“在乎即时”的情境
前面提到 Redis 的特点之一是快,这也让 Redis 经常会用来处理在乎即时的情境。举例来说,在一个有全球排行榜的游戏中,Redis 的快速可以用来确保排名的更新够即时。具体来说,我们可以存每一位玩家的分数,然后透过 Redis 的 ZREVRANGE 来取分数最高的前几名 (例如前十名) 显示在排行榜当中。
甚至目前在这种在乎即时的情境中,会有团队直接拿 Redis 当作主要资料库,而不是当作快取。不过相信读者可能会问,因为前面谈到 Redis 把资料存在记忆体中,换句话说假如机器坏了或重启,这些资料就会丢失,这样作为主要资料库,不是很危险吗?
针对这类问题,目前许多提供类似 Redis 的资料库,都会有相对应的配套。常见的有两种,一种叫 RDB,这种做法是用快照的方式,定期 (例如每隔 5 分钟) 看现在 Redis 中有哪些资料,然后开一个子行程 (child process) 把当下 Redis 有的资料写入一个 rdb 档,然后把该档案存到硬碟中,就不担心资料丢失的问题。当然,这种做法的潜在问题是,如果在拍下一张快照前,跑 Redis 的机器挂掉,那就没办法把资料写到 rdb 档案,因此会有该间隔资料丢失的可能。
而另一种做法叫 AOF,这种做法会记录每一个对 Redis 的写入请求 (write operation),然后把记录存在硬碟。这种做法就能让 Redis 如果机器挂掉,开发者能够透过记录去还原资料。而这种做法,因为要记录每一个写的请求,需要占的硬碟空间大,同时如果要还原也比较花时间。
要选哪一种做法,需要看面对的情境。假如能容忍些微的资料丢失,那可以考虑选 RDB,但假如不能接受任何丢失,则选 AOF (但要承担较大的硬碟空间、还原时间等成本)。又或者可以视需求,考虑采用两种混合搭配的模式。
Redis 其他常见用途与追问的问题
除了上面提到的两种用途外,Redis 也有许多其他在系统设计中常会用到的地方。除此之外,在系统设计中,当提到 Redis 时,经常会有不同的追问题,例如 Redis 做为快取如何扩展到全球多区域、Redis 存记忆体那该如何做到资料持久 (persistence)、Redis 是键值对那该如何处理热键问题 (hot key)
上面这些我们都有在 E+ 的主题文中进一步讨论。有兴趣一读的人,欢迎加入 E+ (详细介绍见此)