精华内容
下载资源
问答
  • 2020-01-13 17:21:08

    (应用程序层攻击(也称为第7层攻击)可以是DoS或DDoS。 这些类型的攻击基于模仿人类与用户界面交互时的行为。

    目标协议通常是HTTP,HTTPS,DNS,SMTP,FTP,VOIP和其他应用程序协议,它们具有可利用的弱点,允许DoS攻击)

    针对应用层的DoS攻击有以下几种:

    1、HTTP Flood

          HTTP泛洪是针对应用程序层的最常见攻击。 它比网络层攻击更难检测,因为请求似乎是合法的。 由于三向握手已经完成,HTTP Flood 可用于欺骗仅检查第4层的防御设备和解决方案。这些类型的攻击由发送到目标服务器的HTTP GET或POST请求集组成。 通常,HTTP泛洪同时从多台计算机启动。

    2、DNS Flood

          域名系统(DNS)是用于将域名解析为IP地址的协议。与其他泛洪攻击一样,DNS泛洪攻击的目的是向DNS应用协议发送大量DNS请求。 DNS服务器不堪重负,无法处理来自其他用户的所有合法请求。

    3、Low and Slow Attacks

          与洪水不同,低速攻击和慢速攻击不需要大量的数据流量。 这些类型的攻击针对应用程序或服务器资源。由于流量似乎是按正常速率发生且合法,因此很难检测到。主要攻击是打开多个连接,并尽可能长地保持连接打开。针对这些连接发送部分HTTP请求,这些连接都不会完成。如果打开到服务器的连接足够多,则服务器将无法处理更多请求。

     

    更多相关内容
  • 通常企业级应用系统为提高系统可用性,会采用较昂贵的软硬件设备,如IBM的小型机及至中型机大型机及专有操作系统、Oracle数据库、EMC存储设备等。互联网公司更过的采用PC级服务器、开源的数据库和操作系统,这些廉价...

    高可用的网站架构

    通常企业级应用系统为提高系统可用性,会采用较昂贵的软硬件设备,如IBM的小型机及至中型机大型机及专有操作系统、Oracle数据库、EMC存储设备等。互联网公司更过的采用PC级服务器、开源的数据库和操作系统,这些廉价的设备在节约成本的同时也降低了可用性,特别是服务器硬件设备,低价的商业级服务器一年宕机一次是一个大概率事件,而那些高强度频繁读写的普通硬盘,损坏的概率则要更高一些。
    既然硬件故障是常态,网站的高可用架构设计的主要目的就是保证服务器硬件故障时服务依然可用、数据依然保存并能被访问。
    实现上述高可用架构的主要手段是数据和服务的冗余备份及失效转移,一旦某些服务器宕机,就将服务切换到其他可用的服务器上,如果磁盘损坏,则从备份的磁盘读取数据。
    一个典型的网站设计通常遵循如下图所示的基本分层架构模型。

    典型的分层模型是三层,即应用层、服务层、数据层;各层之间具有相对独立性,应用层主要负责具体业务逻辑处理;服务层负责提供可复用的服务;数据层负责数据的存储与访问。中小型网站在具体部署时,通常将应用层和服务层部署在一起,而数据层则另外部署,如下图所示(事实上,这也是网站架构演化的第一步)。

    在复杂的大型网站架构中,划分的粒度会更小、更详细,结构更加复杂,服务器规模更加庞大,但通常还是能够把这些服务器划分到这三层中。如下图所示。

    不同的业务产品会部署在不同的服务器集群上,如某网站的文库、贴吧、百科等属于不同的产品,部署在各自独立的服务器集群上,互不相干。这些产品又会依赖一些共同的复用业务,如注册登录、Session管理服务、账户管理服务等,这些可复用的业务服务也独自部署在独立的服务器集群上。至于数据层,数据库服务、文件服务、缓存服务、搜索服务等数据存储与访问服务都部署在各自独立的服务器集群上。

    大型网站的分层架构及物理服务器的分布式部署使得位于不同层次的服务器具有不同的可用性特点。关闭服务或者服务器宕机时产生的影响也不相同,高可用的解决方案也差异甚大。

    位于应用层的服务器通常为了应对高并发的访问请求,会通过负载均衡设备将一组服务器组成一个集群共同对外提供服务,当负载均衡设备通过心跳检测等手段监控到某台应用服务器不可用时,就将其从集群列表中剔除,并将请求分发到集群中其他可用的服务器上,使整个集群保持可用,从而实现应用高可用。
    位于服务层的服务器情况和应用层的服务层类似,也是通过集群方式实现高可用,只是这些服务器被应用层通过分布式服务调用框架访问,分布式服务调用框架会在应用层客户端程序中实现软件负载均衡,并通过服务注册中心对提供服务的服务器进行心跳检测,发现有服务不可用,立即通知客户端程序修改服务访问列表,剔除不可用的服务器。
    位于数据层的服务器情况比较特殊,数据服务器上存储着数据,为了保证服务器宕机时数据不丢失,数据访问服务不中断,需要在数据写入时进行数据同步复制,将数据写入多台服务器上,实现数据冗余备份。当数据服务器宕机时,应用程序将访问切换到有备份数据的服务器上。
    网站升级的频率一般都非常高,大型网站一周发布一次,中小型网站一天发布几次。每次网站发布都需要关闭服务,重新部署系统,整个过程相当于服务器宕机。因此网站的可用性架构设计不但要考虑实际的硬件故障引起的宕机,还要考虑网站升级发布引起的宕机,而后者更加频繁,不能因为系统可以接受偶尔的停机故障就降低可用性设计的标准。

    高可用的应用

    应用层主要处理网站应用的业务逻辑,因此有时也称作业务逻辑层,应用的一个显著特点是应用的无状态性。
    所谓无状态的应用是指应用服务器不保存业务的上下文信息,而仅根据每次请求提交的数据进行相应的业务逻辑处理,多个服务实例(服务器)之间完全对等,请求提交到任意服务器,处理结果都是完全一样的。

    通过负载均衡进行无状态服务的失效转移

    不保存状态的应用给高可用的架构设计带来了巨大便利,既然服务器不保存请求的状态,那么所有的服务器完全对等,当任意一台或多台服务器宕机,请求提交给集群中其他任意一台可用机器处理,这样对终端用户而言,请求总是能够成功的,整个系统依然可用。对于应用服务器集群,实现这种服务器可用状态实时监测、自动转移失效任务的机制是负载均衡。
    负载均衡,顾名思义,主要使用在业务量和数据量较高的情况下,当单台服务器不足以承担所有的负载压力时,通过负载均衡手段,将流量和数据分摊到一个集群组成的多台服务器上,以提高整体的负载成立能力。目前,不管是开源免费的负载均衡软件还是昂贵的负载均衡硬件,都提供失效转移功能。在网站应用中,当集群中服务是无状态对等时,负载均衡可以起到事实上高可用的作用,如下图所示。

    当Web服务器集群中服务器都可用时,负载均衡服务器会把用户发送的访问请求分发到任意一台服务器上进行处理,而当服务器10.0.0.1宕机时,负载均衡服务器通过心跳监测机制发现该服务器失去响应,就会把他从服务器列表中删除,而将请求发送到其他服务器上,这些服务器是完全一样的,请求在任何一台服务器中处理都不会影响最终的结果。
    由于负载均衡在应用层实际上起到了系统高可用的作用,因此即使某个应用访问量非常少,只用一台服务器提供服务就绰绰有余,但如果需要保证该服务高可用,也必须至少部署两台服务器,使用负载均衡技术构建一个小型的集群。

    应用服务器集群的Session管理

    应用服务器的高可用架构设计主要基于服务无状态这一特性,但是事实上,业务总是有状态的,在交易类的电子商务网站,需要有购物车记录用户的购买信息,用户每次购买请求都是向购物车中增加商品;在社交类的网站中,需要记录用户的当前登录状态、最新发布的消息及好友状态等,用户每次刷新页面都需要更新这些信息。
    Web应用中将这些多次请求修改使用的上下文对象称作会话(Session),单机情况下,Session可由部署在服务器上的Web容器(如JBoss)管理。在使用负载均衡的集群环境中,由于负载均衡服务器可能会将请求分发到集群任何一台应用服务器上,所以保证每次请求依然能够获得正确的Session比单机时要复杂很多。
    集群环境下,Session管理主要有以下几种手段。

    Session复制

    Session复制是早期企业应用系统使用较多的一种服务器集群Session管理机制。应用服务器开启Web容器的Session复制功能,在集群中的几台服务器之间同步Session对象,使得每台服务器上都保存所有用户的Session信息,这样任何一台机器宕机都不会导致Session数据的丢失,而服务器使用的Session时,也只需要在本机获取即可。如下图所示。

    这种方案虽然简单,从本机读取Session信息也很快速,但只能使用在集群规模比较小的情况下。当集群规模较大时,集群服务器间需要大量的通信进行Session复制,占用服务器和网络的大量资源,系统不堪负担。而且由于所有用户的Session信息在每台服务器上都有备份,在大量用户访问的情况下,甚至会出现服务器内存不够Session使用的情况。
    而大型网站的核心应用集群就是数千台服务器,同时在线用户可达千万,因此并不适合这种方案。

    Session绑定

    Session绑定可以利用负载均衡的源地址Hash算法实现,负载均衡服务器总是将来源于同一IP的请求分发到同一台服务器上,也可以根据Cookie信息将同一个用户的请求总是分发到同一台服务器上,当然这时负载均衡服务器必须工作在HTTP协议层上。这样在整个会话期间,用户所有的请求都在同一台服务器上处理,即Session绑定在某台特定服务器上,保证Session总能在这台服务器上获取。这种方法又被称作会话黏滞,如下图所示。

    但是Session绑定的方案显然不符合我们对系统高可用的需求,因为一旦某台服务器宕机,那么该机器上的Session也就不复存在了,用户请求切换到其他机器后因为没有Session而无法完成业务处理。因此虽然大部分负载均衡服务器都提供源地址负载均衡算法,但很少有网站利用这个算法进行Session管理。

    利用Cookie记录Session

    早期的企业应用系统使用C/S(客户端/服务端)架构,一种管理Session的方式是将Session记录在客户端,每次请求服务器的时候,将Session放在请求中发送给服务器,服务器处理完请求后再将修改过的Session响应给客户端。
    网站没有客户端,但是可以利用浏览器支持的Cookie记录Session,如下图所示。

    利用Cookie记录Session也有一些缺点,比如受Cookie大小限制,能记录的信息有限;每次请求响应都需要传输Cookie,影响性能;如果用户关闭Cookie,访问就会不正常。但是由于Cookie的简单易用,可用性高,支持应用服务器的线性伸缩,而大部分应用需要记录的Session信息又比较小。因此事实上,许多网站都或多或少的使用Cookie记录Session。

    Session服务器

    那么有没有可用性高、伸缩性好、性能也不错,对信息大小又没有什么限制的服务器集群Session管理方案呢?
    答案就是Session服务器。利用独立部署的Session服务器(集群)统一管理Session,应用服务器每次读写Session时,都访问Session服务器,如下图所示。

    这种解决方案事实上是将应用服务器的状态分离,分为无状态的应用服务器和有状态的Session服务器,然后针对这两种服务器的不同特性分别设计其架构。
    对于有状态的Session服务器,一种比较简单的方法是利用分布式缓存、数据库等,在这些产品的基础上进行包装,使其符合Session的存储和访问要求。如果业务场景对Session管理有比较高的要求,比如利用Session服务集成单点登录(SSO)、用户服务等功能,则需要开发专门的Session服务管理平台。

    高可用的服务

    可复用的服务模块为业务产品提供基础公共服务,大型网站中这些服务通常都独立分布式部署,被具体应用远程调用。可复用的服务和应用一样,也是无状态的服务,因此可以使用类似负载均衡的失效转移策略实现高可用的服务。
    除此之外,具体实践中,还有以下几点高可用的服务策略。

    分级管理

    运维上将服务器进行分级管理,核心应用和服务优先使用更好的硬件,在运维响应速度上也格外迅速。显然,用户及时付款购物比能不能评价商品更重要,所以订单、支付服务比评价服务有更多优先级。
    同时在服务部署上也进行必要的隔离,避免故障的连锁反应。低优先级的服务通过启动不同的线程或者部署在不同的虚拟机上进行隔离,而高优先级的服务则需要部署在不同的物理机上,核心服务和数据甚至需要部署在不同地域的数据中心。

    超时设置

    由于服务端宕机、线程死锁等原因,可能导致应用程序对服务端的调用失去响应,进而导致用户请求长时间得不到响应,同时还占用应用程序的资源,不利于及时将返回请求转移到正常的服务器上。
    在应用程序中设置服务调用的超时时间,一旦超时,通信框架就抛出异常,应用程序根据服务调度策略,可选择继续程重试或将请求转移到提供相同服务的其他服务器上。

    异步调用

    应用对服务的调用通过消息队列等异步方式完成,避免一个服务失败导致整个应用请求失败的情况。如提交一个新用户注册请求,应用需要调用三个服务:将用户信息写入数据库,发送账户注册成功邮件,开通对应权限。如果采用同步服务调用,当邮件队列阻塞不能发送邮件时,会导致其他两个服务也无法执行,最终导致用户注册失败。
    如果采用异步调用的方式,应用程序将用户注册信息发送给消息队列服务器后立即返回用户注册成功响应。而记录用户注册信息到数据库、发送用户注册成功邮件、调用用户服务开通权限这三个服务作为消息的消费者任务,分别从消息队列获取用户注册信息异步执行。及时邮件服务队列阻塞,邮件不能成功发送,也不会影响其他服务的执行,用户注册操作可顺利完成,只是晚一点收到注册成功的邮件而已。
    当然不是所有服务调用都可以异步调用,对于获取用户信息这类调用,采用异步方式会延长响应时间,得不偿失。对于那些必须确认服务调用成功才能继续下一步操作的应用不合适使用异步调用。

    服务降级

    在网站访问高峰期,服务可能因为大量的并发调用而性能下降,严重时可能会导致服务宕机。为了保证核心应用和功能的正常运行,需要对服务进行降级。降级有两种手段:拒绝服务及关闭服务。

    • 拒绝服务:拒绝低优先级应用的调用,减少服务调用并发数,确保核心应用正常使用;或者随机拒绝部分请求调用,解决资源,让另一部分请求得以成功,比卖你要死大家一起死的惨剧。貌似Twitter比较喜欢使用随机拒绝请求的策略,经常有用户看到请求失败的故障页面,但是问下身边的人,其他人都正常使用,自己再刷新页面,也好了。
    • 关闭功能:关闭部分不重要的服务,或者服务内部关闭部分不重要的功能,以解约系统开销,为重要的服务和功能让出资源。淘宝在每年的“双十一”促销中就使用这种方法,在系统最繁忙的时段关闭“评价”、“确认收货”等非核心服务,以保证核心交易服务的顺利完成。

    幂等性设计

    应用调用服务失败后,会将调用请求重新发送到其他服务器,但是这个失败可能是虚假的失败。比如服务已经处理成功,但因为网络故障应用没有收到响应,这时应用重新提交请求就导致服务重复调用,如果这个服务是一个转账操作,就会产生严重后果。
    服务重复调用是无法避免的,应用层也不需要关心服务是否真的失败,只要没有收到调用成功的响应,就可以认为调用失败,并重试服务调用。因此必须在服务层保证服务重复调用和调用一次产生的结果相同,即服务具有幂等性。
    有些服务天然具有幂等性,比如将用户性别设置为男性,不管设置多少次,结果都一样。但是对于转账交易等操作,问题就会比较复杂,需要通过交易编号等信息进行服务调用有效性检验,只有有效的操作才能继续执行。

    高可用的数据

    对许多网站而言,数据是其最宝贵的物质资产,硬件可以购买,软件可以重写,但是多年运营积淀下来的各种数据(用户数据、交易数据、商品数据......),代表着历史,已经成为过往,不能再重来,一旦失去,对网站的打击可以说是毁灭性的,因此可以说,保护网站的数据就是保护企业的命脉。
    不同于高可用的应用和服务,由于数据存储服务器上保存的数据不同,当某台服务器宕机的时候,数据访问请求不能任意切换到集群中其他的机器上。
    保证数据存储高可用的手段主要是数据备份和失效转移机制。数据备份是保证数据有多个副本,任意副本的失效都不会导致数据的永久丢失,从而实现数据完全的持久化。而失效转移机制则保证当一个数据副本不可访问时,可以快速切换访问数据的其他副本,保证系统可用。

    • 关于缓存服务的高可用,在实践中争议很大,一种观点认为缓存已经成为网站数据服务的重要组成部分,事实上承担了业务中绝大多数的数据读取访问服务,缓存服务失效可能会导致数据库负载过高而宕机,进而影响整个网站的可用性,因此缓存服务需要实现和数据存储服务同样的高可用。
    • 另一种观点认为,缓存服务不是数据存储服务,缓存服务器宕机引起缓存数据丢失导致服务器负载压力过高应该通过其他手段解决,而不是提高缓存服务本身的高可用。
    • 这里持后一种观点,对于缓存服务器集群中的单机宕机,如果缓存服务器集群规模较大,那么单机宕机引起的缓存数据丢失比例和数据库负载压力变化都较小,对整个系统影响也较小。扩大缓存服务器集群规模的一个简单手段就是整个网站共享同一个分布式缓存集群,单独的应用和产品不需要部署自己的缓存服务器,只需要向共享缓存集群申请缓存资源即可。并且通过逻辑或物理分区的方式将每个应用的缓存部署在多台服务器上,任何一台服务器宕机引起的缓存失效都只影响应用缓存数据的一小部分,不会对应用性能和数据库负载造成太大影响。

    CAP原理

    在讨论高可用数据服务架构之前,必须先讨论的一个话题是,为了保证数据的高可用,网站通常会牺牲另一个也很重要的指标:数据一致性。
    高可用的数据有如下几个层面的含义。

    数据持久性

    保证数据可持久存储,在各种情况下都不会出现数据丢失的问题。为了实现数据持久性,不但在写入数据时需要写入持久性存储,还需要将数据备份一个或多个副本,存放在不同的物理存储设备上,在某个存储故障或灾害发生时,数据不会丢失。

    数据可访问性

    在多份数据副本分别放在不同存储设备的情况下,如果一个数据存储设备损坏,就需要将数据访问切换到另一个数据存储设备上,如果这个过程不能很快完成(终端用户几乎没有感知),或者在完成过程中需要停止终端用户访问数据,那么这段时间数据是不可访问的。

    数据一致性

    在数据有多份副本的情况下,如果网络、服务器或者软件出现故障,会导致部分副本写入成功,部分副本写入失败。这就会造成各个副本之间的数据不一致,数据内容冲突。实践中,导致数据不一致的情形有很多种,表现形式也多种多样,比如数据更新返回操作失败,事实上数据在存储服务器已经更新失败。
    CAP原理认为,一个提供数据服务的存储系统无法同时满足数据一致性(Consistency)、数据可用性(Availibility)、分区耐受性(Patition Tolerance,系统具有跨网络分区的伸缩性)这三个条件,如下图所示。

    在大型网站应用中,数据规模总是快速扩张的,因此可伸缩性即分区耐受性必不可少,规模变大以后,机器数量也会变得庞大,这时网络和服务器故障会频繁出现,要想保证应用可用,就必须保证分布式处理系统的高可用性。所以在大型网站中,通常会选择强化分布式存储系统的可用性(A)和伸缩性(P),而在某种程度上放弃一致性(C)。一般说来,数据不一致通常出现在系统并发写操作或者集群状态不稳(故障恢复、集群扩容...)的情况下,应用系统需要对分布式数据处理系统的数据不一致性有所了解并进行某种意义上的补偿和纠错,以避免出现应用系统数据不正确。

    2012年淘宝“双十一”活动期间,在活动第一分钟就涌入了1000万独立用户访问,这种极端的高并发场景对数据处理系统造成了巨大压力,存储系统较弱的数据一致性导致出现部分商品超卖现象(交易成功的商品数超过了商品库存数)。

    CAP原理对于可伸缩的分布式系统设计具有重要意义,在系统设计开发过程中,不恰当的迎合各种需求,企图打造一个完美的产品,可能会使设计进入两难境地,难以为继。

    具体说来,数据一致性又可分为如下几点。

    • 数据强一致:各个副本的数据在物理存储中总是一致的;数据更新操作结果和操作响应总是一致的,即操作响应通知更新失败,那么数据一定没有被更细,而不是处于不确定状态。
    • 数据用户一致:即数据在物理存储中的各个副本的数据可能是不一致的,但是终端用户访问时,通过纠错和校验机制,可以确定一个一致的且正确的数据返回给用户。
    • 数据最终一致:这是数据一致性中较弱的一种,即物理存储的数据可能是不一致的,终端用户访问到的数据可能也是不一致的(同一用户连续访问,结果不同;或者不同用户同时访问,结果不同),但系统经过一段时间(通常是一个比较短的时间段)的自我恢复和修正,数据最终会达到一致。

    因为难以满足数据强一致性,网站通常会综合成本、技术、业务场景等条件,结合应用服务和其他的数据监控与纠错功能,使存储系统达到用户一致,保证最终用户访问数据的正确性。

    数据备份

    数据备份是一种古老而有效的数据保存手段,早期的数据备份手段主要是数据冷备,即定期将数据复制到某种存储介质(磁盘、光盘...)上并物理存档保管,如果系统存储损坏,那么就从冷备的存储设备中恢复数据。

    冷备的优点是简单和廉价,成本和技术难度都较低。缺点是不能保证数据最终一致,由于数据时定期复制,因此备份设备中的数据比系统中的数据陈旧,如果系统数据丢失,那么从上个备份点开始后更新的数据就会永久丢失,不能从备份中恢复。同时也不能保证数据可用性,从冷备存储中恢复数据需要较长的时间,而这段时间无法访问数据,系统也不可用。

    因此,数据冷备作为一种传统的数据保护手段,依然在网站日常运维中使用,同时在网站实时在线业务中,还需要进行数据热备,以提供更好的数据可用性。

    数据热备可分为两种:异步热备方式和同步热备方式。

    异步方式是指多份数据副本的写入操作异步完成,应用程序收到数据服务系统的写操作成功响应时,只写成本了一份,存储系统将会异步的写其他副本(这个过程有可能会失败)。如下图所示。

    在异步写入方式下,存储服务器分为主存储服务器(Master)和从存储服务器(Slave),应用程序正常情况下只连接主存储服务器,数据写入时,由主存储服务器的写操作代理模块将数据写入本机存储系统后立即返回写操作成功响应,然后通过异步线程将写操作数据同步到从存储服务器。

    同步方式是指多份数据副本的写入操作同时完成,即应用程序收到数据服务系统的写成功响应时,多份数据都已经写操作成功。但是当应用程序收到数据写操作失败的响应时,可能有部分副本或者全部副本都已经写成功了(因为网络或者系统故障,无法返回操作成功地响应),如下图所示。

    同步热备具体实现的时候,为了提高性能,在应用程序客户端并发向多个存储服务器同时写入数据,然后等待所有存储服务器都返回操作成功地响应后,再通过应用程序写操作成功。

    这种情况下,存储服务器没有主从之分,完全对等,更便于管理和维护。存储服务客户端在写多份数据的时候,并发操作,这意味着多份数据的总写操作延迟是响应最慢的那台存储服务器的响应延迟,而不是多台存储服务器响应延迟之和。其性能和异步热备方式差不多。

    传统的企业级关系数据库系统几乎都提供了数据实时同步备份的机制。而一开始就为大型网站而设计的各种NoSQL数据库(如HBase)更是将数据备份机制作为产品主要的功能点之一。

    关系数据库热备机制就是通常所说的Master-Slave同步机制。Master-Slave机制不但解决了数据备份问题,还改善了数据库系统的性能,实践中,通常使用读写分离的方法访问Slave和Master数据库,写操作只访问Master数据库,读操作只访问Slave数据库。

    失效转移

    若数据服务器集群中任何一台服务器宕机,那么应用程序针对这台服务器的所有读写操作都需要重新路由到其他服务器,保证数据访问不会失败,这个过程叫做失效转移。

    失效转移操作操作由三部分组成:失效确认、访问转移、数据恢复。

    生效确认

    判断服务器宕机是系统进行失效转移的第一步,系统确认一台服务器是否宕机的手段有两种:心跳检测和应用程序访问失败报告,如下图所示。

    对于应用程序的访问失败报告,控制中心还需要再一次发送心跳检测进行确认,以免错误判断服务器宕机,因为一旦进行数据访问失效转移,就意味着数据存储多份副本不一致,需要进行后续一系列复杂的操作。

    访问转移

    确认某台数据存储服务器宕机后,就需要将数据读写访问重新路由到其他服务器上。对于完全对等存储的服务器(几台存储服务器存储的数据完全一样,我们称几台服务器为对等服务器,比如主从结构的存储服务器,其存储的数据完全一样),当其中一台宕机后,应用程序根据配置直接切换到对等服务器上。如果存储是不对等的,那么就需要重新计算路由,选择存储服务器。

    数据恢复

    因为某台服务器宕机,所以数据存储的副本数目会减少,必须将副本的数目恢复到系统设定的值,否则,再有服务器宕机时,就可能出现无法访问转移(所有副本的服务器都宕机了),数据永久丢失的情况。因此系统需要从健康的服务器赋值数据,将数据副本数目恢复到设定值。不影响用户使用。

    展开全文
  • 应用层协议 ——— HTTP协议

    千次阅读 多人点赞 2022-05-15 20:18:31
    如果应用层不考虑下三层,在应用层自己的心目当中,它就可以认为自己是在和对方的应用层在直接进行数据交互。 下三层负责的是通信细节,而应用层负责的是如何使用传输过来的数据,两台主机在进行通信的时候,应用层...

    HTTP协议

    HTTP简介

    HTTP(Hyper Text Transfer Protocol)协议又叫做超文本传输协议,是一个简单的请求-响应协议,HTTP通常运行在TCP之上。

    在编写网络通信代码时,我们可以自己进行协议的定制,但实际有很多优秀的工程师早就已经写出了许多非常成熟的应用层协议,其中最典型的就是HTTP协议。

    认识URL

    URL(Uniform Resource Lacator)叫做统一资源定位符,也就是我们通常所说的网址,是因特网的万维网服务程序上用于指定信息位置的表示方法。

    一个URL大致由如下几部分构成:
    在这里插入图片描述

    一、协议方案名

    http://表示的是协议名称,表示请求时需要使用的协议,通常使用的是HTTP协议或安全协议HTTPS。HTTPS是以安全为目标的HTTP通道,在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性。

    常见的应用层协议:

    • DNS(Domain Name System)协议:域名系统。
    • FTP(File Transfer Protocol)协议:文件传输协议。
    • TELNET(Telnet)协议:远程终端协议。
    • HTTP(Hyper Text Transfer Protocol)协议:超文本传输协议。
    • HTTPS(Hyper Text Transfer Protocol over SecureSocket Layer)协议:安全数据传输协议。
    • SMTP(Simple Mail Transfer Protocol)协议:电子邮件传输协议。
    • POP3(Post Office Protocol - Version 3)协议:邮件读取协议。
    • SNMP(Simple Network Management Protocol)协议:简单网络管理协议。
    • TFTP(Trivial File Transfer Protocol)协议:简单文件传输协议。

    二、登录信息

    usr:pass表示的是登录认证信息,包括登录用户的用户名和密码。虽然登录认证信息可以在URL中体现出来,但绝大多数URL的这个字段都是被省略的,因为登录信息可以通过其他方案交付给服务器。

    三、服务器地址

    www.example.jp表示的是服务器地址,也叫做域名,比如www.alibaba.comwww.qq.comwww.baidu.com

    需要注意的是,我们用IP地址标识公网内的一台主机,但IP地址本身并不适合给用户看。比如说我们可以通过ping命令,分别获得www.baidu.comwww.qq.com这两个域名解析后的IP地址。
    在这里插入图片描述
    如果用户看到的是这两个IP地址,那么用户在访问这个网站之前并不知道这两个网站到底是干什么的,但如果用户看到的是www.baidu.comwww.qq.com这两个域名,那么用户至少知道这两个网站分别对应的是哪家公司,因此域名具有更好的自描述性。

    实际我们可以认为域名和IP地址是等价的,在计算机当中使用的时候既可以使用域名,也可以使用IP地址。但URL呈现出来是可以让用户看到的,因此URL当中是以域名的形式表示服务器地址的。

    四、服务器端口号

    80表示的是服务器端口号。HTTP协议和套接字编程一样都是位于应用层的,在进行套接字编程时我们需要给服务器绑定对应的IP和端口,而这里的应用层协议也同样需要有明确的端口号。

    常见协议对应的端口号:

    协议名称对应端口号
    HTTP80
    HTTPS443
    SSH22

    当我们使用某种协议时,该协议实际就是在为我们提供服务,现在这些常用的服务与端口号之间的对应关系都是明确的,所以我们在使用某种协议时实际是不需要指明该协议对应的端口号的,因此在URL当中,服务器的端口号一般也是被省略的。

    五、带层次的文件路径

    /dir/index.htm表示的是要访问的资源所在的路径。访问服务器的目的是获取服务器上的某种资源,通过前面的域名和端口已经能够找到对应的服务器进程了,此时要做的就是指明该资源所在的路径。

    比如我们打开浏览器输入百度的域名后,此时浏览器就帮我们获取到了百度的首页。
    在这里插入图片描述
    当我们发起网页请求时,本质是获得了这样的一张网页信息,然后浏览器对这张网页信息进行解释,最后就呈现出了对应的网页。
    在这里插入图片描述
    我们可以将这种资源称为网页资源,此外我们还会向服务器请求视频、音频、网页、图片等资源。HTTP之所以叫做超文本传输协议,而不叫做文本传输协议,就是因为有很多资源实际并不是普通的文本资源。

    因此在URL当中就有这样一个字段,用于表示要访问的资源所在的路径。此外我们可以看到,这里的路径分隔符是/,而不是\,这也就证明了实际很多服务都是部署在Linux上的。

    六、查询字符串

    uid=1表示的是请求时提供的额外的参数,这些参数是以键值对的形式,通过&符号分隔开的。

    比如我们在百度上面搜索HTTP,此时可以看到URL中有很多参数,而在这众多的参数当中有一个参数wd(word),表示的就是我们搜索时的搜索关键字wd=HTTP
    在这里插入图片描述
    因此双方在进行网络通信时,是能够通过URL进行用户数据传送的。

    七、片段标识符

    ch1表示的是片段标识符,是对资源的部分补充。

    比如我们在看组图的时候,URL当中就会出现片段标识符。
    在这里插入图片描述
    当我们在翻看组图时,这个片段标识符就会发生变化。
    在这里插入图片描述

    urlencode和urldecode

    如果在搜索关键字当中出现了像/?:这样的字符,由于这些字符已经被URL当作特殊意义理解了,因此URL在呈现时会对这些特殊字符进行转义。

    转义的规则如下:

    • 将需要转码的字符转为十六进制,然后从右到左,取4位(不足4位直接处理),每两位做一位,前面加上%,编码成%XY格式。

    示例

    比如当我们搜索C++时,由于+加号在URL当中也是特殊符号,而+字符转为十六进制后的值就是0x2B,因此一个+就会被编码成一个%2B
    在这里插入图片描述
    说明一下: URL当中除了会对这些特殊符号做编码,对中文也会进行编码。

    在线编码工具

    这里分享一个在线编码工具:

    选中其中的URL编码/解码模式,在输入C++后点击编码就能得到编码后的结果。
    在这里插入图片描述
    再点击解码就能得到原来输入的C++。
    在这里插入图片描述
    实际当服务器拿到对应的URL后,也需要对编码后的参数进行解码,此时服务器才能拿到你想要传递的参数,解码实际就是编码的逆过程。

    HTTP协议格式

    应用层常见的协议有HTTP和HTTPS,传输层常见的协议有TCP,网络层常见的协议是IP,数据链路层对应就是MAC帧了。其中下三层是由操作系统或者驱动帮我们完成的,它们主要负责的是通信细节。如果应用层不考虑下三层,在应用层自己的心目当中,它就可以认为自己是在和对方的应用层在直接进行数据交互。
    在这里插入图片描述
    下三层负责的是通信细节,而应用层负责的是如何使用传输过来的数据,两台主机在进行通信的时候,应用层的数据能够成功交给对端应用层,因为网络协议栈的下三层已经负责完成了这样的通信细节,而如何使用传输过来的数据就需要我们去定制协议,这里最典型的就是HTTP协议。

    HTTP是基于请求和响应的应用层服务,作为客户端,你可以向服务器发起request,服务器收到这个request后,会对这个request做数据分析,得出你想要访问什么资源,然后服务器再构建response,完成这一次HTTP的请求。这种基于request&response这样的工作方式,我们称之为cs或bs模式,其中c表示client,s表示server,b表示browser。

    由于HTTP是基于请求和响应的应用层访问,因此我们必须要知道HTTP对应的请求格式和响应格式,这就是学习HTTP的重点。

    HTTP请求协议格式

    HTTP请求协议格式如下:
    在这里插入图片描述
    HTTP请求由以下四部分组成:

    • 请求行:[请求方法]+[url]+[http版本]
    • 请求报头:请求的属性,这些属性都是以key: value的形式按行陈列的。
    • 空行:遇到空行表示请求报头结束。
    • 请求正文:请求正文允许为空字符串,如果请求正文存在,则在请求报头中会有一个Content-Length属性来标识请求正文的长度。

    其中,前面三部分是一般是HTTP协议自带的,是由HTTP协议自行设置的,而请求正文一般是用户的相关信息或数据,如果用户在请求时没有信息要上传给服务器,此时请求正文就为空字符串。

    如何将HTTP请求的报头与有效载荷进行分离?

    当应用层收到一个HTTP请求时,它必须想办法将HTTP的报头与有效载荷进行分离。对于HTTP请求来讲,这里的请求行和请求报头就是HTTP的报头信息,而这里的请求正文实际就是HTTP的有效载荷。

    我们可以根据HTTP请求当中的空行来进行分离,当服务器收到一个HTTP请求后,就可以按行进行读取,如果读取到空行则说明已经将报头读取完毕,实际HTTP请求当中的空行就是用来分离报头和有效载荷的。

    如果将HTTP请求想象成一个大的线性结构,此时每行的内容都是用\n隔开的,因此在读取过程中,如果连续读取到了两个\n,就说明已经将报头读取完毕了,后面剩下的就是有效载荷了。

    获取浏览器的HTTP请求

    在网络协议栈中,应用层的下一层叫做传输层,而HTTP协议底层通常使用的传输层协议是TCP协议,因此我们可以用套接字编写一个TCP服务器,然后启动浏览器访问我们的这个服务器。

    由于我们的服务器是直接用TCP套接字读取浏览器发来的HTTP请求,此时在服务端没有应用层对这个HTTP请求进行过任何解析,因此我们可以直接将浏览器发来的HTTP请求进行打印输出,此时就能看到HTTP请求的基本构成。
    在这里插入图片描述
    因此下面我们编写一个简单的TCP服务器,这个服务器要做的就是把浏览器发来的HTTP请求进行打印即可。

    #include <iostream>
    #include <fstream>
    #include <string>
    #include <cstring>
    #include <unistd.h>
    #include <sys/wait.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    using namespace std;
    
    int main()
    {
    	//创建套接字
    	int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    	if (listen_sock < 0){
    		cerr << "socket error!" << endl;
    		return 1;
    	}
    	//绑定
    	struct sockaddr_in local;
    	memset(&local, 0, sizeof(local));
    	local.sin_family = AF_INET;
    	local.sin_port = htons(8081);
    	local.sin_addr.s_addr = htonl(INADDR_ANY);
    	if (bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0){
    		cerr << "bind error!" << endl;
    		return 2;
    	}
    	//监听
    	if (listen(listen_sock, 5) < 0){
    		cerr << "listen error!" << endl;
    		return 3;
    	}
    	//启动服务器
    	struct sockaddr peer;
    	memset(&peer, 0, sizeof(peer));
    	socklen_t len = sizeof(peer);
    	for (;;){
    		int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
    		if (sock < 0){
    			cerr << "accept error!" << endl;
    			continue;
    		}
    		if (fork() == 0){ //爸爸进程
    			close(listen_sock);
    			if (fork() > 0){ //爸爸进程
    				exit(0);
    			}
    			//孙子进程
    			char buffer[1024];
    			recv(sock, buffer, sizeof(buffer), 0); //读取HTTP请求
    			cout << "--------------------------http request begin--------------------------" << endl;
    			cout << buffer << endl;
    			cout << "---------------------------http request end---------------------------" << endl;
    			
    			close(sock);
    			exit(0);
    		}
    		//爷爷进程
    		close(sock);
    		waitpid(-1, nullptr, 0); //等待爸爸进程
    	}
    	return 0;
    }
    

    运行服务器程序后,然后用浏览器进行访问,此时我们的服务器就会收到浏览器发来的HTTP请求,并将收到的HTTP请求进行打印输出。
    在这里插入图片描述
    说明一下:

    • 浏览器向我们的服务器发起HTTP请求后,因为我们的服务器没有对进行响应,此时浏览器就会认为服务器没有收到,然后再不断发起新的HTTP请求,因此虽然我们只用浏览器访问了一次,但会受到多次HTTP请求。
    • 由于浏览器发起请求时默认用的就是HTTP协议,因此我们在浏览器的url框当中输入网址时可以不用指明HTTP协议。
    • url当中的/不能称之为我们云服务器上根目录,这个/表示的是web根目录,这个web根目录可以是你的机器上的任何一个目录,这个是可以自己指定的,不一定就是Linux的根目录。

    其中请求行当中的url一般是不携带域名以及端口号的,因为在请求报头中的Host字段当中会进行指明,请求行当中的url表示你要访问这个服务器上的哪一路径下的资源。如果浏览器在访问我们的服务器时指明要访问的资源路径,那么此时浏览器发起的HTTP请求当中的url也会跟着变成该路径。
    在这里插入图片描述
    而请求报头当中全部都是以key: value形式按行陈列的各种请求属性,请求属性陈列完后紧接着的就是一个空行,空行后的就是本次HTTP请求的请求正文,此时请求正文为空字符串,因此这里有两个空行。

    HTTP响应协议格式

    HTTP响应协议格式如下:
    在这里插入图片描述
    HTTP响应由以下四部分组成:

    • 状态行:[http版本]+[状态码]+[状态码描述]
    • 响应报头:响应的属性,这些属性都是以key: value的形式按行陈列的。
    • 空行:遇到空行表示响应报头结束。
    • 响应正文:响应正文允许为空字符串,如果响应正文存在,则响应报头中会有一个Content-Length属性来标识响应正文的长度。比如服务器返回了一个html页面,那么这个html页面的内容就是在响应正文当中的。

    如何将HTTP响应的报头与有效载荷进行分离?

    对于HTTP响应来讲,这里的状态行和响应报头就是HTTP的报头信息,而这里的响应正文实际就是HTTP的有效载荷。与HTTP请求相同,当应用层收到一个HTTP响应时,也是根据HTTP响应当中的空行来分离报头和有效载荷的。当客户端收到一个HTTP响应后,就可以按行进行读取,如果读取到空行则说明报头已经读取完毕。

    构建HTTP响应给浏览器

    服务器读取到客户端发来的HTTP请求后,需要对这个HTTP请求进行各种数据分析,然后构建成对应的HTTP响应发回给客户端。而我们的服务器连接到客户端后,实际就只读取了客户端发来的HTTP请求就将连接断开了。

    接下来我们可以构建一个HTTP请求给浏览器,鉴于现在还没有办法分析浏览器发来的HTTP请求,这里我们可以给浏览器返回一个固定的HTTP响应。我们就将当前服务程序所在的路径作为我们的web根目录,我们可以在该目录下创建一个html文件,然后编写一个简单的html作为当前服务器的首页。
    在这里插入图片描述
    当浏览器向服务器发起HTTP请求时,不管浏览器发来的是什么请求,我们都将这个网页响应给浏览器,此时这个html文件的内容就应该放在响应正文当中,我们只需读取该文件当中的内容,然后将其作为响应正文即可。

    #include <iostream>
    #include <fstream>
    #include <string>
    #include <cstring>
    #include <unistd.h>
    #include <sys/wait.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    using namespace std;
    
    int main()
    {
    	//创建套接字
    	int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    	if (listen_sock < 0){
    		cerr << "socket error!" << endl;
    		return 1;
    	}
    	//绑定
    	struct sockaddr_in local;
    	memset(&local, 0, sizeof(local));
    	local.sin_family = AF_INET;
    	local.sin_port = htons(8081);
    	local.sin_addr.s_addr = htonl(INADDR_ANY);
    	if (bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0){
    		cerr << "bind error!" << endl;
    		return 2;
    	}
    	//监听
    	if (listen(listen_sock, 5) < 0){
    		cerr << "listen error!" << endl;
    		return 3;
    	}
    	//启动服务器
    	struct sockaddr peer;
    	memset(&peer, 0, sizeof(peer));
    	socklen_t len = sizeof(peer);
    	for (;;){
    		int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
    		if (sock < 0){
    			cerr << "accept error!" << endl;
    			continue;
    		}
    		if (fork() == 0){ //爸爸进程
    			close(listen_sock);
    			if (fork() > 0){ //爸爸进程
    				exit(0);
    			}
    			//孙子进程
    			char buffer[1024];
    			recv(sock, buffer, sizeof(buffer), 0); //读取HTTP请求
    			cout << "--------------------------http request begin--------------------------" << endl;
    			cout << buffer << endl;
    			cout << "---------------------------http request end---------------------------" << endl;
    			
    #define PAGE "index.html" //网站首页
    			//读取index.html文件
    			ifstream in(PAGE);
    			if (in.is_open()){
    				in.seekg(0, in.end);
    				int len = in.tellg();
    				in.seekg(0, in.beg);
    				char* file = new char[len];
    				in.read(file, len);
    				in.close();
    				
    				//构建HTTP响应
    				string status_line = "http/1.1 200 OK\n"; //状态行
    				string response_header = "Content-Length: " + to_string(len) + "\n"; //响应报头
    				string blank = "\n"; //空行
    				string response_text = file; //响应正文
    				string response = status_line + response_header + blank + response_text; //响应报文
    				
    				//响应HTTP请求
    				send(sock, response.c_str(), response.size(), 0);
    
    				delete[] file;
    			}
    			close(sock);
    			exit(0);
    		}
    		//爷爷进程
    		close(sock);
    		waitpid(-1, nullptr, 0); //等待爸爸进程
    	}
    	return 0;
    }
    

    因此当浏览器访问我们的服务器时,服务器会将这个index.html文件响应给浏览器,而该html文件被浏览器解释后就会显示出相应的内容。
    在这里插入图片描述
    此外,我们也可以通过telnet命令来访问我们的服务器,此时也是能够得到这个HTTP响应的。
    在这里插入图片描述
    说明一下:

    • 实际我们在进行网络请求的时候,如果不指明请求资源的路径,此时默认你想访问的就是目标网站的首页,也就是web根目录下的index.html文件。
    • 由于只是作为示例,我们在构建HTTP响应时,在响应报头当中只添加了一个属性信息Content-Length,表示响应正文的长度,实际HTTP响应报头当中的属性信息还有很多。

    HTTP为什么要交互版本?

    HTTP请求当中的请求行和HTTP响应当中的状态行,当中都包含了http的版本信息。其中HTTP请求是由客户端发的,因此HTTP请求当中表明的是客户端的http版本,而HTTP响应是由服务器发的,因此HTTP响应当中表明的是服务器的http版本。

    客户端和服务器双方在进行通信时会交互双方http版本,主要还是为了兼容性的问题。因为服务器和客户端使用的可能是不同的http版本,为了让不同版本的客户端都能享受到对应的服务,此时就要求通信双方需要进行版本协商。

    客户端在发起HTTP请求时告诉服务器自己所使用的http版本,此时服务器就可以根据客户端使用的http版本,为客户端提供对应的服务,而不至于因为双方使用的http版本不同而导致无法正常通信。因此为了保证良好的兼容性,通信双方需要交互一下各自的版本信息。

    HTTP的方法

    HTTP常见的方法如下:

    方法说明支持的HTTP协议版本
    GET获取资源1.0、1.1
    POST传输实体主体1.0、1.1
    PUT传输文件1.0、1.1
    HEAD获得报文首部1.0、1.1
    DELETE删除文件1.0、1.1
    OPTIONS询问支持的方法1.1
    TRACE追踪路径1.1
    CONNECT要求用隧道协议连接代理1.1
    LINK建立和资源之间的联系1.0
    UNLINK断开连接关系1.0

    其中最常用的就是GET方法和POST方法。

    GET方法和POST方法

    GET方法一般用于获取某种资源信息,而POST方法一般用于将数据上传给服务器。但实际我们上传数据时也有可能使用GET方法,比如百度提交数据时实际使用的就是GET方法。

    GET方法和POST方法都可以带参:

    • GET方法是通过url传参的。
    • POST方法是通过正文传参的。

    从GET方法和POST方法的传参形式可以看出,POST方法能传递更多的参数,因为url的长度是有限制的,POST方法通过正文传参就可以携带更多的数据。

    此外,使用POST方法传参更加私密,因为POST方法不会将你的参数回显到url当中,此时也就不会被别人轻易看到。不能说POST方法比GET方法更安全,因为POST方法和GET方法实际都不安全,要做到安全只能通过加密来完成。

    Postman演示GET和POST的区别

    如果访问我们的服务器时使用的是GET方法,此时应该通过url进行传参,可以在Params下进行参数设置,因为Postman当中的Params就相当于url当中的参数,你在设置参数时可以看到对应的url也在随之变化。
    在这里插入图片描述
    此时在我们的服务器收到的HTTP请求当中,可以看到请求行中的url就携带上了我们刚才在Postman当中设置的参数。
    在这里插入图片描述
    而如果我们使用的是POST方法,此时就应该通过正文进行传参,可以在Body下进行参数设置,在设置时可以选中Postman当中的raw方式传参,表示原始传参,也就是你输入的参数是什么样的实际传递的参数就是什么样的。
    在这里插入图片描述
    此时服务器收到的HTTP请求的请求正文就不再是空字符串了,而是我们通过正文传递的参数。
    在这里插入图片描述
    说明一下:

    • 因为此时响应正文不为空字符串,因此响应报头当中出现了Content-Length属性,表示响应正文的长度。

    TCP套接字演示GET和POST的区别

    要演示GET方法和POST方法传参的区别,就需要让浏览器提交参数,此时我们可以在index.html当中再加入两个表单,用作用户名和密码的输入,然后再新增一个提交按钮,此时就可以让浏览器提交参数了。
    在这里插入图片描述
    我们可以通过修改表单当中的method属性指定参数提交的方法,还有一个属性叫做action,表示想把这个表单提交给服务器上的哪个资源。

    此时当我们用浏览器访问我们的服务器时,就会显示这两个表单。
    在这里插入图片描述
    当前我们是用GET方法提交参数的,当我们填充完用户名和密码进行提交时,我们的用户名和密码就会自动被同步到url当中。
    在这里插入图片描述
    同时在服务器这边也通过url收到了刚才我们在浏览器提交的参数。
    在这里插入图片描述
    如果我们将提交表单的方法改为POST方法,此时当我们填充完用户名和密码进行提交时,对应提交的参数就不会在url当中体现出来,而会通过正文将这两个参数传递给了服务器。
    在这里插入图片描述
    此时用户名和密码就通过正文的形式传递给服务器了。
    在这里插入图片描述
    说明一下:

    • 当我们使用GET方法时,我们提交的参数会回显到url当中,因此GET方法一般是处理数据不敏感的。
    • 如果你要传递的数据比较私密的话你一定要用POST方法,倒不是因为POST方法更安全,实际上GET和POST方法传参时都是明文传送,所以都不安全,但是POST方法更私密,因为POST是通过正文传参的,不会将参数立马回显到浏览器的url框当中的,所以相对更私密。

    HTTP的状态码

    HTTP的状态码如下:

    类别原因短语
    1XXInformational(信息性状态码)接收的请求正在处理
    2XXSuccess(成功状态码)请求正常处理完毕
    3XXRedirection(重定向状态码)需要进行附加操作以完成请求
    4XXClient Error(客户端错误状态码)服务器无法处理请求
    5XXServer Error(服务器错误状态码)服务器处理请求出错

    最常见的状态码,比如200(OK),404(Not Found),403(Forbidden请求权限不够),302(Redirect),504(Bad Gateway)。

    Redirection(重定向状态码)

    重定向就是通过各种方法将各种网络请求重新定个方向转到其它位置,此时这个服务器相当于提供了一个引路的服务。

    重定向又可分为临时重定向和永久重定向,其中状态码301表示的就是永久重定向,而状态码302和307表示的是临时重定向。

    临时重定向和永久重定向本质是影响客户端的标签,决定客户端是否需要更新目标地址。如果某个网站是永久重定向,那么第一次访问该网站时由浏览器帮你进行重定向,但后续再访问该网站时就不需要浏览器再进行重定向了,此时你访问的直接就是重定向后的网站。而如果某个网站是临时重定向,那么每次访问该网站时如果需要进行重定向,都需要浏览器来帮我们完成重定向跳转到目标网站。

    临时重定向演示

    进行临时重定向时需要用到Location字段,Location字段是HTTP报头当中的一个属性信息,该字段表明了你所要重定向到的目标网站。

    我们这里要演示临时重定向,可以将HTTP响应当中的状态码改为307,然后跟上对应的状态码描述,此外,还需要在HTTP响应报头当中添加Location字段,这个Location后面跟的就是你需要重定向到的网页,比如我们这里将其设置为CSDN的首页。

    #include <iostream>
    #include <fstream>
    #include <string>
    #include <cstring>
    #include <unistd.h>
    #include <sys/wait.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    using namespace std;
    
    int main()
    {
    	//创建套接字
    	int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    	if (listen_sock < 0){
    		cerr << "socket error!" << endl;
    		return 1;
    	}
    	//绑定
    	struct sockaddr_in local;
    	memset(&local, 0, sizeof(local));
    	local.sin_family = AF_INET;
    	local.sin_port = htons(8081);
    	local.sin_addr.s_addr = htonl(INADDR_ANY);
    	if (bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0){
    		cerr << "bind error!" << endl;
    		return 2;
    	}
    	//监听
    	if (listen(listen_sock, 5) < 0){
    		cerr << "listen error!" << endl;
    		return 3;
    	}
    	//启动服务器
    	struct sockaddr peer;
    	memset(&peer, 0, sizeof(peer));
    	socklen_t len = sizeof(peer);
    	for (;;){
    		int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
    		if (sock < 0){
    			cerr << "accept error!" << endl;
    			continue;
    		}
    		if (fork() == 0){ //爸爸进程
    			close(listen_sock);
    			if (fork() > 0){ //爸爸进程
    				exit(0);
    			}
    			//孙子进程
    			char buffer[1024];
    			recv(sock, buffer, sizeof(buffer), 0); //读取HTTP请求
    			cout << "--------------------------http request begin--------------------------" << endl;
    			cout << buffer << endl;
    			cout << "---------------------------http request end---------------------------" << endl;
    			
    			//构建HTTP响应
    			string status_line = "http/1.1 307 Temporary Redirect\n"; //状态行
    			string response_header = "Location: https://www.csdn.net/\n"; //响应报头
    			string blank = "\n"; //空行
    			string response = status_line + response_header + blank; //响应报文
    			
    			//响应HTTP请求
    			send(sock, response.c_str(), response.size(), 0);
    
    			close(sock);
    			exit(0);
    		}
    		//爷爷进程
    		close(sock);
    		waitpid(-1, nullptr, 0); //等待爸爸进程
    	}
    	return 0;
    }
    

    此时运行我们的服务器,当我们用telnet命令登录我们的服务器时,向服务器发起HTTP请求时,此时服务器给我们的响应就是状态码307,响应报头当中是Location字段对应的就是CSDN首页的网址
    在这里插入图片描述
    telnet命令实际上只是一来一回,如果我们用浏览器访问我们的服务器,当浏览器收到这个HTTP响应后,还会对这个HTTP响应进行分析,当浏览器识别到状态码是307后就会提取出Location后面的网址,然后继续自动对该网站继续发起请求,此时就完成了页面跳转这样的功能,这样就完成了重定向功能。

    此时当浏览器访问我们的服务器时,就会立马跳转到CSDN的首页。
    在这里插入图片描述

    HTTP常见的Header

    HTTP常见的Header如下:

    • Content-Type:数据类型(text/html等)。
    • Content-Length:正文的长度。
    • Host:客户端告知服务器,所请求的资源是在哪个主机的哪个端口上。
    • User-Agent:声明用户的操作系统和浏览器的版本信息。
    • Referer:当前页面是哪个页面跳转过来的。
    • Location:搭配3XX状态码使用,告诉客户端接下来要去哪里访问。
    • Cookie:用于在客户端存储少量信息,通常用于实现会话(session)的功能。

    Host

    Host字段表明了客户端要访问的服务的IP和端口,比如当浏览器访问我们的服务器时,浏览器发来的HTTP请求当中的Host字段填的就是我们的IP和端口。但客户端不就是要访问服务器吗?为什么客户端还要告诉服务器它要访问的服务对应的IP和端口?

    因为有些服务器实际提供的是一种代理服务,也就是代替客户端向其他服务器发起请求,然后将请求得到的结果再返回给客户端。在这种情况下客户端就必须告诉代理服务器它要访问的服务对应的IP和端口,此时Host提供的信息就有效了。

    User-Agent

    User-Agent代表的是客户端对应的操作系统和浏览器的版本信息。

    比如当我们用电脑下载某些软件时,它会自动向我们展示与我们操作系统相匹配的版本,这实际就是因为我们在向目标网站发起请求的时候,User-Agent字段当中包含了我们的主机信息,此时该网站就会向你推送相匹配的软件版本。在这里插入图片描述

    Referer

    Referer代表的是你当前是从哪一个页面跳转过来的。Referer记录上一个页面的好处一方面是方便回退,另一方面可以知道我们当前页面与上一个页面之间的相关性。

    Keep-Alive(长连接)

    HTTP/1.0是通过request&response的方式来进行请求和响应的,HTTP/1.0常见的工作方式就是客户端和服务器先建立链接,然后客户端发起请求给服务器,服务器再对该请求进行响应,然后立马端口连接。

    但如果一个连接建立后客户端和服务器只进行一次交互,就将连接关闭,就太浪费资源了,因此现在主流的HTTP/1.1是支持长连接的。所谓的长连接就是建立连接后,客户端可以不断的向服务器一次写入多个HTTP请求,而服务器在上层依次读取这些请求就行了,此时一条连接就可以传送大量的请求和响应,这就是长连接。

    如果HTTP请求或响应报头当中的Connect字段对应的值是Keep-Alive,就代表支持长连接。

    Cookie和Session

    HTTP实际上是一种无状态协议,HTTP的每次请求/响应之间是没有任何关系的,但你在使用浏览器的时候发现并不是这样的。

    比如当你登录一次CSDN后,就算你把CSDN网站关了甚至是重启电脑,当你再次打开CSDN网站时,CSDN并没有要求你再次输入账号和密码,这实际上是通过cookie技术实现的,点击浏览器当中锁的标志就可以看到对应网站的各种cookie数据。
    在这里插入图片描述
    这些cookie数据实际都是对应的服务器方写的,如果你将对应的某些cookie删除,那么此时可能就需要你重新进行登录认证了,因为你删除的可能正好就是你登录时所设置的cookie信息。

    cookie是什么呢?

    因为HTTP是一种无状态协议,如果没有cookie的存在,那么每当我们要进行页面请求时都需要重新输入账号和密码进行认证,这样太麻烦了。

    比如你是某个视频网站的VIP,这个网站里面的VIP视频有成百上千个,你每次点击一个视频都要重新进行VIP身份认证。而HTTP不支持记录用户状态,那么我们就需要有一种独立技术来帮我们支持,这种技术目前现在已经内置到HTTP协议当中了,叫做cookie。

    当我们第一次登录某个网站时,需要输入我们的账号和密码进行身份认证,此时如果服务器经过数据比对后判定你是一个合法的用户,那么为了让你后续在进行某些网页请求时不用重新输入账号和密码,此时服务器就会进行Set-Cookie的设置。(Set-Cookie也是HTTP报头当中的一种属性信息)

    当认证通过并在服务端进行Set-Cookie设置后,服务器在对浏览器进行HTTP响应时就会将这个Set-Cookie响应给浏览器。而浏览器收到响应后会自动提取出Set-Cookie的值,将其保存在浏览器的cookie文件当中,此时就相当于我的账号和密码信息保存在本地浏览器的cookie文件当中。
    在这里插入图片描述
    从第一次登录认证之后,浏览器再向该网站发起的HTTP请求当中就会自动包含一个cookie字段,其中携带的就是我第一次的认证信息,此后对端服务器需要对你进行认证时就会直接提取出HTTP请求当中的cookie字段,而不会重新让你输入账号和密码了。

    也就是在第一次认证登录后,后续所有的认证都变成了自动认证,这就叫做cookie技术。

    内存级别&文件级别

    cookie就是在浏览器当中的一个小文件,文件里记录的就是用户的私有信息。cookie文件可以分为两种,一种是内存级别的cookie文件,另一种是文件级别的cookie文件。

    • 将浏览器关掉后再打开,访问之前登录过的网站,如果需要你重新输入账号和密码,说明你之前登录时浏览器当中保存的cookie信息是内存级别的。
    • 将浏览器关掉甚至将电脑重启再打开,访问之前登录过的网站,如果不需要你重新输入账户和密码,说明你之前登录时浏览器当中保存的cookie信息是文件级别的。

    cookie被盗

    如果你浏览器当中保存的cookie信息被非法用户盗取了,那么此时这个非法用户就可以用你的cookie信息,以你的身份去访问你曾经访问过的网站,我们将这种现象称为cookie被盗取了。

    比如你不小心点了某个链接,这个链接可能就是一个下载程序,当你点击之后它就会通过某种方式把程序下载到你本地,并且自动执行该程序,该程序会扫描你的浏览器当中的cookie目录,把所有的cookie信息通过网络的方式传送给恶意方,当恶意方拿到你的cookie信息后就可以拷贝到它的浏览器对应的cookie目录当中,然后以你的身份访问你曾经访问过的网站。

    SessionID

    单纯的使用cookie是非常不安全的,因为此时cookie文件当中就保存的是你的私密信息,一旦cookie文件泄漏你的隐私信息也就泄漏。

    所以当前主流的服务器还引入了SessionID这样的概念,当我们第一次登录某个网站输入账号和密码后,服务器认证成功后还会服务端生成一个对应的SessionID,这个SessionID与用户信息是不相关的。系统会将所有登录用户的SessionID值统一维护起来。

    此时当认证通过后服务端在对浏览器进行HTTP响应时,就会将这个生成的SessionID值响应给浏览器。浏览器收到响应后会自动提取出SessionID的值,将其保存在浏览器的cookie文件当中。后续访问该服务器时,对应的HTTP请求当中就会自动携带上这个SessionID。
    在这里插入图片描述
    而服务器识别到HTTP请求当中包含了SessionID,就会提取出这个SessionID,然后再到对应的集合当中进行对比,对比成功就说明这个用户是曾经登录过的,此时也就自动就认证成功了,然后就会正常处理你发来的请求,这就是我们当前主流的工作方式。

    安全是相对的

    引入SessionID之后,浏览器当中的cookie文件保存的是SessionID,此时这个cookie文件同样可能被盗取。此时用户的账号和密码虽然不会泄漏了,但用户对应的SessionID是会泄漏的,非法用户仍然可以盗取我的SessionID去访问我曾经访问过的服务器,相当于还是存在刚才的问题。

    • 之前的工作方式就相当于把账号和密码信息在浏览器当中再保存一份,每次请求时都自动将账号和密码的信息携带上,但是账号和密码一直在网当中发送太不安全了。
    • 因此现在的工作方式是,服务器只有在第一次认证的时候需要在网络中传输账号和密码,此后在网络上发送的都是SessionID。

    这种方法虽然没有真正解决安全问题,但这种方法是相对安全的。互联网上是不存在绝对安全这样的概念的,任何安全都是相对的,就算你将发送到网络当中的信息进行加密,也有可能被别人破解。

    不过在安全领域有一个准则:如果破解某个信息的成本已经远远大于破解之后获得的收益(说明做这个事是赔本的),那么就可以说这个信息是安全的。

    引入SessionID后的好处

    • 在引入SessionID之前,用户登录的账号信息都是保存在浏览器内部的,此时的账号信息是由客户端去维护的。
    • 而引入SessionID后,用户登录的账号信息是有服务器去维护的,在浏览器内部保存的只是SessionID。

    此时虽然SessionID可能被非法用户盗取,但服务器也可以使用各种各样的策略来保证用户账号的安全。

    • IP是有归类的,可以通过IP地址来判断登录用户所在的地址范围。如果一个账号在短时间内登录地址发送了巨大变化,此时服务器就会立马识别到这个账号发生异常了,进而在服务器当中清除对应的SessionID的值。这时当你或那个非法用户想要访问服务器时,就都需要重新输入账号和密码进行身份认证,而只有你是知道自己的密码的,当你重新认证登录后服务器就可以将另一方识别为非法用户,进而对该非法用户进行对应的黑名单/白名单认证。
    • 当操作者想要进行某些高权限的操作时,会要求操作者再次输入账号和密码信息,再次确认身份。就算你的账号被非法用户盗取了,但非法用户在改你密码时需要输入旧密码,这是非法用户在短时间内无法做到的,因为它并不知道你的密码。这也就是为什么账号被盗后还可以找回来的原因,因为非法用户无法在短时间内修改你的账号密码,此时你就可以通过追回的方式让当前的SessionID失效,让使用该账号的用户进行重新登录认证。
    • SessionID也有过期策略,比如SessionID是一个小时内是有效的。所以即便你的SessionID被非法用户盗取了,也仅仅是在一个小时内有效,而且在功能上受约束,所以不会造成太大的影响。

    任何事情都有两面性,如果不是这些非法用户的存在,现在的服务器肯定是漏洞百出,只有双方不断进行对抗双方才能不断进步。

    实验演示

    当浏览器访问我们的服务器时,如果服务器给浏览器的HTTP响应当中包含Set-Cookie字段,那么当浏览器再次访问服务器时就会携带上这个cookie信息。

    因此我们可以在服务器的响应报头当中添加上一个Set-Cookie字段,看看浏览器第二次发起HTTP请求时是否会带上这个Set-Cookie字段。

    #include <iostream>
    #include <fstream>
    #include <string>
    #include <cstring>
    #include <unistd.h>
    #include <sys/wait.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    using namespace std;
    
    int main()
    {
    	//创建套接字
    	int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    	if (listen_sock < 0){
    		cerr << "socket error!" << endl;
    		return 1;
    	}
    	//绑定
    	struct sockaddr_in local;
    	memset(&local, 0, sizeof(local));
    	local.sin_family = AF_INET;
    	local.sin_port = htons(8081);
    	local.sin_addr.s_addr = htonl(INADDR_ANY);
    	if (bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0){
    		cerr << "bind error!" << endl;
    		return 2;
    	}
    	//监听
    	if (listen(listen_sock, 5) < 0){
    		cerr << "listen error!" << endl;
    		return 3;
    	}
    	//启动服务器
    	struct sockaddr peer;
    	memset(&peer, 0, sizeof(peer));
    	socklen_t len = sizeof(peer);
    	for (;;){
    		int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
    		if (sock < 0){
    			cerr << "accept error!" << endl;
    			continue;
    		}
    		if (fork() == 0){ //爸爸进程
    			close(listen_sock);
    			if (fork() > 0){ //爸爸进程
    				exit(0);
    			}
    			//孙子进程
    			char buffer[1024];
    			recv(sock, buffer, sizeof(buffer), 0); //读取HTTP请求
    			cout << "--------------------------http request begin--------------------------" << endl;
    			cout << buffer << endl;
    			cout << "---------------------------http request end---------------------------" << endl;
    
    #define PAGE "index.html" //网站首页
    			//读取index.html文件
    			ifstream in(PAGE);
    			if (in.is_open()){
    				in.seekg(0, in.end);
    				int len = in.tellg();
    				in.seekg(0, in.beg);
    				char* file = new char[len];
    				in.read(file, len);
    				in.close();
    
    				//构建HTTP响应
    				string status_line = "http/1.1 200 OK\n"; //状态行
    				string response_header = "Content-Length: " + to_string(len) + "\n"; //响应报头
    				response_header += "Set-Cookie: 2021dragon\n"; //添加Set-Cookie字段
    				string blank = "\n"; //空行
    				string response_text = file; //响应正文
    				string response = status_line + response_header + blank + response_text; //响应报文
    				
    				//响应HTTP请求
    				send(sock, response.c_str(), response.size(), 0);
    
    				delete[] file;
    			}
    			close(sock);
    			exit(0);
    		}
    		//爷爷进程
    		close(sock);
    		waitpid(-1, nullptr, 0); //等待爸爸进程
    	}
    	return 0;
    }
    

    运行服务器后,用浏览器访问我们的服务器,此时通过Fiddler可以看到我们的服务器发给浏览器的HTTP响应报头当中包含了这个Set-Cookie字段。
    在这里插入图片描述
    同时我们也可以在浏览器当中看到这个cookie,这个cookie的值就是我们设置的2021dragon,此时浏览器当中就写入了这样的一个cookie。
    在这里插入图片描述
    然后我们输入用户名和密码并提交,此时相当于第二次访问我们的服务器,此时通过Fiddler可以看到,由于我们提交表单参数用的是POST方法,因此这里的参数是通过正文的形式提交的,更重要的是第二次的HTTP请求当中会携带上这个cookie信息。
    在这里插入图片描述

    科普HTTPS协议

    HTTPS VS HTTP

    早期很多公司刚起步的时候,使用的应用层协议都是HTTP,而HTTP无论是用GET方法还是POST方法传参,都是没有经过任何加密的,因此早期很多的信息都是可以通过抓包工具抓到的。

    为了解决这个问题,于是出现了HTTPS协议,HTTPS实际就是在应用层和传输层协议之间加了一层加密层(SSL&TLS),这层加密层本身也是属于应用层的,它会对用户的个人信息进行各种程度的加密。HTTPS在交付数据时先把数据交给加密层,由加密层对数据加密后再交给传输层。

    当然,通信双方使用的应用层协议必须是一样的,因此对端的应用层也必须使用HTTPS,当对端的传输层收到数据后,会先将数据交给加密层,由加密层对数据进行解密后再将数据交给应用层。
    在这里插入图片描述
    此时数据只有在用户层(应用层)是没有被加密的,而在应用层往下以及网络当中都是加密的,这就叫做HTTPS。

    对称加密 VS 非对称加密

    加密的方式可以分为对称加密和非对称加密:

    • 采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密。
    • 采用公钥和私钥来进行加密和解密,用其中一个密钥进行加密就必须用另一个密钥进行解密,这种加密方法称为非对称加密。

    一般而言,对称加密的效率比非对称加密的效率高。

    对称加密的过程

    对称加密的效率更高,因此双方在进行正常通信时使用的是对称加密。但双方要进行对称加密通信,此时就要去加密方要把密钥给解密方,此时解密方才能对数据进行解密,但加密方把密钥(密钥也是数据)给解密方时也是需要对密钥进行加密的,解密废没法解密就没法拿到密钥,这就变成了鸡生蛋蛋生鸡的问题了。

    这里也不可能让加密方给密钥的时候不进行加密,因为那样全世界都知道这个密钥了,后续加密方的加密也就没有意义了。因此刚开始在进行密钥协商的时候需要用非对称加密。

    对称加密的建立过程如下:

    • 通信双方建立连接的时候,双方就可以把支持的加密算法作协商,协商之后在服务器端形成非对称加密时使用的公钥和私钥,在客户端形成对称加密时使用的密钥。
    • 然后服务器将公钥交给客户端(这个公钥全世界都可以看到),然后客户端用这个公钥对客户端形成的密钥进行加密,将加密后的密钥发送给服务器,服务器拿到后再用它的私钥进行解密,最终服务器就拿到了客户端的密钥。
    • 这时客户端和服务器都有了这个密钥,并且其他人是不知道的,此时客户端和服务器就可以进行对称加密通信了。

    需要注意的是,这里客户端用公钥加密后的密钥只有服务器能够解密,因为在非对称加密当中,数据用公钥加密就必须用私钥解密,而只有服务器才有私钥,其他人都只有公钥。

    简单理解对称加密

    如果你还想知道更多的非对称加密算法,可以了解一下RSA,这是一个基于因式分解的非对称加密算法。

    下面我们简单理解一下对称加密,对称加密最典型的例子就是异或运算。
    在这里插入图片描述
    我们用A异或B得到一个中间值C,此时如果我们采用C异或B就能重新得到A。在这个过程中,A就相当于通信双方想要交互的数据,B就相当于对称加密当中的密钥,C就相当于被密钥加密后的数据,异或运算实际就是一个简单的对称加密算法。

    展开全文
  • 开发者会了解到,通常如何将微服务应用设计为四结构——平台服务层、边界和客户端。开发者还会学习到这四的具体内容,以及它们是如何组合起来交付面向客户的应用程序的。我们会重点介绍事件中枢(event ...

    在本章中,我们会站在更高的角度来思考由微服务组成的整个应用的设计和架构。我们无法代替读者深入了解开发者们自己的应用系统的业务领域,但是我们可以告诉读者的是,深入了解业务领域能够帮助读者构建出足够灵活的系统,这样的系统能够随着时间的推移不断发展和演进。

    开发者会了解到,通常如何将微服务应用设计为四层结构——平台层、服务层、边界层和客户端层。开发者还会学习到这四层的具体内容,以及它们是如何组合起来交付面向客户的应用程序的。我们会重点介绍事件中枢(event backbone)在开发大规模微服务应用中的作用,还会讨论一些构建应用边界的不同模式,如API网关。最后,我们会介绍为微服务应用构建用户界面的最新趋势,如微前端和前端组合。

    3.1 整体架构

    软件设计师希望所开发出来的软件是易于修改的。许多外部力量都会对开发者的软件施加影响:新增需求、系统缺陷、市场需要、新客户、业务增长情况等。理想情况下,工程师可以自信满满地以稳定的步调来响应这些压力。如果想要做到这一点,开发方式就应该减少摩擦并将风险降至最低。

    随着时间的不断推移,系统也在不断演进,工程团队想要将开发道路上的所有拦路石清理掉。有的希望能够无缝地快速替换掉系统中过时的组件,有的希望各个团队能够完全地实现自治,并各自负责系统的不同模块,有的则希望这些团队之间不需要不停地同步各种信息而且相互没有阻碍。为此,我们需要考虑一下架构设计,也就是构建应用的规划。

    3.1.1 从单体应用到微服务

    在单体应用中,主要交付的就是一个应用程序。这个应用程序可以被水平地分成几个不同的技术层。在典型的三层架构的应用中,它们分别是数据层业务逻辑层展示层(图3.1)。应用又会被垂直地分成不同的业务领域。MVC模式以及Rails和Django等框架都体现了三层模型。每一层都为其上一层提供服务:数据层提供持久化状态,业务逻辑层执行有效操作,而展示层则将结果展示给终端用户。

    图3.1 典型的单体应用三层架构

    单个微服务和单体应用是很相似的:微服务会存储数据、执行一些业务逻辑操作并通过API将数据和结果返回给消费者。每个微服务都具备一项业务能力或者技术能力,并且会通过和其他微服务进行交互来执行某些任务。单个服务的抽象架构如图3.2所示。

    图3.2 单个微服务的抽象架构

     注意

    在第4章中,我们将详细讨论微服务的范围划分-如何定义微服务的边界和职责。

    在单体应用中,架构限定在整个应用本身的边界内;而在微服务应用中,开发者是在对从规模到范围都在不断演变的内容进行规划。用城市作类比的话,开发一个单体应用就像建造一幢摩天大厦,而构建微服务应用则像开发一个社区:开发者需要建造基础设施(自来水管道、道路交通、电路线缆),还要规划未来的发展(小型企业区vs. 住宅区)。

    这个类比强调不仅要考虑组件自身,还要考虑这些组件之间的连接方式、放置位置以及如何并行地构建它们。开发者希望自己的方案能促使应用沿着良好的方向发展,而非强行规定或强迫应用采取某种结构。

    最重要的是,微服务并不是孤立地运行的。每个微服务都会和其他的微服务一起共存于一个环境中,而我们就在这个环境中开发、部署和运行微服务。应用架构应该包含整个环境。

    3.1.2 架构师的角色

    软件架构师的职责是什么呢?许多公司都会招聘软件架构师,即便这个职位的实际工作效果和对它的要求出入很大。

    微服务应用使得快速修改成为可能:因为团队在不断地开发新的服务、停用现有服务或者重构现有功能,所以应用也会随着时间慢慢地演进。架构师或者技术负责人的工作就是要确保系统能够不断演进,而不是采用了固化的设计方案。如果微服务应用是一座城市的话,开发者就是市政府的规划师。

    架构师的职责是确保应用的技术基础能够支持快节奏的开发以及频繁的变化。架构师应该具备纵观全局的能力,确保应用的全局需求都能得到满足,并进一步指导应用的演进发展。

    (1)应用和组织远大的战略目标是一致的。

    (2)团队共同承担一套通用的技术价值观和期望。

    (3)跨领域的内容——诸如可观察性、部署、服务间通信——应该满足不同团队的需要。

    (4)面对变化,整个应用是灵活可扩展的。

    为了实现这些目标,架构师应该通过两种方式来指导开发:第一,准则——为了实现更高一层的技术目标或者组织目标,团队要遵循的一套指南;第二,概念模型——系统内部相互联系以及应用层面的模式的抽象模型。

    3.1.3 架构准则

    准则是指团队为了实现更高的目标而要遵循的一套指南(或规则)。准则用于指导团队如何实践。这一模型如图3.3所示。例如,如果某产品的目标是销售给那些对隐私和安全问题特别敏感的企业,那么开发者就要制定这些准则。

    (1)开发实践必须符合那些公认的外部标准(如ISO 27001)。

    (2)时刻牢记,所有数据必须是可转移的,并且在存储数据的时候要有效期限制。

    (3)必须要能够在应用中清晰地跟踪和回溯追查个人信息。

    准则是灵活的,它们可以并且应该随着业务优先级的变化以及应用的技术演进而变化。例如,早期的开发过程会将验证产品和市场需求的匹配度作为更高优先级的工作,而一个更加成熟的应用可能需要更专注于性能和可扩展性。

    图3.3 基于技术准则的架构方法

    3.1.4 微服务应用的4层架构

    架构应该体现出清晰的高层概念模型。在对一个应用的技术结构进行分析时,模型是一个非常有用的工具。如图3.1中的3层模型,这样的多层模型是一种应用程序结构的常见方案,能够反映整个系统不同层次的抽象和职责。

    在本章的其余部分中,我们会探讨微服务的4层模型。

    (1)平台层——微服务平台提供了工具、基础架构和一些高级的基本部件,以支持微服务的快速开发、运行和部署。一个成熟的平台层会让技术人员把重心放在功能开发而非一些底层的工作上。

    (2)服务层——在这一层,开发的各个服务会借助下层的平台层的支持来相互作用,以提供业务和技术功能。

    (3)边界层——客户端会通过定义好的边界和应用进行交互。这个边界会暴露底层的各个功能,以满足外部消费者的需求。

    (4)客户端层——与微服务后端交互的客户端应用,如网站和移动应用。

    上述架构层次如图3.4所示。不管底层使用了什么技术方案,开发者应该都能够将它应用到所有微服务应用中。

    图3.4 微服务应用架构的四层模型

    每一层都是建立在下一层次的功能之上的,比如,每个服务都会利用下层的微服务平台提供的部署流水线、基础设施和通信机制。要设计良好的微服务应用,需要在每个层级上都进行大量的投入并精心设计。

    很棒!开发者现在有了一个可用的模型。在后续的5节中,我们会一一介绍这一架构模型的4个层次,并讨论它们对构建可持续的、灵活的、可演进的微服务应用的贡献。

    3.2 微服务平台

    微服务并不是独立存在的。微服务需要由如下基础设施提供支持。

    (1)服务运行的部署目标,包括基础设施的基本元件,如负载均衡器和虚拟机。

    (2)日志聚合和监控聚合用于观测服务运行情况。

    (3)一致且可重复的部署流水线,用于测试和发布新服务或者新版本。

    (4)支持安全运行,如网络控制、涉密信息管理和应用加固。

    (5)通信通道和服务发现方案,用于支持服务间交互。

    这些功能及其与服务层的关系如图3.5所示。如果把每个微服务看作一栋住宅,那么平台层提供了道路、自来水、电线和电话线。

    图3.5 微服务平台层的功能

    一个具有鲁棒性的平台层既能够降低整体的实现成本,又能够提升整体的可稳定性,甚至能提高服务的开发速度。如果没有平台层,产品开发者就需要重复编写大量的底层的基础代码,无暇交付新的功能和业务价值。一般的开发者不需要也不应该对应用的每一层的复杂性都了然于胸。基本上,一个半独立的专业团队就可以开发出一套平台层,能够满足那些在服务层工作的团队的需求。

    映射运行时平台

    微服务平台有助于提升开发者的自信,让开发者确信团队编写的服务能够支持生产环境的流量压力,并且这些服务是可恢复的、透明的和可扩展的。

    图3.6所示的是某个微服务的运行时平台。运行时平台(或者部署目标)——比如,像AWS的云环境或者像Heroku这样的PaaS平台——提供了运行多个服务实例以及将请求路由给这些实例的基础元件。除此之外,它还提供了相应的机制来为服务实例提供配置信息——机密信息和特定环境的变量。

    开发者在这一基础之上来开发微服务平台的其他部分。观测工具会收集服务以及底层基础设施的数据并进行修正。部署流水线会管理这一应用栈的升级或回滚。

    图3.6 在标准的云环境中运行微服务所需的部署配置

    3.3 服务层

    服务层,正如其名称所描述的——它就是服务所存在的地方。在这一层,服务通过交互完成有用的功能——这依赖于底层平台对可靠的运行和通信方案的抽象,它还会通过边界层将功能暴露给应用的客户端。我们同样还会考虑将服务内部的组件(如数据存储)也作为服务层的一部分。

    业务特点不同,相应服务层的结构也会差异很大。在本节中,我们会讨论一些常见的模式:业务和技术功能、聚合和多元服务以及关键路径和非关键路径的服务。

    3.3.1 功能

    开发者所开发的服务实现的是不同的功能。

    (1)业务能力是组织为了创造价值和实现业务目标所做的工作。划到业务功能的微服务直接体现的是业务目标。

    (2)技术能力通过实现共享的技术功能来支持其他服务。

    图3.7比较了两种不同类型的功能。SimpleBank的order服务公开了管理下单的功能——这是一个业务功能;而market服务是一个技术功能,它提供了和第三方系统通信的网关供其他服务(比如,market服务公开了市场信息数据或者贸易结算功能)使用。

    图3.7 实现业务功能和技术功能的微服务

     注意

    我们会在下一章介绍何时使用业务功能和技术功能,以及如何将它们映射到不同的服务上。

    3.3.2 聚合与多元服务

    在微服务应用的早期阶段,多个服务可能是扁平化的,每个服务的职责都是处于相似的层次的,比如,第2章中的order服务、fee服务、transaction服务和account服务——都处于大致相当的抽象水平。

    随着应用的发展,开发者会面临服务增长的两大压力:从多个服务聚合数据来为客户端的请求提供非规范化的数据(如同时返回费用和订单信息);利用底层的功能来提供专门的业务逻辑(如发布某种特定类型的订单)。

    随着时间的推移,这两种压力会导致服务出现层级结构的分化。靠近系统边界的服务会和某些服务交互以聚合它们的输出——我们将这种服务称为聚合器(aggregator)(图3.8),除此之外,还有些专门的服务会作为协调器(coordinator)来协调下层多个服务的工作。

    在出现新的数据需求或者功能需求时,开发者要决定是开发一个新的服务还是修改已有的服务,这是开发者所面临的重大挑战。创建一个新的服务会增加整体的复杂度并且可能会导致服务间的紧耦合,但是将功能加到现有的服务又可能会导致内聚性降低以及难以替换。这是违背了微服务的基本原则的。

    图3.8 聚合器服务通过将底层服务的数据进行关联来实现查询服务,协调器服务会向下游服务发出各种命令来编配它们的行动

    3.3.3 关键路径和非关键路径

    随着系统的不断演进,有一些功能对顾客的需求和业务的成功经营来说越来越重要。比如,在SimpleBank公司的下单流程中,order服务就处于关键路径。一旦这个服务运行出错,系统就不能执行客户的订单。对应地,其他服务的重要性就弱一些。即便客户的资料服务不可用,它也不大会影响开发者提供的那些关键的、会带来收入的部分服务。SimpleBank公司的一些路径示例如图3.9所示。

    图3.9 服务链对外提供功能,许多服务会参与到多个的路径中

    这是一把双刃剑。关键路径上的服务越多,系统出现故障的可能性就越高。因为不可能哪个服务是100%可靠的,一个服务累积的可靠性是它所依赖的那些服务的可靠性的乘积。

    但是微服务使得我们可以清楚地确定这些路径,然后对它们单独处理,投入更多的精力来尽可能提高这些路径的可恢复性和可扩展性,对于不那么重要的系统领域,则可以少付出一些精力。

    3.4 通信

    通信是微服务应用的一个基本要素。微服务相互通信才能完成有用的工作。微服务会向其他微服务发送命令通知和请求操作,开发者选择的通信方式也决定着所开发的应用的结构。

     提示

    在微服务系统中,网络通信也是一个很主要的不可靠因素。在第6章中,我们会讨论一些提升服务间通信的可靠性的技术。

    在架构上,通信不是独立的一层,但是我们之所以将它拎出来单独作为一节来介绍,是因为网络使得平台层和服务层之间的边界变得模糊。有些组件,比如通信代理,属于平台层。但是负责组装和发送消息的确实服务自己。开发者希望所开发的端点实现智能化、但是管道傻瓜化。

    在本节中,我们会讨论一些常用的服务通信模式及其对微服务应用的灵活性以及演化过程的影响。大部分成熟的微服务应用都会同时掺杂有同步和异步这两种交互方式。

    3.4.1 何时使用同步消息

    同步消息通常是首先会想到的设计方案。它们非常适合于那些在执行新的操作前需要获取前一个操作的数据结果或者确认前一个操作成功还是失败的场景。

    一种请求-响应的同步消息模式如图3.10所示。左边的服务会构造要发给接收方的对应的消息,然后采用一种传输机制(如HTTP)来将消息发送出去。目标服务会收到这条消息并相应地进行处理。

    图3.10 服务间同步的请求-响应生命周期

    1.选择传输方式

    无论是选择RESTful HTTP、RPC库还是选择其他传输方式,都会影响服务的设计方案。每种传输方式都具有不同的特性,这些特性包括时延、语言支持情况和规范性。比如gRPC支持生成使用Protobuf的客户端/服务器端的API契约,而HTTP与消息的上下文无关。在应用中,只使用一种同步传输方式能产生规模效益,这样也更易于通过一些监控和工具来排查问题。

    微服务的关注点分离也同样重要。应该将传输方案的选择与服务的业务逻辑拆分开,服务不需要了解HTTP状态码或者gRPC的响应流。这么做有助于在未来应用演进时替换为另一种不同的机制。

    2.缺点

    同步消息的缺点如下。

    (1)服务之间耦合更紧,因为服务必须知道协作者的存在。

    (2)不能很好地支持广播模型以及发布-订阅模型。这限制了执行并行工作的能力。

    (3)在等待响应的时候,代码执行是被阻塞的。在基于线程或者进程的服务模型中,这可能会耗尽资源并触发连锁故障。

    (4)过度使用同步消息会导致出现很深的依赖链,而这又会增加调用路径整体的脆弱性。

    3.4.2 何时使用异步消息

    异步消息更加灵活。开发者可以通过事件通知的方式来扩展系统处理新的需求。因为服务不再需要了解下游的消费者。新服务可以直接消费已有的事件,而不需要对已有的服务进行修改。

    提示

    事件(event)表示了事后(post-hoc)的状态变化,例如,OrderCreated、OrderPlaced和OrderCanceled都是SimpleBank公司的order服务可以发出的事件。

    这种方式下,应用演化更加平滑,服务间的耦合更低。但是付出的代价是:异步交互更加难以理解,因为整个系统行为不再是那种显式的线性顺序。系统行为变得更加危险——服务间的交互变得不可预测——需要在监控上增加投入来充分地跟踪所发生的情况。

     注意

    有不同类型的事件持久化和事件查询的方式,如事件溯源(event sourcing)和命令查询的责任分离Command Query Responsibility Segregation,CQRS)。它们不是微服务的先决条件,但是与微服务方案会有协同效应。我们将在第5章中进行具体介绍。

    异步消息通常需要一个通信代理(communication broker),这是一个独立的系统组件,负责接收事件并把它们分发给对应的消费者。有时候也叫作事件中枢(event backbone),这也表明了这个组件对整个应用是多么重要。常用作代理的工具包括Kafka、RabbitMQ和Redis。这些工具的意义并不相同:Kafka专门研究的是海量的、可重复的事件存储,而RabbitMQ提供了一套高度抽象的消息中间件(基于AMQP协议)。

    3.4.3 异步通信模式

    我们来看两种常见的基于事件的模式:任务队列和发布-订阅。在对微服务进行架构设计时,开发者会经常遇到这两种模式——大部分更高级的交互模式是基于这两种基本模式实现的。

    1.作业队列

    在这种模式中,工作者(worker)从队列种接收任务并执行它(图3.11)。不管开发者运行了多少个工作者实例,一个作业应该只处理一次。这种模式也称作赢者通吃。

    图3.11 事件驱动的服务间异步通信

    消费者也并不清楚是哪个服务发出的事件,如图3.12所示。

    图3.12 一个任务队列将工作分发给1到n个消费者

    market网关就可以按照这种方式来进行操作。order服务创建的每个订单都会触发一个OrderCreated事件,这个事件会放到队列中供market网关服务来进行下单。这种模式在下列场景中很有用:

    (1)事件与响应该事件所要做的工作之间是一对一的关系;

    (2)要完成的工作比较复杂或者花费时间较长,所以需要和触发事件区分开。

    默认这种方式并不需要复杂的事件传输。有许多任务队列类库可以用,它们使用的就是日常的数据存储方案,如Redis(Resque、Celery、Sidekiq)或SQL数据库。

    2.发布-订阅

    在发布-订阅模式中,服务可以向任意的监听器发送事件。所有接收到事件的监听器都要对事件相应地做出反应。在某些情况下,这是一种理想的微服务模式:一个服务可以发送任意的事件给外界,而不需要关心谁来处理它们(图3.13)。

    图3.13 发布-订阅模式是如何将事件发送到订阅方的

    比如,想象一下,开发者需要在订单发布以后触发另一个下游操作。开发者可能要给用户发送一条推送通知,需要用它满足订单统计和推荐功能的需要。这些功能都可以通过监听同样的事件来实现。

    3.4.4 服务定位

    在结束本节之前,我们来研究一下服务发现(service discovery)。要实现服务间的通信,它们需要能够发现彼此。平台层应该提供这一功能。

    实现服务发现最简单的方式就是使用负载均衡器(图3.14)。比如,AWS上的弹性负载均衡器(ELB)就为服务分配了一个DNS名称并且负责管理底层节点的健康检查。这些节点是属于同一个虚拟机组的(在AWS中是自动扩容组)。

    图3.14 使用负载均衡器和DNS名称的服务发现方案

    这种方式是可行的,但是它无法处理那些更复杂的场景。如果想要将请求路由到不同版本的代码中支持“金丝雀部署”或者“灰度上线”呢?如果需要将请求路由到不同的数据中心呢?

    一种更加复杂先进的方式是使用类似Consul这样的注册中心。每个服务实例把它们自己注册到注册中心,然后注册中心会提供一个API来对这些服务的请求进行解析——实现方式可以是通过DNS,也可以是其他自定义的机制。这种方案如图3.15所示。

    服务发现需要依赖于所部署的应用的拓扑结构的复杂度。部署方式越复杂(比如多地部署),越要求服务发现的架构更具鲁棒性[1]。

     注意

    我们在第9章中部署Kubernetes时,开发者会了解到Kubernetes所使用的服务发现的方案。

    图3.15 以服务注册中心作为真实数据来源的服务发现方案

    3.5 服务边界

    边界层隐藏了内部服务的复杂交互,只展示了一个统一的外观。像移动App、网页用户界面或者物联网设备之类的客户端都可以和微服务应用进行交互。(这些客户端可以是自己进行开发的,也可以由消费应用的公共API的第三方来开发。)比如图3.16描述的SimpleBank公司的内部管理工具、投资网站、iOS和Android 应用以及公共API。

    边界层对内部的复杂度和变更进行了封装和抽象(图3.17)。比如,工程师可以为一个要列出所有历史订单的客户端提供一个固定不变的接口,但是,可以在经过一段时间以后完全重构这个功能的内部实现。如果没有边界层的话,客户端就需要对每个服务都了解很多信息,最后变得和系统的实现耦合越来越严重。

    图3.16 SimpleBank的客户端应用

    图3.17 边界层提供了服务层的外观,将内部的复杂性对消费者隐藏起来

    其次,边界层提供了访问数据和功能的方法,以便客户端可以使用适合自己的传输方式和内容类型来访问。比如,服务之间相互通信采用的是gRPC的方式,而对外的边界层可以暴露一个HTTP API给外部消费者,这种方式更适合外部应用使用。

    将这些功能合到一起,应用就变成了一个黑盒,通过执行各种(客户端并不知道的)操作来提供功能。开发者也可以在修改服务层时更加自信,因为客户端是通过一个入口来与服务层连接起来的。

    边界层还可以实现一些其他面向客户端的功能:认证和授权——验证API客户端的身份和权限;限流——对客户端的滥用进行防卫;缓存——降低后端整体的负载;日志和指标收集——可以对客户端的请求进行分析和监控。

    把这些边缘功能放到边界层可以将关注点的划分更加清晰——没有边界层的话,后端服务就需要独立实现这些事务,这会增加它们的复杂度。

    开发者同样可以在服务层中用边界来划分业务领域,比如,下单流程包括几个不同的服务,但是应该只有一个服务会暴露出其他业务领域可以访问的切入点(图3.18)。

     注意

    内部服务边界通常反应的是限界上下文:整个应用业务领域中关系比较紧密的有边界的业务子集。我们会在下一章探讨这部分内容。

    我们已经从整体角度介绍了边界层的用法,接下来具体研究3种相关但又不同的应用边界模式:API网关、服务于前端的后端以及消费者驱动的网关。

    图3.18 边界也可以存在于在微服务网应用的不同情境上下文中

    3.5.1 API网关

    我们在第2章介绍了API网关模式。API网关在底层的服务后端之上为客户端提供了一个统一的入口点。它会代理发给下层服务的请求并对它们的返回结果进行一定的转换。API网关也可以处理一些客户端关注的其他横向问题,比如认证和请求签名。

     提示

    可选的API网关包括Mashape公司的Kong这样的开源方案,也包括类似AWS API Gateway这样的商业产品。

    API网关如图3.19所示。网关会对请求进行认证,如果认证通过,它就会将请求代理到对应的后端服务上。它还会对收到的结果进行转换,这样返回的数据更适合客户端。

    图3.19 API网关处理客户端请求

    从安全的角度来看,网关也能够将系统的暴露范围控制到最小。我们可以将内部的服务部署到一个专用网络中,限制除网关以外的所有请求进入。

     警告

    有时候,API网关会执行API组合(composition)的工作:将多个服务的返回结果汇总到一个结果中。它和服务层的聚合模式的界限很模糊。最好注意一些,尽量避免将业务逻辑渗透到API网关中,这会极大增加网关和下层服务之间的耦合度。

    3.5.2 服务于前端的后端

    服务于前端的后端(BFF)模式是API网关模式的一种变形。尽管API网关模式很简洁,但是它也存在一些缺点。如果API网关为多个客户端应用充当组合点的角色,它承担的职责就会越来越多。

    比如,假设开发者同时服务于桌面应用和移动应用。移动设备会有不同的需要,可用带宽低,展示数据少,用户功能也会有不同,如定位和环境识别。在操作层面,这意味着桌面API与移动API的需求会出现分歧,因此开发者需要集成到网关的功能就会越来越宽泛。不同的需求可能还会相互冲突,比如某个指定资源返回的数据量的多少(以及体积的大小)。在开发内聚的API和对API进行优化时,在这些相互竞争的因素间进行平衡是很困难的。

    在BFF方案中,开发者会为每种客户端类型提供一个API网关。以SimpleBank公司为例,它们提供的每种服务都有一个自己的网关(图3.20)。

    图3.20 SimpleBank公司的客户端应用的BFF模式

    这么做的话,网关就是高度专一的,对于消费者的需求响应更加及时,而不会导致臃肿或者冲突。这样的网关规模更小,更加简单,也使得开发过程可以更加集中。

    3.5.3 消费者驱动网关

    在前面两种模式中,API网关决定了返回给消费者的数据的结构。为了服务于不同的客户端,开发者可能需要开发不同的后台。现在我们反其道而行之。如果开发者可以开发一个允许消费者向服务端表达它们所需要的数据格式,会怎样呢?可以把它看作 BFF 方案的一种进化版:不构建多个API,开发者可以只构建一个“超级”API来让消费者决定他们所需要的响应数据的样子。

    开发者可以通过GraphQL来实现上述目标。GraphQL是一种用于API的查询语言,它允许消费者指定他们需要的数据字段以及将多个不同资源的查询复用到一个请求中。比如,开发者可以将代码清单3.1所示的格式暴露给SimpleBank客户端。

    代码清单3.1 SimpleBank的基本GraphQL格式

    type Account { 
      id: ID!     ⇽---  “!”表明这个字段不可以为空                    
      name: String!
      currentHoldings: [Holding]!     ⇽---  一个账号包含一组持仓和订单信息             
      orders: [Order]!
    }
    
    type Order {
      id: ID!
      status: String!
      asset: Asset!
      quantity: Float!
    }
    
    type Holding {
      asset: Asset!
      quantity: Float!
    }
    
    type Asset {
      id: ID!
      name: String!
      type: String!
      price: Float!
    }
    
    type Root {                         
      accounts: [Account]!     ⇽---  返回所有数据或者按ID返回一个账户          
      account(id: ID): Account      
    } 
    
    schema: {
      query: Root    ⇽---  只有一个查询入口           
    }

    上述格式展示了消费者的账户以及每个账户所包含的订单和所持股份。客户端之后就可以按照这种格式来进行查询。如果移动应用屏幕显示出某个账户的所持股份和未完成订单,开发者就可以用一个请求来获取这些数据,如代码清单3.2所示。

    代码清单3.2 使用GraphQL的查询体

    {
      account(id: "101") {   ⇽---  按账户ID进行过滤
        orders      ⇽---  在请求中指定响应结果中返回成员字段                    
        currentHoldings                
      }
    }

    在后端,GraphQL服务器的表现像API网关,代理多个后端服务的请求并将数据进行组合(在本例中order服务和holding服务)。我们不会在本书中将进一步详细地介绍GraphQL,如果读者感兴趣的话,可以查看GraphQL的官方文档。我们用Apollo成功地将RESTful 的后端服务封装成了GraphQL API的格式。

    3.6 客户端

    与三层架构中的展示层一样,客户端层为用户提供了一个应用界面。将客户端层与下面的其他几层进行分离,就可以以细粒度的方式来开发用户界面,并且可以满足不同类型的客户端的需求。这也意味着,开发者可以独立于后端的功能来开发前端。正如前面几节中提到的,应用可能需要服务于许多形形色色的客户端——移动设备、网站、对内的和对外的——每种客户端都有自己不同的技术选型和限制。

    微服务为它自己的用户界面提供服务的情况并不常见。通常来说,提供给特定用户的功能要比单个服务的功能要更广泛。比如,SimpleBank的管理人员需要处理订单管理、账户创建、对账、收税等工作。与之相随的是那些横向事务——认证、审计日志、用户管理——显然,它们并不是order服务或者account setup服务的职责。

    3.6.1 前端单体

    后端很明确地被分成了一个个可以独立部署维护的服务——相应地,我们还有10章的内容要介绍。但是在前端也完成这个工作是非常具有挑战性的。一个微服务应用中的标准前端可能依旧是一个单体——前端作为一个整体来部署和修改(图3.21)。专业的前端,特别是移动应用,通常需要专门的小组,这也使得端到端的功能所有权很难实现。

     注意

    我们会在第13章对端到端的所有权(以及在微服务应用开发中的益处和价值)展开进一步的讨论。

    图3.21 微服务应用中,标准的前端客户端可能变得越来越臃肿

    3.6.2 微前端

    随着前端应用的不断发展,它们开始和大规模的后端开发一样面临协作和摩擦问题。

    如果可以像拆分后端服务那样将前端部分也进行拆分,那就太好了。在Web应用中出现的一个新趋势是微前端——以独立打包和部署的组件来提供UI的各个部分的功能,然后合并起来。这一方式如图3.22所示。

    图3.22 由各个独立片段组成的用户界面

    这样,每个微服务团队就可以端到端地交付功能了。比如,如果开发者有一个订单团队,就可以独立地一起交付订单管理微服务以及负责下单和管理订单的Web界面。

    虽然这种方式很有前途,但是也面临许多挑战:在不同的组件间保持视觉和交互的一致性,需要很大的精力来开发和维护通用组件以及设计准则;当需要从多个源头加载JavaScript代码时,Bundle的大小是难以管理的,进而它又会影响加载时间;接口重载和重绘可能会导致整体的性能变差。

    微前端还不是很普遍,但是人们已经在这块荒地上使用了一些不同的技术方案,其中包括:Web组件通过清晰的、事件驱动的API来提供UI片段;使用客户端包含(client-side include)技术来集成片段;使用iframe来将微app放置到不同的屏幕区域;在缓存层使用ESI(edge side include)来集成组件。

    如果开发者有兴趣了解更多内容的话,可参考Micro Frontends和Zalando的Mosaic项目。

     

    展开全文
  • 应用层的基本概念

    千次阅读 2021-03-25 23:38:23
    应用层的功能 每个应用层协议都是为了解决某一类应用问题,而这种问题的解决又必须通过位于不同主机中的多个应用进程之间的通信和协同工作完成。应用进程之间的这种通信情况必须严格遵守这些规则。 所以应用层的具体...
  • [计算机网络]第二章——应用层

    千次阅读 2022-04-03 20:02:21
    [计算机网络]第二章——应用层
  • 计算机网络——应用层

    千次阅读 多人点赞 2021-08-15 16:22:59
    应用层直接为用户提供服务应用层有很多协议,每一个协议对应着计算机上的一个服务。 不同类型的网络应用有不同的通信规则,因此应用层协议是多种多样的,比如DNS、FTP、Telnet、SMTP、HTTP、RIP、NFS等协议都是...
  • 关于TCP/IP模型的简单介绍,在TCP/IP五层模型一文中有简单介绍,本文主要详细介绍其中的应用层相关内容。首先介绍一个定义: 应用层协议:在网络版加法计算器一文中,我们介绍的实现网络版本的加法计算器有两种方法...
  • 应用层协议和传输层协议

    千次阅读 2022-01-17 09:32:21
    osi七层模型和tcp/ip五层模型功能划分基本一样,区别在于tcp/ip模型高度概括了应用层。 虽然看起来分层,但实际上这些层只是人为抽象,物理层面上并存在。传输时就是电信号,类比发报机(嗒嗒嗒)。 我们现在通常...
  • 基于网络层和应用层的DDoS攻击

    万次阅读 2017-11-13 11:54:46
     DDoS攻击是由DoS攻击发展而来的,根据攻击原理和方式的区别,可以把DDoS攻击分为两个阶段,即从传统的基于网络层的DDoS攻击和现阶段较为常见的基于应用层的DDoS攻击,这两类攻击方式各有特点,都对网络的安全造成...
  • OSI第七层:应用层功能及介绍

    万次阅读 多人点赞 2019-01-16 18:59:55
    OSI应用层功能:应用层提供各种各样的应用层协议,这些协议嵌入在各种我们使用的应用程序中,为用户与网络之间提供一个打交道的接口。 OSI应用层的作用 当我们第一次学习网络,对网络的概念不会很直观,我们使用...
  • 2017计算机网络技术练习题一、练习题1、以下对IP地址分配中描述正确的是A 同一网络上每台主机必须分配唯一的主机IDB 网络ID能以127开头C 同一网络上每台主机必须有不同的网络IDD 网络ID能全为1或全为02、对...
  • OSI七层参考模型中的应用层、传输层和网络层所使用的协议: 应用层协议 文件传输协议FTP 从网络上下载文件时使用的是FTP协议。 超文本传输协议HTTP 上网浏览网页时使用的是HTTP协议。 域名服务DNS DNS也是一种应用...
  • DoS是Denial of Service的简称,即拒绝服务,造成DoS的攻击行为被称为DoS攻击,其目的是使计算机或网络无法提供正常的服务。...应用层的可能导致DOS攻击的主要有:注入攻击、权限控制、资源耗尽、Serv
  • 计算机网络第六章答案 (复习六 应用层

    千次阅读 多人点赞 2020-10-20 22:53:37
     答:应用层协议需要的是DNS。运输层协议需要的是UDP(DNS)使用和TCP(HTTP 使用)。 11 你所使用的浏览器的高速缓存有多大?请进行一个试验:访问几个万维网文档,然后将你的计算机与网络断开,然后再回到你...
  • 一、基于TCP的应用层协议有:SMTP、TELNET、HTTP、FTP 基于UDP的应用层协议:DNS、TFTP(简单文件传输协议)、RIP(路由选择协议)、DHCP、BOOTP(是DHCP的前身)、IGMP(Internet组管理协议) ...
  • IPsec并是一个单一协议,而是能够在IP提供互联网通信安全的协议族。IPsec并没有限定用户必须使用何种特定的加密和鉴别算法。实际上,IPsec是个框架,它允许通信双方选择合适的算法和参数(例如:密钥长度)。...
  • 通信协议之应用层

    千次阅读 2018-01-15 15:24:28
    应用层包含所有的高层协议,例如FTP (File Transfer Protocol的简写,中文名称是文件传输协议)、SMTP (Simple Mail Transfer Protocol的简写,中文名称是简单电子邮件传输协议)、DNS (Domain Name Service的简写,...
  • 云计算服务层次的划分与介绍

    千次阅读 2019-04-13 21:01:21
    云计算软件和数据均存储在数据中心,并通过Web方式提供通用的商业应用服务,而用户则采用浏览器访问在线应用服务。 云计算一般被认为包括几个层次的服务:基础设施即服务(IaaS),平台即服务(PaaS)和软件即服务(SaaS)。 ...
  • 来自应用层的攻击

    千次阅读 2011-01-10 10:40:56
    本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝、转载,转载时请...应用层的攻击表现形式很多,但基于的原理就那几种,有的是针对应用层协议的,有的是针对应用程序的,都统并在一起吧。 [code="j...
  • 初识华为防火墙应用层过滤技术

    千次阅读 2019-09-03 08:35:55
    一、应用层过滤有哪些? 1、文件类型过滤 2、内容过滤 3、URL过滤 一、应用层过滤有哪些? 文件类型过滤:主要针对不同类型(扩展名不同)的文件过滤,USG防火墙可以识别数据包携带的应用层文件类型。其检查...
  • 根据之前对计算机网络OSI参考模型的学习,我们知道网络体系结构有7层,前期已经学习了网络的第一、二和三层,为了对网络模型有个整体的认知,同样需要了解网络的传输层、会话层、表示层和应用层。 一、传输层...
  • 计算机网络--运输层--应用层

    千次阅读 2019-05-17 21:23:37
    从通信和信息处理的角度看,运输层向它上面的应用层提供通信服务,它属于面向通信部分的最高层,同时也是用户功能中的最低层。 当网络边缘部分的两台主机使用网络的核心部分的功能进行端到端的...
  • 计算机网络——从物理层到应用层

    千次阅读 2018-10-16 21:25:32
    每一都有自己的功能,就像建筑物一样,每一都靠下一支持。 用户接触到的,只是最上面的一,根本没有感觉到下面的。要理解互联网,必须从最下层开始,自下而上理解每一的功能。 如何分层有不同的模型,...
  • 从以太坊诞生、0网络建立,经过8年时间的建设,区块链0、1网络环境已经初具雏形,跨链与多链共生已经成为现实。接下来,区块链的未来会走向何方? 毋庸置疑,从早期区块链技术兴起到如今Web3.0概念重塑,...
  • 应用层主要包含的协议有:文件传送协议FTP、超文本传送协议HTTP FTP提供交互式的访问,允许客户指明文件的类型与格式,并允许文件具有存取权限。FTP屏蔽了各计算机系统的细节,因而适合于在异构网络中任意计算机...
  • 计算机网络应用层1练习题

    千次阅读 2020-04-24 21:24:52
    以下属于应用层协议的是( )。 A)FTPB)IP C)ARPD)TCP 10.在下面常用的端口号中,默认的用于FTP服务的TCP端口的是( )。 A)80 B)23 C)21 D)25 11.在下面常用的端口号中,默认的用于HTTP服务的TCP端口的是...
  • 本文是计算机网络中最顶层——应用层的知识讲解,主要是万维网的相关知识和HTTP、SMTP等协议的讲解。
  • 对上一提供服务的时候,上一的数据结构是黑盒,直接作为本的数据,而需要关心上一协议的任何细节。 分层模型的封装思想奠定了各种不同类型的设备和网络之间互联的基础,其中核心的协议是 IP,IP 提供...
  • OSI 七模型通过七个层次化的结构模型使不同的系统不同的网络之间实现可靠的通讯,因此其最主要的功能就是帮助不同类型的主机实现数据传输 。 完成中继功能的节点通常称为中继系统。在OSI七模型中,处于不同的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 287,454
精华内容 114,981
关键字:

以下不属于应用层服务的是