Web緩存:通過Java實現更好的經濟戰略

Java 編程語言 經濟 GitHub 最前沿編程諮詢 最前沿編程諮詢 2017-08-25

我讀取 緩存的最好的比喻 來自Peter Chester,他在WordPress會議期間使用。他問觀眾,“3,485,250分為23,235”?最初的沉默之後,有些人把他們的計算器拿出來,最後有人大聲喊道:“150.”切斯特先生又問了一個同樣的問題,能夠迴應 這是緩存!

總而言之,這是一個非常簡單的緩存情況,因為答案總是一樣的。但隱喻是太棒了!實質上,緩存是關於經濟。我們為等待響應的客戶節省時間。我們節省資源,重新計算我們已經知道的答案。我們節省帶寬。

我們該怎麼做?通過保持一些響應“更接近”請求者並再次服務,而不必返回原始服務器並再次計算答案。

這不是一項直接的任務,它的實施需要嚴肅的策略。我們需要評估我們的應用程序的“最終目標”,我們的數據的性質,我們的應用程序的用戶,現有的應用程序設計和緩存的實際目的。如果我們的策略沒有很好的思考,那就是各種各樣的危險。例子?

我們可能會暴露可能損害用戶的敏感數據。我們可能會提供陳舊/無效的數據,這取決於應用程序可能是災難性的。如果我們的緩存命中率不佳,那麼我們甚至可以傷害我們的表現,這僅僅是緩存中可以提供的請求數量除以所提供的總請求數量。

你可能會問,為什麼不把所有東西保存在緩存?再次,經濟!緩存通常在內存中完成,這比數據庫存儲更昂貴。同樣複製我們整個數據庫可能是昂貴和非常複雜的。

那麼,我們如何決定在數據庫中保留什麼以及分配多少空間?在什麼順序上,當緩存中的項目變滿時,我們會將項目從序列中刪除 那麼它更像是一門藝術。我們做一個假設,監測是否和如何工作和相應的調整。這整個運動值得我們的麻煩嗎?

我們可以樂觀地認為,一些數據將比其他依靠諸如參考地點 和 帕累託原理這樣的原則更受歡迎。是的,緩存是值得的。

我們來看一個Web應用程序的高級視圖,以便介紹一些與緩存相關的術語,並在一些不同的場景下給出一些理由。考慮一個典型的多層應用程序。在基本級別上,我們有一個源服務器,它提供對某種存儲庫數據的訪問,並執行計算。另一方面是客戶端,通常是Web瀏覽器,用戶可以從中查看和訪問原始服務器的優秀應用程序和產品。在它們之間,許多其他組件可以作為調用者將數據從源服務器傳遞到瀏覽器,以供用戶滿意。

現在,這些層合作進行緩存的主要方式是通過彼此發信號通知其規則或偏好。這通過HTTP頭完成。我不會詳細介紹與緩存有關的所有標題,但是我們可以根據它們的工作方式對它們進行大致的分類。

時間

某些標頭用於指示緩存資源被認為是新鮮的時間段。當資源可以用於提供請求時,資源是新鮮的,意味著它與原始服務器中的資源處於狀態。不新鮮的內容稱為陳舊。這些標頭在緩存控制中提供,最常見的是:

  • max-age:這是緩存資源必須重新生效之前的時間段

  • s-maxage:與max-age相同,但用於中間緩存。結合起來,它們可以為我們的緩存策略提供靈活性

  • max-stale:客戶願意接受超出到期時間的響應

  • 最新鮮:客戶要求在指定時間段內新鮮的客戶使用(由他們)

正如你所理解的,這個基於緩存的緩存,並不能保證我們從緩存中收到的內容不會過時。在資源被緩存之後立即發生,它將在源服務器中更新。但這種緩存有其用途。例如,有這樣的策略幾秒鐘可以保護我們免受持續按f5的用戶。

