ThinkPHP 表单令牌高并发场景优化

ThinkPHP 表单令牌高并发场景优化

vendor\topthink\framework\src\think\Request.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 生成请求令牌
* @access public
* @param string $name 令牌名称
* @param mixed $type 令牌生成方法
* @return string
*/
public function buildToken(string $name = '__token__', $type = 'md5'): string
{
$type = is_callable($type) ? $type : 'md5';
$token = call_user_func($type, $this->server('REQUEST_TIME_FLOAT'));

$this->session->set($name, $token);

return $token;
}

上面代码是框架生成请求令牌代码,可以看到如果我们没有特殊设置,默认的 token 是用请求开始时的时间戳(微秒级别的精准度)进行 MD5 加密。

$_SERVER 文档里面对 REQUEST_TIME_FLOAT 的说明是微妙级精度,在测试过程中发现最多只有四位小数,意味这一秒最多有 10^4 个不同的 token。

app\common.php

1
2
3
4
5
6
7
8
<?php

function generate_token_str() {
$data = gettimeofday();
$str = $data['sec'] . '.' . $data['usec'];

return md5($str);
}

通过 gettimeofday 函数,可以拿到最长 6 位数的微秒数,意味会有 10^6 个不同的结果。为了写这篇文章我想了五种方案,只有这个方案在 10W 次循环下速度最快,仅耗时为 0.28 秒,其他都是 0.30 ~ 0.36 秒,如果 10^6 还不满足,那可以选择生成随机字符串。

使用

xxx/index.html

1
2
3
4
5
<input type="hidden" name="__token__" value="{:token('__token__', 'generate_token_str')}" />

{:token_field('__token__', 'generate_token_str')}

{:token_meta('__token__', 'generate_token_str')}
往上