写点什么

微服务框架相关技术整理

用户头像
云流
关注
发布于: 2021 年 03 月 02 日

微服务整体框架

开发前后台分离:前台与后台之间,通过 Restful 风格接口通信(HTTP 协议)

内部服务:Dubbo( RPC 框架)

外部服务:SpringCloud Zuul(提供 Restful API 接口)

微服务应用开发

API Gateway

  • API Gateway:网关,统一应用请求接口.API 网关在微服务们的最前端,让 API 网关变成由应用所发起的每个请求的入口,简化客户端实现和微服务应用程序间的沟通方式。

API Gateway 两种方式:

单节点 API Gateway

BFF (Backends for frontends) Gateway

API Gateway 的作用
  • 请求路由,版本控制: API Gateway 是微服务的入口,可以根据不同的请求路由到不同的服务上. 也可以进行路由的版本控制,这样即使后服务发生了变化,Gateway 的路径依然可以不改变

  • 用户登录,权限认证: 客户端在与我们后端服务进行交互之前,由 API Gateway 先进行登录鉴权操作,这是后端所有的服务都需要有的共有逻辑

  • 数据聚合: 由于不同的客户端往往需要的数据完全不同,而这些数据又是不同的 service 提供的,可以借助 Gateway 方便完成来自不同 service 的数据聚合

  • 协议转换: 在项目实践中,CS(Client to Server)协议和 SS(Server to Server)协议是不一样的,为了保证数据传输的可靠性,CS 协议会有鉴权以及加密解密的逻辑,而在内部的 SS 协议则不需要这些逻辑,因此在 Gateway 我们需要有一个协议转换的过程

  • 熔断,降级,限流: 通过 API Gateway 可以在监测到某个服务发生异常,或者当服务的流量超过服务的承载能力等情况时,可以采取相应的措施. 提高整个系统的容错性、稳定性

  • 负载均衡: API Gateway 知道所有服务实例的地址,可以根据不同服务采取不同的负载均衡策略

  • 灰度发布: 灰度发布允许直接只导入指定量的流量请求到新的版本

API Gateway 的架构



  • 多网关集群(Backends for frontends): 针对不同的客户端,都有相应的网关层来接入.功能主要有:用户登录,鉴权,服务发现注册,协议转换,接口版本控制等以及监控,APM 调用链,日志,流控策略等

  • 聚合服务(Merge Service): 在某些客户端的需求中,需要从多个服务拉取数据,为了减少客户端的复杂度,以及加快客户端的访问速度,可以加一个聚合层,用来做聚合查询,在某些接口中可以把多个服务的数据一次性返回给客户端

  • 仪表盘管理端(Dashboard): Dashboard 提供可视化的分析平台,包括服务的管理,监控数据报警配置,日志查询,灰度发布操作,API 文档管理等

Eureka(服务发现框架)

  • Eureka 是一个基于 REST 的服务,主要用于定位运行在 AWS 域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的. SpringCloud 将它集成在其子项目 spring-cloud-netflix 中,以实现 SpringCloud 的服务发现功能

Eureka 的两个组件
  • Eureka Server: Eureka Server 提供服务注册服务,各个节点启动后,会在 Eureka Server 中进行注册,这样 EurekaServer 中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中看到. Eureka Server 之间通过复制的方式完成数据的同步

  • Eureka Client: 是一个 java 客户端,用于简化与 Eureka Server 的交互,客户端同时也就是一个内置的、使用轮询(round-robin)负载算法的负载均衡器

  • Eureka 通过心跳检查、客户端缓存等机制,确保了系统的高可用性、灵活性和可伸缩性在应用启动后,将会向 Eureka Server 发送心跳, 如果 Eureka Server 在多个心跳周期内没有接收到某个节点的心跳,Eureka Server 将会从服务注册表中把这个服务节点移除。Eureka 还提供了客户端缓存机制,即使所有的 Eureka Server 都挂掉,客户端依然可以利用缓存中的信息消费其他服务的 API。Eureka 通过心跳检查、客户端缓存等机制,确保了系统的高可用性、灵活性和可伸缩性

RPC 框架

RPC 定义
  • RPC(Remote Procedure Call Protocol): 远程过程调用协议,一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议.也就是

客户端在不知道调用细节的情况下,调用存在于远程计算机上的某个对象,就像调用本地应用程序中的对象一样
复制代码

RPC 是协议: 协议就是一套规范,目前典型的 RPC 实现包括:Dubbo,Thrift,GRPC,Hetty 等.从目前技术的发展趋势来看,实现了 RPC 协议的应用工具往往都会附加其他重要功能

网络协议和网络 IO 模型对其透明: 既然 RPC 的客户端认为自己是在调用本地对象。那么传输层使用的是 TCP/UDP 还是 HTTP 协议,又或者是一些其他的网络协议它就不需要关心了。既然网络协议对其透明,那么调用过程中,使用的是哪一种网络 IO 模型调用者也不需要关心

信息格式对其透明: 我们知道在本地应用程序中,对于某个对象的调用需要传递一些参数,并且会返回一个调用结果。至于被调用的对象内部是如何使用这些参数,并计算出处理结果的,调用方是不需要关心的。那么对于远程调用来说,这些参数会以某种信息格式传递给网络上的另外一台计算机,这个信息格式是怎样构成的,调用方是不需要关心的

