Nuxt 和 Laravel 的 15000 毫秒 TTFB 等待时间
15000ms TTFB waiting time with Nuxt and Laravel
我的 TTFB 时间很长,GET 请求大约需要 15000/17000 毫秒。只有一个特定的调用会发生这种情况,其余的都很好。
我是在添加了 Nuxt Auth 和 Laravel Sanctum 之后才开始遇到这个问题的。 在完成请求并给出 JSON 结果之前,请求保持挂起状态(在调试器网络选项卡下)大约 10 秒。
这是我的nuxt.confing.js
export default {
srcDir: 'resources/nuxt',
ssr: false,
head: {
titleTemplate: '%s - ' + process.env.APP_VERSION,
title: process.env.APP_NAME || '',
meta: [
{ charset: 'utf-8' },
{
name: 'viewport',
content: 'width=device-width, initial-scale=1'
},
{
hid: 'description',
name: 'description',
content: process.env.npm_package_description || ''
}
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
{ rel: 'stylesheet', href: 'https://raw.githack.com/lucperkins/bulma-dashboard/master/dist/bulma-dashboard.css' }
]
},
loading: { color: '#fff' },
css: [
'@/assets/main.scss'
],
plugins: [
"~/plugins/vee-validate.js"
],
components: true,
buildModules: [
'@nuxtjs/dotenv',
'@nuxtjs/eslint-module',
'@nuxtjs/fontawesome',
'@nuxtjs/moment',
],
modules: [
'nuxt-laravel',
// Doc: https://axios.nuxtjs.org/usage
'@nuxtjs/axios',
'nuxt-buefy',
'nuxt-fontawesome',
'@nuxtjs/auth-next'
],
build: {
transpile: [/@fullcalendar.*/,"vee-validate/dist/rules"],
extend(config, ctx) {
config.module.rules.push({
enforce: 'pre',
test: /\.(js|vue)$/,
loader: 'eslint-loader',
exclude: /(node_modules)/,
options: {
fix: true
}
})
}
},
axios: {
baseURL: process.env.API_URL,
debug: true,
credentials: true
},
auth: {
redirect: {
login: '/login',
logout: '/',
callback: '/login',
home: '/dashboard/'
},
strategies: {
'laravelSanctum': {
provider: 'laravel/sanctum',
url: process.env.API_URL
}
},
localStorage: false
},
buefy: {
materialDesignIcons: false,
defaultIconPack: 'fas',
defaultIconComponent: 'font-awesome-icon'
},
router: {
base: '/dashboard/',
linkActiveClass: 'is-active',
middleware: ['auth']
},
fontawesome: {
icons: {
solid: true
}
}
}
Nuxt页面(为了方便只放了js代码)
<script>
// https://www.tutsmake.com/laravel-vue-js-full-calendar-example/
import FullCalendar from '@fullcalendar/vue'
import timeGridPlugin from '@fullcalendar/timegrid'
import resourceTimelinePlugin from '@fullcalendar/resource-timeline'
export default {
components: {
FullCalendar
},
data() {
return {
sessions: [],
todayDisabled: true,
calTitle: '',
calendarOptions: {
plugins: [timeGridPlugin, resourceTimelinePlugin],
schedulerLicenseKey: 'CC-Attribution-NonCommercial-NoDerivatives',
initialView: 'timeGridWeek',
refetchResourcesOnNavigate: true,
-->> resources: '/api/sessions', //the very long call
eventDisplay: 'block',
contentHeight: 'auto',
nowIndicator: true,
locale: 'en-gb',
timezone: 'Europe/London', // without this, after Daylight Saving Time the event goes 1 hour back
headerToolbar: false,
businessHours: [
{
daysOfWeek: [1, 2, 3, 4, 5],
startTime: '08:00',
endTime: '20:00'
},
{
daysOfWeek: [6],
startTime: '9:00',
endTime: '14:00'
}
],
slotMinTime: '07:00:00',
slotMaxTime: '24:00:00',
expandRows: true,
eventClick: (calendar) => {
this.$router.push({
name: 'calendar-id-sessiondate',
params: {
id: calendar.event.id,
sessiondate: this.$moment(calendar.event.start).format(
'YYYY-MM-DD'
)
}
})
},
datesSet: (dateInfo) => {
this.calTitle = dateInfo.view.title
this.todayDisabled = this.$moment().isBetween(
dateInfo.start,
dateInfo.end
)
}
}
}
}
}
</script>
Laravel 控制器
组件“Fullcalendar”通过“resources:'/api/sessions'”运行 GET 请求,该请求转到以下代码。
private function getIntervalTasks($s, $start_period, $end_period)
{
$sessions = [];
foreach(CarbonPeriod::create($s->start_datetime, "$s->interval_num $s->interval_type_human", $s->end_datetime) as $start_session) {
if (Carbon::parse($start_session)->between($start_period, $end_period)) {
$canceled = false;
if ($s->exceptions->isNotEmpty()) {
foreach ($s->exceptions as $e) {
if (Carbon::parse($e->datetime)->toDateString() === $start_session->toDateString()) {
if($e->is_canceled) {
$canceled = true;
break;
} elseif ($e->is_rescheduled) {
$start_session = Carbon::parse($e->datetime);
}
}
}
}
if ($canceled) {
continue;
}
$end_session = Carbon::parse($start_session)->addMinutes($s->duration);
$sessions[] = [
'id' => (int)$s->id,
'title' => $s->client->name,
'start' => $start_session->format('Y-m-d H:i:s'),
'end' => $end_session->format('Y-m-d H:i:s'),
'className' => $s->status_colors
];
}
}
return $sessions;
}
public function index(Request $request) {
$start = (!empty($_GET["start"])) ? ($_GET["start"]) : ('');
$end = (!empty($_GET["end"])) ? ($_GET["end"]) : ('');
$session_period = SessionPattern::has('client')
->where(fn($q)=> $q->whereDate('start_datetime', '<=', $start)->orWhereDate('end_datetime', '>=', $end)
->orWhereBetween(DB::raw('date(`start_datetime`)'), [$start, $end])
->with('exceptions', fn($q) => $q->whereBetween(DB::raw('date(`datetime`)'), [$start, $end])
))->get();
$sessions = [];
foreach ($session_period as $session) {
if($session->is_recurrent){
foreach ($this->getIntervalTasks($session, $start, $end) as $s) {
$sessions[] = $s;
}
} else {
$items = ['none'];
}
}
return response()->json($sessions);
}
ps:我也试过看看是不是Fullcalendar的问题。使用 axios 调用,问题仍然存在。
根据我的类似经历回答你的问题
但为了获得准确的结果,我建议使用 php 分析工具 ,例如 KCachegrind 来找出代码的哪一部分消耗更多的时间来执行。
我认为问题出在碳,这是我的。
Carbon Object Takes Long time to instantiate (is Slow).
我重构了我的代码,不使用 carbon 进行日期比较(在 DBMS 中进行)并且一切都加快了。
可能的瓶颈
我认为你的问题是,你从数据库中获取了很多记录,并用 foreach 遍历它们:
foreach ($this->getIntervalTasks($session, $start, $end) as $s) {
$sessions[] = $s;
}
然后在 getIntervalTasks
你有第二个循环 :
foreach(CarbonPeriod::create($s->start_datetime, "$s->interval_num $s->interval_type_human", $s->end_datetime) as $start_session) {
它按照N*M的顺序执行请求。
Carbon::parse($start_session)->between($start_period, $end_period)
上面的代码Carbon::parse()
是N*M(二度)次运行的慢码
可能的解决方案
我认为您的解决方案是避免按此顺序创建碳对象。
在 DBMS 中实现您的业务逻辑(时间比较逻辑)(使用 存储过程 或 DB 函数) 将解决 TTFB 问题。
假设检验
放置这个
$start = microtime(true);
foreach ($session_period as $session){
...
}
$time_elapsed_secs = microtime(true) - $start;
return response()->json(array_merge($sessions, $time_elapsed_secs);
您可以使用此技术找到耗时的代码部分。
并且显然测试我的建议是否正确。
注意:返回 $time_elapsed_secs
的时间为 微秒 。
我的 TTFB 时间很长,GET 请求大约需要 15000/17000 毫秒。只有一个特定的调用会发生这种情况,其余的都很好。
我是在添加了 Nuxt Auth 和 Laravel Sanctum 之后才开始遇到这个问题的。 在完成请求并给出 JSON 结果之前,请求保持挂起状态(在调试器网络选项卡下)大约 10 秒。
这是我的nuxt.confing.js
export default {
srcDir: 'resources/nuxt',
ssr: false,
head: {
titleTemplate: '%s - ' + process.env.APP_VERSION,
title: process.env.APP_NAME || '',
meta: [
{ charset: 'utf-8' },
{
name: 'viewport',
content: 'width=device-width, initial-scale=1'
},
{
hid: 'description',
name: 'description',
content: process.env.npm_package_description || ''
}
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
{ rel: 'stylesheet', href: 'https://raw.githack.com/lucperkins/bulma-dashboard/master/dist/bulma-dashboard.css' }
]
},
loading: { color: '#fff' },
css: [
'@/assets/main.scss'
],
plugins: [
"~/plugins/vee-validate.js"
],
components: true,
buildModules: [
'@nuxtjs/dotenv',
'@nuxtjs/eslint-module',
'@nuxtjs/fontawesome',
'@nuxtjs/moment',
],
modules: [
'nuxt-laravel',
// Doc: https://axios.nuxtjs.org/usage
'@nuxtjs/axios',
'nuxt-buefy',
'nuxt-fontawesome',
'@nuxtjs/auth-next'
],
build: {
transpile: [/@fullcalendar.*/,"vee-validate/dist/rules"],
extend(config, ctx) {
config.module.rules.push({
enforce: 'pre',
test: /\.(js|vue)$/,
loader: 'eslint-loader',
exclude: /(node_modules)/,
options: {
fix: true
}
})
}
},
axios: {
baseURL: process.env.API_URL,
debug: true,
credentials: true
},
auth: {
redirect: {
login: '/login',
logout: '/',
callback: '/login',
home: '/dashboard/'
},
strategies: {
'laravelSanctum': {
provider: 'laravel/sanctum',
url: process.env.API_URL
}
},
localStorage: false
},
buefy: {
materialDesignIcons: false,
defaultIconPack: 'fas',
defaultIconComponent: 'font-awesome-icon'
},
router: {
base: '/dashboard/',
linkActiveClass: 'is-active',
middleware: ['auth']
},
fontawesome: {
icons: {
solid: true
}
}
}
Nuxt页面(为了方便只放了js代码)
<script>
// https://www.tutsmake.com/laravel-vue-js-full-calendar-example/
import FullCalendar from '@fullcalendar/vue'
import timeGridPlugin from '@fullcalendar/timegrid'
import resourceTimelinePlugin from '@fullcalendar/resource-timeline'
export default {
components: {
FullCalendar
},
data() {
return {
sessions: [],
todayDisabled: true,
calTitle: '',
calendarOptions: {
plugins: [timeGridPlugin, resourceTimelinePlugin],
schedulerLicenseKey: 'CC-Attribution-NonCommercial-NoDerivatives',
initialView: 'timeGridWeek',
refetchResourcesOnNavigate: true,
-->> resources: '/api/sessions', //the very long call
eventDisplay: 'block',
contentHeight: 'auto',
nowIndicator: true,
locale: 'en-gb',
timezone: 'Europe/London', // without this, after Daylight Saving Time the event goes 1 hour back
headerToolbar: false,
businessHours: [
{
daysOfWeek: [1, 2, 3, 4, 5],
startTime: '08:00',
endTime: '20:00'
},
{
daysOfWeek: [6],
startTime: '9:00',
endTime: '14:00'
}
],
slotMinTime: '07:00:00',
slotMaxTime: '24:00:00',
expandRows: true,
eventClick: (calendar) => {
this.$router.push({
name: 'calendar-id-sessiondate',
params: {
id: calendar.event.id,
sessiondate: this.$moment(calendar.event.start).format(
'YYYY-MM-DD'
)
}
})
},
datesSet: (dateInfo) => {
this.calTitle = dateInfo.view.title
this.todayDisabled = this.$moment().isBetween(
dateInfo.start,
dateInfo.end
)
}
}
}
}
}
</script>
Laravel 控制器 组件“Fullcalendar”通过“resources:'/api/sessions'”运行 GET 请求,该请求转到以下代码。
private function getIntervalTasks($s, $start_period, $end_period)
{
$sessions = [];
foreach(CarbonPeriod::create($s->start_datetime, "$s->interval_num $s->interval_type_human", $s->end_datetime) as $start_session) {
if (Carbon::parse($start_session)->between($start_period, $end_period)) {
$canceled = false;
if ($s->exceptions->isNotEmpty()) {
foreach ($s->exceptions as $e) {
if (Carbon::parse($e->datetime)->toDateString() === $start_session->toDateString()) {
if($e->is_canceled) {
$canceled = true;
break;
} elseif ($e->is_rescheduled) {
$start_session = Carbon::parse($e->datetime);
}
}
}
}
if ($canceled) {
continue;
}
$end_session = Carbon::parse($start_session)->addMinutes($s->duration);
$sessions[] = [
'id' => (int)$s->id,
'title' => $s->client->name,
'start' => $start_session->format('Y-m-d H:i:s'),
'end' => $end_session->format('Y-m-d H:i:s'),
'className' => $s->status_colors
];
}
}
return $sessions;
}
public function index(Request $request) {
$start = (!empty($_GET["start"])) ? ($_GET["start"]) : ('');
$end = (!empty($_GET["end"])) ? ($_GET["end"]) : ('');
$session_period = SessionPattern::has('client')
->where(fn($q)=> $q->whereDate('start_datetime', '<=', $start)->orWhereDate('end_datetime', '>=', $end)
->orWhereBetween(DB::raw('date(`start_datetime`)'), [$start, $end])
->with('exceptions', fn($q) => $q->whereBetween(DB::raw('date(`datetime`)'), [$start, $end])
))->get();
$sessions = [];
foreach ($session_period as $session) {
if($session->is_recurrent){
foreach ($this->getIntervalTasks($session, $start, $end) as $s) {
$sessions[] = $s;
}
} else {
$items = ['none'];
}
}
return response()->json($sessions);
}
ps:我也试过看看是不是Fullcalendar的问题。使用 axios 调用,问题仍然存在。
根据我的类似经历回答你的问题
但为了获得准确的结果,我建议使用 php 分析工具 ,例如 KCachegrind 来找出代码的哪一部分消耗更多的时间来执行。
我认为问题出在碳,这是我的。
Carbon Object Takes Long time to instantiate (is Slow).
我重构了我的代码,不使用 carbon 进行日期比较(在 DBMS 中进行)并且一切都加快了。
可能的瓶颈
我认为你的问题是,你从数据库中获取了很多记录,并用 foreach 遍历它们:
foreach ($this->getIntervalTasks($session, $start, $end) as $s) {
$sessions[] = $s;
}
然后在 getIntervalTasks
你有第二个循环 :
foreach(CarbonPeriod::create($s->start_datetime, "$s->interval_num $s->interval_type_human", $s->end_datetime) as $start_session) {
它按照N*M的顺序执行请求。
Carbon::parse($start_session)->between($start_period, $end_period)
上面的代码Carbon::parse()
是N*M(二度)次运行的慢码
可能的解决方案
我认为您的解决方案是避免按此顺序创建碳对象。
在 DBMS 中实现您的业务逻辑(时间比较逻辑)(使用 存储过程 或 DB 函数) 将解决 TTFB 问题。
假设检验
放置这个
$start = microtime(true);
foreach ($session_period as $session){
...
}
$time_elapsed_secs = microtime(true) - $start;
return response()->json(array_merge($sessions, $time_elapsed_secs);
您可以使用此技术找到耗时的代码部分。
并且显然测试我的建议是否正确。
注意:返回 $time_elapsed_secs
的时间为 微秒 。