ServiceComb中的数据最终一致性方案

本文由华为微服务引擎技术团队&&ServiceComb社区授权发布。

数据一致性是构建业务系统需要考虑的重要问题 , 以往我们是依靠数据库来保证数据的一致性。但是在微服务架构以及分布式环境下实现数据一致性是一个很有挑战的的问题。ServiceComb作为开源的微服务框架致力解决微服务开发过程中的问题。我们最近发起的ServiceComb-Saga项目来解决分布式环境下的数据最终一致性问题。本文将向大家介绍为什么数据一致性如此重要?Saga又是什么?

单体应用的数据一致性

想象一下如果我们经营着一家大型企业,下属有航空公司、租车公司、和连锁酒店。我们为客户提供一站式的旅游行程规划服务,这样客户只需要提供出行目的地, 我们帮助客户预订机票、租车、以及预订酒店。从业务的角度,我们必须保证上述三个服务的预订都完成才能满足一个成功的旅游行程,否则不能成行。

我们的单体应用要满足这个需求非常简单,只需将这个三个服务请求放到同一个数据库事务中,数据库会帮我们保证全部成功或者全部回滚。

这里写图片描述

当这个功能上线以后,公司非常满意,客户也非常高兴。

微服务场景下的数据一致性

这几年中,我们的行程规划服务非常成功,企业蒸蒸日上,用户量也翻了数十倍。企业的下属航空公司、租车公司、和连锁酒店也相继推出了更多服务以满足客户需求, 我们的应用和开发团队也因此日渐庞大。如今我们的单体应用已变得如此复杂,以至于没人了解整个应用是怎么运作的。更糟的是新功能的上线现在需要所有研发团队合作, 日夜奋战数周才能完成。看着市场占有率每况愈下,公司高层对研发部门越来越不满意。

经过数轮讨论,我们最终决定将庞大的单体应用一分为四:机票预订服务、租车服务、酒店预订服务、和支付服务。服务各自使用自己的数据库,并通过HTTP协议通信。 负责各服务的团队根据市场需求按照自己的开发节奏发版上线。如今我们面临新的挑战:如何保证最初三个服务的预订都完成才能满足一个成功的旅游行程, 否则不能成行的业务规则?现在服务有各自的边界,而且数据库选型也不尽相同,通过数据库保证数据一致性的方案已不可行。

这里写图片描述

Sagas

幸运的是我们在互联网找到一篇精彩的论文,文中提出的数据一致性解决方案Saga恰好满足我们的业务要求。

Saga是一个长活事务,可被分解成可以交错运行的子事务集合。其中每个子事务都是一个保持数据库一致性的真实事务。

在我们的业务场景下,一个行程规划的事务就是一个Saga,其中包含四个子事务:机票预订、租车、酒店预订、和支付。

这里写图片描述
Chris Richardson在他的文章Pattern: Saga中对Saga有所描述。 Caitie McCaffrey也在她的演讲中提到如何在微软的光晕 4游戏中如何应用saga解决数据一致性问题。

Saga的运行原理

Saga中的事务相互关联,应作为(非原子)单位执行。任何未完全执行的Saga是不满足要求的,如果发生,必须得到补偿。要修正未完全执行的部分, 每个saga子交易T1应提供对应补偿事务C1

我们根据上述规则定义以下事务及其相应的事务补偿:
这里写图片描述

当每个saga子事务 T1, T2, …, Tn 都有对应的补偿定义 C1, C2, …, Cn-1, 那么saga系统可以保证 [1]

  • 子事务序列 T1, T2, …, Tn得以完成 (最佳情况)
  • 或者序列 T1, T2, …, Tj, Cj, …, C2, C1, 0 < j < n, 得以完成

换句话说,通过上述定义的事务/补偿,saga保证满足以下业务规则:

  • 所有的预订都被执行成功,如果任何一个失败,都会被取消
  • 如果最后一步付款失败,所有预订也将被取消

Saga的恢复方式

原论文中描述了两种类型的Saga恢复方式:

  • 向后恢复 补偿所有已完成的事务,如果任一子事务失败
  • 向前恢复 重试失败的事务,假设每个子事务最终都会成功

显然,向前恢复没有必要提供补偿事务,如果你的业务中,子事务(最终)总会成功,或补偿事务难以定义或不可能,向前恢复更符合你的需求。

