• 0 رای - 0 میانگین
  • 1
  • 2
  • 3
  • 4
  • 5
روش صحیح پیاده سازی یک شمارنده بازدیدکننده
#1
تازگی در بخش PHP فروم برنامه نویس در این ارتباط با دوستان تبادل نظر داشتیم که معلوم شد الگوریتم شمارنده هایی که افراد خودشون طراحی میکنن یا حتی اونایی که احتمالا یکی نوشته و خیلی افراد دیگه همون رو استفاده میکنن، اکثرا دچار ایرادهای اساسی هستن.
این ایرادها به این مسئله برمیگرده که افراد به این نکته توجه ندارن که یک منبع داده ای در وب، اعم از فایل یا دیتابیس، اغلب بصورت همزمان توسط چند Process یا Thread که هرکدوم یک Page/Script هستن مورد دسترسی قرار میگیرن و بنابراین خیلی از خواص و تکنیک های لازم درمورد برنامه نویسی Multi-thread و کلا برنامه هایی که دارای دسترسی همزمان به منابع داده ای مشترک هستن، درمورد ایجاد یک شمارندهء صحیح هم مصداق پیدا میکنن.
شاید بدونید که در برنامه نویسی Multi-thread اغلب از مکانیزمهایی مثل Lock برای جلوگیری از تداخل خواندن و نوشتن همزمان thread های مختلف در منابع مشترک استفاده میشه.

بطور مثال یک روش اشتباه این هست که عدد شماره رو بسادگی توی یک فایل ذخیره میکنن و مقدار عدد رو با هر بازدید میخونن و افزایش میدن و دوباره در فایل ذخیره میکن و تمام اساس الگوریتم همین هست. این معمولا مشکل دار ترین نوع شمارنده هست که میتونه براحتی به شمارش بی دقت، و حتی خرابی اساسی نتایج شمارنده منجر بشه.
این به خاطر این هست که دو یا چند صفحه میتونن بصورت همزمان مقدار داخل این شمارنده رو بخونن یا بصورت همزمان درش بنویسن. درمورد خواندن مشکل عدم دقت شمارنده پیش میاد، چون بطور مثال سه صفحه بصورت همزمان عدد ۵۲۳ رو از شمارنده میخونن و هرکدوم به این عدد یکی اضافه میکنن و مقدار حاصل رو در فایل مینویسن؛ یعنی همه ۵۲۴ رو مینویسن. درحالیکه ما سه صفحه/بازدید مجزا داشتیم و بنابراین عدد ۵۲۶ باید در فایل نوشته میشد.
درمورد نوشتن هم باز بعلت اینکه امکان داره صفحات مختلف بصورت همزمان در یک فایل بنویسن (بنده اینو عملا تست کردم)، اطلاعات درج شده میتونن با هم قاطی بشن که میتونه منجر به اشتباه شدن اساسی شمارنده یا حتی از کار افتادن اون بشه (بسته به نحوهء طراحی شمارنده).
البته ممکنه احتمال هرکدوم از این وقایع کم یا زیاد باشه؛ اما بهرحال تاجایی که فهمیدم تئوری و اصول حاکم به اینصورت هست و باید از جانب برنامه نویس درنظر گرفته و براش تمهید بشه.

در اون تاپیک یک مورد هم از شمارنده ای که عدد شمارش رو در دیتابیس ذخیره میکرد ارائه شد، که اونهم باز اشکال عدم دقت رو داشت.
درمورد دیتابیس خوشبختانه بعلت اینکه نرم افزار سرور دیتابیس خودش نوشتن های همزمان رو کنترل میکنه تا با هم تداخل نکنن، مشکل قاطی شدن اطلاعات حاصل از نوشتن همزمان صفحات مختلف رو نداریم، اما مشکل خواندن هزمان همچنان وجود داره.

خب راه حل چیه؟
یکی از راه حلهای ممکن (که بنده قبلا در شمارنده ای که طراحی کردم بکار بردم) اینه که به ازای هر بازدیدکننده، یک رکورد در جدول مخصوص شمارنده درج میکنیم. برای شمردن تعداد بازدید کننده ها هم کافیه تعداد رکوردهای درج شده در جدول شمارنده رو با دستوری مثل select count(no) as num from visitors بدست بیاریم.
این روش بخاطر اینکه بر خواندن تعداد قبلی بازدید کننده ها از دیتابیس استوار نیست، از خطای ناشی از خواندن همزمان تعداد بازدید کننده ها توسط چند صفحه مصونه. درمورد درج رکوردها در دیتابیس هم که دیتابیس خودش این مسئله رو مدیریت میکنه و هیچوقت در یک DBMS داده های دو یا چند دستور INSERT با هم قاطی نمیشه. هر دستور دیر یا زود (دستور ممکنه توی نوبت بمونه) رکورد مجزای خودش رو ایجاد میکنه.

