关于OAuth2的登录问题追查

案例回顾

一开始测试人员在做测试的时候,偶尔会冒出一个很奇怪的问题:同时3个人登陆,或者打某些页面。服务器会异常卡,最后宕掉!

问题追踪

1. 缓存过大?

登陆需要注册session,session数据量比较大(包含整个权限数组!)
打开美术页面需要远程请求认证系统,获取整张用户表信息缓存起来。(\$this->cache->store(“userList”, $userList ) )

注:如果保存数据到memcached时, 数据量过大 1063648 字节(超过了1m), 只写了 786434 字节, 导致部分数据没写成功. 初步怀疑是保存的数据量过大。

排除:在store之前检查一下数据容量!数据并没有这么大,而且缓存过大也不会导致服务宕掉。

2. 并发请求用户信息,store阻塞?

由于用户信息,经常性得使用。这里的用户信息又不存在本系统,需要远程获取。为了减少远程拉取的次数,设计成缓冲一个小时的用户数据,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 人事信息基本原型
*/
public function getAllUsers( $del=false ){
$key = md5("class:".__CLASS__."+function:".__FUNCTION__);
if( $del ){
$this->cache->delete($key);
}
if( ! $rs = $this->cache->fetch($key) ){
//远程拉取
$res = $this->o->getAllUser($this->openId, $this->token);
$res = json_decode($res,true);
if($res['result']===1){
$rs= $res['data'];
}
if ( $rs ) {
$this->cache->store ( $key, $rs );
}
}
return $rs;
}

注:这里采取的策略是不公平的。因为第一个用户进来页面,暂时没有用户数据,后台会拉取数据缓存起来。第二用户,就不需要额外请求。直到一个小时缓存失效才又拉取。猜想会不会由于请求拉取用户的时间过长。在第一个用户发生拉取动作,等待请求响应时。第二用户来了,同样发起请求。从而导致阻塞。
排除:服务器的并发量不至于这么小吧。在没有发生用户拉取的情况,比如登陆,也会出现服务器宕掉的情况。

3. 密码加/解密?

做了一个小实验,叫上俩个人,每人同时打开两个浏览器,同时点击登陆。神奇的事情发生了。服务宕掉!
由于该系统的登陆比较特殊,参考了OAuth2的设计原理。可疑的点很多,断点逐步检查,初步估计是程序卡在某个死循环里面。
注:登陆的时候用了一个特殊可逆加密方法—authcode(discuz 经典php加密解密函数),每次登陆至少要使用4次这个方法加/解密。这个加密算法里面有大量的循环计算。
排除:碰巧有内网另外一个系统也用了这个加密方法来处理cookie,然而该系统在线上运行颇久,从来没发生过服务器宕掉的情况。
另外,自己写了一个小demo,用ab测试发起请求,100次的并发没任何问题。

4. curl阻塞?

终于把矛头指向OAuth2的设计上面来了。这里说一下OAuth2的基本实现原理:

可疑点在于:登陆验证和确认授权。登陆验证在2.3曾讨论过了,这里来看一下登陆验证密码通过之后会发生什么事。
注:登陆验证是在认证系统域名下进行的。基本过程:

1
2
3
4
5
6
7
4.1 输入用户/密码,POST 到 open.lee.com/index.php?action=authLogin;
4.2 验证密码通过,自动授权/主动授权。认证系统会返回该用户的token、OpenID;
4.3 根据token、OpenID等拼凑CallBack地址,location回到im.lee.com/index.php?action=CallBack……
4.4 CallBack非常忙碌~,而且很关键。先后进行;
4.4.1 getToken:根据token,远程获取OpenID,保存到数据库;
4.4.2 getUserInfo:根据token+OpenID,远程获取用户信息,注册session+cookie;
4.4.3 getAllAuth:根据token+OpenID,远程获取用户权限列表,注册到session。

关于4.4需要后台分别构造三次Curl请求到Open获取一些用户权限信息。
猜想:Curl会阻塞。三个用户ABC同时进来,顺序可能是这样的:

1
2
3
4
5
6
7
8
9
08:00 A->getToken()
08:01 B->getToken()
08:02 C->getToken()
08:03 A->getUserInfo()
08:04 B->getUserInfo()
08:05 C->getUserInfo()
08:06 A->getAllAuth()
08:07 B->getAllAuth()
08:08 C->getAllAuth()

在于用户层来看这只是属于一次请求,然后这次请求很容易就会超时。A从08:00开始请求等到08:06才完成响应。
排除:被主管告知,不同用户访问站点,会实例化不同单独的进程来处理的,不会出现阻塞。也就是说实际却是这样的:

1
2
3
08:00 A->getToken()
08:00 B->getToken()
08:00 C->getToken()

5. max_children=5!

解析:max_children是PHP-FPM Pool 最大的子进程数,他数值取决于你的服务器内存。 假设你打算给10G内存给当前配置的PHP-FPM Pool,一般一个PHP请求占用内存10M-40M,我们按站点每个PHP请求占用内存25M,这样max_children = 10G/25M = 409。所以,这个值可以根据情况算出来
原因:由于fpm允许并发的请求数目比较低,而大明与认证又搭在同一台机器。导致并发数目超出负荷导致。
其他:start_servers 是服务器启动时的最大子进程数
min_spare_servers 闲时,最小子进程数
max_spare_servers 闲时,最大子进程数
max_requests=500,为了预防第三方组件,有内存泄露的问题,每当请求数目超出500次,就会重启一个fpm,释放内存。

罐头很懒 (⊙v⊙)<br><br>工作日日常 :<br>do {<br>&nbsp;&nbsp;打代码<br>} while ( 发呆 || 吃饭 )<br><br>周末日常 :<br>( 鱼罐头 || 午餐肉 || 炸鸡块 ) +<br>( 罐可乐 || 瓶啤酒 ) +<br>( 盒仔饭 || 艇仔粥 || 即食面 ) +<br>( 轻音乐 || 肥皂剧 || 热网综 ) +<br>( 水果糖 || 甜布丁 )