Talking about 23 Design Patterns - Based on Decorative Patterns ~ Designing Multi-level Cache Framework


Designing multi-level cache based on decoration pattern

1. Decoration mode
1. Review the basic concepts of multi-level caching

In the actual development project, in order to reduce the access pressure of the database, we will cache the data in memory,
such as: Redis (distributed cache), EHCHE (JVM built-in cache).
For example, in the early morning, the project is relatively small and may not use Redis to do For caching, the built-in caching framework of the JVM is used. When the
project is relatively large, the Redis distributed caching framework is used. At this time, the first-level and second-level caches need to be designed.

2. The basic concept of decoration mode

Add additional functions without changing the original code
insert image description here

3. Application scenarios of decoration mode

Multi-level cache design, first-level and second-level cache in mybatis, IO stream

4. Decorator pattern definition

(1) Abstract component: Define an abstract interface to standardize the class that prepares additional functions
(2) Concrete component: The class to be added functions implements the abstract component role interface
(3) Abstract decorator: Holds the role of the specific component Refer to and define the interface consistent with the abstract component role
(4) Concrete decoration: implement the abstract decorator role, responsible for adding additional functions to the concrete component.

5. Handwritten JVM built-in cache based on Map
package com.gblfy.utils;

import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 基于Map手写Jvm内置缓存
 *
 * @Author gblfy
 * @Date 2022-03-15 21:01
 **/
@Component
public class JvmMapCacheUtils {
    
    

    /**
     * 并发缓存容器
     */
    private static Map<String, String> cacheList = new ConcurrentHashMap<String, String>();


    /**
     * 从本地缓存中根据key获取值
     *
     * @param key 缓存key
     * @param t   返回对象类型
     * @param <T> 返回对象
     * @return
     */
    public static <T> T getCache(String key, Class<T> t) {
    
    
        //    缓存存储对象
        String jsonValue = cacheList.get(key);
        return JSONObject.parseObject(jsonValue, t);
    }

    /**
     * 缓存数据到本地jvm中
     *
     * @param key
     * @param val
     */
    public static void putCache(String key, Object val) {
    
    
        String jsonValue = JSONObject.toJSONString(val);
        cacheList.put(key, jsonValue);
    }

    /**
     * 根据缓存key删除指定缓存数据
     *
     * @param cacheKey
     */
    public static void removeCacheCacheKey(String cacheKey) {
    
    
        cacheList.remove(cacheKey);
    }

    /**
     * 更新缓存数据
     *
     * @param key
     * @param val
     */
    public static void updateCacheByCacheKey(String key, Object val) {
    
    
        String jsonValue = JSONObject.toJSONString(val);
        cacheList.put(key, jsonValue);
    }

}

Second, handwritten first-level and second-level cache
2.1. redis tool class
package com.gblfy.utils;

import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * redis工具类
 *
 * @author gblfy
 * @date 2022-03-15
 */
@Component
public class RedisUtils {
    
    

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    // 如果key存在的话返回fasle 不存在的话返回true
    public Boolean setNx(String key, String value, Long timeout) {
    
    
        Boolean setIfAbsent = stringRedisTemplate.opsForValue().setIfAbsent(key, value);
        if (timeout != null) {
    
    
            stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
        }
        return setIfAbsent;
    }

    /**
     * 存放string类型
     *
     * @param key     key
     * @param data    数据
     * @param timeout 超时间
     */
    public void setString(String key, String data, Long timeout) {
    
    
        stringRedisTemplate.opsForValue().set(key, data);
        if (timeout != null) {
    
    
            stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
        }
    }

    /**
     * 存放string类型
     *
     * @param key  key
     * @param data 数据
     */
    public void setString(String key, String data) {
    
    
        setString(key, data, null);
    }

    /**
     * 根据key查询string类型
     *
     * @param key
     * @return
     */
    public String getString(String key) {
    
    
        String value = stringRedisTemplate.opsForValue().get(key);
        return value;
    }

    public <T> T getEntity(String key, Class<T> t) {
    
    
        String json = getString(key);
        return JSONObject.parseObject(json, t);
    }

    public void putEntity(String key, Object object) {
    
    
        String json = JSONObject.toJSONString(object);
        setString(key, json);
    }

    /**
     * 根据对应的key删除key
     *
     * @param key
     */
    public boolean delKey(String key) {
    
    
        return stringRedisTemplate.delete(key);
    }


    public void setList(String key, List<String> listToken) {
    
    
        stringRedisTemplate.opsForList().leftPushAll(key, listToken);
    }

    public StringRedisTemplate getStringRedisTemplate() {
    
    
        return stringRedisTemplate;
    }
}

2.2. Entity class
package com.gblfy.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@Data
@TableName("gblfy_user")
public class UserEntity {
    
    
    // 主键
    @TableId(value = "user_id", type = IdType.ASSIGN_ID)
    private Integer userId;
    //用户名称
    @TableField("name")
    private String name;
}