应该有跨语言能力: 调用方实际上也不清楚远程服务器的应用程序是使用什么语言运行的。那么对于调用方来说,无论服务器方使用的是什么语言,本次调用都应该成功,并且返回值也应该按照调用方程序语言所能理解的形式进行描述

RPC 主要组成部分



  • Client: RPC 协议的调用方.最理想的情况是 RPC Client 在完全不知道有 RPC 框架存在的情况下发起对远程服务的调用.但实际情况来说 Client 或多或少的都需要指定 RPC 框架的一些细节

  • Server: 在 RPC 规范中,这个 Server 并不是提供 RPC 服务器 IP,端口监听的模块。而是远程服务方法的具体实现(在 JAVA 中就是 RPC 服务接口的具体实现).其中的代码是最普通的和业务相关的代码,甚至其接口实现类本身都不知道将被某一个 RPC 远程客户端调用

  • Stub/Proxy: RPC 代理存在于客户端,因为要实现客户端对 RPC 框架“透明”调用,那么客户端不可能自行去管理消息格式、不可能自己去管理网络传输协议,也不可能自己去判断调用过程是否有异常。这一切工作在客户端都是交给 RPC 框架中的“代理”层来处理的

  • Message Protocol: 一次完整的 client-server 的交互肯定是携带某种两端都能识别的,共同约定的消息格式.RPC 的消息管理层专门对网络传输所承载的消息信息进行编码和解码操作.目前流行的技术趋势是不同的 RPC 实现,为了加强自身框架的效率都有一套(或者几套)私有的消息格式

  • Transfer/Network Protocol: 传输协议层负责管理 RPC 框架所使用的网络协议,网络 IO 模型. 传输层还需要统一 RPC 客户端和 RPC 服务端所使用的 IO 模型

  • Selector/Processor: 存在于 RPC 服务端,用于服务器端某一个 RPC 接口的实现的特性(它并不知道自己是一个将要被 RPC 提供给第三方系统调用的服务).所以在 RPC 框架中应该有一种 "负责执行 RPC 接口实现" 的角色.包括:管理 RPC 接口的注册,判断客户端的请求权限,控制接口实现类的执行在内

  • IDL: IDL(接口定义语言)并不是 RPC 实现中所必须的.但是需要跨语言的 RPC 框架一定会有 IDL 部分的存在.这是因为要找到一个各种语言能够理解的消息结构、接口定义的描述形式.如果 RPC 实现没有考虑跨语言性,那么 IDL 部分就不需要包括,例如 JAVA RMI 因为就是为了在 JAVA 语言间进行使用,所以 JAVA RMI 就没有相应的 IDL

不同的 RPC 框架实现都有一定设计差异。例如生成 Stub 的方式不一样,IDL 描述语言不一样、服务注册的管理方式不一样、运行服务实现的方式不一样、采用的消息格式封装不一样、采用的网络协议不一样。但是基本的思路都是一样的,上图中的所列出的要素也都是具有的

影响 RPC 框架性能的因素



  • 使用的网络 IO 模型: RPC 服务器可以只支持传统的阻塞式同步 IO,也可以做一些改进让 RPC 服务器支持非阻塞式同步 IO,或者在服务器上实现对多路 IO 模型的支持.这样的 RPC 服务器的性能在高并发状态下,会有很大的差别.特别是单位处理性能下对内存,CPU 资源的使用率

  • 基于的网络协议: 一般来说可以选择让 RPC 使用应用层协议,例如 HTTP 或者 HTTP/2 协议,或者使用 TCP 协议.让 RPC 框架工作在传输层.工作在哪一层网络上会对 RPC 框架的工作性能产生一定的影响,但是对 RPC 最终的性能影响并不大.但是至少从各种主流的 RPC 实现来看,没有采用 UDP 协议做为主要的传输协议的

  • 消息封装格式: 选择或者定义一种消息格式的封装,要考虑的问题包括:消息的易读性,描述单位内容时的消息体大小,编码难度,解码难度,解决半包/粘包问题的难易度. 当然如果您只是想定义一种 RPC 专用的消息格式,那么消息的易读性可能不是最需要考虑的.消息封装格式的设计是目前各种 RPC 框架性能差异的最重要原因,这就是为什么几乎所有主流的 RPC 框架都会设计私有的消息封装格式的原因.dubbo 中消息体数据包含 dubbo 版本号,接口名称,接口版本,方法名称,参数类型列表,参数,附加信息

  • 序列化和反序列化(Schema & Data Serialization): 序列化和反序列化,是对象到二进制数据的转换,程序是可以理解对象的,对象一般含有 schema 或者结构,基于这些语义来做特定的业务逻辑处理.

