数据一致性springcloud+rabbitmq+mysql+redis

来由:

使用redis最常见的就是数据和数据库不一样,库里改了缓存还是旧数据,影响企业数据。

mysql里一般用的主从的binlog通知来更新数据;或者另一种方法就是用消息队列mq来通知后更新。

思路:

本文讲的是:mysql数据更新后rabbitmq通知redis更新数据

解决:

版本:

springboot-2.7.4 springcloudalibaba-2021.0.5.0

redis-8.6.1 rabbitmq-4.2.5

新建项目cache_consistency_rabbitmq_redis

目录:

pom.xml

java 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.14</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.dp.ccrr</groupId>
    <artifactId>cache_consistency_rabbitmq_redis</artifactId>
    <version>1.0.0</version>
    <properties>
        <java.version>17</java.version>
        <spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Spring Boot Data JPA -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <!-- Spring Boot AMQP (RabbitMQ) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

        <!-- Spring Boot 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>
            <version>8.0.33</version>
        </dependency>

        <!-- 连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.16</version>
        </dependency>

        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- JSON -->
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>2.0.43</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
复制代码
RabbitMQConfig.java
java 复制代码
package com.dp.mrr.config;


import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMQConfig {

    // 交换机名称
    public static final String USER_EXCHANGE = "user.exchange";

    // 队列名称
    public static final String USER_CACHE_QUEUE = "user.cache.queue";

    // 路由Key
    public static final String USER_CHANGE_ROUTING_KEY = "user.change";

    /**
     * 创建 Topic 交换机
     */
    @Bean
    public TopicExchange userExchange() {
        return ExchangeBuilder.topicExchange(USER_EXCHANGE).durable(true).build();
    }

    /**
     * 创建队列
     */
    @Bean
    public Queue userCacheQueue() {
        return QueueBuilder.durable(USER_CACHE_QUEUE).build();
    }

    /**
     * 绑定队列到交换机
     */
    @Bean
    public Binding userChangeBinding() {
        return BindingBuilder
                .bind(userCacheQueue())
                .to(userExchange())
                .with(USER_CHANGE_ROUTING_KEY);
    }

    /**
     * 配置消息转换器(JSON格式)
     */
    @Bean
    public Jackson2JsonMessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }

    /**
     * 配置 RabbitTemplate
     */
    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        template.setMessageConverter(messageConverter());
        return template;
    }
}
复制代码
RedisConfig.java
java 复制代码
package com.dp.mrr.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        // Key 序列化器
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());

        // Value 序列化器(JSON格式)
        GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer();
        template.setValueSerializer(jsonSerializer);
        template.setHashValueSerializer(jsonSerializer);

        template.afterPropertiesSet();
        return template;
    }
}
复制代码
UserCacheConsumer.java
java 复制代码
package com.dp.mrr.consumer;


import com.alibaba.fastjson2.JSON;
import com.dp.mrr.message.UserChangeMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

import static com.dp.mrr.config.RabbitMQConfig.USER_CACHE_QUEUE;

@Slf4j
@Component
public class UserCacheConsumer {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    private static final String USER_CACHE_PREFIX = "user:";
    private static final long CACHE_EXPIRE_SECONDS = 3600; // 1小时过期

    /**
     * 监听用户变更消息,更新 Redis 缓存
     */
    @RabbitListener(queues = USER_CACHE_QUEUE)
    public void handleUserChange(UserChangeMessage message) {
        log.info("收到消息: operation={}, userId={}", message.getOperation(), message.getUserId());

        String cacheKey = USER_CACHE_PREFIX + message.getUserId();

        switch (message.getOperation()) {
            case "CREATE":
            case "UPDATE":
                // 创建或更新:将最新数据存入 Redis
                if (message.getUserData() != null) {
                    String userJson = JSON.toJSONString(message.getUserData());
                    redisTemplate.opsForValue().set(cacheKey, userJson, CACHE_EXPIRE_SECONDS, TimeUnit.SECONDS);
                    log.info("缓存已更新: key={}", cacheKey);
                }
                break;

            case "DELETE":
                // 删除:从 Redis 中移除
                redisTemplate.delete(cacheKey);
                log.info("缓存已删除: key={}", cacheKey);
                break;

            default:
                log.warn("未知操作类型: {}", message.getOperation());
        }
    }
}
复制代码
UserController.java
java 复制代码
package com.dp.mrr.controller;


