DukeDuke
主页
项目文档
技术文档
  • 单机版
  • 微服务
  • 代办项目
  • 优鲜项目
项目管理
关于我们
主页
项目文档
技术文档
  • 单机版
  • 微服务
  • 代办项目
  • 优鲜项目
项目管理
关于我们
  • 技术文档

    • 网络原理

      • 交换机
      • 路由器
      • TCP/IP协议
      • HTTP 与 HTTPS
    • 软件架构

      • 什么是软件架构
      • 分层架构
      • 微服务架构
      • 事件驱动架构
      • 领域驱动设计(DDD)
      • 架构图
      • 高并发系统
    • Vue3

      • Vue3简介
      • Vue3响应式系统
      • Vue3组合式API
      • Vue3生命周期
      • Vue3模板语法
      • Vue3组件系统
      • Vue3 路由系统
      • Vue3 状态管理
      • Vue3 性能优化
      • Vue3 TypeScript 支持
      • Vue3 项目实战
      • VUE 面试题大全
      • Node.js 安装
    • JAVA

      • JVM

        • 认识JVM
        • JVM类加载器
        • 运行时数据区
        • 执行引擎
        • 本地方法接口
        • 本地方法库
        • JVM垃圾回收
        • JVM性能监控
        • JVM调优
      • 设计模式
        • 单例模式
        • 工厂模式
        • 策略模式
        • 适配器模式
        • 建造者模式
        • 原型模式
        • 装饰器模式
        • 代理模式
        • 外观模式
        • 享元模式
        • 组合模式
        • 桥接模式
      • Java多线程

        • Java 线程基础详解
        • Java 线程池详解
        • Java ThreadLocal 详解
        • Java volatile 详解
        • Java 线程间通信详解
        • Java 线程安全详解
        • Java 线程调度详解
        • Java 线程优先级详解

        • Java 线程中断详解
        • Java 线程死锁详解
      • Java反射
      • Java 面试题

        • Java 基础概念面试题
        • Java 面向对象编程面试题
        • Java 集合框架面试题
        • Java 多线程与并发面试题
        • JVM 与内存管理面试题
        • Java I/O 与 NIO 面试题
        • Java 异常处理面试题
        • Java 反射与注解面试题
        • Java Spring 框架面试题
        • Java 数据库与 JDBC 面试题
        • Java 性能优化面试题
        • Java 实际项目经验面试题
        • Java 高级特性面试题
        • Java 面试准备建议
    • Python

      • Python简介
      • Python安装
      • Python hello world
      • Python基础语法
      • Python数据类型
      • Python数字
      • Python字符串
      • Python列表
      • Python元组
      • Python字典
      • Python日期时间
      • Python文件操作
      • Python异常处理
      • Python函数
      • Python类
      • Python模块
      • Python包
      • Python多线程
      • Python面向对象
      • Python爬虫
      • Django web框架
      • Python 面试题

        • Python 面试题导航
        • Python 基础概念
        • Python 面向对象编程
        • Python 数据结构
        • Python 高级特性
        • Python 框架
        • Python 性能优化
        • Python 项目经验
    • Spring

      • Spring
      • Springboot
      • Spring Security 安全框架
      • SpringBoot 中的事件详解
      • SpringBoot 中的定时任务详解
      • SpringBoot 自动装配原理与源码解释
    • Mybatis

      • Mybatis
      • Mybatis-Plus
    • 数据库

      • Redis

        • Redis简介
        • Redis(单机)安装
        • Redis配置
        • Redis数据结构
        • RDB、AOF 和混合持久化机制
        • Redis内存管理
        • Redis缓存一致性
        • Redis缓存穿透
        • Redis缓存击穿
        • Redis缓存雪崩
        • Redis Lua脚本
        • Redis主从复制
        • Redis哨兵模式
        • Redis集群
        • Redis数据分片
        • Redis CPU使用率过高
        • Redis面试题
      • MySQL

        • MySQL简介
        • MySQL安装
        • MySQL配置
        • MYSQL日常维护
        • MYSQL优化-慢查询
        • MYSQL优化-索引
        • MYSQL数据库设计规范
    • 消息队列

      • RocketMQ
      • Kafka
      • RabbitMQ
      • 消息队列面试题
    • 微服务

      • SpringCloud 微服务
      • Eureka 注册中心
      • Nacos 注册中心
      • Gateway 网关
      • Feign 服务调用
      • Sentinel 限流 与 熔断
      • Seata 分布式事务
      • CAP 理论
      • Redis 分布式锁
      • 高并发系统设计
    • ELK日志分析系统

      • Elasticsearch 搜索引擎
      • Logstash 数据处理
      • Kibana 可视化
      • ELK 实战
    • 开放API

      • 开放API设计
      • 开放API示例项目
    • 人工智能

      • 人工智能简介
      • 机器学习

      • 深度学习

      • 自然语言处理

      • 计算机视觉

        • CUDA与cuDNN详细安装
        • Conda 安装
        • Pytorch 深度学习框架
        • yolo 目标检测
        • TensorRT 深度学习推理优化引擎
        • TensorFlow 机器学习
        • CVAT 图像标注
        • Windows 下安装 CUDA、cuDNN、TensorRT、TensorRT-YOLO 环境
        • Windows10+CUDA+cuDNN+TensorRT+TensorRT-YOLO 部署高性能YOLO11推理
    • 大数据

      • 大数据简介
      • Hadoop 数据存储
      • Flume 数据采集
      • Sqoop 数据导入导出
      • Hive 数据仓库
      • Spark 数据处理
      • Flink 数据处理
      • Kafka 数据采集
      • HBase 数据存储
      • Elasticsearch 搜索引擎
    • 图像处理

      • 图像处理简介
      • 医学图像web呈现
      • 医学图像处理
      • 切片细胞分离问题
    • 服务器&运维

      • Linux 系统

        • Linux 系统管理
        • Linux 网络管理
        • Linux 文件管理
        • Linux 命令大全
      • Nginx Web 服务器

        • Nginx 安装 与 配置
        • Nginx 负载均衡
        • Nginx SSL证书配置
        • Nginx Keepalived 高可用
      • Docker 容器

        • Docker 简介
        • Docker 安装与配置
        • Docker 命令
        • Docker 部署 Nginx
        • Docker 部署 MySQL
        • Docker 部署 Redis
      • 服务器

        • 塔式服务器
        • 机架式服务器
        • 刀片服务器
      • Git 版本控制
      • Jenkins 持续集成
      • Jmeter 性能测试
      • Let's Encrypt 免费SSL证书
    • 简历

      • 项目经理简历
      • 开发工程师简历

