Bài viết này khám phá về rò rỉ bộ nhớ, một kẻ thù ẩn mình của hiệu suất ứng dụng web. Nó giải thích cách mà việc cấp phát bộ nhớ bị quên, tham chiếu vòng tròn và các lỗi lập trình khác có thể dẫn đến chậm chạp và sập ứng dụng. Các kịch bản phổ biến như quản lý kết nối không đúng cách và lưu trữ cache không hợp lý được khám phá, cùng với các giải pháp như chính sách loại bỏ và quản lý tài nguyên đúng cách.
Rò rỉ bộ nhớ trong ứng dụng web có thể dẫn đến hiệu suất chậm và sập ứng dụng
Quên giải phóng bộ nhớ
Lập trình viên đôi khi cấp phát bộ nhớ cho dữ liệu nhưng quên giải phóng nó khi không còn cần thiết nữa.
Hiểu về Garbage Collection trong Lập trình https://vulehuan.com/vi/blog/2024/7/hieu-ve-garbage-collection-trong-lap-trinh-6697bf7d0dbe71ff65efbbdd.html
Tham chiếu vòng tròn
Khi các đối tượng tham chiếu lẫn nhau trong một vòng lặp, chương trình có thể không xác định được khi nào chúng không còn cần thiết nữa.
Ví dụ: Các mối quan hệ cơ sở dữ liệu phức tạp được mô hình hóa với ORM tạo ra các tham chiếu vòng tròn giữa các đối tượng, khiến cho garbage collector khó có thể dọn dẹp chúng.
Quản lý kết nối không đúng cách
Ví dụ: Một máy chủ duy trì một nhóm kết nối cơ sở dữ liệu nhưng không đóng chúng đúng cách sau khi sử dụng. Theo thời gian, số lượng kết nối mở tăng lên, tiêu tốn tài nguyên máy chủ.
Lưu trữ cache mà không có chính sách loại bỏ
Ví dụ: Một máy chủ API lưu trữ cache dữ liệu được truy cập thường xuyên nhưng không thực hiện giới hạn kích thước hoặc chính sách hết hạn. Cache tăng lên vô hạn định, cuối cùng làm cạn kiệt bộ nhớ máy chủ.
Tác vụ nền kéo dài
Ví dụ: Một máy chủ tạo ra các luồng công việc cho các tác vụ chạy lâu dài nhưng không kết thúc chúng đúng cách. Những luồng này tiếp tục tiêu thụ bộ nhớ ngay cả sau khi tác vụ của chúng đã hoàn thành.
Đừng để các tác vụ dài làm chậm hệ thống của bạn: Làm chủ xử lý bất đồng bộ https://vulehuan.com/vi/blog/2024/7/dung-de-cac-tac-vu-lau-lam-cham-he-thong-cua-ban-lam-chu-xu-ly-bat-dong-bo-669780900dbe71ff65efbbc2.html
Xử lý tệp không đúng cách
Ví dụ: Một dịch vụ tải lên tệp tạm thời lưu trữ các tệp trong bộ nhớ trước khi xử lý chúng. Nếu xử lý lỗi kém, các tệp có thể không được xóa khỏi bộ nhớ khi việc tải lên thất bại.
Tính toán tiêu tốn nhiều bộ nhớ
Ví dụ: Một dịch vụ phân tích thực hiện các phép tính phức tạp trên các tập dữ liệu lớn nhưng không giải phóng bộ nhớ được sử dụng cho các kết quả trung gian, dẫn đến tăng trưởng bộ nhớ dần dần.
https://vulehuan.com/vi/blog/2024/7/giai-ma-de-quy-hieu-ro-su-ky-dieu-dang-sau-no-66951c56cdb8828661f4c41d.html
Promise và callback không được xử lý
Ví dụ: Các hoạt động bất đồng bộ sử dụng promise hoặc callback có thể không xử lý lỗi đúng cách, ngăn không cho garbage collector giải phóng bộ nhớ liên quan.
Tăng trưởng các bộ sưu tập tĩnh (Static collection)
Ví dụ: Một máy chủ duy trì một danh sách tĩnh các người dùng đã đăng nhập cho các tính năng thời gian thực (real-time). Nếu người dùng không bị xóa khỏi danh sách này khi họ đăng xuất, nó sẽ tăng lên vô hạn định.
Vấn đề quản lý phiên (Session)
Ví dụ: Một máy chủ lưu trữ dữ liệu phiên người dùng trong bộ nhớ nhưng không có cơ chế để dọn dẹp các phiên đã hết hạn. Ngay cả sau khi người dùng đăng xuất, dữ liệu phiên của họ vẫn còn trong bộ nhớ.
Ngăn chặn rò rỉ bộ nhớ trong ứng dụng web
Sử dụng kết nối đúng cách
- Thực hiện việc giải phóng kết nối đúng cách sau mỗi thao tác cơ sở dữ liệu
- Thiết lập giới hạn kích thước nhóm kết nối phù hợp
- Sử dụng middleware để tự động giải phóng kết nối sau khi hoàn thành yêu cầu
Triển khai chiến lược lưu trữ cache hiệu quả
- Sử dụng chính sách loại bỏ dựa trên thời gian hoặc LRU (Least Recently Used - Ít được sử dụng gần đây nhất)
- Thiết lập giới hạn cứng cho kích thước cache
- Cân nhắc sử dụng hệ thống cache phân tán cho các ứng dụng quy mô lớn
Quản lý phiên hiệu quả
- Thực hiện thời gian chờ phiên
- Sử dụng cơ sở dữ liệu hoặc Redis để lưu trữ phiên thay vì lưu trữ trong bộ nhớ
- Thường xuyên dọn dẹp các phiên đã hết hạn
Xử lý tải lên tệp cẩn thận
- Sử dụng streaming cho việc tải lên tệp thay vì đệm toàn bộ tệp trong bộ nhớ
- Thực hiện dọn dẹp đúng cách các tệp tạm thời
- Đặt giới hạn về kích thước tệp và đồng thời tải lên
Quản lý đúng cách các tác vụ nền
Tối ưu hóa các hoạt động tiêu tốn nhiều bộ nhớ
- Chia nhỏ các phép tính lớn thành các phần nhỏ hơn
- Sử dụng luồng để xử lý các tập dữ liệu lớn
- Thực hiện phân trang cho các yêu cầu dữ liệu lớn
Xử lý promise và callback đúng cách
- Luôn bao gồm xử lý lỗi trong các hoạt động bất đồng bộ
- Sử dụng Promise.all() một cách thận trọng đối với các mảng promise lớn
- Cân nhắc sử dụng async/await để có mã bất đồng bộ sạch hơn
Thực hiện ghi nhật ký và giám sát đúng cách
- Sử dụng các cấp độ ghi nhật ký để kiểm soát lượng dữ liệu được ghi
- Thực hiện xoay vòng nhật ký để ngăn các tệp nhật ký tăng vô hạn định
- Sử dụng các công cụ APM (Giám sát Hiệu suất Ứng dụng) để theo dõi việc sử dụng bộ nhớ
Sử dụng cấu trúc dữ liệu phù hợp
- Chọn cấu trúc dữ liệu hiệu quả cho trường hợp sử dụng của bạn (ví dụ: Set cho các giá trị duy nhất)
- Cẩn thận với các đối tượng lồng nhau có thể tạo ra các tham chiếu vòng tròn không mong muốn
Đóng tài nguyên đúng cách
- Đảm bảo tất cả các tài nguyên đã mở (tệp, socket, v.v.) được đóng đúng cách
- Sử dụng các khối 'finally' hoặc tương đương để đảm bảo mã dọn dẹp luôn chạy
Thực hiện tắt máy nhẹ nhàng
- Xử lý các tín hiệu SIGTERM và SIGINT để dọn dẹp tài nguyên trước khi tắt
- Đóng kết nối cơ sở dữ liệu, hoàn thành việc ghi vào tệp, v.v. trong quá trình tắt máy
Sử dụng phân cụm máy chủ
- Thực hiện phân cụm để khởi động lại các quy trình công việc định kỳ
- Điều này có thể giúp giảm thiểu tác động của rò rỉ bộ nhớ chậm
Tối ưu hóa việc sử dụng ORM
- Cẩn thận với việc tải dữ liệu liên quan một cách háo hức
- Thực hiện phân trang dữ liệu trong các truy vấn cơ sở dữ liệu
- Sử dụng streaming cho các truy vấn tập dữ liệu lớn khi có thể
Cập nhật thường xuyên các phụ thuộc
Tiến hành đánh giá và kiểm tra mã thường xuyên