import com.dp.mrr.entity.User;
import com.dp.mrr.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/user")
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping
    public User create(@RequestBody User user) {
        return userService.createUser(user);
    }

    @PutMapping("/{id}")
    public User update(@PathVariable Long id, @RequestBody User user) {
        return userService.updateUser(id, user);
    }

    @DeleteMapping("/{id}")
    public String delete(@PathVariable Long id) {
        userService.deleteUser(id);
        return "删除成功";
    }

    @GetMapping("/{id}")
    public User get(@PathVariable Long id) {
        return userService.getUser(id);
    }
}
复制代码
User.java
java 复制代码
package com.dp.mrr.entity;


import lombok.Data;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;

@Data
@Entity
@Table(name = "t_user")
public class User implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;

    private String email;

    private Integer age;

    @Column(updatable = false)
    private Date createTime;

    private Date updateTime;

    @PrePersist
    protected void onCreate() {
        createTime = new Date();
        updateTime = new Date();
    }

    @PreUpdate
    protected void onUpdate() {
        updateTime = new Date();
    }
}
复制代码
UserChangeMessage.java
java 复制代码
package com.dp.mrr.message;


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * 用户数据变更消息
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserChangeMessage implements Serializable {

    /**
     * 操作类型: CREATE, UPDATE, DELETE
     */
    private String operation;

    /**
     * 变更的用户ID
     */
    private Long userId;

    /**
     * 变更后的完整用户数据(CREATE/UPDATE时携带)
     */
    private Object userData;

    /**
     * 时间戳
     */
    private Long timestamp;

    public UserChangeMessage(String operation, Long userId, Object userData) {
        this.operation = operation;
        this.userId = userId;
        this.userData = userData;
        this.timestamp = System.currentTimeMillis();
    }
}
复制代码
UserRabbitMqProducer.java
java 复制代码
package com.dp.mrr.producer;


import com.dp.mrr.message.UserChangeMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import static com.dp.mrr.config.RabbitMQConfig.USER_CHANGE_ROUTING_KEY;
import static com.dp.mrr.config.RabbitMQConfig.USER_EXCHANGE;

@Slf4j
@Component
public class UserRabbitMqProducer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 发送用户变更消息
     *
     * @param operation 操作类型: CREATE, UPDATE, DELETE
     * @param userId    用户ID
     * @param userData  用户数据(可选)
     */
    public void sendUserChangeMessage(String operation, Long userId, Object userData) {
        UserChangeMessage message = new UserChangeMessage(operation, userId, userData);
        log.info("发送消息: operation={}, userId={}", operation, userId);
        rabbitTemplate.convertAndSend(USER_EXCHANGE, USER_CHANGE_ROUTING_KEY, message);
    }
}
复制代码
UserRepository.java
java 复制代码
package com.dp.mrr.repository;

import com.dp.mrr.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
复制代码
UserService.java
java 复制代码
package com.dp.mrr.service;


