Redis单机缓存

和Ehcache一样,如果在classpath下存在Redis并且Redis已经配置好了,此时默认就会使用
RedisCacheManager作为缓存提供者。Redis 单机使用步骤如下。

1. 创建项目,添加缓存依赖

创建Spring Boot项目,添加spring-boot-starter-cache和Redis依赖,代码如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
        <exclusion>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

2.缓存配置

Redis单机缓存只需要开发者在application.properties中进行Redis配置及缓存配置即可,代码
如下:

#缓存配置
spring.cache.cache-names=c1,c2
spring.cache.redis.time-to-live=1800s
#redis配置
spring.redis.database=0
spring.redis.host=120.55.61.170
spring.redis.port=6379
spring.redis.password=123@456
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.max-wait=-1ms
spring.redis.jedis.pool.min-idle=0

代码解释:

  • 第2行是配置缓存名称。Redis 中的key都有一个前缀,默认前缀就是“缓存名::”
  • 第3行配置缓存有效期,即Redis中key的过期时间。
  • 第5~12行是Redis配置,具体含义可以看我的redis整合springboot的博文。

3.开启缓存

接下来在项目入口类中开启缓存,代码如下:

@EnableCaching
@SpringBootApplication
public class TestforeverApplication {
    public static void main(String[] args) {
        SpringApplication.run(TestforeverApplication.class, args);
    }
}

最后创建BookDao和测试的步骤与的第4、5步一致, 这里就不再赘述。

4. 创建BookDao

创建Book实体类和BookService,代码如下:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book implements Serializable {
    private Integer id;
    private String name;
    private String author;
}
@Repository
@CacheConfig(cacheNames = "book_cache")
public class BookDao {
    @Cacheable
    public Book getBookById(Integer id) {
        System.out.println("getBookById");
        Book book = new Book();
        book.setId(id);
        book.setAuthor("罗贯中");
        book.setName("三国演义");
        return book;
    }
    @CachePut(key = "#book.id")
    public Book updateBookById(Book book){
        System.out.println("updateBookById");
        book.setName("三国演义2");
        return book;
    }
    @CacheEvict(key = "#id")
    public void deleteBookById(Integer id){
        System.out.println("deleteBookById");
    }
}

