عیبیابی سیستم کش
این راهنما به شما کمک میکند تا مسائل رایج سیستم کش را تشخیص و حل کنید.
مسائل رایج
دادههای کش گم شده
علائم
- دادههای کش زمانی که انتظار میرود در دسترس نیستند
hasCacheData()برای کلیدهایی که باید پر باشند false برمیگرداند- مقادیر خالی یا null هنگام دسترسی به کلیدهای کش
علل احتمالی
- کنترلکننده کش ثبت نشده
// ❌ مشکل: کنترلکننده کش در service provider ثبت نشده
// ServiceProvider.php ثبت را از دست داده
// ✅ راهحل: کنترلکننده را در service provider خود ثبت کنید
public function register(): void
{
$this->app->tag(
[
ProductRelatedItemsCacheHandler::class,
ProductPricingCacheHandler::class,
],
'cache.handlers'
);
}
- رویدادها ارسال نمیشوند
// ❌ مشکل: رویدادها ارسال نمیشوند
public function update(array $data)
{
$this->product->update($data);
// ارسال رویداد گم شده
}
// ✅ راهحل: پس از بهروزرسانی مدل رویدادها را ارسال کنید
public function update(array $data)
{
$this->product->update($data);
event(new ProductUpdatedEvent($this->product));
}
- پیکربندی رویداد نادرست
// ❌ مشکل: پیکربندی رویداد نادرست
new CacheConfigDTO(
key: 'related_products',
relatedEvents: [
ProductCreatedEvent::class, // ProductUpdatedEvent گم شده
],
// سایر پیکربندیها...
)
// ✅ راهحل: تمام رویدادهای مرتبط را شامل کنید
new CacheConfigDTO(
key: 'related_products',
relatedEvents: [
ProductCreatedEvent::class,
ProductUpdatedEvent::class,
ProductCategoryChangedEvent::class,
],
// سایر پیکربندیها...
)
- خطاهای کنترلکننده کش
لاگهای خود را برای استثناها در کنترلکنندههای کش بررسی کنید. خطاهای رایج شامل:
- استثناهای پرسوجوی پایگاه داده
- وابستگیهای گم شده
- خطاهای منطقی در تبدیل داده
دستورات تشخیصی
# بررسی وجود داده کش برای یک مدل خاص
php artisan cache:check --model=Product --id=123
# مشاهده آمار کش برای یک مدل
php artisan cache:stats --model=Product --id=123
# دیباگ رویدادهای کش
php artisan cache:debug-events --model=Product --watch
خطاهای تبدیل نوع
علائم
- استثناها هنگام دسترسی به دادههای کش
- انواع داده غیرمنتظره
- استثناهای
TypeError
علل احتمالی
- نوع تبدیل نادرست
// ❌ مشکل: نوع تبدیل نادرست
new CacheConfigDTO(
key: 'view_count',
// سایر پیکربندیها...
cast: CacheCastEnum::STRING, // باید INTEGER باشد
)
// ✅ راهحل: از نوع تبدیل مناسب استفاده کنید
new CacheConfigDTO(
key: 'view_count',
// سایر پیکربندیها...
cast: CacheCastEnum::INTEGER,
)
- فرمت داده ناسازگار
// ❌ مشکل: فرمت داده ناسازگار در کنترلکننده
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
// گاهی رشته، گاهی عدد صحیح برمیگرداند
$count = $this->getCount($eventDto->getEntityId());
return new CacheHandlerResultCollection([
new CacheHandlerResult($eventDto->getEntityId(), $count)
]);
}
// ✅ راهحل: اطمینان از نوع داده سازگار
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$count = $this->getCount($eventDto->getEntityId());
// تبدیل صریح به عدد صحیح
$count = (int) $count;
return new CacheHandlerResultCollection([
new CacheHandlerResult($eventDto->getEntityId(), $count)
]);
}
- فرمت تاریخ نامعتبر
// ❌ مشکل: فرمت تاریخ نامعتبر
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$lastActivity = '2023-01-01'; // تاریخ رشتهای
return new CacheHandlerResultCollection([
new CacheHandlerResult($eventDto->getEntityId(), $lastActivity)
]);
}
// ✅ راهحل: از نمونههای Carbon برای تاریخها استفاده کنید
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$lastActivity = Carbon::parse('2023-01-01');
return new CacheHandlerResultCollection([
new CacheHandlerResult($eventDto->getEntityId(), $lastActivity)
]);
}
دستورات تشخیصی
# اعتبارسنجی انواع داده کش
php artisan cache:validate --model=Product --id=123
# دیباگ مسائل تبدیل
php artisan cache:debug-cast --model=Product --id=123 --key=view_count
مسائل عملکرد
علائم
- زمان پاسخ کند
- استفاده بالای CPU
- تمام شدن pool اتصال پایگاه داده
- تجمع صف
علل احتمالی
- پرسوجوهای ناکارآمد در کنترلکنندههای کش
// ❌ مشکل: پرسوجوهای ناکارآمد
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$productId = $eventDto->getEntityId();
// مشکل N+1 query
$relatedProducts = Product::where('category_id', function ($query) use ($productId) {
$query->select('category_id')
->from('products')
->where('id', $productId);
})->get();
foreach ($relatedProducts as $product) {
// این هر دستهبندی محصول را در پرسوجوی جداگانه بارگذاری میکند
$categories[] = $product->category;
}
// پردازش داده...
}
// ✅ راهحل: پرسوجوها را بهینهسازی کنید
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$productId = $eventDto->getEntityId();
// پرسوجوی کارآمد با eager loading
$relatedProducts = Product::with('category')
->where('category_id', function ($query) use ($productId) {
$query->select('category_id')
->from('products')
->where('id', $productId);
})
->get();
// پردازش داده...
}
- تعداد زیاد بهروزرسانیهای همزمان کش
// ❌ مشکل: تعداد زیاد بهروزرسانیهای همزمان
new CacheConfigDTO(
key: 'expensive_computation',
// سایر پیکربندیها...
mode: CacheModeEnum::SYNC, // عملیات مسدودکننده
)
// ✅ راهحل: از حالت ناهمزمان برای عملیات پرهزینه استفاده کنید
new CacheConfigDTO(
key: 'expensive_computation',
// سایر پیکربندیها...
mode: CacheModeEnum::ASYNC, // غیرمسدودکننده
)
- مجموعه دادههای بزرگ در کش
// ❌ مشکل: کش کردن دادههای بیش از حد
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$productId = $eventDto->getEntityId();
// دادههای بیش از حد
$allRelatedProducts = Product::with('category', 'tags', 'reviews', 'images')
->where('category_id', function ($query) use ($productId) {
$query->select('category_id')
->from('products')
->where('id', $productId);
})
->get();
return new CacheHandlerResultCollection([
new CacheHandlerResult($productId, $allRelatedProducts)
]);
}
// ✅ راهحل: محدود کردن دادهها و انتخاب فقط فیلدهای مورد نیاز
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$productId = $eventDto->getEntityId();
// دادههای محدود با فیلدهای مشخص
$relatedProducts = Product::select(['id', 'name', 'price', 'image_url'])
->where('category_id', function ($query) use ($productId) {
$query->select('category_id')
->from('products')
->where('id', $productId);
})
->limit(5)
->get();
return new CacheHandlerResultCollection([
new CacheHandlerResult($productId, $relatedProducts)
]);
}
دستورات تشخیصی
# پروفایل عملکرد کنترلکننده کش
php artisan cache:profile --handler=ProductRelatedItemsCacheHandler
# نظارت بر زمان تولید کش
php artisan cache:monitor --model=Product --watch
# تحلیل اندازه کش
php artisan cache:analyze-size --model=Product --limit=20
خطاهای محافظت فیلد
علائم
- استثناهای
CacheFieldAccessException - عدم توانایی بهروزرسانی دادههای کش
- عدم ماندگاری دادههای کش در پایگاه داده
علل احتمالی
- دسترسی مستقیم به فیلد محافظت شده
// ❌ مشکل: دسترسی مستقیم به فیلد کش
$product->cache = ['related_items' => $items]; // استثنا پرتاب میکند
// ✅ راهحل: از API سیستم کش استفاده کنید
event(new ProductCacheUpdateEvent($product, 'related_items', $items));
- trait HasCacheField گم شده
// ❌ مشکل: trait HasCacheField گم شده
class Product extends Model
{
// trait گم شده
protected $casts = [
'cache' => 'json',
];
}
// ✅ راهحل: trait HasCacheField را اضافه کنید
class Product extends Model
{
use HasCacheField;
protected $casts = [
'cache' => 'json',
];
}
- نام فیلد کش نادرست
// ❌ مشکل: نام فیلد کش نادرست
class Product extends Model
{
use HasCacheField;
// پیکربندی نام فیلد کش گم شده
protected $casts = [
'data_cache' => 'json', // متفاوت از پیشفرض 'cache'
];
}
// ✅ راهحل: پیکربندی نام فیلد کش سفارشی
class Product extends Model
{
use HasCacheField;
protected $cacheField = 'data_cache';
protected $casts = [
'data_cache' => 'json',
];
}
دستورات تشخیصی
# بررسی وضعیت محافظت فیلد
php artisan cache:check-protection --model=Product
# لیست مدلهای دارای trait HasCacheField
php artisan cache:list-models
# دیباگ دسترسی فیلد
php artisan cache:debug-access --model=Product --id=123
مسائل پردازش ناهمزمان
علائم
- عدم بهروزرسانی کش در حالت ناهمزمان
- شکست کارهای صف
- وضعیت ناسازگار کش
علل احتمالی
- عدم اجرای queue worker
# ✅ راهحل: راهاندازی queue workerها
php artisan queue:work --queue=cache
- پیکربندی نادرست صف
// ❌ مشکل: پیکربندی صف گم شده
// config/queue.php فاقد صف کش
// ✅ راهحل: پیکربندی صف کش
// config/queue.php
'connections' => [
'redis' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => 90,
'block_for' => null,
],
],
// .env
QUEUE_CONNECTION=redis
REDIS_QUEUE=cache
- استثنا در کنترلکننده ناهمزمان
لاگهای خود را برای استثناها در کنترلکنندههای کش ناهمزمان بررسی کنید. مسائل رایج شامل:
- مشکلات اتصال پایگاه داده در queue workerها
- مسائل زمان اجرا برای عملیات طولانی
- محدودیتهای حافظه فراتر رفته
دستورات تشخیصی
# نظارت بر وضعیت صف
php artisan queue:monitor
# دیباگ کارهای کش شکست خورده
php artisan cache:debug-queue
# تلاش مجدد کارهای شکست خورده
php artisan queue:retry --queue=cache
ابزارهای دیباگ
بررسی کش
مشاهده محتویات کش
// بررسی دادههای کش برای یک مدل
$product = Product::find(123);
dd($product->getCacheData());
// بررسی وجود کلید کش مشخص
if ($product->hasCacheData('related_products')) {
// کش وجود دارد
}
// دریافت آمار کش
$stats = $product->getCacheStatistics();
dd($stats);
دستورات دیباگ کش
# مشاهده تمام کلیدهای کش برای یک مدل
php artisan cache:keys --model=Product --id=123
# نمایش دادههای کش
php artisan cache:dump --model=Product --id=123 --key=related_products
# پاک کردن کش برای تست
php artisan cache:clear --model=Product --id=123
دیباگ رویداد
نظارت بر رویدادهای کش
// این را برای توسعه محلی به AppServiceProvider اضافه کنید
public function boot()
{
if (app()->environment('local')) {
Event::listen('*', function ($eventName, array $data) {
if (str_contains($eventName, 'Cache')) {
\Log::debug('رویداد کش: ' . $eventName, [
'data' => $data
]);
}
});
}
}
دستورات دیباگ رویداد
# نظارت بر رویدادهای کش در زمان واقعی
php artisan cache:events --watch
# راهاندازی تولید کش برای تست
php artisan cache:generate --model=Product --id=123
عیبیابی پیشرفته
شرایط مسابقه (Race Conditions)
شرایط مسابقه زمانی رخ میدهند که چندین فرآیند همزمان سعی در بهروزرسانی کش دارند.
علائم
- دادههای کش ناسازگار
- بازنویسی بهروزرسانیهای کش
- بهروزرسانیهای جزئی کش
راهحلها
- استفاده از تراکنشهای پایگاه داده
// ✅ راهحل: از تراکنشها برای بهروزرسانی کش استفاده کنید
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$entityId = $eventDto->getEntityId();
DB::transaction(function () use ($entityId) {
$model = Product::lockForUpdate()->find($entityId);
if (!$model) {
return new CacheHandlerResultCollection();
}
// پردازش و بهروزرسانی کش
// ...
});
}
- پیادهسازی قفلگذاری خوشبینانه
// ✅ راهحل: استفاده از قفلگذاری خوشبینانه
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$entityId = $eventDto->getEntityId();
$model = Product::find($entityId);
if (!$model) {
return new CacheHandlerResultCollection();
}
$currentVersion = $model->cache_version ?? 0;
// پردازش دادههای کش
$cacheData = $this->generateCacheData($model);
// بهروزرسانی با بررسی نسخه
$updated = DB::table('products')
->where('id', $entityId)
->where('cache_version', $currentVersion)
->update([
'cache' => json_encode($cacheData),
'cache_version' => $currentVersion + 1
]);
if (!$updated) {
// مدیریت تضاد نسخه
$this->logger->warning('تضاد بهروزرسانی کش تشخیص داده شد', [
'entity_id' => $entityId,
'version' => $currentVersion
]);
// تلاش مجدد یا اطلاعرسانی
}
}
مسائل حافظه
علائم
- خطاهای محدودیت حافظه PHP
- کرش queue worker ها
- عملکرد کند با مجموعه دادههای بزرگ
راهحلها
- تکهتکه کردن مجموعه دادههای بزرگ
// ✅ راهحل: پردازش دادهها به صورت تکهای
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$categoryId = $eventDto->getEntityId();
$results = new CacheHandlerResultCollection();
// پردازش به صورت تکهای
Product::where('category_id', $categoryId)
->chunkById(100, function ($products) use (&$results) {
foreach ($products as $product) {
$cacheData = $this->processProduct($product);
$results->add(new CacheHandlerResult($product->id, $cacheData));
}
});
return $results;
}
- بهینهسازی استفاده از حافظه
// ✅ راهحل: بهینهسازی استفاده از حافظه
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$productId = $eventDto->getEntityId();
// استفاده از cursor برای پیمایش کارآمد حافظه
$relatedProducts = Product::where('category_id', function ($query) use ($productId) {
$query->select('category_id')
->from('products')
->where('id', $productId);
})
->select(['id', 'name', 'price']) // فقط فیلدهای مورد نیاز را انتخاب کنید
->cursor();
$results = [];
foreach ($relatedProducts as $product) {
$results[] = [
'id' => $product->id,
'name' => $product->name,
'price' => $product->price,
];
// پاک کردن موجودیت از حافظه
$product = null;
}
return new CacheHandlerResultCollection([
new CacheHandlerResult($productId, $results)
]);
}
وابستگیهای دایرهای
علائم
- حلقههای بینهایت در بهروزرسانیهای کش
- ارسال بیش از حد رویداد
- استفاده بالای CPU
راهحلها
- تشخیص و شکستن ارجاعات دایرهای
// ✅ راهحل: ردیابی و جلوگیری از ارجاعات دایرهای
class CircularReferenceTracker
{
private static $processing = [];
public static function isProcessing(string $key): bool
{
return isset(self::$processing[$key]);
}
public static function startProcessing(string $key): void
{
self::$processing[$key] = true;
}
public static function endProcessing(string $key): void
{
unset(self::$processing[$key]);
}
}
// در کنترلکننده کش
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$entityId = $eventDto->getEntityId();
$cacheKey = $eventDto->getCacheKey();
$trackingKey = "{$entityId}:{$cacheKey}";
// بررسی ارجاع دایرهای
if (CircularReferenceTracker::isProcessing($trackingKey)) {
$this->logger->warning('ارجاع دایرهای تشخیص داده شد', [
'entity_id' => $entityId,
'cache_key' => $cacheKey
]);
return new CacheHandlerResultCollection();
}
CircularReferenceTracker::startProcessing($trackingKey);
try {
// پردازش کش
// ...
return $results;
} finally {
CircularReferenceTracker::endProcessing($trackingKey);
}
}
- پیادهسازی تأخیر در ارسال رویداد (Debouncing)
// ✅ راهحل: تأخیر در ارسال رویدادهای کش
class DebouncedCacheEventDispatcher
{
private static $pendingEvents = [];
private static $dispatchScheduled = false;
public static function dispatch(string $eventClass, $entity): void
{
$entityId = $entity->id;
$key = "{$eventClass}:{$entityId}";
self::$pendingEvents[$key] = [
'class' => $eventClass,
'entity' => $entity,
];
if (!self::$dispatchScheduled) {
self::$dispatchScheduled = true;
// زمانبندی ارسال در پایان درخواست
app()->terminating(function () {
self::dispatchPending();
});
}
}
private static function dispatchPending(): void
{
foreach (self::$pendingEvents as $event) {
event(new $event['class']($event['entity']));
}
self::$pendingEvents = [];
self::$dispatchScheduled = false;
}
}
// استفاده
DebouncedCacheEventDispatcher::dispatch(ProductUpdatedEvent::class, $product);
پیامهای خطای رایج
"CacheFieldAccessException: دسترسی مستقیم به فیلد کش مجاز نیست"
این خطا زمانی رخ میدهد که سعی در دسترسی مستقیم به فیلد cache میکنید.
راهحل: به جای دسترسی مستقیم از API سیستم کش استفاده کنید.
// ❌ این کار را نکنید
$product->cache = ['data' => $value];
// ✅ این کار را انجام دهید
event(new ProductCacheUpdateEvent($product, 'data', $value));
"TypeError: نمیتوان به offset روی مقدار نوع null دسترسی پیدا کرد"
این خطا زمانی رخ میدهد که سعی در دسترسی به کلید کشی که وجود ندارد یا null است میکنید.
راهحل: همیشه قبل از دسترسی وجود داده کش را بررسی کنید.
// ❌ این کار را نکنید
$relatedProducts = $product->related_products['data'];
// ✅ این کار را انجام دهید
if ($product->hasCacheData('related_products')) {
$relatedProducts = $product->related_products['data'] ?? [];
} else {
$relatedProducts = [];
}
"No handler found for cache key: [key_name]"
این خطا زمانی رخ میدهد که هیچ کنترلکننده کشی برای یک کلید کش خاص ثبت نشده است.
راهحل: کنترلکننده مناسب را در service provider خود ثبت کنید.
// ✅ ثبت کنترلکننده در service provider
public function register(): void
{
$this->app->tag(
[
ProductRelatedItemsCacheHandler::class,
],
'cache.handlers'
);
}
نظارت بر عملکرد
زمان تولید کش
زمان صرف شده برای تولید دادههای کش را نظارت کنید:
// اضافه کردن زمانسنجی به کنترلکنندههای کش
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$startTime = microtime(true);
// پردازش کش...
$endTime = microtime(true);
$duration = round(($endTime - $startTime) * 1000, 2);
$this->logger->info('تولید کش تکمیل شد', [
'entity_id' => $eventDto->getEntityId(),
'cache_key' => $eventDto->getCacheKey(),
'duration_ms' => $duration
]);
return $results;
}
مراحل بعدی
پس از عیبیابی، موارد زیر را در نظر بگیرید:
- بهترین شیوهها - پیادهسازی خود را بهبود دهید
- راهنمای پیادهسازی - جزئیات پیادهسازی را بررسی کنید
- نمای کلی سیستم کش - تصویر کلی را درک کنید