zoukankan      html  css  js  c++  java
  • spring boot:使用spring cache+caffeine做进程内缓存(本地缓存)(spring boot 2.3.1)

    一,为什么要使用caffeine做本地缓存?

    1,spring boot默认集成的进程内缓存在1.x时代是guava cache

      在2.x时代更新成了caffeine,

      功能上差别不大,但后者在性能上更胜一筹,

      使用caffeine做本地缓存,取数据可以达到微秒的级别,

      一次取数据用时经常不足1毫秒,

      这样可以及时响应请求,在高并发的情况下把请求拦截在上游,

      避免把压力带到数据库,

      所以我们在应用中集成它对于系统的性能有极大的提升

    2,与之相比,即使是本地的redis,

         响应时间也比进程内缓存用时要更久,

        而且在应用服务器很少有专门配备redis缓存的做法,

        而是使用专门的redis集群做为分布式缓存

    说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest

             对应的源码可以访问这里获取: https://github.com/liuhongdi/

    说明:作者:刘宏缔 邮箱: 371125307@qq.com

    二,演示项目的相关信息

    1,项目地址:

    https://github.com/liuhongdi/caffeine

    2,项目原理:

    我们建立了两个cache:goods,goodslist

    分别用来缓存单个商品详情和商品列表

    3,项目结构:

    如图:

     

    三,配置文件说明

    1,pom.xml

            <!--local cache begin-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-cache</artifactId>
            </dependency>
            <dependency>
                <groupId>com.github.ben-manes.caffeine</groupId>
                <artifactId>caffeine</artifactId>
                <version>2.8.5</version>
            </dependency>
            <!--local cache   end-->

    2,application.properties

    #profile
    spring.profiles.active=cacheable

    这个值用来配置cache是否生效

    我们在测试或调试时有时会用关闭缓存的需求

    如果想关闭cache,把这个值改变一下即可

    3,mysql的数据表结构:

    CREATE TABLE `goods` (
     `goodsId` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
     `goodsName` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT 'name',
     `subject` varchar(200) NOT NULL DEFAULT '' COMMENT '标题',
     `price` decimal(15,2) NOT NULL DEFAULT '0.00' COMMENT '价格',
     `stock` int(11) NOT NULL DEFAULT '0' COMMENT 'stock',
     PRIMARY KEY (`goodsId`)
    ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='商品表'

    四,java代码说明

    1,CacheConfig.java

    @Profile("cacheable")   //这个当profile值为cacheable时缓存才生效
    @Configuration
    @EnableCaching //开启缓存
    public class CacheConfig {
        public static final int DEFAULT_MAXSIZE = 10000;
        public static final int DEFAULT_TTL = 600;
        private SimpleCacheManager cacheManager = new SimpleCacheManager();
    
        //定义cache名称、超时时长(秒)、最大容量
        public enum CacheEnum{
            goods(600,3000),          //有效期600秒 , 最大容量3000
            goodslist(600,1000),  //有效期600秒 , 最大容量1000
            ;
            CacheEnum(int ttl, int maxSize) {
                this.ttl = ttl;
                this.maxSize = maxSize;
            }
            private int maxSize=DEFAULT_MAXSIZE;    //最大數量
            private int ttl=DEFAULT_TTL;        //过期时间(秒)
            public int getMaxSize() {
                return maxSize;
            }
            public int getTtl() {
                return ttl;
            }
        }
    
        //创建基于Caffeine的Cache Manager
        @Bean
        @Primary
        public CacheManager caffeineCacheManager() {
            ArrayList<CaffeineCache> caches = new ArrayList<CaffeineCache>();
            for(CacheEnum c : CacheEnum.values()){
                caches.add(new CaffeineCache(c.name(),
                        Caffeine.newBuilder().recordStats()
                                .expireAfterWrite(c.getTtl(), TimeUnit.SECONDS)
                                .maximumSize(c.getMaxSize()).build())
                );
            }
            cacheManager.setCaches(caches);
            return cacheManager;
        }
    
        @Bean
        public CacheManager getCacheManager() {
            return cacheManager;
        }
    }

    说明:根据CacheEnum这个enum的内容来生成Caffeine Cache,并保存到cachemanager中

             如果需要新增缓存,保存到CacheEnum中

    @Profile("cacheable"): 当spring.profiles.active的值中包括cacheable时cache才起作用,不包含此值时则cache被关闭不生效

    @EnableCaching :  用来开启缓存

    recordStats:记录统计数据

    expireAfterWrite:在写入到达一定时间后过期

    maximumSize:指定最大容量

    2,GoodsServiceImpl.java

    @Service
    public class GoodsServiceImpl implements GoodsService {
    
        @Resource
        private GoodsMapper goodsMapper;
    
        //得到一件商品的信息
        @Cacheable(value = "goods", key="#goodsId",sync = true)
        @Override
        public Goods getOneGoodsById(Long goodsId) {
            System.out.println("query database");
            Goods goodsOne = goodsMapper.selectOneGoods(goodsId);
            return goodsOne;
        }
    
        //获取商品列表,只更新缓存
        @CachePut(value = "goodslist", key="#currentPage")
        @Override
        public Map<String,Object> putAllGoodsByPage(int currentPage) {
            Map<String,Object> res = getAllGoodsByPageDdata(currentPage);
            return res;
        }
    
        //获取商品列表,加缓存
        //@Cacheable(key = "#page+'-'+#pageSize") 多个参数可以用字符串连接起来
        @Cacheable(value = "goodslist", key="#currentPage",sync = true)
        @Override
        public Map<String,Object> getAllGoodsByPage(int currentPage) {
            Map<String,Object> res = getAllGoodsByPageDdata(currentPage);
            return res;
        }
    
        //从数据库获取商品列表
        public Map<String,Object> getAllGoodsByPageDdata(int currentPage) {
            System.out.println("-----从数据库得到数据");
            Map<String,Object> res = new HashMap<String,Object>();
            PageHelper.startPage(currentPage, 5);
            List<Goods> goodsList = goodsMapper.selectAllGoods();
            res.put("goodslist",goodsList);
            PageInfo<Goods> pageInfo = new PageInfo<>(goodsList);
            res.put("pageInfo",pageInfo);
            return res;
        }
    }

    说明:

    @Cacheable(value = "goodslist", key="#currentPage",sync = true):

    当缓存中存在数据时,则直接从缓存中返回,

    如果缓存中不存在数据,则要执行方法返回数据后并把数据保存到缓存.

    @CachePut(value = "goodslist", key="#currentPage"):

    不判断缓存中是否存在数据,只更新缓存

    3,HomeController.java

        //商品列表 参数:第几页
        @GetMapping("/goodslist")
        public String goodsList(Model model,
                                @RequestParam(value="p",required = false,defaultValue = "1") int currentPage) {
            Map<String,Object> res = goodsService.getAllGoodsByPage(currentPage);
            model.addAttribute("pageInfo", res.get("pageInfo"));
            model.addAttribute("goodslist", res.get("goodslist"));
            return "goods/goodslist";
        }
    
        //更新
        @ResponseBody
        @GetMapping("/goodslistput")
        public String goodsListPut(@RequestParam(value="p",required = false,defaultValue = "1") int currentPage) {
            Map<String,Object> res = goodsService.putAllGoodsByPage(currentPage);
            return "cache put succ";
        }
    
        //清除
        @CacheEvict(value="goodslist", allEntries=true)
        @ResponseBody
        @GetMapping("/goodslistevict")
        public String goodsListEvict() {
            return "cache evict succ";
        }
    

    说明:

    @CacheEvict(value="goodslist", allEntries=true):用来清除goodslist中的缓存

    4,StatsController.java

    @Profile("cacheable")
    @Controller
    @RequestMapping("/stats")
    public class StatsController {
        //统计,如果是生产环境,需要加密才允许访问
        @Resource
        private CacheManager cacheManager;
    
        @GetMapping("/stats")
        @ResponseBody
        public String stats() {
            CaffeineCache caffeine = (CaffeineCache)cacheManager.getCache("goodslist");
            Cache goods = caffeine.getNativeCache();
            String statsInfo="cache名字:goodslist<br/>";
            Long size = goods.estimatedSize();
            statsInfo += "size:"+size+"<br/>";
            ConcurrentMap map= goods.asMap();
            statsInfo += "map keys:<br/>";
            for(Object key : map.keySet()) {
                statsInfo += "key:"+key.toString()+";value:"+map.get(key)+"<br/>";
            }
            statsInfo += "统计信息:"+goods.stats().toString();
            return statsInfo;
        }
    }

    五,效果测试

    1,访问地址:

    http://127.0.0.1:8080/home/goodslist/?p=1

    如图:

    刷新几次后,可以看到,在使用cache时:

    costtime aop 方法doafterreturning:毫秒数:0

    使用的时间不足1毫秒

    2,查看统计信息,访问:

    http://127.0.0.1:8080/stats/stats

    如图:

    3,清除cache信息:

    http://127.0.0.1:8080/home/goodslistevict

    查看缓存的统计:

    cache名字:goodslist
    size:0
    map keys:
    统计信息:CacheStats{hitCount=1, missCount=3, loadSuccessCount=3, loadFailureCount=0, totalLoadTime=596345574, evictionCount=0, evictionWeight=0}

    可以看到数据已被清空

    六,查看spring boot的版本

      .   ____          _            __ _ _
     /\ / ___'_ __ _ _(_)_ __  __ _    
    ( ( )\___ | '_ | '_| | '_ / _` |    
     \/  ___)| |_)| | | | | || (_| |  ) ) ) )
      '  |____| .__|_| |_|_| |_\__, | / / / /
     =========|_|==============|___/=/_/_/_/
     :: Spring Boot ::        (v2.3.1.RELEASE)
  • 相关阅读:
    XPOSED优秀模块列表 Xposed GEL 设置
    XPOSED优秀模块列表 Eggster
    XPOSED优秀模块列表 全部成为F
    XPOSED优秀模块列表 XBridge
    XPOSED优秀模块列表 xBoon
    XPOSED优秀模块列表 定位注入器
    showModalDialog()、showModelessDialog()方法使用详解
    手工更改数据库字符集
    blog开张了
    SQLSERVER 2008 复制之旅
  • 原文地址:https://www.cnblogs.com/architectforest/p/13337171.html
Copyright © 2011-2022 走看看