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)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM