Sep 21, 2017
190 Views

Phân tích hệ thống tiki.vn 2014

Written by

Tổng quan hệ thống

Mọi lời nói không thể thay thế bằng hình ảnh rõ ràng được :)

  • Tiki sử dụng Magento version 1.x
  • Source code Magento sẽ được deploy lên 4 server web app, server Media và request sẽ được load balance qua 4 server này
  • Database sử dụng là Percona MySQL được cấu hình Master-Slave (Data được đồng bộ từ Master server sang Slave server)
    Giữa các Slave server sẽ có 1 server khác làm nhiệm vụ Load balancing
  • 1 server Nginx riêng phục vụ cho các media (css,js,image,video) và với 1 domain khác https://tikicdn.com
  • 1 server để chạy các service khác như Redis, Memcache, Gearman

Bài viết này mình tập trung chia sẻ về các vấn đề mà Tiki đã gặp phải khi scale hệ thống Magento 1.x và cách giải quyết các vấn đề vào thời điểm đó. Tuy nhiên các kiến thức trong này đều có thể áp dụng cho các project PHP khác nhé :)

Profile hệ thống

Trước khi bắt đầu tối ưu system thì mình phải biết những chỗ nào chậm và chậm do đâu. Mình dựa vào 2 loại log để biết được những request và câu SQL nào chậm:

  • MySQL slow log: Cấu hình MySQL để log lại những câu query nào chậm quá 5s
  • Newrelic: Log lại tất cả các request (URL) và thời gian thực thi của từng request.

Sau khi đã biết request nào chậm rồi thì mình cần biết chi tiết hơn là function hay đoạn code nào chậm để debug trên local cho dễ.

Mình dùng 1 extension của PHP gọi là xhprof, nhưng có 1 project giúp mình xem report data được sinh ra từ extension này dễ hơn đó là https://github.com/perftools/xhgui

Sau khi setup Xhgui thì data sẽ được lưu trên MongDB và sau đó mình sẽ xem dễ dàng trên giao diện web.

Các vần đề gặp phải và cách giải quyết

Tạo hình thumbnail

Tiki.vn sẽ có 1 số page có rất nhiều hình thumbnail của các product. Mình lấy ví dụ là Homepage

Bạn sẽ thấy 4 hình thumbnail (Tivi) to, và bên phải là 3 hình thumbnail nhỏ hơn.
Trong Magento thì 1 product sẽ có 1 hình gốc, sẽ có 1 đoạn code để tạo ra các hình thumbnail với kích thước khác nhau để hiển thị ở các page hay các block khác nhau.
Link hình thumbnail sẽ có dạng https://tikicdn.com/media/catalog/product/cache/1/image/200x200/9df78eab33525d08d6e5fb8d27136e95/3/5/351119_1.jpg

  • https://tikicdn.com: Là base URL cho các file dạng media
  • media/catalog/product/cache/1/image/200x200/9df78eab33525d08d6e5fb8d27136e95/3/5: Đường dẫn thư mục chứa file thumbnail
  • 9df78eab33525d08d6e5fb8d27136e95: Hash của các param khi resize như ratio, transparency, quality
  • 200x200: Size của hình thumbnail
  • 351119_1.jpg: Tên file gốc

Trong các file template sẽ có 1 số đoạn như sau:

<img src="<?php echo (string) Mage::helper('catalog/image')->init($product, 'image')->resize(200, 200); ?>"

Đoạn code PHP trong temlate trên sẽ làm những bước sau:

  • Xem product này đã có file thumbnail nào có size 200×200 chưa?
  • Nếu chưa có thì sẽ tạo ra 1 file thumbnail size 200×200 cho product đó tại đường dẫn thư mục media/catalog/product/cache/1/image/200x200/9df78eab33525d08d6e5fb8d27136e95/3/5 trên disk
  • Sau đó sẽ trả về 1 URL thumbnail tương ứng https://tikicdn.com/media/catalog/product/cache/1/image/200x200/9df78eab33525d08d6e5fb8d27136e95/3/5/351119_1.jpg

Như vậy ta thấy vấn đề ở đây là: 1 page cần phải show 100 thumbnail của 100 product, thì sẽ phải query 100 lần xuống disk xem có 100 file thumbnail đó hay chưa, bất kể file thumbnail đó đã tồn tại hay chưa. Các bạn thử tưởng tượng nhiều người reload Homepage như vậy thì sẽ phải query xuống disk bao nhiêu lần???

Vậy thì mình sẽ optimize đoạn này bằng cách viết lại function resize, bỏ bước số 1 và 2 đi, return về 1 URL thumbnail 200×200 tương ứng cho product đó.

Mà đã bỏ bước số 1 và 2 rồi thì mấy file thumbnail của product cũ có trên disk rồi thì không nói, còn mấy product mới thêm vào Database thì làm sao tạo ra file thumbnail được???

Vì Nginx đóng vai trò làm server phục vụ Image, nên mình sẽ cấu hình thêm để làm sao cho khi Browser request 1 URL thumbnail thì check xem URL này có phải là 1 file tồn tại trên disk hay chưa, nếu chưa thì sẽ “redirect” qua 1 đoạn script php tạo ra file thumbnail ứng với URL đó.