理论上补偿事务永不失败,然而,在分布式世界中,服务器可能会宕机,网络可能会失败,甚至数据中心也可能会停电。在这种情况下我们能做些什么? 最后的手段是提供回退措施,比如人工干预。

使用Saga的条件

Saga看起来很有希望满足我们的需求。所有长活事务都可以这样做吗?这里有一些限制:

  • Saga只允许两个层次的嵌套,顶级的Saga和简单子事务 [1]
  • 在外层,全原子性不能得到满足。也就是说,sagas可能会看到其他sagas的部分结果 [1]
  • 每个子事务应该是独立的原子行为 [2]
    在我们的业务场景下,航班预订、租车、酒店预订和付款是自然独立的行为,而且每个事务都可以用对应服务的数据库保证原子操作。

我们在行程规划事务层面也不需要原子性。一个用户可以预订最后一张机票,而后由于信用卡余额不足而被取消。同时另一个用户可能开始会看到已无余票, 接着由于前者预订被取消,最后一张机票被释放,而抢到最后一个座位并完成行程规划。

补偿也有需考虑的事项:

  • 补偿事务从语义角度撤消了事务Ti的行为,但未必能将数据库返回到执行Ti时的状态。(例如,如果事务触发导弹发射, 则可能无法撤消此操作)

但这对我们的业务来说不是问题。其实难以撤消的行为也有可能被补偿。例如,发送电邮的事务可以通过发送解释问题的另一封电邮来补偿。

现在我们有了通过Saga来解决数据一致性问题的方案。它允许我们成功地执行所有事务,或在任何事务失败的情况下,补偿已成功的事务。 虽然Saga不提供ACID保证,但仍适用于许多数据最终一致性的场景。那我们如何设计一个Saga系统?

Saga Log

Saga保证所有的子事务都得以完成或补偿,但Saga系统本身也可能会崩溃。Saga崩溃时可能处于以下几个状态:

  • Saga收到事务请求,但尚未开始。因子事务对应的微服务状态未被Saga修改,我们什么也不需要做。

  • 一些子事务已经完成。重启后,Saga必须接着上次完成的事务恢复。

  • 子事务已开始,但尚未完成。由于远程服务可能已完成事务,也可能事务失败,甚至服务请求超时,saga只能重新发起之前未确认完成的子事务。这意味着子事务必须幂等。
  • 子事务失败,其补偿事务尚未开始。Saga必须在重启后执行对应补偿事务。
  • 补偿事务已开始但尚未完成。解决方案与上一个相同。这意味着补偿事务也必须是幂等的。
  • 所有子事务或补偿事务均已完成,与第一种情况相同。

为了恢复到上述状态,我们必须追踪子事务及补偿事务的每一步。我们决定通过事件的方式达到以上要求,并将以下事件保存在名为saga log的持久存储中:

  • Saga started event 保存整个saga请求,其中包括多个事务/补偿请求
  • Transaction started event 保存对应事务请求
  • Transaction ended event 保存对应事务请求及其回复
  • Transaction aborted event 保存对应事务请求和失败的原因
  • Transaction compensated event 保存对应补偿请求及其回复
  • Saga ended event 标志着saga事务请求的结束,不需要保存任何内容

这里写图片描述

通过将这些事件持久化在saga log中,我们可以将saga恢复到上述任何状态。

由于Saga只需要做事件的持久化,而事件内容以JSON的形式存储,Saga log的实现非常灵活,数据库(SQL或NoSQL),持久消息队列,甚至普通文件可以用作事件存储, 当然有些能更快得帮saga恢复状态。

Saga请求的数据结构

在我们的业务场景下,航班预订、租车、和酒店预订没有依赖关系,可以并行处理,但对于我们的客户来说,只在所有预订成功后一次付费更加友好。 那么这四个服务的事务关系可以用下图表示:

这里写图片描述

将行程规划请求的数据结构实现为有向非循环图恰好合适。 图的根是saga启动任务,叶是saga结束任务。

这里写图片描述

Parallel Saga

如上所述,航班预订,租车和酒店预订可以并行处理。但是这样做会造成另一个问题:如果航班预订失败,而租车正在处理怎么办?我们不能一直等待租车服务回应, 因为不知道需要等多久。

