207|短链接系统设计:编码算法、冲突处理、存储、缓存与统计

架构设计与系统设计约 10 分钟阅读5,380 字符
知识库文档 208 / 251

一、短链接系统概述

1.1 什么是短链接

短链接(Short URL)是将一个冗长的原始 URL 映射为一个更短的字符串,并通过短链服务完成重定向跳转。典型的短链接如 https://t.cn/A6xYz9Qk,其中 A6xYz9Qk 即为短码(short code),对应的原始链接可能是数百字符的带参数 URL。

短链接的本质是"映射 + 重定向"——维护一个 key-value 映射关系,key 是短码,value 是原始 URL,用户访问短链时服务端查表获取原始 URL 并返回 301/302 重定向。

1.2 核心价值

价值维度 说明
节省空间 短信、微博等有字符限制的场景,长链接占用大量空间
美观简洁 二维码中嵌入短链比长链更稀疏、识别率更高
可追踪 在原始链接上附加统计参数,实现点击监控
可管理 短链可设置过期时间、访问密码、权限控制
品牌化 使用自有域名(如 t.yourcompany.com)增强品牌认知

1.3 核心能力矩阵

一个完整的短链接系统需要具备以下核心能力:

  • 生成:将长 URL 转换为全局唯一的短码
  • 解析:根据短码快速查找原始 URL 并重定向
  • 存储:持久化映射关系,支持海量数据
  • 缓存:高频访问的短链需要多级缓存加速
  • 统计:记录和聚合访问数据,提供分析能力
  • 安全:防止恶意链接、控制访问频率
  • 管理:短链的创建、更新、删除、过期控制

文章配图
文章配图


二、编码算法:从长到短的转换

短链接系统的核心挑战之一是如何将任意长度的 URL 映射为一个短且唯一的字符串。主流方案分为两大类:哈希算法和自增 ID 编码。

2.1 哈希算法方案

基本原理

对原始 URL 计算哈希值(如 MD5、SHA-1、MurmurHash),然后截取前 N 位作为短码。MD5 输出 128 位(32 个十六进制字符),SHA-1 输出 160 位(40 个十六进制字符)。

code
long_url = "https://example.com/very/long/path?param1=value1&param2=value2"
hash_value = MD5(long_url)  # → "e4d909c290d0fb1ca068ffaddf22cbd0"
short_code = hash_value[:7]  # → "e4d909c"

字符集扩展

直接使用十六进制字符(0-9a-f)效率较低。改用 Base62(0-9a-zA-Z,共 62 个字符)可以在相同长度下表达更多组合:

  • 7 位十六进制:16^7 ≈ 2.68 亿
  • 7 位 Base62:62^7 ≈ 3.52 万亿

优缺点分析

优点 缺点
实现简单,无需全局发号器 存在哈希冲突(不同 URL 可能产生相同短码)
相同 URL 始终生成相同短码(幂等) 需要额外的冲突处理逻辑
短码长度固定 7 位以上才有足够容量,不够"短"

2.2 自增 ID + Base62 编码

这是业界最主流的方案,也是 TinyURL、Bitly 等系统的核心思路。

核心流程

文章配图
文章配图

code
1. 全局发号器生成唯一递增 ID → 例如 123456789
2. 将十进制 ID 转换为 62 进制 → 得到 "8m0Kx"
3. 短码 = Base62 编码结果
4. 存储:short_code → long_url 映射

Base62 编码算法

java
public class Base62Encoder {
    private static final String CHARS = 
        "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    
    // 十进制 → Base62
    public static String encode(long id) {
        StringBuilder sb = new StringBuilder();
        while (id > 0) {
            sb.append(CHARS.charAt((int)(id % 62)));
            id /= 62;
        }
        return sb.reverse().toString();
    }
    
    // Base62 → 十进制
    public static long decode(String code) {
        long result = 0;
        for (char c : code.toCharArray()) {
            result = result * 62 + CHARS.indexOf(c);
        }
        return result;
    }
}

关键点:encode 是可逆的,这意味着从短码可以直接反算出 ID,无需额外存储短码到 ID 的映射。这是自增 ID 方案相比哈希方案的重要优势。

短码长度与容量

