Redis缓存双写一致性笔记(下)

news2024/9/30 5:33:02

Redis和Canal结合使用是一种常见的解决方案,用于确保MySQL数据库中的更改实时同步到Redis缓存中,从而保持数据的一致性。

这种同步机制虽然能够实现近乎实时的数据同步,但可能会有轻微的延迟,因此它更适合对数据一致性要求不是特别严格的场景。如果需要更强的一致性保证,可能需要考虑其他策略,如延时双删、分布式锁等

1. canal

1.1 介绍

Canal是一款开源的数据同步工具,主要用于实现MySQL数据库增量日志的解析,提供增量数据订阅和消费的功能。Canal通过模拟MySQL slave的交互协议,将自己伪装成一个slave,向MySQL master发送dump协议,从而接收并解析master的binary log。

产生历史背景:早期阿里巴巴因为杭州和美国双机房部署,存在跨机房数据同步的业务需求,实现方式主要是基于业务trigger (触发器)获取增量变更。从2010年开始,阿里巴巴逐步尝试采用解析数据库日志获取增量变更进行同步,由此衍生出了canal项目;

1.2 功能

Canal基于binary log增量订阅和消费,可用于:

  • 数据库镜像
  • 数据库实时备份
  • 索引构建和实时维护
  • 业务cache刷新
  • 带业务逻辑的增量数据处理

1.3 工作原理

我们先看下传统的MySQL主从复制工作原理

MySQL的主从复制将经过如下步骤:

  • 1. 当master主服务器上的数据发生改变时,则将其改变写入二进制事件日志文件中;
  • 2. salve 从服务器会在一定时间间隔内对master主服务器上的二进制日志进行探测,探测其是否发生过改变; 如果探测到master主服务器的二进制事件日志发生了改变,则开始一个I/O Thread请求master二进制事件日志;
  • 3. 同时master主服务器为每个I/0 Thread启动一个dump Thread, 用于向其发送二进制事件日志;
  • 4. slave 从服务器将接收到的二进制事件日志保存至自己本地的中继日志文件中;
  • 5. salve 从服务器将启动SQL Thread从中继日志中读取二进制日志,在本地重放,使得其数据和主服务器保持一致;
  • 6. 后I/O Thread和SQL Thread将进入睡眠状态,等待下一次被唤醒。

canal工作原理

  • 模拟 MySQL Slave:canal 模拟MySQL slave的交互协议,伪装自己为MySQL slave,向MySQL master发送dump协议

  • 接收 Binlog:MySQL master收到dump请求,开始推送binary log给slave (即canal )canal 解析binary log对象(原始为byte流)

  • 解析 Binlog:Canal 接收到 binlog 增量日志后,对其进行解析,从而获取数据库结构及数据变更的信息 。
  • 数据消费: 解析后的 binlog 日志可以被 Canal Client 消费,用于各种业务场景,如数据同步、缓存更新、索引构建等

Canal 的主要组件包括:

  • EventParser:负责模拟 MySQL Slave 协议和 Master 进行交互,解析 Binlog 数据。
  • EventSink:作为 Parser 和 Store 之间的链接器,负责数据过滤、加工和分发。
  • EventStore:负责数据存储,将解析后的数据写入本地内存中的环形队列。
  • MetaManager:负责增量订阅和消费信息的管理。

2.canal部署前置-mysql配置

2.1 查看MySQL版本

确保 MySQL 版本在 Canal 支持的范围内(如 5.1.x, 5.5.x, 5.6.x, 5.7.x, 8.0.x)

select version(); 

2.2 mysql 二进制日志

查询是否开启日志

show master status;
show variables like 'log_bin';

配置 MySQL 的 my.cnf 文件以启用 binary log 并设置日志格式

[mysqld]
log-bin=mysql-bin
binlog-format=ROW
server-id=1

配置后重启 MySQL 服务以应用配置更改。

查看是否成功

show variables like 'log_bin';

2.3 授权canal链接MySQL账号

mysql默认没有canal账户,需要我们新建并授权

