源码解析SeataAT模式中分支事务的提交或回滚是如何被触发的
前言
在之前的博客中,已经介绍过了TM在seata AT模式中的处理流程、TC在seata分布式事务中的主要任务以及RM在seata AT模式中的sql语句执行流程,下面我们通过源码讲述分布式事务是如何实现提交或回滚的。 分支事务的提交或回滚
在seata AT模式中,只有当所有的分支事务全部成功提交后,才会触发分布式事务的提交:public class ConnectionProxy extends AbstractConnectionProxy { private void processGlobalTransactionCommit() throws SQLException { try { // 注册分支事务,添加行锁,其实就是分布式锁 register(); } catch (TransactionException e) { recognizeLockKeyConflictException(e, context.buildLockKeys()); } try { // 插入undolog UndoLogManagerFactory.getUndoLogManager(this.getDbType()).flushUndoLogs(this); // 提交本地事务 targetConnection.commit(); } catch (Throwable ex) { LOGGER.error("process connectionProxy commit error: {}", ex.getMessage(), ex); // 上报分支事务提交失败状态 report(false); // 抛出异常,最终会被TM捕捉到,触发分布式事务的回滚 throw new SQLException(ex); } if (IS_REPORT_SUCCESS_ENABLE) { // 上报分支事务成功提交状态 report(true); } // 恢复现场 context.reset(); } }
在上述源码分析中,当所有的RM分支事务提交成功后,TC会接收到所有RM分支事务的状态,代码最终会执行到TM的模版逻辑中。TM触发分布式事务的提交或回滚
TM模版代码: public Object execute(TransactionalExecutor business) throws Throwable { // 1. 拿到整理好的@GlobalTransactional注解里面的配置信息 TransactionInfo txInfo = business.getTransactionInfo(); if (txInfo == null) { throw new ShouldNeverHappenException("transactionInfo does not exist"); } // 1.1 获取当前的分布式事务,如果为null的话,说明这是分布式事务的发起者;如果不为null,说明这是分布式事务的参与者 GlobalTransaction tx = GlobalTransactionContext.getCurrent(); // 1.2 获取分布式事务的传播级别,其实就是按照spring的传播级别来一套,区别就是spring事务是本地事务,这是分布式事务,原理都一样 Propagation propagation = txInfo.getPropagation(); SuspendedResourcesHolder suspendedResourcesHolder = null; try { // 这个switch里面全都是处理分布式事务传播级别的 switch (propagation) { // 如果不支持分布式事务,如果当前存在事务,那么先挂起当前的分布式事务,再执行业务逻辑 case NOT_SUPPORTED: // 分布式事务存在,先挂起 if (existingTransaction(tx)) { suspendedResourcesHolder = tx.suspend(); } // 执行业务逻辑 return business.execute(); // 如果是每次都要创建一个新的分布式事务,先把当前存在的分布式事务挂起,然后创建一个新分布式事务 case REQUIRES_NEW: // 如果分布式事务存在,先挂起当前分布式事务,再创建一个新的分布式事务 if (existingTransaction(tx)) { suspendedResourcesHolder = tx.suspend(); tx = GlobalTransactionContext.createNew(); } // 之所以用break,是为了后面的代码和其他的传播级别一起共用,业务逻辑肯定还是要执行的 break; // 如果支持分布式事务,如果当前不存在分布式事务,那么直接执行业务逻辑,否则以分布式事务的方式执行业务逻辑 case SUPPORTS: // 如果不存在分布式事务,直接执行业务逻辑 if (notExistingTransaction(tx)) { return business.execute(); } // 否则,以分布式事务的方式执行业务逻辑 break; // 如果有分布式事务,就在当前分布式事务下执行业务逻辑,否则创建一个新的分布式事务执行业务逻辑 case REQUIRED: // If current transaction is existing, execute with current transaction, // else continue and execute with new transaction. break; // 如果不允许有分布式事务,那么一旦发现存在分布式事务,直接抛异常;只有不存在分布式事务的时候才正常执行 case NEVER: // 存在分布式事务,抛异常 if (existingTransaction(tx)) { throw new TransactionException( String.format("Existing transaction found for transaction marked with propagation "never", xid = %s" , tx.getXid())); } else { // 不存在分布式事务,执行业务逻辑 return business.execute(); } // 一定要有分布式事务,分布式事务不存在的话,抛异常; case MANDATORY: // 不存在分布式事务,抛异常 if (notExistingTransaction(tx)) { throw new TransactionException("No existing transaction found for transaction marked with propagation "mandatory""); } // Continue and execute with current transaction. break; default: throw new TransactionException("Not Supported Propagation:" + propagation); } // 上面的传播级别的逻辑处理完毕,下面就是公共的处理逻辑 // 1.3 如果当前分布式事务没有的话,那么我们就要创建新的分布式事务,此时我们就是分布式事务的发起者,也就是TM本身,否则不能称之为`TM` if (tx == null) { tx = GlobalTransactionContext.createNew(); } // 开始准备干活的条件 // 把我们这个方法的全局锁配置放进当前线程中,并且把线程中已有的全局锁的配置取出来 // 我们在干完自己的活后,会把这个取出来的配置放回去的 GlobalLockConfig previousConfig = replaceGlobalLockConfig(txInfo); try { // 2. 如果我们是分布式事务的发起者的话,那么我们会和TC通信,并且拿到一个XID;如果我们不是分布式事务的发起者的话,那么这一步啥也不干 // 这个XID可以从RootContext中获取 beginTransaction(txInfo, tx); Object rs; try { // 执行业务逻辑 rs = business.execute(); } catch (Throwable ex) { // 3. 发生任何异常,我们准备启动回滚机制 completeTransactionAfterThrowing(txInfo, tx, ex); throw ex; } // 4. 一切顺利,通知提交分布式事务 commitTransaction(tx); return rs; } finally { //5. 恢复现场,把之前的配置放回去 resumeGlobalLockConfig(previousConfig); // 触发回调 triggerAfterCompletion(); // 清理工作 cleanUp(); } } finally { // 恢复之前挂起的事务 if (suspendedResourcesHolder != null) { tx.resume(suspendedResourcesHolder); } } }
主要关注这一段代码:try { // 2. 如果我们是分布式事务的发起者的话,那么我们会和TC通信,并且拿到一个XID;如果我们不是分布式事务的发起者的话,那么这一步啥也不干 // 这个XID可以从RootContext中获取 beginTransaction(txInfo, tx); Object rs; try { // 执行业务逻辑 rs = business.execute(); } catch (Throwable ex) { // 3. 发生任何异常,我们准备启动回滚机制 completeTransactionAfterThrowing(txInfo, tx, ex); throw ex; } // 4. 一切顺利,通知提交分布式事务 commitTransaction(tx); return rs; } finally { //5. 恢复现场,把之前的配置放回去 resumeGlobalLockConfig(previousConfig); // 触发回调 triggerAfterCompletion(); // 清理工作 cleanUp(); } } finally { // 恢复之前挂起的事务 if (suspendedResourcesHolder != null) { tx.resume(suspendedResourcesHolder); } }
当任意RM分支事务产生异常后,都会触发TM执行completeTransactionAfterThrowing(txInfo, tx, ex),只有当所有的RM分支事务提交成功后,TM才会发起commitTransaction(tx);异常回滚private void completeTransactionAfterThrowing(TransactionInfo txInfo, GlobalTransaction tx, Throwable originalException) throws TransactionalExecutor.ExecutionException { // 如果异常类型和指定的类型一致,那么发起回滚;不一致还是要提交分布式事务 if (txInfo != null && txInfo.rollbackOn(originalException)) { try { // 回滚分布式事务 rollbackTransaction(tx, originalException); } catch (TransactionException txe) { // 回滚失败抛异常 throw new TransactionalExecutor.ExecutionException(tx, txe, TransactionalExecutor.Code.RollbackFailure, originalException); } } else { // 不是指定的异常类型,还是继续提交分布式事务 commitTransaction(tx); } } private void rollbackTransaction(GlobalTransaction tx, Throwable originalException) throws TransactionException, TransactionalExecutor.ExecutionException { // 执行回调,默认空回调 triggerBeforeRollback(); // 回滚 tx.rollback(); // 执行回调,默认空回调 triggerAfterRollback(); // 就算回滚没问题,照样抛异常,目的应该是告知开发人员此处产生了回滚 throw new TransactionalExecutor.ExecutionException(tx, GlobalStatus.RollbackRetrying.equals(tx.getLocalStatus()) ? TransactionalExecutor.Code.RollbackRetrying : TransactionalExecutor.Code.RollbackDone, originalException); }@Override public void rollback() throws TransactionException { // 如果是分布式事务参与者,那么啥也不做,RM的回滚不在这里,这是TM的回滚 if (role == GlobalTransactionRole.Participant) { // Participant has no responsibility of rollback if (LOGGER.isDebugEnabled()) { LOGGER.debug("Ignore Rollback(): just involved in global transaction [{}]", xid); } return; } assertXIDNotNull(); // 下面就是一个循环重试发起分布式事务回滚 int retry = ROLLBACK_RETRY_COUNT <= 0 ? DEFAULT_TM_ROLLBACK_RETRY_COUNT : ROLLBACK_RETRY_COUNT; try { while (retry > 0) { try { retry--; // 发起回滚的核心代码 status = transactionManager.rollback(xid); // 回滚成功跳出循环 break; } catch (Throwable ex) { LOGGER.error("Failed to report global rollback [{}],Retry Countdown: {}, reason: {}", this.getXid(), retry, ex.getMessage()); // 重试失败次数完成才会跳出循环 if (retry == 0) { throw new TransactionException("Failed to report global rollback", ex); } } } } finally { // 如果回滚的分布式事务就是当前的分布式事务,那么从当前线程中解绑XID if (xid.equals(RootContext.getXID())) { suspend(); } } if (LOGGER.isInfoEnabled()) { LOGGER.info("[{}] rollback status: {}", xid, status); } } @Override public GlobalStatus rollback(String xid) throws TransactionException { // 准备发起请求给TC,回滚指定的分布式事务 GlobalRollbackRequest globalRollback = new GlobalRollbackRequest(); globalRollback.setXid(xid); GlobalRollbackResponse response = (GlobalRollbackResponse) syncCall(globalRollback); return response.getGlobalStatus(); }分布式事务回滚逻辑中有以下几个点:
触发回滚需要产生的异常和注解中指定的异常一致才会发起回滚,否则还是继续提交; 回滚是可以设置重试次数的,只有重试都失败了,才会导致回滚失败,否则只要有一次成功,那么回滚就成功; TM发起的回滚其实只是和TC发起一次分布式事务回滚的通信,并没有数据库的操作;分布式事务提交 private void commitTransaction(GlobalTransaction tx) throws TransactionalExecutor.ExecutionException { try { // 回调,默认空回调 triggerBeforeCommit(); // 分布式事务提交 tx.commit(); // 回调,默认空回调 triggerAfterCommit(); } catch (TransactionException txe) { // 4.1 提交出异常,提交失败 throw new TransactionalExecutor.ExecutionException(tx, txe, TransactionalExecutor.Code.CommitFailure); } } @Override public void commit() throws TransactionException { // 如果只是分布式事务参与者,那么啥也不干,TM只能有一个,哈哈 if (role == GlobalTransactionRole.Participant) { // Participant has no responsibility of committing if (LOGGER.isDebugEnabled()) { LOGGER.debug("Ignore Commit(): just involved in global transaction [{}]", xid); } return; } assertXIDNotNull(); // 分布式事务提交也是有重试的 int retry = COMMIT_RETRY_COUNT <= 0 ? DEFAULT_TM_COMMIT_RETRY_COUNT : COMMIT_RETRY_COUNT; try { while (retry > 0) { try { retry--; // 发起分布式事务提交 status = transactionManager.commit(xid); // 提交成功跳出循环 break; } catch (Throwable ex) { LOGGER.error("Failed to report global commit [{}],Retry Countdown: {}, reason: {}", this.getXid(), retry, ex.getMessage()); // 重试结束,依然失败就抛异常 if (retry == 0) { throw new TransactionException("Failed to report global commit", ex); } } } } finally { // 如果提交的分布式事务就是当前事务,那么需要清理当前线程中的XID if (xid.equals(RootContext.getXID())) { suspend(); } } if (LOGGER.isInfoEnabled()) { LOGGER.info("[{}] commit status: {}", xid, status); } } @Override public GlobalStatus commit(String xid) throws TransactionException { // 发起分布式事务提交请求,这是与TC通信 GlobalCommitRequest globalCommit = new GlobalCommitRequest(); globalCommit.setXid(xid); GlobalCommitResponse response = (GlobalCommitResponse) syncCall(globalCommit); return response.getGlobalStatus(); }
分布式事务回滚也是可以设置重试次数的; 分布式事务提交其实也是TM与TC进行通信,告知TC这个XID对应的分布式事务可以提交了;TC发起分布式事务提交或回滚
TC通过io.seata.server.coordinator.DefaultCore发起分布式事务提交:@Override public boolean doGlobalCommit(GlobalSession globalSession, boolean retrying) throws TransactionException { boolean success = true; // start committing event MetricsPublisher.postSessionDoingEvent(globalSession, retrying); if (globalSession.isSaga()) { success = getCore(BranchType.SAGA).doGlobalCommit(globalSession, retrying); } else { // 直接从这里开始看,这里有一个forEach循环遍历分布式事务中的所有分支事务 Boolean result = SessionHelper.forEach(globalSession.getSortedBranches(), branchSession -> { // if not retrying, skip the canBeCommittedAsync branches if (!retrying && branchSession.canBeCommittedAsync()) { return CONTINUE; } BranchStatus currentStatus = branchSession.getStatus(); if (currentStatus == BranchStatus.PhaseOne_Failed) { SessionHelper.removeBranch(globalSession, branchSession, !retrying); return CONTINUE; } // 前面都是一些状态的校验,下面开始发起分支事务的提交 try { BranchStatus branchStatus = getCore(branchSession.getBranchType()).branchCommit(globalSession, branchSession); // 下面代码可忽略 if (isXaerNotaTimeout(globalSession,branchStatus)) { LOGGER.info("Commit branch XAER_NOTA retry timeout, xid = {} branchId = {}", globalSession.getXid(), branchSession.getBranchId()); branchStatus = BranchStatus.PhaseTwo_Committed; } switch (branchStatus) { // 如果该分支事务已经提交,说明提交成功了,那么移除该分支 case PhaseTwo_Committed: SessionHelper.removeBranch(globalSession, branchSession, !retrying); return CONTINUE; // 如果该分支事务已经提交失败,并且无法重试,那么打上标记 case PhaseTwo_CommitFailed_Unretryable: //not at branch SessionHelper.endCommitFailed(globalSession, retrying); LOGGER.error("Committing global transaction[{}] finally failed, caused by branch transaction[{}] commit failed.", globalSession.getXid(), branchSession.getBranchId()); return false; default: // 进入重试队列 if (!retrying) { globalSession.queueToRetryCommit(); return false; } // 如果可以异步提交,那就直接跳过,定时任务会执行 if (globalSession.canBeCommittedAsync()) { LOGGER.error("Committing branch transaction[{}], status:{} and will retry later", branchSession.getBranchId(), branchStatus); return CONTINUE; } else { // 剩下的就是提交失败的 LOGGER.error( "Committing global transaction[{}] failed, caused by branch transaction[{}] commit failed, will retry later.", globalSession.getXid(), branchSession.getBranchId()); return false; } } } catch (Exception ex) { StackTraceLogger.error(LOGGER, ex, "Committing branch transaction exception: {}", new String[] {branchSession.toString()}); if (!retrying) { // 出了异常,放入队列继续等待提交 globalSession.queueToRetryCommit(); // 抛出异常给TM throw new TransactionException(ex); } } return CONTINUE; }); // Return if the result is not null if (result != null) { return result; } //If has branch and not all remaining branches can be committed asynchronously, //do print log and return false if (globalSession.hasBranch() && !globalSession.canBeCommittedAsync()) { LOGGER.info("Committing global transaction is NOT done, xid = {}.", globalSession.getXid()); return false; } if (!retrying) { // 设置分布式事务状态为提交成功 globalSession.setStatus(GlobalStatus.Committed); } } // if it succeeds and there is no branch, retrying=true is the asynchronous state when retrying. EndCommitted is // executed to improve concurrency performance, and the global transaction ends.. if (success && globalSession.getBranchSessions().isEmpty()) { SessionHelper.endCommitted(globalSession, retrying); LOGGER.info("Committing global transaction is successfully done, xid = {}.", globalSession.getXid()); } return success; }
也就是说,TC在收到TM的提交信号后,会RPC逐个调用RM执行分支事务的提交:public abstract class AbstractRMHandler extends AbstractExceptionHandler implements RMInboundHandler, TransactionMessageHandler { // RM收到分支事务提交请求 @Override public BranchCommitResponse handle(BranchCommitRequest request) { BranchCommitResponse response = new BranchCommitResponse(); // 模版模式 exceptionHandleTemplate(new AbstractCallback() { @Override public void execute(BranchCommitRequest request, BranchCommitResponse response) throws TransactionException { // 执行分支事务提交 doBranchCommit(request, response); } }, request, response); return response; } protected void doBranchCommit(BranchCommitRequest request, BranchCommitResponse response) throws TransactionException { String xid = request.getXid(); long branchId = request.getBranchId(); String resourceId = request.getResourceId(); String applicationData = request.getApplicationData(); if (LOGGER.isInfoEnabled()) { LOGGER.info("Branch committing: " + xid + " " + branchId + " " + resourceId + " " + applicationData); } // 执行分支事务提交 BranchStatus status = getResourceManager().branchCommit(request.getBranchType(), xid, branchId, resourceId, applicationData); // 返回响应结果 response.setXid(xid); response.setBranchId(branchId); response.setBranchStatus(status); if (LOGGER.isInfoEnabled()) { LOGGER.info("Branch commit result: " + status); } } }
根据上述代码,分布式事务的回滚其实也是TC接到TM的回滚信号后,通过RPC依次给RM发请求,触发各分支事务的回滚。
其次就是TC里面的异步提交或回滚了,在博客TC在seata分布式事务中的主要任务中,TC服务里面有多个定时任务,该定时任务就是通过定时检查各分布式事务的状态来判断是否执行提交或回滚的。所以分布式事务的提交与回滚还分为异步方式和同步方式。小结
根据上述源码分析,我们可以知道,分支事务的提交或回滚,是直接由TC服务触发,TM间接触发的;步骤可简单分解为以下几步:1.RM执行分支事务;
1.1:分支事务执行成功; 1.2:分支事务执行失败;
2.TM管理分布式事务的提交或回滚;
2.1:当任意RM分支事务执行出现异常,TM通过RPC向TC服务发起分布式事务的回滚; 2.2:只有所有RM都成功提交分支事务,TM通过RPC向TC服务发起分布式事务的提交;
3.TC服务依次向所有RM发起提交或回滚;
3.1:TC接收到TM的提交请求,查询出所有的分支RM,并依次向所有的RM发起提交请求; 3.2:TC接收到TM的回滚请求,查询所有的分支RM,依次向所有RM发起回滚请求;
4.RM执行分支事务的提交或回滚;
4.1:RM收到TC的提交请求,提交分支事务; 4.2:RM收到TC的回滚请求,回滚分支事务;
五招教你早发现强直性脊柱炎近年来,强直性脊柱炎逐渐被人们熟知,不少人反映,自己经常半夜背部剧烈疼痛,难以入睡,并且随着深秋气温渐冷,情况更加严重,甚至出现了晨僵症状,痛苦无比。强直性脊柱炎虽然无法根治,但早
汤师爷人到中年,你发现了哪些真相?1年少不听劝,人无再少年。年少不听过来人的规劝,等撞破南墙,头破血流才明白,那些过来人的说教当时不理解,但亦是真理,不过悟明白已不再少年2任何的事物一旦过度都会变得异常廉价。为何人
冯远征大9岁妻子真受宠!68岁穿着粉裙娇俏动人,全程依偎老公毫无疑问,粉色是大部分女性都喜爱的一个颜色。无论是少女轻熟女,还是成熟女性,似乎都对粉色情有独钟。不过,不同年龄阶段的女性在穿着粉色时,最好要根据自身年龄的特质去挑选合适的粉色单品
黄桂英带领群众走上蛭富路广西来宾市万才返乡,振兴家乡系列报道之八编者按为破解人才瓶颈,近年来,来宾市深入实施万才返乡振兴家乡计划,加强与来宾籍在外人才的团结联系,搭建创新创业平台,引导在外人才带资金带技术
提高群众防范意识!信宜丁堡开展禁毒反诈等法治宣传活动近日,信宜市丁堡镇政府组织干部深入镇街中心积极开展禁毒反诈宪法宣传提升两度一率暨平安建设宣传活动。进一步增强人民群众防范毒品诱惑养老诈骗邪教组织诱骗的识别能力,提升群众对毒品危害常
每日一图湖南正是冬日好风光,株洲职教城美如画摄影夏正湘图说株洲职教城内晴空碧蓝,树木像卫兵一样列道河边,守护着静谧的校园,静品冬日书香,整个职教城都换上了冬装。株洲职教城位于云龙示范区,一城九校,是我省规模最大的职教城区,是
湖南市场监管年关守护(2023)行动启动,重点整治这些!12月26日,全省市场监管年关守护(2023)行动电视电话会议召开。省市场监管局党组书记局长钱俊君出席会议并讲话。根据全省市场监管年关守护(2023)行动方案,安全隐患排查整治重点
如果你已经三天都没饭吃了,你觉得摆地摊赚钱吃饭丢人吗中国有句老话说得好站着说话,不嫌腰疼,其中的道理显而易见。如果脱离实际,好高骛远,又不能设身处地替别人着想,很容易在这个问题上犯错误。认为摆地摊赚钱丢人的无非有以下几种情况眼高于手
普京抓到内奸,俄军不再单打独斗,强力外援上线!伊朗警告乌克兰大家好,欢迎收看本期节目!俄乌冲突的爆发,给了俄罗斯一个严峻的考验。俄罗斯不光要面对强大的西方集团,还要面对内部的敌对势力。不论是来自乌方的间谍,还是来自俄罗斯的内奸。俄方都要清除
从荔浦芋头到爱尔兰面包蟹,拼多多投入30亿给年货节扩围12月26日,拼多多正式启动2023年年货节。从东北到海南上海至新疆的农特产,延伸至美国开心果智利车厘子挪威北极鳕阿根廷红虾爱尔兰面包蟹等全球直采的尖货,平台将联合全国各省市上千个
富有的心态与贫穷的心态真正的差异也许你很富有,也许你很穷。也许在你生命中的某个时刻,这两者你都经历过。如果你还没有搞清楚,致富并不仅仅是钱。它关乎幸福富足时间成功和正确的心态。社会问题导致贫困,然而,富裕和贫穷的
摩洛哥,非洲国家中的另类今天要给大家介绍非洲的一个小国摩洛哥,虽然这个国家在非洲,可国内却几乎没有黑人,多是居住的白人,这是为什么呢?摩洛哥是非洲西北部的一个沿海国家,它濒临大西洋,北部和西班牙葡萄牙隔海
中国女性独自行走111国游记之11l旅行沙特,躺在能源和古兰经上的国家,石油和罩袍下的女性是它的标志。数年前,单身女性想获沙特签证几无可能,但现在沙特大力发展旅游,中国护照电子签落地签毫无难度。酒店办理入住时,前台只露眼
端庄优雅的旗袍裙搭配黑色丝袜与黑色高跟鞋绽放成熟女性魅力随着秋天的到来,各种时尚新款式也随之而来。最近,有一款新中式改良版黑色无袖旗袍裙在网上大火。这款旗袍裙既端庄优雅,又充满了新中式的魅力。搭配黑色丝袜和黑色高跟鞋,绽放出成熟女性的魅
跨境电商选品美妆市场新机遇男性化妆品跨境电商选品一直是个老大难的问题,易卖产品竞争大,冷门产品没市场,如果可以洞察市场近况,满足消费者需求,就可以在跨境电商快人一步。本篇文章,飞书逸途将为大家带来市场的最新洞察,男性
面叶的情思疫情无情人有情,一碗面叶暖心情。亲情有情也无情,我独自在屋闹心情,咚咚的敲门声,捶散了我的坏心情。那是送饭到了的敲门声,他听到我的咳嗽声,好点没有这一声,突然这时里外都没有声,我没
夜色降临寂寞人头条作者云开日初夜色降临寂寞人,心心念念盼良辰。春夏秋冬已过去,未见爱箭射穿谁。漆黑的夜晚又降临了,一个人的日子深感孤单寂寞,心心念念的盼着有个美好的良辰,有个爱神的眷顾让自己不再
星期六欣赏月季花周六去郊野公园玩了半天,那里依然花开鲜花,美丽漂亮!头条创作挑战赛几许月季花娇艳百媚地盛开在冬日里,今天阳光明媚,暖暖的照耀着月季花,别有一番韵味!草丛那里,一家大小正享受着家庭乐
小米雷军MIUI14拥有无与伦比的流畅体验明天发布手机中国新闻MIUI操作系统是小米手机的灵魂之一,也是无数消费者选择购买小米手机的一大原因。在12月11日晚间,小米即将发布最新旗舰小米13系列。而与此同时,MIUI14操作系统也
俄克师大孔院举办第三届国际教育研讨会(神州学人网讯)12月8日至9日,俄罗斯克拉斯诺亚尔斯克国立师范大学孔子学院(以下简称克师大孔院)举办第三届国际教育研讨会。研讨会聚焦中俄教育领域的新动态和新发展,探讨中俄人文交流
疑似MH370残骸被找到,美英专家飞行员或故意坠机英媒曝马航MH370失联调查新猜测文熊姐在失踪8年多后,马航MH370被证明有故意坠毁的可能。据泰晤士报报道,近日,英国工程师理查德戈弗雷和美国人布莱恩吉布森发布了一份关于马航MH
第五届CIOC2022不动产数字化峰会举行中证网讯(记者张军)日前,由中国房地产业协会指导易居克而瑞主办中国房地产业协会数字科技地产分会合作的第五届CIOC不动产数字化峰会于线上举行。易居企业集团CEO丁祖昱表示,房地产行