温州如何进行网站推广/网站发布与推广怎么写
前面的博客中,我们介绍了,发起全局事务时,是如何进行全局事务提交的,这篇博客,主要记录,在seata分布式事务中,全局事务提交的时候,服务端是如何进行处理的
发起全局事务提交操作
事务发起者,在所有分支事务执行完毕之后,如果没有发生异常,会进行全局事务提交
这里就不做过多解释了,前面seata入门和全局事务begin的博客中,有介绍过这里入参的request对象的重要性
服务端接收到请求
前面全局事务begin的源码,介绍过,netty服务端接收到请求之后,是如何执行到这里的,在这里会根据request请求的类型,交给不同的handler来处理
io.seata.server.AbstractTCInboundHandler#handle(io.seata.core.protocol.transaction.GlobalCommitRequest, io.seata.core.rpc.RpcContext)
io.seata.server.coordinator.DefaultCoordinator#doGlobalCommit
上面也没有太多的业务逻辑,没什么好说的,我们直接来看core.commit()方法的逻辑
io.seata.server.coordinator.DefaultCore#commit
@Override
public GlobalStatus commit(String xid) throws TransactionException {/*** 1.获取全局session:globalSession*/GlobalSession globalSession = SessionHolder.findGlobalSession(xid);if (globalSession == null) {return GlobalStatus.Finished;}/*** 2.给globalSession添加监听*/globalSession.addSessionLifecycleListener(SessionHolder.getRootSessionManager());// just lock changeStatus/*** 3.对globalSession进行一些处理*/boolean shouldCommit = SessionHolder.lockAndExecute(globalSession, () -> {// Highlight: Firstly, close the session, then no more branch can be registered./*** 3.1 globalSession.setActive(false);将全局session的active设置为false* 在clean()方法中会把lockTable中本次加锁的记录(分支事务相关锁信息)删除*/globalSession.closeAndClean();if (globalSession.getStatus() == GlobalStatus.Begin) {/*** 对于AT模式,这里永远返回false* 对于AT模式,这里会执行if的逻辑,将globalSession的status设置为AsyncCommitting*/if (globalSession.canBeCommittedAsync()) {globalSession.asyncCommit();return false;} else {/*** 将globalSession的状态设置为committing状态*/globalSession.changeStatus(GlobalStatus.Committing);return true;}}return false;});/*** 4.通知分支事务进行提交,如果是AT模式,不会进入到这里执行,因为shouldCommit是false* 是通过一个异步线程来进行调用doGlobalCommit()方法的*/if (shouldCommit) {boolean success = doGlobalCommit(globalSession, false);//If successful and all remaining branches can be committed asynchronously, do async commit.if (success && globalSession.hasBranch() && globalSession.canBeCommittedAsync()) {globalSession.asyncCommit();return GlobalStatus.Committed;} else {return globalSession.getStatus();}} else {return globalSession.getStatus() == GlobalStatus.AsyncCommitting ? GlobalStatus.Committed : globalSession.getStatus();}
}
上面贴的这段代码,是netty服务端接收到客户端全局事务请求之后,最核心的一个入口,我们拆开来看
commit
closeAndClean()
上面代码中注释1、2、3就不细看了,是一些不重要的逻辑,我们直接来看3.1这个注释对应的方法
在这个方法中,一个close()方法,一个clean()方法
close()方法中的逻辑比较简单,就是把globalSession的active属性设置为了false
我们接着来看clean()方法:
clean()内部调用的这个方法,如果看过前面全局事务回滚的代码,就会发现这个代码很眼熟,就是把分支事务对应的lockTable中指定的加锁的资源进行释放
shouldCommit()
我们接着来看shouldCommit参数的赋值逻辑,可以着重看下第三点的注释,这里的shouldCommit参数,如果是AT模式,永远false,原因是在这里
在canBeCommittedAsync方法中,下面两张截图我们结合起来看,如果是AT模式,canBeCommitedAsync()返回的一定是true;如果这里返回true,那上面截图,一定会进入到if()的逻辑中
所以,对于AT模式,shouldCommit一定是false,并且会调用globalSession.asyncCommit();
这段代码的整体逻辑,就是说,如果当前事务是允许异步处理的,那就给shouldCommit赋值为false,同时把globalSession的status修该为AsyncCommitting;这个状态很重要,这里的意思,我认为是说,当前事务是需要异步处理的,当前代码中,就不同步处理了,接来下的逻辑,可以证明
可以发现,当是AT模式的时候,直接执行了else的逻辑,那我们接下来看下,对于netty服务端,真正去处理分支事务的代码
init()
这段初始化的代码,和上面commit()有点关联,我们截止到这里,需要知道上面commit()方法,如果是AT模式的时候,只是把当前globalSession的状态改成了AsyncCommitting状态
io.seata.server.coordinator.DefaultCoordinator#init
这个方法,是在服务端启动的时候,会在这里初始化一批异步线程,其中有一个和本篇博客有关系
查找所有AsyncCommitting状态的globalSession
io.seata.server.storage.db.session.DataBaseSessionManager#allSessions
这里就不再继续往下贴代码了,逻辑比较简单,可以自己看下,简单来说,就是根据当前入参中的AsyncCommitting,从globalTable中,根据状态进行查询,然后找到所有待异步处理的globalSession
core.doGlobalCommit
io.seata.server.coordinator.DefaultCore#doGlobalCommit
这个方法,是服务端进行全局事务提交的处理逻辑,中间绕了这么一大圈,现在逻辑应该有点清晰明了了,其实就是在netty服务端接收到同步请求的时候,只会先把lockTable中加锁的数据删除,然后修改globalSession的状态,最后通过异步定时执行的线程池去执行全局事务提交的逻辑
public boolean doGlobalCommit(GlobalSession globalSession, boolean retrying) throws TransactionException {boolean success = true;// start committing eventeventBus.post(new GlobalTransactionEvent(globalSession.getTransactionId(), GlobalTransactionEvent.ROLE_TC,globalSession.getTransactionName(), globalSession.getBeginTime(), null, globalSession.getStatus()));if (globalSession.isSaga()) {success = getCore(BranchType.SAGA).doGlobalCommit(globalSession, retrying);} else {for (BranchSession branchSession : globalSession.getSortedBranches()) {// if not retrying, skip the canBeCommittedAsync branchesif (!retrying && branchSession.canBeCommittedAsync()) {continue;}BranchStatus currentStatus = branchSession.getStatus();/*** 如果二阶段分支事务状态是失败,就无需执行下面的逻辑,直接remove即可*/if (currentStatus == BranchStatus.PhaseOne_Failed) {globalSession.removeBranch(branchSession);continue;}try {/*** 这里是服务端在全局事务提交的时候,会通知RM去对本地的branch事务进行处理,是通过netty去交互的* 1.如果RM删除成功,就将branchSession移除,并释放锁* 2.如果删除失败*/BranchStatus branchStatus = getCore(branchSession.getBranchType()).branchCommit(globalSession, branchSession);switch (branchStatus) {case PhaseTwo_Committed:globalSession.removeBranch(branchSession);continue;case PhaseTwo_CommitFailed_Unretryable:if (globalSession.canBeCommittedAsync()) {LOGGER.error("Committing branch transaction[{}], status: PhaseTwo_CommitFailed_Unretryable, please check the business log.", branchSession.getBranchId());continue;} else {SessionHelper.endCommitFailed(globalSession);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);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();throw new TransactionException(ex);}}}//If has branch and not all remaining branches can be committed asynchronously,//do print log and return falseif (globalSession.hasBranch() && !globalSession.canBeCommittedAsync()) {LOGGER.info("Committing global transaction is NOT done, xid = {}.", globalSession.getXid());return false;}}//If success and there is no branch, end the global transaction./*** 这里的sessionHelper.encCommitted会将globalSession中的记录删除*/if (success && globalSession.getBranchSessions().isEmpty()) {SessionHelper.endCommitted(globalSession);// committed eventeventBus.post(new GlobalTransactionEvent(globalSession.getTransactionId(), GlobalTransactionEvent.ROLE_TC,globalSession.getTransactionName(), globalSession.getBeginTime(), System.currentTimeMillis(),globalSession.getStatus()));LOGGER.info("Committing global transaction is successfully done, xid = {}.", globalSession.getXid());}return success;
}
上面这段代码,是全局事务提交的逻辑,本质上,和全局事务回滚的逻辑,没太大的区别,只是底层一个调用的是commit,一个调用的是rollback;针对上面这段代码,我们需要着重关注的是:branchCommit这个方法,这个方法,我们下面单独说,先来看下这段代码的逻辑
- getCore(branchSession.getBranchType()).branchCommit(globalSession, branchSession); 通过这个方法,发起netty请求,通知各个分支事务,进行全局事务的提交
- 根据分支事务的返回状态,进行不同的处理;如果分支事务处理成功,在seata服务端这里,会把分支事务删除,并且把内存中的分支事务id删除(在seata服务端,每个全局事务都会维护一个集合,存储当前全局事务对应的所有分支事务)
- 在所有的分支事务,处理完毕之后,SessionHelper.endCommitted(globalSession); 通过这个方法,结束所有的逻辑,其实就是把globalSession从mysql数据库中删除
针对第二点和第三点,就不点进去细看了,其实本质上和全局事务回滚时,做的逻辑几乎上是一致的,我们主要来看下第一点,分支事务是如何处理分支事务提交的逻辑,因为这段逻辑和分支事务回滚的逻辑不一样
分支事务提交
io.seata.rm.datasource.AsyncWorker#branchCommit
这是rm这一端接收到分支事务提交的处理逻辑,但是我们会发现,这段代码很简单:
这里可以看到,只是将当前请求信息,塞到了一个queue中
io.seata.rm.datasource.AsyncWorker#init
这里可以看到,在rm启动的时候,会初始化一个定时执行的线程池,在这个线程池中,会定时的调用doBranchCommit()方法
io.seata.rm.datasource.AsyncWorker#doBranchCommits
private void doBranchCommits() {if (ASYNC_COMMIT_BUFFER.isEmpty()) {return;}/*** 1.mappedContexts存储的是从阻塞队列中获取到的要处理的分支事务*/Map<String, List<Phase2Context>> mappedContexts = new HashMap<>(DEFAULT_RESOURCE_SIZE);List<Phase2Context> contextsGroupedByResourceId;while (!ASYNC_COMMIT_BUFFER.isEmpty()) {Phase2Context commitContext = ASYNC_COMMIT_BUFFER.poll();contextsGroupedByResourceId = CollectionUtils.computeIfAbsent(mappedContexts, commitContext.resourceId, key -> new ArrayList<>());contextsGroupedByResourceId.add(commitContext);}/*** 2.遍历mappedContexts*/for (Map.Entry<String, List<Phase2Context>> entry : mappedContexts.entrySet()) {Connection conn = null;DataSourceProxy dataSourceProxy;try {try {DataSourceManager resourceManager = (DataSourceManager) DefaultResourceManager.get().getResourceManager(BranchType.AT);dataSourceProxy = resourceManager.get(entry.getKey());if (dataSourceProxy == null) {throw new ShouldNeverHappenException("Failed to find resource on " + entry.getKey());}conn = dataSourceProxy.getPlainConnection();} catch (SQLException sqle) {LOGGER.warn("Failed to get connection for async committing on " + entry.getKey(), sqle);continue;}contextsGroupedByResourceId = entry.getValue();Set<String> xids = new LinkedHashSet<>(UNDOLOG_DELETE_LIMIT_SIZE);Set<Long> branchIds = new LinkedHashSet<>(UNDOLOG_DELETE_LIMIT_SIZE);/*** 3.判断要处理的分支事务数量是否达到了批量处理的阈值* 如果到了,就批量进行删除* 否则的话,就清空xids和branchIds 然后return*/for (Phase2Context commitContext : contextsGroupedByResourceId) {xids.add(commitContext.xid);branchIds.add(commitContext.branchId);int maxSize = Math.max(xids.size(), branchIds.size());/*** 并不是在每次全局事务提交的时候,就会执行下面的sql* 而是在达到一定的阈值的时候,才会批量执行,阈值默认是1000** 删除undoLog日志*/if (maxSize == UNDOLOG_DELETE_LIMIT_SIZE) {try {UndoLogManagerFactory.getUndoLogManager(dataSourceProxy.getDbType()).batchDeleteUndoLog(xids, branchIds, conn);} catch (Exception ex) {LOGGER.warn("Failed to batch delete undo log [" + branchIds + "/" + xids + "]", ex);}xids.clear();branchIds.clear();}}if (CollectionUtils.isEmpty(xids) || CollectionUtils.isEmpty(branchIds)) {return;}try {UndoLogManagerFactory.getUndoLogManager(dataSourceProxy.getDbType()).batchDeleteUndoLog(xids,branchIds, conn);} catch (Exception ex) {LOGGER.warn("Failed to batch delete undo log [" + branchIds + "/" + xids + "]", ex);}if (!conn.getAutoCommit()) {conn.commit();}} catch (Throwable e) {LOGGER.error(e.getMessage(), e);try {if (conn != null) {conn.rollback();}} catch (SQLException rollbackEx) {LOGGER.warn("Failed to rollback JDBC resource while deleting undo_log ", rollbackEx);}} finally {if (conn != null) {try {conn.close();} catch (SQLException closeEx) {LOGGER.warn("Failed to close JDBC resource while deleting undo_log ", closeEx);}}}}
}
上面这端代码,我们可以看下,没1000ms执行一次,如果这1000ms之内,queue中待指定分支事务commit的请求达到了一定的阈值(UNDOLOG_DELETE_LIMIT_SIZE),就会执行commit请求(UndoLogManagerFactory.getUndoLogManager(dataSourceProxy.getDbType()).batchDeleteUndoLog(
xids, branchIds, conn););如果没有达到阈值,也会去执行commit的操作
UndoLogManagerFactory.getUndoLogManager 这里执行提交的逻辑,其实就是把undoLog数据给删除,因为对于全局事务提交时,其实在第一阶段,mysql已经真正执行了commit的操作,在第二阶段,只需要把undoLog给删除即可
总结
以上,就是全局事务提交的逻辑,整体看下来,我们可以发现,对于全局事务提交的时候,分支事务在处理的时候,是异步来处理的,这是和回滚逻辑有很大的区别,因为上篇博客中,我们有看到,全局事务回滚时,分支事务在第二阶段,是同步处理的,在接收到请求之后,会根据undoLog生成回滚sql,并执行,然后删除undoLog数据,但是对于全局事务提交的第二阶段,会发现,接收到请求之后,直接塞到了队列中,通过异步的请求,没1000ms执行一次提交的逻辑
相关文章:

