51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

模板方法模式实现Redis缓存查询简化

简述 {#简述}

在高并发场景下查询缓存时很容易出现缓存击穿(本文针对单机没有使用分布式锁),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力,因此查询缓存需要进行加锁,但这种代码每次写多了很烦,而且容易写错,因此本文采用模板方法模式简化缓存查询及并发处理

快速开始 {#快速开始}

Redis Dependency:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

Redis Service

public interface RedisService {
    /**
     * 存储数据
     */
    void set(String key, String value);
/**
 * SET key value EX seconds,设置key-value和超时时间(秒)
 * 存储数据并设置过期时间
 *
 * @param key cache key
 * @param value cache value
 * @param expire expire time,time unit is seconds
 */
void set(String key, String value, long expire);

/**

  • 存储数据并设置过期时间通知指定过期时间的单位
  • @param key 数据的键
  • @param value 值
  • @param expire 过期时间
  • @param timeUnit 过期时间的单位 */ void set(String key, String value, long expire, TimeUnit timeUnit);

/**

  • 获取数据 */ String get(String key);

/**

  • 设置超期时间
  • @param key
  • @param expire 过期时间
  • @return 设置成功返回true, 否则返回false */ boolean expire(String key, long expire);

/**

  • 删除数据 */ void remove(String key);

/**

  • 自增操作
  • @param delta 自增步长 */ Long increment(String key, long delta);

}


Redis Service Impl

@Service
public class RedisServiceImpl implements RedisService {
    private static final long DEFAULT_EXPIRE_SECONDS = 10;
private final StringRedisTemplate redisTemplate;

@Autowired public RedisServiceImpl(StringRedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; }

@Override public void set(String key, String value) { redisTemplate.opsForValue().set(key, value, DEFAULT_EXPIRE_SECONDS, TimeUnit.MINUTES); }

@Override public void set(String key, String value, long expire) { redisTemplate.opsForValue().set(key, value, expire, TimeUnit.SECONDS); }

@Override public void set(String key, String value, long expire, TimeUnit timeUnit) { redisTemplate.opsForValue().set(key, value, expire, timeUnit); }

@Override public String get(String key) { return redisTemplate.opsForValue().get(key); }

@Override public boolean expire(String key, long expire) { Boolean result = redisTemplate.expire(key, expire, TimeUnit.SECONDS); if(result == null) { return false; } return result; }

@Override public void remove(String key) { redisTemplate.delete(key); }

@Override public Long increment(String key, long delta) { return redisTemplate.opsForValue().increment(key,delta); }

}


Redis Key Util:

public class RedisUtils {
    /**
     * 主数据系统标识
     */
    private static final String KEY_PREFIX = "halo";
    /**
     * 分割字符,默认[:],使用:可用于rdm分组查看
     */
    private static final String KEY_SPLIT_CHAR = ":";
/**
 * redis的key键规则定义
 * @param module 模块名称
 * @param service service名称
 * @param args 参数..
 * @return key
 */
public static String keyBuilder(String module, String service, String... args) {
    return keyBuilder(null, module, service, args);
}

/**

  • redis的key键规则定义
  • @param module 模块名称
  • @param service service名称
  • @param objStr 对象.toString()
  • @return key */ public static String keyBuilder(String module, String service, String objStr) { return keyBuilder(null, module, service, new String[]{objStr}); }

/**

  • redis的key键规则定义
  • @param prefix 项目前缀
  • @param module 模块名称
  • @param service service名称
  • @param objStr 对象.toString()
  • @return key */ public static String keyBuilder(String prefix, String module, String service, String objStr) { return keyBuilder(prefix, module, service, new String[]{objStr}); }

/**

  • redis的key键规则定义
  • @param prefix 项目前缀
  • @param module 模块名称
  • @param service 方法名称
  • @param args 参数..
  • @return key */ public static String keyBuilder(String prefix, String module, String service, String... args) { // 项目前缀 if (prefix == null) { prefix = KEY_PREFIX; } StringBuilder key = new StringBuilder(prefix); // KEY_SPLIT_CHAR 为分割字符 key.append(KEY_SPLIT_CHAR).append(module).append(KEY_SPLIT_CHAR).append(service); for (String arg : args) { key.append(KEY_SPLIT_CHAR).append(arg); } return key.toString(); }

/**

  • redis的key键规则定义
  • @param cacheEnum 枚举对象
  • @param objStr 对象.toString()
  • @return key */ public static String keyBuilder(CacheEnum cacheEnum, String objStr) { return keyBuilder(cacheEnum.getPrefix(), cacheEnum.getModule(), cacheEnum.getService(), objStr); }

}


Cache Enum

@Data
@AllArgsConstructor
public enum CacheEnum {
    CMS_POST("halo", "cms", "PostService", "文章缓存");
    //other enum....
`/**
* key前缀
`/
private String prefix;
/`*
* 模块名称
`/
private String module;
/`*
* service名称
`/
private String service;
/`*
* 描述
*/
private String desc;
}
`

CacheTemplate:


@Component
public class CacheTemplate {
    @Autowired
    private RedisService redisService;
public &amp;lt;T&amp;gt; T findCache(String key, Class&amp;lt;T&amp;gt; clazz, LoadCallback&amp;lt;T&amp;gt; loadCallback) {
    String cacheValueString =  redisService.get(key);
    if(StringUtils.isEmpty(cacheValueString)) {
        synchronized (this) {
            cacheValueString =  redisService.get(key);
            if(ObjectUtils.isEmpty(cacheValueString)) {
                T dataOfMysql = loadCallback.load();
            // 如果为null也放入缓存,过期时间为1分钟
            if (dataOfMysql == null) {
                redisService.set(key, &amp;quot;null&amp;quot;, 60L);
            } else {
                // 否则放入缓存,过期时间为10分钟
                redisService.set(key, JSON.toJSONString(dataOfMysql));
            }
        return dataOfMysql;
    }

    return JSON.parseObject(cacheValueString, clazz);
}

} else { return JSON.parseObject(cacheValueString, clazz); }

}

public &amp;lt;T&amp;gt; T findCache(String key, TypeReference&amp;lt;T&amp;gt; typeReference, LoadCallback&amp;lt;T&amp;gt; loadCallback) { String cacheValueString = redisService.get(key); if(StringUtils.isEmpty(cacheValueString)) { synchronized (this) { cacheValueString = redisService.get(key); if(ObjectUtils.isEmpty(cacheValueString)) { T dataOfMysql = loadCallback.load(); // 如果为null也放入缓存,过期时间为1分钟 if (dataOfMysql == null) { redisService.set(key, &quot;null&quot;, 60L); } else { // 否则放入缓存,过期时间为10分钟 redisService.set(key, JSON.toJSONString(dataOfMysql)); }

            return dataOfMysql;
        }
    return JSON.parseObject(cacheValueString, typeReference);
}

} else { return JSON.parseObject(cacheValueString, typeReference); }

}

public &amp;lt;T&amp;gt; T findCache(String key, long expire, TypeReference&amp;lt;T&amp;gt; typeReference, LoadCallback&amp;lt;T&amp;gt; loadCallback) { String cacheValueString = redisService.get(key);

if(StringUtils.isEmpty(cacheValueString)) {
    synchronized (this) {
        cacheValueString =  redisService.get(key);
        if(ObjectUtils.isEmpty(cacheValueString)) {
            T dataOfMysql = loadCallback.load();
        // 如果为null也放入缓存,过期时间为1分钟
        if (dataOfMysql == null) {
            redisService.set(key, &amp;amp;quot;null&amp;amp;quot;, 60L);
        } else {
            // 否则放入缓存,过期时间为10分钟
            redisService.set(key, JSON.toJSONString(dataOfMysql), expire);
        }

        return dataOfMysql;
    }

    return JSON.parseObject(cacheValueString, typeReference);
}

} else { return JSON.parseObject(cacheValueString, typeReference); }

}

}


LoadCallback

@FunctionalInterface
public interface LoadCallback<T> {
    /**
     * 从数据库查询数据逻辑,查询后的结果会被优先放入缓存
     * @return 返回查询结果对象
     */
    T load();
}

Example:

@Slf4j
@Service
public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostService {
    private final PostRepository postRepository;
    private final CacheTemplate cacheTemplate;
@Autowired
public PostServiceImpl(PostRepository postRepository,
                      CacheTemplate cacheTemplate) {
    this.postRepository = postRepository;
    this.cacheTemplate = cacheTemplate;
}

public Post getBy(PostStatus status, String slug) { // 构建缓存key String cacheKey = RedisUtils.keyBuilder(CacheEnum.CMS_POST, &quot;status:&quot; + status.getValue() + &quot;:slug:&quot; + slug); return cacheTemplate.findCache(cacheKey, Post.class, ()-&amp;gt;{ // 从数据库查询数据的逻辑,这里可以简化为一行写 return super.getBy(status, slug); }); }

}


赞(1)
未经允许不得转载:工具盒子 » 模板方法模式实现Redis缓存查询简化