SpringCloud学习笔记一
SpringCloud学习笔记
1-架构演进和分布式系统基础
1.1-单体应用
- 把所有的功能集中在同一系统中实现,应用程序的全部功能被一起打包作为单个单元或应用程序
- 这个单元可以是JAR、WAR、EAR,或其他一些归档格式,运行在同一个 tomcat 进程中
- 其全部集成在一个单一的单元.
- 优点:
- 方便调试,代码都在一起
- 易于部署,所有服务都在本地容器内
- 中小型项目可以快速迭代,不需要太多资源
- 缺点:
- 可复用性差:服务被打包在应用中,功能不易复用
- 系统启动慢:一个进程包含了所有的业务逻辑,涉及到的启动模块过多,导致系统的启动、重启时间周期过长
- 线上问题修复周期长:任何一个线上问题修复需要对整个应用系统进行全面升级。
- 系统扩展性比较差:增加新东西的时候不能针对单个点增加,全局性的增加.牵一发而动全身。
- 技术债务逐渐上升:随着时间推移、需求变更和人员更迭,会逐渐形成应用程序的技术债务,并且越积越多。
- 阻碍技术创新:单体应用往往使用统一的技术平台或方案解决所有问题,团队的每个成员都必须使用相同的开发语言和架构,想要引入新的框架或技术平台非常困难。
1.2-微服务应用
- 微服务架构风格是一种将一个单一应用程序开发为一组小型服务的方法
- 每个小型服务都运行在自己的进程中,并经常采用HTTP资源API轻量的机制来相互通信
- 这些服务围绕业务功能进行构建,并能通过全自动的部署机制来进行独立部署
- 一个微服务只关注某个特定的功能
- 优点:
- 易于开发和维护:一个微服务只会关注一个特定的业务功能,所以业务清晰、代码量较少。开发和维护单个微服务相对简单,每个服务为独立的业务开发,一个微服务只关注某个特定的功能,如订单管理、用户管理等
- 单个微服务启动较快,每个微服务可独立运行在自己的进程里
- 局部修改容易部署:单体应用只要有修改,就得重新部署整个应用。一般来说,对某个微服务进行修改,只需要重新部署这个服务即可
- 技术栈不受限制:在微服务架构中,可以结合项目业务及团队的特点,合理的选择技术栈
- 按需伸缩:可根据需求,实现细粒度的扩展
- 缺点:
- 运维要求高:更多的服务意味着要投入更多的运维
- 分布式固有的复杂性:使用微服务构建的是分布式系统。对于一个分布式系统,系统容错、网络延迟、分布式事务等都会带来巨大的问题。
- 接口调整成本高:微服务之间通过接口进行通信。如果修改某一个微服务的API,可能所有用到这个接口的微服务都需要进行调整
1.3-集群、分布式和微服务的区别
- 分布式:
- 一个业务分拆多个子业务,部署在不同的服务器上
- 分布式中的每一个节点,都可以做集群
- 分布式需要做好事务管理
- 区别分布式的方式是根据不同机器不同业务。
- 微服务:
- 微服务是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成
- 系统中的各个微服务可被独立部署,各个微服务之间是松耦合的
- 每个微服务仅关注于完成一件任务并很好地完成该任务
- 集群:
- 同一个业务,部署在多个服务器上
- 区别集群的方式是根据部署多台服务器业务是否相同
- 集群模式需要做好session共享,确保在不同服务器切换的过程中不会因为没有获取到session而中止退出服务
- 一般配置Nginx的负载容器实现:静态资源缓存、Session共享可以附带实现,Nginx支持5000个并发量
- 分布式是否属于微服务
- 不一定,如果一个很大应用,拆分成三个应用,但还是很庞大,虽然分布式了,但不是微服务。微服务核心要素是微小
- 微服务的设计是为了不因为某个模块的升级和BUG影响现有的系统业务
- 微服务的应用不一定是分散在多个服务器上,也可以是同一个服务器
- 微服务架构是分布式服务架构的子集
- 分布式:分散压力。微服务:分散能力
- 单应用与集群
- 整个项目所有的服务都由这台服务器提供。这就是单机结构
- 单机复制几份,这样就构成了一个“集群”
- 集群中每台服务器就叫做这个集群的一个“节点”,所有节点构成了一个集群
- 每个节点都提供相同的服务,那么这样系统的处理能力就相当于提升了好几倍
2-微服务核心基础
2.1-微服务架构
微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务于服务间采用轻量级的通信机制互相沟通(通常是基于 HTTP 的 RESTful API)。每个服务都围绕着具体业务进行构建,并且能够被独立地部署到生产环境、类生产环境等。另外,应尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建。
- 分布式:不同的功能模块部署在不同的服务器上,减轻网站高并发带来的压力。
- 集群:多台服务器上部署相同应用构成一个集群,通过负载均衡共同向外提供服务。
- 微服务:微服务架构模式就是将web应用拆分为一系列小的服务模块,这些模块可以独立地编译、部署,并通过各自暴露的API接口通讯,共同组成一个web应用。
- SpringCloud是基于SpringBoot的一整套微服务框架,提供了一系列可配置的组件,如配置管理、服务发现、负载均衡、熔断器、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等。
2.2-什么是SpringCloud?
Spring Cloud是一个基于Spring Boot实现的服务工具治理包,专注于全局的服务治理框架。
Spring Cloud 是一系列框架的有序集合。
它利用 Spring Boot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 Spring Boot 的开发风格做到一键启动和部署。
Spring 并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过 Spring Boot 风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
- 所有请求都通过API网关来访问内部服务;
- 网关接受请求后,从注册中心获取可用服务模块;
- 由Ribbon进行负载均衡后,分发到后台的具体实例;
- 各个服务模块之间通过Feign进行通信处理业务;
- Hystrix负责处理服务超时熔断;
- Turbine监控服务间的调用和熔断相关指标。
我所理解的Spring Cloud
就是微服务系统架构的一站式解决方案,在平时我们构建微服务的过程中需要做如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等操作,而 Spring Cloud 为我们提供了一套简易的编程模型,使我们能在 Spring Boot 的基础上轻松地实现微服务项目的构建。
SpringCloud版本
Spring Cloud 的版本号并不是我们通常见的数字版本号,而是一些很奇怪的单词。这些单词均为英国伦敦地铁站的站名。同时根据字母表的顺序来对应版本时间顺序,比如:最早 的 Release 版本 Angel,第二个 Release 版本 Brixton(英国地名),然后是 Camden、 Dalston、Edgware、Finchley、Greenwich、Hoxton。
2.3-微服务技术
Spring Cloud Config :服务配置中心,将所有的服务的配置文件放到本地仓库或者远程仓库,配置中心负责读取仓库的配置文件,其他服务向配置中心读取配置。SpringCloud Config 使得服务的配置统一管理 并可以在不人为重启服务的情况下进行配置文件的刷新。
Spring Cloud Netflix :它是通过包装了 Netflix 公司的微服务组件实现的,也是SpringCloud 核心的核心组件,包括 Eureka Hystrix Zuul Archaius 等。
Eureka :服务注册和发现组件,可以细分为eureka server(服务注册中心)和eureka client(服务注册客户端,所有其他需注册到服务注册中心的微服务组件都可以看做是服务注册客户端)
- Eureka Server提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册,这样Eureka Server中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。
- Eureka Client是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)Eureka Server之间将会通过复制的方式完成数据的同步。Eureka还提供了客户端缓存的机制,即使所有的Eureka Server都挂掉了,客户端依然可以利用缓存中的信息消费其它服务的API。综上,Eureka通过心跳检测、健康检查、客户端缓存等机制,确保了系统的高可用性、灵活性和可伸缩性。
Hystrix :熔断器组件 Hystrix 通过控制服务的 API 接口的熔断来转移故障,防止微服务系统发生雪崩效应。另外, Hystrix 能够起到服务限流和服务降级的作用。使用Hystrix Dashboard 组件监控单个服务的熔断器的状态,使用 Turbine 组件可以聚合多,现了断路器的模式。“断路器” 本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
Zuul :(路由转发+过滤器)能路由网关组 Netflix Zuul能够起到智能路由和请求过滤的作用,是服务接口统一暴露 关键模块,也是安全验证、权限控制的一道门;Zuul路由是微服务架构的不可或缺的一部分,提供动态路由、监控、弹性、安全等的边缘服务,Zuul 是 Netflix 出品的一个基于 JVM 路由和服务端的负载均衡器。
Feign 声明式远程调度组件,Feign 是一个声明式的 Web Service 客户端,它的目的就是让 Web Service 调用更加简单。它整合了 Ribbon 和 Hystrix,从而让我们不再需要显式地使用这两个组件。Feign 还提供了 HTTP 请求的模板,通过编写简单的接口和插入注解,我们就可以定义好 HTTP 请求的参数、格式、地址等信息。接下来,Feign 会完全代理 HTTP 的请求,我们只需要像调用方法一样调用它就可以完成服务请求。
Feign 具有如下特性:
- 可插拔的注解支持,包括 Feign 注解和 JAX-RS 注解
- 支持可插拔的 HTTP 编码器和解码器
- 支持 Hystrix 和它的 Fallback
- 支持 Ribbon 的负载均衡
- 支持 HTTP 请求和响应的压缩
Ribbon 负载均衡组件,为REST客户端实现负载均衡。
Archaius :配置管理 API 的组件, 一个基于 Java 的配置管理库主要用于多配置的动态获取。Spring Cloud Bus 消息总线组件,常和 Spring Cloud Config 配合使用,用于动态新服务的配置。
Spring Cloud Sleuth :服务链路追踪组件,封装了 Dapper Zipkin, Kibina 等组件,可以实时监控服务的链路调用情况。
Spring Cloud Data Flow :大数据操作组件,Spring Cloud Data Flow SpringXD替代品,也是 个混合计算的模型,可以通过命令行的方式操作数据流
Spring Cloud Security 安全模块组件,是对 Spring Security 封装,通常配合 0Auth2使用来保护微服务系统的安全。
Spring Cloud Consule :该组件是 Spring Cloud Consul 的封装,和 ureka 类似,它是一个服务注册和发现组件
Spring Cloud Zookeeper 该组件是 Spring Cloud Zookeeper 封装,和 Eureka Consul相似,用于服务的注册和发现
Spring Cloud Stream :数据流操作组件,可以封装 Redis RabbitMQ Kafka 等组件实现发送和接收消息等。
Spring Cloud CLI :该组件是 Spring Cloud Spring Boot CLI 的封装,可以让用户以命令行方式快速运行和搭建容器
Spring Cloud Task 该组件基于 Spring Task ,提供了任务调度和任务管理的功能。
Spring Cloud Connectors 用于 Paas 云平台连接到后端。
3-Dubbo和SpringCloud
dubbo: zookeeper + dubbo + springmvc/springboot
官方地址:http://dubbo.apache.org/#!/?lang=zh-cn
配套: 通信方式:rpc
注册中心:zookeper/redis
配置中心:diamondDubbo核心组件
- Provider:暴露服务的提供方,可以通过 jar 或者容器的方式启动服务。
- Consumer:调用远程服务的服务消费方。
- Registry:服务注册中心和发现中心。
- Monitor:统计服务和调用次数,调用时间监控中心。(Dubbo 的控制台页面中可以显示,目前只有一个简单版本。)
- Container:服务运行的容器。
Spring Cloud
springcloud: 全家桶+轻松嵌入第三方组件
官网:http://projects.spring.io/spring-cloud/
配套 通信方式:http restful
注册中心:eruka/consul
配置中心:config
断路器:hystrix
网关:zuul
分布式追踪系统:sleuth+zipkinSpringCloud和Dubbo区别
Dubbo Spring Cloud 服务注册中心 Zookeeper Spring Cloud Netflix Eureka 服务调用方式 RPC REST API 服务监控 无 Spring Boot Admin 断路器 不完善 Spring Cloud Netflix Hystrix 服务网关 无 Spring Cloud Netflix Zuul 分布式配置 无 Spring Cloud Config 服务跟踪 无 Spring Cloud Sleuth 消息总线 无 Spring Cloud Bus 数据流 无 Spring Cloud Stream 批量任务 无 Spring Cloud Task Spring Cloud 抛弃了 Dubbo 的 RPC 通信,采用的是基于 HTTP 的 REST 方式。严格来说,这两种方式各有优劣。虽然从一定程度上来说,后者牺牲了服务调用的性能,但也避免了上面提到的原生 RPC 带来的问题。而且 REST 相比 RPC 更为灵活,服务提供方和调用方,不存在代码级别的强依赖,这在强调快速演化的微服务环境下显得更加合适。
很明显,Spring Cloud 的功能比 Dubbo 更加强大,涵盖面更广,而且作为 Spring 的拳头项目,它也能够与 Spring Framework、Spring Boot、Spring Data、Spring Batch 等其他 Spring 项目完美融合,这些对于微服务而言是至关重要的。
微服务下电商项目模块设计
- 用户服务
- 用户信息接口
- 登录接口
- 商品服务
- 商品列表
- 商品详情
- 订单服务
- 我的订单
- 下单接口
- 用户服务
4-SpringCloud核心组件注册中心
4.1-什么是微服务的注册中心?
在微服务架构下,主要有三种角色:服务提供者(RPC Server)、服务消费者(RPC Client)和服务注册中心(Registry),三者的交互关系如下面这张图:
RPC Server 提供服务,在启动时,根据服务发布文件 server.xml 中的配置的信息,向 Registry 注册自身服务,并向 Registry 定期发送心跳汇报存活状态。
RPC Client 调用服务,在启动时,根据服务引用文件 client.xml 中配置的信息,向 Registry 订阅服务,把 Registry 返回的服务节点列表缓存在本地内存中,并与 RPC Sever 建立连接。
当 RPC Server 节点发生变更时,Registry 会同步变更,RPC Client 感知后会刷新本地内存中缓存的服务节点列表。
RPC Client 从本地缓存的服务节点列表中,基于负载均衡算法选择一台 RPC Sever 发起调用。
注册中心可以说是微服务架构中的”通讯录“,它记录了服务和服务地址的映射关系。在分布式架构中,服务会注册到这里,当服务需要调用其它服务时,就到这里找到服务的地址,进行调用。
数据模型
注册中心的核心数据是服务的名字和它对应的网络地址,当服务注册了多个实例时,我们需要对不健康的实例进行过滤或者针对实例的一些特征进行流量的分配,那么就需要在实例上存储一些例如健康状态、权重等属性。随着服务规模的扩大,渐渐的又需要在整个服务级别设定一些权限规则、以及对所有实例都生效的一些开关,于是在服务级别又会设立一些属性。再往后,我们又发现单个服务的实例又会有划分为多个子集的需求,例如一个服务是多机房部署的,那么可能需要对每个机房的实例做不同的配置,这样又需要在服务和实例之间再设定一个数据级别。
数据一致性
数据一致性是分布式系统永恒的话题,Paxos协议的艰深更让数据一致性成为程序员大牛们吹水的常见话题。不过从协议层面上看,一致性的选型已经很长时间没有新的成员加入了。目前来看基本可以归为两家:一种是基于Leader的非对等部署的单点写一致性,一种是对等部署的多写一致性。当我们选用服务注册中心的时候,并没有一种协议能够覆盖所有场景,例如当注册的服务节点不会定时发送心跳到注册中心时,强一致协议看起来是唯一的选择,因为无法通过心跳来进行数据的补偿注册,第一次注册就必须保证数据不会丢失。而当客户端会定时发送心跳来汇报健康状态时,第一次的注册的成功率并不是非常关键(当然也很关键,只是相对来说我们容忍数据的少量写失败),因为后续还可以通过心跳再把数据补偿上来,此时Paxos协议的单点瓶颈就会不太划算了,这也是Eureka为什么不采用Paxos协议而采用自定义的Renew机制的原因。
负载均衡
负载均衡严格的来说,并不算是传统注册中心的功能。一般来说服务发现的完整流程应该是先从注册中心获取到服务的实例列表,然后再根据自身的需求,来选择其中的部分实例或者按照一定的流量分配机制来访问不同的服务提供者,因此注册中心本身一般不限定服务消费者的访问策略。
Eureka
、Zookeeper
包括Consul
,本身都没有去实现可配置及可扩展的负载均衡机制,Eureka的负载均衡是由ribbon来完成的,而Consul则是由Fabio做负载均衡。服务端的负载均衡,给服务提供者更强的流量控制权,但是无法满足不同的消费者希望使用不同负载均衡策略的需求。而不同负载均衡策略的场景,确实是存在的。而客户端的负载均衡则提供了这种灵活性,并对用户扩展提供更加友好的支持。但是客户端负载均衡策略如果配置不当,可能会导致服务提供者出现热点,或者压根就拿不到任何服务提供者。
健康检查
Zookeeper和Eureka都实现了一种TTL的机制,就是如果客户端在一定时间内没有向注册中心发送心跳,则会将这个客户端摘除。Eureka做的更好的一点在于它允许在注册服务的时候,自定义检查自身状态的健康检查方法。这在服务实例能够保持心跳上报的场景下,是一种比较好的体验,在Dubbo和SpringCloud这两大体系内,也被培养成用户心智上的默认行为。Nacos也支持这种TTL机制,不过这与ConfigServer在阿里巴巴内部的机制又有一些区别。Nacos目前支持临时实例使用心跳上报方式维持活性,发送心跳的周期默认是5秒,Nacos服务端会在15秒没收到心跳后将实例设置为不健康,在30秒没收到心跳时将这个临时实例摘除。
客户端健康检查和服务端健康检查有一些不同的关注点。客户端健康检查主要关注客户端上报心跳的方式、服务端摘除不健康客户端的机制。而服务端健康检查,则关注探测客户端的方式、灵敏度及设置客户端健康状态的机制。从实现复杂性来说,服务端探测肯定是要更加复杂的,因为需要服务端根据注册服务配置的健康检查方式,去执行相应的接口,判断相应的返回结果,并做好重试机制和线程池的管理。这与客户端探测,只需要等待心跳,然后刷新TTL是不一样的。同时服务端健康检查无法摘除不健康实例,这意味着只要注册过的服务实例,如果不调用接口主动注销,这些服务实例都需要去维持健康检查的探测任务,而客户端则可以随时摘除不健康实例,减轻服务端的压力。
性能与容量
虽然大部分用户用到的性能不高,但是他们仍然希望选用的产品的性能越高越好。影响读写性能的因素很多:一致性协议、机器的配置、集群的规模、存量数据的规模、数据结构及读写逻辑的设计等等。在服务发现的场景中,我们认为读写性能都是非常关键的,但是并非性能越高就越好,因为追求性能往往需要其他方面做出牺牲。Zookeeper在写性能上似乎能达到上万的TPS,这得益于Zookeeper精巧的设计,不过这显然是因为有一系列的前提存在。首先Zookeeper的写逻辑就是进行K-V的写入,内部没有聚合;其次Zookeeper舍弃了服务发现的基本功能如健康检查、友好的查询接口,它在支持这些功能的时候,显然需要增加一些逻辑,甚至弃用现有的数据结构;最后,Paxos协议本身就限制了Zookeeper集群的规模,3、5个节点是不能应对大规模的服务订阅和查询的。
易用性
易用性也是用户比较关注的一块内容。产品虽然可以在功能特性或者性能上做到非常先进,但是如果用户的使用成本极高,也会让用户望而却步。易用性包括多方面的工作,例如API和客户端的接入是否简单,文档是否齐全易懂,控制台界面是否完善等。对于开源产品来说,还有一块是社区是否活跃。在比较Nacos、Eureka和Zookeeper在易用性上的表现时,我们诚邀社区的用户进行全方位的反馈,因为毕竟在阿里巴巴集团内部,我们对Eureka、Zookeeper的使用场景是有限的。从我们使用的经验和调研来看,Zookeeper的易用性是比较差的,Zookeeper的客户端使用比较复杂,没有针对服务发现的模型设计以及相应的API封装,需要依赖方自己处理。对多语言的支持也不太好,同时没有比较好用的控制台进行运维管理。
集群扩展性
集群扩展性和集群容量以及读写性能关系紧密。当使用一个比较小的集群规模就可以支撑远高于现有数量的服务注册及访问时,集群的扩展能力暂时就不会那么重要。从协议的层面上来说,Zookeeper使用的ZAB协议,由于是单点写,在集群扩展性上不具备优势。Eureka在协议上来说理论上可以扩展到很大规模,因为都是点对点的数据同步,但是从我们对Eureka的运维经验来看,Eureka集群在扩容之后,性能上有很大问题。
几大服务注册中心的对比:
4.2-SpringCloud微服务核心组件Eureka
Eureka是Netflix开源的服务发现组件,本身是一个基于REST的服务,包含Server和Client两部分,并且服务端与客户端均采用java编写,所以Eureka主要适用于通过java实现的分布式系统,或是JVM兼容语言构建的系统,Spring Cloud将它集成在子项目Spring Cloud Netflix中。在微服务系统中,我们需要单独创建一个Eureka Server作为注册中心,其他的微服务就相当于客户端,注册到我们的注册中心中。
Eureka是基于REST(代表性状态转移)的服务,主要在AWS云中用于定位服务,以实现负载均衡和中间层服务器的故障转移。我们称此服务为Eureka服务器。Eureka还带有一个基于Java的客户端组件Eureka Client,它使与服务的交互变得更加容易。客户端还具有一个内置的负载平衡器,可以执行基本的循环负载平衡。在Netflix,更复杂的负载均衡器将Eureka包装起来,以基于流量,资源使用,错误条件等多种因素提供加权负载均衡,以提供出色的弹性。
Register(服务注册):把自己的IP和端口注册给Eureka。 |
流程:
各个微服务启动时,会通过 Eureka Client 向 Eureka Server 注册自己,Eureka Server 会存储该服务的信息
也就是说,每个微服务的客户端和服务端,都会注册到 Eureka Server,这就衍生出了微服务相互识别的话题
同步:每个 Eureka Server 同时也是 Eureka Client(逻辑上的)
多个 Eureka Server 之间通过复制的方式完成服务注册表的同步,形成 Eureka 的高可用识别:Eureka Client 会缓存 Eureka Server 中的信息
即使所有 Eureka Server 节点都宕掉,服务消费者仍可使用缓存中的信息找到服务提供者(笔者已亲测)续约:微服务会周期性(默认30s)地向 Eureka Server 发送心跳以Renew(续约)信息(类似于heartbeat)
续期:Eureka Server 会定期(默认60s)执行一次失效服务检测功能
它会检查超过一定时间(默认90s)没有Renew的微服务,发现则会注销该微服务节点Eureka服务端
提供服务注册和发现的能力(通常就是微服务中的注册中心),各个节点启动后,会在Eureka Server中进行注册,这样Eureka Server中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在前端界面中直观的看到。
即服务注册中心。它同其他服务注册中心一样,支持高可用配置。依托于强一致性提供良好的服务实例可用性,可以应对多种不同的故障场景。
Eureka服务端支持集群模式部署,当集群中有分片发生故障的时候,Eureka会自动转入自我保护模式。它允许在分片发生故障的时候继续提供服务的发现和注册,当故障分配恢复时,集群中的其他分片会把他们的状态再次同步回来。集群中的的不同服务注册中心通过异步模式互相复制各自的状态,这也意味着在给定的时间点每个实例关于所有服务的状态可能存在不一致的现象。
Eureka客户端
一个Java客户端,用于简化与 Eureka Server 的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳,默认周期为30秒,如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个**服务节点移除(默认90秒)**。。
Eureka客户端,主要处理服务的注册和发现。客户端服务通过注册和参数配置的方式,嵌入在客户端应用程序的代码中。在应用程序启动时,Eureka客户端向服务注册中心注册自身提供的服务,并周期性的发送心跳来更新它的服务租约。同时,他也能从服务端查询当前注册的服务信息并把它们缓存到本地并周期行的刷新服务状态。
心跳检测
在应用启动后,客户端将会向Eureka Server发送心跳(默认为30秒,我们项目配置的是30秒)。Eureka Serber如果在多个心跳周期内没有收到某个微服务节点的心跳,将会剔除该节点(默认90秒,我们项目配置的是90秒)。
集群数据同步
Eureka Server之间通过复制的方式来进行数据同步。
客户端缓存功能
Eureka Client具有缓存功能,即使所有的Eureka Server都挂掉,客户端依然可以利用缓存中的信息消费其他服务的API。
清理失效节点
自我保护模式
自我保护模式是指在网络出现异常的情况下,由于Eureka Server无法收到客户端的心跳续约,Eureka Server会判断该节点不可用,但其实该节点可能是正常的,可用的。为了避免误删,Eureka Server引入了自我保护模式。一旦Eureka Server发现当前收到的心跳总次数小于心跳阈值的85%(默认值),就会进入自我保护模式,此时Eureka Server不会清理任何节点。直到Eureka Server收到的心跳总次数大于等于心跳阈值的85%。
自我保护模式的设计哲学是:在不确定节点是否可用的情况下,尽可能保留节点。
Eureka工作流程:
- Eureka Server 启动成功,等待服务端注册。在启动过程中如果配置了集群,集群之间定时通过 Replicate 同步注册表,每个 Eureka Server 都存在独立完整的服务注册表信息
- Eureka Client 启动时根据配置的 Eureka Server 地址去注册中心注册服务
- Eureka Client 会每 30s 向 Eureka Server 发送一次心跳请求,证明客户端服务正常
- 当 Eureka Server 90s 内没有收到 Eureka Client 的心跳,注册中心则认为该节点失效,会注销该实例
- 单位时间内 Eureka Server 统计到有大量的 Eureka Client 没有上送心跳,则认为可能为网络异常,进入自我保护机制,不再剔除没有上送心跳的客户端
- 当 Eureka Client 心跳请求恢复正常之后,Eureka Server 自动退出自我保护模式
- Eureka Client 定时全量或者增量从注册中心获取服务注册表,并且将获取到的信息缓存到本地
- 服务调用时,Eureka Client 会先从本地缓存找寻调取的服务。如果获取不到,先从注册中心刷新注册表,再同步到本地缓存
- Eureka Client 获取到目标服务器信息,发起服务调用
- Eureka Client 程序关闭时向 Eureka Server 发送取消请求,Eureka Server 将实例从注册表中删除
服务发现:其实就是一个“中介”,整个过程中有三个角色:**服务提供者(出租房子的)、服务消费者(租客)、服务中介(房屋中介)**。
服务提供者:就是提供一些自己能够执行的一些服务给外界。
服务消费者:就是需要使用一些服务的“用户”。
服务中介:其实就是服务提供者和服务消费者之间的“桥梁”,服务提供者可以把自己注册到服务中介那里,而服务消费者如需要消费一些服务(使用一些功能)就可以在服务中介中寻找注册在服务中介的服务提供者。
服务注册 Register:
官方解释:当 Eureka 客户端向[Eureka] Server
注册时,它提供自身的元数据,比如IP地址、端口,运行状况指示符URL,主页等。
结合中介理解:房东 (提供者[Eureka] Client Provider
)在中介 (服务器[Eureka] Server
) 那里登记房屋的信息,比如面积,价格,地段等等(元数据metaData
)。
服务续约 Renew:
官方解释:Eureka 客户会每隔30秒(默认情况下)发送一次心跳来续约。通过续约来告知[Eureka] Server
该 Eureka 客户仍然存在,没有出现问题。正常情况下,如果[Eureka] Server
在90秒没有收到 Eureka 客户的续约,它会将实例从其注册表中删除。
结合中介理解:房东 (提供者[Eureka] Client Provider
) 定期告诉中介 (服务器[Eureka] Server
) 我的房子还租(续约) ,中介 (服务器[Eureka] Server
) 收到之后继续保留房屋的信息。
获取注册列表信息 Fetch Registries:
官方解释:Eureka 客户端从服务器获取注册表信息,并将其缓存在本地。客户端会使用该信息查找其他服务,从而进行远程调用。该注册列表信息定期(每30秒钟)更新一次。每次返回注册列表信息可能与 Eureka 客户端的缓存信息不同, Eureka 客户端自动处理。如果由于某种原因导致注册列表信息不能及时匹配,Eureka 客户端则会重新获取整个注册表信息。Eureka 服务器缓存注册列表信息,整个注册表以及每个应用程序的信息进行了压缩,压缩内容和没有压缩的内容完全相同。Eureka 客户端和 Eureka 服务器可以使用JSON / XML格式进行通讯。在默认的情况下 Eureka 客户端使用压缩JSON
格式来获取注册列表的信息。
结合中介理解:租客(消费者[Eureka] Client Consumer
) 去中介 (服务器[Eureka] Server
) 那里获取所有的房屋信息列表 (客户端列表[Eureka] Client List
) ,而且租客为了获取最新的信息会定期向中介 (服务器[Eureka] Server
) 那里获取并更新本地列表。
服务下线 Cancel:
官方解释:Eureka客户端在程序关闭时向Eureka服务器发送取消请求。发送请求后,该客户端实例信息将从服务器的实例注册表中删除。该下线请求不会自动完成,它需要调用以下内容:DiscoveryManager.getInstance().shutdownComponent();
结合中介理解:房东 (提供者[Eureka] Client Provider
) 告诉中介 (服务器[Eureka] Server
) 我的房子不租了,中介之后就将注册的房屋信息从列表中剔除。
服务剔除 Eviction:
官方解释:在默认的情况下,当Eureka客户端连续90秒(3个续约周期)没有向Eureka服务器发送服务续约,即心跳,Eureka服务器会将该服务实例从服务注册列表删除,即服务剔除。
结合中介理解:房东(提供者[Eureka] Client Provider
) 会定期联系 中介 (服务器[Eureka] Server
) 告诉他我的房子还租(续约),如果中介 (服务器[Eureka] Server
) 长时间没收到提供者的信息,那么中介会将他的房屋信息给下架(服务剔除)。
4.3-Eureka Server搭建实战
创建eureka-server工程
添加依赖
父工程添加:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>Eureka Server工程添加:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>添加配置
server:
port: 8761
spring:
application:
name: eureka-server
eureka:
instance:
hostname: 127.0.0.1
client:
service-url:
defaultZone: http://${eureka.instance.hostname}}:${server.port}/eureka/
#声明自己是个服务端
register-with-eureka: false
fetch-registry: false启动类配置:
启动Eureka Server注册中心,和普通的SpringBoot应用的启动没有太大的区别。只需要在启动类上增加@EnableEurekaServe
r注解,来开启Eureka Server服务即可。
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudEurekaServerApplication.class, args);
}
}访问注册中心
4.4-Eureka Client搭建商品服务实战
创建实体类
public class Product {
private int id;
//商品名称
private String name;
//商品价格
private int price;
//库存
private int store;
}创建Service
public interface ProductService {
List<Product> getProducts();
Product findById(int id);
}
public class ProductServiceImpl implements ProductService {
private static final Map<Integer, Product> daoMap = new HashMap<>();
static {
Product p1 = new Product(1,"iphonex",9999, 10);
Product p2 = new Product(2,"冰箱",5342, 19);
Product p3 = new Product(3,"洗衣机",523, 90);
Product p4 = new Product(4,"电话",64345, 150);
Product p5 = new Product(5,"汽车",2345, 140);
Product p6 = new Product(6,"椅子",253, 20);
Product p7 = new Product(7,"java编程思想",2341, 10);
daoMap.put(p1.getId(),p1);
daoMap.put(p2.getId(),p2);
daoMap.put(p3.getId(),p3);
daoMap.put(p4.getId(),p4);
daoMap.put(p5.getId(),p5);
daoMap.put(p6.getId(),p6);
daoMap.put(p7.getId(),p7);
}
public List<Product> getProducts() {
Collection<Product> collection = daoMap.values();
List<Product> productList = new ArrayList<>(collection);
return productList;
}
public Product findById(int id) {
return daoMap.get(id);
}
}Controller
public class ProductController {
private ProductService productService;
/**
* 获取所有商品列表
* @return
*/
public Object list(){
return productService.getProducts();
}
/**
* 根据id查找商品详情
* @param id
* @return
*/
public Object findById(int id) {
return productService.findById(id);
}
}启动类
//By having spring-cloud-starter-netflix-eureka-client on the classpath, your application automatically registers with the Eureka Server. Configuration is required to locate the Eureka server, as shown in the following example:
public class ProductServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ProductServiceApplication.class, args);
}
}配置文件
server:
port: 8081
spring:
application:
name: product-service
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8761/eureka
instance:
prefer-ip-address: true # 当其它服务获取地址时提供ip而不是hostname
ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的话会自己寻找访问注册中心可以看到我们的商品服务
eureka管理后台出现一串红色字体:是警告,说明有服务上线率低
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.
关闭检查方法:eureka服务端配置文件加入
server:
enable-self-preservation: false注意:自我保护模式禁止关闭,默认是开启状态true
5-服务消费者Ribbon和Feign
5.1-常用的服务间调用方式
RPC
RPC 即远程过程调用(Remote Procedure Call Protocol,简称RPC),像调用本地服务(方法)一样调用服务器的服务(方法)。通常的实现有 XML-RPC , JSON-RPC , 通信方式基本相同, 所不同的只是传输数据的格式.
客户端和服务器之间建立TCP连接(长连接),可以一次建立一个,也可以多个调用复用一次链接。
RPC是分布式架构的核心,按响应方式分如下两种:
同步调用:客户端调用服务方方法,等待直到服务方返回结果或者超时,再继续自己的操作
异步调用:客户端把消息发送给中间件,不再等待服务端返回,直接继续自己的操作。
同步调用的实现方式有WebService和RMI。Web Service提供的服务是基于web容器的,底层使用http协议,因而适合不同语言异构系统间的调用。RMI实际上是Java语言的RPC实现,允许方法返回 Java 对象以及基本数据类型,适合用于JAVA语言构建的不同系统间的调用。
异步调用的JAVA实现版就是JMS(Java Message Service),目前开源的的JMS中间件有Apache社区的ActiveMQ、Kafka消息中间件,另外有阿里的RocketMQ。
RPC架构里包含如下4个组件:
- 客户端(Client):服务调用方
- 客户端存根(Client Stub):存放服务端地址信息,将客户端的请求参数打包成网络消息,再通过网络发送给服务方
- 服务端存根(Server Stub):接受客户端发送过来的消息并解包,再调用本地服务
- 服务端(Server):真正的服务提供者。
RPC主要是用在大型企业里面,因为大型企业里面系统繁多,业务线复杂,而且效率优势非常重要的一块,这个时候RPC的优势就比较明显了。实际的开发当中是这么做的,项目一般使用maven来管理。比如我们有一个处理订单的系统服务,先声明它的所有的接口(这里就是具体指Java中的
interface
),然后将整个项目打包为一个jar
包,服务端这边引入这个二方库,然后实现相应的功能,客户端这边也只需要引入这个二方库即可调用了。为什么这么做?主要是为了减少客户端这边的jar
包大小,RPC数据包较小,因为每一次打包发布的时候,jar
包太多总是会影响效率。另外也是将客户端和服务端解耦,提高代码的可移植性。同步调用与异步调用:
- 同步调用就是客户端等待调用执行完成并返回结果。
- 异步调用就是客户端不等待调用执行完成返回结果,不过依然可以通过回调函数等接收到返回结果的通知。如果客户端并不关心结果,则可以变成一个单向的调用。这个过程有点类似于Java中的callable和runnable接口,我们进行异步执行的时候,如果需要知道执行的结果,就可以使用callable接口,并且可以通过Future类获取到异步执行的结果信息。如果不关心执行的结果,直接使用runnable接口就可以了,因为它不返回结果,当然callable也是可以的,我们不去获取Future就可以了。
流行的RPC框架:
目前流行的开源RPC框架还是比较多的。下面重点介绍三种:- gRPC是Google的开源软件,基于最新的HTTP2.0协议,并支持常见的众多编程语言。 我们知道HTTP2.0是基于二进制的HTTP协议升级版本,目前各大浏览器都在快马加鞭的加以支持。 这个RPC框架是基于HTTP协议实现的,底层使用到了Netty框架的支持。
- Thrift是Facebook的一个开源项目,主要是一个跨语言的服务开发框架。它有一个代码生成器来对它所定义的IDL定义文件自动生成服务代码框架。用户只要在其之前进行二次开发就行,对于底层的RPC通讯等都是透明的。不过这个对于用户来说的话需要学习特定领域语言这个特性,还是有一定成本的。
- Dubbo是阿里集团开源的一个极为出名的RPC框架,在很多互联网公司和企业应用中广泛使用。协议和序列化框架都可以插拔是及其鲜明的特色。同样 的远程接口是基于Java Interface,并且依托于spring框架方便开发。可以方便的打包成单一文件,独立进程运行,和现在的微服务概念一致。
RPC框架要做到的最基本的三件事:
1、服务端如何确定客户端要调用的函数;
在远程调用中,客户端和服务端分别维护一个【ID->函数】的对应表, ID在所有进程中都是唯一确定的。客户端在做远程过程调用时,附上这个ID,服务端通过查表,来确定客户端需要调用的函数,然后执行相应函数的代码。
2、如何进行序列化和反序列化;
客户端和服务端交互时将参数或结果转化为字节流在网络中传输,那么数据转化为字节流的或者将字节流转换成能读取的固定格式时就需要进行序列化和反序列化,序列化和反序列化的速度也会影响远程调用的效率。
3、如何进行网络传输(选择何种网络协议);
多数RPC框架选择TCP作为传输协议,也有部分选择HTTP。如gRPC使用HTTP2。不同的协议各有利弊。TCP更加高效,而HTTP在实际应用中更加的灵活。
REST
REST即表述性状态传递(Representational State Transfer,简称REST),是一种软件架构风格。REST通过HTTP协议定义的通用动词方法(GET、PUT、DELETE、POST) ,以URI对网络资源进行唯一标识,响应端根据请求端的不同需求,通过无状态通信,对其请求的资源进行表述。
Rest架构的主要原则:
网络上的所有事物都被抽象为资源
每个资源都有一个唯一的资源标识符
同一个资源具有多种表现形式(xml,json等)
对资源的各种操作不会改变资源标识符
所有的操作都是无状态的
其中表述性状态,是指(在某个瞬间状态的)资源数据的快照,包括资源数据的内容、表述格式(XML、JSON)等信息。
其中无状态通信,是指服务端(响应端)不保存任何与特定HTTP请求相关的资源,应用状态必须由请求方在请求过程中提供。要求在网络通信过程中,任意一个Web请求必须与其他请求隔离,当请求端提出请求时,请求本身包含了响应端为响应这一请求所需的全部信息。
REST使用HTTP+URI+XML /JSON 的技术来实现其API要求的架构风格:HTTP协议和URI用于统一接口和定位资源,文本、二进制流、XML、JSON等格式用来作为资源的表述。
满足REST约束条件和原则的架构,就被称为是RESTful架构。就像URL都是URI(统一资源标识)的表现形式一样,RESTful是符合REST原则的表现形式。
RPC和REST比较
RPC优缺点
原理:socket+动态代理
优点:
调用简单,清晰,透明,不用像 rest 一样复杂,就像调用本地方法一样简单
高效低延迟,性能高
自定义协议(让传输报文提及更小),数据包较小
性能消耗低,高效的序列化协议可以支持高效的二进制传输
自带负载均衡
缺点:
耦合性强
他人总结:
我们为每个微服务定义了各自的 service 抽象接口,并通过持续集成发布到私有仓库中,调用方应用对微服务提供的抽象接口存在强依赖关系,因此不论开发、测试、集成环境都需要严格的管理版本依赖,才不会出现服务方与调用方的不一致导致应用无法编译成功等一系列问题,以及这也会直接影响本地开发的环境要求,往往一个依赖很多服务的上层应用,每天都要更新很多代码并 install 之后才能进行后续的开发。若没有严格的版本管理制度或开发一些自动化工具,这样的依赖关系会成为开发团队的一大噩梦。
而 REST 接口相比 RPC 更为轻量化,服务提供方和调用方的依赖只是依靠一纸契约,不存在代码级别的强依赖,当然 REST 接口也有痛点,因为接口定义过轻,很容易导致定义文档与实际实现不一致导致服务集成时的问题,但是该问题很好解决,只需要通过每个服务整合swagger,让每个服务的代码与文档一体化,就能解决。所以在分布式环境下,REST 方式的服务依赖要比 RPC 方式的依赖更为灵活。无法跨语言,平台敏感
Java 写的 RPC 微服务无法给 Python 调用,需要再实现一层 REST 来对外提供服务。
REST
- 原理:HTTP调用
- 优点:
- 耦合性低,兼容性好,提高开发效率
- 不用关心接口实现细节,相对更规范,更标准,更通用,跨语言支持
- 缺点:
- 性能不如 RPC 高
- HTTP数据包较大
应用场景选择
RPC 适用于内网服务调用,对外提供服务请走 REST。
IO 密集的服务调用用 RPC,低频服务用 REST。
服务调用过于密集与复杂,RPC 就比较适用。
REST和RPC都常用于微服务架构中。
- HTTP相对更规范,更标准,更通用,无论哪种语言都支持http协议。如果你是对外开放API,例如开放平台,外部的编程语言多种多样,你无法拒绝对每种语言的支持,现在开源中间件,基本最先支持的几个协议都包含RESTful。
- RPC 框架作为架构微服务化的基础组件,它能大大降低架构微服务化的成本,提高调用方与服务提供方的研发效率,屏蔽跨进程调用函数(服务)的各类复杂细节。让调用方感觉就像调用本地函数一样调用远端函数、让服务提供方感觉就像实现一个本地函数一样来实现服务。
5.2-Ribbon
什么是负载均衡?
负载均衡是微服务架构中必须使用的技术,通过负载均衡来实现系统的高可用、集群扩容等功能。负载均衡可通过硬件设备及软件来实现,硬件比如:F5、Array等,软件比如:LVS、Nginx等。
用户请求先到达负载均衡器(也相当于一个服务),负载均衡器根据负载均衡算法将请求转发到微服务。负载均衡算法有:轮训、随机、加权轮训、加权随机、地址哈希等方法,负载均衡器维护一份服务列表,根据负载均衡算法将请求转发到相应的微服务上,所以负载均衡可以为微服务集群分担请求,降低系统的压力。
什么是客户端负载均衡?
上图是服务端负载均衡,客户端负载均衡与服务端负载均衡的区别在于客户端要维护一份服务列表,Ribbon从Eureka Server获取服务列表,Ribbon根据负载均衡算法直接请求到具体的微服务,然后进行访问,这是客户端负载均衡。
当我们将Ribbon和Eureka一起使用时,Ribbon会从Eureka注册中心去获取服务端列表,然后进行轮询访问以到达负载均衡的作用,客户端负载均衡中也需要心跳机制去维护服务端清单的有效性,当然这个过程需要配合服务注册中心一起完成。
什么是服务端负载均衡?
负载均衡是我们处理高并发、缓解网络压力和进行服务端扩容的重要手段之一,但是一般情况下我们所说的负载均衡通常都是指服务端负载均衡,服务端负载均衡又分为两种,一种是硬件负载均衡,还有一种是软件负载均衡。
硬件负载均衡主要通过在服务器节点之间安装专门用于负载均衡的设备,常见的如F5。
软件负载均衡则主要是在服务器上安装一些具有负载均衡功能的软件来完成请求分发进而实现负载均衡,常见的就是Nginx。
无论是硬件负载均衡还是软件负载均衡都会维护一个可用的服务端清单,然后通过心跳机制来删除故障的服务端节点以保证清单中都是可以正常访问的服务端节点,此时当客户端的请求到达负载均衡服务器时,负载均衡服务器按照某种配置好的规则从可用服务端清单中选出一台服务器去处理客户端的请求。这就是服务端负载均衡。
Ribbon是什么?
Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随即连接等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法。
Load Balance负载均衡是用于解决一台机器(一个进程)无法解决所有请求而产生的一种算法。像nginx可以使用负载均衡分配流量,ribbon为客户端提供负载均衡,dubbo服务调用里的负载均衡等等,很多地方都使用到了负载均衡。
使用负载均衡带来的好处很明显(系统高可用、网络压力缓解、处理能力扩容):
当集群里的1台或者多台服务器down的时候,剩余的没有down的服务器可以保证服务的继续使用
使用了更多的机器保证了机器的良性使用,不会由于某一高峰时刻导致系统cpu急剧上升
负载均衡有好几种实现策略,常见的有:- 随机 (Random)
- 轮询 (RoundRobin)
- 一致性哈希 (ConsistentHash)
- 哈希 (Hash)
- 加权(Weighted)(默认会启动一个每隔30秒的定时任务来为每个服务实例计算权重.)
服务发现的任务由Eureka完成,而服务消费的任务由Ribbon完成,它是一个基于Http和TCP的客户端负载均衡器,可以通过在客户端中配置的ribbonServerList服务端列表去轮询访问以达到均衡负载的作用(对于服务提供方,同一服务的实例通常会有多个来保证服务的高可用性).
Ribbon实战
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>在application.yml中添加配置
ribbon:
MaxAutoRetries: 2 #最大重试次数,当Eureka中可以找到服务,但是服务连不上时将会重试
MaxAutoRetriesNextServer: 3 #切换实例的重试次数
OkToRetryOnAllOperations: false #对所有操作请求都进行重试,如果是get则可以,如果是post,put等操作没有实现幂等的情况下是很危险的,所以设置为false
ConnectTimeout: 5000 #请求连接的超时时间
ReadTimeout: 6000 #请求处理的超时时间在订单服务启动类中添加如下代码:
public RestTemplate restTemplate() {
return new RestTemplate();
}修改调用方式,不再手动获取ip和端口,而是直接通过服务名称调用:
public ProductOrder save(int userId, int productId) {
Object obj = restTemplate.getForObject("http://product-service/api/v1/product/find?id="+productId, Object.class);
System.out.println(obj);
ProductOrder productOrder = new ProductOrder();
productOrder.setCreateTime(new Date());
productOrder.setUserId(userId);
productOrder.setTradeNo(UUID.randomUUID().toString());
return productOrder;
}控制台输出如下:
2020-01-18 22:32:24.792 INFO 28215 --- [nio-8081-exec-1] c.n.l.DynamicServerListLoadBalancer : DynamicServerListLoadBalancer for client product-service initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=product-service,current list of Servers=[127.0.0.1:8082],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone; Instance count:1; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;]
},Server stats: [[Server:127.0.0.1:8082; Zone:defaultZone; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0]
]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@56d1dd2a
{id=6, name=椅子, price=253, store=20}将商品服务多节点启动,会看到订单服务会通过Ribbon的负载均衡来调用商品服务。
Ribbon修改负载均衡策略
负载均衡,不管
Nginx
还是Ribbon
都需要其算法的支持,如果我没记错的话Nginx
使用的是 轮询和加权轮询算法。而在Ribbon
中有更多的负载均衡调度算法,其默认是使用的RoundRobinRule
轮询策略。- RoundRobinRule:轮询策略。
Ribbon
默认采用的策略。若经过一轮轮询没有找到可用的provider
,其最多轮询 10 轮。若最终还没有找到,则返回 null。 - RandomRule: 随机策略,从所有可用的 provider 中随机选择一个。
- RetryRule: 重试策略。先按照 RoundRobinRule 策略获取 provider,若获取失败,则在指定的时限内重试。默认的时限为 500 毫秒。
在application.yml配置文件中加入:
- RoundRobinRule:轮询策略。
#自定义负载均衡策略 |
策略选择:
- 如果每个机器配置一样,则建议不修改策略 (推荐)
- 如果部分机器配置强,则可以改为 WeightedResponseTimeRule
5.3-微服务调用方式之Feign
Feign是什么?
Feign是Netflix开发的声明式、模板化的HTTP客户端, Feign可以帮助我们更快捷、优雅地调用HTTP API。
Feign是一个声明式WebService客户端,使用Feign能让编写WebService客户端更加简单,它的使用方法是定义一个接口,然后在上面添加注解,同时也支持JAX-RS标准的注解。Feign也支持可拔插式的编码器和解码器,Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters.Feign可以与Eureka和Ribbon组合使用以支持负载均衡。
只需要你创建一个接口,然后在上面添加注解即可。
Feign 是一种声明式、模板化的 HTTP 客户端。在 Spring Cloud 中使用 Feign,可以做到使用 HTTP 请求访问远程服务,就像调用本地方法一样的,开发者完全感知不到这是在调用远程方法,更感知不到在访问 HTTP 请求。接下来介绍一下 Feign 的特性,具体如下:
- 可插拔的注解支持,包括 Feign 注解和AX-RS注解。
- 支持可插拔的 HTTP 编码器和解码器。
- 支持 Hystrix 和它的 Fallback。
- 支持 Ribbon 的负载均衡。
- 支持 HTTP 请求和响应的压缩。Feign 是一个声明式的 WebService 客户端,它的目的就是让 Web Service 调用更加简单。它整合了 Ribbon 和 Hystrix,从而不需要开发者针对 Feign 对其进行整合。Feign 还提供了 HTTP 请求的模板,通过编写简单的接口和注解,就可以定义好 HTTP 请求的参数、格式、地址等信息。Feign 会完全代理 HTTP 的请求,在使用过程中我们只需要依赖注入 Bean,然后调用对应的方法传递参数即可。
Feign能干什么?
Feign旨在使编写Java Htpp客户端变得更容易。
使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模板化的调用方法.但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用.所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需要创建一个接口并使用注解的方式来配置它(以前是Dao接口上标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。
添加Feign依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
# 注意:新旧版本依赖名称不同添加配置
#feign的配置,连接超时及读取超时配置
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic启动类增加注解
@EnableFeignClients
(开启 Feign 扫描支持)Feign接口编写
public interface ProductFeignClient {
String findById(int id) ;
}Controller
RestController
public class ProductController {
private ProductFeignClient productFeignClient;
String findById(int id) {
return productFeignClient.findById(id);
}
}Feign工作原理
- 在开发微服务应用时,我们会在主程序入口添加 @EnableFeignClients 注解开启对 Feign Client 扫描加载处理。根据 Feign Client 的开发规范,定义接口并加 @FeignClients 注解。
- 当程序启动时,会进行包扫描,扫描所有 @FeignClients 的注解的类,并将这些信息注入 Spring IOC 容器中。当定义的 Feign 接口中的方法被调用时,通过JDK的代理的方式,来生成具体的 RequestTemplate。当生成代理时,Feign 会为每个接口方法创建一个 RequetTemplate 对象,该对象封装了 HTTP 请求需要的全部信息,如请求参数名、请求方法等信息都是在这个过程中确定的。
- 然后由 RequestTemplate 生成 Request,然后把 Request 交给 Client 去处理,这里指的 Client 可以是 JDK 原生的 URLConnection、Apache 的 Http Client 也可以是 Okhttp。最后 Client 被封装到 LoadBalanceclient 类,这个类结合 Ribbon 负载均衡发起服务之间的调用。
@FeignClient 注解
- name:指定 Feign Client 的名称,如果项目使用了 Ribbon,name 属性会作为微服务的名称,用于服务发现。
- url:url 一般用于调试,可以手动指定 @FeignClient 调用的地址。
- decode404:当发生404错误时,如果该字段为 true,会调用 decoder 进行解码,否则抛出 FeignException。
- configuration:Feign 配置类,可以自定义 Feign 的 Encoder、Decoder、LogLevel、Contract。
- fallback:定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback 指定的类必须实现 @FeignClient 标记的接口。
- fallbackFactory:工厂类,用于生成 fallback 类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码。
- path:定义当前 FeignClient 的统一前缀。
超时时间配置
如果在一个微服务当中对同一个接口同时配置了Hystrix与ribbon两个超时时间,则在接口调用的时候,两个计时器会同时读秒。
比如,访问一个接口需要2秒,你的ribbon配置的超时时间是3秒,Hystrix配置的超时时间是1秒。
在这种情况下,程序会回调进入到Hystrix的fallback方法,因为在访问接口的时候,Hystrix与ribbon的两个计时器同时计时,而在Hystrix计时器结束的时候自动停止了访问进行回调,进入fallback方法。
如果没有配置Hystrix的话,访问一个接口需要2秒,你的ribbon配置的超时时间是3秒,Hystrix配置的超时时间是1秒,不会有异常。
在这个地方建议配置Hystrix的超时时间要大于ribbon的超时时间,否则会在接口调用还未完成的时候直接进入回调方法。
Hystrix与ribbon的默认请求超时时间都是1秒
配置如下:
server:
port: 8200
spring:
application:
name: testFeign
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8100/eureka/
register-with-eureka: true
fetch-registry: true
###设置feign客户端超时时间
###SpringCloud feign 默认开启支持ribbon
ribbon:
###指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间。
ReadTimeout: 5000
###指的是建立连接后从服务器读取到可用资源所用的时间。
ConnectTimeout: 5000
###配置请求超时时间
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 7000
###配置具体方法超时时间
serverMethod:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
###开启Hystrix断路器
feign:
hystrix:
enabled: true一般情况下 都是
ribbon 的超时时间(<)hystrix的超时时间
(因为涉及到ribbon的重试机制) 因为ribbon的重试机制和Feign的重试机制有冲突,所以源码中默认关闭Feign的重试机制。Feign和Ribbon的区别:
- 启动类使用的注解不同,Ribbon用的是@RibbonClient,Feign用的是@EnableFeignClients。
- 服务的指定位置不同,Ribbon是在@RibbonClient注解上声明,Feign则是在定义抽象方法的接口中使用@FeignClient声明。
- 调用方式不同,Ribbon需要自己构建http请求,是一个基于 HTTP 和 TCP 客户端 的负载均衡的工具。它可以在客户端配置
RibbonServerList
(服务端列表),使用 HttpClient 或 RestTemplate http请求然后使用RestTemplate发送给其他服务,步骤相当繁琐。Feign则是在Ribbon的基础上进行了一次改进,采用接口的方式,将需要调用的其他服务的方法定义成抽象方法即可,不需要自己构建http请求。然后就像是调用自身工程的方法调用,而感觉不到是调用远程方法,使得编写 客户端变得非常容易。要注意的是抽象方法的注解、方法签名要和提供服务的方法完全一致。
6-服务降级熔断 Hystrix
6.1-分布式核心知识之服务熔断、降级、限流
服务熔断:
在介绍熔断机制之前,我们需要了解微服务的雪崩效应。在微服务架构中,微服务是完成一个单一的业务功能,这样做的好处是可以做到解耦,每个微服务可以独立演进。但是,一个应用可能会有多个微服务组成,微服务之间的数据交互通过远程过程调用完成。这就带来一个问题,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。
熔断机制是应对雪崩效应的一种微服务链路保护机制。我们在各种场景下都会接触到熔断这两个字。高压电路中,如果某个地方的电压过高,熔断器就会熔断,对电路进行保护。股票交易中,如果股票指数过高,也会采用熔断机制,暂停股票的交易。同样,在微服务架构中,熔断机制也是起着类似的作用。当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。
服务熔断一般是指软件系统中,由于某些原因使得服务出现了过载现象,为防止造成整个系统故障,从而采用的一种保护措施,所以很多地方把熔断亦称为过载保护;服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;熔断其实是一个框架级的处理,每个微服务都需要(无层级之分),而降级一般需要对业务有层级之分(比如降级一般是从最外围服务开始)
在Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。
服务降级
- 当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心交易正常运作或高效运作;服务降级主要用于什么场景呢?当整个微服务架构整体的负载超出了预设的上限阈值或即将到来的流量预计将会超过预设的阈值时,为了保证重要或基本的服务能正常运行,我们可以将一些 不重要 或 不紧急 的服务或任务进行服务的 延迟使用 或 暂停使用,
- 是当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行;对于复杂系统而言,会有很多的微服务通过 rpc 调用,从而产生一个业务需要一条很长的调用链,其中任何一环故障了都会导致整个调用链失败或超时而导致业务服务不可用或阻塞。这种情况下,可以暂时去掉调用链中故障的服务来进行降级,其中降级策略又有很多种,比如限流,接口拒绝等。
服务熔断和降级的区别
相同点:
- 目的:目的很一致,都是从可用性可靠性着想,为防止系统的整体缓慢甚至崩溃,采用的技术手段;
- 最终表现:最终表现类似,对于两者来说,最终让用户体验到的是某些功能暂时不可达或不可用;
- 粒度:粒度一般都是服务级别,当然,业界也有不少更细粒度的做法,比如做到数据持久层(允许查询,不允许增删改);
- 自治:自治性要求很高,熔断模式一般都是服务基于策略的自动触发,降级虽说可人工干预,但在微服务架构下,完全靠人显然不可能,开关预置、配置中心都是必要手段;
区别:
- 触发原因不太一样,服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;
- 管理目标的层次不太一样,熔断其实是一个框架级的处理,每个微服务都需要(无层级之分),而降级一般需对业务有层级之分(比如降级一般是从最外围服务开始)
- 实现方式不太一样;服务降级具有代码侵入性(由控制器完成/或自动降级),熔断一般称为自我熔断。
服务限流
- 在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流。缓存的目的是提升系统访问速度和增大系统能处理的容量,可谓是抗高并发流量的银弹;而降级是当服务出问题或者影响到核心流程的性能则需要暂时屏蔽掉,待高峰或者问题解决后再打开;而有些场景并不能用缓存和降级来解决,比如稀缺资源(秒杀、抢购)、写服务(如评论、下单)、频繁的复杂查询(评论的最后几页),因此需有一种手段来限制这些场景的并发/请求量,即限流。
- 限流的目的是通过对并发访问/请求进行限速或者一个时间窗口内的的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务(定向到错误页或告知资源没有了)、排队或等待(比如秒杀、评论、下单)、降级(返回兜底数据或默认数据,如商品详情页库存默认有货)。
- 一般开发高并发系统常见的限流有:限制总并发数(比如数据库连接池、线程池)、限制瞬时并发数(如nginx的limit_conn模块,用来限制瞬时并发连接数)、限制时间窗口内的平均速率(如Guava的RateLimiter、nginx的limit_req模块,限制每秒的平均速率);其他还有如限制远程接口调用速率、限制MQ的消费速率。另外还可以根据网络连接数、网络流量、CPU或内存负载等来限流。
- 常见的限流算法有:令牌桶、漏桶。计数器也可以进行粗暴限流实现。
6.2-Netflix开源组件断路器Hystrix
文档地址:
https://github.com/Netflix/Hystrix
https://github.com/Netflix/Hystrix/wiki
6.2.1-Hystrix是什么?
在一个分布式系统里,一个服务依赖多个服务,可能存在某个服务调用失败,比如超时、异常等,如何能够保证在一个依赖出问题的情况下,不会导致整体服务失败,通过Hystrix就可以解决这些问题。
Hystrix是一个库,通过添加延迟容忍和容错逻辑,帮助你控制这些分布式服务之间的交互。Hystrix通过隔离服务之间的访问点、停止级联失败和提供回退选项来实现这一点,所有这些都可以提高系统的整体弹性。
在微服务场景中,通常会有很多层的服务调用。如果一个底层服务出现问题,故障会被向上传播给用户。我们需要一种机制,当底层服务不可用时,可以阻断故障的传播。这就是断路器的作用。他是系统服务稳定性的最后一重保障。
在springcloud中断路器组件就是Hystrix。Hystrix也是Netflix套件的一部分。他的功能是,当对某个服务的调用在一定的时间内(默认10s,由metrics.rollingStats.timeInMilliseconds配置),有超过一定次数(默认20次,由circuitBreaker.requestVolumeThreshold参数配置)并且失败率超过一定值(默认50%,由circuitBreaker.errorThresholdPercentage配置),该服务的断路器会打开。返回一个由开发者设定的fallback
fallback可以是另一个由Hystrix保护的服务调用,也可以是固定的值。fallback也可以设计成链式调用,先执行某些逻辑,再返回fallback。
6.2.2-Hystrix设计目的
- 通过第三方客户端库提供保护,并控制延迟和失败(通常通过网络)依赖项的故障。
- 停止复杂的分布式系统中的级联故障。
- 快速失败,迅速恢复。
- 回退并在可能的情况下正常降级。
- 启用近乎实时的监视,警报和操作控制。
6.2.3-Hystrix解决什么问题?
复杂分布式体系结构中的应用程序具有数十种依赖关系,每种依赖关系不可避免地会在某个时刻失败。如果主机应用程序未与这些外部故障隔离开来,则可能会被淘汰。
例如,对于依赖于30个服务的应用程序,其中每个服务的正常运行时间为99.99%,您可以期望:
99.99 30 = 99.7%的正常运行时间
10亿个请求中的0.3%= 3,000,000次故障
/每月2小时以上的停机时间,即使所有依赖项都具有出色的正常运行时间。
现实通常更糟。
即使您没有对整个系统进行永续性设计,即使所有依赖项都能很好地执行,即使0.01%的停机时间对数十种服务中的每一项的总影响也等于一个月可能停机数小时。
当一切正常时,请求流如下所示:
当许多后端系统之一变得潜在时,它可以阻止整个用户请求:
随着高流量,单个后端依赖性变得潜在,这可能导致所有服务器上的所有资源在几秒钟内变得饱和。
应用程序中可能会引起网络请求的,通过网络或客户端库进行访问的每个点都是潜在失败的根源。比故障更糟糕的是,这些应用程序还会导致服务之间的延迟增加,从而备份队列,线程和其他系统资源,从而导致整个系统出现更多级联故障。
当通过第三方客户端执行网络访问时,这些问题会更加严重。在“黑盒子”中,实现细节被隐藏并且可以随时更改,并且每个客户端库的网络或资源配置都不相同,并且通常难以监视和监控。更改。
更糟糕的是,传递依赖项会执行潜在的昂贵或易出错的网络调用,而不会被应用程序明确调用。
网络连接失败或降级。服务和服务器出现故障或变慢。新的库或服务部署会更改行为或性能特征。客户端库有错误。
所有这些都代表需要隔离和管理的故障和延迟,以使单个故障依赖项无法关闭整个应用程序或系统。
6.2.3-Hystrix遵循的设计原则:
- 防止任何单个依赖项耗尽所有容器(例如Tomcat)用户线程。
- 减少负载并快速失败,而不是排队。
- 在可行的情况下提供备用,以保护用户免受故障的影响。
- 使用隔离技术(例如隔板,泳道和断路器模式)来限制任何一种依赖关系的影响。
- 通过近实时指标,监视和警报优化发现时间
- 通过在Hystrix的大多数方面中以低延迟传播配置更改来优化恢复时间,并支持动态属性更改,这使您可以通过低延迟反馈环路进行实时操作修改。
- 防止整个依赖项客户端执行失败,而不仅仅是网络流量失败。
6.2.4-Hystrix如何实现这些设计目标?
- 将对外部系统(或“依赖项”)的所有调用包装在通常在单独线程中执行的
HystrixCommand
或HystrixObservableCommand
对象中(这是命令模式的示例)。 - 超时呼叫花费的时间超过您定义的阈值。有一个默认的,而是由“属性”,使它们比测量的99.5略高的方式对大多数依赖你自定义设置这些超时个百分点每个依存性的性能。
- 为每个依赖项维护一个小的线程池(或信号灯);如果已满,发往该依赖项的请求将立即被拒绝,而不是排队。
- 测量成功,失败(客户端抛出的异常),超时和线程拒绝。
- 如果某个服务的错误百分比超过阈值,则使断路器跳闸,以在一段时间内手动或自动停止所有对特定服务的请求。
- 当请求失败,被拒绝,超时或短路时执行回退逻辑。
- 几乎实时监控指标和配置更改。
当您使用Hystrix封装每个基础依赖项时,如上图所示的体系结构将更改为类似于下图。每个依赖项彼此隔离,受到延迟时发生饱和的资源的限制,并包含回退逻辑,该逻辑决定了在依赖项中发生任何类型的故障时做出什么响应:
6.3-Feign结合Hystrix断路器开发实战
- 加入依赖
<dependency> |
注意:网上新旧版本问题,所以要以官网为主,不然部分注解会丢失
- 启动类增加注解
@EnableCircuitBreaker
|
- API接口编码实战
熔断->服务降级
|
编写fallback方法实现,方法签名一定要和api方法签名一致(注意点!!!)
- Feign结合Hystrix断路器
开启feign支持hystrix (注意,一定要开启,旧版本默认支持,新版本默认关闭)
feign:
hystrix:
enabled: trueFeignClient(name="xxx", fallback=xxx.class )
, class需要继承当前FeignClient的类自定义服务降级处理
/**
* 针对商品服务,做降级处理
*/
public class ProductClientFallback implements ProductClient {
public String findById(int id) {
System.out.println("feign 调用product-service findbyid 异常");
return null;
}
}/**
* 商品服务客户端
*/
public interface ProductClient {
String findById(int id) ;
}
6.4-熔断降级服务异常报警通知实战
加入redis依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>配置redis连接信息
redis:
database: 0
host: 127.0.0.1
port: 6379
timeout: 2000监控报警
public class OrderController {
private ProductOrderService productOrderService;
private StringRedisTemplate redisTemplate;
public Object save(int userId, int productId, HttpServletRequest request){
Map<String, Object> data = new HashMap<>();
data.put("code", 0);
data.put("data", productOrderService.save(userId, productId));
return data;
}
//注意,方法签名一定要要和api方法一致
private Object saveOrderFail(int userId, int productId, HttpServletRequest request){
//监控报警
String saveOrderKey = "save-order";
String sendValue = redisTemplate.opsForValue().get(saveOrderKey);
final String ip = request.getRemoteAddr();
new Thread( ()->{
if (StringUtils.isBlank(sendValue)) {
System.out.println("紧急短信,用户下单失败,请离开查找原因,ip地址是="+ip);
//发送一个http请求,调用短信服务 TODO
redisTemplate.opsForValue().set(saveOrderKye, "save-order-fail", 20, TimeUnit.SECONDS);
}else{
System.out.println("已经发送过短信,20秒内不重复发送");
}
}).start();
Map<String, Object> msg = new HashMap<>();
msg.put("code", -1);
msg.put("msg", "抢购人数太多,您被挤出来了,稍等重试");
return msg;
}
}
6.5-Hystrix降级策略和调整
查看默认降级策略 HystrixCommandProperties
execution.isolation.strategy
隔离策略
THREAD 线程池隔离 (默认)
SEMAPHORE 信号量
信号量适用于接口并发量高的情况,如每秒数千次调用的情况,导致的线程开销过高,通常只适用于非网络调用,执行速度快execution.isolation.thread.timeoutInMilliseconds
超时时间
默认 1000毫秒execution.timeout.enabled
是否开启超时限制 (一定不要禁用)execution.isolation.semaphore.maxConcurrentRequests
隔离策略为 信号量的时候,如果达到最大并发数时,后续请求会被拒绝,默认是10
官方文档:https://github.com/Netflix/Hystrix/wiki/Configuration#execution.isolation.strategy
调整策略
超时时间调整hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 4000
6.6-断路器Dashboard监控仪表盘
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>启动类增加注解
@EnableHystrixDashboard
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
public RestTemplate restTemplate() {
return new RestTemplate();
}
}配置文件增加endpoint
management:
endpoints:
web:
exposure:
include: "*"访问入口
http://localhost:8080/hystrix
Hystrix Dashboard输入: http://localhost:8080/actuator/hystrix.stream
参考资料默认开启监控配置 https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-security-actuator 配置文件类:spring-configuration-metadata.json
7-微服务网关zuul开发实战
7.1-微服务网关介绍和使用场景
7.1.1-什么是网关?
API Gateway,是系统的唯一对外的入口,介于客户端和服务器端之间的中间层,处理非业务功能 提供路由请求、鉴权、监控、缓存、限流等功能。
- 统一接入:
智能路由
AB测试、灰度测试
负载均衡、容灾处理
日志埋点(类似Nignx日志) - 流量监控:
限流处理
服务降级 - 安全防护:
鉴权处理
监控
机器网络隔离
7.1.2-主流的网关
- zuul:是Netflix开源的微服务网关,和Eureka,Ribbon,Hystrix等组件配合使用,Zuul 2.0比1.0的性能提高很多。
- kong: 由Mashape公司开源的,基于Nginx的API gateway。
- nginx+lua:是一个高性能的HTTP和反向代理服务器,lua是脚本语言,让Nginx执行Lua脚本,并且高并发、非阻塞的处理各种请求
- spring-cloud-gateway:是spring 出品的 基于spring 的网关项目,集成断路器,路径重写,性能比Zuul好,是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。Spring Cloud Gateway作为Spring Cloud生态系中的网关,目标是替代ZUUL,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。
7.1.3-微服务网关概述
不同的微服务一般会有不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信,会有以下的问题:
- 客户端会多次请求不同的微服务,增加了客户端的复杂性
- 存在跨域请求,在一定场景下处理相对复杂
- 认证复杂,每个服务都需要独立认证
- 难以重构,随着项目的迭代,可能需要重新划分微服务。例如,可能将多个服务合并成一个或者将一个服务拆分成多个。如果客户端直接与微服务通信,那么重构将会很难实施
- 某些微服务可能使用了防火墙 / 浏览器不友好的协议,直接访问会有一定的困难
以上这些问题可以借助网关解决。
网关是介于客户端和服务器端之间的中间层,所有的外部请求都会先经过 网关这一层。也就是说,API 的实现方面更多的考虑业务逻辑,而安全、性能、监控可以交由 网关来做,这样既提高业务灵活性又不缺安全性,典型的架构图如图所示:
优点:
- 安全 ,只有网关系统对外进行暴露,微服务可以隐藏在内网,通过防火墙保护。
- 易于监控。可以在网关收集监控数据并将其推送到外部系统进行分析。
- 易于认证。可以在网关上进行认证,然后再将请求转发到后端的微服务,而无须在每个微服务中进行认证。
- 减少了客户端与各个微服务之间的交互次数
- 易于统一鉴权。
总结:微服务网关就是一个系统,通过暴露该微服务网关系统,方便我们进行相关的鉴权,安全控制,日志统一处理,易于监控的相关功能。
7.2-SpringCloud的网关组件zuul基本使用
路由在微服务体系结构的一个组成部分。例如,/
可以映射到您的Web应用程序,/api/users
映射到用户服务,并将/api/shop
映射到商店服务。Zuul是Netflix的基于JVM的路由器和服务器端负载均衡器。
Netflix使用Zuul进行以下操作:
- 认证
- 洞察
- 压力测试
- 金丝雀测试
- 动态路由
- 服务迁移
- 负载脱落
- 安全
- 静态响应处理
- 主动/主动流量管理
Zuul的规则引擎允许基本上写任何JVM语言编写规则和过滤器,内置Java和Groovy。
Zuul是NetFlix开源的微服务网关,它可以和Eureka、Ribbon、Hystrix等组件配合使用。Zuul的核心是一些列的过滤器,这些过滤器可以完成以下功能。
- 身份认证与安全:识别每个资源的验证要求,并拒绝那些与要求不符合的请求。
- 审查与监控:在边缘位置追踪有意义的数据和统计结果,从而带来精确的生产视图。
- 动态路由:动态的将请求路由到不同的后端集群。
- 压力测试:逐渐增加指向集群的流量,以了解性能。
- 负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求。
- 静态响应处理:在边缘位置直接建立部分响应,从而避免其转发到内部集群。
- 多区域弹性:跨越AWS Region进行请求路由,旨在实现ELB(Elastic Load Balancing)使用的多样化,以及让系统的便越更贴近系统的使用者。
Spring Cloud对Zuul进行了整合与增强。目前,Zuul使用的默认HTTP客户端是Apache HTTP Client,也可以使用使用其他的。
上面的你理解了,那么就能理解关于Zuul
最基本的配置了,看下面。
server: |
然后在启动类上加入@EnableZuulProxy
注解就行了。没错,就是那么简单😃。
统一前缀
这个很简单,就是我们可以在前面加一个统一的前缀,比如我们刚刚调用的是localhost:9000/consumer1/studentInfo/update
,这个时候我们在yaml
配置文件中添加如下。
zuul: |
这样我们就需要通过localhost:9000/zuul/consumer1/studentInfo/update
来进行访问了。
路由策略配置
你会发现前面的访问方式(直接使用服务名),需要将微服务名称暴露给用户,会存在安全性问题。所以,可以自定义路径来替代微服务名称,即自定义路由策略。
zuul: |
这个时候你就可以使用localhost:9000/zuul/FrancisQ1/studentInfo/update
进行访问了。
服务名屏蔽
这个时候你别以为你好了,你可以试试,在你配置完路由策略之后使用微服务名称还是可以访问的,这个时候你需要将服务名屏蔽。
zuul: |
路径屏蔽
Zuul
还可以指定屏蔽掉的路径 URI,即只要用户请求中包含指定的 URI 路径,那么该请求将无法访问到指定的服务。通过该方式可以限制用户的权限。
zuul: |
这样关于 auto 的请求我们就可以过滤掉了。
** 代表匹配多级任意路径
*代表匹配一级任意路径
敏感请求头屏蔽
默认情况下,像 Cookie、Set-Cookie 等敏感请求头信息会被 zuul 屏蔽掉,我们可以将这些默认屏蔽去掉,当然,也可以添加要屏蔽的请求头。
7.2.1-添加依赖
<dependency> |
7.2.2-在启动类上加注解@EnableZuulProxy
|
7.2.3-添加配置(application.yml)
server: |
默认访问规则:http://gateway:port/service-id/**
自定义路由转发:
zuul: |
环境隔离配置:
需求 :不想让默认的服务对外暴露接口
zuul: |
7.2.4-使用Zuul
1. 拦截请求
在服务网关中定义过滤器只需要继承ZuulFilter
抽象类实现其定义的四个抽象函数就可对请求进行拦截与过滤。
比如下面的例子,定义了一个Zuul过滤器,实现了在请求被路由之前检查请求中是否有accessToken
参数,若有就进行路由,若没有就拒绝访问,返回401 Unauthorized
错误。
public class AccessFilter extends ZuulFilter { |
自定义过滤器的实现,需要继承ZuulFilter
,需要重写实现下面四个方法:
filterType
:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下:pre
:可以在请求被路由之前调用routing
:在路由请求时候被调用post
:在routing和error过滤器之后被调用error
:处理请求时发生错误时被调用filterOrder
:通过int值来定义过滤器的执行顺序shouldFilter
:返回一个boolean类型来判断该过滤器是否要执行,所以通过此函数可实现过滤器的开关。在上例中,我们直接返回true,所以该过滤器总是生效。run
:过滤器的具体逻辑。需要注意,这里我们通过ctx.setSendZuulResponse(false)
令zuul过滤该请求,不对其进行路由,然后通过ctx.setResponseStatusCode(401)
设置了其返回的错误码,当然我们也可以进一步优化我们的返回,比如,通过ctx.setResponseBody(body)
对返回body内容进行编辑等。
在实现了自定义过滤器之后,还需要实例化该过滤器才能生效,我们只需要在应用主类中增加如下内容:
|
2. 自定义Zuul过滤器实现登录鉴权实战
- 登录过滤器
package net.xdclass.apigateway.filter; |
- 订单限流
package net.xdclass.apigateway.filter; |
application.yml
server: |
8-分布式链路追踪系统Sleuth和ZipKin实战
8.1-微服务下的链路追踪讲解和重要性
8.1.1-微服务下的问题
在大型系统的微服务化构建中,一个系统会被拆分成许多模块。这些模块负责不同的功能,组合成系
统,最终可以提供丰富的功能。在这种架构中,一次请求往往需要涉及到多个服务。互联网应用构建在
不同的软件模块集上,这些软件模块,有可能是由不同的团队开发、可能使用不同的编程语言来实现、
有可能布在了几千台服务器,横跨多个不同的数据中心,也就意味着这种架构形式也会存在一些问题:
- 如何快速发现问题?
- 如何判断故障影响范围?
- 如何梳理服务依赖以及依赖的合理性?
- 如何分析链路性能问题以及实时容量规划?
分布式链路追踪(Distributed Tracing),就是将一次分布式请求还原成调用链路,进行日志记录,性
能监控并将 一次分布式请求的调用情况集中展示。比如各个服务节点上的耗时、请求具体到达哪台机器
上、每个服务节点的请求状态等等。
分布式环境下,尤其是微服务架构应用广泛,各个服务按照不同的维度进行拆分,一次请求请求往往需要涉及到多个服务。这些服务之间的调用可能非常复杂。因此,全链路调用的跟踪就变得非常重要,我们需要一些可以帮助理解系统行为、用于分析性能问题的工具,以便发生故障的时候,能够快速定位和解决。
目前业界比较流行的链路追踪系统如:Twitter的Zipkin,Sleuth, 阿里的鹰眼,美团的Mtrace,大众点评的
cat等,大部分都是基于google发表的 Dapper。Dapper阐述了分布式系统,特别是微服务架构中链路
追踪的概念、数据表示、埋点、传递、收集、存储与展示等技术细节。
8.1.2-什么是分布式链路追踪?
在软件工程中,Tracing 指使用特定的日志记录程序的执行信息,与之相近的还有两个概念,它们分别是 Logging 和 Metrics。
- Logging:用于记录离散的事件,包含程序执行到某一点或某一阶段的详细信息。
- Metrics:可聚合的数据,且通常是固定类型的时序数据,包括 Counter、Gauge、Histogram 等。
- Tracing:记录单个请求的处理流程,其中包括服务调用和处理时长等信息。
同时这三种定义相交的情况也比较常见。
- Logging & Metrics:可聚合的事件。例如分析某对象存储的 Nginx 日志,统计某段时间内 GET、PUT、DELETE、OPTIONS 操作的总数。
- Metrics & Tracing:单个请求中的可计量数据。例如 SQL 执行总时长、gRPC 调用总次数。
- Tracing & Logging:请求阶段的标签数据。例如在 Tracing 的信息中标记详细的错误原因。
针对每种分析需求,我们都有非常强大的集中式分析工具。
- Logging:ELK,近几年势头最猛的日志分析服务,无须多言。
- Metrics:Prometheus,第二个加入 CNCF 的开源项目,非常好用。
- Tracing:OpenTracing 和 Jaeger,Jaeger 是 Uber 开源的一个兼容。OpenTracing 标准的分布式追踪服务。目前 Jaeger 也加入了 CNCF。
8.1.3-为什么需要分布式链路追踪
微服务极大地改变了软件的开发和交付模式,单体应用被拆分为多个微服务,单个服务的复杂度大幅降低,库之间的依赖也转变为服务之间的依赖。由此带来的问题是部署的粒度变得越来越细,众多服务给运维带来巨大压力,不过好在我们有 Kubernetes,可以解决大部分运维方面的难题。
随着服务数量的增多和内部调用链的复杂化,仅凭借日志和性能监控很难做到“See the Whole Picture”,在进行问题排查或是性能分析的时候,无异于盲人摸象。分布式追踪能够帮助开发者直观分析请求链路,快速定位性能瓶颈,逐渐优化服务间依赖,也有助于开发者从更宏观的角度更好地理解整个分布式系统。
8.2-Sleuth
8.2.1-什么是Sleuth?
Sleuth是Spring Cloud的组件之一,它为Spring Cloud实现了一种分布式追踪解决方案,兼容Zipkin,HTrace和其他基于日志的追踪系统,例如 ELK(Elasticsearch 、Logstash、 Kibana)。
Spring Cloud Sleuth 为Spring Cloud提供了分布式根据的解决方案。它大量借用了Google Dapper的
设计。先来了解一下Sleuth中的术语和相关概念。
Spring Cloud Sleuth采用的是Google的开源项目Dapper的专业术语。
- Span :基本工作单元,例如,在一个新建的span中发送一个RPC等同于发送一个回应请求给
RPC,span通过一个64位ID唯一标识,trace以另一个64位ID表示,span还有其他数据信息,比
如摘要、时间戳事件、关键值注释(tags)、span的ID、以及进度ID(通常是IP地址)
span在不断的启动和停止,同时记录了时间信息,当你创建了一个span,你必须在未来的某个时刻停止它。 - Trace :一系列spans组成的一个树状结构,例如,如果你正在跑一个分布式大数据工程,你可能
需要创建一个trace。 - Annotation :用来及时记录一个事件的存在,一些核心annotations用来定义一个请求的开始和结束
- cs - Client Sent - 客户端发起一个请求,这个annotion描述了这个span的开始
- sr - Server Received - 服务端获得请求并准备开始处理它,如果将其sr减去cs时间戳便可得到
网络延迟 - ss - Server Sent - 注解表明请求处理的完成(当请求返回客户端),如果ss减去sr时间戳便可得
到服务端需要的处理请求时间 - cr - Client Received - 表明span的结束,客户端成功接收到服务端的回复,如果cr减去cs时间
戳便可得到客户端从服务端获取回复的所有所需时间。
例如:[order-service,96f95a0dd81fe3ab,852ef4cfcdecabf3,false]
- 第一个值:spring.application.name的值;
- 第二个值:96f95a0dd81fe3ab ,sleuth生成的一个ID,叫Trace ID,用来标识一条请求链路,一条请求链路中包含一个Trace ID,多个Span ID;
- 第三个值:852ef4cfcdecabf3、spanid 基本的工作单元,获取元数据,如发送一个http;
- 第四个值:false,是否要将该信息输出到zipkin服务中来收集和展示。
8.2.2-Sleuth实战
1. 添加依赖
<dependency> |
2. 修改配置文件
修改application.yml添加日志级别
logging: |
3. 查看日志
网关服务日志
订单服务日志
- 商品服务日志
8.3-ZipKin
8.3.1-什么是ZipKin?
Zipkin是一款开源的分布式实时数据追踪系统(Distributed Tracking System),基于 Google Dapper的论文设计而来,由 Twitter 公司开发贡献。其主要功能是聚集来自各个异构系统的实时监控数据。分布式跟踪系统还有其他比较成熟的实现,例如:Naver的Pinpoint、Apache的HTrace、阿里的鹰眼Tracing、京东的Hydra、新浪的Watchman,美团点评的CAT,skywalking等。
Zipkin 是一个开放源代码分布式的跟踪系统,每个服务向zipkin报告计时数据,zipkin会根据调用关系通过Zipkin UI生成依赖关系图。
Zipkin提供了可插拔数据存储方式:In-Memory、MySql、Cassandra以及Elasticsearch。为了方便在开发环境我直接采用了In-Memory方式进行存储,生产数据量大的情况则推荐使用Elasticsearch。
8.3.2-为什么使用ZipKin?
随着业务越来越复杂,系统也随之进行各种拆分,特别是随着微服务架构和容器技术的兴起,看似简单的一个应用,后台可能有几十个甚至几百个服务在支撑;一个前端的请求可能需要多次的服务调用最后才能完成;当请求变慢或者不可用时,我们无法得知是哪个后台服务引起的,这时就需要解决如何快速定位服务故障点,Zipkin分布式跟踪系统就能很好的解决这样的问题。
8.3.3-ZipKin架构?
如图所示,Zipkin 主要由四部分构成:收集器、数据存储、查询以及 Web 界面。Zipkin 的收集器负责将各系统报告过来的追踪数据进行接收;而数据存储默认使用 Cassandra,也可以替换为 MySQL;查询服务用来向其他服务提供数据查询的能力,而 Web 服务是官方默认提供的一个图形用户界面。
而各个异构的系统服务向 Zipkin 报告数据的架构如下图。
8.3.4-ZipKin的客户端Brave
Brave 是用来装备 Java 程序的类库,提供了面向 Standard Servlet、Spring MVC、Http Client、JAX RS、Jersey、Resteasy 和 MySQL 等接口的装备能力,可以通过编写简单的配置和代码,让基于这些框架构建的应用可以向 Zipkin 报告数据。同时 Brave 也提供了非常简单且标准化的接口,在以上封装无法满足要求的时候可以方便扩展与定制。
如下图是Brave的结构图。Brave利用reporter向zipkin的Collector发送trace信息。
8.3.5-启动ZipKin
Docker
docker run -d -p 9411:9411 openzipkin/zipkin
Java
curl -sSL https://zipkin.io/quickstart.sh | bash -s
java -jar zipkin.jar源代码运行
# get the latest source
git clone https://github.com/openzipkin/zipkin
cd zipkin
# Build the server and also make its dependencies
./mvnw -DskipTests --also-make -pl zipkin-server clean install
# Run the server
java -jar ./zipkin-server/target/zipkin-server-*exec.jar
8.4-Sleuth+ZipKin实战
sleuth收集跟踪信息通过http请求发送给zipkin server,zipkinserver进行跟踪信息的存储以及提供Rest API即可,Zipkin UI调用其API接口进行数据展示默认存储是内存,可也用mysql、或者elasticsearch等存储
8.4.1-添加依赖
<!--包含Sleuth和ZipKin的依赖--> |
8.4.2-添加配置
spring: |
这里的base-url
是zipkin服务端的地址,percentage
是采样比例,设置为1.0时代表全部强求都需要采样。Sleuth默认采样算法的实现是Reservoir sampling,具体的实现类是PercentageBasedSampler
,默认的采样比例为: 0.1(即10%)。
8.4.3-创建服务
创建order-service和product-service两个服务,并在order-service中通过Feign对product-service进行远程调用。
1. product-service
在product-service的controller中提供一个商品服务:
|
2. order-service
由于需要在 order-service 中调用product-service,先创建一个ProductClient:
|
我们直接在order-service的controller中通过ProductClient对product-service进行调用:
|
8.4.4-启动并测试
按照以下顺序启动应用进行测试:
==>启动Eureka注册中心,端口号 8761 |
启动完成之后,Eureka注册中心中注册的服务列表如下:
在浏览器中输入以下地址: http://localhost:8781/api/v1/order/service,后台打印如下日志:
2019-02-27 13:49:17.439 INFO [order-service,895caa4daa30bb0a,895caa4daa30bb0a,true] 2812 --- [nio-8781-exec-2] c.pengjunlee.controller.OrderController : Order Service Is Called... |
在product-service后台打印如下日志:
2019-02-27 13:49:17.448 INFO [product-service,895caa4daa30bb0a,9cd122253ea82104,true] 20736 --- [nio-8771-exec-8] c.p.controller.ProductController : Product Service Is Called... |
正如上面 product-service 和 order-service 中打印的日志所示,Sleuth将Trace Id和Span Id添加到Slf4J MDC(Mapped Diagnostic Context)并在日志中进行了打印,这样,你就能够从日志聚合器中提取任何一个给定的Trace 或者Span 的所有日志了。
接下来,重点解释一下日志中的 [appname,traceId,spanId,exportable] 各部分所代表的含义:
appname:记录日志的应用的名称,即spring.application.name的值;
traceId:Sleuth为一次请求链路生成的唯一ID,一个Trace中可以包含多个Span;
spanId:请求链路基本的工作单元,代表发生一次特定的操作,例如:发送一个Http请求;
exportable:是否需要将日志导出到 Zipkin;
Sleuth提供了对常见分布式链路追踪数据模型的抽象:Trace、Span、Annotation和键值对Annotation。Spring-Cloud-Sleuth虽然基于htrace,但与Zipkin(dapper)也兼容。
Sleuth记录时间信息以帮助进行延迟分析。通过使用sleuth,您可以查明应用程序中延迟的原因。
当spring-cloud-sleuth-zipkin包含在classpath中时,应用程序将生成并收集与zipkin兼容的追踪记录。默认情况下,会通过HTTP将它们发送到本地主机(端口9411)上的Zipkin服务器。您可以通过设置spring.zipkin.baseurl来配置服务的地址。
如果你依赖的是spring-rabbit,那么应用程序会将追踪记录发送到Rabbit MQ代理,而不是HTTP。
如果你依赖的是spring-kafka,并设置了spring.zipkin.sender.type:kafka,那么应用程序会将追踪记录发送到Kafka代理而不是HTTP。
注意:如果你使用的是Zipkin,请通过设置spring.sleuth.sampler.probability来配置导出Span的概率(默认值:0.1,即10%)。否则,您可能会认为Sleuth不起作用,因为它省略了一些Span。
注意:如果你使用的是SLF4J,Trace和Span的追踪记录默认会被记录到MDC,所以日志的用户可以立刻看到。但如果你使用的是其他的日志系统,你还需要对日志的打印格式进行设置才能看到相同的结果:
logging.pattern.level = %5p [${spring.zipkin.service.name:${spring.application.name:-}},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}] |
9-分布式配置中心Config实战
9.1-微服务下的分布式配置中心
9.1.1-什么是配置中心?
顾名思义,就是用来统一管理项目中所有配置的系统,统一管理配置, 快速切换各个环境的配置。
9.1.2-为什么需要配置中心?
在传统项目中,我们是怎么处理各类配置参数问题的:
- 一般是静态化配置。大多数在项目中单独写一个配置文件,例如 “config.conf”,然后将各类 参数配置、应用配置、环境配置、安全配置、业务配置 都写到这个文件里。当项目代码逻辑中需要使用配置的时候,就从这个配置文件中读取。这种做法虽然简单,但如果参数需要修改,就非常的不灵活,甚至需要重启运行中的项目才能生效。相信大多数开发同学都深有体会。
- 配置文件无法区分环境。由于配置文件是放在项目中的,但是我们项目可能会有多个环境,例如:测试环境、预发布环境、生产环境。每一个环境所使用的配置参数理论上都是不同的,所以我们在配置文件中根据不同环境配置不同的参数,这些都是手动维护,在项目发布的时候,极其容易因开发人员的失误导致出错。
- 配置文件过于分散。如果一个项目中存在多个逻辑模块独立部署,每个模块所使用的配置内容又不相同,传统的做法是会在每一个模块中都放一个配置文件,甚至不同模块的配置文件格式还不一样。那么长期的结果就是配置文件过于分散混乱,难以管理。
- 配置修改无法追溯。因为采用的静态配置文件方式,所以当配置进行修改之后,不容易形成记录,更无法追溯是谁修改的、修改时间是什么、修改前是什么内容。既然无法追溯,那么当配置出错时,更没办法回滚配置了。
上面只是拿配置文件的形式来举例,有的项目会采用数据库配置,虽然灵活一点,但是依旧不能完全解决上述问题。既然传统的项目配置有这么多弊端,那我们看看「配置中心」的方案是如何解决这些痛点的:
配置中心的思路就是把项目中各种配置、各种参数、各种开关,全部都放到一个集中的地方进行统一管理,并提供一套标准的接口。当各个服务需要获取配置的时候,就来配置中心的接口拉取。当配置中心中的各种参数有更新的时候,也能通知到各个服务实时的过来同步最新的信息,使之动态更新。
那么,按照上述思路,我们理想中的配置中心应该具备如下特点:
- 配置集中管理、统一标准
- 配置与应用分离
- 实时更新
- 高可用
具有上述特性的配置中心是如何解决上面传统配置所面临的问题的呢?
- 采用“配置集中管理”,可以很好的解决传统的“配置文件过于分散”的问题。所有的配置都集中在配置中心这一个地方管理,不需要每一个项目都自带一个,这样极大的减轻了开发成本。
- 采用“配置与应用分离”,可以很好的解决传统的“配置文件无法区分环境”的问题,配置并不跟着环境走,当不同环境有不同需求的时候,就到配置中心获取即可,极大的减轻了运维部署成本。
- 具备“实时更新”的功能,就是用来解决传统的“静态化配置”的问题。线上系统需要调整参数的时候,只需要在配置中心动态修改即可。
- 既然配置都统一管理了,那配置中心在整个系统中的地位就非常重要了,一旦配置中心不能正常提供服务,就可能会导致项目整体故障,因此“高可用”就是配置中心又一个很关键的指标了。
9.1.3-配置中心的原理和应用
配置中心 」的原理不是很复杂。其核心功能也不多,主要是:
- 实现配置的记录
- 实现配置的读取、更新、取消
- 实现配置的查看
但是围绕着这几个核心功能,我们还需要保障高可行、要实现实时更新、要能方便的使用,还希望有权限管理的功能、操作审计的功能等等,加上这些周边辅助功能之后,一个完善的「 配置中心 也就不那么简单了。
我们再来看一下在实际项目中如何去选型和应用:
虽然配置中心的核心原理并不复杂,我们可以根据原理自己去实现一个配置中心,但是如果没有特殊需求,还是不建议重复造轮子了,毕竟业内已经有很多成熟的开源方案可以直接选用了。下面就列举几个比较热门的配置中心开源组件给大家参考:
Apollo
Apollo是由携程开源的分布式配置中心。
Apollo的特点有很多,比如:配置更新之后可以实时生效,还可以支持灰度发布功能。并且能对所有的配置进行版本管理、操作审计等功能,提供开放平台API。另外由于Apollo使用的人很多,所以网上的资料也非常的丰富,并且github上资料也写的很详细。
上面即是Apollo的基础模型,看结构很简单。但是其功能很多,之前说过配置中心对高可用的要求很高。下面可以继续看一下Apollo的架构:
更多的Apollo资料可以直接去github上查看,可以说官方文档是非常体贴的。
Spring Cloud Config
这是Spring Cloud中带的配置中心组件。也正是这个原因,所以它和Spring是无缝集成,使用起来非常方便。并且它的配置存储支持Git,不过它没有可视化的操作界面,配置的生效也不是实时的,需要重启或去刷新。所以比较适用于小型项目快速上手。

