通过READ-BEHIND 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
  • 在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

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

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

相关文章

mysql用户名锁定_MySQL用户锁定

修改方式&#xff1a;set global log_warnings2;MySQL 5.5版本官方文档中&#xff0c;并没有用户锁的状态。因此不存在用户会被锁。。The account-locking capability depends on the presence of the account_locked column in themysql.user table. For upgrades to MySQL 5.…

python package和目录_PyCharm中Directory与Python package的区别

对于Python而言&#xff0c;有一点是要认识明确的&#xff0c;python作为一个相对而言轻量级的&#xff0c;易用的脚本语言(当然其功能并不仅限于此&#xff0c;在此只是讨论该特点)&#xff0c;随着程序的增长&#xff0c;可能想要把它分成几个文件&#xff0c;以便逻辑更加清…

mysql过滤器_MYSQL复制过滤器

vim /etc/my.cnf.d/mariadb-server.cnf[mysqld]binlog-do-dbdb1 #白名单模式&#xff0c;仅允许主服务器上生成db1的二进制日志&#xff0c;此选项不支持一行指定多个参数&#xff0c;需要每个参数写一行binlog-do-dbdb2重启服务systemctl restart mariadb.service主服务器…

mysql锁表查询_Mysql数据库锁情况下开启备份导致数据库无法访问处理分享

[背景简介]MySQL是一种开放源代码的关系型数据库管理系统(RDBMS)&#xff0c;因为其速度、可靠性和适应性而备受关注。大多数人都认为在不需要事务化处理的情况下&#xff0c;MySQL是管理内容最好的选择。mysql虽然功能未必很强大&#xff0c;但因为它的开源、广泛传播&#xf…

junit编写测试代码_编写数据访问代码测试-不测试框架

junit编写测试代码当我们向数据访问代码编写测试时&#xff0c;是否应该测试其公共API的每种方法&#xff1f; 一开始听起来很自然。 毕竟&#xff0c;如果我们不测试所有内容&#xff0c;那么如何知道我们的代码可以按预期工作&#xff1f; 这个问题为我们提供了重要的线索&…

mysql直接执行文件格式_Windows 环境下执行 .sql 格式文件方式

windows 命令行中有2种执行 .sql 文件的方式&#xff1a;直接行文件 和 先进入mysql命令行然后执行文件。具体操作如下:1. 直接在windows命令行执行。打开windows命令行(winR打开运行窗口然后输入cmd&#xff0c;回车)&#xff0c;进入mysql的本机地址&#xff0c;如果配置了环…

python输入多个坐标点_判断多个坐标是否在同一条直线上|Python练习系列[13]

练习内容:判断多个坐标是否在同一条直线上完整代码和注释如下print(请输入几个点的横纵坐标,程序将会返回这几个点是否在同一条直线上)def coor_nums():#获得每个值的横纵坐标int_list[]#初始化坐标列表wrong_list[]#初始化错误列表judgement#判断是否需要修正坐标值while True…

Java大数据处理的流行框架

大数据挑战 在公司需要处理不断增长的数据量的各个领域中&#xff0c;对大数据的概念有不同的理解。 在大多数这些情况下&#xff0c;需要以某种方式设计所考虑的系统&#xff0c;以便能够处理该数据&#xff0c;而不会随着数据大小的增加而牺牲吞吐量。 从本质上讲&#xff0c…

工信部python证书多少钱_python requests SSL证书问题

错误信息如下&#xff1a;requests.exceptions.SSLError: ("bad handshake: Error([(SSL routines, tls_process_server_certificate, certificate verify failed)],)",)python做爬虫&#xff0c;对于有的网站&#xff0c;需要验证证书&#xff0c;比如&#xff1a;1…

mysql binlog线程恢复_使用MySQL SQL线程回放Binlog实现恢复

[toc]1. 需求部分1.1 基于MySQL复制同步特性&#xff0c;尝试使用Replication的SQL线程来回放binlog&#xff0c;可基于以下逻辑模拟场景做全量xtrabackup备份模拟日常备份执行sysbench压测4张表&#xff0c;20个线程&#xff0c;压测10分钟&#xff0c;模拟大量binlog删除实例…