最好的办法是再次发送租车请求,获得回应,以便我们能够继续补偿操作。但如果租车服务永不回应,我们可能需要采取回退措施,比如手动干预。

超时的预订请求可能最后仍被租车服务收到,这时服务已经处理了相同的预订和取消请求。

这里写图片描述
因此,服务的实现必须保证补偿请求执行以后,再次收到的对应事务请求无效。 Caitie McCaffrey在她的演讲Distributed Sagas: A Protocol for Coordinating Microservices中把这个称为可交换的补偿请求 (commutative compensating request)。

ACID and Saga

ACID是具有以下属性的一致性模型:

  • 原子性(Atomicity)
  • 一致性(Consistency)
  • 隔离性(Isolation)
  • 持久性(Durability)

Saga不提供ACID保证,因为原子性和隔离性不能得到满足。原论文描述如下:

full atomicity is not provided. That is, sagas may view the partial results of other sagas [1]

通过saga log,saga可以保证一致性和持久性。

Saga 架构

最后,我们的Saga架构如下:

这里写图片描述

  • Saga Execution Component解析请求JSON并构建请求图
  • TaskRunner 用任务队列确保请求的执行顺序
  • TaskConsumer 处理Saga任务,将事件写入saga log,并将请求发送到远程服务

在上文中,我谈到了ServiceComb下的Saga是怎么设计的。 然而,业界还有其他数据一致性解决方案,如两阶段提交(2PC)和Try-Confirm / Cancel(TCC)。那saga相比之下有什么特别?

两阶段提交 Two-Phase Commit (2PC)

两阶段提交协议是一种分布式算法,用于协调参与分布式原子事务的所有进程,以保证他们均完成提交或中止(回滚)事务。

2PC包含两个阶段:

  • 投票阶段 协调器向所有服务发起投票请求,服务回答yes或no。如果有任何服务回复no以拒绝或超时,协调器则在下一阶段发送中止消息。
    这里写图片描述

  • 决定阶段 如果所有服务都回复yes,协调器则向服务发送commit消息,接着服务告知事务完成或失败。如果任何服务提交失败, 协调器将启动额外的步骤以中止该事务。
    这里写图片描述

在投票阶段结束之后与决策阶段结束之前,服务处于不确定状态,因为他们不确定交易是否继续进行。当服务处于不确定状态并与协调器失去连接时, 它只能选择等待协调器的恢复,或者咨询其他在确定状态下的服务来得知协调器的决定。在最坏的情况下, n个处于不确定状态的服务向其他n-1个服务咨询将产生O(n2)个消息。

另外,2PC是一个阻塞协议。服务在投票后需要等待协调器的决定,此时服务会阻塞并锁定资源。由于其阻塞机制和最差时间复杂度高, 2PC不能适应随着事务涉及的服务数量增加而扩展的需要。

有关2PC实现的更多细节可参考2和3。

Try-Confirm/Cancel (TCC)

TCC也是补偿型事务模式,支持两阶段的商业模型。

  • 尝试阶段 将服务置于待处理状态。例如,收到尝试请求时,航班预订服务将为客户预留一个座位,并在数据库插入客户预订记录,将记录设为预留状态。 如果任何服务失败或超时,协调器将在下一阶段发送取消请求。
    这里写图片描述
  • 确认阶段 将服务设为确认状态。确认请求将确认客户预订的座位,这时服务已可向客户收取机票费用。数据库中的客户预订记录也会被更新为确认状态。 如果任何服务无法确认或超时,协调器将重试确认请求直到成功,或在重试了一定次数后采取回退措施,比如人工干预。
    这里写图片描述

与saga相比,TCC的优势在于,尝试阶段将服务转为待处理状态而不是最终状态,这使得设计相应的取消操作轻而易举。

例如,电邮服务的尝试请求可将邮件标记为准备发送,并且仅在确认后发送邮件,其相应的取消请求只需将邮件标记为已废弃。但如果使用saga, 事务将发送电子邮件,及其相应的补偿事务可能需要发送另一封电子邮件作出解释。

TCC的缺点是其两阶段协议需要设计额外的服务待处理状态,以及额外的接口来处理尝试请求。另外,TCC处理事务请求所花费的时间可能是saga的两倍, 因为TCC需要与每个服务进行两次通信,并且其确认阶段只能在收到所有服务对尝试请求的响应后开始。