短码长度 Base62 组合数 可服务量级
5 位 62^5 ≈ 9.16 亿 亿级
6 位 62^6 ≈ 568 亿 百亿级
7 位 62^7 ≈ 3.5 万亿 万亿级
8 位 62^8 ≈ 218 万亿 千万亿级

建议:生产环境使用 7 位短码,兼顾简洁性和容量(3.5 万亿足够支撑绝大多数业务)。

2.3 分布式 ID 生成方案

自增 ID 方案的最大挑战在于:分布式环境下,如何生成全局唯一的递增 ID?

方案一:UUID

code
UUID.randomUUID() → "550e8400-e29b-41d4-a716-446655440000"

不推荐:UUID 是无序的 128 位值,作为主键会导致 B+ 树频繁分裂,写入性能极差。而且 128 位转换为 Base62 后仍有约 22 位字符,不够短。

方案二:雪花算法(Snowflake)

Twitter 开源的分布式 ID 生成算法,生成 64 位长整型:

文章配图
文章配图

code
| 1bit 符号位 | 41bit 时间戳(ms) | 10bit 机器ID | 12bit 序列号 |
  • 41 位时间戳可支撑约 69 年
  • 10 位机器 ID 支持 1024 个节点
  • 12 位序列号支持每毫秒 4096 个 ID

优点:趋势递增、高性能、纯内存计算 缺点:依赖机器时钟,时钟回拨会导致 ID 重复

时钟回拨解决方案:

  1. 等待时钟追上(简单粗暴)
  2. 使用备用 worker ID
  3. 扩展位(牺牲序列号位给时钟回拨计数器)
  4. 引入外部协调服务(ZooKeeper/etcd)

方案三:号段模式(美团 Leaf-segment)

文章配图
文章配图

code
// 从数据库批量获取号段
UPDATE leaf_alloc SET max_id = max_id + step WHERE biz_tag = 'short_url'
// 返回 [max_id - step + 1, max_id] 的号段
  • 每个节点预取一个号段(如 step=1000),本地分配
  • 号段用完再取下一段
  • 即使数据库短暂不可用,本地缓存的号段可继续服务

优点:即使数据库宕机,已取号段仍可用;ID 连续递增 缺点:号段用完后需要数据库交互,存在短暂延迟

双 Buffer 优化:当号段消耗到 10% 时,异步预取下一个号段,保证号段耗尽前新号段已就绪。

方案四:Redis 自增

python
# 单机 Redis
short_id = redis.incr("short_url:id:counter")

# Redis Cluster(使用 Lua 脚本保证原子性)
# 每个分片独立维护计数器

优点:实现简单,Redis 单线程天然原子性 缺点:Redis 持久化可能丢失部分 ID;Redis Cluster 下需要分片策略

2.4 方案对比与选型

维度 哈希截取 自增+Base62 雪花算法 号段模式
实现复杂度 低 中 中 中
冲突概率 有(需处理) 无 无 无
短码长度 固定(较长) 随 ID 增长 固定(Base62 编码后) 固定(Base62 编码后)
分布式支持 天然支持 需额外设计 天然支持 需数据库
性能 低(哈希计算) 高 极高 高
短码可逆 否 是 是 是

推荐方案:雪花算法 + Base62 编码。兼顾高性能、水平扩展和短码可逆性。


三、冲突处理策略

3.1 冲突的来源

在哈希算法方案中,不同 URL 可能产生相同的哈希短码,导致冲突。在自增 ID 方案中,如果 ID 生成器出现时钟回拨或配置错误,也可能产生重复 ID。

冲突的本质:短码空间有限(如 7 位 Base62),而长 URL 空间无限,必然存在碰撞。

3.2 再哈希法

当检测到冲突时,对原始 URL 进行微小变形后重新哈希:

code
short_code = hash(url)
while (exists_in_db(short_code)):
    short_code = hash(url + salt)       // 方案1:加盐重试
    short_code = hash(short_code + url)  // 方案2:链式哈希
    short_code = hash(url) + counter     // 方案3:附加计数器

问题:随着数据量增长,冲突概率增加,重试次数增多,生成性能下降。

3.3 布隆过滤器预判

使用布隆过滤器快速判断短码是否已存在,避免每次都查询数据库:

code
if (bloom_filter.might_contain(short_code)):
    // 可能存在,查询数据库确认
    if (db.exists(short_code)):
        // 确实冲突,重新生成
        short_code = generate_new()
