杰瑞科技汇

Java、Redis、MySQL 如何协同工作?

  1. 各自的角色和定位:每个技术是做什么的。
  2. 为什么要把它们组合在一起:这种组合的优势。
  3. 它们如何协同工作:一个典型的交互流程。
  4. 核心代码示例:用 Java 代码展示如何操作 Redis 和 MySQL。
  5. 常见问题和解决方案:比如缓存穿透、击穿、雪崩。

各自的角色和定位

可以把它们想象成一个高效的团队,分工明确:

Java、Redis、MySQL 如何协同工作?-图1
(图片来源网络,侵删)

MySQL (关系型数据库 - 持久化存储)

  • 角色:团队的“档案库”或“硬盘”。
  • 定位:负责数据的持久化存储,数据安全地存储在磁盘上,即使服务器断电,数据也不会丢失,它提供事务支持(ACID 特性),保证了数据的一致性和可靠性。
  • 特点
    • 结构化存储:数据存储在预定义的表、行、列中,有严格的 schema。
    • 支持复杂查询:使用 SQL 语言,可以进行多表连接、分组、排序等复杂操作。
    • 持久化:数据写入磁盘,可靠性高。
    • 速度相对较慢:因为涉及到磁盘 I/O,读写速度远低于内存。

Redis (内存数据库 - 高速缓存)

  • 角色:团队的“办公桌”或“记事本”。
  • 定位:作为高速缓存,也用作数据中间件,它将数据存储在内存中,因此读写速度极快,通常比 MySQL 快几个数量级。
  • 特点
    • 内存存储:数据在内存中,读写速度极快。
    • 丰富的数据结构:不仅支持简单的 key-value,还支持 String, List, Set, Sorted Set, Hash 等多种数据结构。
    • 持久化选项:虽然主要在内存中,但也提供了 RDB(快照)和 AOF(日志)两种持久化方式,可以在一定程度上保证数据不丢失。
    • 多用途:除了缓存,还可用于分布式锁、消息队列、计数器、排行榜等场景。

Java (编程语言 - 业务逻辑)

  • 角色:团队的“项目经理”或“核心执行者”。
  • 定位:作为业务逻辑的实现者,Java 代码负责接收客户端请求,调用 Redis 和 MySQL,处理业务逻辑,并返回响应。
  • 特点
    • 跨平台:“一次编写,到处运行”。
    • 强大的生态系统:拥有海量的开源库和框架(如 Spring Boot),能非常方便地集成 Redis 和 MySQL。
    • 稳定性和高性能:经过多年发展,非常适合构建大型、高并发的企业级应用。

为什么要把它们组合在一起?(核心优势)

核心原因就是性能可扩展性

  1. 缓解数据库压力:绝大多数互联网应用都是“读多写少”,如果所有读请求都直接打到 MySQL 上,数据库很快会成为瓶颈,导致应用响应缓慢甚至崩溃,Redis 作为缓存,可以拦截掉大量对 MySQL 的读请求,极大地减轻了数据库的压力。
  2. 提升响应速度:用户请求的数据如果能从 Redis(内存)中直接获取,响应时间可以从几十甚至几百毫秒(MySQL 磁盘 I/O)降低到几毫秒,用户体验会得到质的飞跃。
  3. 提高系统吞吐量:由于 Redis 速度极快,系统可以在单位时间内处理更多的请求,从而提高了整体的吞吐量。

它们如何协同工作?(经典读写流程)

最常用的策略是 Cache-Aside Pattern(旁路缓存策略)

读操作流程

用户请求 -> Java服务 -> 检查Redis缓存
                              |
                              |-- (缓存命中) -> 直接返回数据给用户
                              |
                              |-- (缓存未命中) -> 查询MySQL数据库
                                                      |
                                                      |-- 获取数据 -> 将数据写入Redis缓存 -> 返回数据给用户

写操作流程

写操作相对复杂,因为要保证数据一致性,最简单的策略是 Write-Through(写穿透)先更新数据库,再删除缓存

用户请求 -> Java服务 -> 更新MySQL数据库
                              |
                              |-- 删除Redis中对应的缓存

为什么删除缓存而不是更新缓存?

Java、Redis、MySQL 如何协同工作?-图2
(图片来源网络,侵删)
  • 复杂性:如果缓存的数据结构复杂,更新缓存比删除缓存开销更大。
  • 原子性问题:先更新数据库再更新缓存,如果两个操作之间出现异常,可能导致缓存和数据库数据不一致。
  • 读时更新:下次读取时,发现缓存没有,会从数据库加载最新数据到缓存,这是一种最终一致性的体现。

核心代码示例 (基于 Spring Boot)

pom.xml 中添加依赖:

<!-- Spring Data Redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<!-- Spring Data JPA (用于简化MySQL操作) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

配置 application.yml:

spring:
  redis:
    host: 127.0.0.1
    port: 6379
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&serverTimezone=UTC
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: update # 自动更新表结构
    show-sql: true

