Về rò rỉ bộ nhớ

webman là một framework lưu trữ trong bộ nhớ, vì vậy chúng ta cần chú ý một chút đến tình trạng rò rỉ bộ nhớ. Tuy nhiên, các nhà phát triển không cần quá lo lắng vì rò rỉ bộ nhớ xảy ra trong những điều kiện cực đoan và rất dễ tránh. Trải nghiệm phát triển webman cơ bản tương tự như phát triển framework truyền thống, không cần phải thực hiện các thao tác thừa thãi về quản lý bộ nhớ.

Mẹo
Quá trình monitor tự đi kèm với webman sẽ giám sát tình trạng sử dụng bộ nhớ của tất cả các quá trình. Nếu bộ nhớ được sử dụng của một quá trình sắp đạt giá trị được thiết lập trong memory_limit của php.ini, quá trình tương ứng sẽ tự động khởi động lại an toàn để giải phóng bộ nhớ, không ảnh hưởng đến hoạt động kinh doanh trong suốt thời gian này.

Định nghĩa về rò rỉ bộ nhớ

Khi số lượng yêu cầu tăng dần, bộ nhớ sử dụng bởi webman cũng tăng mãi mãi (chú ý là tăng mãi mãi), đạt tới hàng trăm M hoặc thậm chí nhiều hơn, đó là rò rỉ bộ nhớ. Nếu bộ nhớ có sự gia tăng nhưng không tiếp tục tăng nữa thì không được coi là rò rỉ bộ nhớ.

Thông thường, việc một tiến trình sử dụng vài chục M bộ nhớ là điều bình thường. Khi tiến trình xử lý các yêu cầu lớn hoặc duy trì nhiều kết nối, việc một tiến trình chiếm dụng bộ nhớ lên tới hàng trăm M cũng là chuyện phổ biến. Bộ nhớ này sau khi sử dụng, php có thể không trả hết cho hệ điều hành. Thay vào đó, nó sẽ giữ lại để tái sử dụng, vì vậy có thể xảy ra tình trạng bộ nhớ chiếm dụng lớn sau khi xử lý một yêu cầu lớn mà không được giải phóng, đây là hiện tượng bình thường. (Gọi phương thức gc_mem_caches() có thể giải phóng một phần bộ nhớ dư thừa)

Rò rỉ bộ nhớ xảy ra như thế nào

Rò rỉ bộ nhớ xảy ra khi thỏa mãn hai điều kiện sau:

  1. Có một mảng có tuổi thọ dài (chú ý là mảng có tuổi thọ dài, mảng thông thường không có vấn đề gì)
  2. Và mảng có tuổi thọ dài này sẽ mở rộng vô hạn (do nghiệp vụ liên tục thêm dữ liệu vào mà không bao giờ dọn dẹp dữ liệu)

Nếu cả hai điều kiện 1 và 2 đều thỏa mãn (chú ý là đều thỏa mãn), thì sẽ xảy ra rò rỉ bộ nhớ. Ngược lại, nếu không thỏa mãn các điều kiện trên hoặc chỉ thỏa mãn một trong hai điều kiện thì không phải là rò rỉ bộ nhớ.

Mảng có tuổi thọ dài

Trong webman, các mảng có tuổi thọ dài bao gồm:

  1. Mảng sử dụng từ khóa static
  2. Thuộc tính mảng đơn thể
  3. Mảng được định nghĩa bằng từ khóa global

Lưu ý
webman cho phép sử dụng dữ liệu có tuổi thọ dài, nhưng cần đảm bảo rằng dữ liệu trong đó có giới hạn, số lượng phần tử sẽ không mở rộng vô hạn.

Dưới đây là các ví dụ cụ thể

Mảng static mở rộng vô hạn

class Foo
{
    public static $data = [];
    public function index(Request $request)
    {
        self::$data[] = time();
        return response('hello');
    }
}

Mảng $data được định nghĩa bằng từ khóa static là mảng có tuổi thọ dài, và trong ví dụ này, mảng $data sẽ không ngừng mở rộng theo số lượng yêu cầu, dẫn đến rò rỉ bộ nhớ.

Thuộc tính mảng đơn thể mở rộng vô hạn

class Cache
{
    protected static $instance;
    public $data = [];

    public function instance()
    {
        if (!self::$instance) {
            self::$instance = new self;
        }
        return self::$instance;
    }

    public function set($key, $value)
    {
        $this->data[$key] = $value;
    }
}

Mã gọi

class Foo
{
    public function index(Request $request)
    {
        Cache::instance()->set(time(), time());
        return response('hello');
    }
}

Cache::instance() trả về một đơn thể Cache, nó là một instance lớp có tuổi thọ dài. Mặc dù thuộc tính $data của nó không được sử dụng từ khóa static, nhưng do lớp này có tuổi thọ dài, nên $data cũng là mảng có tuổi thọ dài. Khi liên tục thêm dữ liệu với key khác vào mảng $data, bộ nhớ chiếm dụng của chương trình cũng sẽ ngày càng lớn, gây ra rò rỉ bộ nhớ.

Lưu ý
Nếu key được thêm vào bằng Cache::instance()->set(key, value) có số lượng có hạn thì sẽ không xảy ra rò rỉ bộ nhớ, vì mảng $data không mở rộng vô hạn.

Mảng global mở rộng vô hạn

class Index
{
    public function index(Request $request)
    {
        global $data;
        $data[] = time();
        return response($foo->sayHello());
    }
}

Mảng được định nghĩa bằng từ khóa global sẽ không bị thu hồi sau khi hàm hoặc phương thức lớp kết thúc, vì vậy nó là mảng có tuổi thọ dài. Mã trên đây sẽ gây rò rỉ bộ nhớ với số lượng yêu cầu tăng dần. Tương tự, một mảng được định nghĩa bằng từ khóa static trong hàm hoặc phương thức cũng là mảng có tuổi thọ dài, nếu mảng này mở rộng vô hạn cũng sẽ gây rò rỉ bộ nhớ, ví dụ:

class Index
{
    public function index(Request $request)
    {
        static $data = [];
        $data[] = time();
        return response($foo->sayHello());
    }
}

Đề xuất

Chúng tôi khuyên các nhà phát triển không cần phải đặc biệt quan tâm đến rò rỉ bộ nhớ vì nó rất hiếm xảy ra. Nếu không may rò rỉ xảy ra, chúng ta có thể tìm ra đoạn mã nào gây ra rò rỉ thông qua kiểm tra tải, từ đó xác định vấn đề. Ngay cả khi các nhà phát triển không tìm thấy điểm rò rỉ, dịch vụ monitor đi kèm với webman sẽ khởi động lại an toàn các quá trình gặp rò rỉ bộ nhớ vào thời điểm thích hợp, giải phóng bộ nhớ.

Nếu bạn thực sự muốn hạn chế rò rỉ bộ nhớ, bạn có thể tham khảo các gợi ý sau.

  1. Cố gắng không sử dụng mảng từ khóa global, static, nếu sử dụng hãy đảm bảo chúng không mở rộng vô hạn.
  2. Đối với các lớp chưa quen, cố gắng không sử dụng đơn thể, hãy khởi tạo bằng từ khóa new. Nếu cần đơn thể, hãy kiểm tra xem nó có thuộc tính mảng mở rộng vô hạn hay không.