Не сте регистриран! Регистрирайте се БЕЗПЛАТНО, за да използвате услугите на сайта!

   Рубрики
 
 
 
 

 Форуми
» SEO и оптимизация
» Всичко за PHP и Perl
» Всичко за C, C++ и .NET
» Всичко за Java и JSP
» Всичко за SQL и MySQL
» Всичко за XHTML и CSS
» Презентация на сайтове
 Кеширане в PHP с използване на файловата система и паметта чрез APC и Memcache
  1. Файлов кеш
  2. Кеширане в паметта чрез APC и Memcache
     
Автор  plamenSm (12.04.2008 12:04)  съобщение до автора
Погледнат  1945 пъти  добави към любими
Оценка  добави коментар
Гласове  --  изпрати на приятел
Коментари  (1)  абонирай се за PHP
    Страница 1 / 2

 



Кеширането е особено наложително в големи интернет приложения с голям обем данни и интезивни заявки към базите данни. Кеширането на данните извлечени от базата данни в повечето случаи води до намаляване натоварването на сървъра и ускоряване на работата на приложението.


Файлов кеш

Един от начините за кеширане е просто запазване на резултатите от SELECT заявките във файлове. Отварянето на файл и съответно десерелизирането на данните е значително по-бързо в сравнение с изпълнението на сложна SELECT заявка включваща голям брой таблици и релации.

Следващия пример представлява опростена реализация на файлов кеш:


CODE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?php
class FileCache
{
 
// Запазване на информация в кеша
 
function store($key,$data,$ttl) {

  
// Отваряне на файла
   
$h = fopen($this->getFileName($key),'w');
   
if (!$h) throw new Exception('Could not write to cache');
  
// Сериализиране на данните заедно с TTL (време на валидност)
   
$data = serialize(array(time()+$ttl,$data));
   
if (fwrite($h,$data) === false) {
     
throw new Exception('Could not write to cache');
   
}
   
fclose($h);
 
}

 
// Създаване име на файл по зададения ключ
 
private function getFileName($key) {
     
return '/tmp/s_cache' . md5($key);
 
}

 
// Извличане на данни от кеша.
 
// Функцията връща false ако няма данни отговарящи на ключа или
 
// данните са с изтекъл срок на валидност
 
function fetch($key) {

     
$filename = $this->getFileName($key);
     
if (!file_exists($filename) || !is_readable($filename)) return false;

     
$data = file_get_contents($filename);

     
$data = @unserialize($data);
     
if (!$data) {

       
// Грешка при десериалзацията
        
unlink($filename);
        
return false;
     
}

    
// Проверка дали данните са все още валидни
     
if (time() > $data[0]) {
        
unlink($filename);
        
return false;
     
}

    
// Всичко е коректно. Функцията връща данните.
     
return $data[1];
   
}
}

?>


Стратегии за формиране на ключа

Кешираните данни се идентифицират по зададен ключ. Ключът трябва да бъде уникален в рамките на цялата система. Добра практика е използването на принципа на namespace. Например ако имаме клас за оторизация на потребителите наречен „UserAuth”, и всички потребители се идентифицират с число - идентификатор (id), то подходящ ключ би бил: „UserAuth:users:1234”, където 1234 е идентификатора на потребителя.


Някои разсъждения за горния код

Възможно е да се пропусне записването на времето на валидност на данните. Вместо това то да се подава като параметър при извличане на данните. Това дава възможност да се провери дали файла е валиден още преди да се отвори, като се използва функцията filemtime().

Това на пръв поглед изглежда чудесна идея, но така нарушаваме общия интерфейс за кеширане. Тоест, ако в последствие решим да използваме кеш в паметта, ще трябва навсякъде да променяме начина на използване на класа.

Друга важна причина да запишем времето на валидност заедно с данните е тази, че това ни дава възможност да направим скрипт за „почистване” на кеша. Тоест изтриване на вече невалидните файлове.


Как се използва този клас

В едно web-приложение кеширането е подходящо най-вече при заявките към базата данни. Въпреки, че повечето SQL сървъри поддържат вътрешен кеш, който е напълно прозрачен, не винаги той се оказва оптималното решение. Причината е в това, че вътрешния кеш на сървъра не е съобразен с конкретната логика на вашето приложение.

