当前位置: 代码网 > it编程>数据库>Redis > 封装Redis工具类实践

封装Redis工具类实践

2025年12月08日 Redis 我要评论
一、封装redis工具类基于stringredistemplate封装一个缓存工具类,满足下列需求:方法1:将任意java对象序列化为json并存储在string类型的key中,并且可以设置ttl过期

一、封装redis工具类

基于stringredistemplate封装一个缓存工具类,满足下列需求:

  • 方法1:将任意java对象序列化为json并存储在string类型的key中,并且可以设置ttl过期时间
  • 方法2:将任意java对象序列化为json并存储在string类型的key中,并且可以设置逻辑过期时间,用于处理缓存击穿问题
  • 方法3:根据指定的key查询缓存,并反序列化为指定类型,利用缓存空值的方式解决缓存穿透问题
  • 方法4:根据指定的key查询缓存,并反序列化为指定类型,需要利用逻辑过期解决缓存击穿问题
  • 方法5:根据指定的key查询缓存,并反序列化为指定类型,需要利用互斥锁解决缓存击穿问题

1. 使用构造方法注入stringredistemplate

@slf4j
@component
public class cacheclient {

    private final stringredistemplate stringredistemplate;

    // 构造方法注入stringredistemplate
    public cacheclient(stringredistemplate stringredistemplate) {
        this.stringredistemplate = stringredistemplate;
    }
}

2. 方法1

将任意java对象序列化为json并存储在string类型的key中,并且可以设置ttl过期时间

public void set(string key, object value, long time, timeunit unit) {
    stringredistemplate.opsforvalue().set(key, jsonutil.tojsonstr(value), time, unit);
}

3. 方法2

将任意java对象序列化为json并存储在string类型的key中,并且可以设置逻辑过期时间,用于处理缓存击穿问题

public void setwithlogicalexpire(string key, object value, long time, timeunit unit) {
    // 设置逻辑过期
    redisdata redisdata = new redisdata();
    redisdata.setdata(value);
    redisdata.setexpiretime(localdatetime.now().plusseconds(unit.toseconds(time)));
    // 写入redis
    stringredistemplate.opsforvalue().set(key, jsonutil.tojsonstr(redisdata));
}

4. 方法3

根据指定的key查询缓存,并反序列化为指定类型,利用缓存空值的方式解决缓存穿透问题

/**
 * @param keyprefix 业务的缓存key前缀,例如:"cache:shop:"
 * @param id
 * @param type 对象类型,例如:user.class、shop.class
 * @param dbfallback 传入查询数据库的方法,例如:getbyid(id);
 * @param time
 * @param unit
 * @param <r>   返回值类型
 * @param <id>  id的类型,string、integer、long。。。
 * @return
 */
public <r,id> r querywithpassthrough(string keyprefix, id id, class<r> type, function<id, r> dbfallback, long time, timeunit unit){
    string key = keyprefix + id;
    // 1.从redis查询商铺缓存
    string json = stringredistemplate.opsforvalue().get(key);
    // 2.判断是否存在
    if (strutil.isnotblank(json)) {
        // 3.存在,直接返回
        return jsonutil.tobean(json, type);
    }
    // 判断命中的是否是空值
    if (json != null) {
        // 返回一个错误信息
        return null;
    }

    // 4.不存在,根据id查询数据库
    r r = dbfallback.apply(id);
    // 5.不存在,返回错误
    if (r == null) {
        // 将空值写入redis
        stringredistemplate.opsforvalue().set(key, "", cache_null_ttl, timeunit.minutes);
        // 返回错误信息
        return null;
    }
    // 6.存在,写入redis
    this.set(key, r, time, unit);
    return r;
}

5. 方法4

根据指定的key查询缓存,并反序列化为指定类型,需要利用逻辑过期解决缓存击穿问题

