实现一个短域名系统
面试官的提问
设计一个短域名系统
评估
-
输入是什么?
一个冗长的域名, 以及一个过期时间和一个自定义的别名。
-
输出是什么?
自定义别名或者随机生成的短域名,在过期时间到来前访问都可以被重定向到原始的冗长域名上
-
约束是什么?
-
过期后将失效
-
短域名是唯一的
-
支持自定义短域名,长度在7个字符(不含域名长度),由[0-9,a-z,A-Z]等字符组成
-
QPS
- 写 DAU = 100M -> QPS = 100M * 0.1/ 86400 = 1K -> 峰值 2k -> 预估值 = 4K
- 读 DAU = 100M -> QPS = 100M * 10 / 86400 = 115k -> 峰值230k-> 预估值460k
- 这是一个读多写少的系统
-
延迟 = 10ms以内
-
存储 = 128+7 = 100 bytes/条 * 1500 * 60 * 60 * 24 * 365 = 20T/年
-
可靠性: 5个9(可运维性, 可扩展性,迭代效率)
-
安全性: 防止被人爬取,盗用数据
方案设计
可行解
-
写接口
- 输入长域名,判断是否曾经存储过 是的话不需要进一步存储
- 生成一个从未用过的短域名
- 关联二者存储在持久化的DB中
-
读接口
- 获取短域名,判断是否有效,没有被存储过直接返回
- 根据短域名查询长域名
- 使用长域名通知客户端进行重定向
暂时无法在文档外展示此内容
增加过期时间怎么处理?
- 延迟删除 性能损失小,修改方便,存储空间的利用效率低
- 定时删除 存储空间的利用率高, 占用内存大,性能差
- 轮询删除 后台逻辑周期性扫描全表,清理过期元素, 空间与性能的折中方案
由于 我们对延迟要求高,并且存储空间占用较少,所以采用延迟删除的策略。
在DB建的表里加入过期时间的时间戳列,写入时计算读取时比较,如果过期就清楚。
如何保证短域名的唯一性?
- 读时消重 在返回的时候过滤掉重复的元素
- 写时消重 写时判断曾经写过避免重复写入
- 那如何设计短域名的唯一ID生成算法呢?
- UUID -> 7位数字要求 -> 截取部分 -> 不能保证唯一性 pass
- 哈希 -> MurmurHash 等方法 同样不能保证长度需要截取,在7位字符的空间上存在冲突
- 自增ID -> 62位长, 因此 可以用7位62进制来生成唯一递增的ID,利用数据库的自增主键
还要支持用户自定义? 那需要一个索引来记忆正在使用的ID(过期删除时要更新索引)
维护一个待分配的ID列表段,为了防止大key可以分段存储,在数据过期或者自定义ID时进行更新
暂时无法在文档外展示此内容
更优解
吞吐量优化
- 单机服务端扛不住这么大的QPS 所以需要水平扩展,因此需要NGINX做负载均衡。
- 优化吞吐量的方法无外乎,分片,副本
延迟优化
Server 需要写redis 与 mysql 需要三次网络调用其延迟 至少100ms以上,同时考虑短域名系统的全球化,跨越过远的广域网络请求,延迟再增加80ms以上,因此预估一次请求延迟在200ms。
存储层
- 存储介质 数据表的业务逻辑非常简单,没必要使用关系型数据库,可以更换为持久化kv引擎 比如rocksDB,如果考虑数据分析的需求,则可以将数据构建离线的ELK导入数仓。
- 构建索引 写入时要判断长域名是否存在,因此要对长域名建立索引,读取时要根据短域名查询长域名因此也要在redis中维护短域名到长域名的倒排索引。
- 为了应对极高的写入QPS,必须使用分片技术, 使用短域名7位62进制的特性,进行一致性哈希,来计算写入分片位置,读取时也用相同的哈希算法。
缓存层
- 读多写少 的系统中,可以使用缓存技术来降低延迟, 尽量保证所有请求在缓存中完成
- 对于判断长域名是否存储过,以及短域名是否被分配过可以构建布隆过滤器。
- 如果对延迟非常苛刻,完全可以使用本地缓存, 在本地维护一个LRU缓存来存储长短域名映射
- 短域名数据的变化非常缓慢,可以考虑静态化技术,把数据发送给服务节点以文件的方式同步
业务层
- 通过尾号分片将ID生成器分布式化,基于预处理的思路可以分段缓存一部分未分配的短域名
- 分布式ID生成需要使用zk等组件处理数据一致性的问题
- 使用lua脚本降低对redis的减少网络调用次数
拓扑层
- 利用地理位置的局部性,将同一地区的请求都路由到最近的机房访问,降低广域延迟
- 可以支持跨机房全球化的多重分片,来应对同一地区QPS过高问题,使用7位数字中的首位进行一致性哈希(其哈希空间为62),后6位进行本机房内的rocksDB 节点的分片。
可靠性优化
- Redis 和 RocksDB 可以支持主从副本等方式,一方面分担读写请求,一方面提供数据备份
- 提供跨机房级别的数据冗余备份,通过kafka与binlog的方式全量与增量的进行数据备份
- 基于监控指标等阈值,来实现系统的熔断,限流,扩缩容等特性
迭代效率,可运维性
- 此需求功能简单,没有迭代效率上的问题
- 但是复杂的分布式软件必须保证可运维性,需要监控缓存的命中率,top热点URL等
- 监控qps阈值进行扩缩容,节省运维成本
安全性
- 如果使用自增ID作为短域名,那么就有可能被人逐个遍历而被爬取
- 使用MurmurHash 算法转义为7位字符串(存在冲突,但可解决安全性问题)
- 防止DOS攻击等,要对IP限流和IP黑名单等机制
Bad Case 分析
- 301 与 302重定向, 301永久重定向浏览器会缓存无法统计到访问次数,但会减少服务端压力
- 分布式ID信号发生器,数据一致性以及跨机房同步等问题
无法复制加载中的内容
总结
-
需求评估 输入,输出,约束
-
方案设计
- 根据业务约束得到一个可行解
- 根据系统约束得到一个更优解(吞吐,延迟,可靠,迭代,运维,安全性)
-
Bad Case 分析