序列化框架一般会关注以下几点:Encoding format:是human readable(是否能直观看懂 json)还是binary(二进制)Schema declaration:也叫作契约声明,基于IDL,比如 Protocol Buffers/Thrift.还是自描述的,比如 JSON、XML.另外还需要看是否是强类型的语言平台的中立性:比如Java的Native Serialization就只能自己玩,而Protocol Buffers可以跨各种语言和平台新老契约的兼容性:比如IDL加了一个字段,老数据是否还可以反序列化成。 和压缩算法的契合度 :运行benchmark(基准)和实际应用都会结合各种压缩算法,例如gzip,snappy 性能 :这是最重要的,序列化,反序列化的时间,序列化后数据的字节大小是考察重点。 
序列化方式非常多,常见的有Protocol Buffers,Avro,Thrift,XML,JSON,MessagePack,Kyro,Hessian,Protostuff,Java Native Serialize,FST
复制代码
  • 实现的服务处理管理方式: 在高并发请求下,如何管理注册的服务也是一个性能影响点.可以让 RPC 的 Selector/Processor 使用单个线程运行服务的具体实现(这意味着上一个客户端的请求没有处理完,下一个客户端的请求就需要等待). 也可以为每一个 RPC 具体服务的实现开启一个独立的线程运行(可以一次处理多个请求,但是操作系统对于“可运行的最大线程数”是有限制的). 也可以线程池来运行 RPC 具体的服务实现(目前看来,在单个服务节点的情况下,这种方式是比较好的). 还可以通过注册代理的方式让多个服务节点来运行具体的 RPC 服务实现

工业界的 RPC 框架
如何选择 RPC 框架

选择一个 rpc 框架会基于多方面的考虑:框架特性、性能、成熟度、技术支持、社区活跃度等多个方面.最重要一点,这也是往往很多技术人员进入的误区, "对于技术,不要为了使用而使用,用最简单合适的技术实现解决问题才是正道" .架构是服务于业务的,能快速方便的满足业务需求的架构才是好的架构.没有最好的,只有适合自己的

Dubbo

  • Dubbo 是一个开源分布式服务框架,阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring 框架无缝集成.

  • Dubbo 是一款高性能,轻量级的开源 Java RPC 框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现

核心组件
  • Remoting: 网络通信框架,实现了 sync-over-async request-response 消息机制

  • RPC: 一个远程过程调用的抽象.支持负载均衡,容灾和集群功能

  • Registry: 服务目录框架,用于服务的注册和服务事件发布和订阅

工作原理



Provider:暴露服务方称之为“服务提供者”Consumer:调用远程服务方称之为“服务消费者”Registry:服务注册与发现的中心目录服务称之为“服务注册中心”Monitor:统计服务的调用次数和调用时间的日志服务称之为“服务监控中心”
连通性:注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示服务提供者向注册中心注册其提供的服务,并汇报调用时间到监控中心,此时间不包含网络开销服务消费者向注册中心获取服务提供者地址列表,并根据负载算法直接调用提供者,同时汇报调用时间到监控中心,此时间包含网络开销注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表注册中心和监控中心都是可选的,服务消费者可以直连服务提供者
健壮性:监控中心宕掉不影响使用,只是丢失部分采样数据数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务注册中心对等集群,任意一台宕掉后,将自动切换到另一台注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯服务提供者无状态,任意一台宕掉后,不影响使用服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
伸缩性:注册中心为对等集群,可动态增加机器部署实例,所有客户端将自动发现新的注册中心服务提供者无状态,可动态增加机器部署实例,注册中心将推送新的服务提供者信息给消费者
复制代码
Dubbo 特性
  • 面向接口代理的高性能 RPC 调用: 提供高性能的基于代理的远程调用能力,服务以接口为粒度,为开发者屏蔽远程调用底层细节

  • 智能负载均衡: 内置多种负载均衡策略,智能感知下游节点健康状况,显著减少调用延迟,提高系统吞吐量

  • 服务自动注册与发现: 支持多种注册中心服务,服务实例上下线实时感知

  • 高度可扩展能力: 遵循微内核+插件的设计原则,所有核心能力如 Protocol,Transport,Serialization 被设计为扩展点,平等对待内置实现和第三方实现

  • 运行期流量调度: 内置条件,脚本等路由策略.通过配置不同的路由规则,轻松实现灰度发布,同机房优先等功能

  • 可视化的服务治理与运维: 提供丰富服务治理,运维工具:随时查询服务元数据,服务健康状态及调用统计,实时下发路由策略,调整配置参数

使用示例





Zuul

Zuul 是 netflix 开源的一个 API Gateway 服务器, 本质上是一个 web servlet 应用

-Zuul 是一个基于 JVM 路由和服务端的负载均衡器,提供动态路由,监控,弹性,安全等边缘服务的框架,相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门

Zuul 工作原理

过滤器机制

  • Zuul 提供了一个框架,可以对过滤器进行动态的加载,编译,运行

1.Zuul的过滤器之间没有直接的相互通信,他们之间通过一个RequestContext的静态类来进行数据传递的。RequestContext类中有ThreadLocal变量来记录每个Request所需要传递的数据2.Zuul的过滤器是由Groovy写成,这些过滤器文件被放在Zuul Server上的特定目录下面,Zuul会定期轮询这些目录,修改过的过滤器会动态的加载到Zuul Server中以便过滤请求使用
复制代码

标准过滤器类型:

Zuul 大部分功能都是通过过滤器来实现的。Zuul 中定义了四种标准过滤器类型,这些过滤器类型对应于请求的典型生命周期

  • PRE: 在请求被路由之前调用,利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等

  • ROUTING: 请求路由到微服务,用于构建发送给微服务的请求,使用 Apache HttpClient 或 Netfilx Ribbon 请求微服务

  • POST: 在路由到微服务以后执行,用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等

  • ERROR: 在其他阶段发生错误时执行该过滤器

内置的特殊过滤器:

  • StaticResponseFilter: StaticResponseFilter 允许从 Zuul 本身生成响应,而不是将请求转发到源

  • SurgicalDebugFilter: SurgicalDebugFilter 允许将特定请求路由到分隔的调试集群或主机

