Redis-不止是缓存

一、Redis 到底是什么?

Redis(Remote Dictionary Server)是一个开源的、基于内存的、高性能的键值对数据库。它支持多种数据结构,包括字符串(String)、哈希(Hash)、列表(List)、集合(Set)、有序集合(Sorted Set)等,因此也被称为 "数据结构服务器"。

Redis 的核心特点

  • 极致性能:基于内存操作,单线程模型,官方给出的 QPS(每秒查询率)可以达到 10 万 +
  • 丰富的数据结构:支持 8 种以上的数据结构,能满足各种业务场景
  • 持久化:支持 RDB 和 AOF 两种持久化方式,保证数据不丢失
  • 高可用:支持主从复制、哨兵模式和集群模式,能搭建高可用的分布式系统
  • 原子性:所有操作都是原子性的,不用担心并发问题

简单来说,Redis 就是一个 "又快又全能" 的数据库。它比 MySQL 快 100 倍以上,又比 Memcached 支持更多的数据结构和功能。

二、Redis 的 8 大核心应用场景

很多人只用 Redis 做缓存,这简直是 "杀鸡用牛刀"。下面我就介绍 Redis 最常用的 8 个应用场景,每一个都能解决你实际开发中的痛点。

1. 数据缓存(最常用)

这是 Redis 最基本也是最常用的功能。把热点数据(比如商品详情、用户信息、配置信息)存到 Redis 中,用户请求时直接从 Redis 获取,不用再查数据库,能极大提升系统性能,降低数据库压力。

典型场景:电商首页的商品列表、用户登录信息、系统配置参数。

2. 分布式锁

在分布式系统中,多个服务实例同时操作同一个资源时,需要用分布式锁来保证数据一致性。Redis 的SETNX命令可以非常方便地实现分布式锁。

典型场景:订单扣库存、秒杀活动、定时任务防重复执行。

3. 计数器

Redis 的INCR命令是原子性的,可以用来实现各种计数器功能。

典型场景:文章阅读量、点赞数、评论数、商品销量、接口调用次数统计。

4. 限流

基于 Redis 的计数器或滑动窗口算法,可以实现接口限流功能,防止系统被恶意请求打垮。

典型场景:短信验证码发送频率限制、API 接口调用频率限制、秒杀活动的流量控制。

5. 排行榜

Redis 的有序集合(Sorted Set)天生就是为排行榜设计的。它可以快速插入、删除和排序,性能非常高。

典型场景:游戏排行榜、商品销量排行榜、热门搜索排行榜。

6. 消息队列

Redis 的列表(List)可以作为简单的消息队列使用,支持先进先出(FIFO)的消息传递。

典型场景:异步任务处理、日志收集、订单状态变更通知。

7. 会话存储

把用户的 Session 信息存到 Redis 中,可以实现分布式系统的会话共享。

典型场景:单点登录(SSO)、分布式系统的用户状态管理。

8. 布隆过滤器

Redis 4.0 之后支持布隆过滤器插件,可以用来快速判断一个元素是否存在于一个集合中。

典型场景:缓存穿透防护、垃圾邮件过滤、重复数据判断。

三、Spring Boot 集成 Redis 完整教程

下面我就以最常用的 Spring Boot 框架为例,教你如何快速集成和使用 Redis。

第一步:引入依赖

pom.xml中引入 Spring Data Redis 依赖:

xml

复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 连接池依赖 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

第二步:配置 Redis

application.yml中配置 Redis 连接信息:

yaml

复制代码
spring:
  redis:
    host: localhost
    port: 6379
    password: 123456
    database: 0
    # 连接池配置
    lettuce:
      pool:
        max-active: 8
        max-wait: -1ms
        max-idle: 8
        min-idle: 0

第三步:配置 RedisTemplate

默认的RedisTemplate会使用 JDK 序列化,序列化后的数据在 Redis 中看起来是乱码。我们需要自定义RedisTemplate,使用 Jackson2JsonRedisSerializer 序列化:

java

运行

复制代码
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        
        // 使用Jackson2JsonRedisSerializer序列化值
        Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, 
                ObjectMapper.DefaultTyping.NON_FINAL);
        jacksonSerializer.setObjectMapper(objectMapper);
        
        // String序列化器
        StringRedisSerializer stringSerializer = new StringRedisSerializer();
        
        // key和hashKey使用String序列化
        template.setKeySerializer(stringSerializer);
        template.setHashKeySerializer(stringSerializer);
        // value和hashValue使用Jackson序列化
        template.setValueSerializer(jacksonSerializer);
        template.setHashValueSerializer(jacksonSerializer);
        
        template.afterPropertiesSet();
        return template;
    }
}

第四步:使用 RedisTemplate 操作 Redis

现在你就可以在代码中注入RedisTemplate,操作各种数据结构了:

java

运行

复制代码
@Service
public class UserService {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    // 操作字符串
    public void setUser(User user) {
        redisTemplate.opsForValue().set("user:" + user.getId(), user, 30, TimeUnit.MINUTES);
    }
    
    public User getUser(Long id) {
        return (User) redisTemplate.opsForValue().get("user:" + id);
    }
    
    // 操作哈希
    public void setUserField(Long id, String field, Object value) {
        redisTemplate.opsForHash().put("user:" + id, field, value);
    }
    
