实现一个短域名系统

427 阅读6分钟

实现一个短域名系统

面试官的提问

设计一个短域名系统

评估

  1. 输入是什么?

一个冗长的域名, 以及一个过期时间和一个自定义的别名。

  1. 输出是什么?

自定义别名或者随机生成的短域名,在过期时间到来前访问都可以被重定向到原始的冗长域名上

  1. 约束是什么?

  1. 过期后将失效

  2. 短域名是唯一的

  3. 支持自定义短域名,长度在7个字符(不含域名长度),由[0-9,a-z,A-Z]等字符组成

  4. QPS

    1. 写 DAU = 100M -> QPS = 100M * 0.1/ 86400 = 1K -> 峰值 2k -> 预估值 = 4K
    2. 读 DAU = 100M -> QPS = 100M * 10 / 86400 = 115k -> 峰值230k-> 预估值460k
    3. 这是一个读多写少的系统
  5. 延迟 = 10ms以内

  6. 存储 = 128+7 = 100 bytes/条 * 1500 * 60 * 60 * 24 * 365 = 20T/年

  7. 可靠性: 5个9(可运维性, 可扩展性,迭代效率)

  8. 安全性: 防止被人爬取,盗用数据

方案设计

可行解

  1. 写接口

    1. 输入长域名,判断是否曾经存储过 是的话不需要进一步存储
    2. 生成一个从未用过的短域名
    3. 关联二者存储在持久化的DB中
  2. 读接口

    1. 获取短域名,判断是否有效,没有被存储过直接返回
    2. 根据短域名查询长域名
    3. 使用长域名通知客户端进行重定向

暂时无法在文档外展示此内容

增加过期时间怎么处理?

  1. 延迟删除 性能损失小,修改方便,存储空间的利用效率低
  2. 定时删除 存储空间的利用率高, 占用内存大,性能差
  3. 轮询删除 后台逻辑周期性扫描全表,清理过期元素, 空间与性能的折中方案

由于 我们对延迟要求高,并且存储空间占用较少,所以采用延迟删除的策略。

在DB建的表里加入过期时间的时间戳列,写入时计算读取时比较,如果过期就清楚。

如何保证短域名的唯一性?

  1. 读时消重 在返回的时候过滤掉重复的元素
  2. 写时消重 写时判断曾经写过避免重复写入
  3. 那如何设计短域名的唯一ID生成算法呢?
  4. UUID -> 7位数字要求 -> 截取部分 -> 不能保证唯一性 pass
  5. 哈希 -> MurmurHash 等方法 同样不能保证长度需要截取,在7位字符的空间上存在冲突
  6. 自增ID -> 62位长, 因此 可以用7位62进制来生成唯一递增的ID,利用数据库的自增主键

还要支持用户自定义? 那需要一个索引来记忆正在使用的ID(过期删除时要更新索引)

维护一个待分配的ID列表段,为了防止大key可以分段存储,在数据过期或者自定义ID时进行更新

暂时无法在文档外展示此内容

更优解

吞吐量优化

  1. 单机服务端扛不住这么大的QPS 所以需要水平扩展,因此需要NGINX做负载均衡。
  2. 优化吞吐量的方法无外乎,分片,副本

延迟优化

Server 需要写redis 与 mysql 需要三次网络调用其延迟 至少100ms以上,同时考虑短域名系统的全球化,跨越过远的广域网络请求,延迟再增加80ms以上,因此预估一次请求延迟在200ms。

存储层

  1. 存储介质 数据表的业务逻辑非常简单,没必要使用关系型数据库,可以更换为持久化kv引擎 比如rocksDB,如果考虑数据分析的需求,则可以将数据构建离线的ELK导入数仓。
  2. 构建索引 写入时要判断长域名是否存在,因此要对长域名建立索引,读取时要根据短域名查询长域名因此也要在redis中维护短域名到长域名的倒排索引。
  3. 为了应对极高的写入QPS,必须使用分片技术, 使用短域名7位62进制的特性,进行一致性哈希,来计算写入分片位置,读取时也用相同的哈希算法。

缓存层

  1. 读多写少 的系统中,可以使用缓存技术来降低延迟, 尽量保证所有请求在缓存中完成
  2. 对于判断长域名是否存储过,以及短域名是否被分配过可以构建布隆过滤器
  3. 如果对延迟非常苛刻,完全可以使用本地缓存, 在本地维护一个LRU缓存来存储长短域名映射
  4. 短域名数据的变化非常缓慢,可以考虑静态化技术,把数据发送给服务节点以文件的方式同步

业务层

  1. 通过尾号分片将ID生成器分布式化,基于预处理的思路可以分段缓存一部分未分配的短域名
  2. 分布式ID生成需要使用zk等组件处理数据一致性的问题
  3. 使用lua脚本降低对redis的减少网络调用次数

拓扑层

  1. 利用地理位置的局部性,将同一地区的请求都路由到最近的机房访问,降低广域延迟
  2. 可以支持跨机房全球化的多重分片,来应对同一地区QPS过高问题,使用7位数字中的首位进行一致性哈希(其哈希空间为62),后6位进行本机房内的rocksDB 节点的分片。

可靠性优化

  1. Redis 和 RocksDB 可以支持主从副本等方式,一方面分担读写请求,一方面提供数据备份
  2. 提供跨机房级别的数据冗余备份,通过kafka与binlog的方式全量与增量的进行数据备份
  3. 基于监控指标等阈值,来实现系统的熔断,限流,扩缩容等特性

迭代效率,可运维性

  1. 此需求功能简单,没有迭代效率上的问题
  2. 但是复杂的分布式软件必须保证可运维性,需要监控缓存的命中率,top热点URL等
  3. 监控qps阈值进行扩缩容,节省运维成本

安全性

  1. 如果​使用自增ID作为短域名,那么就有可能被人逐个遍历而被爬取
  2. 使用MurmurHash 算法转义为7位字符串(存在冲突,但可解决安全性问题)
  3. 防止DOS攻击等,要对IP限流和IP黑名单等机制

Bad Case 分析

  1. 301 与 302重定向, 301永久重定向浏览器会缓存无法统计到访问次数,但会减少服务端压力
  2. 分布式ID信号发生器,数据一致性以及跨机房同步等问题

无法复制加载中的内容

总结

  1. 需求评估 输入,输出,约束

  2. 方案​设计

    1. 根据业务约束得到一个可行解
    2. 根据系统约束得到一个更优解(吞吐,延迟,可靠,迭代,运维,安全性)
  3. Bad Case 分析