有关TCC的更多细节可参考Transactions for the REST of Us.

事件驱动的架构

和TCC一样,在事件驱动的架构中,长活事务涉及的每个服务都需要支持额外的待处理状态。接收到事务请求的服务会在其数据库中插入一条新的记录, 将该记录状态设为待处理并发送一个新的事件给事务序列中的下一个服务。

因为在插入记录后服务可能崩溃,我们无法确定是否新事件已发送,所以每个服务还需要额外的事件表来跟踪当前长活事务处于哪一步。

这里写图片描述

一旦长活事务中的最后一个服务完成其子事务,它将通知它在事务中的前一个服务。接收到完成事件的服务将其在数据库中的记录状态设为完成。

这里写图片描述

如果仔细比较,事件驱动的架构就像非集中式的基于事件的TCC实现。如果去掉待处理状态而直接把服务记录设为最终状态,这个架构就像非集中式的基于事件的saga实现。 去中心化能达到服务自治,但也造成了服务之间更紧密的的耦合。假设新的业务需求在服务B和C之间的增加了新的流程D。在事件驱动架构下,服务B和C必须改动代码以适应新的流程D。
这里写图片描述
Saga则正好相反,所有这些耦合都在saga系统中,当在长活事务中添加新流程时,现有服务不需要任何改动。

更多细节可参考Event-Driven Data Management for Microservices.

集中式与非集中式实现

这个Saga系列的文章讨论的都是集中式的saga设计。但saga也可用非集中式的方案来实现。那么非集中式的版本有什么不同?
非集中式saga没有专职的协调器。启动下一个服务调用的服务就是当前的协调器。例如,

  • 服务A收到要求服务A,B和C之间的数据一致性的事务请求。
  • A完成其子事务,并将请求传递给事务中的下一个服务,服务B.
  • B完成其子事务,并将请求传递给C,依此类推。
  • 如果C处理请求失败,B有责任启动补偿事务,并要求A回滚。
    这里写图片描述

与集中式相比,非集中式的实现具有服务自治的优势。但每个服务都需要包含数据一致性协议,并提供其所需的额外持久化设施。

我们更倾向于自治的业务服务,但服务还关联很多应用的复杂性,如数据一致性,服务监控和消息传递, 将这些棘手问题集中处理,能将业务服务从应用的复杂性中释放,专注于处理复杂的业务,因此我们采用了集中式的saga设计。

另外,随着长活事务中涉及的服务数量增长,服务之间的关系变得越来越难理解,很快便会呈现下图的死星形状。
这里写图片描述
图片来源: http://www.slideshare.net/BruceWong3/the-case-for-chaos (s12)

同时,在长活事务中定位问题也变得更加复杂,因为服务日志遍布群集节点。

Summary

本文将saga与其他数据一致性解决方案进行了比较。Saga比两阶段提交更易扩展。在事务可补偿的情况下, 相比TCC,saga对业务逻辑几乎没有改动的需要,而且性能更高。集中式的saga设计解耦了服务与数据一致性逻辑及其持久化设施, 并使排查事务中的问题更容易。

References

https://en.wikipedia.org/wiki/Two-phase_commit_protocol
https://cs.nyu.edu/courses/spring03/G22.2631-001/lecture8.pdf
http://courses.cs.vt.edu/~cs5204/fall00/distributedDBMS/duckett/tpcp.html
https://www.infoq.com/presentations/Transactions-HTTP-REST
https://www.nginx.com/blog/event-driven-data-management-microservices/
Original Paper on Sagas by By Hector Garcia-Molina & Kenneth Salem
Original Paper on Sagas by By Hector Garcia-Molina & Kenneth Salem
Gifford, David K and James E Donahue, “Coordinating Independent Atomic Actions”, Proceedings of IEEE COMPCON, San Francisco, CA, February, 1985
Chris Richardson: http://www.chrisrichardson.net/
ServiceComb Saga Project: https://github.com/ServiceComb/saga
ServiceComb官方网站
ServiceComb Github

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

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

相关文章

laydate点击输入框闪一下不见了_爱剪辑:如何制作抖音、苹果风格的快闪视频...