Следва пример, който извлича съдържанието на цялата таблица с информация за потребителите като използва описания по-горе клас. Ако няма валидни кеширани данни, функцията създава такива с валидност 10 минути:

CODE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
 
// Създаване на обект от клас FileCache
 
$cache = new FileCache();

 
function getUsers() {

   
global $cache;

   
// Задаване на подходящ ключ
   
$key = 'getUsers:selectAll';

   
// Опитваме да вземем данните от кеща
   
if (!$data = $cache->fetch($key)) {
      
// Няма валидни кеширани данни. Ще ги вземем от базата данни

       // Подразбира се, че имаме връзка с базата данни
      
$result = mysql_query("SELECT * FROM users");
      
$data = array();

      
// Изчитаме редовете в масив $data
      
while($row = mysql_fetch_assoc($result)) { $data[] = $row; }

      
// Записваме данните в кеша със срок на валидност 10 минути
      
$cache->store($key,$data,600);
   
}
   
return $data;
}

$users = getUsers();
?>


В примера за краткост са пропуснати проверките за грешка и се подразбира, че имаме отворена връзка с базата данни. Също така не е описана и самата таблица users.


Какви проблеми можем да очакваме

Първия проблем е че този клас така както е написан, ще работи само в Linux. Проблема е в това, че използваме „/tmp”. Можем вместо това да вземем от 'session.save_path' от php.ini. Също твърдото задаване на разделителя за директории е неуместно. Във Windows се използват обратно наклонени черти. Можем да направим функцията getFileName() универсална така:

CODE
1
2
3
4
5
6
<?php
 
private function getFileName($key) {
     
return ini_get('session.save_path') . FILE_SEPARATOR . 's_cache' . md5($key);

 
}
?>


Следващия проблем е малко по-сложен. Нека си представим, че една инстанция на нашия клас започва да чете от кеша. Както знаете това става в отделна нишка. В следващия момент друга инстанция, активирана от друг потребител и съответно работеща в друга нишка също опитва да чете. Дотук няма нищо нередно. Но ако междувременно срока на валидност е изтекъл, втората нишка ще започне да записва - ето го и проблема. Ще се получи неочакван резултат. Сега проблема ни изглежда очевиден, но преди малко не всеки би се досетил за него. Това е сериозен и трудно откриваем бъг, тъй като на практика е малко вероятно да се случи.

Има ли решение - да. PHP може да заключва файлове за чрез функцията flock(). Заключването работи с отворен чрез fopen() файл и има 2 режима:

  • „shared lock” - останалите процеси могат да четат, но не и да записват в този файл
  • „exclusive lock” - останалите процеси изчакват файла да бъде освободен

В следващия пример е показано как с тези 2 възможности се решава конфликта с едновременния достъп до файла (коментирани са само промените):

CODE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<?php
 
// Функцията за запис на данни в кеша:
 
function store($key,$data,$ttl) {

   
// Отваряне на файла в режим за четене и запис
   
$h = fopen($this->getFileName($key),'a+');
   
if (!$h) throw new Exception('Could not write to cache');
   
   
// Exclusive lock! Ще действа докато файла бъде затворен
   
flock($h,LOCK_EX);

   
// Унищожаване съдържанието на файла
   
fseek($h,0);
   
ftruncate($h,0);

   
// Запис (serializing) на данните и TTL
   
$data = serialize(array(time()+$ttl,$data));
   
if (fwrite($h,$data) === false) {
     
throw new Exception('Could not write to cache');
   
}

   
// Затваряне на файла. Това премахва и заключването.
   
fclose($h);

 
}

 
// Функцията за четене от кеша:
 
function fetch($key) {

     
$filename = $this->getFileName($key);
     
if (!file_exists($filename)) return false;
     
$h = fopen($filename,'r');

     
if (!$h) return false;

     
// Shared lock! Ако файлът е заключен от друг записващ процес,
      // в тази точка текущия процес ще изчака той да бъде освободен

     
flock($h,LOCK_SH);

     
$data = file_get_contents($filename);
     
fclose($h);

     
$data = @unserialize($data);
     
if (!$data) {
        
unlink($filename);
        
return false;
     
}

     
if (time() > $data[0]) {
        
unlink($filename);
        
return false;
     
}
     
return $data[1];
  
}
?>