Redis 的过期策略以及内存淘汰机制详解

Redis 作为一个高性能的内存数据库,其内存管理机制至关重要。本文将深入探讨 Redis 的过期策略和内存淘汰机制,帮助您更好地理解和使用 Redis。

1. Redis 过期策略

Redis 提供了两种过期策略来处理过期键,这两种策略相互配合,共同确保过期键能够被及时清理。

1.1 惰性删除(Lazy Expiration)

介绍

惰性删除是一种"按需删除"的策略,它只在访问键时才检查键是否过期,只在真正需要时才执行删除操作,对 CPU 友好,不会占用额外的 CPU 时间,适合处理过期时间分布均匀的场景。如果过期键长期不被访问,导致大量过期键占用内存。

工作原理

1.2 定期删除(Periodic Expiration)

定期删除

定期删除是一种主动删除的策略,Redis 会定期检查并删除过期键,可以及时清理过期键,通过限制删除操作执行的时长和频率,减少对 CPU 的影响,如果过期键太多,可能会影响 Redis 的性能,也可能存在过期键未被及时删除的情况,需要合理配置检查频率和每次检查的键数量。

工作原理

1.3 策略配合使用

策略配合

Redis 的惰性删除和定期删除策略相互配合,共同确保过期键能够被及时清理。惰性删除保证访问时及时清理,定期删除则主动清理未被访问的过期键。

