03.装饰模式设计多级缓存框架&外观模式
jvm缓存(一级缓存)
优点:查询速度非常快
缺点:
1,jvm内置缓存与业务申请内存没有解耦,有可能会发生内存溢出问题
2,缓存容量比较少的
3,jvm内置缓存集群的话要注意数据一致性的问题
4,jvm内存缓存jvm服务重启,内存数据就没有了
缓存---当缓存满了缓存淘汰策略将近期没有使用的缓存自动清理掉
jvm内置缓存: EhCache,OSCache 底层就是一个map结合。
触发gc回收时,stw短暂暂停用户线程,会形成短暂卡顿问题。
将数据存放在内存---将对象序列化json
从内存中读取数据到程序中(反序列化json数据变成对象)
将数据存放在内存,redis,数据库。
redis缓存(二级缓存)
优点: 查询数据存放内存中,缓存数据非常多redis集群,。
缺点: 数据直接持久化在硬盘中,查询效率偏低,会经过磁盘io
image.png
在实际开发项目,为了减少数据库的访问压力,我们都会将数据缓存到内存中,比如:Redis(分布式缓存)、EhCache(JVM内置缓存)。
缓存级别越小缓存内容越少,缓存级别越大缓存内容越多;
例如在早期项目中,项目比较小可能不会使用Redis作为缓存,使用JVM内置的缓存框架,项目比较大的时候开始采用Redis分布式缓存框架,这时候需要设计一级与二级缓存。
缓存机制:jvm内置缓存: 将数据缓存到当前jvm中 缺陷:占用当前jvm内存空间,可能会造成内存溢出问题;集群很难保证各个节点之间数据同步问题。举例:EhCache,OsCache底层原理采用HashMap实现 淘汰策略
分布式缓存Redis:数据能够共享
装饰模式概念
不改变原有的代码实现增强。mybatis,hibernate 二级缓存都属于开发者自己去实现扩展功能。
装饰模式与代理模式区别
代理模式对目标对象(目标方法)实现增强:
装饰模式对装饰对象实现增强,不能改变原有代码。
基于HashMap手写Jvm内置缓存
public class JvmMapCacheUtils<V> {
private static HashMap<String, String> cacheMapping=new HashMap<>();
public static void putEntity(String key,Object object){
cacheMapping.put(key, JSONObject.toJSONString(object));
}
public static <T> T getEntity(String key,Class<T> t){
String json=cacheMapping.get(key);
JSONObject jsonObject=JSONObject.parseObject(json);
return JSONObject.parseObject(json,t);
}
public static void main(String[] args) {
UserEntity userEntity=new UserEntity(1,"3222",22);
JvmMapCacheUtils.putEntity("user1",userEntity);
UserEntity userEntity1=JvmMapCacheUtils.getEntity("user1",UserEntity.class);
System.out.println(userEntity1);
}
}
模拟一级与二、三级缓存概念
package com.taotao.entity;
import lombok.Data;
/**
* @author wangjin
* @title: UserEntity
* @projectName designmodule
* @description: TODO
* @date 2022/6/19 0019 19:53
*/
@Data
public class UserEntity {
private int id;
private String name;
private int age;
public UserEntity() {
}
public UserEntity(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "UserEntity{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
package com.taotao.mapper;
import com.taotao.entity.UserEntity;
import org.apache.ibatis.annotations.Select;
/**
* @author wangjin
* @title: UserMapper
* @projectName designmodule
* @description: TODO
* @date 2022/6/19 0019 20:00
*/
public interface UserMapper {
@Select("select * from users where id=#{id} ")
UserEntity findByUser(Integer id);
}
package com.taotao.utils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* Redis工具类
*/
@Component
public class RedisUtil implements ApplicationContextAware {
/**
* 以后学会工具类封装
* 直接通过类名称.方法
* 在该类里面有些程序属性----需要 在ioc容器获取。
* bean生命周期回调获取。
*/
@Autowired
private static StringRedisTemplate stringRedisTemplate;
private static ApplicationContext applicationContext;
/**
* 存放string类型
*
* @param key key
* @param data 数据
* @param timeout 超时间
*/
public static void setString(String key, String data, Long timeout) {
getStringRedisTemplate().opsForValue().set(key, data);
if (timeout != null) {
getStringRedisTemplate().expire(key, timeout, TimeUnit.SECONDS);
}
}
/**
* 存放string类型
*
* @param key key
* @param data 数据
*/
public static void setString(String key, String data) {
setString(key, data, null);
}
/**
* 根据key查询string类型
*
* @param key
* @return
*/
public static String getString(String key) {
String value = getStringRedisTemplate().opsForValue().get(key);
return value;
}
/**
* 根据对应的key删除key
*
* @param key
*/
public static void delKey(String key) {
getStringRedisTemplate().delete(key);
}
// @Override
// public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// this.applicationContext = applicationContext;
// }
public static StringRedisTemplate getStringRedisTemplate() {
if (stringRedisTemplate == null) {
stringRedisTemplate = applicationContext.getBean("stringRedisTemplate", StringRedisTemplate.class);
}
return stringRedisTemplate;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
server:
port: 60
redis:
host: 127.0.0.1
port: 6379
spring:
###数据库相关连接
datasource:
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
####打印MyBatias日志
logging:
level:
### 开发环境使用DEBUG 生产环境info或者error
com.taotao.mapper: DEBUG
package com.taotao;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author wangjin
* @title: AppDecorate
* @projectName designmodule
* @description: TODO
* @date 2022/6/19 0019 14:31
*/
@MapperScan("com.taotao.mapper")
@SpringBootApplication
public class AppDecorate {
public static void main(String[] args) {
SpringApplication.run(AppDecorate.class,args);
}
}
package com.taotao.service;
import com.alibaba.fastjson.JSONObject;
import com.taotao.entity.UserEntity;
import com.taotao.mapper.UserMapper;
import com.taotao.utils.JvmMapCacheUtils;
import com.taotao.utils.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author wangjin
* @title: MemberService
* @projectName designmodule
* @description: TODO
* @date 2022/6/19 0019 19:59
*/
@RestController
public class MemberService {
@Autowired
private UserMapper userMapper;
@RequestMapping("/getMember")
public UserEntity getMember(Integer id) {
if (StringUtils.isEmpty(id)) {
return null;
}
// 1.先查询一级缓存,一级缓存有的情况下 直接返回 如果一级缓存没有的情况下则 查询二级缓存
String cacheKey = "com.mayikt.service.MemberService.getMember,Integer:" + id;
UserEntity jvmUserEntity = JvmMapCacheUtils.getEntity(cacheKey, UserEntity.class);
if (jvmUserEntity != null) {
return jvmUserEntity;
}
// 2.查询二级缓存 (走redis)
String redisJson = RedisUtil.getString(cacheKey);
if (!StringUtils.isEmpty(redisJson)) {
UserEntity redisUser = JSONObject.parseObject(redisJson, UserEntity.class);
// 需要将数据存放在 jvm内存中
JvmMapCacheUtils.putEntity(cacheKey, redisUser);
return redisUser;
}
// 3.查询三级缓存 数据库
UserEntity dbUser = userMapper.findByUser(id);
if (dbUser == null) {
return null;
}
// 三级缓存中数据存放在二级和一级缓存
RedisUtil.setString(cacheKey, JSONObject.toJSONString(dbUser));
JvmMapCacheUtils.putEntity(cacheKey, dbUser);
// 扩展新增级别缓存 改动了
return dbUser;
}
}
装饰模式基本框架架构设计原理
在不改变原有代码的情况下,新增附加功能
装饰模式应用场景
多级缓存设计,mybatis中一级与二级缓存,io流/发送短信
image.png
装饰者模式定义(1)抽象组件;定义一个抽象接口,来规范准备附加功能的类
(2)具体组件:将要被附加功能的类,实现抽象角色接口
(3): 抽象装饰者:持有对具体构件角色的引用并定义与抽象构件角色一直的接口
4.具体装饰:实现抽象装饰者角色,负责对具体构件添加额外功能。
package com.taotao.decorate;
/**
* @Author: wangjin
* @CreateTime: 2022-06-21 22:55
*/
public interface ComponentCache {
/**
* 返回泛型T
*
* @param <T>
* @return
*/
<T> T getCache(String key,Integer id);
}
package com.taotao.decorate;
/**
* @Author: wangjin
* @CreateTime: 2022-06-22 21:03
*/
public interface AbstractDecorate extends ComponentCache{
}
package com.taotao.decorate;
import com.taotao.entity.UserEntity;
import com.taotao.utils.JvmMapCacheUtils;
import org.springframework.stereotype.Component;
/**
* @Author: wangjin
* @CreateTime: 2022-06-21 22:57
*/
@Component
public class JvmComponentCache implements ComponentCache{
@Override
public <T> T getCache(String key,Integer id) {
//1,一级缓存,从jvm中查询到数据
UserEntity userEntity= JvmMapCacheUtils.getEntity(key,UserEntity.class);
return (T) userEntity;
}
}
package com.taotao.decorate;
import com.alibaba.fastjson.JSONObject;
import com.taotao.entity.UserEntity;
import com.taotao.utils.JvmMapCacheUtils;
import com.taotao.utils.RedisUtil;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
/**
* @Author: wangjin
* @CreateTime: 2022-06-22 21:04
*/
@Component
public class RedisDecorate extends JvmComponentCache implements AbstractDecorate {
//二级装饰缓存,装饰不改变原来的代码实现增强
@Override
public <T> T getCache(String key,Integer id) {
//先查询一级缓存,一级缓存有数据直接返回
UserEntity jvmUser= super.getCache(key,id);
if(jvmUser !=null){
return (T)jvmUser;
}
//查询二级缓存,二级缓存如果有就返回
// 2.查询二级缓存 (走redis)
String redisJson = RedisUtil.getString(key);
if (!StringUtils.isEmpty(redisJson)) {
UserEntity redisUser = JSONObject.parseObject(redisJson, UserEntity.class);
// 需要将数据存放在 jvm内存中
JvmMapCacheUtils.putEntity(key, redisUser);
return (T)redisUser;
}
return null;
}
}
package com.taotao.decorate;
import com.alibaba.fastjson.JSONObject;
import com.taotao.entity.UserEntity;
import com.taotao.mapper.UserMapper;
import com.taotao.utils.JvmMapCacheUtils;
import com.taotao.utils.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @Author: wangjin
* @CreateTime: 2022-06-22 21:14
*/
@Component
public class MySQLDecorate extends RedisDecorate implements AbstractDecorate{
@Autowired
private UserMapper userMapper;
@Override
public <T> T getCache(String key,Integer id) {
UserEntity userEntity=super.getCache(key,id);
if(userEntity!=null){
return (T)userEntity;
}
// 3.查询三级缓存 数据库
UserEntity dbUser = userMapper.findByUser(id);
if (dbUser == null) {
return null;
}
// 三级缓存中数据存放在二级和一级缓存
RedisUtil.setString(key, JSONObject.toJSONString(dbUser));
JvmMapCacheUtils.putEntity(key, dbUser);
// 扩展新增级别缓存 改动了
return (T)dbUser;
}
}
package com.taotao.service;
import com.alibaba.fastjson.JSONObject;
import com.taotao.decorate.MySQLDecorate;
import com.taotao.entity.UserEntity;
import com.taotao.mapper.UserMapper;
import com.taotao.utils.JvmMapCacheUtils;
import com.taotao.utils.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author wangjin
* @title: MemberService
* @projectName designmodule
* @description: TODO
* @date 2022/6/19 0019 19:59
*/
@RestController
public class MemberService {
@Autowired
private UserMapper userMapper;
@Autowired
private MySQLDecorate mySQLDecorate;
@RequestMapping("/getMember")
public UserEntity getMember(Integer id) {
if (StringUtils.isEmpty(id)) {
return null;
}
// 1.先查询一级缓存,一级缓存有的情况下 直接返回 如果一级缓存没有的情况下则 查询二级缓存
String cacheKey = "com.mayikt.service.MemberService.getMember,Integer:" + id;
UserEntity jvmUserEntity = JvmMapCacheUtils.getEntity(cacheKey, UserEntity.class);
if (jvmUserEntity != null) {
return jvmUserEntity;
}
// 2.查询二级缓存 (走redis)
String redisJson = RedisUtil.getString(cacheKey);
if (!StringUtils.isEmpty(redisJson)) {
UserEntity redisUser = JSONObject.parseObject(redisJson, UserEntity.class);
// 需要将数据存放在 jvm内存中
JvmMapCacheUtils.putEntity(cacheKey, redisUser);
return redisUser;
}
// 3.查询三级缓存 数据库
UserEntity dbUser = userMapper.findByUser(id);
if (dbUser == null) {
return null;
}
// 三级缓存中数据存放在二级和一级缓存
RedisUtil.setString(cacheKey, JSONObject.toJSONString(dbUser));
JvmMapCacheUtils.putEntity(cacheKey, dbUser);
// 扩展新增级别缓存 改动了
return dbUser;
}
@RequestMapping("/getUser")
public UserEntity getUser(Integer id){
String cacheKey = "com.mayikt.service.MemberService.getMember,Integer:" + id;
return mySQLDecorate.getCache(cacheKey,id);
}
}