else:
    // 一定不存在,直接使用
    insert_db(short_code, long_url)

布隆过滤器可以减少 90% 以上的无效数据库查询,但需要额外维护布隆过滤器的数据一致性。

3.4 预生成 + 号段分配

这是彻底避免冲突的方案,也是生产环境推荐的做法:

code
1. 离线批量生成短码(如预生成 100 万个),通过唯一索引保证不重复
2. 将短码写入"短码池"(short_code_pool 表)
3. 创建短链时,从池中取一个未使用的短码(SELECT ... FOR UPDATE)
4. 使用后标记为已用
5. 异步补充池中短码
sql
-- 短码池表结构
CREATE TABLE short_code_pool (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    short_code VARCHAR(8) NOT NULL,
    status TINYINT DEFAULT 0,  -- 0:未使用, 1:已使用
    used_at DATETIME,
    UNIQUE KEY uk_code (short_code),
    KEY idx_status (status)
);

-- 原子获取一个未使用短码
START TRANSACTION;
SELECT short_code FROM short_code_pool 
WHERE status = 0 LIMIT 1 FOR UPDATE;
UPDATE short_code_pool SET status = 1, used_at = NOW() 
WHERE short_code = ?;
COMMIT;

3.5 分布式环境下的冲突处理

文章配图
文章配图

在分布式短链生成场景中,冲突处理面临额外挑战:

乐观重试:多个节点同时生成短码,插入数据库时通过唯一索引检测冲突,冲突则重试。适合冲突率低的场景。

预占机制:每个节点预分配一段短码范围(如节点 A 使用 0-999,节点 B 使用 1000-1999),避免跨节点冲突。

一致性哈希路由:根据原始 URL 的哈希值,将请求路由到固定节点,同一 URL 始终由同一节点处理,天然避免跨节点冲突。


四、存储架构设计

4.1 数据模型设计

核心映射表

sql
CREATE TABLE short_url_mapping (
    id BIGINT PRIMARY KEY,
    short_code VARCHAR(8) NOT NULL,
    original_url VARCHAR(2048) NOT NULL,
    expire_at DATETIME,
    creator_id BIGINT,
    status TINYINT DEFAULT 1,  -- 1:有效, 0:无效
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY uk_short_code (short_code),
    KEY idx_expire_at (expire_at),
    KEY idx_creator (creator_id)
);

访问日志表

sql
CREATE TABLE access_log (
    id BIGINT PRIMARY KEY,
    short_code VARCHAR(8) NOT NULL,
    access_time DATETIME NOT NULL,
    ip VARCHAR(45),
    user_agent VARCHAR(512),
    referer VARCHAR(2048),
    country VARCHAR(64),
    city VARCHAR(64),
    device_type VARCHAR(32),
    browser VARCHAR(64),
    os VARCHAR(64),
    KEY idx_short_code_time (short_code, access_time),
    KEY idx_access_time (access_time)
) PARTITION BY RANGE (TO_DAYS(access_time)) (
    PARTITION p202401 VALUES LESS THAN (TO_DAYS('2024-02-01')),
    PARTITION p202402 VALUES LESS THAN (TO_DAYS('2024-03-01'))
    -- ...
);

关键设计决策:

  • original_url 使用 VARCHAR(2048):大多数浏览器和 HTTP 协议限制 URL 不超过 2048 字符
  • 访问日志表使用时间分区:便于按时间范围查询和数据清理
  • 访问日志独立存储:避免影响核心映射表的读写性能

4.2 存储选型

方案一:MySQL

code
┌─────────────────────────────────┐
│        MySQL 分库分表            │
│  ┌─────────┐    ┌─────────┐    │
│  │ db_0    │    │ db_1    │    │
│  │ ├─ m_0  │    │ ├─ m_0  │    │
│  │ ├─ m_1  │    │ ├─ m_1  │    │
│  │ └─ ...  │    │ └─ ...  │    │
│  └─────────┘    └─────────┘    │
│  ┌─────────┐    ┌─────────┐    │
│  │ db_2    │    │ db_3    │    │
│  └─────────┘    └─────────┘    │
└─────────────────────────────────┘
  • 分片键:short_code 的哈希值
  • 分片算法:shard_id = hash(short_code) % shard_count
  • 优点:成熟稳定、事务支持好、运维经验丰富
  • 缺点:分库分表后跨分片查询困难;扩展需要数据迁移

