V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
hansonwang99
V2EX  ›  Blogger

Guava Cache 本地缓存在 Spring Boot 应用中的实践

  •  
  •   hansonwang99 · 2019-01-08 07:44:24 +08:00 · 2587 次点击
    这是一个创建于 2131 天前的主题,其中的信息可能已经有所发展或是发生改变。

    概述

    在如今高并发的互联网应用中,缓存的地位举足轻重,对提升程序性能帮助不小。而 3.x 开始的 Spring 也引入了对 Cache 的支持,那对于如今发展得如火如荼的 Spring Boot 来说自然也是支持缓存特性的。当然 Spring Boot 默认使用的是 SimpleCacheConfiguration,即使用 ConcurrentMapCacheManager 来实现的缓存。但本文将讲述如何将 Guava Cache 缓存应用到 Spring Boot 应用中。

    Guava Cache 是一个全内存的本地缓存实现,而且提供了线程安全机制,所以特别适合于代码中已经预料到某些值会被多次调用的场景

    下文就上手来摸一摸它,结合对数据库的操作,我们让 Guava Cache 作为本地缓存来看一下效果!

    注: 本文首发于 My Personal Blog:CodeSheep·程序羊,欢迎光临 小站


    准备工作

    • 准备好数据库和数据表并插入相应实验数据( MySQL )

    比如我这里准备了一张用户表,包含几条记录:

    准备好 MySQL 数据库和数据表

    我们将通过模拟数据库的存取操作来看看 Guava Cache 缓存加入后的效果。


    搭建工程:Springboot + MyBatis + MySQL + Guava Cache

    pom.xml 中添加如下依赖:

        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
    
            <!--for mybatis-->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>1.3.2</version>
            </dependency>
    
            <!--for Mysql-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
    
            <!-- Spring boot Cache-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-cache</artifactId>
            </dependency>
    
            <!--for guava cache-->
            <dependency>
                <groupId>com.google.guava</groupId>
                <artifactId>guava</artifactId>
                <version>27.0.1-jre</version>
            </dependency>
    
        </dependencies>
    

    建立 Guava Cache 配置类

    引入 Guava Cache 的配置文件 GuavaCacheConfig

    @Configuration
    @EnableCaching
    public class GuavaCacheConfig {
    
        @Bean
        public CacheManager cacheManager() {
            GuavaCacheManager cacheManager = new GuavaCacheManager();
            cacheManager.setCacheBuilder(
                    CacheBuilder.newBuilder().
                            expireAfterWrite(10, TimeUnit.SECONDS).
                            maximumSize(1000));
            return cacheManager;
        }
    }
    

    Guava Cache 配置十分简洁,比如上面的代码配置缓存存活时间为 10 秒,缓存最大数目为 1000 个


    配置 application.properties

    server.port=82
    
    # Mysql 数据源配置
    spring.datasource.url=jdbc:mysql://121.116.23.145:3306/demo?useUnicode=true&characterEncoding=utf-8&useSSL=false
    spring.datasource.username=root
    spring.datasource.password=xxxxxx
    spring.datasource.driver-class-name=com.mysql.jdbc.Driver
    
    # mybatis 配置
    mybatis.type-aliases-package=cn.codesheep.springbt_guava_cache.entity
    mybatis.mapper-locations=classpath:mapper/*.xml
    mybatis.configuration.map-underscore-to-camel-case=true
    

    编写数据库操作和 Guava Cache 缓存的业务代码

    • 编写 entity
    public class User {
    
        private Long userId;
        private String userName;
        private Integer userAge;
    
        public Long getUserId() {
            return userId;
        }
    
        public void setUserId(Long userId) {
            this.userId = userId;
        }
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName) {
            this.userName = userName;
        }
    
        public Integer getUserAge() {
            return userAge;
        }
    
        public void setUserAge(Integer userAge) {
            this.userAge = userAge;
        }
    }
    
    • 编写 mapper
    public interface UserMapper {
    
        List<User> getUsers();
        int addUser(User user);
        List<User> getUsersByName( String userName );
    }
    
    • 编写 service
    @Service
    public class UserService {
    
        @Autowired
        private UserMapper userMapper;
    
        public List<User> getUsers() {
            return userMapper.getUsers();
        }
    
        public int addUser( User user ) {
            return userMapper.addUser(user);
        }
    
        @Cacheable(value = "user", key = "#userName")
        public List<User> getUsersByName( String userName ) {
            List<User> users = userMapper.getUsersByName( userName );
            System.out.println( "从数据库读取,而非读取缓存!" );
            return users;
        }
    }
    

    看得很明白了,我们在 getUsersByName接口上添加了注解:@Cacheable。这是 缓存的使用注解之一,除此之外常用的还有 @CachePut@CacheEvit,分别简单介绍一下:

    1. @Cacheable:配置在 getUsersByName方法上表示其返回值将被加入缓存。同时在查询时,会先从缓存中获取,若不存在才再发起对数据库的访问
    2. @CachePut:配置于方法上时,能够根据参数定义条件来进行缓存,其与 @Cacheable不同的是使用 @CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中,所以主要用于数据新增和修改操作上
    3. @CacheEvict:配置于方法上时,表示从缓存中移除相应数据。
    • 编写 controller
    @RestController
    public class UserController {
    
        @Autowired
        private UserService userService;
    
        @Autowired
        CacheManager cacheManager;
    
        @RequestMapping( value = "/getusersbyname", method = RequestMethod.POST)
        public List<User> geUsersByName( @RequestBody User user ) {
            System.out.println( "-------------------------------------------" );
            System.out.println("call /getusersbyname");
            System.out.println(cacheManager.toString());
            List<User> users = userService.getUsersByName( user.getUserName() );
            return users;
        }
    
    }
    

    改造 Spring Boot 应用主类

    主要是在启动类上通过 @EnableCaching 注解来显式地开启缓存功能

    @SpringBootApplication
    @MapperScan("cn.codesheep.springbt_guava_cache")
    @EnableCaching
    public class SpringbtGuavaCacheApplication {
        public static void main(String[] args) {
            SpringApplication.run(SpringbtGuavaCacheApplication.class, args);
        }
    }
    

    最终完工的整个工程的结构如下:

    完整工程结构


    实际实验

    通过多次向接口 localhost:82/getusersbyname POST 数据来观察效果:

    向接口提交数据

    可以看到缓存的启用和失效时的效果如下所示(上文 Guava Cache 的配置文件中设置了缓存 user 的实效时间为 10s ):

    缓存的启用和失效时的取数据效果

    怎么样,缓存的作用还是很明显的吧!


    后 记

    由于能力有限,若有错误或者不当之处,还请大家批评指正,一起学习交流!



    5 条回复    2019-01-09 13:20:27 +08:00
    dengtongcai
        1
    dengtongcai  
       2019-01-08 08:29:29 +08:00 via iPhone
    支持,前几天刚用这个 guava cache,很爽
    hansonwang99
        2
    hansonwang99  
    OP
       2019-01-08 08:30:40 +08:00 via iPhone
    @dengtongcai 请问你们是用在什么场景呢
    dengtongcai
        3
    dengtongcai  
       2019-01-08 08:41:58 +08:00 via iPhone   ❤️ 1
    @hansonwang99 我用到的是一个图片验证码反馈,打码(返回附带打码 id)id,验证,统一反馈。其中还用到了 removeListener,处理不同的移除情况。本来想用 AOP 静态织入,但是和 lombok 冲突了
    hansonwang99
        4
    hansonwang99  
    OP
       2019-01-08 09:39:39 +08:00
    @dengtongcai 哦哦,学习啦
    peihanw
        5
    peihanw  
       2019-01-09 13:20:27 +08:00
    俺现在 spring-boot 2.x 里用的是 caffeine,pom 引用的包和楼主有些差异:
    <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>
    应用 yml:(网关验证登录 token 是系统签发的,防密钥泄露 token 被伪造)
    spring:
    cache:
    type: caffeine
    cache-names: tokens
    caffeine:
    spec: maximumSize=100000,expireAfterWrite=300s
    代码上差不多。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2779 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 13:02 · PVG 21:02 · LAX 05:02 · JFK 08:02
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.