快取架構中的服務詳解!SpringBoot中二級快取服務的實現
創建快取服務
創建快取服務介面項目
- 創建myshop-service-redis-api項目,該項目只負責定義介面
- 創建項目的pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="//maven.apache.org/POM/4.0.0" xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="//maven.apache.org/POM/4.0.0 //maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.oxford</groupId>
<artifactId>myshop-dependencies</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../myshop-dependencies/pom.xml</relativePath>
</parent>
<artifactId>myshop-service-redis-api</artifactId>
<packaging>jar</packaging>
</project>
- 定義數據Redis介面RedisService:
package com.oxford.myshop.service.redis.api
public interface RedisService{
void set(String key,Object value);
void set(String key,Object value,int seconds);
void del(String key);
Object get(String key);
}
創建快取服務提供者項目
- 創建myshop-service-redis-provider項目,該項目用作快取服務提供者
- 創建項目的pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="//maven.apache.org/POM/4.0.0" xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="//maven.apache.org/POM/4.0.0 //maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.oxford</groupId>
<artifactId>myshop-dependencies</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../myshop-dependencies/pom.xml</relativePath>
</parent>
<artifactId>myshop-service-redis-api</artifactId>
<packaging>jar</packaging>
<dependencies>
<!-- Spring Boot Starter Settings-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--Common Setting-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>de.javakaffee</groupId>
<artifactId>kryo-serializers</artifactId>
</dependency>
<!--Project Settings-->
<dependency>
<groupId>com.oxford</groupId>
<artifactId>my-shop-commons-dubbo</artifactId>
<version>${Project.parent.version}</version>
</dependency>
<dependency>
<groupId>com.oxford</groupId>
<artifactId>my-shop-service-redis-api</artifactId>
<version>${Project.parent.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.oxford.myshop.service.redis.provider.MyshopServiceRedisProviderApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
Redis底層實現的Java的lettuce客戶端
- 創建快取服務介面實現類RedisServiceImpl
package com.oxford.myshop.service.redis.provider.api.impl;
@Service(version="${service.versions.redis.v1}")
public class RedisServiceImpl implements RedisService{
@Override
public void set(String key,Object value){
redisTemplate.opsForValue().set(key,value);
}
@Override
public void set(String key,Object value,int seconds){
redisTemplate.opsForValue().set(key,value,seconds,TimeUnit.SECONDS);
}
@Override
public void del(String key){
redisTemplate.delete(key);
}
@Override
public Object get(String key){
return redisTemplate.opsForValue().get(key);
}
}
- 創建啟動類SpringBootApplication
package com.oxford.myshop.service.redis.provider;
import com.alibaba.dubbo.container.Main;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
@EnableHystrix
@EnableHystrixDashboard
public class MyShopServiceRedisrProviderApplication {
public static void main(String[]args) {
SpringApplication.run(MyShopServiceRedisProviderApplication.class,args);
Main.main(args);
}
}
- 創建配置文件application.yml
spring:
application:
name: myshop-service-redis-provider
redis:
lettuce:
pool:
max-active: 8
max-idle: 8
max-wait: -1ms
min-idle: 0
sentinel:
master: mymaster
nodes: 192.168.32.255:26379,192.168.32.255:26380,192.168.32.255:26381
server:
port: 8503
services:
version:
redis:
v1: 1.0.0
user:
v1: 1.0.0
dubbo:
scan:
basePackages: com.oxford.myshop.service.redis.provider.api.impl
application:
id: com.oxford.myshop.service.redis.provider.api
name: com.oxford.myshop.service.redis.provider.api
qos-port: 22224
qos-enable: true
protocal:
id: dubbo
name: dubbo
port: 20883
status: server
serialization: kryo
regitry:
id: zookeeper
address: zookeeper://localhost:2181?backup=192.168.32.255:2182,192.168.32.255:2183
management:
endpoint:
dubbo:
enabled: true
dubbo-shutdown:
enabled: true
dubbo-configs:
enabled: true
dubbo-sevicies:
enabled: true
dubbo-reference:
enabled: true
dubbo-properties:
enabled: true
health:
dubbo:
status:
defaults: memory
extras: load,threadpool
創建快取服務消費者項目
- 在pom文件中引入redis介面依賴
- 在快取服務消費者項目的ServiceImpl中調用RedisService
@Reference(version="services.versions.redis.v1")
private RedisService redisService;
MyBatis Redis二級快取
MyBatis快取
- 一級快取:
- MyBatis會在表示會話的SqlSession對象中建立一個簡單的快取: 將每次查詢到的結果快取起來,當下次查詢的時候,如果判斷先前有個完全一樣的查詢,會直接從快取中直接將結果取出,返回給用戶,不需要再進行一次資料庫查詢
- 一級快取是SqlSession級別的快取:
- 在操作資料庫時需要構造SqlSession對象
- 對象中有一個(記憶體區域)數據結構(HashMap)用於存儲快取數據
- 不同的SqlSession之間的快取數據區域(HashMap)互不影響,
- 一級快取的作用域是同一個SqlSession
- 在同一個SqlSession中兩次執行相同的SQL語句: 第一次執行完畢會將資料庫中查詢的數據寫到快取(記憶體),第二次會從快取中獲取數據,將不再從資料庫查詢,從而提高查詢效率
- 當一個SqlSession結束後該SqlSession中的一級快取就不存在了
- MyBatis默認開啟一級快取
- 二級快取:
- 二級快取是Mapper級別的快取: 多個SqlSession去操作同一個Mapper的SQL語句,多個SqlSession去操作資料庫得到數據會存在二級快取區域,多個SqlSession可以共用二級快取,二級快取是跨SqlSession的
- 二級快取的作用域是mapper的同一個namespace
- 不同的SqlSession兩次執行相同namespace下的SQL語句且向SQL中傳遞參數也相同即最終執行相同的SQL語句: 第一次執行完畢會將資料庫中查詢的數據寫到快取(記憶體),第二次會從快取中獲取數據將不再從資料庫查詢,從而提高查詢效率
- MyBatis默認沒有開啟二級快取,需要在setting全局參數中配置開啟二級快取
配置MyBatis二級快取
SpringBoot中開啟MyBatis二級快取
- 在myshop-service-user-provider的配置文件中開啟MyBatis二級快取
spring:
application:
name: myshop-service-user-provider
datasource:
druid:
url: jdbc:mysql://localhost:3306/myshop?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
initial-size: 1
min-idle: 1
main-active: 20
test-on-borrow: true
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
lettuce:
pool:
max-active: 8
max-idle: 8
max-wait: -1ms
min-idle: 0
sentinel:
master: mymaster
nodes: 192.168.32.255:26379,192.168.32.255:26380,192.168.32.255:26381
server:
port: 8501
# MyBatis Config properties
mybatis:
configuration:
cache-enabled: true
type-aliases-package: com.oxford.myshop.commons.domain
mapper-location: classpath:mapper/*.xml
services:
version:
redis:
v1: 1.0.0
user:
v1: 1.0.0
dubbo:
scan:
basePackages: com.oxford.myshop.service.user.provider.api.impl
application:
id: com.oxford.myshop.service.user.provider.api
name: com.oxford.myshop.service.user.provider.api
qos-port: 22222
qos-enable: true
protocal:
id: dubbo
name: dubbo
port: 20001
status: server
serialization: kryo
regitry:
id: zookeeper
address: zookeeper://localhost:2181?backup=192.168.32.255:2182,192.168.32.255:2183
management:
endpoint:
dubbo:
enabled: true
dubbo-shutdown:
enabled: true
dubbo-configs:
enabled: true
dubbo-sevicies:
enabled: true
dubbo-reference:
enabled: true
dubbo-properties:
enabled: true
health:
dubbo:
status:
defaults: memory
extras: load,threadpool
- 在myshop-commons-mapper的pom.xml中增加redis依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifacted>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifacted>
</dependency>
實體類實現序列化介面並聲明序列號
private static final long serialVersionUID = 82897704415244673535L
IDEA生成序列號方法:
- 使用GenerateSerialVersionUID插件生成,安裝完插件後在實現了序列化介面的類中
- 使用快捷鍵Alt+Insert即可呼出生成菜單,即可自動生成序列號
實現Mybatis Cache介面,自定義快取為Redis
- 在myshop-commons項目中創建ApplicationContextHolder類
package com.oxford.myshop.commons.context;
@Component
public class ApplicationContextHolder implements ApplicationContextAware,DisposableBean{
private static final Logger logger=LoggerFactory.getLogger(ApplicationContext.class);
private static ApplicationContext applicationContext;
/**
* 獲取存儲在靜態變數中的ApplicationContext
*/
public static ApplicationContext getApplicationContext(){
assertContextInjected();
return applicationContext;
}
/**
* 從靜態變數applicationContext中獲取Bean,自動轉型成所賦值對象的類型
*/
public static <T> T getBean(String name){
assertContextInjected();
return (T) applicationContext.getBean(name);
}
/**
* 從靜態變數applicationContext中獲取Bean,自動轉型成所賦值對象的類型
*/
public static <T> T getBean(Class<T> clazz){
assertContextInjected();
return (T) applicationContext.getBean(clazz);
}
/**
* 實現DisposableBean介面,在Context關閉時清理靜態變數
*/
public void destroy() throws Exception{
logger.debug("清除 SpringContext 中的 ApplicationContext: {}",applicationContext);
applicationContext=null;
}
/**
* 實現ApplicationContextAware介面,注入Context到靜態變數中
*/
public void setApplicationContext(ApplicationContext applicationContext) throws BeanException{
ApplicationContext.applicationContext=applicationContext;
}
/**
* 斷言Context已經注入
*/
private static void assertContextInjected(){
Validate.validState(applicationContext !=null,"applicationContext 屬性未注入,請在配置文件中配置定義ApplicationContextContext");
}
}
- 在myshop-commons-mapper項目中創建一個RedisCache的工具類
package com.oxford.myshop.commons.utils;
public class RedisCache implements Cache{
private static final Logger logger=LoggerFactory.getLogger(RedisCache.class);
private final ReadWriteLock readWriteLock=new ReentranReadWriteLock();
private final String id;
private RedisTemplate redisTemplate;
private static final long EXPIRE_TIME_IN_MINUTES=30 // redis過期時間
public RedisCache(String id){
if(id==null){
throw new IllegalArgumentException("Cache instances require an ID");
}
this.id=id;
}
@Override
public String getId(){
return id;
}
/**
* Put query result to redis
*/
@Override
public void putObject(Object key,Object value){
try{
RedisTemplate redisTemplate=getRedisTemplate();
ValueOperations opsForValue=redisTemplate.opsForValue();
opsForValue.set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
logger.debug("Put query result to redis");
}catch(Throwable t){
logger.error("Redis put failed",t);
}
}
/**
* Get cached query result from redis
*/
@Override
public Object getObject(Object key){
try{
RedisTemplate redisTemplate=getRedisTemplate();
ValueOperations opsForValue=redisTemplate.opsForValue();
opsForValue.set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
logger.debug("Get cache query result from redis");
return opsForValue.get(key);
}catch(Throwable t){
logger.error("Redis get failed, fail over to db");
return null;
}
}
/**
* Get cached query result from redis
*/
@Override
public Object getObject(Object key){
try{
RedisTemplate redisTemplate=getRedisTemplate();
ValueOperations opsForValue=redisTemplate.opsForValue();
opsForValue.set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
logger.debug("Get cache query result from redis");
return opsForValue.get(key);
}catch(Throwable t){
logger.error("Redis get failed, fail over to db");
return null;
}
}
/**
* Remove cached query result from redis
*/
@Override
@SuppressWarnings("unchecked")
public Object removeObject(Object key){
try{
RedisTemplate redisTemplate=getRedisTemplate();
redisTemplate.delete(key);
logger.debug("Remove cached query result from redis");
}catch(Throwable t){
logger.error("Redis remove failed");
}
return null;
}
/**
* Clear this cache instance
*/
@Override
public void clear(){
RedisTemplate redisTemplate=getRedisTemplate();
redisTemplate.execute((RedisCallback)->{
connection.flushDb();
return null;
});
logger.debug("Clear all the cached query result from redis");
}
@Override
public int getSize(){
return 0;
}
@Override
public ReadWriteLock getReadWriteLock(){
return readWriteLock;
}
private RedisTemplate getRedisTemplate(){
if(redisTemplate==null){
redisTemplate=ApplicationContextHolder.getBean("redisTemplate");
}
return redisTemplate;
}
}
Mapper介面類中標註註解
- 在Mapper介面類上標註註解,聲明使用二級快取
@CacheNamespace(implementation=RedisCache.class)