想象一下,我们的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.cnblogs.com/dc20181010/p/15239551.html

你还没有登录,请先登录注册
  • 还没有人评论,欢迎说说您的想法!