对于高并发系统来说,有三个重要的机制来保障其高效运行,它们分别是:缓存、限流和熔断,所以本文就来和大家探讨一下多级缓存的实现方法,希望对大家有所帮助。
科普文:Redis系列之【Redis 性能问题及优化方案】-CSDN博客
科普文:Java基础系列之【并发编程三JMM内存模型和 volatile缓存一致性】-CSDN博客
实战:微服务之Spring Cloud 熔断保护组件Hystrix熔断、请求合并、请求缓存实操-CSDN博客
科普文:深入理解MyBatis 的缓存机制_mybatis 缓存更新机制-CSDN博客
科普文:11个后端API接口性能优化技巧_后端接口改造-CSDN博客
科普文:分布式架构中的三高:高并发、高性能、高可用_分布式高级总结-CSDN博客
Java web应用性能分析之【高并发之降级】-CSDN博客
Java web应用性能分析之【高并发之限流】_web服务限流-CSDN博客
Java web应用性能分析之【高并发之缓存-多级缓存】_java多级缓存-CSDN博客
前面的文章关于缓存对性能的优化都有整理,包括请求合并、多级缓存:本地线程、请求上下文、jvm缓存,分布式缓存redis、mybatis缓存,Nginx缓存等等,有需要的可以自行查阅。
1.冗余设计理念
当然不是,不要把所有鸡蛋放到一个篮子里,成熟的系统在关键功能实现时一定会考虑冗余设计,注意这里的冗余设计不是贬义词。
冗余设计是在系统或设备完成任务起关键作用的地方,增加一套以上完成相同功能的功能通道(or 系统)、工作元件或部件,以保证当该部分出现故障时,系统或设备仍能正常工作,以减少系统或者设备的故障概率,提高系统可靠性。
例如,飞机的设计,飞机正常运行只需要两个发动机,但在每台飞机的设计中可能至少会设计四个发动机,这就有冗余设计的典型使用场景,这样设计的目的是为了保证极端情况下,如果有一个或两个发动机出现故障,不会因为某个发动机的故障而引起重大的安全事故。
2.多级缓存概述
缓存功能的设计也是一样,我们在高并发系统中通常会使用多级缓存来保证其高效运行,其中的多级缓存就包含以下这些:
- 浏览器缓存:它的实现主要依靠 HTTP 协议中的缓存机制,当浏览器第一次请求一个资源时,服务器会将该资源的相关缓存规则(如 Cache-Control、Expires 等)一同返回给客户端,浏览器会根据这些规则来判断是否需要缓存该资源以及该资源的有效期。
- Nginx 缓存:在 Nginx 中配置中开启缓存功能。
- 分布式缓存:所有系统调用的中间件都是分布式缓存,如 Redis、MemCached 等。
- 本地缓存:JVM 层面,单系统运行期间在内存中产生的缓存,例如 Caffeine、Google Guava 等。
以下是它们的具体使用。
2.1 开启浏览器缓存
在 Java Web应用中,实现浏览器缓存可以使用 HttpServletResponse 对象来设置与缓存相关的响应头,以开启浏览器的缓存功能,它的具体实现分为以下几步。
① 配置 Cache-Control
Cache-Control 是 HTTP/1.1 中用于控制缓存策略的主要方式。它可以设置多个指令,如 max-age(定义资源的最大存活时间,单位秒)、no-cache(要求重新验证)、public(指示可以被任何缓存区缓存)、private(只能被单个用户私有缓存存储)等,设置如下:
1 |
|
② 配置 Expires
设置一个绝对的过期时间,超过这个时间点后浏览器将不再使用缓存的内容而向服务器请求新的资源,设置如下:
1 |
|
③ 配置 ETag
ETag(实体标签)一种验证机制,它为每个版本的资源生成一个唯一标识符。当客户端发起请求时,会携带上先前接收到的 ETag,服务器根据 ETag 判断资源是否已更新,若未更新则返回 304 Not Modified 状态码,通知浏览器继续使用本地缓存,设置如下:
1 2 |
|
④ 配置 Last-Modified
指定资源最后修改的时间戳,浏览器下次请求时会带上 If-Modified-Since 头,服务器对比时间戳决定是否返回新内容或发送 304 状态码,设置如下:
1 2 |
|
整体配置
在 Spring Web 框架中,可以通过 HttpServletResponse 对象来设置这些头信息。例如,在过滤器中设置响应头以启用缓存:
1 2 3 4 5 6 7 8 9 |
|
以上就是在 Java Web 应用程序中利用 HTTP 协议特性控制浏览器缓存的基本方法。
2.2 开启 Nginx 缓存
Nginx 中开启缓存的配置总共有以下 5 步。
① 定义缓存配置
在 Nginx 配置中定义一个缓存路径和配置,通过 proxy_cache_path 指令完成,例如,以下配置:
1 |
|
其中:
- /path/to/cache:这是缓存文件的存放路径。
- levels=1:2:定义缓存目录的层级结构。
- keys_zone=my_cache:10m:定义一个名为
my_cache
的共享内存区域,大小为 10MB。 - max_size=10g:设置缓存的最大大小为 10GB。
- inactive=60m:如果在 60 分钟内没有被访问,缓存将被清理。
- use_temp_path=off:避免在文件系统中进行不必要的数据拷贝。
② 启用缓存
在 server 或 location 块中,使用 proxy_cache 指令来启用缓存,并指定要使用的 keys zone,例如,以下配置:
1 2 3 4 5 6 7 |
|
③ 设置缓存有效期
使用 proxy_cache_valid 指令来设置哪些响应码的缓存时间,例如,以下配置:
1 2 3 4 5 6 |
|
④ 配置反向代理
确保你已经配置了反向代理,以便 Nginx 可以将请求转发到后端服务器。例如,以下配置:
1 2 3 4 |
|
⑤ 重新加载配置
保存并关闭 Nginx 配置文件后,使用 nginx -s reload 命令重新加载配置,使更改生效。
2.3 使用分布式缓存
在 Spring Boot 项目中使用注解的方式来操作分布式缓存 Redis 的实现步骤如下。
① 添加依赖
在你的 pom.xml 文件中添加 Spring Boot 的 Redis 依赖,如下所示:
1 2 3 4 5 6 |
|
② 配置 Redis 连接信息
在 application.properties 或 application.yml 文件中配置 Redis 的相关信息,如下所示。
1 2 3 |
|
③ 启动缓存
在 Spring Boot 主类或者配置类上添加 @EnableCaching 注解来启用缓存。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
④ 使用缓存
在服务类或方法上使用 @Cacheable,@CacheEvict,@CachePut 等注解来定义缓存行为。
例如,使用 @Cacheable 注解来缓存方法的返回值:
1 2 3 4 5 6 7 8 9 10 11 |
|
也可以使用 @CacheEvict 注解来删除缓存:
1 2 3 4 5 6 7 8 9 10 |
|
在这个例子中,deleteUser 方法会删除 "users" 缓存中 key 为 id 的缓存项。
可以使用 @CachePut 注解来更新缓存:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
在这个例子中,updateUser 方法会更新 "users" 缓存中 key 为 user.id 的缓存项,缓存的值是方法的返回值。
2.4 使用本地缓存
以 Caffeine 本地缓存的使用为例,它在 Spring Boot 项目中的使用如下。
① 添加依赖
1 2 3 4 5 6 7 8 |
|
② 配置 Caffeine 缓存
在 application.properties 或 application.yml 文件中配置 Caffeine 缓存的相关参数。例如:
1 2 3 |
|
这里 spring.cache.caffeine.spec 是一个 Caffeine 规范字符串,用于设置初始容量、最大容量和写入后过期时间等缓存策略,其中:
- initialCapacity:初始容器容量。
- maximumSize:最大容量。
- expireAfterWrite:写入缓存后 N 长时间后过期。
③ 自定义 Caffeine 配置类(可选步骤)
如果需要更复杂的配置,可以创建一个 Caffeine CacheManager 的配置类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
④ 开启缓存
若要利用 Spring Cache 抽象层,以便通过注解的方式更方便地管理缓存,需要在启动类上添加 @EnableCaching 注解,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
⑤ 使用注解进行缓存操作
在业务逻辑类中使用 @Cacheable、@CacheEvict 等注解实现数据的缓存读取和更新,和上面分布式缓存的使用相同,具体示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|