配置说明

# redis.conf

# 1. 基础配置
# 开启两种删除策略(默认开启)
lazyfree-lazy-eviction no
lazyfree-lazy-expire no
active-expire-cycle yes

# 2. 定期删除配置
# 设置执行频率(根据系统负载调整,默认10)
hz 10

# 设置每次检查的键数量(根据过期键数量调整,默认20)
active-expire-cycles-per-second 20

# 设置过期键比例阈值(默认25%)
active-expire-cycles-max-keys 25

# 设置时间限制(默认25ms)
active-expire-cycles-max-time 25

# 3. 键过期时间设置
# 设置键的过期时间
EXPIRE key seconds
EXPIREAT key timestamp
PEXPIRE key milliseconds
PEXPIREAT key milliseconds-timestamp

# 查看剩余生存时间
TTL key
PTTL key

# 移除过期时间
PERSIST key

最佳实践

  1. 合理设置过期时间

    • 根据业务需求设置合适的过期时间
    • 避免大量键同时过期
    • 使用随机过期时间分散过期时间点
  2. 调整定期删除参数

    • 根据系统负载调整 hz 参数
    • 根据过期键数量调整每次检查的键数量
    • 监控过期键清理效果
  3. 监控指标

    • 监控过期键数量
    • 监控内存使用情况
    • 监控删除操作的执行时间
  4. 性能优化

    • 避免存储过大的值
    • 使用合适的数据结构
    • 及时清理无用数据

实际应用示例

# 1. 设置不同过期时间的键
# 基础过期时间 + 随机值,避免同时过期
SET key1 "value1" EX $((3600 + RANDOM % 300))  # 1小时 + 随机0-5分钟
SET key2 "value2" EX $((7200 + RANDOM % 300))  # 2小时 + 随机0-5分钟
SET key3 "value3" EX $((10800 + RANDOM % 300)) # 3小时 + 随机0-5分钟

# 2. 监控过期键数量
INFO keyspace

# 3. 查看键的剩余生存时间
TTL key1
PTTL key2

# 4. 手动触发过期检查(不推荐,仅用于测试)
DEBUG OBJECT key1

2. 内存淘汰机制

当 Redis 内存使用达到上限时,会触发内存淘汰机制。Redis 提供了 8 种淘汰策略,可以根据实际需求选择合适的策略。

策略说明适用场景
noeviction不淘汰,内存不足时报错对数据一致性要求高的场景
allkeys-lru所有键中最近最少使用的淘汰热点数据访问场景
volatile-lru设置了过期时间的键中最近最少使用的淘汰需要过期时间的场景
allkeys-random所有键中随机淘汰数据访问均匀的场景
volatile-random设置了过期时间的键中随机淘汰需要过期时间的随机淘汰场景
volatile-ttl设置了过期时间的键中即将过期的淘汰优先淘汰即将过期的数据

实际应用示例

#redis.conf
# 配置 Redis 使用 volatile-lru 策略
CONFIG SET maxmemory-policy volatile-lru

# 设置最大内存为 1GB
CONFIG SET maxmemory 1gb

# 存储一些带过期时间的键
SET cache:user:1 "user_data" EX 3600
SET cache:product:1 "product_data" EX 7200
SET cache:order:1 "order_data" EX 1800

# 当内存不足时,Redis 会根据访问频率淘汰键
# 访问频率最低的键会被优先淘汰

3. 底层实现原理

3.1 过期键的存储结构

Redis 使用一个特殊的字典(expires)来存储设置了过期时间的键。这个字典的结构如下:

typedef struct redisDb {
    dict *dict;        // 数据字典
    dict *expires;     // 过期字典
    // ... 其他字段
} redisDb;

过期字典的结构:

  • 键:指向实际键的指针
  • 值:过期时间戳(毫秒级)

3.2 惰性删除的实现原理

工作原理序列图

3.3 定期删除的实现原理

工作原理序列图

实现原理

Redis 使用一个定时任务来执行定期删除:

