Tăng cường sức mạnh cache page cho codeigniter (Improve CodeIgniter cache page - auto delete cache)

Ngày 22 tháng 5 năm 2013 Trương Chương Dương
Codeigniter hỗ trợ một chế độ cache page khá hiệu quả và nhanh chóng. Người lập trình hầu như không cần can thiệp gì nhiều vào code vẫn có thể khiến website hoạt động cực kỳ hiệu quả với chế độ cache. Thậm chí nếu trang của bạn đã được cache đầy đủ, bạn có thể tắt mysql mà site vẫn chạy bình thường, do CI lúc này hoàn toàn không đụng chạm gì tới db cả.

Tuy nhiên một nhược điểm của cache page là nó không tự động xoá khi dữ liệu có thay đổi mà phải chờ hết thời gian sống của cache.

Điều đó đôi khi gây ra vài phiền toái đồng thời cứ mỗi lần hết thời gian sống của file cache hệ thống lại phải tạo ra file mới gây "lãng phí".

Để khắc phục chuyện đó, ta chỉ cần cải tiến cơ chế cache của CI một chút là cuộc đời lại tương sáng ngay.

Ý tưởng cải tiến của mình như sau:
  • Thay file file cache có một cái gọi là thời gian sống, sau thời gian sống thì sẽ bị xoá, mình sẽ sử dụng cái gọi là "thời gian sinh ra", đánh dấu thời điểm file cache được tạo ra.
  • Một mốc thời gian khác đó là thời điểm dữ liệu được cập nhật. Hay còn gọi là ngày cập nhật cuối cùng của dữ liệu.
  • Mỗi khi dữ liệu có thay đổi, mình sẽ ghi nhận lại thời điểm này.
  • Tất cả các file cache đã được tạo trước thời điểm cập nhật dữ liệu thì được xem như đã sử dụng dữ liệu cũ và cần xoá bỏ. Tất cả những file cache tạo sau thời điểm cập nhật cuối cùng của dữ liệu thì xem như là nội dung mới và cứ giữ như thế.
  • Như vậy, chỉ cần so sánh thời điểm file cache được sinh ra so với thời điểm cập nhật cuối cùng của dữ liệu. Nếu file cache được tạo trước khi cập nhật thì xoá nó, được tạo sau khi cập nhật thì giữ lại và hiển thị.
  • Ví dụ:
    • Ngày 20 tháng 5 mình có tạo file cache A
    • Ngày 21 tháng 5 mình thay đổi dữ liệu
    • Ngày 22 tháng 5 mình tạo file cache B
    • Ngày 23 tháng 5, có yêu cầu hiển thị file A, mình kiểm tra và thấy nó được tạo này 20, trước thời điểm thay đổi dữ liệu => file cache quá cũ => xoá nó
    • Ngày 23 tháng 5 có yêu cầu hiển thị file B, mình kiểm tra và thấy nó được tạo ngày 22 sau thời điểm thay đổi dữ liệu => hiển thị file cache B

Sau đây là những tính năng mà mình cải tiến được:
1. Thời gian sống của file cache là vĩnh viễn hoặc cho tới khi dữ liệu có thay đổi và bạn xoá cache.
2. Khi bạn có yêu cầu xoá cache, các file cache vẫn nằm đó, và chỉ bị xoá khi có yêu cầu hiển thị trang đã cache tương ứng => hệ thống sẽ tạo lại từng file cache khi thật cần thiết, không cần xoá và tạo lại ngay khi có yêu cầu xoá cache.

Trước khi bắt tay tiến hành cải tiến cache file, bạn lưu ý điểm sau:
KHÔNG ĐƯỢC dùng autoload để load database. Do autoload được load trước nên nếu bạn để database ở chế độ autoload thì nó sẽ giảm phần nào hiệu quả do CI sẽ tiến hành kết nối db trước, sau đó mới load cache. Hơn nữa, nếu mysql bị treo hoặc không kết nối được, CI sẽ báo lỗi kết nối chứ không tự động load cache như mong đợi.

Bây giờ tới lúc thực hiện.

Bước 1: Custom class output:
Trong application/core, bạn tạo file MY_Output.php (tên phân biệt hoa thường) nội dung như sau:
PHP Code:
<?php

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 * Description of My_Output
 *
 * @author Truong Chuong Duong
 * @email: truong@chuongduong.net
 */

class MY_Output extends CI_Output
{
    function 
__construct()
    {
        
parent::__construct();
    }
    
    public function 
cache($n)
    {
        if (!empty(
$n))
            
$this->cache_expiration 1/60;
        else    
            
$this->cache_expiration 0;
    }