带有Prometheus的Spring Boot和测微表第6部分:保护指标

以前&#xff0c;我们使用Prometheus成功启动了Spring Boot应用程序。 Spring应用程序中的一个端点正在公开我们的指标数据&#xff0c;以便Prometheus能够检索它们。 想到的主要问题是如何保护此信息。 Spring已经为我们提供了强大的安全框架 因此&#xff0c;将其轻松用于…

列举python中常用的数据类型_列举Python常用数据类型并尽量多的写出其中的方法...

#1 把字符串的第一个字符大写string.capitalize()#2 返回一个原字符串居中,并使用空格填充至长度 width 的新字符串string.center(width)#3 返回 str 在 string 里面出现的次数&#xff0c;如果 beg 或者 end 指定则返回指定范围内 str 出现的次数string.count(str, beg0, endl…

mysql 二元分词_MySQL 中文分词原理

一&#xff0c;首先我们来了解一下其他几个知识点&#xff1a;1. Mysql的索引意义&#xff1f;索引是加快访问表内容的基本手段&#xff0c;尤其是在涉及多个表的关联查询里。当然&#xff0c;索引可以加快检索速度&#xff0c;但是它也同时降低了索引列的插入&#xff0c;删除…

python 元类 type_Python 使用元类type创建类对象常见应用详解

本文实例讲述了Python 使用元类type创建类对象。分享给大家供大家参考&#xff0c;具体如下&#xff1a;type("123") 可以查看变量的类型;同时 type("类名",(父类),{类属性:值,类属性2:值}) 可以创建一个类。在Python中不建议一个函数具有不同的功能(重载)…

使用AWS Elastic Beanstalk轻松进行Spring Boot部署

朋友不允许朋友写用户身份验证。 厌倦了管理自己的用户&#xff1f; 立即尝试Okta的API和Java SDK。 在几分钟之内即可对任何应用程序中的用户进行身份验证&#xff0c;管理和保护。 几乎所有应用程序都依赖于身份验证。 开发人员以及雇用他们的公司都想确认谁在发出请求&…

mysql报错乱码_连接mysql服务器报错时,出现乱码

页头用了header(content-type:text/html;charsetutf-8);try{$this->dbonew PDO($dsn,$dbuser,$dbpassword);}catch(Exception $e){echo $e->getMessage();}连接失败时会报错&#xff0c;但是乱码&#xff0c;IE下编码查看是UTF-8&#xff0c;但是是乱码&#xff0c;如果选…

自学python条件_自学Python2.8-条件(if、if...else)

自学Python2.8-条件(if、if...else)1.if 判断语句if语句是用来进行判断的&#xff0c;其使用格式如下&#xff1a;if 要判断的条件:条件成立时&#xff0c;要做的事情当“判断条件”成立(True)时&#xff0c;才执行语句&#xff1b;反之&#xff0c;则不执行。执行语句可以为多…

mac lion 安装 mysql_mac osx下安装mysql

操作系统版本&#xff1a;mac osx 10.11mysql版本&#xff1a;官网下载dmg v5.6.33 https://www.mysql.com/安装步骤1.双击dmg安装2.开启mysql服务系统偏好设置-底部-mysql-打开服务这个时候还不能使用mysql命令&#xff0c;需要配置mysql命令的路径。3.配置环境变量mysql的路…

python爬虫实训日志_Python学习学习日志——爬虫《第一篇》(BeautifulSoup)

爬虫简介(学习日志第一篇)一、爬虫介绍爬虫&#xff1a;一段自动抓取互联网信息的程序&#xff0c;从互联网上抓取对于我们有价值的信息。二、Pyyhon爬虫架构Python 爬虫架构主要由五个部分组成&#xff0c;分别是调度器、URL管理器、网页下载器、网页解析器、应用程序(爬取的有…

zookeeper 负载_ZooKeeper,策展人以及微服务负载平衡的工作方式

zookeeper 负载Zookeeper如何确保每个工人都能从工作委托经理那里愉快地完成工作。 Apache ZooKeeper是注册&#xff0c;管理和发现在不同计算机上运行的服务的工具。 当我们必须处理具有许多节点的分布式系统时&#xff0c;它是技术堆栈中必不可少的成员&#xff0c;这些节点…