自定义的过滤器:

除了默认的过滤器类型,Zuul 还允许我们创建自定义的过滤器类型。如 STATIC 类型的过滤器,直接在 Zuul 中生成响应,而不将请求转发到后端的微服务

过滤器的生命周期

Zuul 请求的生命周期详细描述了各种类型的过滤器的执行顺序

过滤器调度过程

动态加载过滤器

Zuul 的作用

Zuul 可以通过加载动态过滤机制实现 Zuul 的功能:

  • 验证与安全保障: 识别面向各类资源的验证要求并拒绝那些与要求不符的请求

  • 审查与监控: 在边缘位置追踪有意义数据及统计结果,得到准确的生产状态结论

  • 动态路由: 以动态方式根据需要将请求路由至不同后端集群处

  • 压力测试: 逐渐增加指向集群的负载流量,从而计算性能水平

  • 负载分配: 为每一种负载类型分配对应容量,并弃用超出限定值的请求

  • 静态响应处理: 在边缘位置直接建立部分响应,从而避免其流入内部集群

  • 多区域弹性: 跨越 AWS 区域进行请求路由,旨在实现 ELB 使用多样化并保证边缘位置与使用者尽可能接近

Zuul 与应用的集成方式

ZuulServlet - 处理请求(调度不同阶段的 filters,处理异常等)

所有的 Request 都要经过 ZuulServlet 的处理,

Zuul 对 request 处理逻辑的三个核心的方法: preRoute(),route(), postRoute()

ZuulServletZuulServlet 交给 ZuulRunner 去执行。由于 ZuulServlet 是单例,因此 ZuulRunner 也仅有一个实例。ZuulRunner 直接将执行逻辑交由 FilterProcessor 处理,FilterProcessor 也是单例,其功能就是依据 filterType 执行 filter 的处理逻辑

FilterProcessor 对 filter 的处理逻辑:

1.首先根据Type获取所有输入该Type的filter:List<ZuulFilter> list2.遍历该list,执行每个filter的处理逻辑:processZuulFilter(ZuulFilter filter)3.RequestContext对每个filter的执行状况进行记录,应该留意,此处的执行状态主要包括其执行时间、以及执行成功或者失败,如果执行失败则对异常封装后抛出4.到目前为止,Zuul框架对每个filter的执行结果都没有太多的处理,它没有把上一filter的执行结果交由下一个将要执行的filter,仅仅是记录执行状态,如果执行失败抛出异常并终止执行
复制代码

ContextLifeCycleFilter - RequestContext 的生命周期管理:

  • ContextLifecycleFilter 的核心功能是为了清除 RequestContext;请求上下文 RequestContext 通过 ThreadLocal 存储,需要在请求完成后删除该对象 RequestContext 提供了执行 filter Pipeline 所需要的 Context,因为 Servlet 是单例多线程,这就要求 RequestContext 即要线程安全又要 Request 安全。context 使用 ThreadLocal 保存,这样每个 worker 线程都有一个与其绑定的 RequestContext,因为 worker 仅能同时处理一个 Request,这就保证了 Request Context 即是线程安全的由是 Request 安全的。

GuiceFilter - GOOLE-IOC(Guice 是 Google 开发的一个轻量级,基于 Java5(主要运用泛型与注释特性)的依赖注入框架(IOC).Guice 非常小而且快.)

StartServer - 初始化 zuul 各个组件(ioc,插件,filters,数据库等)

FilterScriptManagerServlet - uploading/downloading/managing scripts, 实现热部署

Filter 源码文件放在 zuul 服务特定的目录, zuul server 会定期扫描目录下的文件的变化,动态的读取\编译\运行这些 filter,如果有 Filter 文件更新,源文件会被动态的读取,编译加载进入服务,接下来的 Request 处理就由这些新加入的 filter 处理

React 前端框架

React 定义
  • React 前端框架是 Facebook 开源的一个 js 库,用于动态构建用户界面.

  • React 解决的问题:数据绑定的时候,大量操作真实 dom,性能成本太高网站的数据流向太混乱,不好控制

  • React 把用户界面抽象成一个个组件.如按钮组件 Button,对话框组件 Dialog,日期组件 Calendar.开发者通过组合这些组件,最终得到功能丰富,可交互的页面.通过引入 JSX 语法,复用组件变得非常容易,同时也能保证组件结构清晰.有了组件这层抽象,React 把代码和真实渲染目标隔离开来,除了可以在浏览器端渲染到 DOM 来开发网页外,还能用于开发原生移动应用

React 核心

虚拟 DOM 是 React 的基石,React 的核心是组件,React 的精髓是函数式编程 ,在 React 中是单向响应的数据流

组件的设计目的是提高代码复用率,降低测试难度和代码复杂度:
提高代码复用率:组件将数据和逻辑封装,类似面向对象中的类 降低测试难度:组件高内聚低耦合,很容易对单个组件进行测试 降低代码复杂度:直观的语法可以极大提高可读性
复制代码
React 特点
  • JSX: JSX 是 JavaScript 语法的扩展

  • 组件: 通过 React 构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中

  • 单向响应的数据流: React 实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单

  • Declarative(声明式编码): React 采用声明范式,可以轻松描述应用(自动 dom 操作)

  • Component-Based(组件化编码)

  • Learn Once,Write Anywhere(支持客户端与服务器渲染)

  • 高效:React 通过对 DOM 的模拟(虚拟 dom),最大限度地减少与 DOM 的交互

