github地址:h2cache-spring-boot-starter

一、缓存

当系统的并发量上来了,如果我们频繁地去访问数据库,那么会使数据库的压力不断增大,在高峰时甚至可以出现数据库崩溃的现象。所以一般我们会使用缓存来解决这个数据库并发访问问题,用户访问进来,会先从缓存里查询,如果存在则返回,如果不存在再从数据库里查询,最后添加到缓存里,然后返回给用户,当然了,接下来又能使用缓存来提供查询功能。

而缓存,一般我们可以分为本地缓存和分布式缓存。

常用的本地缓存有 ehcache、guava cache,而我们一般都是使用 ehcache,毕竟他是纯 Java 的,出现问题我们还可以根据源码解决,并且还能自己进行二次开发来扩展功能。

常用的分布式缓存当然就是 Redis 了,Redis 是基于内存和单线程的,执行效率非常的高。

二、Spring Cache

相信如果要整合缓存到项目中,大家都会使用到 Spring Cache,它不但整合了多种缓存框架(ehcache、jcache等等),还可以基于注解来使用,是相当的方便。

缓存框架的整合在 spring-context-support 中:



缓存注解在 spring-context 中:



当然了,在 Spring 的 context 中没有整合 Redis,但是我们可以在 spring-data-redis 中找到。



但是我们都知道,不管是在 Spring 项目 还是 Spring Boot 中,我们都只能整合一种缓存,不能同时整合多种缓存。

在 Spring Boot 中,我们一般是利用 spring.cache.type 来指定使用哪种缓存,然后填写相关配置信息来完成自动配置。



CacheType 的源码:我们可以看到,Spring 是支持非常多种缓存框架的。

package org.springframework.boot.autoconfigure.cache;

public enum CacheType {
GENERIC,
JCACHE,
EHCACHE,
HAZELCAST,
INFINISPAN,
COUCHBASE,
REDIS,
CAFFEINE,
SIMPLE,
NONE; private CacheType() {
}
}

那么如果我们就是有这么一种需求,要整合两种缓存框架:例如一个本地缓存 Ehcache,一个分布式缓存 Redis,

那能整么?

能是能,但是 Spring 可不提供这种多级缓存,而是需要你自己动手来整了。

三、h2cache-spring-boot-starter

1、什么是 h2cache-spring-boot-starter?

在微服务中,每个服务都是无状态的,服务之间需要经过 HTTP 或者 RPC 来进行通信。而每个服务都拥有自己对应的数据库,所以说如果服务A 需要获取服务B 的某个表的数据,那么就需要一次 HTTP 或 RPC 通信,那如果高峰期每秒需要调用100次,那岂不是需要100次 HTTP 或 RPC 通信,这是相当耗费接口性能的。

那怎么解决呢?

本地缓存那是肯定不是的,因为一般不同服务都是部署在不同的机器上面的,所以此时我们需要的是分布式缓存,例如 Redis;但是,访问量高的的服务当然还是需要本地缓存了。所以最后,我们不但需要本地缓存,还需要分布式缓存,但是 Spring Boot 却不能提供这种多级缓存的功能,所以需要我们自己来整合。

不用怕,我已经自己整了一个 Spring Boot Starter了,就是 h2cache-spring-boot-starter,我们只需要在配置文件配置上对应的信息,就可以启用这个多级缓存的功能了。

2、开始使用

添加依赖:

大家正常引入下面依赖即可,因为我已经将此项目发布到 Maven 中央仓库了~

<denpency>
<groupId>com.github.howinfun</groupId>
<artifactId>h2cache-spring-boot-starter</artifactId>
<version>0.0.1</version>
</denpency>

在 Spring Boot properties 启用服务,并且加上对应的配置:

开启多级缓存服务:

# Enable L2 cache or not
h2cache.enabled=true

配置 Ehcache:

# Ehcache Config
## the path of ehcache.xml (We can put it directly under Resources)
h2cache.ehcache.filePath=ehcache.xml
#Set whether the EhCache CacheManager should be shared (as a singleton at the ClassLoader level) or independent (typically local within the application).Default is "false", creating an independent local instance.
h2cache.ehcache.shared=true

配置 Redis:主要包括默认的缓存配置和自定义缓存配置

要注意一点的是:h2cache-spring-boot-starter 同时引入了 LettuceJedis 客户端,而 Spring Boot 默认使用 Lettuce 客户端,所以如果我们需要使用 Jedis 客户端,需要将 Lettuce 依赖去除掉。

# Redis Config
## default Config (expire)
h2cache.redis.default-config.ttl=200
### Disable caching {@literal null} values.Default is "false"
h2cache.redis.default-config.disable-null-values=true
### Disable using cache key prefixes.Default is "true"
h2cache.redis.default-config.use-prefix=true ## Custom Config list
### cacheName -> @CacheConfig#cacheNames @Cacheable#cacheNames and other comments, etc
h2cache.redis.config-list[0].cache-name=userCache
h2cache.redis.config-list[0].ttl=60
h2cache.redis.config-list[0].use-prefix=true
h2cache.redis.config-list[0].disable-null-values=true h2cache.redis.config-list[1].cache-name=bookCache
h2cache.redis.config-list[1].ttl=60
h2cache.redis.config-list[1].use-prefix=true #Redis
spring.redis.host=10.111.0.111
spring.redis.password=
spring.redis.port=6379
spring.redis.database=15
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=8
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=30

如何使用缓存注解

我们只要像之前一样使用 Spring Cache 的注解即可。

for example:

代码里的持久层,我使用的是: mybatis-plus.

package com.hyf.testDemo.redis;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Repository; /**
* @author Howinfun
* @desc
* @date 2020/3/25
*/
@Repository
// Global cache config,We usually set the cacheName
@CacheConfig(cacheNames = {"userCache"})
public interface UserMapper extends BaseMapper<User> { /**
* put the data to cache(Ehcache & Redis)
* @param id
* @return
*/
@Cacheable(key = "#id",unless = "#result == null")
User selectById(Long id); /**
* put the data to cache After method execution
* @param user
* @return
*/
@CachePut(key = "#user.id", condition = "#user.name != null and #user.name != ''")
default User insert0(User user) { this.insert(user);
return user;
} /**
* evict the data from cache
* @param id
* @return
*/
@CacheEvict(key = "#id")
int deleteById(Long id); /**
* Using cache annotations in combination
* @param user
* @return
*/
@Caching(
evict = {@CacheEvict(key = "#user.id", beforeInvocation = true)},
put = {@CachePut(key = "#user.id")}
)
default User updateUser0(User user){ this.updateById(user);
return user;
}
}

测试一下:

查询:我们可以看到,在数据库查询到结果后,会将数据添加到 EhcacheRedis 缓存中;接着之后的查询都将会先从 Ehcache 或者 Redis 里查询。

2020-04-03 09:55:09.691  INFO 5920 --- [nio-8080-exec-7] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2020-04-03 09:55:10.044 INFO 5920 --- [nio-8080-exec-7] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2020-04-03 09:55:10.051 DEBUG 5920 --- [nio-8080-exec-7] c.h.t.redis.BookMapper2.selectById : ==> Preparing: SELECT id,create_time,update_time,read_frequency,version,book_name FROM book WHERE id=?
2020-04-03 09:55:10.068 DEBUG 5920 --- [nio-8080-exec-7] c.h.t.redis.BookMapper2.selectById : ==> Parameters: 51(Long)
2020-04-03 09:55:10.107 DEBUG 5920 --- [nio-8080-exec-7] c.h.t.redis.BookMapper2.selectById : <== Total: 1
2020-04-03 09:55:10.113 INFO 5920 --- [nio-8080-exec-7] c.hyf.cache.cachetemplate.H2CacheCache : insert into ehcache,key:51,value:Book2(id=51, bookName=微服务架构, readFrequency=1, createTime=2020-03-20T16:10:13, updateTime=2020-03-27T09:14:44, version=1)
2020-04-03 09:55:10.118 INFO 5920 --- [nio-8080-exec-7] c.hyf.cache.cachetemplate.H2CacheCache : insert into redis,key:51,value:Book2(id=51, bookName=微服务架构, readFrequency=1, createTime=2020-03-20T16:10:13, updateTime=2020-03-27T09:14:44, version=1) 2020-04-03 09:55:31.864 INFO 5920 --- [nio-8080-exec-2] c.hyf.cache.cachetemplate.H2CacheCache : select from ehcache,key:51

