+4

Ba Sự Cố Chính Về Bộ Nhớ Đệm(cache) Mà Bạn Cần Biết

Bộ nhớ đệm là một phần quan trọng trong hệ thống, giúp tăng tốc độ truy cập dữ liệu bằng cách lưu trữ các dữ liệu đã được truy cập gần đây trong bộ nhớ tạm thời. Tuy nhiên, việc quản lý bộ nhớ đệm không đúng cách có thể gây ra một số vấn đề nghiêm trọng. Trong bài viết này, chúng ta sẽ tìm hiểu về ba sự cố chính liên quan đến bộ nhớ đệm và các giải pháp để khắc phục chúng.

1. Lở Bộ Nhớ Đệm (Cache Avalanche)

Nguyên Nhân

Lở bộ nhớ đệm xảy ra khi bộ đệm ban đầu không hợp lệ hoặc dữ liệu không được tải vào bộ đệm, và bộ đệm mới chưa được tạo ra. Khi điều này xảy ra, tất cả các yêu cầu dự kiến để truy cập vào bộ đệm sẽ thay vào đó truy vấn trực tiếp vào cơ sở dữ liệu, tạo áp lực lớn lên CPU và bộ nhớ của cơ sở dữ liệu. Trong những trường hợp nghiêm trọng, điều này có thể dẫn đến thời gian ngừng hoạt động của cơ sở dữ liệu và thậm chí là sự sập hệ thống.

Cách Hiểu Đơn Giản: Khi một số lượng lớn các key trong bộ đệm hết hạn đồng thời, một số lượng lớn yêu cầu sẽ được gửi đến cơ sở dữ liệu, gây ra sự cố cho cơ sở dữ liệu.

Giải Pháp

  1. Sử Dụng Khóa hoặc Hàng Đợi

    Đối với việc quản lý khi bộ đệm hết hạn, có thể sử dụng khóa hoặc hàng đợi để kiểm soát số lượng truy cập cùng một lúc vào cơ sở dữ liệu để đọc và ghi dữ liệu vào bộ đệm.

    // Ví dụ mã nguồn Java cho việc sử dụng khóa
    public Users getByUserId(Long id) {
        // Tạo khóa cache dựa trên id của người dùng
        String key = this.getClass().getName() + "-" + Thread.currentThread().getStackTrace()[1].getMethodName() + "-id" + id;
    
        // Kiểm tra dữ liệu người dùng có sẵn trong cache hay không
        String userJson = redisService.getString(key);
        if (!StringUtils.isEmpty(userJson)) {
            // Nếu dữ liệu tồn tại trong cache, chuyển đổi từ JSON sang đối tượng Users và trả về
            Users users = JSONObject.parseObject(userJson, Users.class);
            return users;
        }
    
        Users user = null;
        try {
            // Mở khóa để tránh xung đột khi truy vấn vào cơ sở dữ liệu
            lock.lock();
    
            // Thực hiện truy vấn vào cơ sở dữ liệu để lấy thông tin người dùng
            user = userMapper.getUser(id);
    
            // Lưu thông tin người dùng vào cache
            redisService.setString(key, JSONObject.toJSONString(user));
        } catch (Exception e) {
            // Xử lý ngoại lệ nếu có
        } finally {
            // Giải phóng khóa sau khi hoàn thành truy vấn
            lock.unlock();
        }
    
        // Trả về thông tin người dùng
        return user;
    }
    

Lưu Ý: Việc sử dụng khóa hàng đợi chỉ nhằm giảm áp lực lên cơ sở dữ liệu và không cải thiện thông lượng của hệ thống. Trong trường hợp có nhiều yêu cầu cùng thời điểm, quá trình tái tạo bộ đệm có thể khiến khóa bị chặn, gây ra thời gian chờ cho người dùng.

  1. Cung Cấp Thời Gian Hết Hạn Ngẫu Nhiên cho Các Giá Trị

    Bằng cách phân tích hành vi của người dùng và thiết lập thời gian hết hạn khác nhau cho từng khóa, đồng thời cân nhắc làm cho các điểm thời gian vô hiệu hóa bộ đệm càng đa dạng càng tốt. Mục tiêu là giảm tốc độ lặp lại của các thời gian hết hạn trong bộ đệm, từ đó giảm nguy cơ gây ra thất bại toàn bộ hệ thống.

  2. Xây Dựng Kiến Trúc Bộ Đệm Đa Cấp

    Sử dụng bộ đệm nginx làm tầng trên cùng, sau đó là bộ đệm Redis ở tầng giữa và có thể thêm các bộ đệm khác như Ehcache ở tầng dưới cùng. Cách tiếp cận này giúp cải thiện hiệu suất và linh hoạt trong việc mở rộng hệ thống.

2. Thâm Nhập Bộ Đệm (Cache Penetration)

Nguyên Nhân