    // 操作有序集合(排行榜)
    public void addScore(Long userId, double score) {
        redisTemplate.opsForZSet().incrementScore("rank", userId, score);
    }
    
    public Set<ZSetOperations.TypedTuple<Object>> getTop10() {
        return redisTemplate.opsForZSet().reverseRangeWithScores("rank", 0, 9);
    }
}

四、深入原理:Redis 为什么这么快?

很多人都知道 Redis 很快,但很少有人能说清楚它为什么这么快。其实 Redis 的高性能是由四个关键因素共同决定的:

1. 完全基于内存

Redis 的所有数据都存储在内存中,内存的读写速度比磁盘快几个数量级。这是 Redis 快的最根本原因。

2. 单线程模型

Redis 采用单线程模型来处理请求,避免了多线程之间的上下文切换和竞争条件。很多人会问:"单线程怎么能处理这么多并发请求?"

答案是:Redis 的单线程指的是处理网络请求和命令执行的线程只有一个,但 Redis 还有其他线程负责持久化、集群同步等操作。而且单线程并不意味着性能差,只要 CPU 不是瓶颈,单线程的效率往往比多线程更高。

3. 高效的数据结构

Redis 的每种数据结构都经过了精心设计,底层使用了多种高效的数据结构实现:

  • 字符串:SDS(简单动态字符串)
  • 哈希:压缩列表 + 哈希表
  • 列表:压缩列表 + 双向链表
  • 集合:整数集合 + 哈希表
  • 有序集合:压缩列表 + 跳表

4. 非阻塞 I/O 多路复用

Redis 使用 I/O 多路复用技术(epoll)来处理多个网络连接,一个线程可以同时处理成千上万的客户端连接。

五、Redis 三大缓存问题与解决方案

在生产环境中使用 Redis,最常见的问题就是缓存穿透、缓存击穿和缓存雪崩。这三个问题如果处理不好,会导致数据库压力骤增,甚至宕机。

1. 缓存穿透

问题描述:用户请求的数据既不在缓存中,也不在数据库中。这样每次请求都会打到数据库上,恶意攻击者可以利用这个漏洞来攻击系统。

解决方案

  • 布隆过滤器:将所有可能存在的数据哈希到一个足够大的布隆过滤器中,请求先经过布隆过滤器过滤
  • 缓存空值:如果数据库中也没有数据,就缓存一个空值,并设置一个较短的过期时间

2. 缓存击穿

问题描述:一个热点 key 在过期的瞬间,有大量的并发请求同时访问这个 key。这些请求都会打到数据库上,导致数据库压力骤增。

解决方案

  • 互斥锁:当缓存失效时,只有第一个请求能获取到锁,去数据库查询数据并更新缓存
  • 永不过期:对于特别热点的 key,不设置过期时间,后台异步更新缓存

3. 缓存雪崩

问题描述:大量的 key 在同一时间过期,导致所有请求都打到数据库上,数据库压力骤增。

解决方案

  • 过期时间加随机值:给每个 key 的过期时间加上一个随机值,避免同时过期
  • 多级缓存:使用本地缓存 + Redis 缓存的多级缓存架构
  • 熔断降级:当数据库压力过大时,熔断部分非核心接口,返回默认数据

六、Redis 生产环境最佳实践

最后,我分享几个我在生产环境中总结的 Redis 最佳实践,帮你避开常见的坑:

  1. key 命名规范 :使用冒号分隔的命名方式,如业务:模块:id,例如user:info:123
  2. 设置过期时间:所有的 key 都应该设置合理的过期时间,避免内存泄漏
  3. 避免大 key:单个 key 的大小不要超过 1MB,大 key 会导致网络传输慢、内存碎片多
  4. 批量操作 :使用pipeline进行批量操作,减少网络往返次数
  5. 不要在 Redis 中存储大对象:大对象应该存储在数据库或对象存储中,Redis 只存储引用
  6. 开启持久化:根据业务需求选择合适的持久化方式,重要数据一定要开启持久化
  7. 监控 Redis 状态:监控 Redis 的内存使用率、QPS、命中率等指标,及时发现问题
相关推荐
2501_914245932 小时前
AWS CodeBuild 中 PHP 8.0 运行时版本不支持的解决方案
jvm·数据库·python
花间相见2 小时前
【大模型微调与部署02】—— ms-swift 自定义数据集完全教程:格式、dataset_info 配置、多格式兼容实战
开发语言·ssh·swift
噢,我明白了2 小时前
Java 入门,详解List,Map集合使用
java·list·map
Hello--_--World2 小时前
JS:闭包、函数柯里化、工厂函数、偏函数、立即执行函数 相关知识点与面试题
开发语言·javascript·ecmascript
ZenosDoron2 小时前
函数形参传数组
java·jvm·算法
一只幸运猫.2 小时前
字节跳动Java大厂面试版
java·开发语言·面试
2301_813599552 小时前
如何监控表空间自动扩展_DBA_DATA_FILES中的MAXBYTES分析
jvm·数据库·python
我不听你讲话2 小时前
Redis 配置与优化核心内容总结
数据库·redis·缓存
xier_ran2 小时前
【C++】“内部”、“外部”、“派生类”、“友元“类
java·开发语言·c++