不知道大家有没有看过iPhone的宣传片&#xff0c;视频开头有几十秒的快闪字幕&#xff0c;当时视频一出来就有很多剪刀手求教程&#xff0c;因为这个效果不仅酷炫&#xff0c;用途还很广&#xff0c;可以用于&#xff1a;日常生活介绍、产品介绍、搞笑段子......今天就来教大家…

C++和Lua交互教程(基于LuaBridge)

作者&#xff1a;查志旺 &#xff0c;向日葵远程控制软件前端开发工程师。 最近公司需要做向日葵远程控制软件跨平台项目&#xff0c;为了代码的可复用性&#xff0c;需嵌入跨平台脚本语言&#xff0c;我们选择了Lua&#xff0c;理由是Lua由标准C编写而成&#xff0c;几乎在所有…

与Serverless 的第一次亲密接触

Servrless概念 Serverless 是一个架构上的概念&#xff0c;从字面上理解就是无服务器架构。Serverless最初是用于描述依赖第三方服务实现对逻辑和状态进行管理的应用&#xff0c;典型的例子是单页 Web 和移动 App 这种富客户端应用&#xff0c;他们一般都使用基于云端的数据库…

eclipse把tomcant用到一个项目里_聊一个镜头工艺里容易被忽略,但很重要的项目...

在不改换门庭的情况下&#xff0c;一颗镜头一般都会伴随大家使用很长一段时间&#xff0c;也相信大多数人都遇到过剐蹭镜头前组的情况&#xff0c;这时候最容易引发的担忧就是“伤着镀膜了么&#xff1f;会不会影响成像效果&#xff1f;”其实换个角度来看&#xff0c;这个问题…

华为手机怎么看图片属性_华为手机怎么才能息屏显示时间?操作方法很简单,看完涨知识了...

现如今大家几乎都是手机不离身&#xff0c;甚至有些朋友机不离手。所以已经比较少人&#xff0c;会因为看时间而佩戴手表了&#xff0c;毕竟只要按下电源键就可以看时间了。其实现在的很多手机&#xff0c;不用亮屏也能看时间&#xff0c;下面我们就一起来看看是如何设置的吧。…

开源神器,无需一行代码就能搞定机器学习,不会数学也能上手

对于机器学习和数据科学的初学者来说&#xff0c;最大的挑战之一是需要同时学习太多知识&#xff0c;特别是如果你不知道如何编码。你需要快速地适应线性代数、统计以及其他数学概念&#xff0c;并学习如何编码它们&#xff0c;对于新用户来说&#xff0c;这可能会有点难以承受…

OpenStack不行了吗?悉尼峰会,OpenStack的白城反击战?

作者&#xff1a;李开&#xff0c;九州云99Cloud联合创始人&副总裁。 11月悉尼的春天忽然变得阴冷潮湿&#xff0c;和第一天抵达时候的风和日丽大相径庭&#xff0c;海风推动着飘忽的乌云&#xff0c;有点电影《魔戒》里黑暗军团压境的味道。 由于早上不小心睡过了头&…

档案盒正面标签制作_错题本科学制作方法、正确使用方式及窍门

错题本的作用不用多说&#xff0c;重点是如何制作错题本以及正确使用方法。小编整理了以下制作错图集的正确步骤&#xff0c;希望对大家有用。一、制作错题本的简单步骤步骤1&#xff1a;把所有的练习册和试卷找出来;分学科按学期顺序整理;以学年或学期为单位装订在一起&#x…

android触摸效果,Android UI实现单行文本水平触摸滑动效果

本文实例为大家分享了单行文本水平触摸滑动效果&#xff0c;通过EditText实现TextView单行长文本水平滑动效果。下一篇再为大家介绍 多行文本折叠展开效果&#xff0c;自定义布局View实现多行文本折叠和展开。1.初衷最近做应用的时候有用到TextView单行长文本&#xff0c;当文本…

机器人爱因斯坦、索菲亚对话人类:“人类必须自我修复”

人工智能的发展速度真的太快了&#xff0c;就在不久前机器人“索菲亚”获得沙特阿拉伯获得了公民身份之后&#xff0c;机器人版的爱因斯坦教授也登上了历史的舞台。 机器人版的爱因斯坦教授是由汉森机器人(Hanson Robotics)公司制造的&#xff0c;这家机器人制造商制造的另外一…