    public function 
clearCache()
    {
        global 
$CFG$URI;
        
$cache_path = ($CFG->item('cache_path') == '') ? APPPATH.'cache/' $CFG->item('cache_path');
        
$checkfile $cache_path 'CACHE_LAST_UPDATE_DATA.check';
        @
file_put_contents($checkfile$checkTime);
    }

    
/**
     * Update/serve a cached file
     *
     * @access    public
     * @param     object    config class
     * @param     object    uri class
     * @return    void
     */
    
function _display_cache(&$CFG, &$URI)
    {
        
$cache_path = ($CFG->item('cache_path') == '') ? APPPATH.'cache/' $CFG->item('cache_path');

        
// Build the file path.  The file name is an MD5 hash of the full URI
        
$uri =    $CFG->item('base_url').
                
$CFG->item('index_page').
                
$URI->uri_string;

        
$filepath $cache_path.md5($uri);

        if ( ! @
file_exists($filepath))
        {
            return 
FALSE;
        }

        if ( ! 
$fp = @fopen($filepathFOPEN_READ))
        {
            return 
FALSE;
        }

        
flock($fpLOCK_SH);

        
$cache '';
        if (
filesize($filepath) > 0)
        {
            
$cache fread($fpfilesize($filepath));
        }

        
flock($fpLOCK_UN);
        
fclose($fp);

        
// Strip out the embedded timestamp
        
if ( ! preg_match("/(\d+TS--->)/"$cache$match))
        {
            return 
FALSE;
        }

        
//Đọc thời điểm dữ liệu bị thay đổi
        
$checkfile $cache_path 'CACHE_LAST_UPDATE_DATA.check';
        
$checkTime = @file_get_contents($checkfile);

        
//Nếu không có => giả định thời điểm thay đổi là ngay bây giờ
        
if (!$checkTime)
        {
            
$checkTime time();
            
//Lưu lại thời điểm dữ liệu có thay đổi
            
@file_put_contents($checkfile$checkTime);
        }
        
$checkTime intval($checkTime);

        
//Lấy thời điểm file cache được tạo ra
        
$cacheCreatedTime intval(trim(str_replace('TS--->'''$match['1'])));
        
        
// Has the file expired? If so we'll delete it.
        
if ($cacheCreatedTime <= $checkTime)//Nếu thời điểm tạo file cache trước thời điểm dữ liệu có thay đổi => cache timeout
        
{
            if (
is_really_writable($cache_path))
            {
                @
unlink($filepath);
                
log_message('debug'"Cache file has expired. File deleted");
                return 
FALSE;
            }
        }

        
// Display the cache
        
$this->_display(str_replace($match['0'], ''$cache));
        
log_message('debug'"Cache file is current. Sending it to browser.");
        return 
TRUE;
    }
}
Ghi chú:
  • Dòng 21: Thay phương thức cache thông thường thành phươn thức mới. Bây giờ thay vì truyền vào thời gian sống của file cache, bạn truyền vào TRUE để mở chế độ cache, FALSE để tắt cache.
  • Dòng 29: Bất kỳ khi nào có sự thay đổi dữ liệu, bạn gọi phương thức này để báo hệ thống biết rằng các file cache cần phải được xoá.
  • Dòng 85: Lấy ra thời điểm kiểm tra cuối cùng của lệnh xoá cache. Tất cả các file cache tạo trước thời điểm này sẽ bị xem là lỗi thời và bị xoá.
  • Dòng 100: Kiểm tra xem nếu thời điểm tạo file cache nhỏ hơn thời điểm xoá cache thì file cache này đã cũ cần phải xoá nó.

Xong, vậy là đã cải tiến được cache của CI. Bây giờ tới khâu sử dụng.

Bất kỳ đâu trong controller, để bật chế độ cache bạn gọi lệnh:
PHP Code:
$this->output->cache(true); 
Để tắt chế độ cache đã bật, bạn gọi lệnh:
PHP Code:
$this->output->cache(false); 
Lưu ý là lệnh tắt chỉ có hiệu lực nếu file cache chưa được tạo. Nếu file cache đã được tạo trước đó thì chỉ tới khi có lệnh xoá cache thì lệnh tắt này mới có hiệu lực.

Tiếp theo là MỘT CHÚ Ý CỰC KỲ QUAN TRỌNG:
- Mỗi khi dữ liệu có bất kỳ sự thay đổi nào, hoặc khi bạn muốn xoá cache, bạn hãy gọi function sau trong controller:
PHP Code:
$this->output->clearCache(); 
vậy khi nào thì phải gọi function này???
- Trong các action insert/update/delete dữ liệu.
- Trong chức năng clear cache nếu có