方案二:Redis

python
# 短码 → 原始 URL 映射
redis.setex(f"su:{short_code}", ttl_seconds, original_url)

# 访问计数
redis.hincrby(f"su:stats:{short_code}", "pv", 1)
redis.pfadd(f"su:uv:{short_code}:{date}", ip)  # HyperLogLog 统计 UV
  • 优点:极低延迟、数据结构丰富、天然支持 TTL
  • 缺点:全内存存储,成本高;持久化依赖 RDB/AOF,数据量大时恢复慢

方案三:混合存储(推荐)

code
                ┌──────────────┐
    请求 ──────▶│   API 网关    │
                └──────┬───────┘
                       │
          ┌────────────┴────────────┐
          ▼                         ▼
   ┌──────────────┐         ┌──────────────┐
   │  Redis 热数据 │         │  MySQL 全量   │
   │  (近 7 天)    │         │  (持久化)     │
   └──────────────┘         └──────────────┘
          │                         │
          └────────────┬────────────┘
                       ▼
              ┌─────────────────┐
              │ ClickHouse/日志  │
              │   (统计与分析)   │
              └─────────────────┘
  • 热数据(近期活跃的短链)存储在 Redis 中,提供毫秒级响应
  • 全量数据存储在 MySQL 中,保证持久化和数据完整性
  • 统计日志写入 ClickHouse,支持高效的多维聚合分析
  • Redis 中未命中时回源 MySQL,并异步回填 Redis

4.3 数据过期与清理

python
# 策略 1:Redis TTL 自动过期
redis.setex(f"su:{short_code}", expire_seconds, original_url)

# 策略 2:定时任务清理
# 每天凌晨执行,删除过期映射和归档日志
DELETE FROM short_url_mapping 
WHERE expire_at < NOW() AND expire_at IS NOT NULL
LIMIT 10000;  -- 分批删除,避免长事务

# 策略 3:惰性删除
# 访问时检查是否过期,过期则返回 404 并异步清理
def resolve_short_url(short_code):
    record = cache.get(short_code) or db.get(short_code)
    if record and record.is_expired():
        cache.delete(short_code)
        db.mark_deleted(short_code)
        return None
    return record

文章配图
文章配图

五、缓存体系设计

5.1 缓存层次结构

code
                    ┌──────────────────┐
                    │   CDN / 边缘节点   │  ← 全国/全球就近访问
                    └────────┬─────────┘
                             │
                    ┌────────▼─────────┐
                    │  本地缓存 Caffeine │  ← 进程内,纳秒级
                    │  (maxSize: 10000) │
                    └────────┬─────────┘
                             │
                    ┌────────▼─────────┐
                    │  Redis Cluster    │  ← 毫秒级,分布式共享
                    └────────┬─────────┘
                             │
                    ┌────────▼─────────┐
                    │   MySQL           │  ← 持久化存储
                    └──────────────────┘

5.2 缓存策略

Cache-Aside(旁路缓存)

java
public String resolveUrl(String shortCode) {
    // 1. 查本地缓存
    String url = localCache.get(shortCode);
    if (url != null) return url;
    
    // 2. 查 Redis
    url = redis.get("su:" + shortCode);
    if (url != null) {
        localCache.put(shortCode, url);
        return url;
    }
    
    // 3. 查 MySQL
    ShortUrl record = db.findByShortCode(shortCode);
    if (record != null) {
        // 4. 回写缓存
        redis.setex("su:" + shortCode, ttlSeconds, record.getOriginalUrl());
        localCache.put(shortCode, record.getOriginalUrl());
        return record.getOriginalUrl();
    }
    return null;
}

为什么用 Cache-Aside 而不是 Write-Through?

短链接系统的读写比极高(读远多于写),Cache-Aside 只在读时填充缓存,避免写操作时维护缓存一致性,实现更简单。

5.3 缓存三大问题防护