删除:删除数据库中的数据后,也会删除 EhcacheRedis 中对应的缓存数据。

2020-04-03 10:05:18.704 DEBUG 5920 --- [nio-8080-exec-3] c.h.t.redis.BookMapper2.deleteById       : ==>  Preparing: DELETE FROM book WHERE id=?
2020-04-03 10:05:18.704 DEBUG 5920 --- [nio-8080-exec-3] c.h.t.redis.BookMapper2.deleteById : ==> Parameters: 51(Long)
2020-04-03 10:05:18.731 DEBUG 5920 --- [nio-8080-exec-3] c.h.t.redis.BookMapper2.deleteById : <== Updates: 1
2020-04-03 10:05:18.732 INFO 5920 --- [nio-8080-exec-3] c.hyf.cache.cachetemplate.H2CacheCache : delete from ehcache,key:51
2020-04-03 10:05:18.844 INFO 5920 --- [nio-8080-exec-3] c.hyf.cache.cachetemplate.H2CacheCache : delete from redis,key:51

其他的就不用演示了...

四、最后

当然啦,这个 starter 还是比较简单的,如果大家感兴趣,可以去看看源码是如何基于 Spring Cache 实现多级缓存的~

【开源项目系列】如何基于 Spring Cache 实现多级缓存(同时整合本地缓存 Ehcache 和分布式缓存 Redis)的更多相关文章

  1. 基于Spring Cache实现二级缓存(Caffeine+Redis)

    一.聊聊什么是硬编码使用缓存? 在学习Spring Cache之前,笔者经常会硬编码的方式使用缓存. 我们来举个实际中的例子,为了提升用户信息的查询效率,我们对用户信息使用了缓存,示例代码如下: @A ...

  2. 【c3p0】目前使用它的开源项目有Hibernate,Spring等

    C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展.目前使用它的开源项目有Hibernate,Spring等. c3p0与dbcp区别 JNDI ...

  3. 「性能提升」扩展 Spring Cache 支持多级缓存

    为什么多级缓存 缓存的引入是现在大部分系统所必须考虑的 redis 作为常用中间件,虽然我们一般业务系统(毕竟业务量有限)不会遇到如下图 在随着 data-size 的增大和数据结构的复杂的造成性能下 ...

  4. [DIOCP3/MyBean/QDAC开源项目] DataModule-DB例子基于MyBean的插件实例<三层数据库方案>

    [说明] 这个例子答应大家很久了,一直没有时间弄,现在正式结合MyBean插件可以很方便的在客户端共享操作连接,执行数据库的各项工作,屏蔽了底层的通信解码器编码等工作,直接传递Variant,给了开发 ...

  5. 安装开源项目 MultiType (基于 RecyclerView)出现的各种问题 -- 自己的第一篇博客

    一.引入开源项目的方式 使用开源项目 MultiType 的两种方式: 1.maven引入:在主Module 的 build.gradle 中加入 dependencies { ...... comp ...

  6. 来自GitHub的优秀开源项目系列

    开发必看: 如何设计大型系统? 架构师技术图谱. 互联网Java工程师进阶扫盲 Java学习指南 Java工程师成神之路 有趣开源项目: 中华古诗词数据库 表情包博物馆

  7. J20航模遥控器开源项目系列教程(三)开发说明 | 想要自己改造程序,扩充功能,怎么实现?

    我们的开源宗旨:自由 协调 开放 合作 共享 拥抱开源,丰富国内开源生态,开展多人运动,欢迎加入我们哈~ 和一群志同道合的人,做自己所热爱的事! 项目开源地址:https://github.com/C ...

  8. J20航模遥控器开源项目系列教程(二)使用说明 | 遥控器制作完成了,怎么用?

    我们的开源宗旨:自由 协调 开放 合作 共享 拥抱开源,丰富国内开源生态,开展多人运动,欢迎加入我们哈~ 和一群志同道合的人,做自己所热爱的事! 项目开源地址:https://github.com/C ...

  9. J20航模遥控器开源项目系列教程(一)制作教程 | 基础版V1.0发布,从0到1

    我们的开源宗旨:自由 协调 开放 合作 共享 拥抱开源,丰富国内开源生态,开展多人运动,欢迎加入我们哈~ 和一群志同道合的人,做自己所热爱的事! 项目开源地址:https://github.com/C ...

随机推荐

  1. HTTP入门(一):在Bash中curl查看请求与响应

    HTTP入门(一):在Bash中curl查看请求与响应 本文简单总结HTTP的请求与响应. 本文主要目的是对学习内容进行总结以及方便日后查阅. 详细教程和原理可以参考HTTP文档(MDN). 本文版权 ...

  2. 简单服务器端Blazor Cookie身份验证的演示

    为了演示身份验证如何在服务器端 Blazor 应用程序中工作,我们将把身份验证简化为最基本的元素. 我们将简单地设置一个 cookie,然后读取应用程序中的 cookie. 应用程序身份验证 大多数商 ...

  3. step(iter)、epoch、batch size之间的关系

    转自:https://blog.csdn.net/wcy23580/article/details/90082221

  4. 点云3d检测模型pointpillar

    PointPillars 一个来自工业界的模型.https://arxiv.org/abs/1812.05784 3D目标检测通常做法 3d卷积 投影到前平面 在bird-view上操作 处理思路依然 ...

  5. 论nw.js的坑~~~感觉我所有的前端能遇到的坑都踩了一遍

    先总结:nw.js 真特么的...难用...文档,我得先百度才能看的稍微明白点文档......!!!!!!我感觉我所有的前端能遇到的坑都踩了一遍,此文针对前后端分离项目,别的先不说 一.不需要在项目里 ...

  6. nginx显示静态html爆502 bad gateway的错误提示

    在centos的服务器上,没有启动php-fcgi. 本来是想设置显示一个静态文件目录的,用不着php,于是就关闭了php-fcgi. 结果打开爆了一个大大的nginx502 bad gateway的 ...

  7. JavaFX之FXML+CSS创建窗体以及透明窗体添加阴影

    前言 开通博客园有一段日子了,一直没空也没想好该写点什么.最近正好在做一个桌面程序,初次接触JavaFX,体验下来确实比swing好用不少.索性便记记学习笔记吧,虽然FX好像挺没存在感,没人用的感觉. ...

  8. 使用AJAX实现用户名的唯一性校验(注册界面)-JAVA(新手)

    (1)实现用户名的唯一性校验 所需要准备的: Servlet 注册界面的JSP 接口和实现类 所需要的接口和实现类: 接口: /* * 用户注册 * 账号的唯一性校验,需要传参(username) * ...

  9. File的功能--> 获取功能-->所有的根目录 | 创建文件功能,但是如果文件已经存在-->不再创建(新手)

    //导入的包.import java.io.File;import java.io.FileFilter;import java.io.IOException; // 获取功能-->所有的根目录 ...

  10. MySQL笔记(6)-- SQL更新语句日志系统流程

    一.背景 在上一篇[MySQL笔记(5)-- SQL执行流程,MySQL体系结构]中讲述了select查询语句在MySQL体系中的运行流程,从连接器开始,到分析器.优化器.执行器等,最后到达存储引擎. ...