如何設計一個高性能短網址服務?
時間:2023-04-18 08:58:02 | 來源:網站運營
時間:2023-04-18 08:58:02 來源:網站運營
如何設計一個高性能短網址服務?:
一、簡介
不知道大家有沒有了解過
短網址,所謂
短網址就是將一個長的url轉換成一個短的url,這樣用戶訪問短url的時候也能夠正常訪問到正常的url。應用廣泛,比如短信、分享鏈接等等。。。
下面我會從幾個方面來講述如何設計一個高性能的短網址服務
干貨滿滿,你準備好了嗎
二、正文
你為什么能通過一個短的鏈接訪問的正常的數據呢
這里假設短網址為
http://mtw.so/6kK03S 我們稱之為A
對應的長鏈接為
https://tech.meituan.com/2021/10/20/the-applications-of-sentiment-analysis-meituan.html 我們稱之為B
(這里是通過一個在線網址轉換工具生成的,圖省事 )
當我們訪問A時,會經過mtw.so的域名解析最終會請求該域名下的某一個http接口(
后段程序員都懂,就不做多解釋了)
這個接口會拿到url后的參數 6kK03S
通過這個參數后端會定位到一個長鏈接,也就是原始鏈接,最后通過重定向到原始鏈接(
301/302按需使用),至此就結束了
如何將一個長鏈接變成一個短鏈接呢
錯誤的做法 ?
你首先肯定會想到
加密啊,把長的url通過一種算法加密,
壓縮長度,用戶訪問加密后的url,通過后段再
解密就好了呀。ohhhh!! 我是個天才,這也太簡單了吧!?。ㄈ绻娴挠羞@樣子的算法,那么你就創(chuàng)造奇跡了。。。原因自行百度,不做多解釋了)
那么應該怎么做呢? 如果我們能吧長鏈接和短鏈接的對應關系存儲起來,用戶訪問短鏈接時,去數據庫中把長鏈接讀取出來,不就可以完美解決了嗎?
如何通過短鏈接定位到長鏈接
那么問題來了,這個存儲要怎么設計?
如何通過短鏈接定位到長鏈接?
使用id,如果我們有一個
全局id,這個id永遠不會重復,是不是就解決問題了?
當一個長鏈接來申請變成短鏈接時候,我們?yōu)樗梢粋€id,然后
存到數據庫中,假設
id=996,那么996這個id就對應我們這個長鏈接,最后我們直接把這個996提供給用戶使用就好了,假設生成的
短鏈接為
https://baidu.com/996 ,那么接下來的流程就簡單了
1. 用戶訪問
https://baidu.com/996 2. 服務器接受到請求,獲得996這個參數 3. 后端服務那996這個id,去數據庫中查詢長鏈接 4. 重定向到這個長鏈接 5. 用戶看到長鏈接的頁面效果 完美解決問題!??!
你認為這樣解決了嗎?既然這么簡單,那這篇文的意義何在?
問題一:全局id怎么生成?
問題二:如果id還是太長了怎么辦?
問題三:如果我這個短網址服務一天要生成十萬、百萬、千萬級別的短網址,要如何存儲?
問題四:請求量大了怎辦?服務扛得住嗎?
(面試官的致命組合拳)
莫慌莫慌,下面我們一一道來 (以下問題解決方案基于分布式環(huán)境探討)
全局id怎么生成
常見的全局id生成有幾種,對于高并發(fā)情況,我們一般會涉及一個id生成器,后文會詳解
redis incr ????????
完全依賴于redis,
優(yōu)點:
簡單無腦,
缺點:
數據會丟失,即便是redis有RDB和AOF,也會丟失不能保證
數據庫自增id ????????
基于數據庫的自增id
優(yōu)點:簡單無腦
缺點:要做好并發(fā)控制,比如sql層面可以通過select for update,代碼層面可以使ReentrantLock來保證線程安全,還要注意id是有上限的(具體上限跟隨數據類型),如果達到上限,講不會再生成id
uuid (不推薦)??
優(yōu)點:簡單無腦
缺點:生成的id不連續(xù)切中英文混合,落地到mysql會造成頁分裂,影響mysql存儲性能
雪花算法(SnowFlake) ??????????
優(yōu)點:通過單例模式保證id唯一性
缺點:由于是基于時間的,所以時間回退會造成id重復 (誰沒事會去改服務器的時間???)
如果自增id很長了怎么辦
當我們的自增id長度很長,生成了8573834749584939,也不滿足我們短鏈接的需求怎么辦?
進制轉化 我們可以使用
62進制(數字 + 小寫字母 + 大寫字母)來處理我們的id
| 10進制 | 62進制 | | --------------------- | ------------ | | 996 | g4 | | 1024102410241024 | 4GNTCX7B6 | | 996996996996996996996 | j9TiP3ZLxcIA |
是不是變短了很多(你那么短,卻那么能干 )
數據量大怎么存儲
數據量大?多大算大呢?個、十、百、千、萬、十萬、爸、爺、祖宗?。。?!
根據《
阿里巴巴Java開發(fā)手冊》,單表超過
500W行,或者單表數據大小超過
2GB,才推薦做
分表操作
如何選擇分表策略?
這里給出兩張常見的分表策略方案: 1. 根據日期按月/季度/年分表 2. 固定表分表
按時間份表
適用場景:日增數據量很大,比如日增十萬、百萬級別的數據
具體實現:根據時間,把當前時間生成的數據存儲到具體的時間表中,比如今天是2021-10-28,選擇按月份表,我們就把10月份產生的數據都存到t_id_test_202110,這張表中
優(yōu)點:可以利用mysql的
事件自動創(chuàng)建表,且單表容量可控
缺點:id生成需要帶上時間,不方便做統計操作
固定表分表
固定表分表指的就是自己評估未來的數據增長,把數據表分為若干個表,比如分為8、16、32、64、128張表(
這里建議是2的x次方)
適用場景:能預估數據量的大小,并且不希望創(chuàng)建很多表
具體實現:創(chuàng)建若干數據表8、16、32、64、128張表,以序號命名,比如:t_id_test_1、t_id_test_43等,當生成一個id(996996),我們將這個id
取模分表總數(
這里也是為什么建議分表數一定要是2的x次方的原因,因為當n=2的x次方,任意數%n 結果和 任意數&n-1 的結果是一致的,但是位運算的效率要高得多得多得多)
比如分32張表,996996%32、996996&31=4,那么我們就把數據寫到t_id_test_4 這張表中,讀取同理
優(yōu)點:id
不用額外處理,分表數量可控,統計方便
缺點:表
擴容比較復雜,需要暫停服務,重新做一次hash計算
如何扛住高并發(fā),1W+的QPS
對于高并發(fā)情況,我們要如何應對?我們先總結一下短鏈接的過程 1. 生成短鏈接
a. 獲取一個全局id
b. 把長鏈接和id存到數據庫
c. 將id做62進制轉換
d. 拼接成短鏈接返回
2. 訪問短鏈接
a. 后段接收請求
b. 拿到請求參數
c. 將參數取10進制
d. 從數據庫中講這個id對應的長鏈接取出來
e. 重定向
我們來思考一下,對于高并發(fā)場景,我們要考慮以上哪一過程呢? 我們一步一步來
r/> 先看 1 生成端鏈接的過程 1.b、1.c 和1.d 是不需要考慮的,因為你并發(fā)量再大,他們也不會影響,就是一個計算+返回+寫db的過程
1.a有沒有并發(fā)問題?當然有,什么問題?I/O瓶頸呀,如果選擇redis、mysql自增作為全局id,那么I/O是重大損耗,那么如何解決呢?可以利用
id生成器r/> 再來看 2 訪問短鏈接的過程 2.a、2.b、2.c、2.e 是不會有高并發(fā)的問題的,我們重點看2.d
我們知道,高并發(fā)環(huán)境下影響性能最嚴重的就是I/O,而且大批量的請求直接打到mysql,如果mysql的配置不高,也容易搞快mysql,那么如何解決呢?可以利用
緩存全局id生成器
所謂id生成器,就是我
負責生(I/O),你
負責用(從內存拿),職責單一,減小I/O!
具體實現:一條線程不停的
生成id,保證唯一性和順序性,然后將生成的id放入
隊列中(先進先出、保證順序),
工作線程要獲取id時直接從
隊列頭拿出來即可,這樣是不就除去了
工作線程I/O的過程,直接讀內存,速度大大的提高。當然我們也可以設置一個規(guī)則,比如當隊列長度小于某一個界限的時候再生成id。
緩存
我們要從兩個地方坐緩存,第一數據過濾,第二數據緩存
數據過濾: 為什么要有數據過濾?因為我們要先行過濾無效的數據,如果這個數據是有效的我們才會往后面走流程,常用的數據過濾器可以使用布隆過濾器(Bloom Filter),生成短鏈接時,講當時的id放入BloomFilter,后面有請求來先通過一層Filter,如果為true,往下執(zhí)行,如果是false,直接拒絕
(BloomFilter會有誤差,如果存在則不一定存在,如果不存在則一定不存在) 數據緩存: 當每生成一個短鏈接,我們可以先將其緩存至redis,讀取時先讀redis,redis沒有再去mysql讀,然后再放入redis,防止大批量的請求直接打到mysql,將其擊垮。這里要考慮一個問題,因為短鏈接的量非常大,我們要選擇性長期緩存,對于一些熱點數據,我們要緩存時間長一些,直到他成為冷門數據,對于冷門數據,我們甚至可以不用緩存。
如果還是并發(fā)大相應慢,我們可以利用本地緩存,也就是jvm層面的緩存,可以講鏈接信息暫時放入本地map,使用lru算法保證不會OOM,先讀本地緩存、再讀redis、最后mysql兜底。
三、總結
本文主要探討了一個高性能的短鏈接服務的設計思路,如有錯誤的地方,希望各位大佬幫助小弟改正。
新人小白第一文,可以關注我,后續(xù)會有很多干貨哦~~~
一個努力讓自己變強的深漂Java程序員