当前位置:

首页 > 编程开发 > Redis+Caffeine如何实现分布式二级缓存组件

Redis+Caffeine如何实现分布式二级缓存组件

Redis Assistant
Redis Assistant

Redis Assistant 是一款为Redis设计的高效可视化管理与监控工具,提供多种操作模式,高效处理服务器数据。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

立即下载
¥19
Windows/macOS/Linux 2026-08-22
正版软件 Windows macOS软件 Linux 数据库管理工具

所谓二级缓存缓存就是将数据从读取较慢的介质上读取出来放到读取较快的介质上,如磁盘-->内存。平时我们会将数据存储到磁盘上,如:数据库。如果每次都从数据库里去读取,会因为磁盘本身的IO影响读取速度,所以就有了像redis这种的内存缓存。可以将数据读取出来放到内存里,这样当需要获取数据时,就能够直接从内存中拿到数据返回,能够很大程度的提高速度。但是一般redis是单独部署成集群,所以会有网络IO上的消耗,虽然与redis集群的链接已经有连接池这种工具,但是数据传输上也还是会有一定消耗。所以就有了进程内缓存,如

本文目录

    所谓二级缓存

    缓存就是将数据从读取较慢的介质上读取出来放到读取较快的介质上,如磁盘-->内存。

    平时我们会将数据存储到磁盘上,如:数据库。如果每次都从数据库里去读取,会因为磁盘本身的IO影响读取速度,所以就有了像redis这种的内存缓存。可以将数据读取出来放到内存里,这样当需要获取数据时,就能够直接从内存中拿到数据返回,能够很大程度的提高速度。
    但是一般redis是单独部署成集群,所以会有网络IO上的消耗,虽然与redis集群的链接已经有连接池这种工具,但是数据传输上也还是会有一定消耗。所以就有了进程内缓存,如:caffeine。当应用内缓存有符合条件的数据时,就可以直接使用,而不用通过网络到redis中去获取,这样就形成了两级缓存。应用内缓存叫做一级缓存,远程缓存(如redis)叫做二级缓存。

    • 系统是否需要缓存CPU占用:如果你有某些应用需要消耗大量的cpu去计算获得结果。

    • 数据库IO占用:如果你发现你的数据库连接池比较空闲,那么不应该用缓存。但是如果数据库连接池比较繁忙,甚至经常报出连接不够的报警,那么是时候应该考虑缓存了。

    分布式二级缓存的优势

    Redis用来存储热点数据,Redis中没有的数据则直接去数据库访问。
    已经有Redis了,干嘛还需要了解Guava,Caffeine这些进程缓存呢:

    • Redis如果不可用,这个时候我们只能访问数据库,很容易造成雪崩,但一般不会出现这种情况。

    • 访问Redis会有一定的网络I/O以及序列化反序列化开销,虽然性能很高但是其终究没有本地方法快,可以将最热的数据存放在本地,以便进一步加快访问速度。这个思路并不是我们做互联网架构独有的,在计算机系统中使用L1,L2,L3多级缓存,用来减少对内存的直接访问,从而加快访问速度。

    所以如果仅仅是使用Redis,能满足我们大部分需求,但是当需要追求更高的性能以及更高的可用性的时候,那就不得不了解多级缓存。

    二级缓存操作过程数据读流程描述

    Redis+Caffeine如何实现分布式二级缓存组件

    redis 与本地缓存都查询不到值的时候,会触发更新过程,整个过程是加锁的缓存失效流程描述

    Redis+Caffeine如何实现分布式二级缓存组件

    redis更新与删除缓存key都会触发,清除redis缓存后

    如何使用组件?

    组件是基于Spring Cache框架上改造的,在项目中使用分布式缓存,仅仅需要在缓存注解上增加:cacheManager ="L2_CacheManager",或者 cacheManager = CacheRedisCaffeineAutoConfiguration.分布式二级缓存

    //这个方法会使用分布式二级缓存来提供查询
    @Cacheable(cacheNames = CacheNames.CACHE_12HOUR, cacheManager = "L2_CacheManager")
    public Config getAllValidateConfig() { 
    }

    如果你想既使用分布式缓存,又想用分布式二级缓存组件,那你需要向Spring注入一个 @Primary  的 CacheManager bean

    @Primary
    @Bean("deaultCacheManager")
    public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
        // 生成一个默认配置,通过config对象即可对缓存进行自定义配置
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        // 设置缓存的默认过期时间,也是使用Duration设置
        config = config.entryTtl(Duration.ofMinutes(2)).disableCachingNullValues();
    
        // 设置一个初始化的缓存空间set集合
        Set cacheNames =  new HashSet<>();
        cacheNames.add(CacheNames.CACHE_15MINS);
        cacheNames.add(CacheNames.CACHE_30MINS);
    
        // 对每个缓存空间应用不同的配置
        Map configMap = new HashMap<>();
        configMap.put(CacheNames.CACHE_15MINS, config.entryTtl(Duration.ofMinutes(15)));
        configMap.put(CacheNames.CACHE_30MINS, config.entryTtl(Duration.ofMinutes(30)));
      
        // 使用自定义的缓存配置初始化一个cacheManager
        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
            .initialCacheNames(cacheNames)  // 注意这两句的调用顺序,一定要先调用该方法设置初始化的缓存名,再初始化相关的配置
            .withInitialCacheConfigurations(configMap)
            .build();
        return cacheManager;
    }

    然后:

    //这个方法会使用分布式二级缓存
    @Cacheable(cacheNames = CacheNames.CACHE_12HOUR, cacheManager = "L2_CacheManager")
    public Config getAllValidateConfig() {
    }
    
    //这个方法会使用分布式缓存
    @Cacheable(cacheNames = CacheNames.CACHE_12HOUR)
    public Config getAllValidateConfig2() {
    }

    核心实现方法

    核心其实就是实现 org.springframework.cache.CacheManager接口与继承org.springframework.cache.support.AbstractValueAdaptingCache,在Spring缓存框架下实现缓存的读与写。

    RedisCaffeineCacheManager实现CacheManager 接口

    RedisCaffeineCacheManager.class 主要来管理缓存实例,根据不同的 CacheNames 生成对应的缓存管理bean,然后放入一个map中。

    package com.axin.idea.rediscaffeinecachestarter.support;
    
    import com.axin.idea.rediscaffeinecachestarter.CacheRedisCaffeineProperties;
    import com.github.benmanes.caffeine.cache.Caffeine;
    import com.github.benmanes.caffeine.cache.stats.CacheStats;
    import lombok.extern.slf4j.Slf4j;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.cache.Cache;
    import org.springframework.cache.CacheManager;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.util.CollectionUtils;
    
    import java.util.*;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.ConcurrentMap;
    import java.util.concurrent.TimeUnit;
    
    @Slf4j
    public class RedisCaffeineCacheManager implements CacheManager {
    
        private final Logger logger = LoggerFactory.getLogger(RedisCaffeineCacheManager.class);
    
        private static ConcurrentMap cacheMap = new ConcurrentHashMap();
    
        private CacheRedisCaffeineProperties cacheRedisCaffeineProperties;
    
        private RedisTemplate stringKeyRedisTemplate;
    
        private boolean dynamic = true;
    
        private Set cacheNames;
        {
            cacheNames = new HashSet<>();
            cacheNames.add(CacheNames.CACHE_15MINS);
            cacheNames.add(CacheNames.CACHE_30MINS);
            cacheNames.add(CacheNames.CACHE_60MINS);
            cacheNames.add(CacheNames.CACHE_180MINS);
            cacheNames.add(CacheNames.CACHE_12HOUR);
        }
        public RedisCaffeineCacheManager(CacheRedisCaffeineProperties cacheRedisCaffeineProperties,
                                         RedisTemplate stringKeyRedisTemplate) {
            super();
            this.cacheRedisCaffeineProperties = cacheRedisCaffeineProperties;
            this.stringKeyRedisTemplate = stringKeyRedisTemplate;
            this.dynamic = cacheRedisCaffeineProperties.isDynamic();
        }
    
        //——————————————————————— 进行缓存工具 ——————————————————————
        /**
        * 清除所有进程缓存
        */
        public void clearAllCache() {
            stringKeyRedisTemplate.convertAndSend(cacheRedisCaffeineProperties.getRedis().getTopic(), new CacheMessage(null, null));
        }
    
        /**
        * 返回所有进程缓存(二级缓存)的统计信息
        * result:{"缓存名称":统计信息}
        * @return
        */
        public static Map getCacheStats() {
            if (CollectionUtils.isEmpty(cacheMap)) {
                return null;
            }
    
            Map result = new LinkedHashMap<>();
            for (Cache cache : cacheMap.values()) {
                RedisCaffeineCache caffeineCache = (RedisCaffeineCache) cache;
                result.put(caffeineCache.getName(), caffeineCache.getCaffeineCache().stats());
            }
            return result;
        }
    
        //—————————————————————————— core —————————————————————————
        @Override
        public Cache getCache(String name) {
            Cache cache = cacheMap.get(name);
            if(cache != null) {
                return cache;
            }
            if(!dynamic && !cacheNames.contains(name)) {
                return null;
            }
    
            cache = new RedisCaffeineCache(name, stringKeyRedisTemplate, caffeineCache(name), cacheRedisCaffeineProperties);
            Cache oldCache = cacheMap.putIfAbsent(name, cache);
            logger.debug("create cache instance, the cache name is : {}", name);
            return oldCache == null ? cache : oldCache;
        }
    
        @Override
        public Collection getCacheNames() {
            return this.cacheNames;
        }
    
        public void clearLocal(String cacheName, Object key) {
            //cacheName为null 清除所有进程缓存
            if (cacheName == null) {
                log.info("清除所有本地缓存");
                cacheMap = new ConcurrentHashMap<>();
                return;
            }
    
            Cache cache = cacheMap.get(cacheName);
            if(cache == null) {
                return;
            }
    
            RedisCaffeineCache redisCaffeineCache = (RedisCaffeineCache) cache;
            redisCaffeineCache.clearLocal(key);
        }
    
        /**
        * 实例化本地一级缓存
        * @param name
        * @return
        */
        private com.github.benmanes.caffeine.cache.Cache caffeineCache(String name) {
            Caffeine cacheBuilder = Caffeine.newBuilder();
            CacheRedisCaffeineProperties.CacheDefault cacheConfig;
            switch (name) {
                case CacheNames.CACHE_15MINS:
                    cacheConfig = cacheRedisCaffeineProperties.getCache15m();
                    break;
                case CacheNames.CACHE_30MINS:
                    cacheConfig = cacheRedisCaffeineProperties.getCache30m();
                    break;
                case CacheNames.CACHE_60MINS:
                    cacheConfig = cacheRedisCaffeineProperties.getCache60m();
                    break;
                case CacheNames.CACHE_180MINS:
                    cacheConfig = cacheRedisCaffeineProperties.getCache180m();
                    break;
                case CacheNames.CACHE_12HOUR:
                    cacheConfig = cacheRedisCaffeineProperties.getCache12h();
                    break;
                default:
                    cacheConfig = cacheRedisCaffeineProperties.getCacheDefault();
            }
            long expireAfterAccess = cacheConfig.getExpireAfterAccess();
            long expireAfterWrite = cacheConfig.getExpireAfterWrite();
            int initialCapacity = cacheConfig.getInitialCapacity();
            long maximumSize = cacheConfig.getMaximumSize();
            long refreshAfterWrite = cacheConfig.getRefreshAfterWrite();
    
            log.debug("本地缓存初始化:");
            if (expireAfterAccess > 0) {
                log.debug("设置本地缓存访问后过期时间,{}秒", expireAfterAccess);
                cacheBuilder.expireAfterAccess(expireAfterAccess, TimeUnit.SECONDS);
            }
            if (expireAfterWrite > 0) {
                log.debug("设置本地缓存写入后过期时间,{}秒", expireAfterWrite);
                cacheBuilder.expireAfterWrite(expireAfterWrite, TimeUnit.SECONDS);
            }
            if (initialCapacity > 0) {
                log.debug("设置缓存初始化大小{}", initialCapacity);
                cacheBuilder.initialCapacity(initialCapacity);
            }
            if (maximumSize > 0) {
                log.debug("设置本地缓存最大值{}", maximumSize);
                cacheBuilder.maximumSize(maximumSize);
            }
            if (refreshAfterWrite > 0) {
                cacheBuilder.refreshAfterWrite(refreshAfterWrite, TimeUnit.SECONDS);
            }
            cacheBuilder.recordStats();
            return cacheBuilder.build();
        }
    }

    RedisCaffeineCache 继承 AbstractValueAdaptingCache

    核心是get方法与put方法。

    package com.axin.idea.rediscaffeinecachestarter.support;
    
    import com.axin.idea.rediscaffeinecachestarter.CacheRedisCaffeineProperties;
    import com.github.benmanes.caffeine.cache.Cache;
    import lombok.Getter;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.cache.support.AbstractValueAdaptingCache;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.util.StringUtils;
    
    import java.time.Duration;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;
    import java.util.concurrent.Callable;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class RedisCaffeineCache extends AbstractValueAdaptingCache {
    
        private final Logger logger = LoggerFactory.getLogger(RedisCaffeineCache.class);
    
        private String name;
    
        private RedisTemplate redisTemplate;
    
        @Getter
        private Cache caffeineCache;
    
        private String cachePrefix;
    
        /**
         * 默认key超时时间 3600s
         */
        private long defaultExpiration = 3600;
    
        private Map defaultExpires = new HashMap<>();
        {
            defaultExpires.put(CacheNames.CACHE_15MINS, TimeUnit.MINUTES.toSeconds(15));
            defaultExpires.put(CacheNames.CACHE_30MINS, TimeUnit.MINUTES.toSeconds(30));
            defaultExpires.put(CacheNames.CACHE_60MINS, TimeUnit.MINUTES.toSeconds(60));
            defaultExpires.put(CacheNames.CACHE_180MINS, TimeUnit.MINUTES.toSeconds(180));
            defaultExpires.put(CacheNames.CACHE_12HOUR, TimeUnit.HOURS.toSeconds(12));
        }
    
        private String topic;
        private Map keyLockMap = new ConcurrentHashMap();
    
        protected RedisCaffeineCache(boolean allowNullValues) {
            super(allowNullValues);
        }
    
        public RedisCaffeineCache(String name, RedisTemplate redisTemplate,
                                  Cache caffeineCache, CacheRedisCaffeineProperties cacheRedisCaffeineProperties) {
            super(cacheRedisCaffeineProperties.isCacheNullValues());
            this.name = name;
            this.redisTemplate = redisTemplate;
            this.caffeineCache = caffeineCache;
            this.cachePrefix = cacheRedisCaffeineProperties.getCachePrefix();
            this.defaultExpiration = cacheRedisCaffeineProperties.getRedis().getDefaultExpiration();
            this.topic = cacheRedisCaffeineProperties.getRedis().getTopic();
            defaultExpires.putAll(cacheRedisCaffeineProperties.getRedis().getExpires());
        }
    
        @Override
        public String getName() {
            return this.name;
        }
    
        @Override
        public Object getNativeCache() {
            return this;
        }
    
        @Override
        public  T get(Object key, Callable valueLoader) {
            Object value = lookup(key);
            if (value != null) {
                return (T) value;
            }
            //key在redis和缓存中均不存在
            ReentrantLock lock = keyLockMap.get(key.toString());
    
            if (lock == null) {
                logger.debug("create lock for key : {}", key);
                keyLockMap.putIfAbsent(key.toString(), new ReentrantLock());
                lock = keyLockMap.get(key.toString());
            }
            try {
                lock.lock();
                value = lookup(key);
                if (value != null) {
                    return (T) value;
                }
                //执行原方法获得value
                value = valueLoader.call();
                Object storeValue = toStoreValue(value);
                put(key, storeValue);
                return (T) value;
            } catch (Exception e) {
                throw new ValueRetrievalException(key, valueLoader, e.getCause());
            } finally {
                lock.unlock();
            }
        }
    
        @Override
        public void put(Object key, Object value) {
            if (!super.isAllowNullValues() && value == null) {
                this.evict(key);
                return;
            }
            long expire = getExpire();
            logger.debug("put:{},expire:{}", getKey(key), expire);
            redisTemplate.opsForValue().set(getKey(key), toStoreValue(value), expire, TimeUnit.SECONDS);
    
            //缓存变更时通知其他节点清理本地缓存
            push(new CacheMessage(this.name, key));
            //此处put没有意义,会收到自己发送的缓存key失效消息
    //        caffeineCache.put(key, value);
        }
    
        @Override
        public ValueWrapper putIfAbsent(Object key, Object value) {
            Object cacheKey = getKey(key);
            // 使用setIfAbsent原子性操作
            long expire = getExpire();
            boolean setSuccess;
            setSuccess = redisTemplate.opsForValue().setIfAbsent(getKey(key), toStoreValue(value), Duration.ofSeconds(expire));
    
            Object hasValue;
            //setNx结果
            if (setSuccess) {
                push(new CacheMessage(this.name, key));
                hasValue = value;
            }else {
                hasValue = redisTemplate.opsForValue().get(cacheKey);
            }
    
            caffeineCache.put(key, toStoreValue(value));
            return toValueWrapper(hasValue);
        }
    
        @Override
        public void evict(Object key) {
            // 先清除redis中缓存数据,然后清除caffeine中的缓存,避免短时间内如果先清除caffeine缓存后其他请求会再从redis里加载到caffeine中
            redisTemplate.delete(getKey(key));
    
            push(new CacheMessage(this.name, key));
    
            caffeineCache.invalidate(key);
        }
    
        @Override
        public void clear() {
            // 先清除redis中缓存数据,然后清除caffeine中的缓存,避免短时间内如果先清除caffeine缓存后其他请求会再从redis里加载到caffeine中
            Set keys = redisTemplate.keys(this.name.concat(":*"));
            for (Object key : keys) {
                redisTemplate.delete(key);
            }
    
            push(new CacheMessage(this.name, null));
            caffeineCache.invalidateAll();
        }
    
        /**
         * 取值逻辑
         * @param key
         * @return
         */
        @Override
        protected Object lookup(Object key) {
            Object cacheKey = getKey(key);
            Object value = caffeineCache.getIfPresent(key);
            if (value != null) {
                logger.debug("从本地缓存中获得key, the key is : {}", cacheKey);
                return value;
            }
    
            value = redisTemplate.opsForValue().get(cacheKey);
    
            if (value != null) {
                logger.debug("从redis中获得值,将值放到本地缓存中, the key is : {}", cacheKey);
                caffeineCache.put(key, value);
            }
            return value;
        }
    
        /**
         * @description 清理本地缓存
         */
        public void clearLocal(Object key) {
            logger.debug("clear local cache, the key is : {}", key);
            if (key == null) {
                caffeineCache.invalidateAll();
            } else {
                caffeineCache.invalidate(key);
            }
        }
    
        //————————————————————————————私有方法——————————————————————————
    
        private Object getKey(Object key) {
            String keyStr = this.name.concat(":").concat(key.toString());
            return StringUtils.isEmpty(this.cachePrefix) ? keyStr : this.cachePrefix.concat(":").concat(keyStr);
        }
    
        private long getExpire() {
            long expire = defaultExpiration;
            Long cacheNameExpire = defaultExpires.get(this.name);
            return cacheNameExpire == null ? expire : cacheNameExpire.longValue();
        }
    
        /**
         * @description 缓存变更时通知其他节点清理本地缓存
         */
        private void push(CacheMessage message) {
            redisTemplate.convertAndSend(topic, message);
        }
    }                        
                                                    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
                                                    
    作者最新文章
    编程开发
    相关文章 更多
    php72w redis,docker php7安装php-redis
    php72w redis,docker php7安装php-redis

    在Docker环境下为PHP安装Redis扩展:手动编译需下载源码、安装phpize、配置编译并安装,生成redis.so并在php.d目录新建redis.ini加载,重启PHP-FPM;也可直接yum安装php72w-pecl-redis,免手动编译。

    php 如何配置 redis,php 如何设置redis
    php 如何配置 redis,php 如何设置redis

    配置PHP的Redis扩展:先安装Redis服务,从GitHub下载phpredis源码,用phpize编译安装,修改php.ini添加extension=redis.so,重启php-fpm或Apache,通过phpinfo验证并连接Redis。

    c++如何将结构体序列化为Redis兼容的RESP协议格式【深度】
    c++如何将结构体序列化为Redis兼容的RESP协议格式【深度】

    C++结构体无法直接序列化为RESP协议,需手动将字段映射为*N数组嵌套$lenbulkstring形式。整数需转换为字符串并加:前缀,空字符串用$0\r\n,null用$-1\r\n。长度计算需精确到字节数,特别注意二进制安全,同时确保字段顺序与结构体定义一致。

    如何在Golang架构中部署Redict作为高能数据临时存储中转站
    如何在Golang架构中部署Redict作为高能数据临时存储中转站

    Redis作为跨服务临时状态枢纽,需按消息队列、缓存和原子状态三重角色设计。选型用List实现任务分发及延迟重试,Stream用于事件广播与位点回溯。go-redis/v8需配置MinIdleConns、MaxRetries及超时。数据写入应JSON序列化并带type标签、设合理TTL、将关键字段提至顶层。消费通过原子GET+DEL或processedkey

    TP8.0 高并发下库存超卖?Redis 原子递减结合数据库乐观锁控制
    TP8.0 高并发下库存超卖?Redis 原子递减结合数据库乐观锁控制

    在高并发秒杀场景下,利用Redis的减库存原子操作预扣减库存拦截超卖请求,再通过数据库乐观锁(版本号与库存校验)确保最终一致性,异常时回滚Redis库存并借助Lua脚本保证原子性,从而有效防止库存超卖。

    如何在 Go 中利用 go-redis 执行 Lua 脚本优化逻辑
    如何在 Go 中利用 go-redis 执行 Lua 脚本优化逻辑

    在Go中使用go-redis执行Lua脚本可借助原子性解决竞态问题,通过redis.Script封装脚本并自动处理SHA1缓存与重试。需注意KEYS必须为非空切片且所有键在Cluster模式下落在同一slot,ARGV以字符串传入并由tonumber转换。返回值需先检查错误再调用Val(),避免panic。

    TP6.0 基于 Redis SetNX 实现的简单互斥锁机制【并发】
    TP6.0 基于 Redis SetNX 实现的简单互斥锁机制【并发】

    先看一个典型场景:电商秒杀、库存扣减、定时任务重复调度——这些并发冲突在 TP6 应用里屡见不鲜。用 Redis 的 SETNX 做互斥锁,确实是轻量又直接的方案,但“能用”和“稳妥”之间,隔着三层硬功夫:原子加锁、身份校验、合理超时。这三条,差一条就翻车。 很多人第一次上手,直接写 SETNX 加

    TP6.0 如何处理高并发抢购?Redis 原子锁与队列削峰详解【秒杀】
    TP6.0 如何处理高并发抢购?Redis 原子锁与队列削峰详解【秒杀】

    高并发抢购中,Redis的DECR原子操作可安全扣库存;配合think-queue异步削峰,接口仅扣库存和推消息;分布式锁需用SETNXEX加唯一标识,释放时用Lua脚本校验;成功资格后须以数据库事务双写为准,失败时补回Redis库存。

    Golang 实现基于 Redis 的延时任务队列
    Golang 实现基于 Redis 的延时任务队列

    基于Redis的ZSET实现延时队列需解决并发重复消费、失败重试混乱、精度不足及内存爆炸等问题。通过Lua脚本保证取删原子性,使用毫秒级时间戳,对临时错误采用指数退避重试,永久错误记入独立哈希表,并限制最大延迟、定期清理过期任务、使用紧凑序列化格式。

    RedisInsight怎么修改Key的过期时间 Redis操作方法【步骤】
    RedisInsight怎么修改Key的过期时间 Redis操作方法【步骤】

    操作 Redis 的时候,改个 Key 的过期时间其实是个高频场景。特别是做运维排查或者临时调试那会儿,谁也不想反复切命令行窗口、背那些命令参数。好在 RedisInsight 这个图形工具把这事儿做得很顺手——三种方式任选,总有一种合你胃口。 在动手前,先确认一下 Key 当前的 TTL 状态是个

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。

    即将离开本站
    您即将前往第三方网站,请确认是否继续?
    为什么要购买正版软件?
    01 官方授权

    授权来源透明,订单与许可信息可随时查询。

    02 即时交付

    付款成功后自动发放激活信息,支持订单追踪。

    03 中文协助

    安装、激活、迁移与续费问题都有专人响应。

    04 长期可用

    提供升级提醒与授权管理,换机时也能轻松迁移。

    网站备案号:苏ICP备2026018738号-1 联系邮箱:bd@zhengruan.com 网站地图

    Copyright ©2018-2026