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。 即使在值已经存在于高速缓存中时,高速缓存机制将抑制方法的调用,方面也需要启动
 @Aspect  @Component  public class ReadBehindAdvice implements Ordered { @Before( "@annotation(ReadBehind)" ) public Object cacheInvocation(JoinPoint joinPoint) {  ... @Override public int getOrder() { return 1; } 

现在,我们有了一种方法来拦截对@ReadBehind方法的所有调用。

记住电话

现在有了调用,我们需要保存所有需要的数据,以便能够从另一个线程调用它。

为此,我们需要保留:

  • 被称为
  • 称为参数
  • 方法名称
 @Before( "@annotation(ReadBehind)" ) public Object cacheInvocation(JoinPoint joinPoint) { invocations.addInvocation(new CachedInvocation(joinPoint)); return null; } 
 public CachedInvocation(JoinPoint joinPoint) { targetBean = joinPoint.getTarget(); arguments = joinPoint.getArgs(); targetMethodName = joinPoint.getSignature().getName(); } 

我们将这些对象保留在另一个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注释的方法创建作业类
 @Component  @RequiredArgsConstructor  public class ReadBehindJob { private final CachedInvocations invocations; @Scheduled(fixedDelay = 10000) public void job() { invocations.nextInvocations() .forEach(this::refreshInvocation); }  } 

刷新缓存

现在,我们已经收集了所有信息,我们可以在后读线程上进行真正的调用 ,并更新缓存中的信息。

首先,我们需要调用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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/331889.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

azkaban mysql参数_azkaban参数详解

参数传递是调度字体工作流运行时非常重要的一部分&#xff0c;工作流的执行&#xff0c;单个作业的执行&#xff0c;多个工作流之间的依赖执行&#xff0c;历史任务重算&#xff0c;都涉及到参数传递和同步。1 参数类型综述azkaban的工作流中的参数可以分为如下几个类型&#x…

接口与回调

【0】README 0.1&#xff09; 本文描述源代码均 转自 core java volume 1&#xff0c; 旨在理解 接口与回调 概念 &#xff1b; 【1】接口与回调相关 1.1&#xff09;回调定义&#xff1a; 回调是一种常见的程序设计模式&#xff0c; 在这种模式中&#xff0c; 可以指出某个…

spring react_使用Spring Cloud Gateway保护React式微服务

spring react朋友不允许朋友写用户身份验证。 厌倦了管理自己的用户&#xff1f; 立即尝试Okta的API和Java SDK。 数分钟之内即可在任何应用程序中对用户进行身份验证&#xff0c;管理和保护。 所以你想完全React&#xff0c;是吗&#xff1f; 大&#xff01; React式编程是使…

mysql断电同步不起作用_mysql主从同步因断电产生的不能同步问题

偶尔因为断电导致mysql slave 出现复制错误“Could not parse relay log event entry”Could not parse relay log event entry. The possible reasons are: the masters binary log is corrupted (you can check this by running mysqlbinlog on the binary log), the slaves …

图论——Dijkstra+prim算法涉及到的优先队列(二叉堆)

【0】README 0.1&#xff09;为什么有这篇文章&#xff1f;因为 Dijkstra算法的优先队列实现 涉及到了一种新的数据结构&#xff0c;即优先队列&#xff08;二叉堆&#xff09;的操作需要更改以适应这种新的数据结构&#xff0c;我们暂且吧它定义为Distance&#xff0c; 而不是…

cucumber测试_如何在Cucumber中进行后端测试

cucumber测试Cucumber是一种规范语言的执行框架。 它并不是要成为测试语言&#xff0c;而是用于创建测试自动化。 Cucumber最适合出现一些实际参与者互动并取得某种成果的情况。 当可以从用户的角度编写它时&#xff0c;它特别有用。 Given Sarah is a premium club member W…

linux mysql删除密码忘记了_linux下忘记mysql密码的几种找回方法(推荐)

今天我们主要是讲一下关于linux忘记mysql密码处理方法&#xff0c;下面提供了5种linux忘记mysql密码找回方法哦。方法一(先进入root权限)&#xff1a;# /etc/init.d/mysql stop# mysqld_safe --usermysql --skip-grant-tables --skip-networking &# mysql -u rootmysql>…

Dijkstra 算法——计算有权最短路径(边有权值)

【0】README 0.1&#xff09; 本文总结于 数据结构与算法分析&#xff0c; 源代码均为原创&#xff0c; 旨在理解 Dijkstra 的思想并用源代码加以实现&#xff1b; 0.2&#xff09;最短路径算法的基础知识&#xff0c;参见 http://blog.csdn.net/pacosonswjtu/article/detail…

spring使用自定义注解_用Spring组成自定义注释

spring使用自定义注解Java批注在2004年随Java 5一起引入&#xff0c;是一种将元数据添加到Java源代码中的方法。 如今&#xff0c;许多主要框架&#xff08;如Spring或Hibernate&#xff09;都严重依赖注释。 在本文中&#xff0c;我们将介绍一个非常有用的Spring功能&#xf…

打印结果和调试结果不一样(C语言)

【0】README 0.1&#xff09;本文旨在阐述 个人的debug经历&#xff0c;遇到的各种debug 奇葩问题&#xff0c; 说是奇葩&#xff0c;其实也是自己 不小心或者说是编程习惯不好&#xff1b; 【1】debug和running的运行结果不一致&#xff08;乍眼一看&#xff0c;你肯定醉了&a…

mysql add default_MySQL中create table DEFAULT 用法

CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name [(create_definition,...)][table_options] [select_statement]TEMPORARY&#xff1a;该关键字表示用create table新建的表为临时表&#xff0c;此表在当前会话结束后将自动消失。临时表主要被应用于存储过程中&#xff0c;…

jakarta ee_Jakarta EE贡献–入门

jakarta ee您是否有兴趣帮助Jakarta EE向前发展&#xff1f; 我也是。我想提供一些详细信息&#xff0c;以帮助有兴趣入门的人。 第1步&#xff1a; 开始捐款的第一步是签署Eclipse Foundation Committer and Contributor Agreement&#xff08;ECA&#xff09;&#xff1a; …

最小生成树基础

【0】README 0.1&#xff09; 本文总结于 数据结构与算法分析&#xff0c; 源代码均为原创&#xff0c; 旨在 review 最小生成树的基础知识&#xff1b; 0.2&#xff09;了解本文的内容是 分析 Prim算法&#xff08;普利姆算法&#xff09;和 Kruskal算法&#xff08;克鲁斯卡…

mysql dump gtid_mysqldump命令详解 Part 3- 备份全库

前面说了MySQL Linux平台和Windows平台的安装下面开始是MySQL的一些学习笔记前面我们说了如果构造数据这节开始说MySQL 的备份环境为MySQL 5.7.25在解释命令之前我们先弄清楚数据库中有哪些对象上一节我们建立了数据库并建立相关的对象数据库表存储过程函数触发器事件这节讲一些…

apache lucene_Apache Lucene中的并发查询执行

apache luceneApache Lucene是一个出色的并发纯Java搜索引擎&#xff0c;如果您愿意&#xff0c;它可以轻松地使服务器上的可用CPU或IO资源饱和。 “典型” Lucene应用程序的并发模型在搜索时每个查询一个线程&#xff0c;但是您是否知道Lucene也可以使用多个线程同时执行一个查…

最小生成树——Prim(普利姆)算法

【0】README 0.1&#xff09; 本文总结于 数据结构与算法分析&#xff0c; 源代码均为原创&#xff0c; 旨在 理解Prim算法的idea 并用 源代码加以实现&#xff1b; 0.2&#xff09;最小生成树的基础知识&#xff0c;参见 http://blog.csdn.net/pacosonswjtu/article/details…

mysql grant usage on_grant 权限 on 数据库对象 to 用户

grant 权限 on 数据库对象 to 用户一、grant 普通数据用户&#xff0c;查询、插入、更新、删除 数据库中所有表数据的权利。grant select on testdb.* to common_user’%’grant insert on testdb.* to common_user’%’grant update on testdb.* to common_user’%’grant del…

openjdk8 项目结构_OpenJDK织机和结构化并发

openjdk8 项目结构Project Loom是Hotspot Group赞助的项目之一&#xff0c;旨在向JAVA世界提供高吞吐量和轻量级的并发模型。 在撰写本文时&#xff0c;Loom项目仍在积极开发中&#xff0c;其API可能会更改。 为什么要织机&#xff1f; 每个新项目可能会出现的第一个问题是为什…

mysql连库串_数据库连接串整理 - osc_ac5z111b的个人空间 - OSCHINA - 中文开源技术交流社区...

常用JDBC驱动与连接字符串MySQLdriver&#xff1a;com.mysql.jdbc.Driverurl&#xff1a;jdbc:mysql://localhost:3306/mydbMySQL url格式&#xff1a;jdbc:mysql://[host:port]/[database][?参数名1][参数值1][&参数名2][参数值2]…参数名称参数说明缺省值最低版本要求us…

最小生成树——Kruskal(克鲁斯卡尔)算法

【0】README 0.1&#xff09; 本文总结于 数据结构与算法分析&#xff0c; 源代码均为原创&#xff0c; 旨在 理解 Kruskal&#xff08;克鲁斯卡尔&#xff09;算法 的idea 并用 源代码加以实现&#xff1b; 0.2&#xff09;最小生成树的基础知识&#xff0c;参见 http://blo…