问题 场景 解决方案
缓存穿透 查询不存在的短码,绕过缓存直接打到 DB 布隆过滤器 + 空值缓存(短 TTL)
缓存击穿 热点短码过期瞬间,大量并发请求打到 DB 互斥锁 + 永不过期 + 异步刷新
缓存雪崩 大量缓存同时过期,DB 压力骤增 过期时间随机化 + 多级缓存 + 限流
java
// 缓存击穿的互斥锁方案
public String resolveUrlWithLock(String shortCode) {
    String url = redis.get("su:" + shortCode);
    if (url != null) return url;
    
    // 获取分布式锁
    String lockKey = "lock:su:" + shortCode;
    if (redis.setnx(lockKey, "1", 10)) {  // 10 秒超时
        try {
            // 双重检查
            url = redis.get("su:" + shortCode);
            if (url != null) return url;
            
            // 查 DB 并回写
            ShortUrl record = db.findByShortCode(shortCode);
            if (record != null) {
                redis.setex("su:" + shortCode, ttlSeconds, record.getOriginalUrl());
                return record.getOriginalUrl();
            }
        } finally {
            redis.del(lockKey);
        }
    } else {
        // 未获取锁,短暂等待后重试
        Thread.sleep(50);
        return resolveUrl(shortCode);
    }
    return null;
}

5.4 热点短链优化

对于营销活动等场景,单个短链可能瞬间产生百万级访问:

  • 多级缓存:本地缓存(Caffeine)→ Redis → MySQL,逐级下沉
  • 本地缓存预热:监控系统检测到短链访问量飙升时,主动推送至各节点本地缓存
  • 广播失效:短链更新时,通过消息队列广播失效指令,通知所有节点清除本地缓存

文章配图
文章配图

六、跳转与高可用设计

6.1 301 vs 302 重定向

301 永久重定向 302 临时重定向
浏览器行为 后续直接访问短链,不再请求服务端 每次访问短链都请求服务端
统计准确性 无法统计后续点击(浏览器缓存) 每次点击都能统计
服务端压力 低 高
短链更新 更新后已缓存用户看不到新目标 每次都能获取最新目标

推荐使用 302:虽然增加服务端压力,但保证了统计准确性和短链可更新性。配合缓存优化,302 的性能开销可控。

6.2 高可用架构

code
        用户请求
           │
    ┌──────▼──────┐
    │   DNS 解析   │
    └──────┬──────┘
           │
    ┌──────▼──────────────────────┐
    │  CDN (边缘节点就近加速)      │
    └──────┬──────────────────────┘
           │
    ┌──────▼──────┐    ┌──────────┐
    │   SLB 负载   │◀───│ 健康检查  │
    └──────┬──────┘    └──────────┘
           │
    ┌──────▼──────────────────────┐
    │  API 网关 (限流/鉴权/日志)    │
    └──────┬──────────────────────┘
           │
    ┌──────▼──────────────────────┐
    │  短链服务集群 (多机房)        │
    │  ┌─────┐ ┌─────┐ ┌─────┐   │
    │  │ 机房A │ │ 机房B │ │ 机房C │   │
    │  └─────┘ └─────┘ └─────┘   │
    └──────┬──────────────────────┘
           │
    ┌──────▼──────────────────────┐
    │  存储层 (Redis + MySQL)      │
    └─────────────────────────────┘

关键高可用措施:

  • 多机房部署:用户就近访问,单机房故障自动切换
  • 限流降级:令牌桶/漏桶算法限制单 IP 和全局 QPS
  • 熔断降级:存储层故障时,返回预设的降级页面
  • 读写分离:MySQL 主从复制,读操作走从库

七、统计分析系统

7.1 统计维度

code
┌─────────────────────────────────────────────────────────┐
│                   短链访问统计维度                         │
├───────────┬───────────┬───────────┬───────────────────────┤
│  基础指标  │  地域分布  │  设备信息  │  来源分析              │
├───────────┼───────────┼───────────┼───────────────────────┤
│  PV       │  国家     │  设备类型  │  直接访问              │
│  UV       │  省份     │  操作系统  │  搜索引擎              │
│  IP       │  城市     │  浏览器   │  社交媒体              │
│  访问时长  │  运营商   │  屏幕分辨率│  外部链接              │
│  跳出率   │          │  网络类型  │  二维码                │
└───────────┴───────────┴───────────┴───────────────────────┘

7.2 数据采集架构

访问日志是海量且对时效性要求较低的数据,不适合直接写入 MySQL。推荐的采集架构:

code
┌──────────┐     ┌──────────┐     ┌──────────┐     ┌──────────────┐
│ 短链服务  │────▶│  Kafka   │────▶│  Flink   │────▶│  ClickHouse  │
│ (异步写入) │     │ (消息队列) │     │ (实时计算) │     │  (OLAP 存储)  │
└──────────┘     └──────────┘     └──────────┘     └──────────────┘
                                            │
                                            ▼
                                     ┌──────────────┐
                                     │   展示/报表    │
                                     │  (Grafana等)  │
                                     └──────────────┘

异步写入:短链服务将访问日志异步写入 Kafka,不阻塞跳转响应。日志丢失不影响核心跳转功能。

实时 + 离线结合:

  • Flink 实时聚合最近 5 分钟/1 小时的 PV/UV
  • Spark 离线计算每日报表和趋势分析
  • ClickHouse 作为 OLAP 引擎,支持秒级多维查询

7.3 UV 统计的近似算法

精确 UV 统计需要存储每个短链的访问 IP 集合,在亿级数据量下成本极高。使用 HyperLogLog 算法可实现误差 1% 以内的近似 UV:

java
// Redis HyperLogLog 统计 UV
redis.pfadd("su:uv:" + shortCode + ":" + date, ip);
long uv = redis.pfcount("su:uv:" + shortCode + ":" + date);

每个 HyperLogLog 仅占用约 12KB 内存,可统计 2^64 个不同元素。


文章配图
文章配图

八、安全与风控

8.1 短链滥用场景

  • 钓鱼攻击:短链隐藏真实目标,诱导用户点击钓鱼网站
  • 恶意跳转:短链指向恶意软件下载、垃圾广告
  • 垃圾信息:利用短链绕过短信/邮件系统的黑名单检测
  • DDoS 利用:大量短链指向同一目标,形成流量攻击

8.2 防护策略

code
            ┌─────────────────────┐
  创建短链   │  1. 域名白名单校验    │
  ─────────▶│  2. 内容安全检测      │──▶ 拦截/放行
            │  3. 频率限制          │
            └─────────────────────┘

            ┌─────────────────────┐
  访问短链   │  1. 安全中间页(可选) │
  ─────────▶│  2. IP 黑名单过滤     │──▶ 跳转/拦截
            │  3. 访问频率限制      │
            └─────────────────────┘

安全中间页(Preview Page):在跳转前展示目标 URL 的预览,用户可以确认后再跳转。这是防止钓鱼链接的有效手段。

域名白名单:仅允许将特定域名(如企业自有域名、合作方域名)生成短链,拒绝未知域名。

短链有效期:默认设置 30 天有效期,到期自动失效,降低长期风险。


文章配图
文章配图

九、面试官提问

以下问题从面试官视角设计,考察对短链接系统设计的深层理解,而非简单复述文档内容。

Q1:如果短链的 QPS 达到 10 万级别,你的缓存架构需要做哪些调整?布隆过滤器应该放在哪里?本地缓存的大小如何确定?

Q2:雪花算法中,如果机器时钟发生回拨,除了常见的等待和备用 worker ID 方案,你还能想到哪些工程上的容错手段?这些手段各自有什么代价?

Q3:你提到使用 Base62 编码,为什么不直接用 Base64?Base64 中的 + 和 / 在 URL 中会有什么问题?如果短链需要支持大小写不敏感的场景,应该怎么调整编码方案?

Q4:如果短链服务需要支持用户自定义短码(如 t.cn/my-brand),你的数据库设计和冲突处理策略需要做哪些改变?自定义短码和系统生成的短码如何统一管理?

Q5:访问日志的写入量可能达到每秒数万条,如果 Kafka 集群出现故障,你会如何设计降级方案保证核心跳转功能不受影响?

Q6:你的混合存储方案中,Redis 和 MySQL 之间如何保证数据一致性?如果 Redis 中的数据与 MySQL 不一致,会出现什么问题?你如何监控和修复这种不一致?

Q7:短链系统中的"短码"是否有被枚举遍历的风险?攻击者可以通过遍历短码获取所有映射关系,你如何防范这种风险?

Q8:如果要设计一个全球化的短链服务,多机房之间如何同步短链映射数据?如何保证用户在不同地区访问同一短链时都能正确跳转,同时最小化跨机房流量?

文章配图
文章配图