1.虚拟(virtual)DOM, 不总是直接操作DOM,减少页面更新次数;2.高效的DOM Diff算法, 最小化页面重绘;
复制代码
  • 灵活:React 可以与已知的库或框架很好地配合

React 的虚拟 DOM

传统 DOM 更新

真实页面对应一个 DOM 树.在传统页面的开发模式中,每次需要更新页面时,都要手动操作 DOM 来进行更新

虚拟 DOM

DOM 操作非常昂贵.我们都知道在前端开发中,性能消耗最大的就是 DOM 操作,而且这部分代码会让整体项目的代码变得难以维护.React 把真实 DOM 树转换成 JavaScript 对象树,也就是 Virtual DOM

  • 虚拟 DOM 定义:一个虚拟 DOM(元素)是一个一般的 js 对象,准确的说是一个对象树(倒立的)虚拟 DOM 保存了真实 DOM 的层次关系和一些基本属性,与真实 DOM 一一对应如果只是更新虚拟 DOM, 页面是不会重绘的

  • Virtual DOM 算法步骤:用 JS 对象树表示 DOM 树的结构.然后用这个树构建一个真正的 DOM 树插入到文档中当状态变更的时候,重新构造一棵新的对象树.然后用新的树和旧的树进行比较,记录两棵树差异把差异应用到真实 DOM 树上,视图就更新了

  • 进一步理解:Virtual DOM 本质上就是在 JS DOM 之间做了一个缓存

