设计一个短链系统, 每日写入量百万,访问量千万 ,要求通过短链找到长链 10ms内,同时不能重复插入长链
设计目标
- 高吞吐量:支持每日百万级别的短链生成(写入)。
- 高并发:支持每日千万级别的短链访问(读取)。
- 低延迟:短链重定向(通过短链找长链)的核心耗时在 10ms 以内。
- 幂等性:同一个长链接无论请求多少次,都返回相同的短链接,不重复插入。
- 高可用:系统能够持续提供服务,无单点故障。
核心架构
一个典型的短链系统架构包含以下几个核心组件:
用户 -> 负载均衡 -> 应用服务集群 -> 缓存集群 -> 数据库集群
短码生成策略
短码(Short Code)是短链接中的核心部分,例如 https://url.short/AbC123 中的 AbC123。它的生成方式直接影响系统的性能和扩展性。
- 挑战:需要生成全局唯一且不重复的短码,同时要高效且尽可能短。
- 方案:分布式ID生成器(发号器)
- 雪花算法 (Snowflake):这是一个非常成熟的方案。它可以生成一个 64-bit 的 long 类型整数,具有全局唯一、趋势递增的特点。
- 转换:将生成的 64-bit 整数转换为62进制的字符串(
0-9,a-z,A-Z)。一个 64-bit 的整数可以转换为一个 11 位左右的62进制字符串,长度适中且唯一。例如,convert_to_base62(snowflake_id)。
优势: * 性能高:ID在服务本地生成,不依赖于数据库,速度极快。 * 全局唯一:算法本身保证了在分布式环境下ID的唯一性。 * 不重复:生成的ID永不重复。
存储系统
存储系统的选择对于满足10ms的延迟要求至关重要。我们将采用“缓存 + 数据库”的多级存储策略。
缓存层 (Cache)
- 目的:最大化地提升读取性能,将延迟控制在毫秒级。
- 技术选型:Redis。
- 缓存内容:
- 正向映射:
Key: 短码 (Short Code)->Value: 长链接 (Long URL)。这是最核心的缓存,用于加速重定向。 - 反向映射:
Key: 长链接的MD5摘要->Value: 短码 (Short Code)。用于快速判断长链接是否已存在,防止重复插入。 - 布隆过滤器 (Bloom Filter):在缓存中用一个布隆过滤器来存放所有已经生成过的短码,可以快速判断一个短码是否存在,有效防止缓存穿透。
- 正向映射:
数据库层 (Database)
- 目的:持久化存储数据,保证数据不丢失。
- 技术选型:选择 NoSQL 数据库,如 Cassandra 或 DynamoDB,因为它们提供了极好的横向扩展能力和低延迟的键值查询性能。如果业务初期规模不大,MySQL 配合分库分表也能满足需求,但NoSQL是更长远的选择。
-
表设计:
-
主表 (url_mapping):
short_code(主键/分区键): 短码long_url: 长链接created_at: 创建时间等元数据- 查询模式:
SELECT long_url FROM url_mapping WHERE short_code = ?。这是典型的KV查询,性能极高。
-
反向索引表 (long_url_index):
long_url_md5(主键/分区键): 长链接的MD5值short_code: 对应的短码- 查询模式:
SELECT short_code FROM long_url_index WHERE long_url_md5 = ?。
-
3. 核心流程设计
A. 创建短链接(写入流程)
此流程必须解决“防重复插入”的问题。
- 接收请求:应用服务接收到用户提交的长链接
long_url。 - 计算摘要:计算
long_url的 MD5 值,得到long_url_md5。 - 查询缓存:
- 首先查询 Redis 的反向映射缓存:
GET long_url_md5。 - 如果命中,说明该长链接已存在,直接将对应的
short_code返回给用户。流程结束。
- 首先查询 Redis 的反向映射缓存:
- 查询数据库:
- 如果缓存未命中,则查询数据库中的反向索引表
long_url_index。 - 如果数据库中存在记录,说明长链接已存在。将查询到的
short_code返回给用户,并回写到 Redis 反向映射缓存中,方便下次快速查找。流程结束。
- 如果缓存未命中,则查询数据库中的反向索引表
- 生成新短链:
- 如果数据库中也不存在,说明这是一个新的长链接。
- 调用分布式ID生成器获取一个唯一的 ID。
- 将该 ID 转换为62进制,得到新的
short_code。 - 写入数据库:将
(short_code, long_url)存入主表url_mapping,同时将(long_url_md5, short_code)存入反向索引表long_url_index。为保证数据一致性,这两个操作最好在一个事务中完成。 - 写入缓存:将新的映射关系写入 Redis 的正向和反向缓存。
- 返回短码:将新生成的
short_code返回给用户。
B. 访问短链接(读取流程)
此流程必须满足 10ms 内的延迟要求。
- 接收请求:负载均衡将用户的短链接访问请求(如
https://url.short/AbC123)转发到应用服务,服务层提取出short_code为AbC123。 - 查询缓存:
- 直接从 Redis 查询正向映射:
GET AbC123。 - 缓存命中 (Cache Hit):绝大多数(99%以上)的请求应该在这里命中。直接获取到
long_url,应用服务返回 302/301 重定向响应。此过程通常在 1-2ms 内即可完成,远低于 10ms 的要求。
- 直接从 Redis 查询正向映射:
- 查询数据库:
- 缓存未命中 (Cache Miss):如果 Redis 中没有数据(可能是因为缓存过期或服务冷启动),则查询数据库主表
url_mapping。 - 如果数据库中存在记录,获取
long_url,返回给用户,并回写到 Redis 缓存中,以便下次访问。 - 如果数据库中也不存在,说明这是一个无效的短链接,返回 404 Not Found 页面。
- 缓存未命中 (Cache Miss):如果 Redis 中没有数据(可能是因为缓存过期或服务冷启动),则查询数据库主表