void activeExpireCycle(int type) {
    // 每次处理的数据库数量
    int dbs_per_call = CRON_DBS_PER_CALL;

    // 每次处理的键数量
    int keys_per_loop = 20;

    // 计算需要处理的数据库数量
    if (dbs_per_call > server.dbnum || type == ACTIVE_EXPIRE_CYCLE_FAST)
        dbs_per_call = server.dbnum;

    // 遍历数据库
    for (int j = 0; j < dbs_per_call; j++) {
        // 随机选择键
        for (int i = 0; i < keys_per_loop; i++) {
            // 随机选择一个键
            key = dictGetRandomKey(db->expires);

            // 检查是否过期
            if (expireIfNeeded(db, key)) {
                expired++;
            }
        }
    }
}

3.4 LRU 算法的实现原理

Redis 使用近似 LRU 算法,通过记录键的最后访问时间来实现:

对象结构

typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:LRU_BITS;  // 记录最后访问时间
    int refcount;
    void *ptr;
} robj;

淘汰过程

int evictKey(redisDb *db, robj *key) {
    // 获取对象的最后访问时间
    unsigned long long idle = estimateObjectIdleTime(key);

    // 选择最久未访问的键进行淘汰
    if (idle > max_idle) {
        max_idle = idle;
        bestkey = key;
    }
}

3.5 LFU 算法的实现原理

Redis 4.0 引入的 LFU 算法通过计数器记录访问频率:

计数器结构

typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:LRU_BITS;  // 用于 LFU 的计数器
    int refcount;
    void *ptr;
} robj;

计数器更新

void updateLFU(robj *val) {
    // 获取当前计数器值
    unsigned long counter = LFUDecrAndReturn(val);

    // 增加计数器
    counter = LFULogIncr(counter);

    // 更新对象的计数器
    val->lru = (LFUGetTimeInMinutes()<<8) | counter;
}

3.6 内存淘汰的触发机制

内存检查

int processCommand(client *c) {
    // 检查内存使用情况
    if (server.maxmemory) {
        int retval = freeMemoryIfNeeded();
        if (retval == C_ERR) {
            addReply(c, shared.oomerr);
            return C_ERR;
        }
    }
}

内存释放

int freeMemoryIfNeeded(void) {
    // 计算需要释放的内存
    size_t mem_tofree = mem_used - server.maxmemory;

    // 根据策略选择要删除的键
    while (mem_freed < mem_tofree) {
        // 选择要删除的键
        bestkey = selectKeyToEvict();

        // 删除键
        dbDelete(c->db, bestkey);

        // 更新已释放内存
        mem_freed += keySize + valSize;
    }
}

4. 实际应用场景

4.1 百万级数据热点筛选案例

场景描述

假设有一个电商系统,需要从 100 万商品数据中筛选出热点商品进行缓存,同时需要处理商品数据的更新和过期。

1. 数据特点分析

  • 高频访问商品(约 5 万)

    • 热门商品、促销商品
    • 访问频率:每分钟>100 次
    • 数据特点:需要实时性,频繁更新
  • 中频访问商品(约 15 万)

    • 普通热销商品
    • 访问频率:每小时>100 次
    • 数据特点:需要一定实时性,定期更新
  • 低频访问商品(约 80 万)

    • 普通商品、冷门商品
    • 访问频率:每天<100 次
    • 数据特点:更新频率低,可以接受一定延迟

2. 缓存策略设计

# 1. 高频商品缓存配置
# 使用 volatile-lfu 策略,优先淘汰访问频率最低的键
CONFIG SET maxmemory-policy volatile-lfu

# 设置较短的过期时间(5分钟),确保数据实时性
SET product:hot:123 "商品数据" EX 300

# 2. 中频商品缓存配置
# 使用 volatile-lru 策略,优先淘汰最近最少使用的键
SET product:normal:456 "商品数据" EX 3600

# 3. 低频商品缓存配置
# 使用 volatile-ttl 策略,优先淘汰即将过期的键
SET product:cold:789 "商品数据" EX 86400

3. 实现方案

