如何實現多級緩存?

對於高併發系統來說,有三個重要的機制來保障其高效運行,它們分別是:緩存、限流和熔斷。而緩存是排在最前面也是高併發系統之所以高效運行的關鍵手段,那麼問題來了:緩存只使用 Redis 就夠了嗎?

  1. 冗餘設計理念

當然不是,不要把所有雞蛋放到一個籃子裏,成熟的系統在關鍵功能實現時一定會考慮冗餘設計,注意這裏的冗餘設計不是貶義詞。

冗餘設計是在系統或設備完成任務起關鍵作用的地方,增加一套以上完成相同功能的功能通道(or 系統)、工作元件或部件,以保證當該部分出現故障時,系統或設備仍能正常工作,以減少系統或者設備的故障概率,提高系統可靠性。

例如,飛機的設計,飛機正常運行只需要兩個發動機,但在每臺飛機的設計中可能至少會設計四個發動機,這就有冗餘設計的典型使用場景,這樣設計的目的是爲了保證極端情況下,如果有一個或兩個發動機出現故障,不會因爲某個發動機的故障而引起重大的安全事故。

  1. 多級緩存概述

緩存功能的設計也是一樣,我們在高併發系統中通常會使用多級緩存來保證其高效運行,其中的多級緩存就包含以下這些:

  1. 瀏覽器緩存:它的實現主要依靠 HTTP 協議中的緩存機制,當瀏覽器第一次請求一個資源時,服務器會將該資源的相關緩存規則(如 Cache-Control、Expires 等)一同返回給客戶端,瀏覽器會根據這些規則來判斷是否需要緩存該資源以及該資源的有效期。

  2. Nginx 緩存:在 Nginx 中配置中開啓緩存功能。

  3. 分佈式緩存:所有系統調用的中間件都是分佈式緩存,如 Redis、MemCached 等。

  4. 本地緩存:JVM 層面,單系統運行期間在內存中產生的緩存,例如 Caffeine、Google Guava 等。

以下是它們的具體使用。

2.1 開啓瀏覽器緩存

在 Java Web 應用中,實現瀏覽器緩存可以使用 HttpServletResponse 對象來設置與緩存相關的響應頭,以開啓瀏覽器的緩存功能,它的具體實現分爲以下幾步。

① 配置 Cache-Control

Cache-Control 是 HTTP/1.1 中用於控制緩存策略的主要方式。它可以設置多個指令,如 max-age(定義資源的最大存活時間,單位秒)、no-cache(要求重新驗證)、public(指示可以被任何緩存區緩存)、private(只能被單個用戶私有緩存存儲)等,設置如下:

response.setHeader("Cache-Control""max-age=3600, public"); // 緩存一小時

② 配置 Expires

設置一個絕對的過期時間,超過這個時間點後瀏覽器將不再使用緩存的內容而向服務器請求新的資源,設置如下:

response.setDateHeader("Expires", System.currentTimeMillis() + 3600 * 1000); // 緩存一小時

③ 配置 ETag

ETag(實體標籤)一種驗證機制,它爲每個版本的資源生成一個唯一標識符。當客戶端發起請求時,會攜帶上先前接收到的 ETag,服務器根據 ETag 判斷資源是否已更新,若未更新則返回 304 Not Modified 狀態碼,通知瀏覽器繼續使用本地緩存,設置如下:

String etag = generateETagForContent(); // 根據內容生成ETag
response.setHeader("ETag", etag);

④ 配置 Last-Modified

指定資源最後修改的時間戳,瀏覽器下次請求時會帶上 If-Modified-Since 頭,服務器對比時間戳決定是否返回新內容或發送 304 狀態碼,設置如下:

long lastModifiedDate = getLastModifiedDate();
response.setDateHeader("Last-Modified", lastModifiedDate);

整體配置

在 Spring Web 框架中,可以通過 HttpServletResponse 對象來設置這些頭信息。例如,在過濾器中設置響應頭以啓用緩存:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
       throws IOException, ServletException {
   HttpServletResponse httpResponse = (HttpServletResponse) response;
   // 設置緩存策略
   httpResponse.setHeader("Cache-Control""max-age=3600");

   // 其他響應頭設置...
   chain.doFilter(request, response);
}

以上就是在 Java Web 應用程序中利用 HTTP 協議特性控制瀏覽器緩存的基本方法。

2.2 開啓 Nginx 緩存

Nginx 中開啓緩存的配置總共有以下 5 步。

① 定義緩存配置

在 Nginx 配置中定義一個緩存路徑和配置,通過 proxy_cache_path 指令完成,例如,以下配置:

proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m use_temp_path=off;

其中:

② 啓用緩存

在 server 或 location 塊中,使用 proxy_cache 指令來啓用緩存,並指定要使用的 keys zone,例如,以下配置:

server {  
    ...  
    location / {  
        proxy_cache my_cache;  
        ...  
    }  
}

③ 設置緩存有效期

使用 proxy_cache_valid 指令來設置哪些響應碼的緩存時間,例如,以下配置:

location / {  
    proxy_cache my_cache;  
    proxy_cache_valid 200 304 12h;  
    proxy_cache_valid any 1m;  
    ...  
}