location ~ \.(gif|jpg|png) {
    try_files $uri @img_proxy;
}

location @img_proxy {
    rewrite ^(.*)$ /thumbnail.php?uri=$1;
}

Vd URL thumbnail https://tikicdn.com/media/catalog/product/cache/1/image/200x200/9df78eab33525d08d6e5fb8d27136e95/3/5/351119_1.jpg chưa tồn tại trên disk thì sẽ được redirect thành URL https://tikicdn.com/thumbnail.php?uri=/media/catalog/product/cache/1/image/200x200/9df78eab33525d08d6e5fb8d27136e95/3/5/351119_1.jpg

Session

Mặc định session sẽ được lưu vào File, nên sau này Tiki chuyển qua dùng Memcached vì sẽ đọc ghi trên RAM nên sẽ cho tốc độ truy xuất tốt hơn

Nhưng cũng giống như File, có 1 issue gọi là Session locking trong PHP, mục đích để tránh nhiều request update đồng thời vào 1 session.

Vd trên Homepage mình có 5 Ajax request để lấy các product hiển thị trên 5 block khác nhau. Mỗi ajax request phải đi qua các bước sau:

  • Start session bằng function session_start
  • Đọc data từ Database
  • Xử lý data
  • Return về browser

Nếu request ajax số 1 chậm ở bước 2 và 3 thì 4 request ajax còn lại sẽ bị block ngay bước 1 (start session). Khi nào request 1 xử lý xong, return về browser (lúc này session sẽ được close) thì 4 request kia mới tiếp tục xử lý.

Giải pháp là mình sẽ sử dụng function session_write_close để close session sớm nhất có thể, để tránh tình trạng các request khác đứng chờ lẫn nhau.

Tham khảo thêm tại đây http://konrness.com/php5/how-to-prevent-blocking-php-requests

Master-Slave MySQL

Tiki cấu hình MySQL dạng Master-Slave, 1 Master server sẽ phục vụ cho Read/Write, và 3 Slave server phục vụ Read. 3 Slave server này sẽ được load balancing thông qua Haproxy server.
Và vì không sử dụng mô hình Cluster (có thể Read/Write đồng thời trên tất cả các server) nên tuyệt đối không được write vào các Slave server (Vì Slave server sẽ không tự đồng bộ data đến Master server).

Như vậy cần phải cấu hình Magento để support Read và Write connection trên 2 IP của 2 server khác nhau.

Đối với mỗi request thì Magento sẽ tạo ra 2 Database connection: 1 cho các thao tác write vào Master server và 1 cho các thao tác Read vào Haproxy server.

Nhưng lại nảy sinh vấn đề là có 1 khoảng thời gian delay nhất định để data được đồng bộ từ Master server sang các Slave server. Vì vậy đối với những đoạn code vừa Write vào Database xong, sau đó lại Read data lên liền thì có khi sẽ không tìm thấy data đó.

Đối với những trường hợp như vậy, mình phải dùng Write connection cho các thao tác Read luôn.

Vd Magento có cách viết sau để lấy data của 1 product theo ID:

$product = Mage::getModel('catalog/product')->load(123); // Product ID = 123

Đoạn code trên mặc định dùng Read connection để lấy data từ Database. Như vậy, mình sẽ viết thêm 1 function setAdapter để có thể viết như vậy:

$writeAdapter = Mage::getSingleton('core/resource')->getConnection('core_write');
$product = Mage::getModel('catalog/product')->setAdapter($writeAdapter)->load(123);

Còn viết thêm như thế nào thì cái này dễ nên mình bỏ qua nhé :)

Tối ưu các đoạn code được gọi nhiều lần

Tại Tiki có trường hợp là cùng 1 function lấy data từ Database lại được sử dụng nhiều lần ở những chỗ khác trên cùng 1 trang.

Như vậy khi load 1 trang thì function đó sẽ được gọi nhiều hơn 1 lần có nghĩa là query vào Database nhiều lần.

Vd mình có function lấy 10 product mới nhất:

function get10LatestProduct()
{
    $collection = Mage::getModel('catalog/product')
        ->getCollection()
        ->addAttributeToSort('created_at', 'desc');

    return $collection->getSelect()->limit(10);
}

Cách giải quyết nhanh nhất là mình sẽ Cache kết quả trả về của function get10LatestProduct, để những lần sau gọi lại thì sẽ return về kết quả luôn, chứ không query database nữa.

function get10LatestProduct()
{
    static $collection = null;

    if ($collection === null) {
        $collection = Mage::getModel('catalog/product')
            ->getCollection()
            ->addAttributeToSort('created_at', 'desc')
            ->getSelect()->limit(10);
    }

    return $collection;
}

 

 

 

 

Article Tags:
· · · · · · · · · · · · · · · · · · · · · · · · ·
Article Categories:
Code/Web
    http://linholiver.com

    https://linholiver.com/diary/about/