美文网首页
SpringCache使用Redis来缓存热点数据

SpringCache使用Redis来缓存热点数据

作者: 砒霜拌辣椒 | 来源:发表于2020-08-10 16:34 被阅读0次

在很多应用场景中通常是获取前后相同或更新不频繁的数据,比如访问产品信息数据、网页数据。如果没有使用缓存,则访问每次需要重复请求数据库,这会导致大部分时间都耗费在数据库查询和方法调用上,因为数据库进行I/O操作非常耗费时间,这时就可以利用Spring Cache来解决。

Spring Cache 是Spring提供的一整套缓存解决方案。它本身并不提供缓存实现,而是提供统一的接口和代码规范、配置、注解等,以便整合各种Cache方案,使用户不用关心Cache的细节。

Spring Cache作用在方法上。当调用一个缓存方法时,会把方法参数和返回结果作为一个“键值对”(key/value)存放在缓存中,下次用同样的参数来调用该方法时将不再执行该方法,而是直接从缓存中获取结果进行返回。所以在使用Spring Cache时,要保证在缓存的方法和方法参数相同时返回相同的结果。

1、声明式注解

注解 说明
@EnableCaching 启动类声明,用来开启缓存
@Cacheable 可以作用在类和方法上,以键值对的方式缓存类或方法的返回值
@CachePut 方法被调用,然后结果被缓存
@CacheEvict 清空缓存
@Caching 用来组合多个注解标签
  • @Cacheable会先查询是否已有缓存,没有则再执行方法,将返回值缓存起来,key可以有默认策略和自定义策略。用于查询热点数据。
  • @CachePut每次都会执行方法,并将方法的返回值缓存。用于更新数据。

2、Maven依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.4.RELEASE</version>
</parent>
<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.10</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.3.2</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
</dependencies>

3、应用缓存

3.1、Redis配置类

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
    /**
     * 缓存管理器,适合2.x版本
     *
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        // 对于指定的cacheNames设置缓存的过期时长
        Map<String, RedisCacheConfiguration> cacheConfigMap = new HashMap<>();
        cacheConfigMap.put("users", this.getCacheConfigurationWithTtl(2 * 60 * 60));

        // 构建CacheManager对象,指定的cacheNames过期时长为2小时,其它默认为1小时
        return RedisCacheManager.builder(redisConnectionFactory)
                .withInitialCacheConfigurations(cacheConfigMap)
                .cacheDefaults(this.getCacheConfigurationWithTtl(1 * 60 * 60))
                .build();
    }

    /**
     * 设置缓存过期时长
     *
     * @param seconds 过期时长
     * @return
     */
    private RedisCacheConfiguration getCacheConfigurationWithTtl(long seconds) {
        // 序列化设置
        Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会抛出异常
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); // 2.1.x
//        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL); // 2.2.x
        jacksonSeial.setObjectMapper(om);

        // 基本配置
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration
                .defaultCacheConfig()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jacksonSeial))
                // 不缓存null
                .disableCachingNullValues()
                // 缓存数据保存时长
                .entryTtl(Duration.ofSeconds(seconds));
        return redisCacheConfiguration;
    }
}

这里主要配置了序列化方式和对于不同缓存的过期时长设置。

3.2、数据层

建表语句

DROP TABLE IF EXISTS user;
CREATE TABLE user (
    id bigint(20) NOT NULL AUTO_INCREMENT,
    name varchar(64) COMMENT '姓名',
    age int(4) COMMENT '年龄',
    PRIMARY KEY (id)
)COMMENT = '用户表';

insert into user values (1, '赵晓斌', 28);
insert into user values (2, '李白', 22);
insert into user values (3, '宋老三', 30);

实体类,一定要实现Serializable接口。

@Data
public class User implements Serializable {
    private static final long serialVersionUID = 5485617646232613710L;
    private Long id;
    private String name;
    private int age;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return age == user.age &&
                Objects.equals(id, user.id) &&
                Objects.equals(name, user.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name, age);
    }
}

缓存层接口

public interface UserCacheService {
    User findById(Long id);
    User updateUserById(User user);
    void deleteById(Long id);
}

这里采用Mybatis来操作数据库,具体操作和配置不在这里展开。

3.3、缓存查询数据

@Service
@CacheConfig(cacheNames = "users") // 缓存名称,也是key前缀
public class UserCacheServiceImpl implements UserCacheService {
    @Autowired
    private UserDao userDao;

    @Override
    @Cacheable(key = "#p0") // 取第一个参数作为key的一部分,比如users::1
    public User findById(Long id) {
        return userDao.findById(id);
    }
}