Điều gì khi bạn quên nó:
- CI sẽ luôn hiển thị dữ liệu cũ, người dùng sẽ không bao giờ thấy sự thay đổi trên website của bạn.

Ví dụ, đây là controller của mình:
PHP Code:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class 
Welcome extends CI_Controller {

    
/**
     * Index Page for this controller.
     *
     * Maps to the following URL
     *         http://example.com/index.php/welcome
     *    - or -  
     *         http://example.com/index.php/welcome/index
     *    - or -
     * Since this controller is set as the default controller in 
     * config/routes.php, it's displayed at http://example.com/
     *
     * So any other public methods not prefixed with an underscore will
     * map to /index.php/welcome/<method_name>
     * @see http://codeigniter.com/user_guide/general/urls.html
     */
    
public function index()
    {
        
$this->output->cache(true);

        
$data = array(
            
'time' => date('d-m-Y h:m:s')
        );
        
$this->load->view('welcome_message'$data);
    }

    public function 
clearCache()
    {
        
$this->output->clearCache();
        echo 
'Cache da clear';
    }
}


/* End of file welcome.php */
/* Location: ./application/controllers/welcome.php */
Code view:
HTML Code:
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<title>Welcome to CodeIgniter</title>

	<style type="text/css">

	::selection{ background-color: #E13300; color: white; }
	::moz-selection{ background-color: #E13300; color: white; }
	::webkit-selection{ background-color: #E13300; color: white; }

	body {
		background-color: #fff;
		margin: 40px;
		font: 13px/20px normal Helvetica, Arial, sans-serif;
		color: #4F5155;
	}

	a {
		color: #003399;
		background-color: transparent;
		font-weight: normal;
	}

	h1 {
		color: #444;
		background-color: transparent;
		border-bottom: 1px solid #D0D0D0;
		font-size: 19px;
		font-weight: normal;
		margin: 0 0 14px 0;
		padding: 14px 15px 10px 15px;
	}

	code {
		font-family: Consolas, Monaco, Courier New, Courier, monospace;
		font-size: 12px;
		background-color: #f9f9f9;
		border: 1px solid #D0D0D0;
		color: #002166;
		display: block;
		margin: 14px 0 14px 0;
		padding: 12px 10px 12px 10px;
	}

	#body{
		margin: 0 15px 0 15px;
	}
	
	p.footer{
		text-align: right;
		font-size: 11px;
		border-top: 1px solid #D0D0D0;
		line-height: 32px;
		padding: 0 10px 0 10px;
		margin: 20px 0 0 0;
	}
	
	#container{
		margin: 10px;
		border: 1px solid #D0D0D0;
		-webkit-box-shadow: 0 0 8px #D0D0D0;
	}
	</style>
</head>
<body>

<div id="container">
	<h1>Welcome to CodeIgniter!</h1>

	<div id="body">
		<p>The page you are looking at is being generated dynamically by CodeIgniter.</p>

		<p>If you would like to edit this page you'll find it located at:</p>
		<code>application/views/welcome_message.php</code>

		<p>The corresponding controller for this page is found at:</p>
		<code>application/controllers/welcome.php</code>

		<p>If you are exploring CodeIgniter for the very first time, you should start by reading the <a href="user_guide/">User Guide</a>.</p>
        <p>Page is created at <b><?php echo $time ?></b></p>
	</div>

	<p class="footer">Page rendered in <strong></strong> seconds</p>
</div>

</body>
</html>
Hãy chạy function index và chú ý ở chỗ "Page is created at ".
Lần đầu tiên bạn chạy, nó hiển thị đúng giờ hiện hành của hệ thống, nhưng các lần sau thì nó không thay đổi. Bởi vì dữ liệu cache tạo lần đầu tiên được load và hiển thị lên, controller không hề chạy nên kết quả không thay đổi.

Bây giờ bạn thử vào link http://localhost/ci213/index.php/welcome/clearcache
Sau đó quay lại function index, bạn sẽ thấy thời gian ở chỗ "Page is created at " lại được cập nhật mới. Bởi vì trong function clearcache có lệnh $this->output->clearCache(); nên nó đã đánh dấu file cache được tạo ra lúc nãy đã cũ và cần làm mới.

Chỉ cần kết hợp đúng chỗ 2 function $this->output->clearCache(); và $this->output->cache(true); website của bạn sẽ mượt mà hơn bao giờ hết

Link dowbload full example: http://www.chuongduong.net/link/17031
Đang tải dữ liệu...