3.1 整体架构
3.2 热点数据识别流程
3.3 数据更新流程
3.4 过期处理流程
  1. 数据分层存储

    @Service
    public class ProductCacheService {
        private final RedisTemplate<String, Object> redisTemplate;
        private static final int HOT_EXPIRE = 300;      // 5分钟
        private static final int NORMAL_EXPIRE = 3600;  // 1小时
        private static final int COLD_EXPIRE = 86400;   // 24小时
    
        @Autowired
        public ProductCacheService(RedisTemplate<String, Object> redisTemplate) {
            this.redisTemplate = redisTemplate;
        }
    
        public void cacheHotProduct(String productId, Product product) {
            String key = "product:hot:" + productId;
            redisTemplate.opsForValue().set(key, product, HOT_EXPIRE, TimeUnit.SECONDS);
        }
    
        public void cacheNormalProduct(String productId, Product product) {
            String key = "product:normal:" + productId;
            redisTemplate.opsForValue().set(key, product, NORMAL_EXPIRE, TimeUnit.SECONDS);
        }
    
        public void cacheColdProduct(String productId, Product product) {
            String key = "product:cold:" + productId;
            redisTemplate.opsForValue().set(key, product, COLD_EXPIRE, TimeUnit.SECONDS);
        }
    }
    
  2. 热点数据识别

    @Service
    public class HotProductDetector {
        private final RedisTemplate<String, Object> redisTemplate;
        private static final int HOT_THRESHOLD = 100;
    
        @Autowired
        public HotProductDetector(RedisTemplate<String, Object> redisTemplate) {
            this.redisTemplate = redisTemplate;
        }
    
        public void updateAccessCount(String productId) {
            String key = "product:access:" + productId;
            // 使用 HyperLogLog 统计访问次数
            redisTemplate.opsForHyperLogLog().add(key, System.currentTimeMillis());
            // 设置过期时间,避免占用过多内存
            redisTemplate.expire(key, 1, TimeUnit.HOURS);
        }
    
        public boolean isHotProduct(String productId) {
            String key = "product:access:" + productId;
            Long count = redisTemplate.opsForHyperLogLog().size(key);
            return count != null && count > HOT_THRESHOLD;
        }
    
        public boolean isNormalProduct(String productId) {
            String key = "product:access:" + productId;
            Long count = redisTemplate.opsForHyperLogLog().size(key);
            return count != null && count > 10 && count <= HOT_THRESHOLD;
        }
    }
    
  3. 数据更新机制

    @Service
    public class ProductUpdateService {
        private final ProductCacheService cacheService;
        private final HotProductDetector hotDetector;
        private final ProductRepository productRepository;
    
        @Autowired
        public ProductUpdateService(
            ProductCacheService cacheService,
            HotProductDetector hotDetector,
            ProductRepository productRepository
        ) {
            this.cacheService = cacheService;
            this.hotDetector = hotDetector;
            this.productRepository = productRepository;
        }
    
        @Transactional
        public void updateProduct(String productId, Product product) {
            // 更新数据库
            productRepository.save(product);
    
            // 更新缓存
            if (hotDetector.isHotProduct(productId)) {
                cacheService.cacheHotProduct(productId, product);
            } else if (hotDetector.isNormalProduct(productId)) {
                cacheService.cacheNormalProduct(productId, product);
            } else {
                cacheService.cacheColdProduct(productId, product);
            }
        }
    
        @EventListener
        public void handleExpiredProduct(ExpiredProductEvent event) {
            String productId = event.getProductId();
            // 从数据库重新加载数据
            Product product = productRepository.findById(productId)
                .orElseThrow(() -> new ProductNotFoundException(productId));
    
            // 根据访问频率决定缓存策略
            if (hotDetector.isHotProduct(productId)) {
                cacheService.cacheHotProduct(productId, product);
            } else if (hotDetector.isNormalProduct(productId)) {
                cacheService.cacheNormalProduct(productId, product);
            } else {
                cacheService.cacheColdProduct(productId, product);
            }
        }
    }
    
  4. 配置类

    @Configuration
    public class RedisConfig {
        @Bean
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
            RedisTemplate<String, Object> template = new RedisTemplate<>();
            template.setConnectionFactory(factory);
    
            // 设置key的序列化方式
            template.setKeySerializer(new StringRedisSerializer());
            // 设置value的序列化方式
            template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
            // 设置hash key的序列化方式
            template.setHashKeySerializer(new StringRedisSerializer());
            // 设置hash value的序列化方式
            template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
    
            template.afterPropertiesSet();
            return template;
        }
    
        @Bean
        public CacheManager cacheManager(RedisConnectionFactory factory) {
            RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(5))
                .serializeKeysWith(RedisSerializationContext.SerializationPair
                    .fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                    .fromSerializer(new GenericJackson2JsonRedisSerializer()))
                .disableCachingNullValues();
    
            return RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .transactionAware()
                .build();
        }
    }
    
  5. 实体类

    @Data
    @Entity
    public class Product {
        @Id
        private String id;
        private String name;
        private BigDecimal price;
        private String description;
        private LocalDateTime updateTime;
        // 其他字段...
    }
    
    @Data
    public class ExpiredProductEvent {
        private final String productId;
        private final LocalDateTime expiredTime;
    }
    