那麼基於對象狀態的緩存呢?有標題可以幫助:

  • ETag:源服務器與資源一起提供的值。在此資源的後續請求中,客戶端提供ETag,並且服務器檢查資源是否改變(基於計算的ETag),並相應地進行響應

  • 最後修改:以類似的方式,服務器向資源提供最後的修改時間,客戶端在下一個請求中使用它,並根據資源的最後修改日期作出響應,不會對其進行修改或者為新的最後修改時間

這些頭確保確保緩存的內容與源服務器處於狀態。但是,它們並沒有真正使我們免於計算,起始服務器仍然需要評估資源的etag或檢查其最後修改的時間。但是,當資源未被修改時,它可以節省帶寬,因為它不再發送(用HTTP 304響應)。

所以我們還想要一些更好的東西,稍後我將在本教程中給出一個簡單的例子,介紹如何保持緩存的新鮮。但現在讓我們關閉一些其他標題,這些標題可以指定要緩存的內容。例如,如果內容可以被緩存,如果可以被轉換,並且響應必須被重新驗證,則由誰來緩存。

  • 無存儲:請求不以任何方式緩存資源,並且與敏感數據一起使用很重要

  • no-cache:請求每次都要重新驗證任何緩存請求。不意味著必須重複所有內部計算,只需確保緩存的資源處於原始服務器的狀態

  • private:指示資源不能被中間緩存緩存

  • public:允許資源由中間緩存緩存

  • content-length:指定要緩存的資源的大小

  • 無變換:請求資源不會以任何方式進行轉換(例如,為了性能原因而進行壓縮)

  • 必須重新驗證:指示必須遵守最高級別和管理級(而不是例如在網絡中斷時提供過時的內容)

  • proxy-revalidate:與must-revalidate相同,但在中間緩存中使用

好的,現在我們瞭解Web緩存的工作原理,讓我們看看如何在代碼中實現這些。我創建了一個可以在我的github上找到的教程的小項目 。我們從我們的pom開始,這是相當簡單的,因為我們將使用Spring Boot,它為我們提供了許多依賴。另外我們將使用EhCcache和其他依賴項,您可以在下面的pom中看到。

< project xmlns = “http://maven.apache.org/POM/4.0.0” xmlns:xsi = “http://www.w3.org/2001/XMLSchema-instance” xsi:schemaLocation = “http:/ /maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd“ >

接下來,我們將為我們的REST應用程序設置配置。我們擴展了AbstractAnnotationConfigDispatcherServletInitializer'類,它註冊了一個ContextLoaderListener和一個DispatcherServlet。這簡化了我們的工作,我們可以定義配置類(例如servlet)和servlet映射。

包 com.tasosmartidis.caching_demo.config;import org.springframework.web.servlet.support。AbstractAnnotationConfigDispatcherServletInitializer ;

WebAppInitializer類只有3種方法。getServletMappings()表示DispatcherServlet映射到的路徑。我們把它映射到默認的servlet,它將處理進入我們應用程序的所有請求。getRootConfigClasses()處理由ContextLoaderListener創建的應用程序上下文的配置。最後,getServletConfigClasses()處理DispacherServlet的配置。

在我們的RootConfig類中,我們將創建通用目的bean,在這種情況下,EhCache的bean將為我們提供緩存支持。

