乐趣区

关于缓存:缓存中的服务详解SpringBoot中二级缓存服务实现

创立缓存服务

创立缓存服务接口我的项目

  • 创立 myshop-service-redis-api 我的项目, 该我的项目只负责定义接口
  • 创立我的项目的pom.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <parent>
          <groupId>com.oxford</groupId>
          <artifactId>myshop-dependencies</artifactId>
          <version>1.0.0-SNAPSHOT</version>
          <relativePath>../myshop-dependencies/pom.xml</relativePath>
      </parent>
    
      <artifactId>myshop-service-redis-api</artifactId>
      <packaging>jar</packaging>
    </project>
  • 定义数据 Redis 接口RedisService:

    package com.oxford.myshop.service.redis.api
    
    public interface RedisService{void set(String key,Object value);
      
      void set(String key,Object value,int seconds);
    
      void del(String key);
    
      Object get(String key);
    }

    创立缓存服务提供者我的项目

  • 创立 myshop-service-redis-provider 我的项目, 该我的项目用作缓存服务提供者
  • 创立我的项目的pom.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <parent>
          <groupId>com.oxford</groupId>
          <artifactId>myshop-dependencies</artifactId>
          <version>1.0.0-SNAPSHOT</version>
          <relativePath>../myshop-dependencies/pom.xml</relativePath>
      </parent>
    
      <artifactId>myshop-service-redis-api</artifactId>
      <packaging>jar</packaging>
    
      <dependencies>
          <!-- Spring Boot Starter Settings-->
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-data-redis</artifactId>
          </dependency>
    
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-test</artifactId>
              <scope>test</scope>
          </dependency>
    
    
          <!--Common Setting-->
          <dependency>
              <groupId>org.apache.commons</groupId>
              <artifactId>commons-pool2</artifactId>
          </dependency>
          <dependency>
              <groupId>de.javakaffee</groupId>
              <artifactId>kryo-serializers</artifactId>
          </dependency>
    
          <!--Project Settings-->
          <dependency>
              <groupId>com.oxford</groupId>
              <artifactId>my-shop-commons-dubbo</artifactId>
              <version>${Project.parent.version}</version>
          </dependency>
          <dependency>
              <groupId>com.oxford</groupId>
              <artifactId>my-shop-service-redis-api</artifactId>
              <version>${Project.parent.version}</version>
          </dependency>
      </dependencies>
    
      <build>
          <plugins>
              <plugin>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-maven-plugin</artifactId>
                  <configuration>
                      <mainClass>com.oxford.myshop.service.redis.provider.MyshopServiceRedisProviderApplication</mainClass>
                  </configuration>
              </plugin>
          </plugins>
      </build>
    </project>

    Redis 底层实现的 Java 的 lettuce 客户端

  • 创立缓存服务接口实现类RedisServiceImpl

    package com.oxford.myshop.service.redis.provider.api.impl;
    
    @Service(version="${service.versions.redis.v1}")
    public class RedisServiceImpl implements RedisService{
      
      @Override
      public void set(String key,Object value){redisTemplate.opsForValue().set(key,value);
      }
    
      @Override
      public void set(String key,Object value,int seconds){redisTemplate.opsForValue().set(key,value,seconds,TimeUnit.SECONDS);
      }
    
      @Override
      public void del(String key){redisTemplate.delete(key);
      }
    
      @Override
      public Object get(String key){return redisTemplate.opsForValue().get(key);
      }
    }
  • 创立启动类SpringBootApplication

    package com.oxford.myshop.service.redis.provider;
    
    import com.alibaba.dubbo.container.Main;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.hystrix.EnableHystrix;
    import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
    
    
    
    @EnableHystrix
    @EnableHystrixDashboard
    public class MyShopServiceRedisrProviderApplication {public static void main(String[]args) {SpringApplication.run(MyShopServiceRedisProviderApplication.class,args);
          Main.main(args);
      }
    }
  • 创立配置文件application.yml

    spring:
    application:
      name: myshop-service-redis-provider
    redis:
        lettuce:
        pool:
            max-active: 8
            max-idle: 8
            max-wait: -1ms
            min-idle: 0
      sentinel:
        master: mymaster
        nodes: 192.168.32.255:26379,192.168.32.255:26380,192.168.32.255:26381
    server:
    port: 8503
    
    services:
    version:
      redis:
          v1: 1.0.0
      user:
        v1: 1.0.0
    
    dubbo:
    scan:
      basePackages: com.oxford.myshop.service.redis.provider.api.impl
    application:
      id: com.oxford.myshop.service.redis.provider.api
      name: com.oxford.myshop.service.redis.provider.api
      qos-port: 22224
      qos-enable: true
    protocal:
      id: dubbo
      name: dubbo
      port: 20883
      status: server
      serialization: kryo
    
    regitry:
      id: zookeeper
      address: zookeeper://localhost:2181?backup=192.168.32.255:2182,192.168.32.255:2183
    
    management:
    endpoint:
      dubbo:
        enabled: true
      dubbo-shutdown:
        enabled: true
      dubbo-configs:
        enabled: true
      dubbo-sevicies:
        enabled: true
      dubbo-reference:
        enabled: true
      dubbo-properties:
        enabled: true
    health:
      dubbo:
        status:
          defaults: memory
          extras: load,threadpool

    创立缓存服务消费者我的项目

  • 在 pom 文件中引入 redis 接口依赖
  • 在缓存服务消费者我的项目的 ServiceImpl 中调用 RedisService

    @Reference(version="services.versions.redis.v1")
    private RedisService redisService;

    MyBatis Redis 二级缓存

    MyBatis 缓存

  • 一级缓存:

    • MyBatis 会在示意会话的 SqlSession 对象中建设一个简略的缓存: 将每次查问到的后果缓存起来, 当下次查问的时候, 如果判断先前有个齐全一样的查问, 会间接从缓存中间接将后果取出, 返回给用户, 不须要再进行一次数据库查问
    • 一级缓存是 SqlSession 级别的缓存:

      • 在操作数据库时须要结构 SqlSession 对象
      • 对象中有一个 (内存区域) 数据结构 (HashMap) 用于存储缓存数据
      • 不同的 SqlSession 之间的缓存数据区域 (HashMap) 互不影响,
      • 一级缓存的作用域是同一个 SqlSession
      • 在同一个 SqlSession 中两次执行雷同的 SQL 语句: 第一次执行结束会将数据库中查问的数据写到缓存(内存), 第二次会从缓存中获取数据, 将不再从数据库查问, 从而进步查问效率
      • 当一个 SqlSession 完结后该 SqlSession 中的一级缓存就不存在了
      • MyBatis 默认开启一级缓存
  • 二级缓存:

    • 二级缓存是 Mapper 级别的缓存: 多个 SqlSession 去操作同一个 Mapper 的 SQL 语句, 多个 SqlSession 去操作数据库失去数据会存在二级缓存区域, 多个 SqlSession 能够共用二级缓存, 二级缓存是跨 SqlSession 的
    • 二级缓存的作用域是 mapper 的同一个 namespace
    • 不同的 SqlSession 两次执行雷同 namespace 下的 SQL 语句且向 SQL 中传递参数也雷同即最终执行雷同的 SQL 语句: 第一次执行结束会将数据库中查问的数据写到缓存(内存), 第二次会从缓存中获取数据将不再从数据库查问, 从而进步查问效率
    • MyBatis 默认没有开启二级缓存, 须要在 setting 全局参数中配置开启二级缓存

      配置 MyBatis 二级缓存

      SpringBoot 中开启 MyBatis 二级缓存
  • 在 myshop-service-user-provider 的配置文件中开启 MyBatis 二级缓存

    spring:
    application:
      name: myshop-service-user-provider
    datasource:
      druid:
        url: jdbc:mysql://localhost:3306/myshop?useUnicode=true&characterEncoding=utf-8&useSSL=false
        username: root
        password: 123456
        initial-size: 1
        min-idle: 1
        main-active: 20
        test-on-borrow: true
        driver-class-name: com.mysql.cj.jdbc.Driver
    redis:
        lettuce:
        pool:
            max-active: 8
            max-idle: 8
            max-wait: -1ms
            min-idle: 0
      sentinel:
        master: mymaster
        nodes: 192.168.32.255:26379,192.168.32.255:26380,192.168.32.255:26381
    
    server:
    port: 8501
    
    # MyBatis Config properties
    mybatis:
    configuration:
        cache-enabled: true
    type-aliases-package: com.oxford.myshop.commons.domain
    mapper-location: classpath:mapper/*.xml
    
    services:
    version:
        redis:
          v1: 1.0.0
      user:
        v1: 1.0.0
    
    dubbo:
    scan:
      basePackages: com.oxford.myshop.service.user.provider.api.impl
    application:
      id: com.oxford.myshop.service.user.provider.api
      name: com.oxford.myshop.service.user.provider.api
      qos-port: 22222
      qos-enable: true
    protocal:
      id: dubbo
      name: dubbo
      port: 20001
      status: server
      serialization: kryo
    
    regitry:
      id: zookeeper
      address: zookeeper://localhost:2181?backup=192.168.32.255:2182,192.168.32.255:2183
    
    management:
    endpoint:
      dubbo:
        enabled: true
      dubbo-shutdown:
        enabled: true
      dubbo-configs:
        enabled: true
      dubbo-sevicies:
        enabled: true
      dubbo-reference:
        enabled: true
      dubbo-properties:
        enabled: true
    health:
      dubbo:
        status:
          defaults: memory
          extras: load,threadpool
  • 在 myshop-commons-mapper 的 pom.xml 中减少 redis 依赖:

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifacted>
    </dependency>
    
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-pool2</artifacted>
    </dependency>
    实体类实现序列化接口并申明序列号
    private static final long serialVersionUID = 82897704415244673535L
  • 应用 GenerateSerialVersionUID 插件生成, 装置完插件后在实现了序列化接口的类中
  • 应用快捷键 Alt+Insert 即可呼出生成菜单, 即可主动生成序列号

  • 在 myshop-commons 我的项目中创立 ApplicationContextHolder 类

    package com.oxford.myshop.commons.context;
    
    @Component
    public class ApplicationContextHolder implements ApplicationContextAware,DisposableBean{private static final Logger logger=LoggerFactory.getLogger(ApplicationContext.class);
    
      private static ApplicationContext applicationContext;
    
      /**
       * 获取存储在动态变量中的 ApplicationContext
       */
       public static ApplicationContext getApplicationContext(){assertContextInjected();
           return applicationContext;
       }
    
      /**
       * 从动态变量 applicationContext 中获取 Bean, 主动转型成所赋值对象的类型
       */
       public static <T> T getBean(String name){assertContextInjected();
           return (T) applicationContext.getBean(name);
       }
    
      /**
       * 从动态变量 applicationContext 中获取 Bean, 主动转型成所赋值对象的类型
       */
      public static <T> T getBean(Class<T> clazz){assertContextInjected();
           return (T) applicationContext.getBean(clazz);
       }
    
      /**
       * 实现 DisposableBean 接口, 在 Context 敞开时清理动态变量
       */
       public void destroy() throws Exception{logger.debug("革除 SpringContext 中的 ApplicationContext: {}",applicationContext);
           applicationContext=null;
       }
    
      /**
       * 实现 ApplicationContextAware 接口, 注入 Context 到动态变量中
       */
       public void setApplicationContext(ApplicationContext applicationContext) throws BeanException{ApplicationContext.applicationContext=applicationContext;}
    
      /**
       * 断言 Context 曾经注入
       */
       private static void assertContextInjected(){Validate.validState(applicationContext !=null,"applicationContext 属性未注入, 请在配置文件中配置定义 ApplicationContextContext");
      }
    }
  • 在 myshop-commons-mapper 我的项目中创立一个 RedisCache 的工具类

    package com.oxford.myshop.commons.utils;
    
    public class RedisCache implements Cache{private static final Logger logger=LoggerFactory.getLogger(RedisCache.class);
    
      private final ReadWriteLock readWriteLock=new ReentranReadWriteLock();
      private final String id;
      private RedisTemplate redisTemplate;
    
      private static final long EXPIRE_TIME_IN_MINUTES=30        // redis 过期工夫
    
      public RedisCache(String id){if(id==null){throw new IllegalArgumentException("Cache instances require an ID");
          }
          this.id=id;
      }
    
      @Override
      public String getId(){return id;}
    
      /**
       * Put query result to redis 
       */
      @Override
      public void putObject(Object key,Object value){
          try{RedisTemplate redisTemplate=getRedisTemplate();
              ValueOperations opsForValue=redisTemplate.opsForValue();
              opsForValue.set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
              logger.debug("Put query result to redis");
          }catch(Throwable t){logger.error("Redis put failed",t);
          }
      }
    
      /**
       * Get cached query result from redis 
       */
       @Override
       public Object getObject(Object key){
          try{RedisTemplate redisTemplate=getRedisTemplate();
              ValueOperations opsForValue=redisTemplate.opsForValue();
              opsForValue.set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
              logger.debug("Get cache query result from redis");
              return opsForValue.get(key);
          }catch(Throwable t){logger.error("Redis get failed, fail over to db");
              return null;
          }
      }
    
      /**
       * Get cached query result from redis 
       */
       @Override
       public Object getObject(Object key){
          try{RedisTemplate redisTemplate=getRedisTemplate();
              ValueOperations opsForValue=redisTemplate.opsForValue();
              opsForValue.set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
              logger.debug("Get cache query result from redis");
              return opsForValue.get(key);
          }catch(Throwable t){logger.error("Redis get failed, fail over to db");
              return null;
          }
      }
    
      /**
       * Remove cached query result from redis 
       */
       @Override
       @SuppressWarnings("unchecked")
       public Object removeObject(Object key){
          try{RedisTemplate redisTemplate=getRedisTemplate();
              redisTemplate.delete(key);
              logger.debug("Remove cached query result from redis");
          }catch(Throwable t){logger.error("Redis remove failed");
          }
              return null;
      }
    
      /**
       * Clear this cache instance
       */
       @Override
       public void clear(){RedisTemplate redisTemplate=getRedisTemplate();
           redisTemplate.execute((RedisCallback)->{connection.flushDb();
               return null;
           });
           logger.debug("Clear all the cached query result from redis");
       }
    
      @Override
      public int getSize(){return 0;}
      
      @Override
      public ReadWriteLock getReadWriteLock(){return readWriteLock;}
    
      private RedisTemplate getRedisTemplate(){if(redisTemplate==null){redisTemplate=ApplicationContextHolder.getBean("redisTemplate");
          }
          return redisTemplate;
      }
    }
    Mapper 接口类中标注注解
  • 在 Mapper 接口类上标注注解, 申明应用二级缓存

    @CacheNamespace(implementation=RedisCache.class)
退出移动版