seata源码-全局事务提交 服务端源码
前面的博客中,我们介绍了,发起全局事务时,是如何进行全局事务提交的,这篇博客,主要记录,在seata分布式事务中,全局事务提交的时候,服务端是如何进行处理的 发起全局事务提交操作 事…...

C++ 模板
文章目录一、泛型编程二、 函数模板三、类模板一、泛型编程 泛型编程:编写与类型无关的通用代码,代码复用的一种方法 在 C 中,我们可以通过函数重载实现通用的交换函数 Swap ,但是有一些缺点 重载函数只有类型不同,…...

JWT安全漏洞以及常见攻击方式
前言 随着web应用的日渐复杂化,某些场景下,仅使用Cookie、Session等常见的身份鉴别方式无法满足业务的需要,JWT也就应运而生,JWT可以有效的解决分布式场景下的身份鉴别问题,并且会规避掉一些安全问题,如CO…...

华为OD机试题 - 最小施肥机能效(JavaScript)
最近更新的博客 华为OD机试题 - 任务总执行时长(JavaScript) 华为OD机试题 - 开放日活动(JavaScript) 华为OD机试 - 最近的点 | 备考思路,刷题要点,答疑 【新解法】 华为OD机试题 - 最小步骤数(JavaScript) 华为OD机试题 - 任务混部(JavaScript) 华为OD机试题 - N 进…...

