天天看點

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

  我讀取 緩存的最好的比喻 來自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=“maven.apache/POM/4.0.0” xmlns:xsi=“w3/2001/XMLSchema-instance” xsi:schemaLocation=“http:/ /maven.apache/POM/4.0.0 maven.apache/maven-v4_0_0.xsd“ >

  接下來,我們将為我們的

二手手遊購買

應用程式設定配置。我們擴充了

  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。注釋 ponentScan;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 ponentScan ;進口 組織.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=“w3/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.junitnerWith;導入 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.stereotypeponent;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.junitnerWith;導入 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進階大牛直播講解知識點,分享知識,

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

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