hook控制浏览器的方法_Java-Hook技术-入门实践+反射、动态代理、热修复再看看

延续之前的MonkeyLei&#xff1a;Android-模块化、组件化、插件化、热修复-插件化-起个头&#xff0c;我们复习下里面的关于反射和动态代理点的知识。然后尝试简单了解下Hook...看之前文章&#xff0c;记得多复习下反射代理&#xff0c;比如使用这些....&#xff1a;public cla…

vscode 头文件包含问题_使用clangd替代c/c++配置vscode c++项目

背景&#xff1a;最近从Clion切换到了vscode来进行代码开发&#xff0c;发现vscode自带的c/c插件除了能够使用debug功能&#xff0c;其余代码补全&#xff0c;跳转等功能都和基于clangd的clion有较大差距&#xff0c;经常出现匹配不上或者跳转不准确的问题&#xff0c;在这背景…

微服务拆分需要考虑的必要因素与坚持原则

前言&#xff1a;创业公司往往因为有限的时间和投入&#xff0c;把系统所有的功能都聚集在一起。随着业务的不断发展&#xff0c;技术人员开始不断地对架构进行解耦和拆分。微服务在最近几年大行其道&#xff0c;很多公司的研发人员都在考虑微服务架构&#xff0c;或者在做微服…

data后缀文件解码_Java语法进阶13-文件、IO流

FileFile是文件和目录路径名的抽象表示形式&#xff0c;即File类是文件或目录的路径&#xff0c;而不是文件本身&#xff0c;因此File类不能直接访问文件内容本身&#xff0c;如果需要访问文件内容本身&#xff0c;则需要使用输入/输出流。File类的对象用来表示文件和文件夹的对…

中小型互联网公司微服务实践-经验和教训

上次写了一篇文章叫Spring Cloud在国内中小型公司能用起来吗?介绍了Spring Cloud是否能在中小公司使用起来&#xff0c;这篇文章是它的姊妹篇。其实我们在这条路上已经走了一年多&#xff0c;从16年初到现在。在使用Spring Cloud之前我们对微服务实践是没有太多的体会和经验的…

对于机器学习,到底该选择哪种编程语言

开发者到底应该学习哪种编程语言才能获得机器学习或数据科学这类工作呢&#xff1f;这是一个非常重要的问题。我们在许多论坛上都有讨论过。现在&#xff0c;我可以提供我自己的答案并解释原因&#xff0c;但我们先看一些数据。毕竟&#xff0c;这是机器学习者和数据科学家应该…

android如何适配平板,适用于平板电脑、大屏设备和可折叠设备的自适应布局

将应用支持扩展到更大屏幕的设备(例如平板电脑、可折叠设备和 Chrome 操作系统设备)是扩大您的覆盖面和互动度的绝佳方式。平板电脑的增长率比去年同期 (YOY) 大幅增长了 30%&#xff0c;Chrome 操作系统设备比去年同期增长了 92%。我们还发现&#xff0c;用户使用平板电脑的时…

对于机器学习,到底该选择哪种编程语言?

开发者到底应该学习哪种编程语言才能获得机器学习或数据科学这类工作呢&#xff1f;这是一个非常重要的问题。我们在许多论坛上都讨论过这个问题。今天&#xff0c;我将给出我自己的答案并解释其中原因&#xff0c;但我们首先看一些数据。毕竟&#xff0c;这是机器学习者和数据…

置顶图片代码加链接html,css图片怎么加链接?

css可以通过在图片前加标签实现为图片加链接&#xff0c;语法&#xff1a;。使用此方法引入图片即可为图片添加链接。设定图片超链接&#xff0c;用css样式来实现&#xff0c;其实就是给这个图片的块添加超链接&#xff0c;我们可以通过在这个块的前面添加一个a标签来实现&…

linux上修改html,linux进程名修改

如何修改 Linux 中的进程名在编写网络服务器程序时&#xff0c;为了响应客户端的请CSS布局HTML小编今天和大家分享&#xff0c;我们经常需要新建进程来处理业务流程&#xff1b;而且又是为了关闭某个非法请CSS布局HTML小编今天和大家分享或者关闭长连接的客户端&#xff0c;这时…