Python(1)变量的命名规则
目录 1.变量的命名原则 3.内置函数尽量不要做变量 4.删除变量和垃圾回收机制 5.结语 参考资料 1.变量的命名原则 ①由英文字母、_(下划线)、或中文开头 ②变量名称只能由英文字母、数字、下画线或中文字所组成。 ③英文字母大小写不相同 实例: 爱_aiA1 print(…...

Shiro1.9学习笔记
文章目录一、Shiro概述1、Shiro简介1.1 介绍1.2 Shiro特点2、Shiro与SpringSecurity的对比3、Shiro基本功能4、Shiro原理4.1 Shiro 架构(外部)4.2 shiro架构(内部)二、Shiro基本使用1、环境准备2、登录认证2.1 登录认证概念2.2 登录认证基本流程2.3 登录认证实例2.4 身份认证源…...
2.5|iot|嵌入式Linux系统开发与应用|第4章:Linux外壳shell脚本程序编程
1.shell基础 Shell是Linux操作系统内核的外壳,它为用户提供使用操作系统的命令接口。 用户在提示符下输入的每个命令都由shell先解释然后发给Linux内核,所以Linux中的命令通称为shell命令。 通常我们使用shell来使用Linux操作系统。Linux系统的shell是…...

九龙证券|连续七周获加仓,四大行业成“香饽饽”!
本周17个申万职业北上资金持股量环比增加。 北上资金抢筹铝业龙头 本周A股商场全体冲高回落,沪指收跌1.12%,深成指跌2.18%,创业板指跌3.76%。北上资金周内小幅净流入。在大盘体现较差的周四周五,北上资金别离逆市回流67.94亿元、…...

210天从外包踏进华为跳动那一刻,我泪目了
前言 没有绝对的天才,只有持续不断的付出。对于我们每一个平凡人来说,改变命运只能依靠努力幸运,但如果你不够幸运,那就只能拉高努力的占比。 2021年4月,我有幸成为了华为的一名高级测试工程师,正如标题所…...

CMake 引入第三方库
CMake 引入第三方库 在 CMake 中,如何引入第三方库是一个常见的问题。在本文中,我们将介绍 CMake 中引入第三方库的不同方法,以及它们的优缺点。 1. 使用 find_package 命令 在 CMake 中,使用 find_package 命令是最简单和最常…...

软考中级-面向对象
面向对象基础(1)类类分为三种:实体类(世间万物)、接口类(又称边界类,提供用户与系统交互的方式)、控制类(前两类之间的媒介)。对象:由对象名数据&…...

Linux 系统构成:bootloader、kernel、rootfs
写在前面: 本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。 目录前言bootloaderk…...

SpringCloud - Eureka注册发现
目录 提供者与消费者 Eureka原理分析 搭建Eureka服务 服务注册 服务发现 提供者与消费者 服务提供者: 一次业务中,被其它微服务调用的服务(提供接口给其它微服务)服务消费者: 一次业务中,调用其它微服务的服务(调用其它微服务…...

WampServer安装教程
文章目录简介:官网地址安装步骤:我是阿波,学习PHP记录一下笔记,如果对你有帮助,欢迎一键三连,谢谢! 简介: WampServer是一个用于Windows操作系统的Web开发环境,其名称来…...

Go语言泛型基础
泛型 Go 并不是一种静止的、一成不变的编程语言。新的功能是在经过大量的讨论和实验后慢慢采用的。最初的 Go1.0发布以来,Go语言习惯的模式已经发生了重大变化1.7的context、1.11的modules、1.13 error嵌套等Go的 1.18 版本包括了类型参数的实现,也就是…...

基于android的中医养生app
需求信息: 中医健康养生APP分为四大模块,其中个人中心又分为4大块,游客用户个人中心是空白的。 上图为养生知识推广普及模块的功能结构图。 在养生知识推广普及模块界面,用户可以选择自己感兴趣的模块进行文章浏览,文章…...

2023美赛C代码思路结果【全部更新完毕】注释详尽
C题已完成全部代码,注释详尽,并增加扰动项,保证大家的结果不会撞 需要全部问题的可以点击:https://www.jdmm.cc/file/2708697/ 下面贴出核心代码: -- coding: utf-8 -- TODO: 入口函数 import numpy as np from…...

实现8086虚拟机(二)——模拟CPU和内存
文章目录CPU 架构EU(执行单元)BIU(总线接口单元)小结一下模拟内存模拟 BIU模拟 EU模拟 CPU总结要模拟 8086 CPU 运行,必须知道 CPU 的一些知识。下文的知识点都来自《Intel_8086_Family_Users_Manual 》。CPU 架构 微…...

Windows7下使用VMware11.1.1安装ubuntu-16.04.7
一、说明二、安装说明三、安装步骤详解1、先安装VMware软件2、创建虚拟机3、编辑虚拟机4、开启虚拟机,初始化Linux系统一、说明 虽然VMware和ubuntu最新版已经很高了,我这电脑由于是win7配值还低,所以采用低版本来安装 VMware版本࿱…...

基于SSM框架的CMS内容管理系统的设计与实现
基于SSM框架的CMS内容管理系统的设计与实现 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取项目下载方式🍅 一、项目…...

华为OD机试 - 运动会 | 机试题算法思路 【2023】
最近更新的博客 华为OD机试 - 自动曝光(Python) | 机试题算法思路 【2023】 华为OD机试 - 双十一(Python) | 机试题算法思路 【2023】 华为OD机试 - 删除最少字符(Python) | 机试题算法思路 【2023-02】 华为OD机试 - Excel 单元格数值统计(Python) | 机试题算法思路 …...

(C语言篇)扫雷的实现
文章目录 一、开始时的基本思维:二、进入游戏的逻辑(test.c文件中实现)三、游戏的编写 1. 初始化棋盘 I. test.cII. game.hIII. game.c 2.打印棋盘 I. test.cII. game.hIII. game.c 3.布置雷 I. test.cII. game.hIII. game.c 4.排查雷 I. test.cII. game.hIII. gam…...

华为手表开发:WATCH 3 Pro(8)获取位置服务
华为手表开发:WATCH 3 Pro(8)获取位置服务初环境与设备文件夹:文件新增第二页面geolocation.hmlgeolocation.js修改首页 -> 新建按钮 “ 跳转 ”index.hmlindex.js 引用包:system.router首页效果点击结果按钮跳转后…...

AnLogicFPGA设计的时序约束及时序收敛
本篇博文讲了三个内容:时序约束基本概念、时序约束命令、时序收敛技巧 时序约束基本概念 时序设计的实质就是满足每一个触发器的建立(setup)时间和保持(hold)时间。 建立时间(Tsu) 触发器的时钟信号沿到来以前&…...

ubuntu22.10安装sogou输入法后不能输入中文字符(可以输入中文标点符号)
问题描述 想在ubuntu22.10系统上安装sogou中文输入法,按照sogou输入法网站给出的步骤安装后,发现无法输入中文字符,但是可以输入中文标点符号。 sogou网站:https://shurufa.sogou.com/linux/guide 寻找答案1 通过各种百度和必…...

基于微信小程序的生活日用品交易平台 的设计与实现
基于微信小程序的生活日用品交易平台 的设计与实现 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取项目下载方式🍅 一…...

15:高级篇 - CTK 事件与监听
作者: 一去、二三里 个人微信号: iwaleon 微信公众号: 高效程序员 生命周期层事件 在 Plugin 生命周期的不同状态相互转换时,CTK Plugin Framework 会发出各种不同的事件,以供事先注册好的事件监听器处理,这些事件被称为“生命周期层事件”。CTK Plugin Framework 支持的…...

SpringBoot Notes
文章目录1 SpringBootWeb快速入门1.1Spring官网1.2 Web分析2. HTTP协议2.1 HTTP介绍34 SpringBootWeb请求响应5 响应6 分层解耦6.1 三层架构6.1.1 三层架构介绍6.1.2 基于三层架构的程序执行流程:6.1.3 代码拆分6.2 分层解耦6.2.1 内聚、耦合6.2.2 解耦思路6.3 IOC&…...

CoreDNS
目录 文章目录目录本节实战前言1、环境变量2、DNS1.DNS 解析过程2.根域名服务器3.顶级域名服务器4.权威性域名服务器5.dig 域名3、CoreDNS1.CoreDNS 扩展配置(1)开开启日志服务(2)特定域名使用自定义 DNS 服务器(3&…...

码农饭碗不保——ChatGPT正在取代Coder
码农饭碗不保——ChatGPT正在取代Coder 最近被OpenAI的ChatGPT刷屏了。我猜你已经读了很多关于ChatGPT的文章,不需要再介绍了。假如碰巧您还不太了解ChatGPT是什么,可以先看一下这篇文章,然后再回来继续。 与ChatGPT对话很有趣,…...