4.1 JVM进程缓存

一、传统缓存的问题

  1. 传统的缓存策略一般是请求到达Tomcat后,先查Redis,如果未命中,则查询数据库,存在如下问题:

    1. 请求要经过Tomcat处理,Tomcat的性能称为整个系统的瓶颈

    2. Redis缓存失效时,会对数据库产生冲击

  2. 多级缓存:就是充分利用请求处理的每一个环节,分别添加缓存,减轻Tomcat压力,提升服务性能

    多级缓存示例

    备注:可以将nginx与Redis调用的服务做成集群,前面再加一个nginx做反向代理

    优化后的多级缓存

  3. 反向代理配置:

    http {
        include       mime.types;
        default_type  application/octet-stream;
    
        sendfile        on;
        #tcp_nopush     on;
        keepalive_timeout  65;
        
    	# nginx的业务集群,nginx本地缓存
        upstream nginx-cluster{
            server 192.168.150.101:8081;
        }
        
        # 监听地址+端口
        server {
            listen       80;
            server_name  localhost;
    	
    	# 反向代理
    	location /api {
                proxy_pass http://nginx-cluster;
            }
    
            location / {
                root   html;
                index  index.html index.htm;
            }
    
            error_page   500 502 503 504  /50x.html;
            location = /50x.html {
                root   html;
            }
        }
    }

二、初始Caffeine

  1. 缓存在日出厂开发中起到至关重要的作用,由于是存储在内存中,数据的读取速度是非常快的,能大量减少对数据库的访问,减少数据库的压力。我们吧缓存分为两类:

    1. 分布式缓存:例如Redis:

      1. 优点:存储容量更大、可靠性更好、可以在集群间共享

      2. 缺点:访问缓存有网络开销

      3. 场景:缓存数据量较大、可靠性要求较高、需要在集群间共享

    2. 进程本地缓存,例如HashMap、GuavaCache:

      1. 优点:读取本地内存,没有网络开销,速度更快

      2. 缺点:存储容量有限、可靠性较低、无法共享

      3. 场景:性能要求较高,缓存数据量较小

  2. Caffeine是一个基于Java8开发的,提供了近乎最佳命中的高性能本地缓存库。目前Spring内部的缓存使用的就是Caffeine,GitHubub地址:https://github.com/ben-manes/caffeine

  3. 简单示例:

    1. 引入坐标

              <dependency>
                  <groupId>com.github.ben-manes.caffeine</groupId>
                  <artifactId>caffeine</artifactId>
              </dependency>
    2. 代码示例

      public void testBasicOps() {
          // 创建缓存对象
          Cache<String, String> cache = Caffeine.newBuilder.build();
          
          // 存数据
          cache.put("ls", "LonelySnow");
          
          // 取数据,不存在返回null
          String ls = cache.getIfPresent("ls");
          System.out.print("ls = " + ls);
          
          // 取数据,不存在则去数据库查询
          String defaultLS = cache.get("defaultLS", key -> {
              // 这里可以去数据库中查询,根据key去查询,key的值就是defaultLS
              // 查询数据库的方法(参数-key)    
              return "SnowLonely";
          });
          System.out.print("defaultLS = " + defaultLS);
      }

      备注:cache.get过程中,如果不存在,直接调用数据库,,得到数据库的结果后,直接存入缓存,并返回

  4. Caffeine提供了三种缓存驱逐策略

    1. 基于容量:设置缓存的数量上限

      // 创建缓存对象
      Cache<String, String> cache = Caffeine.newBuilder()
          							.maximumSize(1)   // 设置缓存大小上限为1
          							.build();
    2. 基于时间:设置缓存的有效时间

      // 创建缓存对象
      Cache<String, String> cache = Caffeine.newBuilder()
          							.expireAfterWrite(Duration.ofSeconds(10)) // 设置缓存有效期为10秒,从最后一次写入时开始计时
          							.build();
    3. 基于引用:设置缓存为软引用或弱引用,利用那个GC来回收缓存数据。性能较差,不建议使用。

    默认情况下,当一个缓存元素过期的时候,Caffeine不会自动立即将其清理和驱逐。而是在一次读或写操作后,或在空闲时间完成对失效数据的驱逐

三、实现进程缓存

  1. 声明Bean配置

    @Configuration
    public class CaffeineConfig() {
        
        @Bean
        public Cache<Long, Item> itemCache() {   // Cache<>声明的key与value,跟实际情况定义
            return Caffeine.newBuilder()
                .initialCapacity(100)   // 初始化大小为100
                .maximumSize(10_000)   // 设置缓存上线为10000
                .build();
        }
        
        // 上面的Bean可以创建多个
        
    }
  2. 代码中写入

    // 在方法中注入对应的Bean
    @Autowired
    private Cache<Long, Item> itemCache;
    
    // 代码中就可以正常操作
    // 取数据,不存在则去数据库查询
    String defaultLS = cache.get("defaultLS", key -> {
        // 这里可以去数据库中查询,根据key去查询,key的值就是defaultLS
        // Service.查询数据方法(key);      
        return "SnowLonely";
    });

最后更新于

这有帮助吗?