Skip to content

设计一个短链系统, 每日写入量百万,访问量千万 ,要求通过短链找到长链 10ms内,同时不能重复插入长链

设计目标

  1. 高吞吐量:支持每日百万级别的短链生成(写入)。
  2. 高并发:支持每日千万级别的短链访问(读取)。
  3. 低延迟:短链重定向(通过短链找长链)的核心耗时在 10ms 以内。
  4. 幂等性:同一个长链接无论请求多少次,都返回相同的短链接,不重复插入。
  5. 高可用:系统能够持续提供服务,无单点故障。

核心架构

一个典型的短链系统架构包含以下几个核心组件:

用户 -> 负载均衡 -> 应用服务集群 -> 缓存集群 -> 数据库集群

短码生成策略

短码(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
  • 缓存内容
    1. 正向映射Key: 短码 (Short Code) -> Value: 长链接 (Long URL)。这是最核心的缓存,用于加速重定向。
    2. 反向映射Key: 长链接的MD5摘要 -> Value: 短码 (Short Code)。用于快速判断长链接是否已存在,防止重复插入。
    3. 布隆过滤器 (Bloom Filter):在缓存中用一个布隆过滤器来存放所有已经生成过的短码,可以快速判断一个短码是否存在,有效防止缓存穿透。

数据库层 (Database)

  • 目的:持久化存储数据,保证数据不丢失。
  • 技术选型:选择 NoSQL 数据库,如 CassandraDynamoDB,因为它们提供了极好的横向扩展能力和低延迟的键值查询性能。如果业务初期规模不大,MySQL 配合分库分表也能满足需求,但NoSQL是更长远的选择。
  • 表设计

    1. 主表 (url_mapping)

      • short_code (主键/分区键): 短码
      • long_url: 长链接
      • created_at: 创建时间等元数据
      • 查询模式SELECT long_url FROM url_mapping WHERE short_code = ?。这是典型的KV查询,性能极高。
    2. 反向索引表 (long_url_index)

      • long_url_md5 (主键/分区键): 长链接的MD5值
      • short_code: 对应的短码
      • 查询模式SELECT short_code FROM long_url_index WHERE long_url_md5 = ?

3. 核心流程设计

A. 创建短链接(写入流程)

此流程必须解决“防重复插入”的问题。

  1. 接收请求:应用服务接收到用户提交的长链接 long_url
  2. 计算摘要:计算 long_url 的 MD5 值,得到 long_url_md5
  3. 查询缓存
    • 首先查询 Redis 的反向映射缓存:GET long_url_md5
    • 如果命中,说明该长链接已存在,直接将对应的 short_code 返回给用户。流程结束。
  4. 查询数据库
    • 如果缓存未命中,则查询数据库中的反向索引表 long_url_index
    • 如果数据库中存在记录,说明长链接已存在。将查询到的 short_code 返回给用户,并回写到 Redis 反向映射缓存中,方便下次快速查找。流程结束。
  5. 生成新短链
    • 如果数据库中也不存在,说明这是一个新的长链接。
    • 调用分布式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 内的延迟要求。

  1. 接收请求:负载均衡将用户的短链接访问请求(如 https://url.short/AbC123)转发到应用服务,服务层提取出 short_codeAbC123
  2. 查询缓存
    • 直接从 Redis 查询正向映射:GET AbC123
    • 缓存命中 (Cache Hit):绝大多数(99%以上)的请求应该在这里命中。直接获取到 long_url,应用服务返回 302/301 重定向响应。此过程通常在 1-2ms 内即可完成,远低于 10ms 的要求。
  3. 查询数据库
    • 缓存未命中 (Cache Miss):如果 Redis 中没有数据(可能是因为缓存过期或服务冷启动),则查询数据库主表 url_mapping
    • 如果数据库中存在记录,获取 long_url,返回给用户,并回写到 Redis 缓存中,以便下次访问。
    • 如果数据库中也不存在,说明这是一个无效的短链接,返回 404 Not Found 页面。