关于java:面试官如何实现多级缓存

4次阅读

共计 6902 个字符,预计需要花费 18 分钟才能阅读完成。

对于高并发零碎来说,有三个重要的机制来保障其高效运行,它们别离是:缓存、限流和熔断。而缓存是排在最后面也是高并发零碎之所以高效运行的要害伎俩,那么问题来了:缓存只应用 Redis 就够了吗?

1. 冗余设计理念

当然不是,不要把所有鸡蛋放到一个篮子里,成熟的零碎在要害性能实现时肯定会思考冗余设计,留神这里的冗余设计不是贬义词。

冗余设计是在零碎或设施实现工作起 关键作用的中央,减少一套以上实现雷同性能的性能通道(or 零碎)、工作元件或部件,以保障当该局部呈现故障时,零碎或设施仍能失常工作,以缩小零碎或者设施的故障概率,进步系统可靠性。

例如,飞机的设计,飞机失常运行只须要两个发动机,但在每台飞机的设计中可能至多会设计四个发动机,这就有冗余设计的典型应用场景,这样设计的目标是为了保障极其状况下,如果有一个或两个发动机呈现故障,不会因为某个发动机的故障而引起重大的安全事故。

2. 多级缓存概述

缓存性能的设计也是一样,咱们在高并发零碎中通常会应用多级缓存来保障其高效运行,其中的多级缓存就蕴含以下这些:

  1. 浏览器缓存:它的实现次要依附 HTTP 协定中的缓存机制,当浏览器第一次申请一个资源时,服务器会将该资源的相干缓存规定(如 Cache-Control、Expires 等)一起返回给客户端,浏览器会依据这些规定来判断是否须要缓存该资源以及该资源的有效期。
  2. Nginx 缓存:在 Nginx 中配置中开启缓存性能。
  3. 分布式缓存:所有零碎调用的中间件都是分布式缓存,如 Redis、MemCached 等。
  4. 本地缓存:JVM 层面,单零碎运行期间在内存中产生的缓存,例如 Caffeine、Google Guava 等。

以下是它们的具体应用。

2.1 开启浏览器缓存

在 Java Web 利用中,实现浏览器缓存能够应用 HttpServletResponse 对象来设置与缓存相干的响应头,以开启浏览器的缓存性能,它的具体实现分为以下几步。

① 配置 Cache-Control

Cache-Control 是 HTTP/1.1 中用于管制缓存策略的次要形式。它能够设置多个指令,如 max-age(定义资源的最大存活工夫,单位秒)、no-cache(要求从新验证)、public(批示能够被任何缓存区缓存)、private(只能被单个用户公有缓存存储)等,设置如下:

response.setHeader("Cache-Control", "max-age=3600, public"); // 缓存一小时

② 配置 Expires

设置一个相对的过期工夫,超过这个工夫点后浏览器将不再应用缓存的内容而向服务器申请新的资源,设置如下:

response.setDateHeader("Expires", System.currentTimeMillis() + 3600 * 1000); // 缓存一小时

③ 配置 ETag

ETag(实体标签)一种验证机制,它为每个版本的资源生成一个惟一标识符。当客户端发动申请时,会携带上先前接管到的 ETag,服务器依据 ETag 判断资源是否已更新,若未更新则返回 304 Not Modified 状态码,告诉浏览器持续应用本地缓存,设置如下:

String etag = generateETagForContent(); // 依据内容生成 ETag
response.setHeader("ETag", etag);

④ 配置 Last-Modified

指定资源最初批改的工夫戳,浏览器下次申请时会带上 If-Modified-Since 头,服务器比照工夫戳决定是否返回新内容或发送 304 状态码,设置如下:

long lastModifiedDate = getLastModifiedDate();
response.setDateHeader("Last-Modified", lastModifiedDate);

整体配置

在 Spring Web 框架中,能够通过 HttpServletResponse 对象来设置这些头信息。例如,在过滤器中设置响应头以启用缓存:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
       throws IOException, ServletException {HttpServletResponse httpResponse = (HttpServletResponse) response;
   // 设置缓存策略
   httpResponse.setHeader("Cache-Control", "max-age=3600");

   // 其余响应头设置...
   chain.doFilter(request, response);
}

以上就是在 Java Web 应用程序中利用 HTTP 协定个性管制浏览器缓存的根本办法。

2.2 开启 Nginx 缓存

Nginx 中开启缓存的配置总共有以下 5 步。

① 定义缓存配置

在 Nginx 配置中定义一个缓存门路和配置,通过 proxy_cache_path 指令实现,例如,以下配置:

proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m use_temp_path=off;