2.3. Interface
package com.gblfy.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gblfy.entity.UserEntity;
import org.apache.ibatis.annotations.Select;

public interface UserMapper extends BaseMapper<UserEntity> {
    
    
}
2.4. Database scripts
drop database  IF EXISTS `design_pattern`;
create database `design_pattern`;
use `design_pattern`;

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for gblfy_strategy
-- ----------------------------
DROP TABLE IF EXISTS `gblfy_user`;
CREATE TABLE `gblfy_user` (
  `user_id` int NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `name` varchar(32) NOT NULL COMMENT '用户名称',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表';

INSERT INTO `gblfy_user` VALUES (1, '雨昕');

2.5. Test Cases
package com.gblfy.controller;

import com.gblfy.entity.UserEntity;
import com.gblfy.mapper.UserMapper;
import com.gblfy.utils.JvmMapCacheUtils;
import com.gblfy.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 使用装饰模式~查询用户数据
 *
 * @Author gblfy
 * @Date 2022-03-15 21:12
 **/
@Slf4j
@RestController
public class UserController {
    
    
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RedisUtils redisUtils;

    @GetMapping("/getUser")
    public UserEntity getUser(Integer userId) {
    
    
        //一级缓存和二级缓存
        //方法名+参数类型+参数
        String key = "getUser(Integer)" + userId;
        //先查询二级缓存
        UserEntity redisUser = redisUtils.getEntity(key, UserEntity.class);
        if (redisUser != null) {
    
    
            return redisUser;
        }

        //先查询我们的一级缓存(jvm内置缓存)
        UserEntity jvmUser = JvmMapCacheUtils.getCache(key, UserEntity.class);
        if (jvmUser != null) {
    
    

            //当一级缓存不为空时,将内容添加到二级缓存redia中,减少一级缓存的查询压力
            redisUtils.putEntity(key, jvmUser);
            return jvmUser;
        }

        //查询我们的db
        UserEntity dbUser = userMapper.selectById(userId);
        if (dbUser == null) {
    
    
            return null;
        }

        //将db查询的内容添加到一级缓存中,减少数据库压力
        JvmMapCacheUtils.putCache(key, dbUser);
        return dbUser;
    }
}

2.6. Test effect sharing

When the user data is queried for the first time, the process is as follows:
first determine whether it exists in redis, if not, query whether it exists in
the jvm cache When it does not exist in the jvm cache, query the database, and then add the queried data to the jvm cache

When querying user data for the second time, the process is as follows:
first determine whether it exists in redis, if not, query whether it exists in
the jvm cache When it exists in the jvm cache, first add the queried data to the redis cache, and then return the response cache data

When the user data is queried for the third time, the process is as follows:
first determine whether it exists in redis, and if it exists, return the cached data directly

3. Design a multi-level cache framework
3.1. Cache Container Abstraction
package com.gblfy.decoration;

import org.aspectj.lang.ProceedingJoinPoint;

/**
 * 缓存容器抽象
 *
 * @Author gblfy
 * @Date 2022-03-15 21:42
 **/
public interface ComponentCache {
    
    

    /**
     * 根据key查询缓存数据
     *
     * @param key       缓存的key
     * @param t         传入对象参数类型
     * @param joinPoint 目标方法内容
     * @param <T>       返回的对象类型
     * @return
     */
    <T> T getCacheEntity(String key, Class<T> t, ProceedingJoinPoint joinPoint);
}

3.2. Level 1 JVM cache
package com.gblfy.decoration.impl;

import com.gblfy.decoration.ComponentCache;
import com.gblfy.utils.JvmMapCacheUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;

/**
 * 一级缓存查询处理类
 *
 * @Author gblfy
 * @Date 2022-03-15 21:45
 **/
@Component
public class JvmComponentCache implements ComponentCache {
    
    

    // @Autowired
    // private UserMapper userMapper;

    @Override
    public <T> T getCacheEntity(String key, Class<T> t, ProceedingJoinPoint joinPoint) {
    
    
        //先查询我们的一级缓存(jvm内置缓存)
        T jvmUser = JvmMapCacheUtils.getCache(key, t);
        if (jvmUser != null) {
    
    
            return (T) jvmUser;
        }
        //查询我们的db
        // UserEntity dbUser = userMapper.selectById("1");
        // if (dbUser == null) {
    
    
        //     return null;
        // }

        try {
    
    
            /**
             * 当一级缓存不存在时,查询我们的db,相当于userMapper.selectById(userId)这一行代码
             * 1.通过aop直接获取目标对象的方法
             * 解析:
             * 目的:执行joinPoint.proceed();这一行就相当于执行目标方法,为了做成抽象通用的,
             * 方案:采用aop来实现仅此而已
             */
            Object resultDb = joinPoint.proceed();
            //将db查询的内容添加到一级缓存中,减少数据库压力
            JvmMapCacheUtils.putCache(key, resultDb);
            return (T) resultDb;
        } catch (Throwable e) {
    
    
            e.printStackTrace();
            return null;
        }
    }
}

3.3. L2 cache abstract interface
package com.gblfy.decoration;

/**
 * 二级缓存抽象接口
 *
 * @author gblfy
 * @date 2022-03-16
 */
public interface AbstractDecorate extends ComponentCache {
    
    
}
3.4. Add L2 cache
package com.gblfy.decoration.impl;

import com.gblfy.decoration.AbstractDecorate;
import com.gblfy.utils.RedisUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * 二级缓存查询处理类
 *
 * @Author gblfy
 * @Date 2022-03-15 21:50
 **/
@Component
public class RedistDecorate extends JvmComponentCache implements AbstractDecorate {
    
    
    @Autowired
    private RedisUtils redisUtils;
    // @Autowired
    // private JvmComponentCache jvmComponentCache;

    @Override
    public <T> T getCacheEntity(String key, Class<T> t, ProceedingJoinPoint joinPoint) {
    
    

        //先查询二级缓存
        T tRedis = redisUtils.getEntity(key, t);
        if (tRedis != null) {
    
    
            return (T) tRedis;
        }

        //先查询我们的一级缓存(jvm内置缓存)
        T tJvm = super.getCacheEntity(key, t, joinPoint);

        //如果 extends JvmComponentCache的话可以写成上面super.getCacheEntity(key)这种,前提是装饰类不能new
        // UserEntity jvmUser = jvmComponentCache.getCacheEntity(key);
        if (tJvm == null) {
    
    
            return null;
        }

        //当一级缓存不为空时,将内容添加到二级缓存redia中,减少一级缓存的查询压力
        redisUtils.putEntity(key, tJvm);
        return (T) tJvm;
    }
}

3.5. Aop and custom annotations
package com.gblfy.annotation;

import java.lang.annotation.*;

/**
 * 二级缓存查询aop拦截
 *
 * @author gblfy
 * @date 2022-03-15
 */
@Target({
    
    ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExtGblfyCache {
    
    
}
3.6. Implement second-level cache query aop interception
package com.gblfy.aop;

import com.gblfy.decoration.GblfyCache;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * 实现二级缓存查询aop拦截
 * 自定义ExtGblfyCache注解
 *
 * @author gblfy
 * @date 2022-03-15
 */
@Aspect
@Component
@Slf4j
public class ExtAsyncAop {
    
    
    @Autowired
    private GblfyCache gblfyCache;

    @Around(value = "@annotation(com.gblfy.annotation.ExtGblfyCache)")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
    
    
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        //获取目标方法
        Method targetMethod = methodSignature.getMethod();
        //缓存key拼接(方法名+参数类型+参数值)
        String cacheKey = targetMethod.getName() + "," + Arrays.toString(targetMethod.getParameterTypes());
        log.info(">>cacheKey:" + cacheKey);

        // 开始先查询二级缓存是否存在
        return gblfyCache.getCacheEntity(cacheKey, targetMethod.getReturnType(), joinPoint);
        //    这里的泛型T等于方法的返回结果类型简言之targetMethod.getReturnType()
    }
}

3.7. L2 Cache Shell Encapsulation
package com.gblfy.decoration;

import com.gblfy.decoration.impl.RedistDecorate;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * 二级缓存外壳封装
 *
 * @Author gblfy
 * @Date 2022-03-15 22:01
 **/
@Component
public class GblfyCache {
    
    

    @Autowired
    private RedistDecorate redistDecorate;

    public <T> T getCacheEntity(String key, Class<T> t, ProceedingJoinPoint joinPoint) {
    
    
        return redistDecorate.getCacheEntity(key, t, joinPoint);
    }
}

3.8. Cache Container Abstraction
package com.gblfy.controller;

import com.gblfy.annotation.ExtGblfyCache;
import com.gblfy.entity.UserEntity;
import com.gblfy.mapper.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 使用装饰模式~查询用户数据
 *
 * @Author gblfy
 * @Date 2022-03-15 21:12
 **/
@Slf4j
@RestController
public class UserController {
    
    
    @Autowired
    private UserMapper userMapper;

    @GetMapping("/getUser")
    @ExtGblfyCache
    public UserEntity getUser(Integer userId) {
    
    
        return userMapper.selectById(userId);
    }
 }
3.9. Request Process Link

When we access http://localhost:8080/getUser?userId=1the method, it will be intercepted by aop because of the @ExtGblfyCache annotation on the method.
insert image description here

insert image description here

insert image description here

insert image description here
insert image description here
insert image description here


When the local secondary query is performed, only redis will be queried, and the query will be returned directly.

3.10. Open Source Projects

https://gitee.com/gblfy/design-pattern/tree/decoration-mode/

Guess you like

Origin blog.csdn.net/weixin_40816738/article/details/123521172