DROP USER IF EXISTS 'canal'@'%' ;
CREATE USER 'canal'@'%' IDENTIFIED BY 'canal' ;
GRANT ALL PRIVILEGES ON *.* TO 'canal'@'%' IDENTIFIED BY 'canal' ;
FLUSH PRIVILEGES;
SELECT * FROM mysql.user;

3. canal服务端部署

3.1下载地址

https://github.com/alibaba/canal/releases

选择你需要的版本

不同语言的下载地址

canal java 客户端: https://github.com/alibaba/canal/wiki/ClientExample
canal c# 客户端: https://github.com/dotnetcore/CanalSharp
canal go客户端: https://github.com/CanalClient/canal-go
canal php客户端: https://github.com/xingwenge/canal-php
canal Python客户端:https://github.com/haozi3156666/canal-python
canal Rust客户端:https://github.com/laohanlinux/canal-rs
canal Nodejs客户端:https://github.com/marmot-z/canal-nodejs

3.2 安装配置

上传到服务器

/usr/local/mysoftware/canal/

使用tar -zxvf解压缩

tar -zxvf /usr/local/mysoftware/canal.版本  -C /usr/local/mysoftware/canal/

修改配置文件

修改 /mycanal/conf/example路径下instance.properties文件

链接地址换成自己的MySQL主机master的IP地址

登录账户换成自己的在MySQL新建的canal账户

Canal 的配置文件中一些重要的配置参数说明:

  • canal.instance.mysql.slaveId: 指定 MySQL 服务器的 slaveId,需要保证在 MySQL 集群中唯一。在 1.1.x 版本之后,Canal 可以自动生成,不需要手工指定 。

  • canal.instance.master.address: 指定 MySQL 主库的连接地址,例如 127.0.0.1:3306

  • canal.instance.dbUsernamecanal.instance.dbPassword: 分别指定连接 MySQL 数据库的用户名和密码 。

  • canal.instance.connectionCharset: 指定连接数据库时使用的字符集,如 UTF-8

  • canal.instance.filter.regex: 使用正则表达式指定 Canal 需要监听的数据库和表。例如 .*\\..* 表示监听所有数据库和表 。

  • canal.instance.filter.black.regex: 指定 Canal 需要忽略的数据库和表的黑名单 。

  • canal.ipcanal.port: 分别指定 Canal 服务绑定的 IP 地址和端口号 。

  • canal.serverMode: 指定 Canal 服务的模式,如 tcpkafkarocketMQ 等 。

  • canal.destinations: 指定当前 Canal 服务上部署的实例列表 。

  • canal.conf.dir: 指定 Canal 配置文件的目录 。

  • canal.auto.scancanal.auto.scan.interval: 分别指定是否开启实例自动扫描以及扫描间隔时间 。

  • canal.instance.memory.buffer.sizecanal.instance.memory.buffer.memunit: 分别指定内存缓冲区的大小和单位大小 。

  • canal.instance.parser.parallel: 指定是否并行解析 binlog 。

  • canal.instance.tsdb.enable: 指定是否开启 table meta 的时间序列版本记录功能 。

  • canal.instance.network.receiveBufferSizecanal.instance.network.sendBufferSize: 指定网络接收和发送缓冲区的大小 。

  • canal.instance.detecting.enable: 是否开启心跳检查 。

  • canal.instance.fallbackIntervalInSeconds: 当 Canal 发生 MySQL 切换时,在新的 MySQL 库上查找 binlog 时需要往前查找的时间,单位为秒

3.3启动

在/usr/local/mysoftware/canal//bin路径下执行 

使用 startup.sh 脚本启动 Canal 服务。
使用 stop.sh 脚本停止 Canal 服务。

通过查看日志文件 log/canal/canal.log 来验证 Canal 是否成功启动

4. canal客户端

以java为例

4.1 添加依赖

<dependency>
    <groupId>com.alibaba.otter</groupId>
    <artifactId>canal.client</artifactId>
    <version>1.1.0</version>
</dependency>

4.2 配置YML

# ====================== alibaba.druid ======================
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.druid.test-while-idle=fasle

