Web缓存:通过Java实现更好的经济战略

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介:   我读取 缓存的最好的比喻 来自Peter Chester,他在WordPress会议期间使用。他问观众,“3,485,250分为23,235”?最初的沉默之后,有些人把他们的计算器拿出来,最后有人大声喊道:“150.”切斯特先生又问了一个同样的问题,能够回应 这是缓存!  总而言之,这是一个非常简单的缓存情况,因为答案总是一样的。但隐喻是太棒了!实质上,缓存是关于经济。我们为等待响应的客户节省时间。我们节省资源,重新计算我们已经知道的答案。我们节省带宽。  我们该怎么做?通过保持一些响应“更接近”请求者并再次服务,而不必返回原始服务器并再次计算答案。

  我读取 缓存的最好的比喻 来自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高级大牛直播讲解知识点,分享知识,

  多年工作经验的梳理和总结,带着大家全面、

  科学地建立自己的技术体系和技术认知!

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
2月前
|
缓存 JavaScript 前端开发
Java 如何确保 JS 不被缓存
【10月更文挑战第19天】在 Java 中,可以通过设置 HTTP 响应头来确保 JavaScript 文件不被浏览器缓存。方法包括:1. 使用 Servlet 设置响应头,通过 `doGet` 方法设置 `Expires`、`Cache-Control` 和 `Pragma` 头;2. 在 Spring Boot 中配置拦截器,通过 `NoCacheInterceptor` 类和 `WebConfig` 配置类实现相同功能。这两种方法都能确保每次请求都能获取到最新的 JavaScript 内容。
|
20天前
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
38 6
Spring Boot 入门:简化 Java Web 开发的强大工具
|
1月前
|
Java Maven Spring
Java Web 应用中,资源文件的位置和加载方式
在Java Web应用中,资源文件如配置文件、静态文件等通常放置在特定目录下,如WEB-INF或classes。通过类加载器或Servlet上下文路径可实现资源的加载与访问。正确管理资源位置与加载方式对应用的稳定性和可维护性至关重要。
54 6
|
1月前
|
存储 安全 搜索推荐
理解Session和Cookie:Java Web开发中的用户状态管理
理解Session和Cookie:Java Web开发中的用户状态管理
70 4
|
1月前
|
Java 持续交付 项目管理
使用Maven进行项目管理:提高Java Web开发的效率
Maven 是一款强大的项目管理和构建自动化工具,广泛应用于Java社区。它通过依赖管理、构建生命周期管理、插件机制和多模块项目支持等功能,简化了项目的构建过程,提高了开发效率。本文将介绍Maven的核心功能及其在Java Web开发中的应用。
65 0
WK
|
1月前
|
安全 Java 编译器
C++和Java哪个更适合开发web网站
在Web开发领域,C++和Java各具优势。C++以其高性能、低级控制和跨平台性著称,适用于需要高吞吐量和低延迟的场景,如实时交易系统和在线游戏服务器。Java则凭借其跨平台性、丰富的生态系统和强大的安全性,广泛应用于企业级Web开发,如企业管理系统和电子商务平台。选择时需根据项目需求和技术储备综合考虑。
WK
84 0
|
2月前
|
前端开发 Java API
JAVA Web 服务及底层框架原理
【10月更文挑战第1天】Java Web 服务是基于 Java 编程语言用于开发分布式网络应用程序的一种技术。它通常运行在 Web 服务器上,并通过 HTTP 协议与客户端进行通信。
35 1
|
2月前
|
缓存 JavaScript 前端开发
Java 如何确保 JS 不被缓存
大家好,我是 V 哥。本文探讨了 Java 后端确保 JavaScript 不被缓存的问题,分析了文件更新后无法生效、前后端不一致、影响调试与开发及安全问题等场景,并提供了使用版本号、设置 HTTP 响应头、配置静态资源缓存策略和使用 ETag 等解决方案。最后讨论了缓存的合理使用及其平衡方法。
103 0
|
2月前
|
存储 缓存 NoSQL
构建高性能Web应用:缓存的重要性及其实现
构建高性能Web应用:缓存的重要性及其实现
消息中间件 缓存 监控
143 0
下一篇
DataWorks