Java 代码实现

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository; // JPA接口,用于操作MySQL
    @Autowired
    private RedisTemplate<String, Object> redisTemplate; // 用于操作Redis
    // 定义缓存中用户信息的key前缀
    private static final String USER_CACHE_KEY_PREFIX = "user::";
    /**
     * 获取用户信息 (实现了读操作的缓存逻辑)
     * @param userId 用户ID
     * @return 用户对象
     */
    public User getUserById(Long userId) {
        // 1. 构建Redis的Key
        String redisKey = USER_CACHE_KEY_PREFIX + userId;
        // 2. 先从Redis缓存中查询
        User user = (User) redisTemplate.opsForValue().get(redisKey);
        // 3. 缓存命中
        if (user != null) {
            System.out.println("Cache HIT! User " + userId + " found in Redis.");
            return user;
        }
        // 4. 缓存未命中,从MySQL数据库查询
        System.out.println("Cache MISS! Querying database for user " + userId);
        user = userRepository.findById(userId).orElse(null);
        // 5. 如果数据库中存在,则将数据写入Redis缓存,并设置过期时间(例如10分钟)
        if (user != null) {
            System.out.println("Writing user " + userId + " to Redis cache.");
            redisTemplate.opsForValue().set(redisKey, user, 10, TimeUnit.MINUTES);
        }
        return user;
    }
    /**
     * 更新用户信息 (实现了写操作的缓存逻辑)
     * @param user 用户对象
     */
    public void updateUser(User user) {
        // 1. 更新MySQL数据库
        userRepository.save(user);
        System.out.println("User " + user.getId() + " updated in MySQL.");
        // 2. 删除Redis中的缓存
        String redisKey = USER_CACHE_KEY_PREFIX + user.getId();
        redisTemplate.delete(redisKey);
        System.out.println("Cache for user " + user.getId() + " has been evicted.");
    }
}

常见问题和解决方案

使用缓存时,会遇到一些经典问题,需要特别注意。

缓存穿透

  • 现象:查询一个根本不存在的数据,由于缓存中没有,请求会直接打到数据库,数据库中也没有,所以不会写入缓存,如果大量这种请求涌入,数据库压力会骤增。
  • 解决方案
    • 缓存空对象:如果数据库查询结果为空,仍然将一个“空对象”或“特殊标记”缓存起来,并设置一个较短的过期时间。
    • 布隆过滤器:在访问缓存前,使用布隆过滤器快速判断一个 key 是否可能存在,如果过滤器说“不存在”,就直接拒绝请求,根本不会查询数据库。

缓存击穿

  • 现象:某个热点 Key(访问非常频繁)在某一刻突然失效了,在这个 Key 重新缓存数据之前,大量的并发请求会同时穿透缓存,直接打到数据库上,可能导致数据库崩溃。
  • 解决方案
    • 互斥锁/分布式锁:当缓存失效时,只允许第一个请求去查询数据库并重建缓存,其他请求等待第一个请求完成后,直接从缓存中获取数据,可以使用 Redis 的 SETNX 命令实现分布式锁。
    • 热点数据永不过期:逻辑上设置一个过期时间,但不采用 Redis 的过期机制,而是由后台任务定时更新缓存。

缓存雪崩

  • 现象
    1. 大规模 Key 同时失效:在某一时刻,系统中有大量的 Key 同时过期,导致大量请求在同一时间涌向数据库。
    2. Redis 服务宕机:Redis 实例本身不可用了,所有请求都打到了数据库上。
  • 解决方案
    • 针对 Key 过期:给 Key 的过期时间加上一个随机值,避免大规模 Key 同时失效。
    • 针对 Redis 宕机
      • 高可用架构:搭建 Redis 集群(主从、哨兵或 Cluster),避免单点故障。
      • 服务降级与熔断:当检测到 Redis 不可用时,可以暂时关闭某些非核心功能,或者直接返回默认值/错误信息,保护数据库。
      • 缓存预热:在系统上线前,提前将热点数据加载到缓存中。

特性 MySQL Redis Java
角色 持久化存储、数据仓库 高速缓存、中间件 业务逻辑实现
存储介质 硬盘 内存 内存(运行时)
速度 慢(毫秒级) 极快(微秒/纳秒级) 快(取决于逻辑)
核心优势 数据可靠、事务支持、复杂查询 性能极高、数据结构丰富 跨平台、生态强大、稳定
主要用途 存储核心业务数据、保证数据一致性 缓存热点数据、实现分布式锁、排行榜等 构建应用、连接一切

Java + Redis + MySQL 的组合是构建高性能、高并发后端服务的黄金标准,Java 作为胶水语言,负责业务流程的编排;Redis 作为第一层,提供极致的访问速度;MySQL 作为最后一道防线,保证数据的可靠性和一致性,理解它们各自的定位以及如何协同工作,是后端开发者的必备技能。

Java、Redis、MySQL 如何协同工作?-图3
(图片来源网络,侵删)
分享:
扫描分享到社交APP
上一篇
下一篇