面试官如何设计一个高并发的短链接系统?
1 Scenario 场景
根据一个 long url 生成一个short url。
如 http://www.javadaily.cn => http://bit.ly/1ULoQB6
根据 short url 还原 long url,并跳转:
需和面试官确认的问题:
long url和short url必须一一对应吗?
Short url长时间没人用,需要释放吗? 1.1 QPS 分析问日活,如微博100M 推算产生一条 tiny url 的 qps 假设每个用户平均每天 0.1(发10 条,有一条有链接) 条带 URL 的微博 平均写 QPS = 100M * 0.1 / 86400 = 100 峰值写 qps = 100 * 2 = 200 推算点击一条tiny url的 qps 假设每个用户平均点 1 个tiny url 平均写 QPS = 100M * 1 / 86400 = 1k 峰值读 qps = 1k * 2 = 2k deduce 每天产生的新 URL 所占存储 100M * 0.1 = 10M 条 每条 URL 长度平均按 100 算,共 1G 1T 硬盘能用 3 年
由2、3 分析可知,并不需要分布式或者 sharding,支持 2k QPS,一台 SSD MySQL 即可。 2 Service 服务 - 逻辑块聚类与接口设计
该系统其实很简单,只需要有一个 service即可:URL Service。由于 tiny url只有一个 UrlService: 本身其实就是个小的独立应用 也无需关心其他任何业务功能 方法设计:
UrlService.encode(long_url):编码方法
UrlService.decode(long_url):解码方法
访问端口设计,当前有如下两种常用主流风格: GET / REST 风格
Return a http redirect resonse POST /data/shorten(不太推荐,不符合 REST 设计风格,但也有人在用)
Return a short url
那么,你们公司的短链系统是选择哪种服务设计呢? 3 Storage 数据存取(最能体现实践经验)select 选存储结构 scheme 细化数据表 3.1 SQL V.S NoSQL
需要事务吗?No,nosql+1
需要丰富的 sql query 吗?no,nosql+1
想偷懒吗?tiny url需要写的代码不复杂,nosql+1
qps高吗?2k,不高。sql+1
scalability 要求多高?存储和 qps 都不高,单机都能搞定。sql+1 - sql 需要自己写代码来 scale - nosql,这些都帮你做了
是否需要 sequential ID?取决于你的算法 sql 提供 auto_increment 的 sequencetial ID,即 1,2,3 nosql 的 ID 不是 sequential 3.2 算法
long ur 转成一个 6 位的 short url。给出一个长网址,返回一个短网址。
实现两个方法: longToShort(url) 把一个长网址转换成一个以http://tiny.url/ 开头的短网址 shortToLong(url) 把一个短网址转换成一个长网址
标准: 短网址的key的长度应为6 (不算域名和反斜杠)。可用字符只有 [a-zA-Z0-9] . 比如: abcD9E 任意两个长的url不会对应成同一个短url,反之亦然。
用两个哈希表: 一个是短网址映射到长网址 一个是长网址映射到短网址
短网址是固定的格式: "http://tiny.url/" + 6个字符, 字符可任意。
为避免重复, 我们可以按照字典序依次使用, 或者在随机生成的基础上用一个集合来记录是否使用过。 使用哈希函数(不可行)
如取 long url的 MD5 的最后 6 位: 快 难以设计一个无哈希冲突的哈希算法 随机生成 shortURL+DB去重
随机取一个 6 位的 shortURL,若没使用过,就绑定到改 long url。 public String long2Short(String url) { while(true) { String shortURL = randomShortURL(); if (!databse.filter(shortURL=shortURL).exists()) { database.create(shortURL=shortURL, longURL=url); return shortURL; } } } public class TinyUrl { public TinyUrl() { long2Short = new HashMap(); short2Long = new HashMap(); } /** * @param url a long url * @return a short url starts with http://tiny.url/ */ public String longToShort(String url) { if (long2Short.containsKey(url)) { return long2Short.get(url); } while (true) { String shortURL = generateShortURL(); if (!short2Long.containsKey(shortURL)) { short2Long.put(shortURL, url); long2Short.put(url, shortURL); return shortURL; } } } /** * @param url a short url starts with http://tiny.url/ * @return a long url */ public String shortToLong(String url) { if (!short2Long.containsKey(url)) { return null; } return short2Long.get(url); } private String generateShortURL() { String allowedChars = "0123456789" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; Random rand = new Random(); String shortURL = "http://tiny.url/"; for (int i = 0; i < 6; i++) { int index = rand.nextInt(62); shortURL += allowedChars.charAt(index); } return shortURL; } }
优点:实现简单
缺点:生成短链接的速度,随着短链接越多而越慢
关系型数据库表:只需Short key和 long url两列,并分别建立索引
也可使用 nosql,但需要建立两张表: 根据 long 查询 short key=longurl 列=shorturl value=null or timestamp 根据 short 查询 long key=shorturl 列=longurl value=null or timestamp 进制转换 Base32(微博实现方案)
Base62: 将 6 位 short url 看做一个 62 进制数(0-9,a-z,A-Z) 每个 short url 对应到一个整数 该整数对应 DB 表的主键
6 位可表示的不同 URL: 5 位 = 62^5=0.9B= 9亿 6 位 = 62^6=57B= 570亿 7 位 = 62^7=3.5T= 35000亿
优点:效率高
缺点:强依赖于全局的自增 id public class TinyUrl { public static int GLOBAL_ID = 0; private HashMap id2url = new HashMap(); private HashMap url2id = new HashMap(); private String getShortKey(String url) { return url.substring("http://tiny.url/".length()); } private int toBase62(char c) { if (c >= "0" && c <= "9") { return c - "0"; } if (c >= "a" && c <= "z") { return c - "a" + 10; } return c - "A" + 36; } private int shortKeytoID(String short_key) { int id = 0; for (int i = 0; i < short_key.length(); ++i) { id = id * 62 + toBase62(short_key.charAt(i)); } return id; } private String idToShortKey(int id) { String chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; String short_url = ""; while (id > 0) { short_url = chars.charAt(id % 62) + short_url; id = id / 62; } while (short_url.length() < 6) { short_url = "0" + short_url; } return short_url; } /** * @param url a long url * @return a short url starts with http://tiny.url/ */ public String longToShort(String url) { if (url2id.containsKey(url)) { return "http://tiny.url/" + idToShortKey(url2id.get(url)); } GLOBAL_ID++; url2id.put(url, GLOBAL_ID); id2url.put(GLOBAL_ID, url); return "http://tiny.url/" + idToShortKey(GLOBAL_ID); } /** * @param url a short url starts with http://tiny.url/ * @return a long url */ public String shortToLong(String url) { String short_key = getShortKey(url); int id = shortKeytoID(short_key); return id2url.get(id); } }
因为要用到自增 id,所以只能用关系型 DB 表:
id主键、long url(索引) 4 Scale
如何提高响应速度,和直接打开原链接一样的效率。
明确,这是个读多写少业务。 4.1 缓存提速(Cache Aside)
缓存需存储两类数据: long2short(生成新 short url 需要) short2long(查询 short url 时需要)
4.2 CDN
利用地理位置信息提速。
优化服务器访问速度: 不同地区,使用通不同 web 服务器 通过 dns 解析不同地区用户到不同服务器
优化数据访问速度 使用中心化的 MySQL+分布式的 Redis 一个 MySQL 配多个 Redis,Redis 跨地区分布
4.3 何时需要多台 DB 服务器
cache 资源不够或命中率低
写操作过多
越来越多请求无法通过 cache 满足
多台DB服务器可以优化什么? 解决存不下:存储 解决忙不过:qps
那么 tiny url 的主要问题是啥?存储是没问题的,重点是 qps。那么,如何 sharding 呢?
垂直拆分:将多张表分别分配给多台机器。对此不适用,只有两列,无法再拆分。
横向拆分:
若id、shortURL 做分片键: long2short 查询时,只能广播给 N 台 db 都去查询 为何要查 long2short?避免重复创建呀 若不需要避免重复创建,则这样可行
用 long url 做分片键:
short2long 查询时,只能广播给 N 台 DB 查询。 4.3.1 分片键选择若一个 long 可对应多个 short使用 cache 缓存所有 long2short 在为一个 long url 创建 short url 时,若 cache miss,则创建新 short 若一个 long 只能对应一个 short若使用随机生成算法 两张表,一张存储 long2short,一张存储short2long 每个映射关系存两份,则能同时支持 long2short short2long 查询 若使用 base62 进制转换法 有个严重问题,多台机器之间如何维护一个全局自增的 id? 一般关系型DB只支持在一台机器上实现这台机器上全局自增的 id 4.4 全局自增 id4.4.1 专用一台 DB 做自增服务
该 DB不存储真实数据,也不负责其他查询。
为避免单点故障,可能需要多台 DB。 4.4.2 使用 zk
但使用全局自增 id 不是解决 tiny url最佳方案。Generating a Distributed Sequence Number 4.5 基于 base62 的分片策略
Hash(long_url)%62作为分片键
并将 hash(long_url)%62直接放到 short url
若原来的 short key 是 AB1234,则现在的 short key 是 hash(long_url) % 62 + AB1234 若 hash(long_url)%62=0,那就是0AB1234
这样,就能同时通过 short、long 得到分片键。
缺点:DB 的机器数目不能超过 62。
所以,最后最佳架构:
4.6 还能优化吗?
web server 和 database 之间的通信。
中心化的服务器集群和跨地域的 web server 之间通信较慢:如中国的 Server 需访问美国的 DB。
为何不让中国的 Server 访问中国的 DB 呢?
若数据重复写到中国 DB,如何解决一致性问题?很难解决!
思考用户的习惯: 中国用户访问时,会被 DNS 分配中国的服务器 中国用户访问的网站一般都是中国的网站 所以可按网站的地域信息来 sharding 如何获得网站的地域信息?只需将用户常访问的网站汇总在一张表。 中国用户访问美国网站咋办? 就中国 server 访问美国 db,也不会慢太多 中访中是用户主流,优化系统就是针对主要需求
于是,得到最终架构:
还可以维护一份域名白名单,访问对应地域的 DB。 5 用户自定义短链接
实现一个顾客短网址,使得顾客能创立他们自己的短网址。即你需要在前文基础上再实现一个 createCustom 。
需实现三个方法: long2Short(url) 把一个长网址转换成一个以http://tiny.url/ 开头的短网址 short2Long(url) 把一个短网址转换成一个长网址 createCustom(url, key) 设定一个长网址的短网址为 http://tiny.url/ + key
注意: long2Short 生成的短网址的key的长度应该等于6 (不算域名和反斜杠)。可以使用的字符只有 [a-zA-Z0-9] 。如: abcD9E 任意两个长的url不会对应成同一个短url,反之亦然 如果 createCustom 不能完成用户期望的设定, 那么应该返回 "error" , 反之如果成功将长网址与短网址对应,应该返回这个短网址 5.1 基于 Base62
在URLTable里,直接新增一列custom_url记录对应的custom url是否可行?
不可行!对于大部分数据,该列其实都为空,就会浪费存储空间。
新增一个表,存储自定义 URL:CustomURLTable。
创建自定义短链接:在 CustomURLTable 中查询和插入
根据长链接创建普通短链接: 先查询CustomURLTable是否存在 再在URLTable查询和插入
同前文一样,用两个哈希表处理长网址和短网址之间的相互映射关系。需额外处理的是用户设定的网址与已有冲突时,需返回 "error"。注意:若用户设定的和已有恰好相同,则同样应该返回短网址。 public class TinyUrl2 { private HashMap s2l = new HashMap(); private HashMap l2s = new HashMap(); private int cnt = 0; private final StringBuffer tinyUrl = new StringBuffer("http://tiny.url/"); private final String charset = "qwertyuiopasdfghjklzxcvbnm1234567890QWERTYUIOPASDFGHJKLZXCVBNM"; private String newShortUrl() { StringBuffer res = new StringBuffer(); for (int i = 0, j = cnt; i < 6; i++, j /= 62) res.append(charset.charAt(j % 62)); cnt++; return tinyUrl + res.toString(); } /* * @param long_url: a long url * @param key: a short key * @return: a short url starts with http://tiny.url/ */ public String createCustom(String long_url, String key) { String short_url = tinyUrl + key; if (l2s.containsKey(long_url)) { if (l2s.get(long_url).equals(short_url)) return short_url; else return "error"; } if (s2l.containsKey(short_url)) return "error"; l2s.put(long_url, short_url); s2l.put(short_url, long_url); return short_url; } /* * @param long_url: a long url * @return: a short url starts with http://tiny.url/ */ public String longToShort(String long_url) { if (l2s.containsKey(long_url)) return l2s.get(long_url); String short_url = newShortUrl(); l2s.put(long_url, short_url); s2l.put(short_url, long_url); return short_url; } /* * @param short_url: a short url starts with http://tiny.url/ * @return: a long url */ public String shortToLong(String short_url) { if (s2l.containsKey(short_url)) return s2l.get(short_url); return "error"; } } 5.2 基于随机生成算法
无需做任何改动,直接把custom url当short url创建即可!
岳阳君山精心打造文旅IP倾情回报父老乡亲艳阳高照,云淡风轻。12月10日,位于君山区钱粮湖镇分路口社区的逸馨生态度假村迎来了数百名游客,有帅哥靓妹成双成对骑行过来的,有一家周末自驾游的,他们在这里登山采摘烧烤美食打糍粑,
丽江机场安检人文明热情服务保障旅客安全出行国际民航日丽江三义国际机场于1995年正式建成通航,分别于1998年和2008年先后两次开展改扩建,目前丽江三义国际机场运行等级为4D,国内候机楼3。3万,国际候机楼5300,跑道
让孩子学习很难?能全科学习的喵喵机学习笔S1来助力相信不少父母为了孩子的学习,真心投入了不少精力和金钱,可能很多时候收效甚微,但我们依然毫不吝啬地一掷千金,毕竟这关乎着孩子的一生,所以作为父母的千万不能拖了后腿,该辅导的辅导,该买
2022年末,看一看香港两位李氏富豪开发的新地标建筑,最新进展前言香港两位李氏富豪是指李嘉诚家族和李兆基家族,两都是世界级的富翁,资产超千亿。其中李兆基在19961997连续两年成为福布斯全球富豪榜第4名,这是至今为止全球华人拿到过的最高排名
偏爱富豪的关之琳,不仅恋过男模,还和刘德华有一段往事文阅栀编辑阅栀2010年,关之琳在人民大会堂的慈善活动现场正式宣布息影,虽然香江美人隐退了,但是关于她多情的传说一直在江湖上飘荡。退休还不到一年,关之琳就在刘德华在演唱会上被揪了出
北京毒株凶广州毒株弱,河南及三门峡是啥毒株?头条创作挑战赛近日,中国疾控中心病毒所所长许文波介绍,我国现阶段流行的新冠病毒以奥密克戎变异株BA。5的亚分支BA。5。2和BF。7为主,其中BA。5。2在我国31个省份流行BF。
阳了个阳,不要再慌了,将贩卖焦虑的那些人都推走!目前这一场全民大阳,看来是锁死了,任何人都逃不过。真像人们说的,空气中都弥漫着奥密克戎的味道。药店被抢空了,酒精没货了,连花清瘟胶囊价格翻好几倍还缺货,甚至连黄桃罐头都一罐难求!甚
月河小学教育集团劳动最光荣育人谱新章近日,2022年浙江省小学劳动教学活动评审及劳动新教材培训活动在月河小学教育集团湖东校区举行。本次培训通过线上线下的形式举办,全省各市区教育局劳动教育领导全省小学劳动教育专家指导委
卡塔尔世界杯阿根廷对克罗地亚谁会胜出?我对足球的兴趣不大,但架不住平台近段时间总是推送与世界杯和足彩相关的文章给我,我也就顺带看了看这方面的资料。明日凌晨三点的四分之一比赛是阿根廷对克罗地亚,网上有很多大神说阿根廷后防
中证财富夜读省到就是赚到云烟散,山依然。最近市场日渐回暖,不少投资者喜上眉梢摩拳擦掌,调仓换股,想要把握住反弹的行情。在这个过程中,除了要深入研究选好投资标的,如何以更低的成本获取收益也是一门重要的学问。
马航机长故意坠机?新证据表明起落架已放下以使航班更快沉没马来西亚政府12月12日宣布,上个月在马达加斯加一名渔民家里发现了失踪MH370航班的起落架门耳轴门,它可能穿透了客机解体的发动机内部。专家表示,航班的起落架是放下的,这表明马航机