美文网首页
03.装饰模式设计多级缓存框架&外观模式

03.装饰模式设计多级缓存框架&外观模式

作者: 滔滔逐浪 | 来源:发表于2022-06-21 22:39 被阅读0次
image.png

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);
    }

}


相关文章

网友评论

      本文标题:03.装饰模式设计多级缓存框架&外观模式

      本文链接:https://www.haomeiwen.com/subject/hwjrvrtx.html