④ 配置反向代理

確保你已經配置了反向代理,以便 Nginx 可以將請求轉發到後端服務器。例如,以下配置:

location / {  
    proxy_pass http://backend_server;  
    ...  
}

⑤ 重新加載配置

保存並關閉 Nginx 配置文件後,使用 nginx -s reload 命令重新加載配置,使更改生效。

2.3 使用分佈式緩存

在 Spring Boot 項目中使用註解的方式來操作分佈式緩存 Redis 的實現步驟如下。

① 添加依賴

在你的 pom.xml 文件中添加 Spring Boot 的 Redis 依賴,如下所示:

<dependencies>  
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-data-redis</artifactId>  
    </dependency>  
</dependencies>

② 配置 Redis 連接信息

在 application.properties 或 application.yml 文件中配置 Redis 的相關信息,如下所示。

# application.properties  
spring.redis.host=localhost  
spring.redis.port=6379

③ 啓動緩存

在 Spring Boot 主類或者配置類上添加 @EnableCaching 註解來啓用緩存。

import org.springframework.cache.annotation.EnableCaching;  
import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
  
@SpringBootApplication  
@EnableCaching  
public class Application {  
  
    public static void main(String[] args) {  
        SpringApplication.run(Application.class, args);  
    }  
  
}

④ 使用緩存

在服務類或方法上使用 @Cacheable,@CacheEvict,@CachePut 等註解來定義緩存行爲。

例如,使用 @Cacheable 註解來緩存方法的返回值:

import org.springframework.cache.annotation.Cacheable;  
import org.springframework.stereotype.Service;  
  
@Service  
public class UserService {  
    @Cacheable("users")  
    public User findUserById(Long id) {  
        // 模擬從數據庫中查詢用戶  
        return new User(id, "Alice");  
    }  
}

也可以使用 @CacheEvict 註解來刪除緩存:

import org.springframework.cache.annotation.CacheEvict;  
import org.springframework.stereotype.Service;  
  
@Service  
public class UserService {  
    @CacheEvict(value = "users"key = "#id")  
    public void deleteUser(Long id) {  
        // 模擬從數據庫中刪除用戶  
    }  
}

在這個例子中,deleteUser 方法會刪除 "users" 緩存中 key 爲 id 的緩存項。

可以使用 @CachePut 註解來更新緩存:

import org.springframework.cache.annotation.CachePut;  
import org.springframework.stereotype.Service;  
  
@Service  
public class UserService {  
  
    @CachePut(value = "users"key = "#user.id")  
    public User updateUser(User user) {  
        // 模擬更新數據庫中的用戶信息  
        return user;  
    }  
  
}

在這個例子中,updateUser 方法會更新 "users" 緩存中 key 爲 user.id 的緩存項,緩存的值是方法的返回值。

2.4 使用本地緩存

以 Caffeine 本地緩存的使用爲例,它在 Spring Boot 項目中的使用如下。

① 添加依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
</dependency>

② 配置 Caffeine 緩存

在 application.properties 或 application.yml 文件中配置 Caffeine 緩存的相關參數。例如:

# application.properties
spring.cache.type=caffeine
spring.cache.caffeine.spec=initialCapacity=100,maximumSize=1000,expireAfterWrite=10s

這裏 spring.cache.caffeine.spec 是一個 Caffeine 規範字符串,用於設置初始容量、最大容量和寫入後過期時間等緩存策略,其中:

③ 自定義 Caffeine 配置類(可選步驟)

如果需要更復雜的配置,可以創建一個 Caffeine CacheManager 的配置類:

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CaffeineCacheConfig extends CachingConfigurerSupport {

    @Bean
    public CacheManager cacheManager() {
        Caffeine<Object, Object> caffeine = Caffeine.newBuilder()
                .initialCapacity(100)
                .maximumSize(1000)
                .expireAfterWrite(10, TimeUnit.SECONDS) // 10 秒後過期
                .recordStats(); // 記錄緩存統計信息

        return new CaffeineCacheManager("default", caffeine::build);
    }

    @Override
    public CacheResolver cacheResolver() {
        // 自定義緩存解析器(如果需要)
        // ...
        return super.cacheResolver();
    }
}

④ 開啓緩存

若要利用 Spring Cache 抽象層,以便通過註解的方式更方便地管理緩存,需要在啓動類上添加 @EnableCaching 註解,如下所示:

import org.springframework.cache.annotation.EnableCaching;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableCaching
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

⑤ 使用註解進行緩存操作

在業務邏輯類中使用 @Cacheable、@CacheEvict 等註解實現數據的緩存讀取和更新,和上面分佈式緩存的使用相同,具體示例如下:

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Cacheable(value = "users"key = "#id") // 假設我們有一個名爲"users"的緩存區域
    public User getUserById(Long id) {
        // 這裏是真實的數據庫查詢或其他耗時操作
        return userRepository.findById(id).orElse(null);
    }

    @CacheEvict(value = "users"key = "#user.id")
    public void updateUser(User user) {
        userRepository.save(user);
    }
}
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/O1nQCP2OFp8eqdry0UVNHQ