代码解释:

  • 在BookDao. 上添加@CacheConfig 注解指明使用的缓存的名字,这个配置可选,若不使用@CacheConfig注解,则直接在@Cacheable注解中指明缓存名字。
  • 第4 行在getBookById方法.上添加@Cacheable注解表示对该方法进行缓存,默认情况下,缓存的key是方法的参数,缓存的value是方法的返回值。当开发者在其他类中调用该方法时,首先会根据调用参数查看缓存中是否有相关数据,若有,则直接使用缓存数据,该方法不会执行,否则执行该方法,执行成功后将返回值缓存起来,但若是在当前类中调用该方法,则缓存不会生效。
  • @Cacheable注解中还有一个属性condition用来描述缓存的执行时机,例如.@Cacheable(condition = “#id%2==0”)表示当 id对2取模为0时才进行缓存,否则不缓存。
  • 如果开发者不想使用默认的key, 也可以像第13行和第19行一样自定义key, 第13行表示缓存的key为参数book对象中id的值,第19行表示缓存的key为参数id。除了这种使用参数定义key的方式之外,Spring 还提供了一个root对象用来生成key,如表所示。

使用root对象生成key

属性名称属性描述用法示例
methodName当前方法名#root.methodName
method当前方法对象#root.method.name
caches当前方法使用的缓存#root.cache[0].name
target当前被调用的对象#root.target
targetClass当前被调用的对象的Class#root.targetClass
args当前方法参数数组#root.args[0]
  • 如果这些key不能够满足开发需求,开发者也可以自定义缓存key的生成器KeyGenerator,代码如下:
@Component
public class MyKeyGenerator implements KeyGenerator {
    @Override
    public Object generate(Object target, Method method, Object... params) {
        return Arrays.toString(params);
    }
}
@Repository
@CacheConfig(cacheNames = "book_cache")
public class BookDao {
    @Autowired
    MyKeyGenerator myKeyGenerator;
    @Cacheable(keyGenerator = "myKeyGenerator")
    public Book getBookById(Integer id) {
        System.out.println("getBookById");
        Book book = new Book();
        book.setId(id);
        book.setAuthor("罗贯中");
        book.setName("三国演义");
        return book;
    }
}
  • 自定义MyKeyGenerator实现KeyGenerator接口,然后实现该接口中的generate方法,该方法的三个参数分别是当前对象、当前请求的方法以及方法的参数,开发者可根据这些信息组成一个新的key 返回,返回值就是缓存的key。 第5行在@Cacheable注解中引用MyKeyGenerator实例即可。
  • 第13行@CachePut注解一般用于数据更新方法上,与@Cacheable注解不同,添加了@CachePut注解的方法每次在执行时都不去检查缓存中是否有数据,而是直接执行方法,然后将方法的执行结果缓存起来,如果该key对应的数据已经被缓存起来了,就会覆盖之前的数据,这样可以避免再次加载数据时获取到脏数据。同时,@CachePut 具有和@Cacheable类似的属性,这里不再赘述。
  • 第19行@CacheEvict注解一般用于删除方法上,表示移除一个key对应的缓存。@CacheEvict注解有两个特殊的属性: allEntries 和beforeInvocation,其中allEntries 表示是否将所有的缓存数据都移除,默认为false, beforeInvocation 表示是否在方法执行之前移除缓存中的数据,默认为false,即在方法执行之后移除缓存中的数据。

5. 创建测试类

创建测试类,对Service中的方法进行测试,代码如下:

@SpringBootTest
class TestforeverApplicationTests {

    @Resource
    BookDao bookDao;

    @Test
    public void contextLoads() {
        bookDao.getBookById(1);
        bookDao.getBookById(1);
        bookDao.deleteBookById(1);
        Book bookById = bookDao.getBookById(1);
        System.out.println(bookById);
        Book book = new Book();
        book.setName("张三日记");
        book.setAuthor("罗翔");
        book.setId(1);
        bookDao.updateBookById(book);
        Book book1 = bookDao.getBookById(1);
        System.out.println(book1);
    }

}

执行该方法,控制台打印日志如图所示。

getBookById
deleteBookById
getBookById
Book(id=1, name=三国演义, author=罗贯中)
updateBookById
Book(id=1, name=三国演义2, author=罗翔)

Redis集群缓存

不同于Redis单机缓存,Redis 集群缓存的配置要复杂一些,主要体现在配置上,缓存的使用还是和前面介绍的一样。搭建Redis集群缓存主要分为三个步骤:

  1. 搭建Redis集群;
  2. 配置缓存;
  3. 使用缓存。

下面按照这三个步骤向读者介绍Redis集群缓存的搭建过程。

1. 搭建Redis集群

Redis集群的搭建过程在我的其他博文已经介绍过了,本案例中采用的Redis集群案例和搭建的Redis集群一致,都是8台Redis实例,4主4从,端口从7001到7008,具体搭建过程,这里就不赘述了,读者可以参考6.1.4小节。Redis 集群搭建成功后,通过Spring Data Redis连接Redis集群,这一段配置也和前面的一致,因此这里也不赘述。总之,读者需要参考前面的博文先将Redis集群搭建成功,并且能够在Spring Boot中通过RedisTemplate访问成功。

2. 配置缓存

当Redis集群搭建成功,并且能够从Spring Boot项目中访问Redis 集群后,只需要进行简单的Redis缓存配置即可,代码如下:

@Configuration
public class RedisCacheConfig {
    @Autowired
    RedisConnectionFactory conFactory;

    @Bean
    RedisCacheManager redisCacheManager() {
        Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .prefixCacheNameWith("sang")
                .disableCachingNullValues()
                .entryTtl(Duration.ofMillis(30000));
        configMap.put("c1", redisCacheConfiguration);
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.lockingRedisCacheWriter(conFactory);
        RedisCacheManager redisCacheManager=new RedisCacheManager(redisCacheWriter,RedisCacheConfiguration.defaultCacheConfig(),configMap);
        return redisCacheManager;
    }
}

代码解释:

  • 在配置Redis集群时,已经向Spring容器中注册了一个JedisConnectionFactory的实例,这里将之注入到 RedisCacheConfig 配置文件中备用(RedisConnectionFactory 是JedisConnectionFactory的父类)。
  • 在RedisCacheConfig中提供RedisCacheManager的实例,该实例的构建需要三个参数,第一个参数是一个cacheWriter, 直接通过lockingRedisCacheWriter 方法构造出来即可;第二个参数是默认的缓存配置;第三个参数是提前定义好的缓存配置。
  • RedisCacheManager构造方法中第三个参数是一个提前定义好的缓存参数,它是一个Map类型的参数,该Map中的key就是指缓存名字,value就是该名称的缓存所对应的缓存配置,例如key的前缀、缓存过期时间等,若缓存注解中使用的缓存名称不存在于Map中,则使用RedisCacheManager构造方法中第二个参数所定义的缓存策略进行数据缓存。例如如下两个缓存配置:
@Cacheable(value="c1")
@Cacheable(value="c1")
  • 第1行的注解中,c1存在于configMap 集合中,因此使用的缓存策略是configMap集合中c1所对应的缓存策略,c2不存在于configMap集合中,因此使用的缓存策略是默认的缓存策略。

  • 本案例中默认缓存策略通过调用RedisCacheConfiguration 中的defaultCacheConfig方法获取,该方法部分源码如下:

public static RedisCacheConfiguration defaultCacheConfig(@Nullable ClassLoader classLoader) {
	DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
	registerDefaultConverters(conversionService);
	return new RedisCacheConfiguration(Duration.ZERO, true, true, CacheKeyPrefix.simple(),
			SerializationPair.fromSerializer(RedisSerializer.string()),
			SerializationPair.fromSerializer(RedisSerializer.java(classLoader)), conversionService);
}
  • 由这一段源码可以看到,默认的缓存过期时间为0,即永不过期;第二个参数true表示允许存储null,第三个参数true表示开启key的前缀,第四个参数表示key的默认前缀是“缓存名::”,接下来两个参数表示key和value的序列化方式,最后一个参数则是一个类型转换器。
  • 本案例中第8~12行是一个自定义的缓存配置,第10行设置了key的前缀为“sang:", 第11行禁止缓存一个null,第12行设置缓存的过期时间为30分钟。

3.开启缓存

接下来在项目入口类中开启缓存,代码如下:

@EnableCaching
@SpringBootApplication
public class TestforeverApplication {
    public static void main(String[] args) {
        SpringApplication.run(TestforeverApplication.class, args);
    }
}

然后创建一个BookDao使用缓存,代码如下:

@Repository
public class BookDao {
    @Cacheable(value = "c1")
    public String getBookById(Integer id) {
        System.out.println("getBookById");
        return "这本书是三国演义";
    }

    @CachePut(value = "c1")
    public String updateBookById(Integer id) {
        System.out.println("updateBookById");
        return "这是一本全新的三国演义";
    }

    @CacheEvict(value = "c1")
    public void deleteBookById(Integer id) {
        System.out.println("deleteBookById");
    }

    @Cacheable(value = "c2")
    public String getBookById2(Integer id) {
        System.out.println("getBookById");
        return "这本书是红楼梦";
    }
}

最后创建单元测试,代码如下:

@SpringBootTest
class TestforeverApplicationTests {

    @Resource
    BookDao bookDao;

    @Test
    public void contextLoads() {
        bookDao.getBookById(100);
        String book = bookDao.getBookById(100);
        System.out.println(book);
        bookDao.updateBookById(100);
        String book2 = bookDao.getBookById(100);
        System.out.println(book2);
        bookDao.deleteBookById(100);
        bookDao.getBookById(100);
        bookDao.getBookById2(88);
    }
}

单元检测结果如下

getBookById
这本书是三国演义
updateBookById
这是一本全新的三国演义
deleteBookById
getBookById

由单元测试可以看到,一开始做了两次查询,但是查询方法只调用了一-次,因为第二次使用了缓存;接下来执行了更新,当更新成功后再去查询,此时缓存也已更新成功;接下来执行了删除,删除成功后再去执行查询,查询方法又被调用,说明缓存也已经被删除了;最后查询了一个id为99的记录,这次使用的是默认缓存配置。在Redis服务器上也可以看到缓存结果,如下所示。

120.55.61.170:7002> ttl sangc1::100
(integer) 28
120.55.61.170:7002> get sangc1::100
"\xac\xed\x00\x05t\x00\x18\xe8\xbf\x99\xe6\x9c\xac\xe4\xb9\xa6\xe6\x98\xaf\xe4\xb8\x89\xe5\x9b\xbd\xe6\xbc\x94\xe4\xb9\x89"
127.0.0.1:7001> get c2::99
"\xac\xed\x00\x05t\x00\x15\xe8\xbf\x99\xe6\x9c\xac\xe4\xb9\xa6\xe6\x98\xaf\xe7\xba\xa2\xe6\xa5\xbc\xe6\xa2\xa6"
127.0.0.1:7001> ttl c2::99
(integer) -1

id为100的记录使用的缓存名为c1,因此key的前缀是“sangc1:",这是上文配置的,过期时间还剩28秒(. 上文配置的过期时间是30秒) ;而id为99的记录使用的缓存名称为c2,因此使用了默认的缓存配置,默认的前缀为“缓存名:”,即“c2::”, 默认的过期时间是永不过期。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