import com.alibaba.fastjson2.JSON;
import com.dp.mrr.entity.User;
import com.dp.mrr.producer.UserRabbitMqProducer;
import com.dp.mrr.repository.UserRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Optional;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class UserService {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private UserRepository userRepository;

    @Autowired
    private UserRabbitMqProducer rabbitMqProducer;

    /**
     * 创建用户
     * 步骤:1. 保存到 MySQL → 2. 发送消息通知更新 Redis
     */
    @Transactional
    public User createUser(User user) {
        // 1. 保存到数据库
        User savedUser = userRepository.save(user);
        log.info("用户已保存到 MySQL: id={}", savedUser.getId());

        // 2. 发送消息通知其他服务更新缓存
        rabbitMqProducer.sendUserChangeMessage("CREATE", savedUser.getId(), savedUser);

        return savedUser;
    }

    /**
     * 更新用户
     * 步骤:1. 更新 MySQL → 2. 发送消息通知更新 Redis
     */
    @Transactional
    public User updateUser(Long id, User user) {
        // 1. 检查用户是否存在
        Optional<User> existingOpt = userRepository.findById(id);
        if (!existingOpt.isPresent()) {
            throw new RuntimeException("用户不存在: " + id);
        }

        User existing = existingOpt.get();
        existing.setUsername(user.getUsername());
        existing.setEmail(user.getEmail());
        existing.setAge(user.getAge());

        // 2. 更新到数据库
        User updatedUser = userRepository.save(existing);
        log.info("用户已更新到 MySQL: id={}", updatedUser.getId());

        // 3. 发送消息通知更新缓存
        rabbitMqProducer.sendUserChangeMessage("UPDATE", updatedUser.getId(), updatedUser);

        return updatedUser;
    }

    /**
     * 删除用户
     * 步骤:1. 从 MySQL 删除 → 2. 发送消息通知删除 Redis 缓存
     */
    @Transactional
    public void deleteUser(Long id) {
        // 1. 从数据库删除
        userRepository.deleteById(id);
        log.info("用户已从 MySQL 删除: id={}", id);

        // 2. 发送消息通知删除缓存
        rabbitMqProducer.sendUserChangeMessage("DELETE", id, null);
    }

    /**
     * 查询用户(带缓存)
     * 优先从 Redis 读取,没有则查 MySQL
     */
    public User getUser(Long id) {
        String cacheKey = "user:" + id;

        // 1. 先从 Redis 获取
        String cachedUser = (String) redisTemplate.opsForValue().get(cacheKey);
        if (cachedUser != null) {
            log.info("从 Redis 获取用户: id={}", id);
            return JSON.parseObject(cachedUser, User.class);
        }

        // 2. Redis 没有,查询 MySQL
        Optional<User> userOpt = userRepository.findById(id);
        if (userOpt.isPresent()) {
            User user = userOpt.get();
            log.info("从 MySQL 获取用户: id={}", id);

            // 3. 写入 Redis 缓存
            redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(user), 3600, TimeUnit.SECONDS);

            return user;
        }

        return null;
    }
}

application.yml

java 复制代码
server:
  port: 8081
spring:
  application:
    name: cache_consistency_rabbitmq_redis
  # MySQL 数据源配置
  datasource:
    url: jdbc:mysql://localhost:3306/data_consistency?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8
    username: root
    password: XXX你的密码
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

  # JPA 配置
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL8Dialect
        format_sql: true

  # RabbitMQ 配置
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
    connection-timeout: 5000

  # Redis 配置
  redis:
    host: localhost
    port: 6379
    password: XXX你的密码  # 如果设置了密码。没设置不填
    timeout: 2000ms
    lettuce:
      pool:
        max-active: 10
        max-idle: 5

logging:
  level:
    com.example: DEBUG

测试:

相关推荐
墨神谕2 小时前
什么是回表查询
mysql
草莓熊Lotso2 小时前
MySQL 事务管理全解:从 ACID 特性、隔离级别到 MVCC 底层原理
linux·运维·服务器·c语言·数据库·c++·mysql
不愿透露姓名的大鹏2 小时前
MySQL InnoDB核心参数深度优化/性能调优
运维·服务器·数据库·mysql
零陵上将军_xdr3 小时前
MySQL体系架构
数据库·mysql·架构
柒.梧.3 小时前
MySQL索引优化+慢查询全解析
数据库·mysql
captain3763 小时前
数据库约束
mysql
呆瑜nuage4 小时前
MySQL数据类型全解析
数据库·mysql
Harvy_没救了4 小时前
MySQL主从架构深度解析:原理、优化与实践指南
运维·mysql·架构
黑牛儿4 小时前
MySQL 实战进阶:从单表优化到分布式数据库适配
数据库·分布式·mysql