بحث های مربوط به این موضوع رو میتونید در تاپیک مربوطه در سایت برنامه نویس هم ببینید تا بیشتر روشن بشید که موضوع از چه قراره.

گفتم این موضوع رو مطرح کنم چون بنظر میرسه درحال حاضر خیلی افراد شمارنده های معیوب طراحی یا استفاده میکنن.
منتظر بیان نظر و تجربهء شما در این تاپیک هستیم.
  پاسخ
تشکر شده توسط : molana payam amir.s ahora paull admin
#2
نقل قول:فك كنم update set counter=counter+1 از همه چي راحت تر باشه!!!

خیلی وقته از خوندن رفرنس MySQL میگذره! یادم نیست این ساختار چه خصوصیاتی داره. الانم وقت ندارم برم دنبالش. ببخشید!
البته احتمالش زیاده برای این کاربرد دقیقا درست و مناسب باشه، ولی مطمئن نیستم. کل ساختار باید اتمیک باشه. باید دید رفرنس در اینمورد چی میگه. ولی فکر میکنم همینطور باشه.
اگر اطلاعات بیشتری داشتی در تاپیک بذار. ضمنا این چه وضعشه، یه دستور کامل و دقیق بذار که سر و تهش معلوم باشه!!
اما روشی رو که بنده پیشنهاد کردم بخاطر این هست که بنظرم روی هر نوع دیتابیسی در هر شرایطی درست جواب میده و دیگه توش شک و تردید نیست. ضمنا چون درواقع بغیر از شمارش، اطلاعات دیگری از کاربر مثل نوع مرورگر و Referrer و IP و غیره رو هم ثبت میکنم، روشی که پیشنهاد کردم روش مناسب و کافی هست. یعنی بهرصورت باید به ازای هر کاربر یک رکورد درج کنیم، پس چه بهتر که شمارنده رو هم از طریق همین کدها پیاده سازی کنیم (با یک تیر دو نشان!). تقریبا هیچ کدنویسی اضافه ای هم نداره.

درواقع موقعی که میخواستم شمارندهء خودم رو درست کنم، وقت و حالش رو نداشتم که بازم بصورت دقیق جستجو کنم و کلی مطلب و رفرنس بخونم و دست آخر بازم روزهء شک دار بگیرم و مطمئن نباشم و برنامه بیخودی پیچیده و وابسته به عوامل خارجی بشه. بخاطر همین به فکر یک روش افتادم که بدون مشکل بودنش بدیهی و پیاده سازیش ساده و سریع باشه. اینه که به روش درج یک رکورد به ازای هر کاربر رسیدم؛ ضمن اینکه همراهش اطلاعات جانبی دیگری رو هم درج میکنیم. ضمنا وقتی یک کار دیتابیسی رو به ازای هرکاربر انجام میدیم، بنظرم هزینه های عمدهء ارتباط با دیتابیس و غیره پرداخت شدن، پس ثبت یک مقدار اطلاعات جانبی بیشتر، ضرر خاصی نمیزنه و میتونه استفادهء بهینه باشه و از اطلاعات ثبت شده بعدا برای چیزهایی مثل آمارهای مختلف یا حتی موارد امنیتی استفاده کرد. خوبی دیگرش هم اینه که به وجود و خصوصیات دستورات خاصی در DBMS وابسته نیست و میتونه در هر جایی با هر سرور دیتابیسی با اطمینان بکار بره.

البته روش خوبی معرفی کردی. بنظرم درسته.
  پاسخ
تشکر شده توسط : admin
#3
ثبت یک رکورد رو خیلی می پسندم.
واقعا روش خوبی هست ( البته باید هواستون باشه چون ممکنه بار زیادی رو روی دیتابیس بندازه حتما از delay insert استفاده کنین) استفاده از این مورد به شدت می تونه توی بارهای زیاد بهتون کمک کنه و منابعتون رو صرف آمار در لحظه نکنه.

البته وقتی که چیزهایی مثل google analytic هست به نظر من رفتن دنبال این جور چیزها در مواقعی که میشه از اینها استفاده کرد یه جورایی اشتباه هست.
وقتی حتی قابلیت ایجاد goal و آمار بسیار دقیقی رو میده
البته این اطلاعات فقط در حد اطلاعات در دسترس هست و اطلاعات در سرور نیست
  پاسخ