Spring Cloud Config包含了Config Client和Config Server两部分,Config Server 实现配置文件的存储,对外以接口的形式提供获取配置文件,然后Config Client通过这些接口获取数据。
Disconf
Disconf是由百度开源的分布式配置中心。其实很多一线大厂都有开源自己的配置中心组件,这里挑出百度的Disconf也是因为网上比较火热,易用性也还不错,项目也是托管在github上很容易找到。它是基于Zookeeper来实现配置变更后实时通知和生效的。
9.2-SpringCloud的配置中心组件config-server实战
创建配置中心服务端
9.2.1-新建 Spring Boot 项目,引入 config-server 和 starter-web
<dependencies> |
9.2.2-添加相关配置
server: |
9.2.3-在启动类上添加注解
|
启动服务,配置中心服务端即搭建成功。
Spring Cloud Config 有它的一套访问规则,我们通过这套规则在浏览器上直接访问就可以。
/{application}/{profile}[/{label}] |
{application} 就是应用名称,对应到配置文件上来,就是配置文件的名称部分,例如我上面创建的配置文件。
{profile} 就是配置文件的版本,我们的项目有开发版本、测试环境版本、生产环境版本,对应到配置文件上来就是以 application-{profile}.yml 加以区分,例如application-dev.yml、application-sit.yml、application-prod.yml。
{label} 表示 git 分支,默认是 master 分支,如果项目是以分支做区分也是可以的,那就可以通过不同的 label 来控制访问不同的配置文件了。
上面的 5 条规则中,我们只看前三条,因为我这里的配置文件都是 yml 格式的。根据这三条规则,我们可以通过以下地址查看配置文件内容:
http://localhost:8089/config-single-client/dev/master
http://localhost:8089/config-single-client/prod
http://localhost:8089/config-single-client-dev.yml
http://localhost:8089/config-single-client-prod.yml
http://localhost:8089/master/config-single-client-prod.yml
通过访问以上地址,如果可以正常返回数据,则说明配置中心服务端一切正常。
9.4-分布式配置中心客户端实战
创建配置中心客户端,使用配置
配置中心服务端好了,配置数据准备好了,接下来,就要在我们的项目中使用它了。
9.4.1-添加依赖
<dependency> |
或者:
<!-- Config-Client 依赖 --> |
9.4.2-添加配置
先将product-service中的application.yml文件改名为bootstrap.yml(bootstrap.yml在应用上下文启动阶段加载,比application.yml早),然后再对其内容进行修改:
# 设置服务(应用)名称 |
如果不指定配置中心,客户端默认会从http://localhost:8888 加载与服务ID相同的配置。
9.4.3-在Git仓库创建配置文件
客户端通过发送Http请求来从配置中心读取配置,这些Http请求的URI遵循以下规则:
/{name}-{profiles}.properties |
其中各个参数的含义如下:
name
服务的ID,即spring.application.name的值,本例中为 product-service;profiles
激活的profile,通过spring.cloud.config.profile指定,本例中为 dev;label
分枝的版本,通过spring.cloud.config.label指定,本例中为默认值 master;
接下来我们需要按照上述规则,在Git仓库的相应位置创建配置文件。根据bootstrap.yml中的配置,资源请求地址可以为 /master/product-service-dev.yml 。
在Git仓库的master分枝中创建product-service-dev.yml和product-service-test.yml两个文件,分别将服务的启动端口指定为8771和8772:
# product-service-dev.yml |
文件创建完成之后,启动配置中心,先在浏览器对两个文件进行访问。
此时,启动product-service将读取http://localhost:8888/product-service-dev.yml中的配置,即启动端口为8771。若将 bootstrap.yml中的spring.cloud.config.profile的值设置为test,则将读取http://localhost:8888/product-service-test.yml中的配置,应用的启动端口也会相应地变为8772,证明从配置中心读取配置成功。
10-微服务消息总线Bus
10.1-消息总线Bus介绍
Spring Cloud Bus集成了市面上常见的RabbitMQ和Kafka等消息代理。其会连接微服务系统中所有拥有Bus总线机制的节点,当有数据变更的时候,会通过消息中间件使用消息广播的方式通知所有的微服务节点同步更新数据。(如:微服务配置更新等)。
在微服务架构中,通常会使用轻量级的消息代理来构建一个共用的消息主题来连接各个微服务实例,它广播的消息会被所有在注册中心的微服务实例监听和消费,也称消息总线。
SpringCloud Bus 将分布式的节点用轻量的消息代理连接起来,可以很容易搭建消息总线,配合SpringCloud config 实现微服务应用配置信息的动态更新。
消息总线其实通过消息中间主题模式,他使用广播消息的机制被所有在注册中心微服务实例进行监听和消费。以广播形式将消息推送给所有注册中心服务列表
消息代理属于中间件。设计代理的目的就是为了能够从应用程序中传入消息,并执行一些特别的操作。开源产品很多如ActiveMQ、Kafka、RabbitMQ、RocketMQ等,目前springCloud仅支持RabbitMQ和Kafka。本文采用RabbitMQ实现这一功能。
10.2-Config+Bus实现配置信息刷新
上图为客户端的刷新。当git远程仓库有变动的时候,如果我们是去刷新客户端即微服务,那么刷新之后,服务A会获取配置信息,并且发送消息给Ribbit MQ,然后由RibbitMQ发消息通知其他的微服务,然后这些微服务再去本地仓库获取配置信息。
上图为服务端的刷新。当git仓库发生变动之后,如果是在服务端刷新,那么服务端会直接通知RibbitMQ,由RibbitMQ去通知其他所有的微服务,然后这些微服务再自行去config-server种获取配置信息,而config-server再去git远程仓库拉取最新配置。
10.2.1-采用bus实现自动刷新配置信息
配置中心服务端
1. 添加依赖
<!--配置中心结合消息队列--> |
2. 添加RabbitMQ配置
server: |
3. 启动类不变
|
配置中心客户端
1. 添加依赖
<!--是用来接收更新的消息,类似心跳检测--> |
2. 添加配置(application.yml+bootstrap.yml)
application.yml
server: |
bootstrap.yml
spring: |
3. 启动类
|
4. 读取配置测试类
需添加@RefreshScope
注解
|
11-Stream消息驱动
Spring Cloud Stream 的 binder 负责与消息中间件交互(和消息中间件解耦,不需要关注具体的消息中间件)应用通过Spring Cloud Stream插入的input(相当于消费者consumer,它是从队列中接收消息的)和output(相当于生产者producer,它是从队列中发送消息的。)通道与外界交流。当需要升级消息中间件,或者是更换其他消息中间件产品时,我们需要做的就是更换对应的Binder绑定器而不需要修改任何应用逻辑。
11.1-Stream内置接口
Stream内置了三个接口,分别是Source
,Sink
,Processor
Source是输出通道,通道名是output:
public interface Source { |
Sink是输入通道,通道名是input:
public interface Sink { |
Processor即是输入通道,也是输出通道,通道名是output,input
public interface Processor extends Source, Sink { |
下面我们使用stream提供的三个通道来做一个demo
- 生产者发送消息到中转者
- 中转者接收消息并对消息做出修改,接着中转者将消息发送给消费者
- 消费者消费消息
下面三个模块的启动类都不变,使用默认的就行。
11.2-创建消息发送者
11.2.1-添加依赖
<dependency> |
11.2.2-添加配置
server: |
11.2.3-创建user类
public class User implements Serializable { |
11.2.4-创建消息发送者
//绑定输出通道 |
11.2.5-提供一个访问接口
|
11.3-创建消息中转者
11.3.1-添加依赖
<dependency> |
11.3.2-添加配置
server: |
11.3.3-创建消息中转者
//绑定Processor通道 |
11.4-创建消息消费者
11.4.1-添加依赖
<dependency> |
11.4.2-添加配置
server: |
11.4.3-创建消息消费者
//绑定输入通道 |
11.5-测试
分别启动这三个模块,访问http://localhost:8013/sendMsg接口,向消息发送者发送消息,可以看到中转者和消费者分别打印出消息。
如果我们有多个消息消费者,而消息每次只需要被消费一次该怎么办呢?
我们可以将多个消息消费者放到同一个组中,这样他们就会互相竞争,最终只有一个消费者会接收到消息。
bindings: |
11.6-自定义消息通道
在实际生产中,我们不可能仅仅使用stream提供的三个通道,这还远远不够,因此我们要自定义消息通道,其实很简单,就是比着葫芦画瓢。
修改消息发送者,模拟一个myOutput通道
public interface MyOutput {
String MYOUTPUT = "myOutput";
//方法名是什么无所谓
MessageChannel output();
}修改消息发送者,使用我们自定义的发送通道
//绑定输出通道
public class Producer {
private MyOutput myOutput;
public void sendMsg(User user){
myOutput.output().send(MessageBuilder.withPayload(user).build());
}
}修改配置文件
spring:
cloud:
stream:
bindings:
myOutput: #绑定的通道,这里使用的是自定义的输出通道
destination: msg #发送的目的地
content-type: application/json #消息类型修改消息中转者,来模拟一个MyTransfer通道
public interface MyTransfer {
String MYOUTPUT = "myOutput";
String MYINPUT = "myInput";
MessageChannel output();
MessageChannel input();
}修改消息中转者,使用我们自定义的中转通道
//绑定我们自定义的中转通道
public class Transfer {
/**
* 监控myInput通道,接收到的消息就是obj,最后发送加工过的消息到myOutput通道
* @param obj 接收到的消息
* @return 加工后的消息
*/
public Object getMsg(Object obj){
//这里先将消息输出,再返回加工后的消息
System.out.println(obj);
return "经过中转者加工过的消息:"+obj;
}
}修改配置文件
spring:
cloud:
stream:
bindings:
myInput: #绑定的通道,这里使用的是我们自定义的输入通道
destination: msg #从该目的地接受消息,上面消息发送者是向这个目的地发送消息
content-type: application/json #消息类型
myOutput: #绑定的通道,这里使用的是我们自定义的输出通道
destination: transfer-msg #消息中转者等会向这个目的地发送消息
content-type: application/json #消息类型修改消息消费者,我们模拟一个MyInput通道
public interface MyInput {
String MYINPUT = "myInput";
MessageChannel input();
}修改消息消费者,换成我们自定义的输入通道
//绑定我们自定义的输入通道
public class Consumer {
/**
* 监控myInput通道,接收到的消息就是obj
* @param obj 接收到的消息
*/
public void getMsg(Object obj){
System.out.println(obj);
}
}修改配置文件
spring:
cloud:
stream:
bindings:
myInput: #绑定的通道,这里使用的是自定义的输入通道
destination: transfer-msg #从该目的地接受消息,上面消息中转者是向这个目的地发送消息
content-type: application/json #消息类型