通过READ-BEHIND CACHE来控制缓慢的生产者
在我們的互聯(lián)世界中,我們經(jīng)常使用我們不擁有或無權改善的API中的數(shù)據(jù)。 如果一切順利,他們的表現(xiàn)就會很好,每個人都會感到高興。 但是太多次,我們不得不使用延遲小于最佳延遲的 API。
當然,答案是緩存該數(shù)據(jù) 。 但是,您不知道何時過時的緩存是很危險的事情,因此這不是一個適當?shù)慕鉀Q方案。
因此,我們陷入困境。 我們需要習慣于等待頁面加載,或者投資一個非常好的微調器來招待用戶等待數(shù)據(jù)。 還是……是嗎? 如果為一個較小的,經(jīng)過計算的折衷而又使用相同的緩慢生成器可以達到期望的性能,該怎么辦?
我想每個人都聽說過后寫式緩存。 它是高速緩存的一種實現(xiàn),該高速緩存注冊了將異步發(fā)生的寫操作,在對后臺任務執(zhí)行寫操作的同時,調用者可以自由地繼續(xù)其業(yè)務。
如果我們將這個想法用于問題的閱讀方面該怎么辦。 讓我們?yōu)槁偕a者提供一個后置緩存 。
合理警告 :此技術僅適用于我們可以在有限數(shù)量的請求中提供過時的數(shù)據(jù)。 因此,如果您可以接受您的數(shù)據(jù)將是“ 最終新鮮的 ”,則可以應用此數(shù)據(jù)。
我將使用Spring Boot來構建我的應用程序。 可以在GitHub上訪問所有提供的代碼: https : //github.com/bulzanstefan/read-behind-presentation 。 在實施的不同階段有3個分支。
代碼示例僅包含相關的行,以簡化操作。
現(xiàn)狀
分支機構:現(xiàn)狀
因此,我們將從現(xiàn)狀開始。 首先,我們有一個緩慢的生產者,它接收URL參數(shù)。 為了簡化此過程,我們的生產者將睡眠5秒鐘,然后返回一個時間戳(當然,這不是低變化數(shù)據(jù)的一個很好的示例,但是出于我們的目的,盡快檢測到數(shù)據(jù)是有用的) 。
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
- 在application.properties中啟用緩存: spring.cache.type= simple
- 將@EnableCaching注解添加到您的Spring Application主類
- 將@Cacheable("cacheName")添加到要緩存的方法中
現(xiàn)在我們有一個簡單的緩存表示。 這也適用于分布式緩存 ,但是在此示例中,我們將堅持使用內存中的緩存。 使用者將緩存數(shù)據(jù),并且在第一次調用后,等待時間消失了。 但是數(shù)據(jù)很快就會過時 ,沒有人將其逐出。 我們可以做得更好!
接聽電話
分行:碩士
我們需要做的下一件事是在發(fā)生呼叫時對其進行攔截,而不管是否將其緩存。
為了做到這一點,我們需要
- 創(chuàng)建一個自定義注釋: @ReadBehind
- 注冊一個方面,該方面將攔截以@ReadBehind注釋的方法調用
因此,我們創(chuàng)建了注釋并將其添加到performRequest方法
@ReadBehind @Cacheable(value = CACHE_NAME, keyGenerator = "myKeyGenerator" ) public String performRequest(String name) {如您所見,定義了一個CACHE_NAME常量。 如果需要動態(tài)設置緩存名稱,則可以使用CacheResolver和配置。 同樣,為了控制密鑰結構,我們需要定義一個密鑰生成器。
@Bean KeyGenerator myKeyGenerator() { return (target, method, params) -> Stream.of(params) .map(String::valueOf) .collect(joining( "-" )); }此外,為了添加方面,我們需要
- 將依賴項添加到org.springframework.boot:spring-boot-starter-aop
- 創(chuàng)建方面類
- 我們需要實現(xiàn)Ordered接口并為getOrder方法返回1。 即使在值已經(jīng)存在于高速緩存中時高速緩存機制將抑制方法的調用,方面也需要啟動
現(xiàn)在,我們可以攔截所有對@ReadBehind方法的調用。
記住電話
現(xiàn)在有了調用,我們需要保存所有需要的數(shù)據(jù),以便能夠從另一個線程調用它。
為此,我們需要保留:
- 被稱為豆
- 參數(shù)調用
- 方法名稱
我們將這些對象保留在另一個bean中
@Component public class CachedInvocations { private final Set<CachedInvocation> invocations = synchronizedSet(new HashSet<>()); public void addInvocation(CachedInvocation invocation) { invocations.add(invocation); } }我們將調用保持在一個集合中,并且我們有一個計劃的工作以固定的速率處理這些調用,這一事實將給我們帶來一個很好的副作用,即限制了對外部API的調用。
安排落后的工作
現(xiàn)在我們知道執(zhí)行了哪些調用,我們可以開始計劃的作業(yè)以接聽這些調用并刷新緩存中的數(shù)據(jù)
為了在Spring Framework中安排工作,我們需要
- 在您的Spring應用程序類中添加注釋@EnableScheduling
- 使用@Scheduled注釋的方法創(chuàng)建作業(yè)類
刷新緩存
現(xiàn)在我們已經(jīng)收集了所有信息,我們可以對后讀線程進行真正的調用并更新緩存中的信息。
首先,我們需要調用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; } }現(xiàn)在我們有了新數(shù)據(jù),我們需要更新緩存
首先, 計算 緩存密鑰 。 為此,我們需要使用為緩存定義的密鑰生成器。
現(xiàn)在,我們擁有所有信息來更新緩存,讓我們獲取緩存參考并更新值
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); } }至此,我們完成了“隱藏式”想法的實施。 當然,您仍然需要解決其他問題。
例如,您可以執(zhí)行此實現(xiàn)并立即在線程上觸發(fā)調用。 這樣可以確保在第一時間刷新緩存。 如果過時的時間是您的主要問題,則應該這樣做。
我喜歡調度程序,因為它也可以作為一種限制機制 。 因此,如果您一遍又一遍地進行相同的呼叫,則后讀調度程序會將這些呼叫折疊為一個呼叫
運行示例代碼
- 先決條件:已安裝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 –新請求)返回更新后的值,但在首次呼叫后不應看到任何延遲 。
警告
就像我在本文開頭所說的那樣,在實現(xiàn)read-behind時您應該注意一些事情。
另外,如果您負擔不起最終的一致性 ,請不要這樣做
這適用于具有 低頻變化 API的高頻讀取
如果API實現(xiàn)了某種ACL ,則需要在緩存鍵中添加用于發(fā)出請求的用戶名。 否則,可能會發(fā)生非常糟糕的事情。
因此,請仔細分析您的應用程序,并僅在適當?shù)牡胤绞褂么讼敕ā?
翻譯自: https://www.javacodegeeks.com/2019/12/take-control-your-slow-producers-read-behind-cache.html
總結
以上是生活随笔為你收集整理的通过READ-BEHIND CACHE来控制缓慢的生产者的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 嵌入式linux开发(嵌入式linux程
- 下一篇: ddos黑客技术教程视频讲解(ddos黑