@data
public class redisdata {
    // 逻辑过期时间
    private localdatetime expiretime;
    // 缓存预热对象
    private object data;
}
public <r, id> r querywithlogicalexpire(string keyprefix, id id, class<r> type, function<id, r> dbfallback, long time, timeunit unit) {
     string key = keyprefix + id;
     // 1.从redis查询商铺缓存
     string json = stringredistemplate.opsforvalue().get(key);
     // 2.判断是否存在
     if (strutil.isblank(json)) {
         // 3.存在,直接返回
         return null;
     }
     // 4.命中,需要先把json反序列化为对象
     redisdata redisdata = jsonutil.tobean(json, redisdata.class);
     r r = jsonutil.tobean((jsonobject) redisdata.getdata(), type);
     localdatetime expiretime = redisdata.getexpiretime();
     // 5.判断是否过期
     if(expiretime.isafter(localdatetime.now())) {
         // 5.1.未过期,直接返回店铺信息
         return r;
     }
     // 5.2.已过期,需要缓存重建
     // 6.缓存重建
     // 6.1.获取互斥锁
     string lockkey = lock_shop_key + id;
     boolean islock = trylock(lockkey);
     // 6.2.判断是否获取锁成功
     if (islock){
         // 6.3.成功,开启独立线程,实现缓存重建
         cache_rebuild_executor.submit(() -> {
             try {
                 // 查询数据库
                 r newr = dbfallback.apply(id);
                 // 重建缓存
                 this.setwithlogicalexpire(key, newr, time, unit);
             } catch (exception e) {
                 throw new runtimeexception(e);
             }finally {
                 // 释放锁
                 unlock(lockkey);
             }
         });
     }
     // 6.4.返回过期的商铺信息
     return r;
 }

6. 方法5

根据指定的key查询缓存,并反序列化为指定类型,需要利用互斥锁解决缓存击穿问题

/**
 * 自定义互斥锁,利用redis的setnx方法来表示获取锁
 * @param key
 * @return
 */
private boolean trylock(string key) {
    boolean flag = stringredistemplate.opsforvalue().setifabsent(key, "1", lock_shop_ttl, timeunit.seconds);
    return booleanutil.istrue(flag);
}
/**
 * 释放互斥锁
 * @param key
 */
private void unlock(string key) {
    stringredistemplate.delete(key);
}
public <r, id> r querywithmutex(string keyprefix, id id, class<r> type, function<id, r> dbfallback, long time, timeunit unit) {
    string key = keyprefix + id;
    // 1.从redis查询商铺缓存
    string shopjson = stringredistemplate.opsforvalue().get(key);
    // 2.判断是否存在
    if (strutil.isnotblank(shopjson)) {
        // 3.存在,直接返回
        return jsonutil.tobean(shopjson, type);
    }
    // 判断命中的是否是空值
    if (shopjson != null) {
        // 返回一个错误信息
        return null;
    }

    // 4.实现缓存重建
    // 4.1.获取互斥锁
    string lockkey = lock_shop_key + id;
    r r = null;
    try {
        boolean islock = trylock(lockkey);
        // 4.2.判断是否获取成功
        if (!islock) {
            // 4.3.获取锁失败,休眠并重试
            thread.sleep(50);
            return querywithmutex(keyprefix, id, type, dbfallback, time, unit);
        }
        // 4.4.获取锁成功,根据id查询数据库
        r = dbfallback.apply(id);
        // 5.不存在,返回错误
        if (r == null) {
            // 将空值写入redis
            stringredistemplate.opsforvalue().set(key, "", cache_null_ttl, timeunit.minutes);
            // 返回错误信息
            return null;
        }
        // 6.存在,写入redis
        this.set(key, r, time, unit);
    } catch (interruptedexception e) {
        throw new runtimeexception(e);
    } finally {
        // 7.释放锁
        unlock(lockkey);
    }
    // 8.返回
    return r;
}

二、调用redis工具类

  • 在shopserviceimpl中,调用redis工具类
@service
public class shopserviceimpl extends serviceimpl<shopmapper, shop> implements ishopservice {

    @resource
    private cacheclient cacheclient;


    @override
    public result querybyid(long id) {
        // 解决缓存穿透
        shop shop = cacheclient.querywithpassthrough(cache_shop_key, id, shop.class, this::getbyid, cache_shop_ttl, timeunit.minutes);

        // 互斥锁解决缓存击穿
        // shop shop = cacheclient.querywithmutex(cache_shop_key, id, shop.class, this::getbyid, cache_shop_ttl, timeunit.minutes);

        // 逻辑过期解决缓存击穿
        // shop shop = cacheclient.querywithlogicalexpire(cache_shop_key, id, shop.class, this::getbyid, 20l, timeunit.seconds);

        if (shop == null) {
            return result.fail("店铺不存在!");
        }

        // 返回
        return result.ok(shop);
    }
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com