根据key值查询缓存,如果没有则执行方法查询数据库,将结果缓存起来,下次同样的key查询时直接从缓存取值返回,不再执行方法。

  • @CacheConfig注解指定缓存key的前缀为users
  • @Cacheable等注解指定key时,使用SpEL表达式:
    • #p0:表示取第一个参数。
    • #p0.id:表示取第一个参数的id属性。
    • #id:表示取参数id。

3.4、更新缓存数据

@Override
@CachePut(key = "#p0.id") // 访问bean属性
@Transactional
public User updateUserById(User user) {
    userDao.updateUserById(user);
    return user;
}

每次更新完数据库后,同步更新缓存中的数据。需要保持和缓存时的key相同。

3.5、删除缓存数据

@Override
@CacheEvict(key = "#id") // 可以用形参名表示key,allEntries = true 则代表 users:: 开头的键全部删除
@Transactional
public void deleteById(Long id) {
    userDao.deleteById(id);
}

在删除完数据库的数据后,同步删除缓存中的数据。也是需要保持和缓存时的key相同。
如果需要批量删除某一类key,只需要把@CacheEvict注解中的allEntries属性设为true,那就会清空所有缓存数据(仅限于同一个cacheNames也就是@CacheConfig注解指定的key前缀)。

4、测试验证

@RestController
public class CacheController {
    @Autowired
    private UserCacheService userCacheService;

    @RequestMapping("/findById")
    public User findById(Long id) {
        return userCacheService.findById(id);
    }

    @RequestMapping("/updateUserById")
    public void updateUserById(User user) {
        userCacheService.updateUserById(user);
    }

    @RequestMapping("/deleteById")
    public void deleteById(Long id) {
        userCacheService.deleteById(id);
    }
}
  1. 在第一次查询时,可以看到有查询数据库的日志输出。
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@608e7bd3] was not registered for synchronization because synchronization is not active
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4945f51a] will not be managed by Spring
==>  Preparing: select id, name, age from user where id = ? 
==> Parameters: 3(Long)
<==    Columns: id, name, age
<==        Row: 3, ares, 33
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@608e7bd3]
  1. 查询Redis,可以看到已经有数据缓存了,过期时间就是前面配置中设置的2小时。
127.0.0.1:9427> keys *
users::3
127.0.0.1:9427> get users::3
["net.zhaoxiaobin.cache.domain.User",{"id":3,"name":"ares","age":22}]
127.0.0.1:9427> ttl users::3
7185
  1. 再次查询,发现依旧可以得到相同结果,但没有查询数据库的日志输出,说明缓存生效了。

  2. 修改字段更新数据库,然后再查询,可以看到缓存数据也被更新。

  3. 删除一条数据后,再根据id查询就得到空,说明缓存中的这条数据也已被删除了。

参考链接

代码地址

相关文章

  • SpringCache使用Redis来缓存热点数据

    在很多应用场景中通常是获取前后相同或更新不频繁的数据,比如访问产品信息数据、网页数据。如果没有使用缓存,则访问每次...

  • redis配置文件

    redis配置优化 整合springCache的文章 redis配置 Redis数据库索引(缓存将使用此索引编号的...

  • SpringCache踩坑记

    SpringCache配合Redis使用缓存. 完整配置在最后 目的:使用注解形式优雅地序列化数据到redis中,...

  • 如何用redis做缓存

    redis缓存 在互联网应用中经常需要用redis来缓存热点数据。 redis数据在内存,可以保证数据读取的高效,...

  • 缓存解决方案-简单记录

    1、springcache:SpringBoot基础系列-SpringCache使用 - 简书 2、缓存击穿和缓存...

  • [Springboot]SpringCache + Redis实

    前言 本文实现了SpringCache + Redis的集中式缓存,方便大家对学习了解缓存的使用。 本文实现: S...

  • MI 2021-07-09

    一面 问的设计题偏多 Redis 缓存热点数据 热点数据指的是什么? Redis缓存数据量关注过吗? 如果全部缓存...

  • 多级缓存之二:本地缓存Guava

    接上一篇Redis集中式缓存应用,作为缓存的数据库中间件redis的集中式缓存管理。下面使用Guava进行热点数据...

  • 【Redis篇】【常用应用场景】

    一.缓存 热点数据(经常会被查询,但是不经常被修改或者删除的数据),首选是使用redis缓存.热点数据如果没次都需...

  • Redis 缓存穿透,缓存击穿,缓存雪崩

    Redis 最常用的工能就是作为缓存使用, 我们可以把热点数据保存在redis数据库中,以此来减轻关系型数据库的压...

网友评论

      本文标题:SpringCache使用Redis来缓存热点数据

      本文链接:https://www.haomeiwen.com/subject/yvpjdktx.html