read cache_通过READ-BEHIND CACHE控制您的慢速生产者
read cache
在我們的互聯世界中,我們經常使用我們不擁有或無權改善的API中的數據。 如果一切順利,他們的表現就會很好,每個人都會高興。 但是太多次,我們不得不使用延遲小于最佳延遲的 API。
當然,答案是緩存該數據 。 但是,當緩存過時時您不知道的緩存是一件危險的事情,因此這不是一個適當的解決方案。
因此...我們被困住了。 我們需要習慣于等待頁面加載,或者投資一個非常好的微調器來招待用戶等待數據。 還是……是嗎? 如果為一個較小的,經過計算的折衷而又使用相同的緩慢生成器可以達到期望的性能,該怎么辦?
我想每個人都聽說過后寫式緩存。 它是高速緩存的一種實現,該高速緩存注冊了將異步發生的寫操作,在對后臺任務執行寫操作的同時,調用者可以自由地繼續其業務。
如果我們將這個想法用于問題的閱讀方面該怎么辦。 讓我們為慢速生產者提供一個后置緩存 。
合理警告 :此技術僅適用于我們可以在有限數量的請求中提供過時的數據。 因此,如果您可以接受您的數據將“ 最終更新 ”,則可以應用此數據。
我將使用Spring Boot來構建我的應用程序。 可以在GitHub上訪問所有提供的代碼: https : //github.com/bulzanstefan/read-behind-presentation 。 在實施的不同階段有3個分支。
代碼示例僅包含相關的行,以簡化操作。
現狀
分支機構:現狀
因此,我們將從現狀開始。 首先,我們有一個接收URL參數的緩慢生成器。 為了簡化此過程,我們的生產者將睡眠5秒鐘,然后返回一個時間戳(當然,這不是低變化數據的一個很好的例子,但是出于我們的目的,盡快檢測到數據是有用的) 。
public static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat( "HH:mm:ss.SSS" ); @GetMapping String produce(@RequestParam String name) throws InterruptedException { Thread. sleep (5000); return name + " : " + SIMPLE_DATE_FORMAT. format (new Date()); }在消費者中,我們只是致電生產者:
//ConsumerController .java @GetMapping public String consume(@RequestParam(required = false ) String name) { return producerClient.performRequest(ofNullable(name).orElse( "default" )); } //ProducerClient .java @Component class ProducerClient { public String performRequest(String name) { return new RestTemplate().getForEntity( " http://localhost:8888/producer?name= {name}" , String.class, name) .getBody(); } }簡單緩存
分支:簡單緩存
為了在Spring啟用簡單的緩存 ,我們需要添加以下內容
- 對org.springframework.boot:spring-boot-starter-cache依賴org.springframework.boot:spring-boot-starter-cache
- 在application.properties中啟用緩存: spring.cache.type= simple
- 將@EnableCaching注解添加到您的Spring Application主類
- 將@Cacheable("cacheName")添加到要緩存的方法中
現在我們有一個簡單的緩存表示。 這也適用于分布式緩存 ,但是在此示例中,我們將堅持使用內存中的緩存。 使用者將緩存數據,并且在第一次調用后,等待時間消失了。 但是數據很快就會過時 ,沒有人將其逐出。 我們可以做得更好!
接聽電話
分行:碩士
我們需要做的下一件事是在發生調用時攔截該調用,無論是否緩存該調用。
為了做到這一點,我們需要
- 創建一個自定義注釋: @ReadBehind
- 注冊一個方面,該方面將攔截以@ReadBehind注釋的方法調用
因此,我們創建了注釋并將其添加到performRequest方法
@ReadBehind @Cacheable(value = CACHE_NAME, keyGenerator = "myKeyGenerator" ) public String performRequest(String name) {如您所見,定義了一個CACHE_NAME常量。 如果需要動態設置緩存名稱,則可以使用CacheResolver和配置。 同樣,為了控制密鑰結構,我們需要定義一個密鑰生成器。
@Bean KeyGenerator myKeyGenerator() { return (target, method, params) -> Stream.of(params) .map(String::valueOf) .collect(joining( "-" )); }此外,為了添加方面,我們需要
- 將依賴項添加到org.springframework.boot:spring-boot-starter-aop
- 創建方面類
- 我們需要實現Ordered接口并為getOrder方法返回1。 即使在值已經存在于高速緩存中時,高速緩存機制將抑制方法的調用,方面也需要啟動
現在,我們有了一種方法來攔截對@ReadBehind方法的所有調用。
記住電話
現在有了調用,我們需要保存所有需要的數據,以便能夠從另一個線程調用它。
為此,我們需要保留:
- 被稱為豆
- 稱為參數
- 方法名稱
我們將這些對象保留在另一個bean中
@Component public class CachedInvocations { private final Set<CachedInvocation> invocations = synchronizedSet(new HashSet<>()); public void addInvocation(CachedInvocation invocation) { invocations.add(invocation); } }我們將調用保持在一個集合中,并且我們有一個計劃的工作以固定的速率處理這些調用,這一事實將給我們帶來一個很好的副作用,即限制了對外部API的調用。
安排落后的工作
現在我們知道執行了哪些調用,我們可以開始計劃的作業以接聽這些調用并刷新緩存中的數據
為了在Spring Framework中安排工作,我們需要
- 在您的Spring應用程序類中添加注釋@EnableScheduling
- 使用帶有@Scheduled注釋的方法創建作業類
刷新緩存
現在,我們已經收集了所有信息,我們可以在后讀線程上進行真正的調用 ,并更新緩存中的信息。
首先,我們需要調用real方法 :
private Object execute(CachedInvocation invocation) { final MethodInvoker invoker = new MethodInvoker(); invoker.setTargetObject(invocation.getTargetBean()); invoker.setArguments(invocation.getArguments()); invoker.setTargetMethod(invocation.getTargetMethodName()); try { invoker.prepare(); return invoker.invoke(); } catch (Exception e) { log.error( "Error when trying to reload the cache entries " , e); return null; } }現在我們有了新數據,我們需要更新緩存
首先, 計算 緩存鍵 。 為此,我們需要使用為緩存定義的密鑰生成器。
現在,我們擁有所有信息來更新緩存,讓我們獲取緩存參考并更新值
private final CacheManager cacheManager; ... private void refreshForInvocation(CachedInvocation invocation) { var result = execute(invocation); if (result != null) { var cacheKey = keyGenerator.generate(invocation.getTargetBean(), invocation.getTargetMethod(), invocation.getArguments()); var cache = cacheManager.getCache(CACHE_NAME); cache.put(cacheKey, result); } }至此,我們完成了“隱藏式”想法的實施。 當然,您還需要解決其他問題。
例如,您可以執行此實現并立即在線程上觸發調用。 這樣可以確保在第一時間刷新緩存。 如果過時是您的主要問題,則應該這樣做。
我喜歡調度程序,因為它也可以作為一種限制機制 。 因此,如果您一遍又一遍地進行相同的呼叫,則后讀調度程序會將這些呼叫折疊為一個呼叫
運行示例代碼
- 先決條件:已安裝Java 11+
- 下載或克隆代碼https://github.com/bulzanstefan/read-behind-presentation
- 構建生產者: mvnw package or mvnw.bat package
- 運行生產者: java -jar target\producer.jar
- 構建使用者: mvnw package or mvnw.bat package
- 運行使用者: java -jar target\consumer.jar
- 訪問生產者: http:// localhost:8888 / producer?name = test
- 訪問使用者: http:// localhost:8080 / consumer?name = abc
- 使用者將在約15秒(10秒調度程序,5 –新請求)后返回更新的值,但是在第一次呼叫后應該看不到任何延遲 。
警告
正如我在本文開頭所說的那樣,在實現read-behind時您應該注意一些事情。
另外,如果您負擔不起最終的一致性 ,請不要這樣做
這適用于具有 低頻變化 API的高頻讀取
如果API實現了某種ACL ,則需要在緩存鍵中添加用于發出請求的用戶名。 否則,可能會發生非常糟糕的事情。
因此,請仔細分析您的應用程序,并僅在適當的地方使用此想法。
翻譯自: https://www.javacodegeeks.com/2019/12/take-control-your-slow-producers-read-behind-cache.html
read cache
總結
以上是生活随笔為你收集整理的read cache_通过READ-BEHIND CACHE控制您的慢速生产者的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 飞快的反义词是什么 飞快的反义词具体是什
- 下一篇: 生物用英语怎么说 非常简单