Springboot?多級緩存設計與實現(xiàn)方案
對于高并發(fā)系統(tǒng)來說,有三個重要的機制來保障其高效運行,它們分別是:緩存、限流和熔斷。而緩存是排在最前面也是高并發(fā)系統(tǒng)之所以高效運行的關鍵手段,那么問題來了:緩存只使用 Redis 就夠了嗎?
冗余設計理念
當然不是,不要把所有雞蛋放到一個籃子里,成熟的系統(tǒng)在關鍵功能實現(xiàn)時一定會考慮冗余設計,注意這里的冗余設計不是貶義詞。
冗余設計是在系統(tǒng)或設備完成任務起關鍵作用的地方,增加一套以上完成相同功能的功能通道(or 系統(tǒng))、工作元件或部件,以保證當該部分出現(xiàn)故障時,系統(tǒng)或設備仍能正常工作,以減少系統(tǒng)或者設備的故障概率,提高系統(tǒng)可靠性。
例如,飛機的設計,飛機正常運行只需要兩個發(fā)動機,但在每臺飛機的設計中可能至少會設計四個發(fā)動機,這就有冗余設計的典型使用場景,這樣設計的目的是為了保證極端情況下,如果有一個或兩個發(fā)動機出現(xiàn)故障,不會因為某個發(fā)動機的故障而引起重大的安全事故。
多級緩存概述
緩存功能的設計也是一樣,我們在高并發(fā)系統(tǒng)中通常會使用多級緩存來保證其高效運行,其中的多級緩存就包含以下這些:
- 瀏覽器緩存:它的實現(xiàn)主要依靠 HTTP 協(xié)議中的緩存機制,當瀏覽器第一次請求一個資源時,服務器會將該資源的相關緩存規(guī)則(如 Cache-Control、Expires 等)一同返回給客戶端,瀏覽器會根據(jù)這些規(guī)則來判斷是否需要緩存該資源以及該資源的有效期。
- Nginx 緩存:在 Nginx 中配置中開啟緩存功能。
- 分布式緩存:所有系統(tǒng)調用的中間件都是分布式緩存,如 Redis、MemCached 等。
- 本地緩存:JVM 層面,單系統(tǒng)運行期間在內存中產生的緩存,例如 Caffeine、Google Guava 等。
以下是它們的具體使用。
開啟瀏覽器緩存
在 Java Web應用中,實現(xiàn)瀏覽器緩存可以使用 HttpServletResponse 對象來設置與緩存相關的響應頭,以開啟瀏覽器的緩存功能,它的具體實現(xiàn)分為以下幾步。
① 配置 Cache-Control
Cache-Control 是 HTTP/1.1 中用于控制緩存策略的主要方式。它可以設置多個指令,如 max-age(定義資源的最大存活時間,單位秒)、no-cache(要求重新驗證)、public(指示可以被任何緩存區(qū)緩存)、private(只能被單個用戶私有緩存存儲)等,設置如下:
response.setHeader("Cache-Control", "max-age=3600, public"); // 緩存一小時② 配置 Expires
設置一個絕對的過期時間,超過這個時間點后瀏覽器將不再使用緩存的內容而向服務器請求新的資源,設置如下:
response.setDateHeader("Expires", System.currentTimeMillis() + 3600 * 1000); // 緩存一小時③ 配置 ETag
ETag(實體標簽)一種驗證機制,它為每個版本的資源生成一個唯一標識符。當客戶端發(fā)起請求時,會攜帶上先前接收到的 ETag,服務器根據(jù) ETag 判斷資源是否已更新,若未更新則返回 304 Not Modified 狀態(tài)碼,通知瀏覽器繼續(xù)使用本地緩存,設置如下:
String etag = generateETagForContent(); // 根據(jù)內容生成ETagresponse.setHeader("ETag", etag);④ 配置 Last-Modified
指定資源最后修改的時間戳,瀏覽器下次請求時會帶上 If-Modified-Since 頭,服務器對比時間戳決定是否返回新內容或發(fā)送 304 狀態(tài)碼,設置如下:
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 協(xié)議特性控制瀏覽器緩存的基本方法。
開啟 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;
其中:
- /path/to/cache:這是緩存文件的存放路徑。
- levels=1:2:定義緩存目錄的層級結構。
- keys_zone=my_cache:10m:定義一個名為 my_cache 的共享內存區(qū)域,大小為 10MB。
- max_size=10g:設置緩存的最大大小為 10GB。
- inactive=60m:如果在 60 分鐘內沒有被訪問,緩存將被清理。
- use_temp_path=off:避免在文件系統(tǒng)中進行不必要的數(shù)據(jù)拷貝。
② 啟用緩存
在 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 可以將請求轉發(fā)到后端服務器。例如,以下配置:
location / {
proxy_pass http://backend_server;
...
}⑤ 重新加載配置
保存并關閉 Nginx 配置文件后,使用 nginx -s reload 命令重新加載配置,使更改生效。
Redis+Caffeine實現(xiàn)應用層二級緩存
在SpringBoot中實現(xiàn)多級緩存需要解決兩個關鍵問題:緩存數(shù)據(jù)的讀取順序和數(shù)據(jù)的一致性。以下是實現(xiàn)多級緩存的步驟:
導入依賴:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<!-- 其他依賴 -->
</dependencies>編寫redis相關配置:
spring:
redis:
host: 127.0.0.1
port: 6379
password:
jedis:
pool:
max-active: 8
max-wait: -1
max-idle: 500
min-idle: 0
lettuce:
shutdown-timeout: 0本地緩存配置類
/**
* 本地緩存Caffeine配置類
*/
@Configuration
public class LocalCacheConfiguration {
@Bean("localCacheManager")
public Cache<String, Object> localCacheManager() {
return Caffeine.newBuilder()
//寫入或者更新5s后,緩存過期并失效, 實際項目中肯定不會那么短時間就過期,根據(jù)具體情況設置即可
.expireAfterWrite(5, TimeUnit.SECONDS)
// 初始的緩存空間大小
.initialCapacity(50)
// 緩存的最大條數(shù),通過 Window TinyLfu算法控制整個緩存大小
.maximumSize(500)
//打開數(shù)據(jù)收集功能
.recordStats()
.build();
}
}Redis客戶端配置類:
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
//關聯(lián)
template.setConnectionFactory(factory);
//設置key的序列化方式
// template.setKeySerializer();
//設置value的序列化方式
// template.setValueSerializer();
return template;
}
}編寫測試用的服務類接口:
public interface UserService {
void add(User user);
User getById(String id);
User update(User user);
void deleteById(String id);
}編寫測試用的服務類:
這里本地緩存也可以用注解式緩存來實現(xiàn),這里就不細寫啦~
import com.alibaba.fastjson.JSON;
import com.github.benmanes.caffeine.cache.Cache;
import com.wsh.springboot_caffeine.entity.User;
import com.wsh.springboot_caffeine.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@Service
public class UserServiceImpl implements UserService {
/**
* 模擬數(shù)據(jù)庫存儲數(shù)據(jù)
*/
private static HashMap<String, User> userMap = new HashMap<>();
private final RedisTemplate<String, Object> redisTemplate;
private final Cache<String, Object> caffeineCache;
@Autowired
public UserServiceImpl(RedisTemplate<String, Object> redisTemplate,
@Qualifier("localCacheManager") Cache<String, Object> caffeineCache) {
this.redisTemplate = redisTemplate;
this.caffeineCache = caffeineCache;
}
static {
userMap.put("1", new User("1", "zhangsan"));
userMap.put("2", new User("2", "lisi"));
userMap.put("3", new User("3", "wangwu"));
userMap.put("4", new User("4", "zhaoliu"));
}
@Override
public void add(User user) {
// 1.保存Caffeine緩存
caffeineCache.put(user.getId(), user);
// 2.保存redis緩存
redisTemplate.opsForValue().set(user.getId(), JSON.toJSONString(user), 20, TimeUnit.SECONDS);
// 3.保存數(shù)據(jù)庫(模擬)
userMap.put(user.getId(), user);
}
@Override
public User getById(String id) {
// 1.先從Caffeine緩存中讀取
Object o = caffeineCache.getIfPresent(id);
if (Objects.nonNull(o)) {
System.out.println("從Caffeine中查詢到數(shù)據(jù)...");
return (User) o;
}
// 2.如果緩存中不存在,則從Redis緩存中查找
String jsonString = (String) redisTemplate.opsForValue().get(id);
User user = JSON.parseObject(jsonString, User.class);
if (Objects.nonNull(user)) {
System.out.println("從Redis中查詢到數(shù)據(jù)...");
// 保存Caffeine緩存
caffeineCache.put(user.getId(), user);
return user;
}
// 3.如果Redis緩存中不存在,則從數(shù)據(jù)庫中查詢
user = userMap.get(id);
if (Objects.nonNull(user)) {
// 保存Caffeine緩存
caffeineCache.put(user.getId(), user);
// 保存Redis緩存,20s后過期
redisTemplate.opsForValue().set(user.getId(), JSON.toJSONString(user), 20, TimeUnit.SECONDS);
}
System.out.println("從數(shù)據(jù)庫中查詢到數(shù)據(jù)...");
return user;
}
@Override
public User update(User user) {
User oldUser = userMap.get(user.getId());
oldUser.setName(user.getName());
// 1.更新數(shù)據(jù)庫
userMap.put(oldUser.getId(), oldUser);
// 2.更新Caffeine緩存
caffeineCache.put(oldUser.getId(), oldUser);
// 3.更新Redis數(shù)據(jù)庫
redisTemplate.opsForValue().set(oldUser.getId(), JSON.toJSONString(oldUser), 20, TimeUnit.SECONDS);
return oldUser;
}
@Override
public void deleteById(String id) {
// 1.刪除數(shù)據(jù)庫
userMap.remove(id);
// 2.刪除Caffeine緩存
caffeineCache.invalidate(id);
// 3.刪除Redis緩存
redisTemplate.delete(id);
}
}總結
多級緩存是提升高并發(fā)系統(tǒng)性能的關鍵策略之一。它不僅能夠減少系統(tǒng)的響應時間,提高用戶體驗,還能有效降低后端系統(tǒng)的負載,防止系統(tǒng)過載。在實際應用中,開發(fā)者應根據(jù)系統(tǒng)的具體需求和資源情況,靈活設計和調整多級緩存策略,以達到最佳的性能表現(xiàn)。大部分情況下我們使用redis作為緩存是可以滿足需求的,加入本地緩存后雖然帶來了部分性能提升,但是存在數(shù)據(jù)一致性的問題,一定程度上添加了維護難度。
到此這篇關于Springboot 多級緩存設計與實現(xiàn)的文章就介紹到這了,更多相關Springboot 多級緩存內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
線程池FutureTask異步執(zhí)行多任務實現(xiàn)詳解
這篇文章主要為大家介紹了線程池FutureTask異步執(zhí)行多任務實現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-11-11
SpringBoot實現(xiàn)緩存組件配置動態(tài)切換的步驟詳解
現(xiàn)在有多個springboot項目,但是不同的項目中使用的緩存組件是不一樣的,有的項目使用redis,有的項目使用ctgcache,現(xiàn)在需要用同一套代碼通過配置開關,在不同的項目中切換這兩種緩存,本文介紹了SpringBoot實現(xiàn)緩存組件配置動態(tài)切換的步驟,需要的朋友可以參考下2024-07-07
java基于quasar實現(xiàn)協(xié)程池的方法示例
本文主要介紹了java基于quasar實現(xiàn)協(xié)程池的方法示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧<BR>2022-06-06