包 com.tasosmartidis.caching_demo.config;import org.springframework.cache.CacheManager;import org.springframework.cache。註釋 .EnableCaching;import org.springframework.cache.ehcache.EhCacheCacheManager;import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;import org.springframework.context。註釋。import org.springframework.context。註釋 .ComponentScan;import org.springframework.context。註釋。import org.springframework.core.io.ClassPathResource;@Configuration @EnableCaching @ComponentScan(“com.tasosmartidis.caching_demo”)public class RootConfig { private static final String EHCACHE_CONFIGURATION = “ehcache.xml” ; @Bean

在WebConfig類中,我們將附加攔截器,我們將使用它們來記錄來電的開始和結束。

package com .tasosmartidis .caching_demo .config ;import com .tasosmartidis .caching_demo .utils .LoggingInterceptor ;import org .springframework .context .annotation .ComponentScan ;進口 組織.springframework .context .annotation .Configuration ;進口 組織.springframework 名.web .servlet 的.config .annotation .DefaultServletHandlerConfigurer ;進口 組織.springframework 名.web .servlet 的.config .annotation .EnableWebMvc ;進口 組織.springframework 名.web .servlet 的.config .annotation .InterceptorRegistry ;進口 組織.springframework 名.web .servlet 的.config .annotation .WebMvcConfigurerAdapter ;

LoggingInterceptor類實現了HandleInterceptor接口,其實現如下所示:

包 com.tasosmartidis.caching_demo.utils;導入 org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;公共 類 LoggingInterceptor 實現 HandlerInterceptor { private static final Logger LOGGER = LoggerFactory.getLogger(LoggingInterceptor.class); @覆蓋

現在我們在使用類和日誌記錄,這裡是另一個用於在輸入,啟動和完成方法時記錄的日誌。

包 com.tasosmartidis.caching_demo.utils;導入 org.slf4j.Logger;public class LoggingUtils { public static void logMethodEntered (Logger logger) {

我們幾乎準備好進入我們的演示項目。我們將要看到的緩存類型是基於時間的緩存,基於狀態的緩存和緩存的無效。如前所述,我們使用EhCache進行緩存,以下是包含緩存配置的文件:

< ehcache xmlns:xsi = “http://www.w3.org/2001/XMLSchema-instance” xsi:noNamespaceSchemaLocation = “ehcache.xsd” updateCheck = “true” monitoring = “ autodetect ” dynamicConfig = “true” >

現在我們將創建一個端點來展示基於時間的緩存的工作原理。該服務將簡單地返回“問候”,但端點的響應將被緩存指定的時間段。我們將使用日誌記錄來了解服務何時執行計算,而不是從緩存中提供服務。

包 com.tasosmartidis.caching_demo.web;import org.slf4j。*;import org.springframework.cache。註釋。import org.springframework.http.ResponseEntity;import org.springframework.web.bind。註釋 *import static com.tasosmartidis.caching_demo.utils.LoggingUtils.logMethodEntered;@RestController public class TimeBasedCachedService { 私人靜態最終記錄器記錄儀= LoggerFactory.getLogger(TimeBasedCachedService。類); private static final String HELLO_INPUT_NAME_ENDPOINT = “/ hello / {name}” ; private static final String HELLO_WORLD_ENDPOINT = “/ helloworld” ; private static final String HELLO_SHORT_CACHE = “time-based-short-lived” ; private static final String HELLO_LONG_CACHE = “time-based-long-lived” ; private static final String NAME_CACHE_KEY = “#name” ; @RequestMapping(value = HELLO_WORLD_ENDPOINT,method = RequestMethod.GET)

現在我們將看看它的效果如何。我們將使用放心的呼叫端點,並確保我們的響應是按預期的,但記錄的呼叫將給我們的主要輸入。下面的課程會使事情更清楚:

包 com.tasosmartidis.caching_demo.web;import org.junit.Test;import org.junit.runner.RunWith;導入 org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import static com.tasosmartidis.caching_demo.testutils.HttpMethodsUtils.doGetEnsure200AndReturnResponseAsString;import static com.tasosmartidis.caching_demo.utils.LoggingUtils。*;@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) public class TimeBasedCachedServiceTest { private static final Logger LOGGER = LoggerFactory.getLogger(TimeBasedCachedServiceTest.class); private static final String HELLO_WORLD_RESOURCE = “helloworld” ; private static final String HELLO_INPUT_RESOURCE = “hello /” ; @Test

“doGetEnsure200AndReturnResponseAsString”方法在一個實用程序類中,如下所示:

包com.tasosmartidis.caching_demo.testutils;

運行我們的TimeBasedCachedServiceTest類將會提供類似的東西:

網頁。TimeBasedCachedServiceTest:測試的方法的“Hello World”開始......

這是基於時間的,但是正如我們所討論的,這樣的緩存不能保證我們的緩存響應處於起始服務器的狀態。所以我們來看看基於狀態的服務。我們現在將會變得更加複雜,並且有數據保存和偽存儲庫來保存它們。然後,服務將允許我們更新和檢索數據。我們有一個StubData POJO如下:

包 com.tasosmartidis.caching_demo.data;進口 lombok。*;import java.util.Date;@Getter @Setter @EqualsAndHashCode @Builder @ToString public class StubData { private String id; 私有字符串名稱 私人日期lastModified; public void updateData () {

還有一個假倉庫:

包com.tasosmartidis.caching_demo.data;import org.springframework.stereotype.Component;import java.util.Date;import java.util.HashMap;import java.util.Map;

沒有什麼奇怪的,只是一個地圖內存與一些條目,我們可以通過我們的端點檢索和更新。端點位於StateBasedCacheService類中:

包 com.tasosmartidis.caching_demo.web;導入 com.tasosmartidis.caching_demo。數據 .StubData;導入 com.tasosmartidis.caching_demo。數據 .StubDataDao;import com.tasosmartidis.caching_demo.utils.LoggingUtils;導入 org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory。註釋。自動連線import org.springframework.http.CacheControl;import org.springframework.http.ResponseEntity;import org.springframework.web.bind。註釋 .PathVariable;import org.springframework.web.bind。註釋 .RequestMapping;import org.springframework.web.bind。註釋 .RequestMethod;import org.springframework.web.bind。註釋 .RestController;import javax.servlet.http.HttpServletRequest;import java.text.ParseException;import java.util.Date;import static com.tasosmartidis.caching_demo.utils.HttpUtils。*;@RestController public class StateBasedCachedService { 私人靜態最終記錄器記錄儀= LoggerFactory.getLogger(StateBasedCachedService。類); private static final String STATE_BASED_BASE_ENDPOINT = “/ stubdata” ; private static final String STATE_BASED_UPDATE_RESOURCE_ENDPOINT = STATE_BASED_BASE_ENDPOINT + “/ {id}” ; private static final String LAST_MODIFIED_ENDPOINT = STATE_BASED_BASE_ENDPOINT + “/ lastmodified / {id}” ; private static final String ETAG_ENDPOINT = STATE_BASED_BASE_ENDPOINT + “/ etag / {id}” ; @Autowired

我們的端點與ETag和Last-Modified標題一起使用,因此我們創建一個實用程序方法來幫助我們進行必要的檢查和操作。HttpUtils類顯示如下:

包 com.tasosmartidis.caching_demo.utils;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import javax.servlet.http.HttpServletRequest;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Date;公共 類 HttpUtils { private static final String IF_MODIFIED_SINCE_HEADER = “if-modified-since” ; private static final String HEADER_DATE_PATTERN = “EEE,dd MMM yyyy HH:mm:ss zzz” ; private static final String ETAG_HEADER = “etag” ; public static ResponseEntity make304NotModifiedResponse () { return ResponseEntity.status( HttpStatus.NOT_MODIFIED ).build();

現在我們來為這些端點創建我們的測試並查看日誌:

包com.tasosmartidis.caching_demo.web;

我們測試的日誌顯示了我們的預期,如下圖所示:

網頁。StateBasedCachedServiceTest:測試的方法“最後一次修改”開始......

所以我們在不改變資源的情況下不再發送資源來節省一些帶寬。但是,對於從存儲庫處理和檢索資源來說,這並不算太多。而基於時間的緩存並沒有確保我們的資源處於原始服務器的狀態。那麼什麼

那麼,我們可以提出一個解決方案來確保緩存被使用並得到保證。我們可以提出我們的緩存的無效解決方案。這絕對不是一個容易的任務,有一個長期的引用(起源未知):“計算機科學中只有兩件難事:緩存無效和命名的東西。

讓我們來看一個簡單的無效示例,當資源被更新時:我們要麼刪除該資源的緩存版本,要麼更新緩存中的版本。這保證我們永遠不會提供陳舊的版本的請求。

以下課程將展示如何使用這種方式的服務:

包 com.tasosmartidis.caching_demo.web;導入 com.tasosmartidis.caching_demo。數據 .StubData;導入 com.tasosmartidis.caching_demo。數據 .StubDataDao;導入 org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory。註釋。自動連線import org.springframework.cache。註釋 .CacheEvict;import org.springframework.cache。註釋 .CachePut;import org.springframework.cache。註釋。import org.springframework.http.ResponseEntity;import org.springframework.web.bind。註釋 .PathVariable;import org.springframework.web.bind。註釋 .RequestMapping;import org.springframework.web.bind。註釋 .RequestMethod;import org.springframework.web.bind。註釋 .RestController;import static com.tasosmartidis.caching_demo.utils.LoggingUtils.logMethodEntered;@RestController 公共 類 CacheWithInvalidationService { 私人靜態最終記錄器記錄儀= LoggerFactory.getLogger(CacheWithInvalidationService 類); private static final String STUB_DATA_UPDATE_CACHE_ENDPOINT = “/ update-cache / stubdata / {id}” ; private static final String STUB_DATA_EVICT_CACHE_ENDPOINT = “/ evict-cache / stubdata / {id}” ; private static final String INVALIDATION_CACHE_NAME = “resource-level-cache” ; private static final String STUB_DATA_ENDPOINT = “/ stubdata / {id}” ;

現在我們來創建一個測試類,並確保所有的工作都像我們想要的那樣工作:

包 com.tasosmartidis.caching_demo.web;import org.junit.Ignore;import org.junit.Test;import org.junit.runner.RunWith;導入 org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import static com.tasosmartidis.caching_demo.testutils.HttpMethodsUtils.doGetEnsure200AndReturnResponseAsString;import static com.tasosmartidis.caching_demo.testutils.HttpMethodsUtils.doPutEnsure200AndReturnResponseAsString;import static com.tasosmartidis.caching_demo.utils.LoggingUtils。*;@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) public class CacheWithInvalidationServiceTest { private static final Logger LOGGER = LoggerFactory.getLogger(CacheWithInvalidationServiceTest.class); private static final String STUB_DATA_UPDATE_CACHE_ENDPOINT = “/ update-cache / stubdata / 1” ; private static final String STUB_DATA_EVICT_CACHE_ENDPOINT = “/ evict-cache / stubdata / 1” ; private static final String STUB_DATA_ENDPOINT = “/ stubdata / 1” ; @Test

運行測試課程給我們帶來了綠色,日誌告訴了以下故事:

W上。CacheWithInvalidationServiceTest:測試的方法“緩存驅逐”開始......

Java高級進階:

1、具有1-5工作經驗的,面對目前流行的技術不知從何下手,

需要突破技術瓶頸的可以加。2、在公司待久了,過得很安逸,

但跳槽時面試碰壁。需要在短時間內進修、跳槽拿高薪的可以加。

3、如果沒有工作經驗,但基礎非常紮實,對java工作機制,

常用設計思想,常用java開發框架掌握熟練的,可以加。

4、覺得自己很牛B,一般需求都能搞定。

但是所學的知識點沒有系統化,很難在技術領域繼續突破的可以加。

5. 群號:高級架構群 469691824備註好信息!

6.阿里Java高級大牛直播講解知識點,分享知識,

多年工作經驗的梳理和總結,帶著大家全面、

科學地建立自己的技術體系和技術認知!

相關推薦

推薦中...