Thâm nhập bộ đệm xảy ra khi dữ liệu mà người dùng truy vấn không tồn tại trong cơ sở dữ liệu và cũng không có trong bộ đệm. Kết quả là mỗi khi truy vấn, người dùng sẽ không tìm thấy dữ liệu trong bộ đệm và phải truy vấn cơ sở dữ liệu mỗi lần, sau đó nhận lại kết quả trống. Như vậy, yêu cầu sẽ bỏ qua bộ đệm và trực tiếp kiểm tra cơ sở dữ liệu, gây ra vấn đề về tốc độ truy cập bộ đệm.

Cách Hiểu Đơn Giản: Khi một key không tồn tại trong bộ đệm, nhưng Redis không lưu giá trị null vào cache, điều này khiến mọi yêu cầu truy cập cơ sở dữ liệu.

Giải Pháp

  1. Đặt Giá Trị Mặc Định và Lưu vào Bộ Đệm

    public String getByUsers2(Long id) {
        // Khởi tạo key để lưu vào Redis
        String key = this.getClass().getName() + Thread.currentThread().getStackTrace()[1].getMethodName() + "-id:" + id;
        
        // Kiểm tra xem tên người dùng có tồn tại trong Redis không
        String userName = redisService.getString(key);
    
        // Nếu không tìm thấy trong Redis
        if (StringUtils.isEmpty(userName)) {
            // In ra thông báo bắt đầu gửi yêu cầu tới cơ sở dữ liệu
            System.out.println("##### Bắt đầu gửi yêu cầu tới cơ sở dữ liệu ******");
            
            // Lấy thông tin người dùng từ cơ sở dữ liệu
            Users user = userMapper.getUser(id);
            
            // Khởi tạo giá trị để lưu vào Redis
            String value = null;
            
            // Kiểm tra xem người dùng có tồn tại không
            if (user != null) {
                // Nếu tồn tại, lấy tên của người dùng
                value = user.getName();
            }
            
            // Lưu giá trị vào Redis
            redisService.setString(key, value);
            
            // Trả về tên người dùng
            return value;
        } else {
            // Nếu tìm thấy trong Redis, trả về giá trị đó
            return userName;
        }
    }
    

Lưu Ý: Khi lưu trữ giá trị thực cho một địa chỉ IP cụ thể, trước hết, bạn cần xóa các bản ghi trống tương ứng từ bộ đệm để đảm bảo dữ liệu là chính xác.

3. Sự Cố Bộ Đệm (Cache Breakdown)

Nguyên Nhân

Sự cố bộ đệm xảy ra khi một số khóa có thời gian hết hạn được thiết lập, và chúng nhận được một lượng lớn yêu cầu truy cập vào một số thời điểm cụ thể. Điều này gây ra vấn đề của việc "hỏng" cache, khác biệt giữa nó và hiện tượng cache avalanche là ở chỗ "hỏng" cache xảy ra với một key cụ thể, trong khi cache avalanche thường liên quan đến nhiều key khác nhau.

Cách Hiểu Đơn Giản: Khi một key "nóng" bất ngờ hết hạn, một lượng lớn các yêu cầu sẽ đổ vào và tất cả đều yêu cầu truy vấn cơ sở dữ liệu, có thể gây ra sự sụp đổ của cơ sở dữ liệu.

Giải Pháp

  1. Sử Dụng Khóa

    Trên một máy chủ, ta có thể áp dụng các biện pháp như synchronized, lock và các biện pháp tương tự. Trong môi trường phân tán, ta có thể sử dụng khóa phân tán để đảm bảo chỉ một quá trình có thể truy cập vào một tài nguyên cùng một thời điểm.

  2. Điều Chỉnh Theo Thời Gian Thực

    Thay vì thiết lập thời gian hết hạn cho bộ đệm, ta có thể lưu trữ thời gian hết hạn trong giá trị tương ứng với mỗi khóa và cập nhật bộ đệm một cách đồng bộ khi phát hiện thời gian lưu trữ vượt quá thời gian hết hạn.

  3. Đặt Trước Dữ Liệu Phổ Biến

    Trước khi truy cập lại mức cao nhất, hãy lưu trữ trước một số dữ liệu phổ biến vào Redis và tăng thời gian hết hạn của những dữ liệu phổ biến này.

4. Kết Luận

Bộ nhớ đệm là một khía cạnh quan trọng trong việc phát triển ứng dụng, nhưng thường được ít lập trình viên đề cập đến. Qua việc tìm hiểu về ba vấn đề chính và cách khắc phục chúng, chúng ta đã thấy rõ giá trị của việc áp dụng kiến thức này trong thực tế. Việc hiểu sâu hơn về bộ nhớ đệm không chỉ giúp cải thiện hiệu suất ứng dụng mà còn tạo ra cơ hội mới trong sự nghiệp lập trình.

Bạn có thể đọc thêm những bài viết khác của mình tại đây


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí