所以把第二章的内容,摘抄了下来。
**文件夹分层法
:**应用分层采用文件夹方式的优点是可大可小、简单易用、统一规,以满足所有业务应用的多种不同场景。
**调用规约
:**在开发过程中,需要遵循分层架构的约束,禁止跨层次的调用。
**下层为上层服务
:**以用户为中心,以目标为导向, 上层(业务逻辑层) 需要什么,下层(数据访问层)就提供什么,而不是下层(数据访问层)有什么,就向上层(业务逻辑层〉提供什么。
**实体层规约
:**Entity 是数据表对象,不是数据访问层对象, DTO 是网络传输对象,不是表现层对象; BO是内存计算逻辑对象,不是业务逻辑层对象,不是只能给业务逻辑层使用。如果仅限定在本层访问,则导致单个应用内大量没有价值的对象转换。 以用户为中心来设计实体类,可以减少无价值重复对象和无用转换。
**U型访问
:**下行时表现层是input,业务逻辑层是Process,数据访问层是output。上行时数据访问层是input,业务逻辑层是process,表现层是output。
接口调用如果是远程调用 ,那么就构成了简单的分布式。最简单的远程接口实现方式是Web Service 或者REST 。当然 一个合理的分布式应用不仅仅是远程接口这么简单, 还需要有负载均衡、缓存等功能。实现分布式最简单的技术是 REST 接口,因为 REST 接口可以使用现存的各种服务器,比如使用负载均衡服务器和缓存服务器来实现负载均衡和缓存功能。
关于通信协议,不同的公司有不同的选择,但是建议同一公司内部使用统一的通信协议,比较典型的有GRPC和BRPC。
服务间的通信通过轻量级的 Web 服务,使用同步的 REST API 进行通信。在实际的项目应用中, 一般推荐在查询是时候使用同步机制,在增删改的时候,使用异步的方式,结合消息队列来实现数据的操作,以保证最终的数据一致性。
REST API 应为创建、检索、更新和删除操作,使用标准HTTP 动词, 而且应该特别注意操作是否幂等性等。
请求形式 | 说明 | 操作类型 | 样例 | 幂等性 |
---|---|---|---|---|
GET | 必须是幂等的且不会产生意外结果。具体来讲,带有查询参数的GET请求不应用于更改或更新信息( 应使POST、PUT、PATCH)。 | 查询 | emp/{id} | 是 |
POST | 可用于创建资源,POST 操作的明显特征是,它不是幕等的。举例说明:如果使用POST请求创建资源,而且启动该请求多次,那么每次调用后都会 创建一个新的唯一资源。 | 新增 | emp | 否 |
PUT | 可用于更新资源。 PUT 操作通常包含要更新的资源的完整副本, 使该操作具有幂等性。 | 修改 | emp/{id} | 是 |
DELETE | 用于删除资源。删除操作是幂等性的,因为资源只能删除一次。但是,返回代码不同,因为第一次操作将成功( 200 ),而后续调用不会找到资源(204 )。 | 删除 | emp/{id} | 是 |
PATCH | 允许对资源执行部分更新。它不一定是幕等的,具体取决于如何指定增量并应用到资源上。例如,如果一个 PATCH 操作表明一个值应从A改为B,那么它就是幕等的。如果它已启动多次而且值己是B ,则没有任何效果。对 PATCH 操作的支持仍不一致。 一般用PUT。 | 部分修改 | 是 |
设计原则很重要的一点就是简单,单一职责也就是我们经常所说的专人干专事。一个单元(一 个类、函数或者微服务〉应该有且只有一个职责。无论如何,一个微服务不应该包含多于一个的职责。职责单一的后果之一就是职责单位(微服务、类、接口、函数)的数量剧增。据说 Amazon、Netflix 这些采用微服务架构的网站一个小功能就会调用几十上百个微服务。但是相较于每个函数都是多个业务逻辑或职责功能的混合体的情况,维护成本还是低很多的。
SRP(单一职责原则 )中的“单一职责”是一个比较模糊的概念。对于函数,它可能指单一的功能,不涉及复杂逻 ;但对于类或者接 口,它可能是指对单一对象的操作,也可能是指对该对象单一属性的操作。总而言之,单一职责原则就是为了让代码逻辑更加清晰,可维护性更好,定位问题更快的一种设计原则。
单一职责的优点如下:
实施单一职责的目的如下:
拆分粒度不应该过分追求细粒度,要考虑适中,不能过大或过小。按照单一职责原则和康戚定律,在业务域、团队和技术上平衡粒度。拆分后的代码应该是易控制、易维护的,业务职责也是明确单一的。
AKF 扩展立方体是一个叫 AKF 公司的技术专家抽象总结的应用扩展的三个维度 。理论上按照这三个扩展模式,可以将一个单体系统进行无限扩展。 AKF 扩展立方如图所示:
三个维度的扩展对比比如表所示:
维度 | 优点 | 缺点 | 场景 |
---|---|---|---|
x轴 | 成本最低,实施简单 | 受指令集多少盒数据集大小的约束。当单个产品或者应用过大的时,服务响应变慢,无法通过x轴的水平扩展提高数据 | 发展初期,业务复杂度低,需要增加系统容量 |
y轴 | 可以解决指令集和数据集的约束,解决代码复杂度问题,可以实现隔离故障,可以提高响应时间,可以使团队聚焦更利于团队成长 | 成本相对较高 | 业务复杂,数据量大,代码耦合度高,团队规模大 |
z轴 | 能解决数据集的约束,降低故障风险,实现渐进交付,可以带来最大的扩展性 | 成本最昂贵,且不一定能解决指令集的问题 | 用户指数级快速增长 |
下面看一下 AKF 的拆分实践。
拆分应用:
拆分数据库:
要做好微服务的分层:梳理和抽取核心应用、公共应用,作为独立的服务下沉到核心和公共能力层,逐渐形成稳定的服务中心,使前端应用能更快速地响应多变的市场需求。
对于服务的拆分,要使用迭代演进的方式,不能一次性完成所有的服务的拆分,需要确保团队可接受,粒度适中 ,同时需要优先考虑 API 的版本兼容性。不能够单纯以代码量来对服务拆分的成果进行评估。
在传统的 Web 应用开发中,大多数的程序员会将浏览器作为前后端的分界线。将浏览器中为用户进行页面展示的部分称之为前端,而将运行在服务器,为前端提供业务逻辑和数据准备的所有代码统称为后端。
由于前后端分离这个概念相对来说刚出现不久,很多人都是只闻其声,不见其形,所以可能会对它产生一些误解,误以为前后端分离只是一种Web 应用开发模式,只要在 Web 应用的开发期进行了前后端开发工作的分工,就是前后端分离。
其实前后端分离并不只是开发模式,而是 Web应用的一种架构模式。在开发阶段,前后端工程师约定好数据交互接口,实现并行开发和测试;在运行阶段前后端分离模式需要对 Web应用进行分离部署,前后端之前使用HTTP 或者其他协议进行交互请求。
前后端分离原则,简单来讲就是前端和后端的代码分离,也就是技术上做分离。推荐的模式是直接采用物理分离的方式部署,进一步进行更彻底的分离。不要继续使用以前的服务端模板技术,比如 JSP ,把 Java JS HTML CSS 都堆到一个页面里,稍复杂的页面就无法维护。前后端分离意味着前后端之间使用 SON 来交流,两个开发团队之间使用 API 作为契约进行交互。
这种分离模式的方式有几个好处:
在团队的内部,尤其是 API 设计优先的微服务架构中,接口的版本管理显得尤其重要。
微服务的一个主要优势是, 允许服务独立的演变。考虑到微服务会调用其他服,这种独立性需要引起高度注意,不能在 API 中制造破坏性更改。
接纳更改的最简单方法是绝不破坏 API 如果遵循稳健性原则,而且两端都保守地发送数据,慷慨地接收数据,那么可能很长一段时间都不需要执行破坏性更改。当最终发生破坏性更改时,可以选择构建 一个完全不同的服务并不断退役原始服务,原因可能是领域模型己进化,而且一种更好的抽象更有意义。
如果对现有服务执行破坏性的 API 更改,应决定如何管理这些更改:
在确定了困难部分后,如何在 API 反映该版本是更容易解决的问题。通常可通过3种式处理阻REST 资源的版本控制。
将版本放入URI
:这是指定版本的最简单方法。它的优点是非常容易理解,容易在微服务应用构建服务时实现,与 API 浏览工具和命令行工具兼容。如果将版本放在URL中,版本应该会应用于整个应用程序,所以使用/api/v1/accounts 而不是/api/accounts/v1。
使用自定义请求标头
:可以添加自定义请求标头来表明 API 版本。在将流量路由到特定后端实例时,路由器和其基础架构可能会考虑使用自定义标头。但是,此机制不容易使用,原因与 Accept 标头不容易使用相同 此外,它是一个仅适用于应用程序的自定义标头,这意味着使用者需要学习如何使用它。
将版本放在 HTTP Accept 标头中,并依靠内容协商
:Accept 标头是一个定义版本的明显位置,但它是最难测试的地方之一。 URI 很容易指定和替换,但指定HTTP 标头需要更详细的 API 和命令行调用。
正所谓“不围绕业务构建的架构就是耍流氓”。
微服务应当聚焦于某一特定的业务功能,并且确保完成它。其实这给需求管理也带来了挑战,需求需要切分将更加精细,以满足系统业务的不断变化。在传统的方式中,一般是产品人员进行需求调研,然后经过整理后提交给开发团队,这种方式在微服务的环境下需要重新定义,即产品核实需求后,需要在提交给开发团队之前进行评审,评审需要开发团队的人员参与,确认无误后再提交给开发团队。
从技术上说,微服务不应该局限于某个技术核或者后端存储,可以非常灵活,以便于解决业务问题。这一点在非微服务的系统设计时,可能导致我们做一些妥协。而微服务可以让你用你认为最合适的方式解决问题。这和上面的统一框架井不冲突,统一是指构建团队的过程中,尽量保持统一的架构,从而降低交互和沟通所带来的额外成本。
系统可以根据业务切分为不同的子系统,子系统又可以根据重要程度切分为核心和非核心子系统。切分的目的就是当出现问题时,保证核心业务模块正常运行,不影响业务的正常操作。同时解决各个模块子系统间的祸合、维护及拓展性。方便单独部署,确保当 个系统出现问题时,不 出现连锁反应而导致整个系统瘫痪。
各个系统间合理地使用消息队列,解决系统或模块间的异步通信,实现高可用、高性能的通信系统。
大流量一般的衡量指标就是系统的 TPS (每秒事务量)和 QPS (每秒请求量〉。其实这个没有一个绝对的标准,一般都是根据机器的配置情况而定的,如果当前配置不能应对请求量,那么就可以视为大流量了。
一般的应对方案包括:
应对并发,很重要的一点就是区分CPU 密集型和IO密集型。
CPU密集型(CPU-bound)
CPU密集型也叫计算密集型,指的是系统的硬盘、内存性能相对 CPU 要好很多。此时,系统运作大部分的状况是CPU Loading 100%, CPU 要读/写 I/O(硬盘/内存), I/O在很短的时间就可以完成,而 CPU 还有许多运算要处理, CPU Loading 很高。
CPU bound的程序一般而言 CPU 占用率相当高。这可能是因为任务本身不太需要访问I/O设备,也可能是因为程序是多线程实现,因此屏蔽掉了等待I/O的时间
I/O 密集型( I/O bound )
I/O密集型指的是系统的 CPU 性能相对硬盘、内存要好很多。 此时,系统运作大部分的状况是 CPU 在等 I/O (硬盘/内存)的读/写操作,此时 CPU Loading 并不高。
I/O bound 程一 般在达到性能极限时, CPU 占用率仍然较低。这可能是因为任务本身需要大I/O操作,而 pipeline 做得不是很好,没有充分利用处理器能力。
在微服务架构下,如果涉及不同类型的业务,需要根据资源的使用情况选用合适的处理资源。
CAP 原则又称 CAP 定理,指的是在一个分布式系统中, Consistency (一致性)
、 Availability(可用性)
和 Partition tolerance (分区容错性)
,三者不可兼得;
一致性(C):在分布式系统中的所有数据备份在同一时刻是否有同样的值。
可用性(A):在集群中一部分节点故障后 ,集群整体是否还能响应客户端的读写请求。
分区容忍性( P):以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。
掌握 CAP 定理,尤其是能够正确理解C、A、P的含义,对于系统架构来说非常重要。因为对于分布式系统来说,网络故障在所难免,如何在出现网络故障时,维持系统按照正常的行为逻辑运行就显得尤为重要,可以结合实际的业务场景和具体需求来进行权衡。
例如,对于大多数互联网应用来说,因为机器数量庞大,部署节点分散,网络故障是常态,可用性是必须要保证的,所以只有舍弃一致性来保证服务的 AP 。而对于银行等需要确保一致性的场景,通常会权衡 CA和CP 模型, CA 模型网络故障时完全不可用,CP 模型具备部分可用性。所以,在设计微服务的时候一定要选择合适的模型。
CA ( consistency + availability ),这样的系统关注一致性和可用性,它需要非常严格的全体一致的协议,比如“两阶段提交” (2PC)。 CA 系统不能容忍网络错误或节点错误, 一旦出现这样的问题,整个系统就会拒绝写请求,因为它并不知道对面的那个节点是否挂掉了,还是只是网络问题。唯一安全的做法就是把自己变成只读的。
CP ( consistency + partition tolerance),这样的系统关注一致性和分区容忍性。它关注的是系统里大多数人的一致性协议,比如 Paxos 算法( Quorum类的算法)。这样的系统只需要保证大多数节点数据一致,而少数的节点会在没有同步到最新版本的数据时变成不可用的状态,这样能够提供一部分的可用性。
AP ( availability + partition tolerance ),这样的系统关心可用性和分区容忍性。因此,这样的系统不能达成一致性,需要给出数据冲突,给出数据冲突就需要维护数据版本,Dynamo就是这样的系统
EDA是一种以事件为媒介,实现组件或服务之间最大松耦合的方式。传统面向接口编程以接口为媒介,实现调用接口者和接口实现着之间的解耦,但是这种解耦成都不是很高,如果接口发生变化,则双方代码都需要变动,而时间驱动是调用者和被调用者互不知道对方,两者只和中间消息队列耦合。
在事件驱动的架构中,跨服务完成业务逻辑的一个关键点是每个服务自动更新数据库和发布事件, 也就是要以原子粒度更新数据库和发布事件。
保证数据更新与事件发布原子化的方法有以下几种:
使用本地事务发布事件
:一个实现原子化的方法是使用本地事务来更新业务实体和事件列表,由一个独立进程来发布事件。使用单独的事件表记录事件状态,然后使用单独的进程来监控事件的变化情况,确保事件实时发布。这种方式的缺点是数据更新和事件之间的对应关系是由开发者实现的,极有可能出错。
挖掘数据库事务日志
:由线程或者进程通过挖掘数据库事务或提交日志来发布事件。应用更新数据库,数据库的事务日志会记录这些变更。事务日志挖掘线程或进程读取这些日志,并把事件发布到消息队列。这种方式的优点是不需要开发人员参与,缺点是与数据库紧耦合,需要根据数据库的变化而变化, 另外根据数据库日志不一定能推断出所有的业务场景。
使用事件源
:事件源采用一种截然不同的、以事件为中心的方法来保存业务实体一一不同于存储实体的当前状态,应用存储的是状态改变的事件序列。每当业务实体的状态改变,新事件就被附加到事件列表,并且应用可以通过事件回放来重构实体的当前状态。事件长期保存在事件仓库( Event Store ),使用 API 添加和检索实体的事件。通过 API 让服务订阅事件,将所有事件传达到所有感兴趣的订阅者。所以,事件仓库可以认为是数据库与消息代理的综合体。缺点是想要构建一个高效的事件仓库并不容易。
CQRS 是命令查询责任分离。
CQRS 架构由于本身只是 一个读写分离的思想,实现方式多种多样。 比如数据存储不分离,仅仅只是代码层面读写分离,也是 QRS 的体现;数据存储的读写分离,C 端负责数据存储,Q端负责数据查询,Q 端的数据通过C端产生的 Event 来同步,这种也是 CQRS 架构的一种实现。
传统架构的数据一般是强一致性的,我们通常会使用数据库事务保证一次操作的所有数据修改都在一个数据库事务里,从而保证了数据的强一致性 。在分布式的场景中,我们也同样希望数据的强一致性,就是使用分布式事务。众所周知,分布式事务的难度、成本是非常高的,而且采用分布式事务的系统的吞吐量都会比较低,系统的可用性也会比较低。所以,很多时候,们也会放弃数据的强一致性,而采用最终一致性;从 CAP 定理的角度来说,就是放弃一致性,选择可用性。
CQRS 架构则完全秉持最终一致性的理念。这种架构基于一个很重要的假设,就是用户看到的数据总是旧的。对于一个多用户操作的系统,这种现象很普遍。比如秒杀的场景,下单前,也许界面上显示的商品数量是有的,但是当你下单时,系统提示商品卖完了。其实我们只要仔细想想 ,也确实如此。因为我们在界面上看到的数据是从数据库取出来的,一旦显示到界面上就不会变了。但是很可能其他人己经修改了数据库中的数据。这种现象在大部分系统中,尤是高并发的 Web 系统很常。所以,基于这样的假设,我们知道,即便系统做到了数据的强一致性,用户还是很可能会看到旧的数据。这就给我们设计架构提供了一个新的思路;我们能否这样做:只需要确保系统的一切添加、删除和修改操作所基于的数据是最新的 ,而查询的数据不必是最新的。
将一个微服务分为命令端、查询端和事件处理器,这 个部分可以相互独立地部署。
命令端
:请求采取命令的形式,可以驱动对微服务所拥有的领域数据的状态更改。
事件处理器
:事件处理器可通过很多有用的方式对新的领域事件做出响应。一个领域事件可以生成多个
件,这些事件可以发送到其他微服务
查询端
:查询端将提供 REST API ,允 HTTP 客户端读取从己处理事件生成的实体化视图。
微服务准备和构建的基础设施也是一个非常重要的需求。
基础设施应该包括提供给业务相关的应用所有基础保障的服务和设施,比如:
一个服务应当被独立部署,并且包含所有的依赖、环境等物理资源。
服务足够小,功能单一,可以独立打包、部署、升级,不依赖其他服务,实现了局部自治,这就是我们应用架构的演进,从耦合到微服务,便于管理和服务的治理。
在传统的开发中,我们构建一个WAR 包或 EAR 包,然后把它们部署在容器上。而在一个规范的微服务中,每个微服务应该被构建成 JAR,其中内置了所有的依赖,然后作为一个单独的 Java 进程存在。
自动化的原则包括:
数据 致性分为以下几种情况:
强一致性
:当更新操作完成之后,任何多个后续进程或者线程的访问都会返回最新的更新过的值。这种是对用户最友好的,就是用户上一次写什么,下一次就保证能读到什么。根据 CAP 理论,这种实现需要牺牲可用性。弱一致性
:系统并不保证后续进程或者线程的访问都会返回最新的更新过的值。系统在数据写入成功之后,不承诺立即可以读到最新写入的值,也不会具体地承诺多久之后可以读到。最终一致性
:弱一致性的特定形式。系统保证在没有后续更新的前提下,系统最终返回上一次更新操作的值。在没有故障发生的前提下,不一致窗口的时间主要受通信延迟、系统负载和复制副本的个数影响。 DNS是一个典型的最终一致性系统。在工程实践上,为了保障系统的可用性,系统大多将强一致性需求转换成最终一致性的需求,并通过系统执行幕等性来保证数据的最终一致性。
微服务架构下,完整交易跨越多个系统运行,事务一致性是一个极具挑战的话题。依据 CAP理论,必须在可用性( Avaliablitity 〕和一致性( Consistency )之间做出选择。我们认为在微服务架构下应选择可用性,然后保证数据的最终一致性。在实践中有三种模式:可靠事件模式、业务补偿模式和 TCC 模式。
可靠事件模式
:可靠事件模式属于事件驱动架构,当某件重要事情发生时,例如,更新一个业务实体,微服务会向消息代理发布一个事件,消息代理向订阅事件的微服务推送事件,当订阅这些事件的微服务接受此事件时,就可以完成自己的业务;业务补偿模式
:补偿模式使用一个额外的协调服务来协调各个需要保证一致性的工作、服务,协调服务按顺序调用各个工作服务,如果某个工作服务调用失败,则撤销之前所有己经完成的工作服。 要求需要保证一致性的工作服务提供补偿操作。TCC 模式
:一个完整的 TCC 业务,由一个主业务服务和若干个从业务服务组成,主业务服务发起并完成整个业务活动, TCC 模式要求从服务提供 个接口一一 Try(负责资源检查)、 Confirm (真正执行业务)和 Cancel (释放Try阶段预留的资源)。微服务的设计模式主要有以下几种:链式设计模式、聚合器设计模式、数据共享设计模式和异步消息控制模式。
链式设计模式:链式设计模式是常见的一种设计模式,用于微服务之间的调用,应用请求通过网关到达第一个微服务,微服务经过基础业务处理,发现不能满足要求,继续调用第二个服务,然后将多个服务的结果统一返回到请求中,如图所示:
聚合器设计模式:聚合器设计模式是将请求统一由网关路由到聚合器,聚合器向下路由到指定的微服务中获取结果,并且完成聚合,如图所示:
数据共享模式:数据共享模式也是微服务设计模式的一种。应用通过网关调用多个微服务,微服务之间的数据共享通过同一个数据库,这样能够有效地减少请求次数,并且对于某些数据量小的情况非常适合,如图所示:
异步消息设计模式:异步消息设计模式看起来跟聚合器的设计方式很像,唯一的区别就是网关和微服务之间的通信是通过消息队列而不是通过聚合器的方式来进行的,采用的是异步交互的方式,如图所示:
DevOps一词来自Development和Operations的组合,突出重视软件开发人员和运维人员的沟通合作,通过自动化流程来使得软件构建、测试、 发布更加快捷、频繁和可靠。它要求开发、测试、运维进行一体化的合作,进行更小、更频繁、更自动化的应用发布,以及围绕应用架构来构建基础设施的架构。这就要求应用充分内聚,也方便运维和管理。这个理念与微服务理念不谋而合。
DevOps 的出现是为了填补开发端和运维端之间的信息鸿沟,改善团队之间的协作关系。不过需要澄清的一点是,从开发到运维,中间还有测试环节。DevOps 其实包含了三个部分:开发、测试和运维。
换句话说, DevOps 希望做到的是软件产品交付过程中 IT 工具链的打通,使得各个团队减少时间损耗,更加高效地协同工作。
那么如何来评估 DevOps 能力呢?可以通过能力环来对环境进行整体评估。 DevOps 能力环如图所示:
无尽头的可能性: DevOps 涵盖了代码、部署目标的发布和反馈等环节,闭合成一个完整的DevOps 能力闭环。良好的闭环可以大大增加整体的产出。
对于无状态服务,首先说一下什么是状态:如果一个数据需要被多个服务共享,才能完成一笔交易,那么这个数据被称为状态 进而依赖这个“状态”数据的服务被称为有状态服务,反之称为无状态服务。
无状态服务原则并不是说在微服务架构里就不允许存在状态,其表达的真实意思是要把有状态的业务服务改变为无状态的计算类服务,那么状态数据也就相应地迁移到对应的“有状态数据服务”中。
场景说明:例如我们以前在本地内存中建立的数据缓存、 Session 缓存,到现在的微服务架构中就应该把这些数据迁移到分布式缓存中存储,让业务服务变成一个无状态的计算节点。迁移后,就可以做到按需动态伸缩,微服务应用在运行时动态增删节点,就不再需要考虑缓存数据如何同步的问题。
无状态服务( Stateless Service )对单次请求的处理,不依赖其他请求,也就是说,处理一次请求所需的全部信息,要么都包含在这个请求里,要么可以从外部获取到(比如数据库),服务器本身不存储任何信息。Server 要设计为无状态的。
对服务器程序来说,究竟是有状态服务,还是无状态服务,其判断依旧是指两个来自相同发起者的请求在服务器端是否具备上下文关系。如果是状态化请求,那么服务器端一般都要保存请求的相关信息,每个请求可以默认地使用以前的请求信息。而对于无状态请求,服务器端所能够处理的过程必须全部来自请求所携带的信息,以及其他服务器端自身所保存的,并且可以被所有请求所使用的公共信息。
无状态的服务器程序,最有名的就是 Web务器。每次 HTTP 请求和以前都没有关系,只是获取目标url。得到目标内容之后,这次连接就被“杀死”,没有任何痕迹。在后来的发展进程中,逐渐在无状态化的过程中,加入状态化的信息,比如 Cookie 。服务端在响应客户端的请求时,会向客户端推送一个 Cookie ,这个 Cookie 记录服务端上面的一些信息。客户端在后续的请求中 ,可以携带这个Cookie ,服务端可以根据这个 Cookie 判断请求的上下文关系。 Cookie的存在,是无状态化向状态化的一个过渡手段,通过外部扩展手段, Cookie 来维护上下文关系。