其中:

  • /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,例如,以下配置:

    server {  
      ...  
      location / {  
          proxy_cache my_cache;  
          ...  
      }  
    }

    ③ 设置缓存有效期

    应用 proxy_cache_valid 指令来设置哪些响应码的缓存工夫,例如,以下配置:

    location / {  
      proxy_cache my_cache;  
      proxy_cache_valid 200 304 12h;  
      proxy_cache_valid any 1m;  
      ...  
    }

    ④ 配置反向代理

    确保你曾经配置了反向代理,以便 Nginx 能够将申请转发到后端服务器。例如,以下配置:

    location / {  
      proxy_pass http://backend_server;  
      ...  
    }

    ⑤ 从新加载配置

    保留并敞开 Nginx 配置文件后,应用 nginx -s reload 命令从新加载配置,使更改失效。

    2.3 应用分布式缓存

    在 Spring Boot 我的项目中应用注解的形式来操作分布式缓存 Redis 的实现步骤如下。

    ① 增加依赖

    在你的 pom.xml 文件中增加 Spring Boot 的 Redis 依赖,如下所示:

    <dependencies>  
      <dependency>  
          <groupId>org.springframework.boot</groupId>  
          <artifactId>spring-boot-starter-data-redis</artifactId>  
      </dependency>  
    </dependencies>

    ② 配置 Redis 连贯信息

    在 application.properties 或 application.yml 文件中配置 Redis 的相干信息,如下所示。

    # application.properties  
    spring.redis.host=localhost  
    spring.redis.port=6379

    ③ 启动缓存

    在 Spring Boot 主类或者配置类上增加 @EnableCaching 注解来启用缓存。

    import org.springframework.cache.annotation.EnableCaching;  
    import org.springframework.boot.SpringApplication;  
    import org.springframework.boot.autoconfigure.SpringBootApplication;  
    
    @SpringBootApplication  
    @EnableCaching  
    public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);  
      }  
    
    }

    ④ 应用缓存

    在服务类或办法上应用 @Cacheable,@CacheEvict,@CachePut 等注解来定义缓存行为。

例如,应用 @Cacheable 注解来缓存办法的返回值:

import org.springframework.cache.annotation.Cacheable;  
import org.springframework.stereotype.Service;  
  
@Service  
public class UserService {@Cacheable("users")  
    public User findUserById(Long id) {  
        // 模仿从数据库中查问用户  
        return new User(id, "Alice");  
    }  
}

也能够应用 @CacheEvict 注解来删除缓存:

import org.springframework.cache.annotation.CacheEvict;  
import org.springframework.stereotype.Service;  
  
@Service  
public class UserService {@CacheEvict(value = "users", key = "#id")  
    public void deleteUser(Long id) {// 模仿从数据库中删除用户}  
}

在这个例子中,deleteUser 办法会删除 “users” 缓存中 key 为 id 的缓存项。

能够应用 @CachePut 注解来更新缓存:

import org.springframework.cache.annotation.CachePut;  
import org.springframework.stereotype.Service;  
  
@Service  
public class UserService {@CachePut(value = "users", key = "#user.id")  
    public User updateUser(User user) {  
        // 模仿更新数据库中的用户信息  
        return user;  
    }  
  
}

在这个例子中,updateUser 办法会更新 “users” 缓存中 key 为 user.id 的缓存项,缓存的值是办法的返回值。

2.4 应用本地缓存

以 Caffeine 本地缓存的应用为例,它在 Spring Boot 我的项目中的应用如下。

① 增加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
</dependency>

② 配置 Caffeine 缓存

在 application.properties 或 application.yml 文件中配置 Caffeine 缓存的相干参数。例如:

# application.properties
spring.cache.type=caffeine
spring.cache.caffeine.spec=initialCapacity=100,maximumSize=1000,expireAfterWrite=10s

这里 spring.cache.caffeine.spec 是一个 Caffeine 标准字符串,用于设置初始容量、最大容量和写入后过期工夫等缓存策略,其中:

  • initialCapacity:初始容器容量。
  • maximumSize:最大容量。
  • expireAfterWrite:写入缓存后 N 长时间后过期。

    ③ 自定义 Caffeine 配置类(可选步骤)

    如果须要更简单的配置,能够创立一个 Caffeine CacheManager 的配置类:

    import com.github.benmanes.caffeine.cache.Cache;
    import com.github.benmanes.caffeine.cache.Caffeine;
    import org.springframework.cache.CacheManager;
    import org.springframework.cache.annotation.CachingConfigurerSupport;
    import org.springframework.cache.interceptor.CacheResolver;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class CaffeineCacheConfig extends CachingConfigurerSupport {
    
      @Bean
      public CacheManager cacheManager() {Caffeine<Object, Object> caffeine = Caffeine.newBuilder()
                  .initialCapacity(100)
                  .maximumSize(1000)
                  .expireAfterWrite(10, TimeUnit.SECONDS) // 10 秒后过期
                  .recordStats(); // 记录缓存统计信息
    
          return new CaffeineCacheManager("default", caffeine::build);
      }
    
      @Override
      public CacheResolver cacheResolver() {
          // 自定义缓存解析器(如果须要)// ...
          return super.cacheResolver();}
    }

    ④ 开启缓存

    若要利用 Spring Cache 形象层,以便通过注解的形式更不便地治理缓存,须要在启动类上增加 @EnableCaching 注解,如下所示:

    import org.springframework.cache.annotation.EnableCaching;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    @EnableCaching
    public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);
      }
    }

    ⑤ 应用注解进行缓存操作

    在业务逻辑类中应用 @Cacheable、@CacheEvict 等注解实现数据的缓存读取和更新,和下面分布式缓存的应用雷同,具体示例如下:

    import org.springframework.cache.annotation.Cacheable;
    import org.springframework.stereotype.Service;
    
    @Service
    public class UserService {@Cacheable(value = "users", key = "#id") // 假如咱们有一个名为 "users" 的缓存区域
      public User getUserById(Long id) {
          // 这里是实在的数据库查问或其余耗时操作
          return userRepository.findById(id).orElse(null);
      }
    
      @CacheEvict(value = "users", key = "#user.id")
      public void updateUser(User user) {userRepository.save(user);
      }
    }

    课后思考

    除了以上的缓存之外,还有哪些缓存能够减速程序的执行效率呢?

本文已收录到我的面试小站 www.javacn.site,其中蕴含的内容有:Redis、JVM、并发、并发、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、设计模式、音讯队列等模块。

正文完
 0