4.3 业务类

// RedisUtils
package com.luojia.canaldemo.utils;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class RedisUtils {

    public static final String REDIS_IP_ADDR = "127.0.0.1";

    public static final String REDIS_PWD = "123456";

    public static JedisPool jedisPool;

    static {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(20);
        jedisPoolConfig.setMaxIdle(10);
        jedisPool = new JedisPool(jedisPoolConfig, REDIS_IP_ADDR, 6379, 10000, REDIS_PWD);
    }

    public static Jedis getJedis() throws Exception {
        if (null != jedisPool) {
            return jedisPool.getResource();
        }
        throw new Exception("Jedispoll is not ok");
    }
}
package com.luojia.canaldemo.biz;


import com.alibaba.fastjson.JSONObject;
import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.client.CanalConnectors;
import com.alibaba.otter.canal.common.utils.AddressUtils;
import com.alibaba.otter.canal.protocol.CanalEntry.*;
import com.alibaba.otter.canal.protocol.Message;
import com.luojia.canaldemo.utils.RedisUtils;
import io.lettuce.core.RedisClient;
import redis.clients.jedis.Jedis;

import java.net.InetSocketAddress;
import java.util.List;
import java.util.UUID;

public class RedisCanalClientExample {

    public static final Integer _60SECONDS = 60;
    public static final String REDIS_IP_ADDR = "127.0.0.1";

