【Kafka进阶篇】Canal+Kafka+ES实战:内容平台数据同步难题,这样解最优雅


🍃 予枫个人主页
📚 个人专栏 : 《Java 从入门到起飞》《读研码农的干货日常

💻 Debug 这个世界,Return 更好的自己!


引言

做内容平台或知识库开发的同学,大概率踩过这样的坑:MySQL存主数据,Redis做缓存、ES做全文检索,手动写同步逻辑又笨又容易出问题------数据不一致、同步延迟高、耦合度拉满,改一处代码牵一发而动全身。其实不用这么折腾,Canal监听MySQL Binlog,Kafka做消息缓冲,再同步到ES/Redis,一套组合拳就能实现异步解耦+高效同步,今天就手把手教你落地这套实战方案,新手也能快速上手~

文章目录

  • 引言
  • 一、业务痛点:为什么需要Canal+Kafka异构同步?
  • 二、核心组件解析(快速搞懂不踩坑)
    • [2.1 Canal:监听MySQL Binlog的"数据哨兵"](#2.1 Canal:监听MySQL Binlog的“数据哨兵”)
    • [2.2 Kafka:异步缓冲的"消息中间件"](#2.2 Kafka:异步缓冲的“消息中间件”)
    • [2.3 异构数据源(ES/Redis):数据消费的"最终目的地"](#2.3 异构数据源(ES/Redis):数据消费的“最终目的地”)
  • 三、实战演示:内容平台数据同步全流程(附代码)
    • [3.1 第一步:MySQL配置(开启Binlog,关键步骤)](#3.1 第一步:MySQL配置(开启Binlog,关键步骤))
    • [3.2 第二步:Canal配置(监听MySQL Binlog,发送到Kafka)](#3.2 第二步:Canal配置(监听MySQL Binlog,发送到Kafka))
    • [3.3 第三步:Kafka配置(创建主题,接收Canal数据)](#3.3 第三步:Kafka配置(创建主题,接收Canal数据))
    • [3.4 第四步:编写Kafka消费者(同步数据到ES+Redis)](#3.4 第四步:编写Kafka消费者(同步数据到ES+Redis))
      • [3.4.1 依赖配置(pom.xml)](#3.4.1 依赖配置(pom.xml))
      • [3.4.2 核心消费者代码(完整可运行)](#3.4.2 核心消费者代码(完整可运行))
      • [3.4.3 测试验证(确保同步成功)](#3.4.3 测试验证(确保同步成功))
  • 四、常见问题&避坑指南(实战必备)
  • 五、结尾总结

一、业务痛点:为什么需要Canal+Kafka异构同步?

做内容平台(比如博客、知识库)时,我们通常会用「MySQL+Redis+Elasticsearch」的架构组合,各自分工明确:

  • MySQL:存储核心业务数据(文章、用户、分类),保证数据一致性;
  • Redis:缓存热点数据(首页推荐、高频查询文章),提升查询速度;
  • Elasticsearch:实现全文检索(根据关键词搜文章、作者),解决MySQL全文检索低效问题。

但这套架构的核心痛点的是「数据同步」:

  1. 耦合度高:如果手动在业务代码里写同步逻辑(比如新增文章后,同时调用Redis和ES的接口),业务代码会变得臃肿,后续改同步规则要动核心代码;
  2. 数据不一致:网络波动、接口调用失败,都会导致MySQL数据和Redis/ES数据不同步(比如MySQL删了文章,Redis还缓存着);
  3. 性能瓶颈:高并发场景下,新增/修改数据时,同步操作会阻塞业务流程,拖慢接口响应速度。

而Canal+Kafka的组合,刚好能解决这些问题------异步解耦、低延迟、高可用,不用侵入业务代码,就能实现MySQL与异构数据源的无缝同步,这也是一线互联网公司常用的解决方案。

温馨提示:本文结合内容平台实战场景,所有代码可直接复制使用,建议点赞+收藏,后续落地时少走弯路~

二、核心组件解析(快速搞懂不踩坑)

在动手实战前,先快速搞懂三个核心组件的作用,不用深入源码,重点掌握「怎么用」和「为什么这么设计」。

2.1 Canal:监听MySQL Binlog的"数据哨兵"

Canal的核心作用,就是伪装成MySQL的从库,监听MySQL的Binlog(二进制日志,记录所有数据变更操作:新增、修改、删除),然后将这些变更数据解析出来,发送给Kafka。

关键特性(贴合实战):

  • 无侵入:不需要修改MySQL的业务代码,只需要开启Binlog,配置Canal即可;
  • 低延迟:Binlog变更后,Canal能快速解析,延迟在毫秒级;
  • 可定制:可以指定监听某张表、某个数据库,过滤无用的变更数据(比如日志表的变更)。

2.2 Kafka:异步缓冲的"消息中间件"

为什么要在Canal和ES/Redis之间加一层Kafka?而不是让Canal直接发送数据给ES/Redis?

核心原因(避坑重点):

  1. 解耦:Canal只负责"采集数据",不用关心数据要同步到哪里;ES/Redis消费者只负责"消费数据",不用关心数据来自哪里;
  2. 削峰填谷:高并发场景下(比如批量导入10万篇文章),Canal会快速产生大量变更数据,Kafka可以缓冲这些数据,避免ES/Redis被压垮;
  3. 重试机制:如果ES/Redis挂了,Kafka会保存消息,等ES/Redis恢复后,重新消费数据,避免数据丢失。

2.3 异构数据源(ES/Redis):数据消费的"最终目的地"

本文以内容平台为例,重点讲解两种常见的异构数据源消费场景:

  • Redis:同步热点文章数据(比如首页推荐的100篇热门文章),提升查询速度;
  • Elasticsearch:同步文章数据,实现全文检索(比如用户搜索"Canal实战",快速匹配相关文章)。

三、实战演示:内容平台数据同步全流程(附代码)

本节是全文核心,手把手教你从0到1搭建「Canal+Kafka+ES+Redis」的数据管道,所有步骤都经过实测,确保能落地。

前置环境准备(必看):

  • MySQL 8.0(开启Binlog,具体配置见下文);
  • Canal 1.1.7(稳定版,避免用最新版踩坑);
  • Kafka 3.6.0(单节点即可,测试环境无需集群);
  • Elasticsearch 7.17.0 + Kibana(可视化管理ES);
  • Redis 6.2.6;
  • Java 1.8(用于编写Kafka消费者代码,同步数据到ES/Redis)。

3.1 第一步:MySQL配置(开启Binlog,关键步骤)

Canal依赖MySQL的Binlog,所以第一步必须先配置MySQL,开启Binlog,否则Canal无法监听数据变更。

  1. 编辑MySQL的配置文件(my.cnf或my.ini),添加以下配置:
ini 复制代码
# 开启Binlog
log_bin = mysql-bin
# Binlog格式(必须是ROW格式,Canal才能解析)
binlog_format = ROW
# 服务器ID(唯一,不能和Canal、其他从库重复,建议设为1)
server_id = 1
# 只监听内容平台的数据库(本文示例数据库名:content_platform)
binlog_do_db = content_platform
  1. 重启MySQL,验证Binlog是否开启:
sql 复制代码
-- 执行以下SQL,返回ON即为开启成功
show variables like 'log_bin';
  1. 创建Canal专用账号(授予从库权限,用于监听Binlog):
sql 复制代码
CREATE USER 'canal'@'%' IDENTIFIED BY 'Canal@123456';
-- 授予权限
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%';
-- 刷新权限
FLUSH PRIVILEGES;

3.2 第二步:Canal配置(监听MySQL Binlog,发送到Kafka)

  1. 下载Canal 1.1.7(官网地址:https://github.com/alibaba/canal/releases/tag/canal-1.1.7),解压后进入conf目录。
  2. 修改canal.properties(核心配置,其他默认即可):
properties 复制代码
# 配置Kafka地址(单节点:ip:9092;集群:ip1:9092,ip2:9092)
canal.mq.servers = 127.0.0.1:9092
# 全局默认的Kafka主题(本文用:canal_content_platform)
canal.mq.topic = canal_content_platform
  1. 新建实例配置(监听content_platform数据库):
    • 进入conf目录,复制example文件夹,重命名为content_platform;
    • 编辑content_platform/instance.properties:
properties 复制代码
# MySQL主库地址和端口
canal.instance.master.address = 127.0.0.1:3306
# Canal监听的MySQL数据库
canal.instance.dbUsername = canal
canal.instance.dbPassword = Canal@123456
# 监听的数据库名(content_platform)
canal.instance.defaultDatabaseName = content_platform
# 从哪个位置开始监听(初次配置用最新位置:latest)
canal.instance.master.journal.name = 
canal.instance.master.position = 
canal.instance.master.timestamp = 
canal.instance.master.gtid = 
  1. 启动Canal:进入bin目录,执行启动命令(Windows:startup.bat;Linux:sh startup.sh),启动成功后,日志会显示"successfully connected to master"。

3.3 第三步:Kafka配置(创建主题,接收Canal数据)

  1. 启动Kafka(确保ZooKeeper或Kafka内置ZooKeeper已启动);
  2. 创建Kafka主题(canal_content_platform),用于接收Canal发送的数据:
bash 复制代码
# 创建主题(--replication-factor 1 单节点,--partitions 1 分区数)
kafka-topics.sh --create --topic canal_content_platform --bootstrap-server 127.0.0.1:9092 --replication-factor 1 --partitions 1
  1. 启动Kafka消费者,测试Canal是否能发送数据到Kafka:
bash 复制代码
kafka-console-consumer.sh --bootstrap-server 127.0.0.1:9092 --topic canal_content_platform
  1. 测试:在MySQL的content_platform数据库中,创建article表(文章表),并插入一条数据:
sql 复制代码
CREATE TABLE article (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    title VARCHAR(255) NOT NULL COMMENT '文章标题',
    content TEXT NOT NULL COMMENT '文章内容',
    author VARCHAR(50) NOT NULL COMMENT '作者',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) COMMENT '内容平台文章表';

-- 插入测试数据
INSERT INTO article (title, content, author) VALUES ('Canal+Kafka实战指南', '本文讲解如何用Canal+Kafka搭建数据管道...', '予枫');
  1. 观察Kafka消费者控制台,如果能接收到类似以下的JSON数据,说明Canal→Kafka配置成功:
json 复制代码
{
  "data": [
    {
      "id": "1",
      "title": "Canal+Kafka实战指南",
      "content": "本文讲解如何用Canal+Kafka搭建数据管道...",
      "author": "予枫",
      "create_time": "2026-02-24 15:30:00",
      "update_time": "2026-02-24 15:30:00"
    }
  ],
  "database": "content_platform",
  "table": "article",
  "type": "INSERT",
  "ts": 1714000200000
}

3.4 第四步:编写Kafka消费者(同步数据到ES+Redis)

本文用Java编写Kafka消费者,核心逻辑:监听Kafka主题,接收Canal发送的变更数据,根据操作类型(INSERT/UPDATE/DELETE),同步到Redis和ES。

3.4.1 依赖配置(pom.xml)

xml 复制代码
<dependencies>
    <!-- Kafka客户端依赖 -->
    <dependency>
        <groupId>org.apache.kafka</groupId>
        <artifactId>kafka-clients</artifactId>
        <version>3.6.0</version>
    </dependency>
    <!-- Redis依赖 -->
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>4.4.6</version>
    </dependency>
    <!-- ES依赖 -->
    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>elasticsearch-rest-high-level-client</artifactId>
        <version>7.17.0</version>
    </dependency>
    <!-- JSON解析依赖 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>2.0.32</version>
    </dependency>
    <!-- 日志依赖 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.36</version>
    </dependency>
</dependencies>

3.4.2 核心消费者代码(完整可运行)

java 复制代码
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.xcontent.XContentType;
import redis.clients.jedis.Jedis;

import java.io.IOException;
import java.time.Duration;
import java.util.Collections;
import java.util.Properties;

/**
 * Kafka消费者:同步Canal数据到Redis和Elasticsearch
 * 作者:予枫(CSDN)
 */
public class CanalKafkaConsumer {
    // Kafka配置
    private static final String KAFKA_SERVERS = "127.0.0.1:9092";
    private static final String TOPIC = "canal_content_platform";
    private static final String GROUP_ID = "canal_consumer_group";

    // Redis配置
    private static final String REDIS_HOST = "127.0.0.1";
    private static final int REDIS_PORT = 6379;
    private static final String REDIS_PASSWORD = ""; // 无密码留空
    private static final String REDIS_KEY_PREFIX = "article:"; // 文章缓存key前缀

    // ES配置
    private static final String ES_HOST = "127.0.0.1";
    private static final int ES_PORT = 9200;
    private static final String ES_INDEX = "article_index"; // ES索引名(对应文章表)

    public static void main(String[] args) throws IOException {
        // 1. 初始化Kafka消费者
        KafkaConsumer<String, String> kafkaConsumer = initKafkaConsumer();
        // 2. 初始化Redis客户端
        Jedis jedis = initJedis();
        // 3. 初始化ES客户端
        RestHighLevelClient esClient = initEsClient();
        // 4. 初始化ES索引(如果不存在则创建)
        initEsIndex(esClient);

        // 订阅Kafka主题
        kafkaConsumer.subscribe(Collections.singletonList(TOPIC));
        System.out.println("Kafka消费者启动成功,开始监听主题:" + TOPIC);

        // 循环消费消息
        while (true) {
            ConsumerRecords<String, String> records = kafkaConsumer.poll(Duration.ofMillis(100));
            for (ConsumerRecord<String, String> record : records) {
                String message = record.value();
                System.out.println("接收到Canal数据:" + message);
                // 解析Canal数据,同步到Redis和ES
                parseAndSyncData(message, jedis, esClient);
            }
        }
    }

    /**
     * 初始化Kafka消费者
     */
    private static KafkaConsumer<String, String> initKafkaConsumer() {
        Properties props = new Properties();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA_SERVERS);
        props.put(ConsumerConfig.GROUP_ID_CONFIG, GROUP_ID);
        // 自动提交offset(测试环境可用,生产环境建议手动提交)
        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true);
        props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, 1000);
        // 反序列化配置
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        // 首次消费从最新位置开始
        props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");
        return new KafkaConsumer<>(props);
    }

    /**
     * 初始化Redis客户端
     */
    private static Jedis initJedis() {
        Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT);
        if (!REDIS_PASSWORD.isEmpty()) {
            jedis.auth(REDIS_PASSWORD);
        }
        // 测试Redis连接
        try {
            jedis.ping();
            System.out.println("Redis连接成功");
        } catch (Exception e) {
            System.err.println("Redis连接失败:" + e.getMessage());
            System.exit(1);
        }
        return jedis;
    }

    /**
     * 初始化ES客户端
     */
    private static RestHighLevelClient initEsClient() {
        org.elasticsearch.client.RestClientBuilder builder = org.elasticsearch.client.RestClient.builder(
                new org.elasticsearch.client.RestClient.HttpHost(ES_HOST, ES_PORT, "http")
        );
        return new RestHighLevelClient(builder);
    }

    /**
     * 初始化ES索引(如果不存在则创建)
     */
    private static void initEsIndex(RestHighLevelClient esClient) throws IOException {
        GetIndexRequest getIndexRequest = new GetIndexRequest(ES_INDEX);
        boolean exists = esClient.indices().exists(getIndexRequest, RequestOptions.DEFAULT);
        if (!exists) {
            CreateIndexRequest createIndexRequest = new CreateIndexRequest(ES_INDEX);
            // 配置索引映射(文章表字段映射)
            String mapping = "{\n" +
                    "  \"mappings\": {\n" +
                    "    \"properties\": {\n" +
                    "      \"id\": {\"type\": \"long\"},\n" +
                    "      \"title\": {\"type\": \"text\", \"analyzer\": \"ik_max_word\"},\n" +
                    "      \"content\": {\"type\": \"text\", \"analyzer\": \"ik_max_word\"},\n" +
                    "      \"author\": {\"type\": \"keyword\"},\n" +
                    "      \"create_time\": {\"type\": \"date\", \"format\": \"yyyy-MM-dd HH:mm:ss\"},\n" +
                    "      \"update_time\": {\"type\": \"date\", \"format\": \"yyyy-MM-dd HH:mm:ss\"}\n" +
                    "    }\n" +
                    "  }\n" +
                    "}";
            createIndexRequest.mapping(mapping, XContentType.JSON);
            esClient.indices().create(createIndexRequest, RequestOptions.DEFAULT);
            System.out.println("ES索引 " + ES_INDEX + " 创建成功");
        } else {
            System.out.println("ES索引 " + ES_INDEX + " 已存在");
        }
    }

    /**
     * 解析Canal数据,同步到Redis和ES
     * @param message Canal发送的JSON数据
     * @param jedis Redis客户端
     * @param esClient ES客户端
     */
    private static void parseAndSyncData(String message, Jedis jedis, RestHighLevelClient esClient) {
        try {
            JSONObject jsonObject = JSONObject.parseObject(message);
            String database = jsonObject.getString("database");
            String table = jsonObject.getString("table");
            String type = jsonObject.getString("type"); // 操作类型:INSERT/UPDATE/DELETE
            JSONArray dataArray = jsonObject.getJSONArray("data");

            // 只处理content_platform数据库的article表(避免无关数据)
            if (!"content_platform".equals(database) || !"article".equals(table)) {
                return;
            }

            // 解析数据(单条数据,批量操作可循环处理)
            JSONObject data = dataArray.getJSONObject(0);
            String articleId = data.getString("id");
            String redisKey = REDIS_KEY_PREFIX + articleId;

            // 根据操作类型同步数据
            switch (type) {
                case "INSERT":
                    // 同步到Redis(缓存文章数据)
                    jedis.hset(redisKey, "title", data.getString("title"));
                    jedis.hset(redisKey, "author", data.getString("author"));
                    jedis.hset(redisKey, "create_time", data.getString("create_time"));
                    // 设置缓存过期时间(1小时,可根据业务调整)
                    jedis.expire(redisKey, 3600);
                    // 同步到ES
                    IndexRequest indexRequest = new IndexRequest(ES_INDEX).id(articleId);
                    indexRequest.source(data.toJSONString(), XContentType.JSON);
                    esClient.index(indexRequest, RequestOptions.DEFAULT);
                    System.out.println("新增文章同步成功:id=" + articleId);
                    break;

                case "UPDATE":
                    // 同步到Redis(更新缓存)
                    jedis.hset(redisKey, "title", data.getString("title"));
                    jedis.hset(redisKey, "content", data.getString("content"));
                    jedis.hset(redisKey, "update_time", data.getString("update_time"));
                    // 同步到ES(更新文档)
                    UpdateRequest updateRequest = new UpdateRequest(ES_INDEX, articleId);
                    updateRequest.doc(data.toJSONString(), XContentType.JSON);
                    esClient.update(updateRequest, RequestOptions.DEFAULT);
                    System.out.println("更新文章同步成功:id=" + articleId);
                    break;

                case "DELETE":
                    // 同步到Redis(删除缓存)
                    jedis.del(redisKey);
                    // 同步到ES(删除文档)
                    DeleteRequest deleteRequest = new DeleteRequest(ES_INDEX, articleId);
                    esClient.delete(deleteRequest, RequestOptions.DEFAULT);
                    System.out.println("删除文章同步成功:id=" + articleId);
                    break;

                default:
                    System.out.println("不支持的操作类型:" + type);
            }
        } catch (Exception e) {
            System.err.println("数据同步失败:" + e.getMessage());
            e.printStackTrace();
        }
    }
}

3.4.3 测试验证(确保同步成功)

  1. 启动Kafka消费者代码;
  2. 在MySQL中执行新增、修改、删除操作,观察控制台输出;
  3. 验证Redis:连接Redis,执行hgetall article:1,查看是否能获取到文章数据;
  4. 验证ES:打开Kibana,执行GET /article_index/_doc/1,查看是否能获取到文章文档。

如果所有操作都能同步成功,说明整个数据管道搭建完成!

四、常见问题&避坑指南(实战必备)

实战中难免会遇到各种问题,这里整理了4个高频坑,附解决方案,帮你快速排查问题,节省时间。

坑1:Canal启动失败,提示"connection refused"

解决方案:

  1. 检查MySQL是否启动,地址和端口是否正确;
  2. 检查Canal的instance.properties中,数据库账号密码是否正确;
  3. 检查MySQL的binlog_do_db配置,是否正确指定了要监听的数据库。
    坑2:Kafka能接收到数据,但ES/Redis同步失败

解决方案:

  1. 检查ES/Redis是否启动,客户端配置(地址、端口)是否正确;
  2. 检查ES索引是否创建,映射是否正确(比如日期格式是否匹配);
  3. 查看消费者控制台日志,根据异常信息排查(比如JSON解析失败、权限不足)。
    坑3:数据同步延迟高(超过1秒)

解决方案:

  1. 检查Canal的binlog监听配置,是否过滤了无用数据,减少数据传输量;
  2. 检查Kafka的分区数,高并发场景下可增加分区数,提升消费速度;
  3. 优化消费者代码,避免同步操作阻塞(比如异步同步到ES/Redis)。
    坑4:MySQL重启后,Canal无法监听Binlog

解决方案:

  1. 检查MySQL的server_id是否唯一,避免和Canal重复;
  2. 进入Canal的content_platform实例目录,删除meta.dat文件,重启Canal(重置监听位置)。

五、结尾总结

本文结合内容平台实战场景,手把手教你搭建了「Canal+Kafka+ES+Redis」的异步解耦数据管道,核心解决了MySQL与异构数据源之间的同步难题------实现了无侵入、低延迟、高可用的数据同步,同时降低了系统耦合度。

核心要点回顾:

  1. Canal负责监听MySQL Binlog,采集数据变更;
  2. Kafka负责异步缓冲数据,解耦采集和消费环节;
  3. 消费者负责将数据同步到ES/Redis,实现查询高效分离;
  4. 实战中重点关注配置细节和避坑指南,确保方案可落地。

这套方案不仅适用于内容平台,还可迁移到知识库、电商等各类需要异构数据源同步的场景,掌握后能显著提升系统架构的灵活性和性能。

最后,如果你觉得本文对你有帮助,麻烦点赞+收藏+关注,后续会持续更新更多Java、中间件实战干货,一起进阶成长!有任何问题,欢迎在评论区留言讨论~

相关推荐
LSL666_1 小时前
5 Redis通用命令
java·开发语言·redis·命令
rannn_1111 小时前
【Redis|基础篇】初识、Redis的安装与启动、Redis命令、Java客户端
java·redis·后端·缓存·nosql
WKP94182 小时前
ES快速入门
大数据·elasticsearch·搜索引擎
陈桴浮海2 小时前
MySQL 主从复制与 GTID 环形复制
linux·mysql·云原生
xing-xing2 小时前
Spring Data Elasticsearch
后端·spring·elasticsearch
“αβ”2 小时前
MySQL数据类型
c语言·数据库·opencv·mysql·数据挖掘·数据类型·数据
西门吹雪分身2 小时前
SpringCloudGateway过滤器之RequestRateLimiterGatewayFilterFactory
java·redis·spring cloud
難釋懷2 小时前
基于Redis的Stream结构作为消息队列,实现异步秒杀下单
数据库·redis·缓存
七夜zippoe2 小时前
微服务架构下Spring Session与Redis分布式会话实战全解析
java·redis·maven·spring session·分布式会话