可以类比CPU和硬盘,既然硬盘这么慢,我们就在它们之间加个缓存:既然 DOM 这么慢,我们就在它们JS和DOM之间加个缓存.CPU(JS)只操作内存(Virtual DOM,最后的时候再把变更写入硬盘(DOM)
复制代码




  • React 提供了一些 API 来创建一种特别的一般 js 对象

//创建的就是一个简单的虚拟DOM对象var element = React.createElement('h1', {id:'myTitle'}, 'hello');
复制代码
  • 虚拟 DOM 对象最终都会被 React 转换为真实的 DOM

  • 我们编码时基本只需要操作 react 的虚拟 DOM 相关数据,react 会转换为真实 DOM 变化而更新界面

  • 创建虚拟 DOM 的 2 种方式 JSX 方式

 //  jsx方式创建虚拟dom元素对象const vDOM2 = <h3 id={myId.toUpperCase()}>{msg.toLowerCase()}</h3>
复制代码

还有一种是纯 JS,一般不使用:

//  纯JS方式const msg = 'I like you';const myId = 'atguigu';const vDOM1 = React.createElement('h2',{id:myId},msg);
复制代码
  • 渲染虚拟 DOM(元素)语法: ReactDOM.render(virtualDOM,containerDOM)作用: 将虚拟 DOM 元素渲染到真实容器 DOM 中显示参数说明:参数一: 纯 js 或 jsx 创建的虚拟 DOM 对象参数二: 用来包含虚拟 DOM 元素的真实 dom 元素对象(一般是一个 div)

   //  渲染到真实的页面中 ReactDOM.render(vDOM1,document.getElementById('example1')); ReactDOM.render(vDOM2,document.getElementById('example2'));
复制代码

使用示例:

<!DOCTYPE html><html><head><meta charset="UTF-8"><title>02_JSX_DEMO</title></head><body><ul><li>A</li><li>B</li><li>C</li></ul><hr>
<div id="example1"></div><div id="example2"></div>
<script src="../js/react.js"></script><script src="../js/react-dom.js"></script><script src="../js/babel.min.js"></script>
<script type="text/babel">/* 功能: 动态展示列表数据 *//* 技术点: 1). 使用JSX创建虚拟DOM 2). React能自动遍历显示数组中所有的元素 3). array.map()的使用 *///数据的数组var names = ['Tom2', 'Jack2', 'Bob2'];//数据的数组——>标签数组var lis = [];names.forEach((item,index)=>lis.push(<li key={index}>{item}</li>));//创建虚拟的DOMconst ul=<ul>{lis}</ul>;// 将虚拟的Dom渲染到页面中的某个DOM元素中ReactDOM.render(ul,document.getElementById('example1'))const ul2 = <ul>{names.map((name,index)=><li key={index}>{name}</li>)}</ul>ReactDOM.render(ul2, document.getElementById('example2'))
</script></body></html>
复制代码
React 的组件

模块

  • 什么是模块: 向外提供特定功能的 js 程序, 一般就是一个 js 文件

  • 为什么要用模块: js 代码越多越复杂了

  • 使用模块的优势: 简化 js 的编写, 阅读, 提高运行效率

  • 模块化: 当应用的 js 都以模块来编写的, 这个应用就是一个模块化的应用

组件

  • 什么是组件: 用来实现特定功能效果的代码集合(html/css/js)

  • 为什么要用组件: 单个界面的功能更复杂

  • 使用组件的优势: 复用, 简化项目编码, 提高运行效率

  • 组件化: 当应用是以多组件的方式实现功能, 这样应用就是一个组件化的应用

自定义组件:

1. 定义组件

1.工厂(无状态)函数(简单组件,推荐使用)       //  方式一:工厂函数,推荐使用  function MyComponent() {  return <h2>工厂函数</h2>  }
2.ES6类语法// 方式二:ES6类语法(复杂组件,推荐使用)class MyComponent2 extends React.Component{ render(){ return <h2>ES6的语法</h2> } }
复制代码

2. 渲染组件标签

//语法规则ReactDOM.render(<MyComponent/>, document.getElementById('example'));
复制代码
  • 注意返回的组件类必须首字母大写虚拟 DOM 元素必须只有一个根元素虚拟 DOM 元素必须有结束标签

  • ReactDOM.render()渲染组件标签的基本流程:

1.React内部会创建组件实例对象;2.得到包含的虚拟DOM并解析为真实DOM;3.插入到指定的页面元素内部;
复制代码
组件的三大属性

props 属性

1.每个组件对象都会有 props(properties 的简写)属性

2.组件标签的所有属性都保存在 props 中

3.内部读取某个属性值:this.props.propertyName

4.作用: 通过标签属性从组件外向组件内传递数据(只读)

5.对 props 中的属性值进行类型限制和必要性限制:

 //  对标签属性进行限制Person.propTypes = {    name:React.PropTypes.string.isRequired,    sex:React.PropTypes.string,    age:React.PropTypes.number}
复制代码

6.扩展属性: 将对象的所有属性通过 props 传递

 <Person {...person}/> //具体如下: ReactDOM.render(<Person {...person}/>,document.getElementById('example'))
复制代码

7.默认属性值

//  指定属性的默认值Person.defaultProps = {     sex:'男',     age:18 }
复制代码

8.组件类的构造函数

constructor (props) {  super(props)  console.log(props) // 查看所有属性}
复制代码

refs 属性

1.组件内的标签都可以定义 ref 属性来标识本身

2.在组件中可以通过 this.refs.refName 来得到对应的真实 DOM 对象

3.作用: 用于操作指定的 ref 属性的 dom 元素对象(表单标签居多)

事件处理

  • 通过 onXxx 属性指定组件的事件处理函数(注意大小写)React 使用的是自定义(合成)事件, 而不是使用的 DOM 事件 React 中的事件是通过委托方式处理的(委托给组件最外层的元素)

  • 通过 event.target 得到发生事件的 DOM 元素对象

<input onFocus={this.handleClick}/>      handleFocus(event) { event.target  //返回input对象      }
复制代码

强烈注意

组件内置的方法中的 this 为组件对象

在组件中自定义的方法中的 this 为 null

1.强制绑定 this

this.change = this.change.bind(this);
复制代码

2.箭头函数(ES6 模块化编码时才能使用)

state 属性

  • 组件被称为 "状态机" ,通过更新组件的状态值来更新对应的页面显示(重新渲染)

  • 初始化状态:

constructor (props) {   super(props)   this.state = {     stateProp1 : value1,     stateProp2 : value2   }}
复制代码
  • 读取某个状态值:

this.state.statePropertyName
复制代码
  • 更新状态->组件界面更新

this.setState({stateProp1 : value1,stateProp2 : value2})
复制代码
组件的生命周期
  • 组件的三个生命周期状态:Mount: 插入真实 DOMUpdate: 被重新渲染 Unmount: 被移出真实 DOM

  • React 为每个状态都提供了两种勾子(hook)函数,will 函数在进入状态之前调用,did 函数在进入状态之后调用:componentWillMount()componentDidMount(): 已插入页面真实 DOM,在 render 之后才会执行 componentWillUpdate(object nextProps,object nextState)componentDidUpdate(object prevProps,object prevState)componentWillUnmount()

  • 生命周期流程:第一次初始化渲染显示:render()constructor(): 创建对象初始化 statecomponentWillMount(): 将要插入回调函数 render(): 用于插入虚拟 DOM 回调函数 componentDidMount(): 已经插入回调函数.在此方法中启动定时器,绑定监听,发送 Ajax 请求每次更新 state:this.setSate()componentWillUpdate(): 将要更新回调函数 render(): 更新,重新渲染 componentDidUpdate(): 已经更新回调删除组件 ReactDOM.unmountComponentAtNode(div):移除组件 componentWillUnmount():组件将要被移除回调

  • 常用的方法 render(): 必须重写,返回一个自定义的虚拟 DOMconstructor(): 初始化状态,绑定 this(可以箭头函数代替)componentDidMount(): 只执行一次,已经在 DOM 树中,适合启动,设置一些监听

  • 注意一般会在 componentDidMount() 中:开启监听,发送 ajax 请求可以在 componentWillUnmount() 做一些收尾工作:停止监听生命周期还有一个方法:componentWillReceiveProps()

React 的函数式编程
  • 函数式编程: 结构化编程的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用

  • 声明式编程: 只关注做什么,而不关注怎么做(流程),类似于填空题,数组中常见声明式方法:map() , forEach() ,find() ,findIndex()

  • 命令式编程: 要关注做什么和怎么做(流程), 类似于问答题

var arr = [1, 3, 5, 7]// 需求: 得到一个新的数组, 数组中每个元素都比arr中对应的元素大10: [11, 13, 15, 17]// 命令式编程var arr2 = []for(var i =0;i<arr.length;i++) {    arr2.push(arr[i]+10)}console.log(arr2)// 声明式编程var arr3 = arr.map(function(item){    return item +10})// 声明式编程是建立命令式编程的基础上
复制代码
React 的 JSX

JSX 定义: JavaScript XML,react 定义的一种类似于 XML 的 JS 扩展语法:XML+JS,用来创建 react 虚拟 DOM(元素)对象.

注意:

1.它不是字符串, 也不是 HTML/XML 标签

2.它最终产生的就是一个 JS 对象

var ele = <h1>Hello JSX!</h1>;
复制代码
  • JSX 编码:基本语法规则:遇到 < 开头的代码, 以标签的语法解析:html 同名标签转换为 html 同名元素,其它标签需要特别解析遇到以 { 开头的代码,以 JS 的语法解析:标签中的 js 代码必须用{}包含 js 中直接可以套标签, 但标签要套 js 需要放在 { } 中在解析显示 js 数组时,会自动遍历显示把数据的数组转换为标签的数组var liArr = dataArr.map(function(item, index){ return <li key={index}>{item}</li> })babel.js 的作用浏览器的 js 引擎是不能直接解析 JSX 语法代码的,需要 babel 转译为纯 JS 的代码才能运行只要用了 JSX,都要加上 type="text/babel",声明需要 babel 来处理

  • 注意:标签必须有结束标签的 class 属性必须改为 className 属性标签的 style 属性值必须为: {{color:'red', width:12}}

React 的其它操作

双向绑定

  • React 是一个单向数据流

  • 可以自定义双向数据流组件(受控组件),需要通过 onChange 监听手动实现

<script type="text/babel"> class Control extends React.Component{     constructor(props){         super(props)         //初始化状态         this.state = {             msg:'ATGUIGU'         }         this.handleChange = this.handleChange.bind(this)
}
handleChange(event){ //得到最新的state的值 const msg=event.target.value;// console.log(event.target)// console.log(event.target.value) //更新状态 this.setState({msg}) } render(){ const {msg} = this.state return( <div> <input type="text" value={msg} onChange={this.handleChange}/> <p>{msg}</p> </div> ) } } ReactDOM.render(<Control/>,document.getElementById('example'))</script>
复制代码

React 发送 ajax 请求

  • React 没有 ajax 模块,所以只能集成其它的 js 库(如 jQuery/axios/fetch), 发送 ajax 请求 axios 封装 XmlHttpRequest 对象的 ajaxpromise 可以用在浏览器端和服务器 fetch 不再使用 XmlHttpRequest 对象提交 ajax 请求 fetch 就是用来提交 ajax 请求的函数,只是新的浏览才内置了 fetch 为了兼容低版本的浏览器,可以引入 fetch.js

  • 在哪个方法去发送 ajax 请求:只显示一次(请求一次): componentDidMount()显示多次(请求多次): componentWillReceiveProps()

//做一个跳转页面<script src="../js/react.js"></script><script src="../js/react-dom.js"></script><script src="../js/babel.min.js"></script><script src="https://cdn.bootcss.com/axios/0.16.2/axios.js"></script><script type="text/babel">class UserLastGist extends React.Component {    constructor (props) {        super(props)        this.state = {            url: null        }    }    componentDidMount () {        // 发送ajax请求        const url = `https://api.github.com/users/${this.props.username}/gists`        axios.get(url)            .then(response => {                console.log(response)                // 读取响应数据                //0索引位代表最后更新的网页内容                const url = response.data[0].html_url                // 更新状态                this.setState({url})            })            .catch(function (error) {
console.log('----', error); }) } render () { const {url} = this.state if(!url) { return <h2>loading...</h2> } else { return <p>{this.props.username}'s last gist is <a href={url}>here</a> </p> } }}UserLastGist.propTypes = { username: React.PropTypes.string.isRequired}ReactDOM.render(<UserLastGist username="octocat"/>, document.getElementById('example'))</script>
复制代码

RESTful

  • RESTful 是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件. 它主要用于客户端和服务器交互类的软件. 可以使软件更简洁,更有层次,更易于实现缓存等机制

  • REST 原则:客户端和服务器之间的交互在请求之间是无状态的分层系统

RESTful 的关键
  • 定义可表示流程元素或资源的对象: 在 REST 中,每一个对象都是通过 URL 来表示的,对象用户负责将状态信息打包进每一条消息内,以便对象的处理总是无状态的

  • 组合管理及流程绑定

RESTful 与 RPC
  • RPC 样式的 Web 服务客户端将一个装满数据的信封:包括方法和参数信息, 通过 HTTP 发送到服务器。服务器打开信封并使用传入参数执行指定的方法。方法的结果打包到一个信封并作为响应发回客户端。客户端收到响应并打开信封。每个对象都有自己独特的方法以及仅公开一个 URI 的 RPC 样式 Web 服务,URI 表示单个端点。它忽略 HTTP 的大部分特性且仅支持 POST 方法

RESTful Web 服务的 Java 框架
  • Restlet 客户端和服务器都是组件, 组件通过连接器互相通信该框架最重要的类是抽象类 Uniform 及其具体的子类 Restlet,该类的子类是专用类,比如 Application、Filter、Finder、Router 和 Route。这些子类能够一起处理验证、过滤、安全、数据转换以及将传入请求路由到相应资源等操作。Resource 类生成客户端的表示形式 RESTful Web 服务也是多层架构:数据存储层,数据访问层,业务层,表示层

RESTful API

  • RESTful:

 URL定位资源,用HTTP动词(GET,POST,PUT,DELETE)描述操作
复制代码
  • RESTful API 就是一套协议来规范多种形式的前端和同一个后台的交互方式.由 SERVER 来提供前端来调用,前端调用 API 向后台发起 HTTP 请求,后台响应请求将处理结果反馈给前端

RESTful API 设计原则
  • 资源: 首先是弄清楚资源的概念,资源总是要通过一种载体来反应它的内容.JSON 是现在最常用的资源表现形式

  • 统一接口: RESTful 风格的数据元操 CRUD(create,read,update,delete)分别对应 HTTP 方法:GET 用来获取资源,POST 用来新建资源(也可以用于更新资源),PUT 用来更新资源,DELETE 用来删除资源,统一数据操作的接口

  • URI: 可以用一个 URI(统一资源定位符)指向资源,即每个 URI 都对应一个特定的资源.要获取这个资源访问它的 URI 就可以,因此 URI 就成了每一个资源的地址或识别符.一般的,每个资源至少有一个 URI 与之对应,最典型的 URI 就是 URL

  • 无状态:所有的资源都可以 URI 定位,而且这个定位与其他资源无关,也不会因为其他资源的变化而变化。

有状态和无状态的区别:例如要查询员工工资的步骤第一步:登录系统。第二步:进入查询工资的页面。第三步:搜索该员工。第四步:点击姓名查看工资。这样的操作流程就是有状态的,查询工资的每一个步骤都依赖于前一个步骤,只要前置操作不成功,后续操作就无法执行。如果输入一个URL就可以得到指定员工的工资,则这种情况就是无状态的,因为获取工资不依赖于其他资源或状态,且这种情况下,员工工资是一个资源,由一个URL与之对应可以通过HTTP中的GET方法得到资源,这就是典型的RESTful风格。
复制代码
RESTful API 设计规范
  • URI 语法

URI=scheme"://"authority"/"path["?"query]["#"fragment]
- scheme:指底层用的协议:http,https,ftp- host:服务器的IP地址或者域名- port:端口,http中默认80- path:访问资源的路径,就是各种web 框架中定义的route路由- query:为发送给服务器的参数- fragment:锚点,定位到页面的资源,锚点为资源id
复制代码
  • 资源路径: rest 资源的定义,即 URL 的定义,是最重要的;要设计出优雅的、易读的 rest 接口

  • URL 中不能有动词: 在 Restful 架构中,每个网址代表的是一种资源,所以网址中不能有动词,只能有名词,动词由 HTTP 的 get、post、put、delete 四种方法来表示

  • URL 结尾不应该包含斜杠 "/": URI 中的每个字符都会计入资源的唯一身份的识别中,这是作为 URL 路径中处理中最重要的规则之一,正斜杠"/"不会增加语义值,且可能导致混淆.RESTful API 不允许一个尾部的斜杠,不应该将它们包含在提供给客户端的链接的结尾处.两个不同的 URI 映射到两个不同的资源.如果 URI 不同,那么资源也是如此,反之亦然.因此,RESTful API 必须生成和传递精确的 URI,不能容忍任何的客户端尝试不精确的资源定位.

  • 正斜杠分隔符 "/" 必须用来指示层级关系: URI 的路径中的正斜杠 "/" 字符用于指示资源之间的层次关系

  • 应该使用连字符 "-" 来提高 URL 的可读性,而不是使用下划线 "_": 为了使 URL 容易让人们理解,要使用连字符 "-" 字符来提高长路径中名称的可读性

  • URL 路径中首选小写字母: RFC 3986 将 URI 定义为区分大小写,但 scheme 和 host components 除外

  • URL 路径名词均为复数: 为了保证 url 格式的一致性,建议使用复数形式

RESTful API 对资源的操作

对于 RESTful API 资源的操作,由 HTTP 动词表示:

get: 获取资源

post: 新建资源

put: 在服务器更新资源(向客户端提供改变后的所有资源)

delete: 删除资源

patch:在服务器更新资源(向客户端提供改变的属性),一般不用,用 put

资源过滤: 在获取资源的时候,有可能需要获取某些“过滤”后的资源

例如指定前10行数据:http://api.user.com/schools/grades/classes/boys?page=1&page-size=10
复制代码
  • 返回状态码,推荐标准 HTTP 状态码: 有很多服务器将返回状态码一直设为 200,然后在返回 body 里面自定义一些状态码来表示服务器返回结果的状态码.由于 RESTful API 是直接使用的 HTTP 协议,所以它的状态码也要尽量使用 HTTP 协议的状态码

200 OK 服务器返回用户请求的数据,该操作是幂等的201 CREATED 新建或者修改数据成功204 NOT CONTENT 删除数据成功400 BAD REQUEST 用户发出的请求有问题,该操作是幂等的401 Unauthoried 表示用户没有认证,无法进行操作403 Forbidden 用户访问是被禁止的422 Unprocesable Entity 当创建一个对象时,发生一个验证错误500 INTERNAL SERVER ERROR 服务器内部错误,用户将无法判断发出的请求是否成功503 Service Unavailable 服务不可用状态,多半是因为服务器问题,例如CPU占用率大,等等
复制代码

原文链接:https://www.cnblogs.com/chova/p/14468331.html

用户头像

云流

关注

还未添加个人签名 2020.09.02 加入

还未添加个人简介

评论

发布
暂无评论
微服务框架相关技术整理