想象一下,我們的API網關對外開放了好多個介面,有的介面是通用的,大家都可以訪問,有的介面是定制的,只對特定用戶開放,比如付費用戶、合作伙伴等,這就涉及到介面權限控制的問題,
權限功能的示意圖如下:

介面權限是一個網關系統最基本的需求,實作方式也有很多,我們這里只討論最簡單的一種,
ACL這個英文縮寫,大家都不陌生,全拼是Access Control Lists 訪問控制串列,
舉個例子,我們有service1,service2,service3三個介面,有userA, userB, userC三個用戶,
userA可以訪問service1,service2,service3,
userB可以訪問service2,
userC可以訪問service3,
下面講一些技術上的細節
存盤結構
根據上面的場景,我們的ACL怎么設計呢?
第一種辦法是:
service1 -> userA
service2 -> userA,userB
service3 -> userA,userC
第二種辦法是:
userA -> service1,service2,serivce3
userB -> service2
userC -> service3
看起來好像都可以,那么,讓我們來看一看,對一個大型網關來說,系統有什么特點:
- 用戶可能是百萬級別的,
- 按百萬總數算,活躍用戶大概有十萬左右,
- 介面的數目在幾百到幾千,
- QPS一般在幾十萬級別,每個請求都需要校驗權限,所以這些資料是要存到快取中的,
所以,根據網關的特點,第二種存盤結構更適合我們,
那么,mysql的表結構就是下面這樣:
| user_id | acl |
|---|---|
| A | 1,2,3 |
| B | 2 |
| C | 3 |
然后,把這份資料存到redis中,當B用戶的請求進來時,比如說,B用戶請求的是service1,我們取出B的acl,發現沒有service1,那么就給B回傳無權限,
到這里,似乎一切都沒什么問題,文章似乎該結束了,
但是,如果介面數量增長到幾百甚至上千時,會發生什么呢?
這時,mysql的存盤會變成下面的樣子:
| user_id | acl |
|---|---|
| A | 1,6,105,106,112,120,123,125,128,138,205,206,207,208,220,226,229,230,448,459,460,468,469,645,685,686,687,688,689,691,696,699,706,710,712,724,725,726,727,728,729,730,731,732,733,734,735,736,737,738,739,740,741,743,745,746,748,751,756,757,758,759,760,761,762,764,765,766,767,768,769,770,777,778,779,780,781,785,789,797,800,801,806,807,808,809,810,811,812,813,814,822,825,830,834,836,839,840,848,849,850,851,852,862,863,865,866,867,868,869,873,874,875,877,880,882,883,884,885,886,887,888,889,890,891,901,903,905,906,907,909,910,911,912,913,914,921,922,930,931,932,933,934,935,936,937,938,939,940,941,944,945,946,949,950,951,952,953,954,955,956,957,958,959,960,961,962,963,965,966,967,969,970,971,972,973,974,975,976,977,978,979,980,981,982,983,984,985,987,988,989,990,1009,1010,1011,1012,1013,1014,1015,1016,1017,1018,1019,1020,1021,1022,1023,1024,1025,1026,1027,1028,1029,1030,1031,1032,1033,1034,1035,1037,1038,1039,1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1056,1058,1059,1060,1061,1064,1065,1066,1067,1068,1069,1070,1071 |
| B | 1,6,105,106,112,120,123,125,128,138,205,206,207,208,220,226,229,230,448,459,460,468,469,645,685,686,687,688,689,691,696,699,706,710,712,724,725,726,727,728,729,730,731,732,733,734,735,736,737,738,739,740,741,743,745,746,748,751,756,757,758,759,760,761,762,764,765,766,767,768,769,770,777,778,779,780,781,785,789,797,800,801,806,807,808,809,810,811,812,813,814,822,825,830,834,836,839,840,848,849,850,851,852,862,863,865,866,867,868,869,873,874,875,877,880,882,883,884,885,886,887,888,889,890,891,901,903,905,906,907,909,910,911,912,913,914,921,922,930,931,932,933,934,935,936,937,938,939,940,941,944,945,946,949,950,951,952,953,954,955,956,957,958,959,960,961,962,963,965,966,967,969,970,971,972,973,974,975,976,977,978,979,980,981,982,983,984,985,987,988,989,990,1009,1010,1011,1012,1013,1014,1015,1016,1017,1018,1019,1020,1021,1022,1023,1024,1025,1026,1027,1028,1029,1030,1031,1032,1033,1034,1035,1037,1038,1039,1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1056,1058,1059,1060,1061,1064,1065,1066,1067,1068,1069,1070,1071 |
| C | 1,6,105,106,112,120,123,125,128,138,205,206,207,208,220,226,229,230,448,459,460,468,469,645,685,686,687,688,689,691,696,699,706,710,712,724,725,726,727,728,729,730,731,732,733,734,735,736,737,738,739,740,741,743,745,746,748,751,756,757,758,759,760,761,762,764,765,766,767,768,769,770,777,778,779,780,781,785,789,797,800,801,806,807,808,809,810,811,812,813,814,822,825,830,834,836,839,840,848,849,850,851,852,862,863,865,866,867,868,869,873,874,875,877,880,882,883,884,885,886,887,888,889,890,891,901,903,905,906,907,909,910,911,912,913,914,921,922,930,931,932,933,934,935,936,937,938,939,940,941,944,945,946,949,950,951,952,953,954,955,956,957,958,959,960,961,962,963,965,966,967,969,970,971,972,973,974,975,976,977,978,979,980,981,982,983,984,985,987,988,989,990,1009,1010,1011,1012,1013,1014,1015,1016,1017,1018,1019,1020,1021,1022,1023,1024,1025,1026,1027,1028,1029,1030,1031,1032,1033,1034,1035,1037,1038,1039,1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1056,1058,1059,1060,1061,1064,1065,1066,1067,1068,1069,1070,1071 |
發現問題沒?
acl太長,會導致至少兩個問題:
存盤空間,這其實倒沒啥,畢竟現在硬碟已經很便宜了,
更新,比如新增了一個通用的介面,所有用戶都有權訪問,那么,得把這個介面的id追加到所有用戶的acl末尾,這其實也沒啥,畢竟新增介面的頻率特別特別低,新介面也沒那么著急上線,慢慢更新唄,
但是,作為一個程式員,看著又長又慢的系統,心里還是不爽的,
有沒有什么更好的辦法?
其實,對于權限來說,一個用戶對一個介面要么有權限,要么沒權限,也就是說,0和1就可以表示權限的有和無,理論上,1個bit就可以存盤一個用戶對一個介面的權限資訊,顯然比介面id拼接字串劃算的多,剛好,我們的介面id可以是從1開始遞增的,
是不是想到了一種資料結構?
是的,bitmap,這簡直是最適合bitmap登場的場景,(關于bitmap這種資料結構,不清楚的同學可以網上搜一搜,很多詳細的介紹),
先來直觀的感受一下,用bitmap替換字串之后,資料長度的變化:
| user_id | acl |
|---|---|
| A | ????Z???X? ?l????????<???????p \????:???Q????????? |
| B | ????Z???X? ?l????????<???????p \????:???Q????????? |
| C | ????Z???X? ?l????????<???????p \????:???Q????????? |
是不是短了很多很多?
為啥是亂碼?8個bit組成一個位元組,本質上,存盤的是位元組陣列,這些位元組展示出來,就是這樣了,,,
犧牲了可讀性,換來了其他好處,
以上,對MySQL的影響其實還好,最嚴重的是對Redis的影響,
一開始,介面不多時,我們在redis中是用set存盤的,讀取也方便,讀寫類似下面這樣:
sadd A 1,2,3
sadd B 2
sismember A 1
sismember B 1
比如我們有100W用戶,那么,在redis中,也就是100W個set,每個set長度在幾百左右,
這其實也就占用二三十G的空間,一切看起來都還好,
然而,隨著介面數量的增長,很多用戶的set的長度同時來到513時,恐怖的事情發生了,
redis直接報警,空間不足,擴容一倍,依然報警空間不足,事情似乎不太對,
各種調查之后,結論簡單說就是:當set的長度小于512時,redis用intset(可以認為是字串)存盤set結構,空間上能節省很多,當長度大于512時,出于性能考慮,redis的存盤結構退化為hashtable,退化發生時,記憶體占用會增加10倍左右,
這可不得了,好在,Redis提供bitmap操作,bitmap在redis中其實存的是一個string,string的內容和mysql中看到的亂碼差不多,redis的對bitmap的操作就不介紹了,網上有很多,
改成bitmap存盤之后,redis空間穩定緩慢增長,不會再出現暴增的情況了,
最后推一下國產的goku api網關,性能比kong還強:www.eolinker.com

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/298264.html
標籤:其他
上一篇:53.最大子序和(題解)