3.5 关键流程说明
  1. 热点数据识别流程

    • 客户端请求进入系统
    • 检查数据是否已缓存
    • 更新访问计数
    • 根据访问频率判断数据类别
    • 按类别设置不同的缓存策略
  2. 数据更新流程

    • 接收数据更新请求
    • 进行数据验证
    • 更新数据库
    • 根据访问频率更新对应缓存
    • 设置相应的过期时间
  3. 过期处理流程

    • 监听缓存过期事件
    • 重新加载数据
    • 根据访问频率更新缓存
    • 记录处理结果
  4. 监控指标

    • 缓存命中率
    • 内存使用情况
    • 过期键数量
    • 系统响应时间

4. 性能优化

  1. 内存使用优化

    # 配置内存限制和淘汰策略
    CONFIG SET maxmemory 4gb
    CONFIG SET maxmemory-policy volatile-lfu
    
    # 设置内存告警阈值
    CONFIG SET maxmemory-samples 10
    
  2. 过期时间优化

    # 动态调整过期时间
    def adjust_expire_time(self, product_id, access_count):
        base_time = 300  # 基础过期时间:5分钟
        if access_count > 1000:
            # 高频访问:缩短过期时间
            return base_time
        elif access_count > 100:
            # 中频访问:中等过期时间
            return base_time * 12
        else:
            # 低频访问:较长过期时间
            return base_time * 288
    
  3. 监控指标

    # 监控关键指标
    def monitor_metrics(self):
        # 1. 内存使用情况
        memory_info = self.redis_client.info('memory')
    
        # 2. 过期键数量
        expired_keys = self.redis_client.info('stats')['expired_keys']
    
        # 3. 淘汰键数量
        evicted_keys = self.redis_client.info('stats')['evicted_keys']
    
        # 4. 热点商品命中率
        hit_rate = self.calculate_hit_rate()
    
        return {
            'memory_usage': memory_info['used_memory'],
            'expired_keys': expired_keys,
            'evicted_keys': evicted_keys,
            'hit_rate': hit_rate
        }
    

5. 效果评估

  1. 性能指标

    • 热点商品访问延迟:<10ms
    • 缓存命中率:>95%
    • 内存使用率:<80%
    • 过期键清理效率:>99%
  2. 业务指标

    • 商品数据实时性:<5 分钟
    • 系统吞吐量:>10000 QPS
    • 数据一致性:>99.9%
    • 系统可用性:>99.99%
  3. 成本效益

    • 内存使用优化:减少 50%
    • 数据库压力:降低 80%
    • 系统响应时间:提升 90%
    • 运维成本:降低 60%
最近更新:: 2025/8/14 09:20
Contributors: Duke
Prev
RDB、AOF 和混合持久化机制
Next
Redis缓存一致性