ShardingSphere如何轻松驾驭Seata柔性分布式事务?

0 前文

上一文解析了 ShardingSphere 强一致性事务支持 XAShardingTransactionManager ,本文继续:

  • 讲解该类
  • 介绍支持柔性事务的 SeataATShardingTransactionManager

sharding-transaction-xa-core中关于 XAShardingTransactionManager,本文研究 XATransactionManager 和 ShardingConnection 类实现。

1 XAShardingTransactionManager

1.1 init

public void init(final DatabaseType databaseType, final Collection<ResourceDataSource> resourceDataSources) {for (ResourceDataSource each : resourceDataSources) {// 根据传入的 ResourceDataSource创建XATransactionDataSource并缓存cachedDataSources.put(each.getOriginalName(), new XATransactionDataSource(databaseType, each.getUniqueResourceName(), each.getDataSource(), xaTransactionManager));}// 对通过 SPI 创建的 XATransactionManager 也执行其 init 初始化xaTransactionManager.init();
}

1.2 其它方法

实现也简单:

@Override
public TransactionType getTransactionType() {return TransactionType.XA;
}@SneakyThrows
@Override
public boolean isInTransaction() {return Status.STATUS_NO_TRANSACTION != xaTransactionManager.getTransactionManager().getStatus();
}@Override
public Connection getConnection(final String dataSourceName) throws SQLException {return cachedDataSources.get(dataSourceName).getConnection();
}

1.3 事务操作相关

begin、commit 和 rollback直接委托保存在 XATransactionManager#TransactionManager 完成:

@SneakyThrows
@Override
public void begin() {xaTransactionManager.getTransactionManager().begin();
}@SneakyThrows
@Override
public void commit() {xaTransactionManager.getTransactionManager().commit();
}@SneakyThrows
@Override
public void rollback() {xaTransactionManager.getTransactionManager().rollback();
}

2 AtomikosTransactionManager

TransactionManager默认实现。

2.1 AtomikosXARecoverableResource

代表资源:

public final class AtomikosXARecoverableResource extends JdbcTransactionalResource {private final String resourceName;AtomikosXARecoverableResource(final String serverName, final XADataSource xaDataSource) {super(serverName, xaDataSource);resourceName = serverName;}// 比对SingleXAResource#ResourceName,确定是否在使用资源,此即设计包装 XAResource 的 SingleXAResource 类的原因@Overridepublic boolean usesXAResource(final XAResource xaResource) {return resourceName.equals(((SingleXAResource) xaResource).getResourceName());}
}

2.2 AtomikosXARecoverableResource

public final class AtomikosTransactionManager implements XATransactionManager {private final UserTransactionManager transactionManager = new UserTransactionManager();private final UserTransactionService userTransactionService = new UserTransactionServiceImp();@Overridepublic void init() {userTransactionService.init();}@Overridepublic void registerRecoveryResource(final String dataSourceName, final XADataSource xaDataSource) {userTransactionService.registerResource(new AtomikosXARecoverableResource(dataSourceName, xaDataSource));}@Overridepublic void removeRecoveryResource(final String dataSourceName, final XADataSource xaDataSource) {userTransactionService.removeResource(new AtomikosXARecoverableResource(dataSourceName, xaDataSource));}@Override@SneakyThrowspublic void enlistResource(final SingleXAResource xaResource) {transactionManager.getTransaction().enlistResource(xaResource);}@Overridepublic TransactionManager getTransactionManager() {return transactionManager;}@Overridepublic void close() {userTransactionService.shutdown(true);}
}

对 Atomikos 的 UserTransactionManager、UserTransactionService 简单调用,Atomikos#UserTransactionManager 实现 TransactionManager 接口,封装所有 TransactionManager 需要完成的工作。

看完 sharding-transaction-xa-atomikos-manager,再看 sharding-transaction-xa-bitronix-manager 工程。基于 bitronix 的 XATransactionManager 实现方案

3 BitronixXATransactionManager

public final class BitronixXATransactionManager implements XATransactionManager {private final BitronixTransactionManager bitronixTransactionManager = TransactionManagerServices.getTransactionManager();@Overridepublic void init() {}@SneakyThrows@Overridepublic void registerRecoveryResource(final String dataSourceName, final XADataSource xaDataSource) {ResourceRegistrar.register(new BitronixRecoveryResource(dataSourceName, xaDataSource));}@SneakyThrows@Overridepublic void removeRecoveryResource(final String dataSourceName, final XADataSource xaDataSource) {ResourceRegistrar.unregister(new BitronixRecoveryResource(dataSourceName, xaDataSource));}@SneakyThrows@Overridepublic void enlistResource(final SingleXAResource singleXAResource) {bitronixTransactionManager.getTransaction().enlistResource(singleXAResource);}@Overridepublic TransactionManager getTransactionManager() {return bitronixTransactionManager;}@Overridepublic void close() {bitronixTransactionManager.shutdown();}
}

XA两阶段提交核心类:

4 ShardingConnection

上图的整个流程源头ShardingConnection类,构造函数发现创建 ShardingTransactionManager 过程:

@Getter
public final class ShardingConnection extends AbstractConnectionAdapter {public ShardingConnection(...) {...shardingTransactionManager = runtimeContext.getShardingTransactionManagerEngine().getTransactionManager(transactionType);}
}

ShardingConnection多处用到上面创建的shardingTransactionManager。如:

createConnection

获取连接:

@Override
protected Connection createConnection(final String dataSourceName, final DataSource dataSource) throws SQLException {return isInShardingTransaction() ? shardingTransactionManager.getConnection(dataSourceName) : dataSource.getConnection();
}

isInShardingTransaction

判断是否在同一事务:

private boolean isInShardingTransaction() {return null != shardingTransactionManager && shardingTransactionManager.isInTransaction();
}

setAutoCommit

@Override
public void setAutoCommit(final boolean autoCommit) throws SQLException {if (TransactionType.LOCAL == transactionType) {super.setAutoCommit(autoCommit);return;}if (autoCommit && !shardingTransactionManager.isInTransaction() || !autoCommit && shardingTransactionManager.isInTransaction()) {return;}if (autoCommit && shardingTransactionManager.isInTransaction()) {shardingTransactionManager.commit();return;}if (!autoCommit && !shardingTransactionManager.isInTransaction()) {closeCachedConnections();shardingTransactionManager.begin();}
}

事务类型为本地事务时,直接调用 ShardingConnection 父类 AbstractConnectionAdapter#setAutoCommit 完成本地事务自动提交:

  • autoCommit=true 且运行在事务中,调shardingTransactionManager.commit()完成提交
  • autoCommit=false 且当前不在事务中时,调 shardingTransactionManager.begin() 启动事务

commit、rollback

类似setAutoCommit ,按事务类型决定是否进行分布式提交和回滚:

@Override
public void commit() throws SQLException {if (TransactionType.LOCAL == transactionType) {super.commit();} else {shardingTransactionManager.commit();}
}@Override
public void rollback() throws SQLException {if (TransactionType.LOCAL == transactionType) {super.rollback();} else {shardingTransactionManager.rollback();}
}

ShardingSphere提供两阶段提交的 XA 协议实现方案的同时,也实现柔性事务。看完 XAShardingTransactionManager,来看基于 Seata 框架的柔性事务 TransactionManager 实现类 SeataATShardingTransactionManager。

5 SeataATShardingTransactionManager

该类完全采用阿里Seata框架提供分布式事务特性,而非遵循类似 XA 这样的开发规范,所以代码实现比 XAShardingTransactionManager 类层结构简单,复杂性都屏蔽在了框架内部。

集成 Seata,先要初始化 TMClient、RMClient,在 Seata 内部,这两个客户端之间会基于RPC通信。

SeataATShardingTransactionManager#init的initSeataRPCClient初始化这俩客户端对象:

// 根据 seata.conf 创建配置对象
FileConfiguration configuration = new FileConfiguration("seata.conf");initSeataRPCClient() {String applicationId = configuration.getConfig("client.application.id");Preconditions.checkNotNull(applicationId, "please config application id within seata.conf file");String transactionServiceGroup = configuration.getConfig("client.transaction.service.group", "default");TMClient.init(applicationId, transactionServiceGroup);RMClient.init(applicationId, transactionServiceGroup);
}

Seata也提供一套构建在 JDBC 规范之上的实现策略,类似03文介绍的 ShardingSphere 与 JDBC 规范之间兼容性。

Seata使用DataSourceProxy、ConnectionProxy代理对象,如DataSourceProxy:

实现了自定义Resource接口,继承AbstractDataSourceProxy(最终实现JDBC的DataSource接口)。所以,初始化 Seata 框架时,也要根据输入 DataSource 对象构建 DataSourceProxy,并通过 DataSourceProxy 获取 ConnectionProxy。

init、getConnection

@Override
public void init(final DatabaseType databaseType, final Collection<ResourceDataSource> resourceDataSources) {// 初始化 Seata 客户端initSeataRPCClient();// 创建 DataSourceProxy 并放入Mapfor (ResourceDataSource each : resourceDataSources) {dataSourceMap.put(each.getOriginalName(), new DataSourceProxy(each.getDataSource()));}
}@Override
public Connection getConnection(final String dataSourceName) {// 根据 DataSourceProxy 获取 ConnectionProxyreturn dataSourceMap.get(dataSourceName).getConnection();
}

初始化后,提供了事务开启和提交相关的入口。Seata的GlobalTransaction是核心接口,封装了面向用户操作层的分布式事务访问入口:

public interface GlobalTransaction {void begin() throws TransactionException;void begin(int timeout) throws TransactionException;void begin(int timeout, String name) throws TransactionException;void commit() throws TransactionException;void rollback() throws TransactionException;GlobalStatus getStatus() throws TransactionException;String getXid();
}

ShardingSphere 作 GlobalTransaction 的用户层,也基于 GlobalTransaction 完成分布式事务操作。但 ShardingSphere 并未直接使用这层,而是设计位于sharding-transaction-base-seata-at的SeataTransactionHolder类,保存线程安全的 GlobalTransaction 对象。

SeataTransactionHolder

final class SeataTransactionHolder {private static final ThreadLocal<GlobalTransaction> CONTEXT = new ThreadLocal<>();static void set(final GlobalTransaction transaction) {CONTEXT.set(transaction);} static GlobalTransaction get() {return CONTEXT.get();}static void clear() {CONTEXT.remove();}
}

使用 ThreadLocal 确保对 GlobalTransaction 访问的线程安全性。

咋判断当前操作是否处于一个全局事务?Seata存在一个上下文对象RootContex保存参与者和发起者之间传播的 Xid:

  • 当事务发起者开启全局事务,将 Xid 填入 RootContext
  • 然后 Xid 沿服务调用链一直传播,进而填充到每个事务参与者进程的 RootContext
  • 事务参与者发现 RootContext 存在 Xid,就可知自己处于全局事务

因此,只需判断:

@Override
public boolean isInTransaction() {return null != RootContext.getXID();
}

Seata 也提供针对全局事务的上下文类 GlobalTransactionContext,可用:

  • getCurrent 获取一个 GlobalTransaction对象
  • 或通过 getCurrentOrCreate 在无法获取 GlobalTransaction 对象时新建一个

就不难理解如下实现了

begin

@Override
@SneakyThrows
public void begin() {// 创建一个 GlobalTransaction,保存到 SeataTransactionHolderSeataTransactionHolder.set(GlobalTransactionContext.getCurrentOrCreate());// 从 SeataTransactionHolder 获取一个 GlobalTransaction,并调 begin 启动事务SeataTransactionHolder.get().begin();SeataTransactionBroadcaster.collectGlobalTxId();
}

注意到最后的类:

SeataTransactionBroadcaster

保存 Seata 全局 Xid 的一个容器类。事务启动时收集全局 Xid 并进行保存,而在事务提交或回滚时清空这些 Xid。

class SeataTransactionBroadcaster {String SEATA_TX_XID = "SEATA_TX_XID";static void collectGlobalTxId() {if (RootContext.inGlobalTransaction()) {ShardingExecuteDataMap.getDataMap().put(SEATA_TX_XID, RootContext.getXID());}}static void broadcastIfNecessary(final Map<String, Object> shardingExecuteDataMap) {if (shardingExecuteDataMap.containsKey(SEATA_TX_XID) && !RootContext.inGlobalTransaction()) {RootContext.bind((String) shardingExecuteDataMap.get(SEATA_TX_XID));}}static void clear() {ShardingExecuteDataMap.getDataMap().remove(SEATA_TX_XID);}
}

因此

commit、rollback和close

实现就清楚了:

@Override
public void commit() {try {SeataTransactionHolder.get().commit();} finally {SeataTransactionBroadcaster.clear();SeataTransactionHolder.clear();}
}@Override
public void rollback() {try {SeataTransactionHolder.get().rollback();} finally {SeataTransactionBroadcaster.clear();SeataTransactionHolder.clear();}
}@Override
public void close() {dataSourceMap.clear();SeataTransactionHolder.clear();TmRpcClient.getInstance().destroy();RmRpcClient.getInstance().destroy();
}

sharding-transaction-base-seata-at 工程中的代码实际上就只有这些内容,这些内容也构成了在 ShardingSphere中 集成 Seata 框架的实现过程。

6 从源码到开发

本文给出应用程序咋集成 Seata 分布式事务框架的详细过程,ShardingSphere 提供一种模版实现。日常开发,若想在业务代码集成 Seata,可参考 SeataTransactionHolder、SeataATShardingTransactionManager 等核心代码,而无需太多修改。

7 总结

XAShardingTransactionManager理解难在从 ShardingConnection 到底层 JDBC 规范的整个集成和兼容过程。

8 集成Seata框架

参考 ShardingSphere 的实现:


1. 配置 Seata 环境

  • 配置文件准备: 创建 seata.conf 文件,定义 applicationIdtransactionServiceGroup 等参数。
  • 启动 Seata 服务: 启动 Seata Server 并确保其与数据库的事务协调机制正常工作。

2. 初始化 Seata 客户端

项目中初始化 TMClientRMClient,它们分别代表事务管理器和资源管理器:

FileConfiguration configuration = new FileConfiguration("seata.conf");
String applicationId = configuration.getConfig("client.application.id");
String transactionServiceGroup = configuration.getConfig("client.transaction.service.group", "default");
TMClient.init(applicationId, transactionServiceGroup);
RMClient.init(applicationId, transactionServiceGroup);

3. 数据源代理

构建 DataSourceProxy 使用 Seata 的 DataSourceProxy 对数据源进行代理。

DataSourceProxy dataSourceProxy = new DataSourceProxy(originalDataSource);

获取连接代理:从代理数据源中获取 ConnectionProxy,使每个数据库连接支持事务传播。

Connection connection = dataSourceProxy.getConnection();

4. 全局事务上下文管理

基于 GlobalTransactionContext 获取或创建事务对象:

GlobalTransaction transaction = GlobalTransactionContext.getCurrentOrCreate();

绑定全局事务 XID: 当事务发起时,将全局事务的 XID 存储在 RootContext 中:

RootContext.bind(transaction.getXid());

通过 RootContext 判断事务状态:

boolean isInTransaction = RootContext.inGlobalTransaction();

5. 事务操作实现

开启事务:

transaction.begin();

提交事务:

try {transaction.commit();
} finally {RootContext.unbind();
}

回滚事务:

try {transaction.rollback();
} finally {RootContext.unbind();
}

6. 整合业务逻辑

将分布式事务的核心逻辑封装在工具类中,例如 SeataTransactionHolder,以便方便地管理全局事务上下文:

SeataTransactionHolder.set(GlobalTransactionContext.getCurrentOrCreate());

7. 清理资源

在应用关闭时,清理客户端资源:

TmRpcClient.getInstance().destroy();
RmRpcClient.getInstance().destroy();

8. 注意事项

  • 确保所有数据源通过 DataSourceProxy 代理,避免事务管理失效。
  • 配置数据库支持 Undo Log 表,确保事务回滚记录正常存储。
  • 调试过程中,检查 Seata Server 日志和应用日志,定位事务协调的问题。

通过上述步骤,可以在业务代码中顺利集成 Seata,实现分布式事务管理,保障数据一致性。

关注我,紧跟本系列专栏文章,咱们下篇再续!

作者简介:魔都架构师,多家大厂后端一线研发经验,在分布式系统设计、数据平台架构和AI应用开发等领域都有丰富实践经验。

各大技术社区头部专家博主。具有丰富的引领团队经验,深厚业务架构和解决方案的积累。

负责:

  • 中央/分销预订系统性能优化

  • 活动&券等营销中台建设

  • 交易平台及数据中台等架构和开发设计

  • 车联网核心平台-物联网连接平台、大数据平台架构设计及优化

  • LLM Agent应用开发

  • 区块链应用开发

  • 大数据开发挖掘经验

  • 推荐系统项目

    目前主攻市级软件项目设计、构建服务全社会的应用系统。

参考:

  • 编程严选网

    本文由博客一文多发平台 OpenWrite 发布!

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

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

相关文章

将 SQL 数据库连接到云:PostgreSQL、MySQL、SQLite 和云集成说明

在当今数据驱动型世界中&#xff0c;云技术已经完全改变了数据库的管理和扩展。SQL 数据库&#xff08;包括 PostgreSQL、MySQL 和 SQLite&#xff09;在相当长的一段时间内一直是开发人员的最爱。然而&#xff0c;为了响应对可扩展、适应性强且经济高效的解决方案日益增长的需…

Vulnhub靶场案例渗透[10]- Momentum2

文章目录 一、靶场搭建1. 靶场描述2. 下载靶机环境3. 靶场搭建 二、渗透靶场1. 确定靶机IP2. 探测靶场开放端口及对应服务3. 扫描网络目录结构4. 代码审计5. 反弹shell6. 提权 一、靶场搭建 1. 靶场描述 - Difficulty : medium - Keywords : curl, bash, code reviewThis wor…

STM32设计防丢防摔智能行李箱

目录 目录 前言 一、本设计主要实现哪些很“开门”功能&#xff1f; 二、电路设计原理图 1.电路图采用Altium Designer进行设计&#xff1a; 2.实物展示图片 三、程序源代码设计 四、获取资料内容 前言 随着科技的不断发展&#xff0c;嵌入式系统、物联网技术、智能设备…

前后端请求响应

引入 在之前的例子中&#xff0c;我们编写了一个简单的web类&#xff0c;我们运行启动类&#xff0c;启动内嵌的tomcat后就可以在浏览器通过特定的路径访问tomcat中的应用程序。 但之前编写的程序仅仅是个简单的java类&#xff0c;其并未实现某个接口或继承某个类&…

爬虫——数据解析与提取

第二节&#xff1a;数据解析与提取 在网络爬虫开发中&#xff0c;获取网页内容&#xff08;HTML&#xff09;是第一步&#xff0c;但从这些内容中提取有用的数据&#xff0c;才是爬虫的核心部分。HTML文档通常结构复杂且充满冗余信息&#xff0c;因此我们需要使用高效的解析工…

数据结构C语言描述3(图文结合)--双链表、循环链表、约瑟夫环问题

前言 这个专栏将会用纯C实现常用的数据结构和简单的算法&#xff1b;有C基础即可跟着学习&#xff0c;代码均可运行&#xff1b;准备考研的也可跟着写&#xff0c;个人感觉&#xff0c;如果时间充裕&#xff0c;手写一遍比看书、刷题管用很多&#xff0c;这也是本人采用纯C语言…

设计模式-策略模式

1. 策略模式 策略模式&#xff08;Strategy Pattern&#xff09;针对一组算法&#xff0c;将每一个算法封装到 具有共同接口 的独立的类中&#xff0c;从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。 在软件开发中&#xff0c;经常会遇到…

FFmpeg 4.3 音视频-多路H265监控录放C++开发十四,总结编码过程,从摄像头获得数据后,转成AVFrame,然后再次转成AVPacket,

也就是将摄像头采集到的YUV 的数据换成 AVFrame&#xff0c;然后再次转成 AVPacket&#xff0c;那么这AVPakcet数据要怎么办呢&#xff1f;分为三种情况&#xff1a; 一种是将AVPacket存储成h264文件&#xff0c;由于h264编码器在将avframe变成avpacket的时候就是按照h264的格…

【srm,招标询价】采购电子化全流程,供应商准入审核,在线询价流程管理(JAVA+Vue+mysql)

前言&#xff1a; 随着互联网和数字技术的不断发展&#xff0c;企业采购管理逐渐走向数字化和智能化。数字化采购平台作为企业采购管理的新模式&#xff0c;能够提高采购效率、降低采购成本、优化供应商合作效率&#xff0c;已成为企业实现效益提升的关键手段。系统获取在文末…

Transformer学习笔记(一)

Transformer学习笔记 基于 3B1B 可视化视频 自注意力机制 1.每个词的初始嵌入是一个高维向量&#xff0c;只编码该单词含义&#xff0c;与上下文没有关联 2.对初始向量进行位置编码&#xff0c;在高维向量中编码进位置信息&#xff08;单词在语言序列中的位置信息&#xff…

4.4.5 timer中断流向Linux(从interrupt log回放)

4.4.5 timer中断流向Linux&#xff08;从interrupt log回放&#xff09; 按上文所述&#xff0c;timer中断3已经记录到root domain的interrupt log。在《3.4.1.3 IPIPE interrupt log数据结构》中&#xff0c;已经讨论过interrupt log的记录与回放。本小结&#xff0c;讨论什么…

WinDefender Weaker

PPL Windows Vista / Server 2008引入 了受保护进程的概念&#xff0c;其目的不是保护您的数据或凭据。其最初目标是保护媒体内容并符合DRM &#xff08;数字版权管理&#xff09;要求。Microsoft开发了此机制&#xff0c;以便您的媒体播放器可以读取例如蓝光&#xff0c;同时…

基于redis完成延迟队列

添加依赖 使用redisson完成延迟队列效果 <!-- redisson依赖 --><dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.17.4</version> <!-- 请使用最新版本 --></dependency&g…

星辰资讯 | TiDB v7.5.4 v8.4.0 发版

作者&#xff1a; ShawnYan 原文来源&#xff1a; https://tidb.net/blog/6e299751 TiDB 8.4.0 DMR 发版 11 月 11 日&#xff0c;TiDB 8.4.0 版本发布&#xff0c;以下是该版本的一些关键特性和改进&#xff1a; 性能 分区表全局索引成为正式功能 &#xff1a;提高检索…

Spring基础

Spring基础 目录&#xff1a; 一、Spring框架简介 二、Spring容器机制 一、Spring框架简介 1. Spring发展历程 •在Spring兴起之前&#xff0c;Java企业级开发主要通过EJB (Enterprise JavaBean)完成。EJB是服务器端的组件模型&#xff0c;由于它过于依靠EJB容器&#xf…

二分查找法(leetcode 704)

在一个数组里找一个target&#xff0c;判断这个target在不在这个数组里&#xff0c;如果在&#xff0c;返回这个数组所对应的这个元素所对应的下标&#xff0c;否则返回-1. 易错点&#xff1a; &#xff08;1&#xff09;while(left<right) vs while(left<…

python的matplotlib实现数据分析绘图

目录 需求 效果 数据分析绘图示例 代码解释 运行结果 需求 分析一个班级中学生成绩分布&#xff0c;并绘图 效果 数据分析绘图示例 import matplotlib.pyplot as plt import numpy as np# 假设的学生成绩数据 np.random.seed(0) # 设置随机种子以确保结果可复现 score…

STM32电源管理—实现低功耗

注&#xff1a; 本文是学习野火的指南针开发板过程的学习笔记&#xff0c;可能有误&#xff0c;详细请看B站野火官方配套视频教程&#xff08;这个教程真的讲的很详细&#xff0c;请给官方三连吧&#xff09; 在响应绿色发展的同时&#xff0c;在很多应用场合中都对电子设备的功…

[JAVA]MyBatis框架—如何获取SqlSession对象实现数据交互(基础篇)

假设我们要查询数据库的用户信息&#xff0c;在MyBatis框架中&#xff0c;首先需要通过SqlSessionFactory创建SqlSession&#xff0c;然后才能使用SqlSession获取对应的Mapper接口&#xff0c;进而执行查询操作 在前一章我们学习了如何创建MyBatis的配置文件mybatis.config.xm…

【视频讲解】Python深度神经网络DNNs-K-Means(K-均值)聚类方法在MNIST等数据可视化对比分析...

全文链接&#xff1a;https://tecdat.cn/?p38289 分析师&#xff1a;Cucu Sun 近年来&#xff0c;由于诸如自动编码器等深度神经网络&#xff08;DNN&#xff09;的高表示能力&#xff0c;深度聚类方法发展迅速。其核心思想是表示学习和聚类可以相互促进&#xff1a;好的表示会…