تشکر شده توسط : vejmad
#4
با سلام، خوشحال ميشم نظرتون رو درباره روشي كه من استفاده ميكنم هم بدونم:
کد پی‌اچ‌پی:
<?php
require_once("config.php");
$agent=$_SERVER["HTTP_USER_AGENT"];
if((
ereg("Nav",$agent)
    || 
ereg("Gold",$agent)
    || 
ereg("X11",$agent)
    || 
ereg("Netscape",$agent))
    && !
ereg("MSIE",$agent)
    && !
ereg("Mozilla",$agent)
    && !
ereg("Konqueror",$agent)) $browser "Netscape";
elseif(
ereg("Mozilla",$agent)
    || 
eregi("FireFox",$agent)
    || 
eregi("Bon.Echo",$agent)
    && !
ereg("MSIE",$agent)) $browser "FireFox";
elseif(
ereg("MSIE",$agent)) $browser "MSIE";
elseif(
ereg("Lynx",$agent)) $browser "Lynx";
elseif(
ereg("Opera",$agent)) $browser "Opera";
elseif(
ereg("WebTV",$agent)) $browser "WebTV";
elseif(
ereg("Konqueror",$agent)) $browser "Konqueror";
elseif(
eregi("google",$agent)) $browser "Chrome";
elseif(
eregi("bot",$agent)
    || 
ereg("Slurp",$agent)
    || 
ereg("Scooter",$agent)
    || 
eregi("Spider",$agent)
    || 
eregi("Infoseek",$agent)) $browser "Bot";
else 
$browser "Other";
if(
ereg("Win",$agent)) $os "Windows";
elseif((
ereg("Mac",$agent))
    || (
ereg("PPC",$agent))) $os "Mac";
elseif(
ereg("Linux",$agent)) $os "Linux";
elseif(
ereg("FreeBSD",$agent)) $os "FreeBSD";
elseif(
ereg("SunOS",$agent)) $os "SunOS";
elseif(
ereg("IRIX",$agent)) $os "IRIX";
elseif(
ereg("BeOS",$agent)) $os "BeOS";
elseif(
ereg("OS/2",$agent)) $os "OS/2";
elseif(
ereg("AIX",$agent)) $os "AIX";
else 
$os "Other";
try
{
    
mysql_connect($host,$user,$pass);
    
mysql_select_db($db);
    
$query="UPDATE `counter` SET `count`=`count`+1"
    
$query.=" where (`type`='Total' AND `var`='Hits')"
    
$query.=" OR (`type`='Browser' AND `var`='$browser')"
    
$query.=" OR (`type`='OS' AND `var`='$os')";
    
$result=mysql_query($query);
}
catch(
Exception $error)
{
    die(
$error->getMessage()."\n");
}
?>
ساختار جدول هم به اين صورت هست:
کد:
CREATE TABLE `counter` (
  `ID` int(11) NOT NULL,
  `Type` longtext collate utf8_bin NOT NULL,
  `Var` longtext collate utf8_bin NOT NULL,
  `Count` int(11) NOT NULL,
  PRIMARY KEY  (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

INSERT INTO `counter` (`ID`, `Type`, `Var`, `Count`) VALUES
(1, 'Total', 'Hits', 0),
(2, 'Browser', 'Netscape', 0),
(3, 'Browser', 'FireFox', 0),
(4, 'Browser', 'MSIE', 0),
(5, 'Browser', 'Lynx', 0),
(6, 'Browser', 'Opera', 0),
(7, 'Browser', 'WebTV', 0),
(8, 'Browser', 'Konqueror', 0),
(9, 'Browser', 'Chrome', 0),
(10, 'Browser', 'Bot', 0),
(11, 'Browser', 'Other', 0),
(12, 'OS', 'Windows', 0),
(13, 'OS', 'Mac', 0),
(14, 'OS', 'Linux', 0),
(15, 'OS', 'FreeBSD', 0),
(16, 'OS', 'SunOS', 0),
(17, 'OS', 'IRIX', 0),
(18, 'OS', 'BeOS', 0),
(19, 'OS', 'OS/2', 0),
(20, 'OS', 'AIX', 0),
(21, 'OS', 'Other', 0);
فكر كنم اين روش هم ارزش كار كردن رو داشته باشه. حداقل من كه تابحال ازش بدي نديدم. هم آمار كلي رو از اين طريق بدست ميارم هم آمار تفكيكي برحسب نوع مرورگر و سيستم عامل رو. ميشه حتي اون رو توسعه داد و اطلاعات ديگري رو هم ذخيره كرد. موفق و مؤيد باشيد.
  پاسخ
تشکر شده توسط :


پرش به انجمن:


کاربران در حال بازدید این موضوع: 1 مهمان