    public static void redisInsert(List<Column> columns) {

        JSONObject jsonObject = new JSONObject();
        for (Column column : columns) {
            System.out.println(column.getName() + ": " + column.getValue() + " insert = " + column.getUpdated());
            jsonObject.put(column.getName(), column.getValue());
        }

        if (columns.size() > 0) {
            try (Jedis jedis = RedisUtils.getJedis()) {
                jedis.set(columns.get(0).getValue(), jsonObject.toJSONString());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static void redisDelete(List<Column> columns) {
        JSONObject jsonObject = new JSONObject();
        for (Column column : columns) {
            System.out.println(column.getName() + ": " + column.getValue() + " delete = " + column.getUpdated());
            jsonObject.put(column.getName(), column.getValue());
        }

        if (columns.size() > 0) {
            try (Jedis jedis = RedisUtils.getJedis()) {
                jedis.del(columns.get(0).getValue());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static void redisUpdate(List<Column> columns) {

        JSONObject jsonObject = new JSONObject();
        for (Column column : columns) {
            System.out.println(column.getName() + ": " + column.getValue() + " update = " + column.getUpdated());
            jsonObject.put(column.getName(), column.getValue());
        }

        if (columns.size() > 0) {
            try (Jedis jedis = RedisUtils.getJedis()) {
                jedis.set(columns.get(0).getValue(), jsonObject.toJSONString());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static void printEntry(List<Entry> entrys) {
        for (Entry entry : entrys) {
            if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {
                continue;
            }

            RowChange rowChage = null;
            try {
                rowChage = RowChange.parseFrom(entry.getStoreValue());
            } catch (Exception e) {
                throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(),
                        e);
            }

            EventType eventType = rowChage.getEventType();
            System.out.println(String.format("================&gt; binlog[%s:%s] , name[%s,%s] , eventType : %s",
                    entry.getHeader().getLogfileName(), entry.getHeader().getLogfileOffset(),
                    entry.getHeader().getSchemaName(), entry.getHeader().getTableName(),
                    eventType));

            for (RowData rowData : rowChage.getRowDatasList()) {
                if (eventType == EventType.DELETE) {
                    redisDelete(rowData.getBeforeColumnsList());
                } else if (eventType == EventType.INSERT) {
                    redisInsert(rowData.getAfterColumnsList());
                } else {
                    System.out.println("-------&gt; before");
                    redisUpdate(rowData.getBeforeColumnsList());
                    System.out.println("-------&gt; after");
                }
            }
        }
    }

    public static void main(String[] args) {
        // 创建链接
        CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(AddressUtils.getHostIp(),
                11111), "example", "", "");
        int batchSize = 1000;
        int emptyCount = 0;
        try {
            connector.connect();
            // 监听当前库的所有表
            // connector.subscribe(".*\\..*");
            connector.subscribe("jmall.t_user");
            connector.rollback();
            int totalEmptyCount = 10 * _60SECONDS;
            while (emptyCount < totalEmptyCount) {
                System.out.println("我是canal,每秒监听一次:" + UUID.randomUUID().toString());
                Message message = connector.getWithoutAck(batchSize); // 获取指定数量的数据
                long batchId = message.getId();
                int size = message.getEntries().size();
                if (batchId == -1 || size == 0) {
                    // MySQL数据没有变动
                    emptyCount++;
                    System.out.println("empty count : " + emptyCount);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                    }
                } else {
                    emptyCount = 0;
                    // System.out.printf("message[batchId=%s,size=%s] \n", batchId, size);
                    printEntry(message.getEntries());
                }

                connector.ack(batchId); // 提交确认
                // connector.rollback(batchId); // 处理失败, 回滚数据
            }

            System.out.println("empty too many times, exit");
        } finally {
            connector.disconnect();
        }
    }
}

5.监控canal运行状态

监控 Canal 服务的运行状态可以通过以下几种方法:

  • 查看日志文件:Canal 的日志文件通常位于 logs/ 目录下,你可以通过查看 canal.log 和对应实例的日志文件(如 example.log)来获取 Canal 的运行状态和错误信息。

  • 使用 Canal Admin:Canal 提供了一个管理界面 canal-admin,你可以通过它来监控 Canal 实例的状态,包括查看运行日志、订阅的通道、以及解析的 Binlog 位置等信息。

  • 使用 Prometheus 和 Grafana:Canal 支持与 Prometheus 集成,你可以将 Canal 作为数据源配置到 Prometheus 中,并通过 Grafana 进行可视化监控。

  • 使用第三方监控工具:你还可以使用第三方的监控工具来监控 Canal 服务,例如 Zabbix、Nagios 等。

确保监控 Canal 的同时,也要注意保护好 Canal 的安全,避免敏感信息泄露。

6.Redis 和 Canal 结合的思考

使用 Redis 和 Canal 结合实现缓存双写一致性有以下优点和缺点:

优点

  • 数据实时性:Canal 通过监听 MySQL 的 binlog 实现实时的数据捕获,能够快速地将数据变更同步到 Redis 缓存中,从而提高数据的实时性。

  • 减少并发冲突:通过使用 Canal,可以减少应用程序代码中直接操作缓存和数据库的逻辑,从而降低并发写入时的数据冲突。

  • 提高系统稳定性:Canal 作为一个独立的数据同步组件,可以独立于应用程序运行,即使应用程序出现故障,Canal 仍然可以继续监听数据变更并同步到缓存,提高了系统的稳定性。

  • 数据一致性的保证:Canal 可以确保在数据库中的数据发生变更时,缓存中的数据也会相应地更新,从而保证了数据的一致性。

  • 扩展性和灵活性:Canal 不仅可以同步数据到 Redis,还可以同步到其他存储系统,如 Elasticsearch、HBase 等,提供了很好的扩展性。

缺点

  • 复杂性增加:引入 Canal 会增加系统的复杂性,需要额外的部署、配置和维护工作。

  • 性能开销:虽然 Canal 可以提高数据实时性,但在处理大量数据时,可能会对 MySQL 的性能产生一定影响,因为需要读取和解析 binlog。

  • 数据延迟:尽管 Canal 可以实现近乎实时的数据同步,但在极端情况下,仍然可能存在短暂的数据延迟。

  • 故障恢复:如果 Canal 服务出现故障,可能需要一定的机制来保证数据不会丢失,例如通过消息队列来保证数据的可靠性。

  • 资源消耗:Canal 作为一个独立的服务,需要消耗服务器资源,包括 CPU、内存和网络带宽等。

总的来说,Redis 和 Canal 的结合使用可以有效地解决缓存与数据库之间的一致性问题,但也需要考虑到引入的复杂性和可能的性能开销。

7.最后

感谢大家,请大家多多支持!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2178771.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STM32 DMA+AD多通道

单片机学习&#xff01; 目录 一、DMA配置步骤 二、ADC配置步骤 三、DMAAD多通道框图 四、DMAAD多通道函数设计详细步骤 4.1 开启RCC时钟 4.2 配置GPIO 4.3 配置多路开关 4.4 结构体初始化ADC 4.5 DMA参数初始化配置 4.5.1 外设站点的三个参数 4.5.2 存储器站点的三个…

Tomcat 调优技巧(Tomcat Tuning Tips)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

IDEA关联Tomcat

一、Tomcat服务器 web服务器,就是运行web项目的容器 即运行java代码的一个容器 webapp(web应用程序) --> 就是我们写的javaweb项目 Tomcat 是Apache 软件基金会&#xff08;Apache Software Foundation&#xff09;下的一个核心项目&#xff0c;免费开源、并支持Servlet 和J…

yolov8/9/10模型在垃圾分类检测中的应用【代码+数据集+python环境+GUI系统】

yolov8/9/10模型在垃圾分类检测中的应用【代码数据集python环境GUI系统】 yolov8/9/10模型在垃圾分类检测中的应用【代码数据集python环境GUI系统】 背景意义 随着计算机视觉技术和深度学习算法的快速发展&#xff0c;图像识别、对象检测、图像分割等技术在各个领域得到了广泛…

DL_语义分割(学习笔记)

文章目录 图像分割1 常见分类1.1 语义分割1.2 实例分割1.3 全景分割 2 语义分割2.1 模型评价指标2.2 常用数据集2.3 转置卷积2.4 膨胀卷积2.5 感受野2.6 双线性插值2.7 FCN 图像分割 1 常见分类 1.1 语义分割 定义&#xff1a;【只判断类别&#xff0c;无法区分个体】 语义分…

Matlab实现麻雀优化算法优化回声状态网络模型 (SSA-ESN)(附源码)

目录 1.内容介绍 2.部分代码 3.实验结果 4.内容获取 1内容介绍 麻雀搜索算法&#xff08;Sparrow Search Algorithm, SSA&#xff09;是一种新兴的群体智能优化算法&#xff0c;灵感来源于麻雀的觅食行为及其在面临危险时的预警机制。SSA通过模拟麻雀的这些自然行为来寻找问题…

[Docker学习笔记]利用Dockerfile创建镜像

Dockerfile 指令 指令作用from继承基础镜像maintainer镜像制作者信息(可缺省)run用来执行shell命令expose暴露端口号cmd启动容器默认执行的命令entrypoint启动容器真正执行的命令volume创建挂载点env配置环境变量add复制文件到容器copy复制文件到容器workdir设置容器的工作目录…

蓝卓亮相中国工博会,打造以数据驱动的智能工厂

9月28日&#xff0c;以“工业聚能&#xff0c;新质领航”为主题的第24届中国国际工业博览会&#xff08;以下简称“工博会”&#xff09;在国家会展中心&#xff08;上海&#xff09;圆满拉下帷幕。本届工博会共设9大专业展区&#xff0c;吸引了来自全球28个国家和地区的2600余…

针对考研的C语言学习(定制化快速掌握重点4)

typedef的使用 简化变量类型 逻辑结构 集合结构&#xff1a;无关系 线性结构&#xff1a;一对一 树形结构&#xff1a;一对多 图形结构&#xff1a;多对多 存储结构 顺序存储和链式存储&#xff08;考代码&#xff09; 顺序存储优点&#xff1a;1.可以实现随机存取。2.…

针对考研的C语言学习(定制化快速掌握重点5)

顺序表 特点&#xff1a; 写代码主要就是增删改查&#xff01;&#xff01;&#xff01; 写代码的边界性非常重要以及考研插入和删除的位置都是从1开始&#xff0c;而数组下标是从0开始 【注】下标和位置的关系 线性表最重要的是插入和删除会涉及边界问题以及判断是否合法 …

【Spring Boot 入门二】Spring Boot中的配置文件 - 掌控你的应用设置

一、引言 在上一篇文章中&#xff0c;我们开启了Spring Boot的入门之旅&#xff0c;成功构建了第一个Spring Boot应用。我们从环境搭建开始&#xff0c;详细介绍了JDK的安装以及IDE的选择与配置&#xff0c;然后利用Spring Initializr创建了项目&#xff0c;分析了项目结构&am…

资质申请中常见的错误有哪些?

在申请建筑资质的过程中&#xff0c;企业可能会犯一些常见的错误&#xff0c;以下是一些需要避免的错误&#xff1a; 1. 资料准备不充分&#xff1a; 申请资质需要提交大量的资料&#xff0c;包括企业法人资料、财务报表、业绩证明等。资料不齐全或不准确都可能导致申请失败。…

多线程(一):线程的基本特点线程安全问题ThreadRunnable

目录 1、线程的引入 2、什么是线程 3、线程的基本特点 4、线程安全问题 5、创建线程 5.1 继承Thread类&#xff0c;重写run 5.1.1 创建Thread类对象 5.1.2 重写run方法 5.1.3 start方法创建线程 5.1.4 抢占式执行 5.2 实现Runnable&#xff0c;重写run【解耦合】★…

MySQL-数据库设计

1.范式 数据库的范式是⼀组规则。在设计关系数据库时&#xff0c;遵从不同的规范要求&#xff0c;设计出合理的关系型数 据库&#xff0c;这些不同的规范要求被称为不同的范式。 关系数据库有六种范式&#xff1a;第⼀范式&#xff08;1NF&#xff09;、第⼆范式&#xff08;…

【Mysql】SQL语言基础

1、SQL的概述 SQL全称&#xff1a;Structured Query Language,是结构化查询语言&#xff0c;用于访问和处理数据库的标准的计算机语言。SQL语言1974年由Boyce和Chamberlin提出&#xff0c;并首先在IBM公司研制的关系数据库系统systemr上实现。 美国国家标准局&#x…

亚信安全发布第34期《勒索家族和勒索事件监控报告》

本周态势快速感知 本周全球共监测到勒索事件91起&#xff0c;近三周勒索事件数量较为稳定。从整体上看&#xff0c;Ransomhub是影响最严重的勒索家族&#xff1b;Play和ElDorado恶意家族也是两个活动频繁的恶意家族&#xff0c;需要注意防范。本周&#xff0c;土耳其公司巴克皮…

小红书2024秋招后端开发(Java工程师、C++工程师等)

前几天做了美团&#xff0c;OPPO的秋招笔试题&#xff0c;然后又做了一场小红书&#xff0c;总体难度我觉得都差不多&#xff0c;涉及到的知识点要么是语法模拟&#xff0c;或者就是一些基础算法&#xff0c;所以这样看秋招编程题还是很简单的&#xff0c;对于笔试我们还要把除…

深刻理解Redis集群(下):Redis 哨兵(Sentinel)模式

背景 现在对3个节点的sentinel进行配置。sentinel的配置文件在redis的安装目录中已经存在&#xff0c;只需要复制到指定的位置即可。 sentinel是独立进程&#xff0c;有对应的脚本来执行。 基于之前的redis 一主二从的架构&#xff0c;我们继续启动3个sentinel进程。 哨兵模式的…

使用微服务Spring Cloud集成Kafka实现异步通信

在微服务架构中&#xff0c;使用Spring Cloud集成Apache Kafka来实现异步通信是一种常见且高效的做法。Kafka作为一个分布式流处理平台&#xff0c;能够处理高吞吐量的数据&#xff0c;非常适合用于微服务之间的消息传递。 微服务之间的通信方式包括同步通信和异步通信。 1&a…

GPU参数指标

以英伟达的A800卡为例&#xff0c;简单聊聊GPU卡的核心参数指标&#xff0c;A800的核心指标主要有5个&#xff0c;为算力、显存大小、显存带宽、功耗情况和卡间互联速率。 性能&#xff1a;则可以理解为货车对不同货物类型的马力大小&#xff0c;决定能“拉动”多少重量的货&…