Всъщност добавихме само 3 реда код. Следващия проблем е синхронизацията на кеша с реалните данни от таблицата (или заявката в общия случай). Необходимо е при промяна на данните в базата, кеша да се синхронизира. Най-лесния начин е да изтрием файла, а следващият опит за четене от кеша ще го създаде с вече синхронизирани данни. Тоест, трябва ни метод за изтриване:

CODE
1
2
3
4
5
6
7
8
9
10
<?php
   
function delete( $key ) {
       
$filename = $this->getFileName($key);
       
if (file_exists($filename)) {
           
return unlink($filename);
       
} else {
           
return false;
       
}
    }
?>

Сега нека да си припомним обектно-ориентирания подход

Добре е да дефинираме интерфейс, който се имплементира от различните варианти на кеширане. Така ще гарантираме еднотипно използване на различните видове кеш, или иначе казано ще можем да променяме лесно вида на използвания кеш според обстоятелствата. Нека да дефинираме абстрактен клас, който съдържа само 3-те задължителни метода. Този клас в последствие ще се наследява от различните реализации на кеширане:

CODE
1
2
3
4
5
6
7
<?php
   
abstract class Sabre_Cache_Abstract {
       
abstract function fetch($key);
       
abstract function store($key, $data, $ttl);
       
abstract function delete($key);
   
}
?>


Сега да променим класа FileCache:

CODE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<?php
class Sabre_Cache_Filesystem extends Sabre_Cache_Abstract {

 
// Функцията за запис на данни в кеша:
 
function store($key,$data,$ttl) {

   
// Отваряне на файла в режим за четене и запис
   
$h = fopen($this->getFileName($key),'a+');
   
if (!$h) throw new Exception('Could not write to cache');
   
   
// Exclusive lock! Ще действа докато файла бъде затворен
   
flock($h,LOCK_EX);

   
// Унищожаване съдържанието на файла
   
fseek($h,0);
   
ftruncate($h,0);

   
// Запис (serializing) на данните и TTL
   
$data = serialize(array(time()+$ttl,$data));
   
if (fwrite($h,$data) === false) {
     
throw new Exception('Could not write to cache');
   
}

   
// Затваряне на файла. Това премахва и заключването.
   
fclose($h);
 
}

 
// Функцията за четене от кеша:
 
function fetch($key) {
     
$filename = $this->getFileName($key);
     
if (!file_exists($filename)) return false;
     
$h = fopen($filename,'r');

     
if (!$h) return false;

     
// Shared lock! Ако файлът е заключен от друг записващ процес,
      // в тази точка текущия процес ще изчака той да бъде освободен

     
flock($h,LOCK_SH);

     
$data = file_get_contents($filename);
     
fclose($h);

     
$data = @unserialize($data);
     
if (!$data) {
        
unlink($filename);
        
return false;
     
}

     
if (time() > $data[0]) {
        
unlink($filename);
        
return false;
     
}
     
return $data[1];
  
}

  
function delete( $key ) {
       
$filename = $this->getFileName($key);
       
if (file_exists($filename)) {
           
return unlink($filename);
       
} else {
           
return false;
       
}
    }

   
private function getFileName($key) {
       
return ini_get('session.save_path') . FILE_SEPARATOR . 's_cache' . md5($key);

   
}
}
?>

Ето, че имаме завършен клас за файлов кеш.



  Следваща страница >> 


Ключови думи: PHP file cache cache memcache apc cache файлов кеш памет кеш


Още уроци от тази рубрика


 
  • Подобни теми от myLinks
 

 1 посетител чете този урок (0 потребители и 1 гост)  
Активни потребители: ---
   
  

Еmail  
 

  :)

  Miro на 12.04.2008 23:19

 

 
  • Интересно от Софтуер
 



IT-PLACE.NET © 2004 - 2008