【转载】Spring Cloud Alibab 和 Dubbo 有什么区别?以及各个服务的简介

SpringCloud

1. 什么是 SpringCloud

Spring Cloud是快速构建微服务项目的Java框架,为解决微服务架构的各种问题提供了一整套方案和实现。

Spring Cloud为微服务架构开发涉及的配置管理,服务治理,熔断机制,智能路由,微代理,控制总线,一次性token,全局一致性锁,leader选举,分布式session,集群状态管理等操作提供了一种简单的开发方式。

在这里插入图片描述

  • 在微服务架构中,需要几个基础的服务治理组件,包括服务注册与发现、服务消费、负载均衡、断路器、智能路由、配置管理等,由这几个基础组件相互协作,共同组建了一个简单的微服务系统。
  • 在Spring Cloud微服务系统中,一种常见的负载均衡方式是,客户端的请求首先经过负载均衡(zuul、Ngnix),再到达服务网关(zuul集群),然后再到具体的服务,服务统一注册到高可用的服务注册中心集群,服务的所有的配置文件由配置服务管理,配置服务的配置文件放在git仓库,方便开发人员随时改配置。

2. SpringBoot和SpringCloud

总结:

SpringBoot专注于快速方便的开发单个个体的微服务。

SpringCloud是关注全局的微服务协调整理治理框架,整合并管理各个微服务,为各个微服务之间提供,
配置管理,服务发现,断路器,路由,事件总线等集成服务。

SpringBoot不依赖于SpringCloud,SpringCloud依赖于SpringBoot,属于依赖关系。

SpringBoot专注于快速,方便的开发单个的微服务个体,SpringCloud关注全局的服务治理框架。

3. SpringCloud的核心组件

Eureka:服务注册与发现。
Feign:基于动态代理机制,根据注解和选择的机器,拼接请求 url 地址,发起请求。
Ribbon:实现负载均衡,从一个服务的多台机器中选择一台。
Hystrix:提供线程池,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题。
Zuul:网关管理,由 Zuul 网关转发请求给对应的服务。

4. 服务注册和发现

4.1 Zookeeper和Eureka区别
  • ZooKeeper和Consul保证的是CP,Eureka保证的是AP

  • ZooKeeper在选举期间注册服务瘫痪,虽然服务最终会恢复,但是选举期间不可用的。Eureka各个节点是平等关系,只要有一台Eureka就可以保证服务可用,而查询到的数据并不是最新的

  • Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像ZooKeeper一样使得整个注册系统瘫痪

  • ZooKeeper有Leader和Follower角色。Eureka各个节点平等

  • ZooKeeper采用过半数存活原则,Eureka采用自我保护机制解决分区问题

  • Eureka本质上是一个工程,而ZooKeeper只是一个进程

4.2 Eureka自我保护机制是什么
  • 当Eureka Server 节点在短时间内丢失了过多实例的连接时(比如网络故障或频繁启动关闭客户端)节点会进入自我保护模式,保护注册信息,不再删除注册数据,故障恢复时,自动退出自我保护模式。

  • 自我保护机制会导致Eureka不再从注册列表移除因长时间没收到心跳而应该过期的服务,Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点(高可用)。当网络稳定时,当前实例新的注册信息会被同步到其他节点中(最终一致性)

5. 服务调用

OpenFeign 是 Spring Cloud 家族的一个成员, 它最核心的作用是为 HTTP 形式的 Rest API 提供了非常简洁高效的 RPC 调用方式。 如果说 Spring Cloud 其他成员解决的是系统级别的可用性,扩展性问题, 那么 OpenFeign 解决的则是与开发人员利益最为紧密的开发效率问题。

5.1 Feign和OpenFeign
  1. Feign

    • Feign是Spring Cloud组件中的一个轻量级RESTful的HTTP服务客户端
    • Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。整合了Hystrix,具有熔断的能力
    • Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务
    • Feign本身不支持Spring MVC的注解,它有一套自己的注解
  2. OpenFeign

  • OpenFeign则是可以让我们像调用Dubbo接口一样,实现面向接口编程
    • OpenFeign是Spring Cloud 在Feign的基础上支持了Spring MVC的注解,如@RequesMapping等等。
  • OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,
    并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
5.2 负载均衡

在计算中,负载均衡可以改善跨计算机,计算机集群,网络链接,中央处理单元或磁盘驱动器等多种计算资源的工作负载分布。负载均衡旨在优化资源使用,最大吞吐量,最小响应时间并避免任何单一资源的过载。使用多个组件进行负载均衡而不是单个组件可能会通过冗余来提高可靠性和可用性。负载平衡通常涉及专用软件或硬件,例如多层交换机或域名系统服务进程。

Ribbon

  • Ribbon 是一个基于 Http 和 TCP 的客户端负载均衡工具,它是基于Netflix Ribbon实现的。它不像服务注册中心、配置中心、API 网关那样独立部署,但是它几乎存在于每个微服务的基础设施中。理解 Ribbon 对于我们使用 Spring Cloud 来讲非常的重要,因为负载均衡是对系统的高可用、网络压力的缓解和处理能力扩容的重要手段之一。

  • Ribbon 可以基于某种负载均衡算法,如轮询(默认)、随机、加权轮询、加权随机等自动帮助服务消费者调用接口, 甚至包含自定义的负载均衡算法。

  • 在 Spring Cloud 中,有两种服务调用方式,一种是 Ribbon+RestTemplate,另一种是 Feign。

5.3 Ribbon和Feign的区别

Ribbon是一个负载均衡客户端,可以很好的控制http和tcp的一些行为。feign默认集成了ribbon。

  1. Feign和Ribbon都是调用其他服务的,但方式不同。
  2. 启动类注解不同,Ribbon是@RibbonClient, feign的是@EnableFeignClients
  3. 服务指定的位置不同,Ribbon是在@RibbonClient注解上声明,Feign则是在定义抽象方法的接口中使用@FeignClient声明。
  4. 调用方式不同,Ribbon需要自己构建http请求,模拟http请求然后使用RestTemplate发送给其他服务,步骤相当繁琐。Feign需要将调用的方法定义成抽象方法即可。

6. 服务网关

6.1 Gateway和Zuul的比较
  1. Gateway

    • 基本介绍:Spring Cloud Gateway是Spring官方基于SSpring5.0+SpringBoot2.0+WebFlux基于⾼性能的Reactor模式响应式通信框架Netty,异步⾮阻塞模型)的网关,Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。Spring Cloud Gateway作为Spring Cloud生态系中的网关,目标是替代Netflix ZUUL。Spring Cloud GateWay不仅提供统⼀的路由⽅式(反向代理)并且基于 Filter(定义过滤器对请求过滤,完成⼀些功能) 链的⽅式提供了⽹关基本的功能,例如:鉴权、流量控制、熔断、路径重写、⽇志监控等。
    • 性能介绍:WebFlux 模块的名称是 spring-webflux,名称中的 Flux 来源于 Reactor 中的类 Flux。Spring webflux 有一个全新的非堵塞的函数式 Reactive Web 框架,可以用来构建异步的、非堵塞的、事件驱动的服务,在伸缩性方面表现非常好。使用非阻塞API。 Websockets得到支持,并且由于它与Spring紧密集成,所以将会是一个更好的 开发 体验。
    • 源码维护组织:Spring Cloud Gateway是Spring旗下Spring Cloud的一个子项目。还有一种说法是因为Zuul2连续跳票和Zuul1的性能表现不是很理想,所以催生了Spring孵化Gateway项目。
  2. Zuul

    • 基本介绍:Zuul1是基于 Servlet 2.5,兼容 3.x开发的网关,本质上也是一个web servlet应用。 Zuul提供了动态路由、监控、弹性负载和安全功能 。

    • 性能介绍:Zuul1采用的是阻塞和多线程方式,即一个线程处理一次连接请求,这种方式在内部延迟严重、设备故障较多情况下会引起存活的连接增多和线程增加的情况发生。Zuul已经发布了Zuul 2.x,基于Netty,也是非阻塞的,支持长连接,但Spring Cloud暂时还没有整合计划

    • 源码维护组织:Zuul则是Netflix公司的项目,只是Spring将Zuul集成在SpringCloud中使用而已。关键目前Spring不打算集成Zuul2.x。

6.2 Gateway路由配置
  • 路由(route): ⽹关最基础的部分,也是⽹关⽐较基础的⼯作单元。路由由⼀个ID、⼀个⽬标URL(最终路由到的地址)、⼀系列的断⾔(匹配条件判断)和Filter过滤器(精细化控制)组成。如果断⾔为true,则匹配该路由。
  • 断⾔(predicates):参考了Java8中的断⾔java.util.function.Predicate,开发⼈员可以匹配Http请求中的所有内容(包括请求头、请求参数等)(类似于nginx中的location匹配⼀样),如果断⾔与请求相匹配则路由。
  • 过滤器(filter):⼀个标准的Spring webFilter,使⽤过滤器,可以在请求之前或者之后执⾏业务逻辑。

7. 服务熔断、降级

7.1 问题和解决
  • 在复杂的分布式系统中,微服务之间的相互调用,有可能出现各种各样的原因导致服务的阻塞,在高并发场景下,服务的阻塞意味着线程的阻塞,导致当前线程不可用,服务器的线程全部阻塞,导致服务器崩溃,由于服务之间的调用关系是同步的,会对整个微服务系统造成服务雪崩。
  • 为了解决某个微服务的调用响应时间过长或者不可用进而占用越来越多的系统资源引起雪崩效应就需要进行服务熔断和服务降级处理。
7.2 熔断和降级
  1. 熔断

    • 熔断的目的是当服务模块中的某块程序出现故障后为了不影响其他客户端的请求而做出的及时回应
  2. 降级

    • 降级的目的是为了解决整体项目的压力,而牺牲掉某一服务模块而采取的措施。

相同之处:

  • 目的很一致,都是从可用性可靠性着想,为防止系统的整体缓慢甚至崩溃,采用的技术手段;
  • 最终表现类似,对于两者来说,最终让用户体验到的是某些功能暂时不可达或不可用;
  • 粒度一般都是服务级别,当然,业界也有不少更细粒度的做法,比如做到数据持久层(允许查询,不允许增删改);
  • 自治性要求很高,熔断模式一般都是服务基于策略的自动触发,降级虽说可人工干预,但在微服务架构下,完全靠人显然不可能,开关预置、配置中心都是必要手段;

区别之处:

  • 触发原因不太一样,服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;
  • 管理目标的层次不太一样,熔断其实是一个框架级的处理,每个微服务都需要(无层级之分),而降级一般需要对业务有层级之分(比如降级一般是从最外围服务开始)
7.3 Hystrix介绍
  • Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时,异常等,Hystrix能够保证在一个依赖出现问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。

  • 防雪崩利器,具备服务降级,服务熔断,服务限流,依赖隔离,实时监控(Hystrix Dashboard)

断路器作用

“断路器”本身是一种开关设置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方法返回一个服务预期的,可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方法无法处理的异常,这样就可以保证了服务调用方的线程不会被长时间,不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。

  • 断路器完全打开状态:一段时间内 达到一定的次数无法调用 并且多次监测没有恢复的迹象 断路器完全打开 那么下次请求就不会请求到该服务
  • 半开:短时间内 有恢复迹象 断路器会将部分请求发给该服务,正常调用时 断路器关闭
  • 关闭:当服务一直处于正常状态, 能正常调用

8. 配置管理工具

  • 在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。

  • 在Spring Cloud中,有分布式配置中心组件spring cloud config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。

  • 在spring cloud config 组件中,分两个角色,一是config server,二是config client。

9. 事件、消息总线

Spring Cloud Bus整合 Java的事件处理机制和消息中间件消息的发送和接受,主要由发送端、接收端和事件组成。针对不同的业务需求,可以设置不同的事件,发送端发送事件,接收端接受相应的事件,并进行相应的处理。

10. SpringCloud Alibaba

10.1 Nacos
10.1.1 Nacos介绍

Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。

Nacos 的关键特性包括:

  • 服务发现和服务健康监测
  • 动态配置服务
  • 动态 DNS 服务
  • 服务及其元数据管理
10.1.2 Nacos原理

Nacos注册中心分为server与client,server采用Java编写,为client提供注册发现服务与配置服务。而client可以用多语言实现,client与微服务嵌套在一起,nacos提供sdk和openApi,如果没有sdk也可以根据openApi手动写服务注册与发现和配置拉取的逻辑

服务注册: Nacos Client会通过发送REST请求的方式向Nacos Server注册自己的服务,提供自身的元数据,比如ip地址、端口等信息。Nacos Server接收到注册请求后,就会把这些元数据信息存储在一个双层的内存Map中。

服务心跳:在服务注册后,Nacos Client会维护一个定时心跳来持续通知Nacos Server,说明服务一直处于可用状态,防止被剔除。默认5s发送一次心跳。

服务同步:Nacos Server集群之间会互相同步服务实例,用来保证服务信息的一致性。

服务发现:服务消费者(Nacos Client)在调用服务提供者的服务时,会发送一个REST请求给NacosServer,获取上面注册的服务清单,并且缓存在Nacos Client本地,同时会在Nacos Client本地开启一个定时任务定时拉取服务端最新的注册表信息更新到本地缓存

服务健康检查: Nacos Server会开启一个定时任务用来检查注册服务实例的健康情况,对于超过15s没有收到客户端心跳的实例会将它的healthy属性置为false(客户端服务发现时不会发现),如果某个实例超过30秒没有收到心跳,直接剔除该实例(被剔除的实例如果恢复发送心跳则会重新注册)

10.1.3 Nacos优缺点

优点:

  1. 开箱即用,适用于dubbo,spring cloud
  2. AP模型,数据最终一致性
  3. 注册中心,配置中心二合一,提供控制台管理
  4. 纯国产,久经双十一考验

缺点:

  1. 刚刚开源不久,社区热度不够,依然存在bug
  2. 0.5.0 注册服务无鉴权认证机制,存在风险
10.2 Sentinel
10.2.1 Sentinel介绍

Sentinel是面向分布式服务框架的轻量级流量控制框架,主要以流量为切入点,从流量控制,熔断降级,系统负载保护等多个维度来维护系统的稳定性

Sentinel基本概念

  1. 资源:

    资源是Sentinel的关键概念,它可以是Java应用程序中的任何内容,例如由应用程序提供的服务,或由应用程序调用其他应用提供的服务,甚至可以是一段代码.

  2. 规则:

    围绕资源的实时状态设定的规则,可以包括流量控制规则,熔断降级规则,以及系统保护规则,所有规则都可以动态实时调整.

流量控制在网络传输中是一个常用的概念,它用于调整网络包的发送数据.

10.2.2 流量控制设计理念

流量控制有内下几个角度:

  1. 资源的调用关系,例如资源的调用链路,资源和资源之间的关系
  2. 运行指标,例如QPS,线程池,系统负载等
  3. 控制的效果,例如直接限流,冷启动,排队
10.2.3 熔断降级

Sentinel和Hystrix的原则一致,当调用链路中某个资源出现不稳定,例如,timeout,异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,避免影响到其他资源.

熔断降级设计理念

在限制的手段上,Sentinel和Hystrix采取了完全不一样的方法.

Hystrix通过线程池的方式,来对依赖(对应Sentinel中的资源)进行隔离.这样做的好处是资源和资源之间做到了最彻底的隔离.缺点是除了增加了线程切换的成本,还需要预先给各个资源做线程池大小的分配.

10.2.4 两种方式限流方式
  1. 通过并发线程数进行限制
    Sentinel通过限制资源并发线程的数量,来减少不稳定资源对其他资源的影响.这样不但没有线程切换的损耗,也不需要预先分配线程池大小.当某个资源出现不稳定的情况下,例如相应时间变长,对资源的直接影响就是会造成线程数的逐步堆积.当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝.堆积的线程完成任务后才开始继续接受请求.
  2. 通过响应时间对资源进行降级
    除了对并发线程数进行控制以外,Sentinel还可以通过响应时间来快速降级不稳定的资源.当依赖的资源出现响应时间过长后,所有对资源的访问都会被直接拒绝,直接过了指定的时间之后才重新恢复.

系统负载保护
Sentinel同时对系统的维度提供保护,防止雪崩.当系统负载较高的时候,如果还持续让请求进入,可能会导致系统崩溃,无法响应.在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其他机器上去.如果这个时候其它机器也处于一个边缘状态,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用.

Dubbo

1. Dubbo介绍

Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的RPC实现服务的输出和输入功能,可以和Spring框架无缝集成。简单地说,dubbo是一个基于Spring的RPC(远程过程调用)框架,能够实现服务的远程调用、服务的治理。最主要就是服务之间的治理

dubbo的核心服务

  1. 远程通讯: 提供对多种基于长连接的NIO框架抽象封装,包括多种线程模型,序列化,以及“请求-响应”模式的信息交换方式。

  2. 集群容错: 提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持。

  3. 自动发现: 基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。

2. Dubbo架构

节点角色说明

节点角色说明
Provider暴露服务的服务提供方
Consumer调用远程服务的服务消费方
Registry服务注册与发现的注册中心
Monitor统计服务的调用次数和调用时间的监控中心
Container服务运行容器

调用关系说明:

  1. 服务容器负责启动,加载,运行服务提供者。

  2. 服务提供者在启动时,向注册中心注册自己提供的服务。

  3. 服务消费者在启动时,向注册中心订阅自己所需的服务。

  4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。

  5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败, 再选另一台调用。

  6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

总结:

生产者启动时把接口注册到注册中心,消费者启动时从注册中心获取生产者的接口列表放到本地,当有请求过来时,从消费者的本地获取生产者的接口列表选择一个生产者使用底层的调用协议(http、rmi、netty(dubbo))去调用,dubbo同时还能对服务进行监控

3. Dubbo简单案例

案例取自官网,详细内容见官网:https://dubbo.apache.org/zh/docs/v2.7/user/quick-start/

3.1 服务提供者

定义服务接口

DemoService.java 1

package org.apache.dubbo.demo;

public interface DemoService {
String sayHello(String name);
}

  • 1
  • 2
  • 3
  • 4
  • 5

在服务提供方实现接口

DemoServiceImpl.java 2

package org.apache.dubbo.demo.provider;

import org.apache.dubbo.demo.DemoService;

public class DemoServiceImpl implements DemoService {
public String sayHello(String name) {
return "Hello " + name;
}
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

用 Spring 配置声明暴露服务

provider.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
    xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans-4.3.xsd        http://dubbo.apache.org/schema/dubbo        http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<span class="token comment">&lt;!-- 提供方应用信息,用于计算依赖关系 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">dubbo:</span>application</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>hello-world-app<span class="token punctuation">"</span></span>  <span class="token punctuation">/&gt;</span></span>

<span class="token comment">&lt;!-- 使用multicast广播注册中心暴露服务地址 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">dubbo:</span>registry</span> <span class="token attr-name">address</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>multicast://224.5.6.7:1234<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>

<span class="token comment">&lt;!-- 用dubbo协议在20880端口暴露服务 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">dubbo:</span>protocol</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>dubbo<span class="token punctuation">"</span></span> <span class="token attr-name">port</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>20880<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>

<span class="token comment">&lt;!-- 声明需要暴露的服务接口 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">dubbo:</span>service</span> <span class="token attr-name">interface</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>org.apache.dubbo.demo.DemoService<span class="token punctuation">"</span></span> <span class="token attr-name">ref</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>demoService<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>

<span class="token comment">&lt;!-- 和本地bean一样实现服务 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>bean</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>demoService<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>org.apache.dubbo.demo.provider.DemoServiceImpl<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>

</beans>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

加载 Spring 配置

Provider.java:

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Provider {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{ “META-INF/spring/dubbo-demo-provider.xml”});
context.start();
System.in.read(); // 按任意键退出
}
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
3.2 服务消费者

通过 Spring 配置引用远程服务

consumer.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
    xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans-4.3.xsd        http://dubbo.apache.org/schema/dubbo        http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<span class="token comment">&lt;!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">dubbo:</span>application</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>consumer-of-helloworld-app<span class="token punctuation">"</span></span>  <span class="token punctuation">/&gt;</span></span>

<span class="token comment">&lt;!-- 使用multicast广播注册中心暴露发现服务地址 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">dubbo:</span>registry</span> <span class="token attr-name">address</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>multicast://224.5.6.7:1234<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>

<span class="token comment">&lt;!-- 生成远程服务代理,可以和本地bean一样使用demoService --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">dubbo:</span>reference</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>demoService<span class="token punctuation">"</span></span> <span class="token attr-name">interface</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>org.apache.dubbo.demo.DemoService<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>

</beans>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

加载Spring配置,并调用远程服务

Consumer.java 3

import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.apache.dubbo.demo.DemoService;

public class Consumer {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] { “META-INF/spring/dubbo-demo-consumer.xml”});
context.start();
DemoService demoService = (DemoService)context.getBean(“demoService”); // 获取远程服务代理
String hello = demoService.sayHello(“world”); // 执行远程方法
System.out.println( hello ); // 显示调用结果
}
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

4. Dubbo配置

4.1 启动时检查

在启动时检查依赖的服务是否可用

Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check="true"

可以通过 check="false" 关闭检查,比如,测试时,有些服务不关心,或者出现了循环依赖,必须有一方先启动。

另外,如果你的 Spring 容器是懒加载的,或者通过 API 编程延迟引用服务,请关闭 check,否则服务临时不可用时,会抛出异常,拿到 null 引用,如果 check="false",总是会返回引用,当服务恢复时,能自动连上。

通过 spring 配置文件:
//关闭某个服务的启动时检查 (没有提供者时报错):
<dubbo:reference interface="com.foo.BarService" check="false" />

//关闭所有服务的启动时检查 (没有提供者时报错):
<dubbo:consumer check=“false” />

//关闭注册中心启动时检查 (注册订阅失败时报错):
<dubbo:registry check=“false” />

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
4.2 配置覆盖关系
  • 方法级优先,接口级次之,全局配置再次之。
  • 如果级别一样,则消费方优先,提供方次之。
4.3 重试次数

Dubbo 服务在尝试调用一次之后,如出现非业务异常(服务突然不可用、超时等),Dubbo 默认会进行额外的最多2次重试

//1.通过注解/xml进行固定配置
<dubbo:consumer retries="2"></dubbo:consumer>

//2.通过RpcContext进行运行时动态配置,优先级高于注解/xml进行的固定配置(两者都配置的情况下,以RpcContext配置为准). // dubbo服务调用前,通过RpcContext动态设置本次调用的重试次数

RpcContext rpcContext = RpcContext.getContext();
rpcContext.setAttachment(“retries”, 5);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
4.4 多版本

当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。

可以按照以下的步骤进行版本迁移:

  1. 在低压力时间段,先升级一半提供者为新版本
  2. 再将所有消费者升级为新版本
  3. 然后将剩下的一半提供者升级为新版本
//老版本服务提供者配置:

<dubbo:service interface=“com.foo.BarService” version=“1.0.0” />

//新版本服务提供者配置:

<dubbo:service interface=“com.foo.BarService” version=“2.0.0” />

//老版本服务消费者配置:

<dubbo:reference id=“barService” interface=“com.foo.BarService” version=“1.0.0” />

//新版本服务消费者配置:

<dubbo:reference id=“barService” interface=“com.foo.BarService” version=“2.0.0” />

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

5.Dubbo高可用

5.1 负载均衡

负载均衡策略

  1. Random LoadBalance

    • 随机,按权重设置随机概率。

    • 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。

  2. RoundRobin LoadBalance

    • 轮询,按公约后的权重设置轮询比率。
    • 存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
  3. LeastActive LoadBalance

    • 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
    • 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
  4. ConsistentHash LoadBalance

    • 一致性 Hash,相同参数的请求总是发到同一提供者。
    • 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
    • 算法参见:http://en.wikipedia.org/wiki/Consistent_hashing
    • 缺省只对第一个参数 Hash,如果要修改,请配置 <dubbo:parameter key="hash.arguments" value="0,1" />
    • 缺省用 160 份虚拟节点,如果要修改,请配置 <dubbo:parameter key="hash.nodes" value="320" />
配置:
//服务端服务级别
<dubbo:service interface="..." loadbalance="roundrobin" />

//客户端服务级别
<dubbo:reference interface=“…” loadbalance=“roundrobin” />

//服务端方法级别
<dubbo:service interface=“…”>
<dubbo:method name=“…” loadbalance=“roundrobin”/>
</dubbo:service>

//客户端方法级别
<dubbo:reference interface=“…”>
<dubbo:method name=“…” loadbalance=“roundrobin”/>
</dubbo:reference>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
5.2 服务降级

当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心交易正常运作或高效运作。

可以在控制台直接控制:

  • mock=force:return+null表示消费方对该服务的方法调用都直接返回nul值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。

  • 还可以改为mock=fail:return+null表示消费方对该服务的方法调用在失败后,再返回null值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。。

5.3 集群容错

集群容错模式:

  • Failfast Cluster

    快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。

  • Failsafe Cluster

    失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。

  • Failback Cluster

    失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。

  • Forking Cluster

    并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数。

  • Broadcast Cluster

    广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。

集群模式配置

按照以下示例在服务提供方和消费方配置集群模式

<dubbo:service cluster="failsafe" />

 
 
  • 1

<dubbo:reference cluster="failsafe" />

 
 
  • 1

整合Hystrix

和SpringCloud类似

6. Dubbo框架设计

图例说明:

  • 图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口。
  • 图中从下至上分为十层,各层均为单向依赖,右边的黑色箭头代表层之间的依赖关系,每一层都可以剥离上层被复用,其中,Service 和 Config 层为 API,其它各层均为 SPI。
  • 图中绿色小块的为扩展接口,蓝色小块为实现类,图中只显示用于关联各层的实现类。
  • 图中蓝色虚线为初始化过程,即启动时组装链,红色实线为方法调用过程,即运行时调时链,紫色三角箭头为继承,可以把子类看作父类的同一个节点,线上的文字为调用的方法。

详细内容参考官网:

https://dubbo.apache.org/zh/docs/v2.7/dev/design/

7. Dubbo原理

7.1 基本原理
  1. client一个线程调用远程接口,生成一个唯一的ID(比如一段随机字符串,UUID等),Dubbo是使用AtomicLong从0开始累计数字的
  2. 将打包的方法调用信息(如调用的接口名称,方法名称,参数值列表等),和处理结果的回调对象callback,全部封装在一起,组成一个对象object
  3. 向专门存放调用信息的全局ConcurrentHashMap里面put(ID, object)
  4. 将ID和打包的方法调用信息封装成一对象connRequest,使用IoSession.write(connRequest)异步发送出去
  5. 当前线程再使用callback的get()方法试图获取远程返回的结果,在get()内部,则使用synchronized获取回调对象callback的锁, 再先检测是否已经获取到结果,如果没有,然后调用callback的wait()方法,释放callback上的锁,让当前线程处于等待状态。
  6. 服务端接收到请求并处理后,将结果(此结果中包含了前面的ID,即回传)发送给客户端,客户端socket连接上专门监听消息的线程收到消息,分析结果,取到ID,再从前面的ConcurrentHashMap里面get(ID),从而找到callback,将方法调用结果设置到callback对象里。
  7. 监听线程接着使用synchronized获取回调对象callback的锁(因为前面调用过wait(),那个线程已释放callback的锁了),再notifyAll(),唤醒前面处于等待状态的线程继续执行(callback的get()方法继续执行就能拿到调用结果了),至此,整个过程结束。

当前线程怎么让它“暂停”,等结果回来后,再向后执行?

先生成一个对象obj,在一个全局map里put(ID,obj)存放起来,再用synchronized获取obj锁,再调用obj.wait()让当前线程处于等待状态,然后另一消息监听线程等到服 务端结果来了后,再map.get(ID)找到obj,再用synchronized获取obj锁,再调用obj.notifyAll()唤醒前面处于等待状态的线程。

正如前面所说,Socket通信是一个全双工的方式,如果有多个线程同时进行远程方法调用,这时建立在client server之间的socket连接上会有很多双方发送的消息传递,前后顺序也可能是乱七八糟的,server处理完结果后,将结果消息发送给client,client收到很多消息,怎么知道哪个消息结果是原先哪个线程调用的?

使用一个ID,让其唯一,然后传递给服务端,再服务端又回传回来,这样就知道结果是原先哪个线程的了。

img

下面我们用一个精简的图来说明最重要的两种Invoker:服务提供Invoker和服务消费Invoker:

img

7.2 服务暴露原理

处理过程:
IOC容器启动时会将@Service注解(或者xml中的dubbo:service标签)解析成ServiceBean,在ServiceBean对象创建完成的时候回调用afterPropertiesSet方法还会在IOC容器启动完成后调用onApplicationEvent,那么他们都做了什么呢?

  1. afterPropertiesSet 调用:setProvider/setModules/setProtocol,将配置的信息都保存起来

  2. onApplicationEvent方法 延迟/要暴露/还未暴露 则暴露provider服务

  3. doExport执行暴露

  4. doExportUrls执行暴露地址,加载注册中心的信息,加载注册中心的地址,若配置了多个协议的暴露方式(可以这样做),这里会遍历然后以多个协议方式暴露服务。

  5. doExportUrlForProtocol

  6. proxyFactory获取执行器,获取的invoker,通过接口和接口的实现来获取invoker。invoker就是接口、实现类、url的包装对象。继续对invoker进行wrap得到wrapperInvoker

  7. 暴露wrapperInvoker,先来到register注册中心进行暴露,再在dubbo来openServer,然后使用Netty作为底层来监听对应端口,然后将要注册服务的url地址和对应的执行器保存在注册表中

    //保存在一个HashMap中  注册地址和执行器
    public class ProviderConsumerRegTable {
        public static ConcurrentHashMap<String, Set<ProviderInvokerWrapper>> providerInvokers = new ConcurrentHashMap();
        public static ConcurrentHashMap<String, Set<ConsumerInvokerWrapper>> consumerInvokers = new ConcurrentHashMap();
    
       
       
    • 1
    • 2
    • 3
    • 4
  8. 对于(7)的好处,provider端口启动后远程调用某个url,通过该url查找到对应的Invoker进行调用

总结:

整个服务暴露的过程就是服务向注册中心注册的过程,除了基本的实现以外,Dubbo在该过程中还提供了ListenerFilter这两个扩展点方便开发者进行定制化的实现。

可参考博客:https://blog.csdn.net/nangeali/article/details/82633125

7.3 服务引用原理

引用流程:

1.ReferenceBean是远程引用服务的bean,是一个工厂bean,获得该bean需要调用工厂的getObject()方法,然后我们来get()和init()(初始化);

2.主要是ProxyFactory来创建对象,创建对象就是Protocol来引用远程服务(使用DubboProtocol和RegistryProtocol);

3.DubboProtocol负责和远程的服务器(eg:20880)来建起连接,创建一个客户端;

4.RegistryProtocol负责从注册中心订阅服务,并且把创建的invoker(客户端信息)保存到注册表中;

5.invoker的对象创建完了之后,来返回,相当于远程引用的service就是返回的代理对象;

6.核心就是:代理对象里面有和远程建立起连接的客户端,也有注册表里面每一个远程服务的url地址信息。那么如何远程调用服务:客户端和服务器建起连接,再来调用服务;

7.4 服务调用原理

  1. 调用某个接口的方法会调用之前生成的代理类,这个代理对象层层封装了各种invoker,invoker里面是我们真正要执行的功能方法

    • MockClusterInvoker类中的invoke方法又封装了FailoverClusterInvoker(集群容错)

    • AbstractClusterInvoker的invoke方法 (负载均衡)

  2. 然后会从 cluster 中经过路由的过滤、负载均衡机制选择一个 invoker 发起远程调用,此时会记录此请求和请求的 ID 等待服务端的响应。

  3. 服务端接受请求之后会通过参数找到之前暴露存储的 map,得到相应的 exporter ,然后最终调用真正的实现类,再组装好结果返回,这个响应会带上之前请求的 ID。

  4. 消费者收到这个响应之后会通过 ID 去找之前记录的请求,然后找到请求之后将响应塞到对应的 Future 中,唤醒等待的线程,最后消费者得到响应,一个流程完毕。

8. Dubbo SPI机制

8.1 Java SPI机制
  • SPI的全名为Service Provider Interface.java spi机制的思想: 系统里抽象的各个模块,往往有很多不同的实现方案,在面向的对象的设计里,一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。

  • Java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制(JDK内置的一种服务提供发现机制)。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。所以SPI的核心思想就是解耦

  • SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类

实现 SPI 需要遵循的标准:

  1. 需要在 classpath 下创建一个目录,该目录命名必须是:META-INF/service
  2. 在该目录下创建一个 properties 文件,该文件需要满足以下几个条件 :
    2.1 文件名必须是扩展的接口的全路径名称
    2.2 文件内部描述的是该扩展接口的所有实现类
    2.3 文件的编码格式是 UTF-8
  3. 通过 java.util.ServiceLoader 的加载机制来发现

SPI 的缺点:

  1. JDK 标准的 SPI 会一次性加载实例化扩展点的所有实现。如果在 META-INF/service 下的文件里面加了 N 个实现类,那么 JDK 启动的时候都会一次性全部加载。那么如果有的扩展点实现初始化很耗时或者如果有些实现类并没有用到, 那么会很浪费资源
  2. 如果扩展点加载失败,会导致调用方报错,而且这个错误很难定位到是这个原因
8.2 Dubbo SPI机制

Dubbo在原有的SPI基础上主要有以下的改变:

  1. 配置文件采用键值对配置的方式,使用起来更加灵活和简单
  2. 增强了原本SPI的功能,使得SPI具备ioc和aop的功能
  3. Dubbo不会一次性实例化扩展点所有实现,而是在有用到的时候去加载这些扩展组件。

总结

1. SpringCloud和Dubbo

SpringCloud和Dubbo都是现在主流的微服务架构

  • SpringCloud是Apache旗下Spring体系下的微服务解决方案
  • Dubbo是阿里系的分布式服务治理框架
1.1 从技术维度上
  1. 其实SpringCloud远远的超过Dubbo,Dubbo本身只是实现了服务治理,而SpringCloud现在已经有21个子项目以后还会更多。所以其实很多人都会说Dubbo和SpringCloud是不公平的对比。
  2. 但是由于RPC以及注册中心元数据等原因,在技术选型的时候我们只能二者选其一,所以我们常常为用他俩来对比。
  3. 服务的调用方式Dubbo使用的是RPC远程过程调用,而SpringCloud使用的是 Rest API,其实更符合微服务官方的定义。
  4. 服务的注册中心来看,Dubbo使用了第三方的ZooKeeper作为其底层的注册中心,实现服务的注册和发现,SpringCloud使用Spring Cloud Netflix Eureka实现注册中心,当然SpringCloud也可以使用ZooKeeper实现,但一般我们不会这样做。
  5. 服务网关,Dubbo并没有本身的实现,只能通过其他第三方技术的整合,而SpringCloud有Zuul路由网关,作为路由服务器,进行消费者的请求分发,SpringCloud还支持断路器,与git完美集成分布式配置文件支持版本控制,事务总线实现配置文件的更新与服务自动装配等等一系列的微服务架构要素。
1.2 从技术选型上讲
  • 目前国内的分布式系统选型主要还是Dubbo毕竟国产,而且国内工程师的技术熟练程度高,并且Dubbo在其他维度上的缺陷可以由其他第三方框架进行集成和弥补。
  • SpringCloud目前是国外比较流行,国内的市场也会慢慢的偏向SpringCloud,就连刘军作为Dubbo重启的负责人也发表过观点,Dubbo的发展方向是积极适应SpringCloud生态,并不是起冲突
1.3 Rest和RPC对比
  • 其实如果仔细阅读过微服务提出者马丁福勒的论文的话可以发现其定义的服务间通信机制就是Http Rest
  • RPC最主要的缺陷就是服务提供方和调用方式之间依赖太强,我们需要为每一个微服务进行接口的定义,并通过持续继承发布,需要严格的版本控制才不会出现服务提供和调用之间因为版本不同而产生的冲突。
  • 而REST是轻量级的接口,服务的提供和调用不存在代码之间的耦合,只是通过一个约定进行规范,但也有可能出现文档和接口不一致而导致的服务集成问题,但可以通过swagger工具整合,使代码和文档一体化得以解决,所以REST在分布式环境下比RPC更加灵活。这也是为什么当当网的DubboX在对Dubbo的增强中增加了对REST的支持的原因。
1.4 文档质量和社区活跃度
  • SpringCloud社区活跃度远高于Dubbo,毕竟由于梁飞团队的原因导致Dubbo停止更新迭代五年,而中小型公司无法承担技术开发的成本导致Dubbo社区严重低落,而SpringCloud异军突起,迅速占领了微服务的市场,背靠Spring混的风生水起。
  • Dubbo经过多年的积累,文档相当成熟,对于微服务的架构体系各个公司也有稳定的现状。

2. 微服务的优缺点

2.1 优点
  • 每一个服务足够内聚,代码容易理解
  • 开发效率提高,一个服务只做一件事
  • 微服务能够被小团队单独开发
  • 微服务是松耦合的,是有功能意义的服务
  • 可以用不同的语言开发,面向接口编程
  • 易于与第三方集成
  • 微服务只是业务逻辑的代码,不会和HTML,CSS或者其他界面组合
  • 可以灵活搭配,连接公共库/连接独立库
2.2 缺点
  • 分布式系统的复杂性
  • 多服务运维难度,随着服务的增加,运维的压力也在增大
  • 系统部署依赖
  • 服务间通信成本
  • 数据一致性问题
  • 系统集成测试
  • 性能监控问题

3. 你所知道的微服务技术栈

维度(SpringCloud)

  • 服务开发:SpringBoot Spring SpringMVC
  • 服务配置与管理:Netfix公司的Archaiusm ,阿里的Diamond
  • 服务注册与发现:Eureka Consul Zookeeper Nacos
  • 服务调用:Rest RPC gRPC
  • 服务熔断器:Hystrix Sentinel
  • 服务负载均衡:Ribbon Nginx
  • 服务接口调用:Fegin
  • 消息队列:Kafka RabbitMQ ActiveMQ RocketMQ
  • 服务配置中心管理:SpringCloud Config
  • 服务路由(API网关)Zuul Gateway
  • 事件消息总线:SpringCloud Bus

SpringCloud

1. 什么是 SpringCloud

Spring Cloud是快速构建微服务项目的Java框架,为解决微服务架构的各种问题提供了一整套方案和实现。

Spring Cloud为微服务架构开发涉及的配置管理,服务治理,熔断机制,智能路由,微代理,控制总线,一次性token,全局一致性锁,leader选举,分布式session,集群状态管理等操作提供了一种简单的开发方式。

在这里插入图片描述

  • 在微服务架构中,需要几个基础的服务治理组件,包括服务注册与发现、服务消费、负载均衡、断路器、智能路由、配置管理等,由这几个基础组件相互协作,共同组建了一个简单的微服务系统。
  • 在Spring Cloud微服务系统中,一种常见的负载均衡方式是,客户端的请求首先经过负载均衡(zuul、Ngnix),再到达服务网关(zuul集群),然后再到具体的服务,服务统一注册到高可用的服务注册中心集群,服务的所有的配置文件由配置服务管理,配置服务的配置文件放在git仓库,方便开发人员随时改配置。

2. SpringBoot和SpringCloud

总结:

SpringBoot专注于快速方便的开发单个个体的微服务。

SpringCloud是关注全局的微服务协调整理治理框架,整合并管理各个微服务,为各个微服务之间提供,
配置管理,服务发现,断路器,路由,事件总线等集成服务。

SpringBoot不依赖于SpringCloud,SpringCloud依赖于SpringBoot,属于依赖关系。

SpringBoot专注于快速,方便的开发单个的微服务个体,SpringCloud关注全局的服务治理框架。

3. SpringCloud的核心组件

Eureka:服务注册与发现。
Feign:基于动态代理机制,根据注解和选择的机器,拼接请求 url 地址,发起请求。
Ribbon:实现负载均衡,从一个服务的多台机器中选择一台。
Hystrix:提供线程池,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题。
Zuul:网关管理,由 Zuul 网关转发请求给对应的服务。

4. 服务注册和发现

4.1 Zookeeper和Eureka区别
  • ZooKeeper和Consul保证的是CP,Eureka保证的是AP

  • ZooKeeper在选举期间注册服务瘫痪,虽然服务最终会恢复,但是选举期间不可用的。Eureka各个节点是平等关系,只要有一台Eureka就可以保证服务可用,而查询到的数据并不是最新的

  • Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像ZooKeeper一样使得整个注册系统瘫痪

  • ZooKeeper有Leader和Follower角色。Eureka各个节点平等

  • ZooKeeper采用过半数存活原则,Eureka采用自我保护机制解决分区问题

  • Eureka本质上是一个工程,而ZooKeeper只是一个进程

4.2 Eureka自我保护机制是什么
  • 当Eureka Server 节点在短时间内丢失了过多实例的连接时(比如网络故障或频繁启动关闭客户端)节点会进入自我保护模式,保护注册信息,不再删除注册数据,故障恢复时,自动退出自我保护模式。

  • 自我保护机制会导致Eureka不再从注册列表移除因长时间没收到心跳而应该过期的服务,Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点(高可用)。当网络稳定时,当前实例新的注册信息会被同步到其他节点中(最终一致性)

5. 服务调用

OpenFeign 是 Spring Cloud 家族的一个成员, 它最核心的作用是为 HTTP 形式的 Rest API 提供了非常简洁高效的 RPC 调用方式。 如果说 Spring Cloud 其他成员解决的是系统级别的可用性,扩展性问题, 那么 OpenFeign 解决的则是与开发人员利益最为紧密的开发效率问题。

5.1 Feign和OpenFeign
  1. Feign

    • Feign是Spring Cloud组件中的一个轻量级RESTful的HTTP服务客户端
    • Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。整合了Hystrix,具有熔断的能力
    • Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务
    • Feign本身不支持Spring MVC的注解,它有一套自己的注解
  2. OpenFeign

  • OpenFeign则是可以让我们像调用Dubbo接口一样,实现面向接口编程
    • OpenFeign是Spring Cloud 在Feign的基础上支持了Spring MVC的注解,如@RequesMapping等等。
  • OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,
    并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
5.2 负载均衡

在计算中,负载均衡可以改善跨计算机,计算机集群,网络链接,中央处理单元或磁盘驱动器等多种计算资源的工作负载分布。负载均衡旨在优化资源使用,最大吞吐量,最小响应时间并避免任何单一资源的过载。使用多个组件进行负载均衡而不是单个组件可能会通过冗余来提高可靠性和可用性。负载平衡通常涉及专用软件或硬件,例如多层交换机或域名系统服务进程。

Ribbon

  • Ribbon 是一个基于 Http 和 TCP 的客户端负载均衡工具,它是基于Netflix Ribbon实现的。它不像服务注册中心、配置中心、API 网关那样独立部署,但是它几乎存在于每个微服务的基础设施中。理解 Ribbon 对于我们使用 Spring Cloud 来讲非常的重要,因为负载均衡是对系统的高可用、网络压力的缓解和处理能力扩容的重要手段之一。

  • Ribbon 可以基于某种负载均衡算法,如轮询(默认)、随机、加权轮询、加权随机等自动帮助服务消费者调用接口, 甚至包含自定义的负载均衡算法。

  • 在 Spring Cloud 中,有两种服务调用方式,一种是 Ribbon+RestTemplate,另一种是 Feign。

5.3 Ribbon和Feign的区别

Ribbon是一个负载均衡客户端,可以很好的控制http和tcp的一些行为。feign默认集成了ribbon。

  1. Feign和Ribbon都是调用其他服务的,但方式不同。
  2. 启动类注解不同,Ribbon是@RibbonClient, feign的是@EnableFeignClients
  3. 服务指定的位置不同,Ribbon是在@RibbonClient注解上声明,Feign则是在定义抽象方法的接口中使用@FeignClient声明。
  4. 调用方式不同,Ribbon需要自己构建http请求,模拟http请求然后使用RestTemplate发送给其他服务,步骤相当繁琐。Feign需要将调用的方法定义成抽象方法即可。

6. 服务网关

6.1 Gateway和Zuul的比较
  1. Gateway

    • 基本介绍:Spring Cloud Gateway是Spring官方基于SSpring5.0+SpringBoot2.0+WebFlux基于⾼性能的Reactor模式响应式通信框架Netty,异步⾮阻塞模型)的网关,Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。Spring Cloud Gateway作为Spring Cloud生态系中的网关,目标是替代Netflix ZUUL。Spring Cloud GateWay不仅提供统⼀的路由⽅式(反向代理)并且基于 Filter(定义过滤器对请求过滤,完成⼀些功能) 链的⽅式提供了⽹关基本的功能,例如:鉴权、流量控制、熔断、路径重写、⽇志监控等。
    • 性能介绍:WebFlux 模块的名称是 spring-webflux,名称中的 Flux 来源于 Reactor 中的类 Flux。Spring webflux 有一个全新的非堵塞的函数式 Reactive Web 框架,可以用来构建异步的、非堵塞的、事件驱动的服务,在伸缩性方面表现非常好。使用非阻塞API。 Websockets得到支持,并且由于它与Spring紧密集成,所以将会是一个更好的 开发 体验。
    • 源码维护组织:Spring Cloud Gateway是Spring旗下Spring Cloud的一个子项目。还有一种说法是因为Zuul2连续跳票和Zuul1的性能表现不是很理想,所以催生了Spring孵化Gateway项目。
  2. Zuul

    • 基本介绍:Zuul1是基于 Servlet 2.5,兼容 3.x开发的网关,本质上也是一个web servlet应用。 Zuul提供了动态路由、监控、弹性负载和安全功能 。

    • 性能介绍:Zuul1采用的是阻塞和多线程方式,即一个线程处理一次连接请求,这种方式在内部延迟严重、设备故障较多情况下会引起存活的连接增多和线程增加的情况发生。Zuul已经发布了Zuul 2.x,基于Netty,也是非阻塞的,支持长连接,但Spring Cloud暂时还没有整合计划

    • 源码维护组织:Zuul则是Netflix公司的项目,只是Spring将Zuul集成在SpringCloud中使用而已。关键目前Spring不打算集成Zuul2.x。

6.2 Gateway路由配置
  • 路由(route): ⽹关最基础的部分,也是⽹关⽐较基础的⼯作单元。路由由⼀个ID、⼀个⽬标URL(最终路由到的地址)、⼀系列的断⾔(匹配条件判断)和Filter过滤器(精细化控制)组成。如果断⾔为true,则匹配该路由。
  • 断⾔(predicates):参考了Java8中的断⾔java.util.function.Predicate,开发⼈员可以匹配Http请求中的所有内容(包括请求头、请求参数等)(类似于nginx中的location匹配⼀样),如果断⾔与请求相匹配则路由。
  • 过滤器(filter):⼀个标准的Spring webFilter,使⽤过滤器,可以在请求之前或者之后执⾏业务逻辑。

7. 服务熔断、降级

7.1 问题和解决
  • 在复杂的分布式系统中,微服务之间的相互调用,有可能出现各种各样的原因导致服务的阻塞,在高并发场景下,服务的阻塞意味着线程的阻塞,导致当前线程不可用,服务器的线程全部阻塞,导致服务器崩溃,由于服务之间的调用关系是同步的,会对整个微服务系统造成服务雪崩。
  • 为了解决某个微服务的调用响应时间过长或者不可用进而占用越来越多的系统资源引起雪崩效应就需要进行服务熔断和服务降级处理。
7.2 熔断和降级
  1. 熔断

    • 熔断的目的是当服务模块中的某块程序出现故障后为了不影响其他客户端的请求而做出的及时回应
  2. 降级

    • 降级的目的是为了解决整体项目的压力,而牺牲掉某一服务模块而采取的措施。

相同之处:

  • 目的很一致,都是从可用性可靠性着想,为防止系统的整体缓慢甚至崩溃,采用的技术手段;
  • 最终表现类似,对于两者来说,最终让用户体验到的是某些功能暂时不可达或不可用;
  • 粒度一般都是服务级别,当然,业界也有不少更细粒度的做法,比如做到数据持久层(允许查询,不允许增删改);
  • 自治性要求很高,熔断模式一般都是服务基于策略的自动触发,降级虽说可人工干预,但在微服务架构下,完全靠人显然不可能,开关预置、配置中心都是必要手段;

区别之处:

  • 触发原因不太一样,服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;
  • 管理目标的层次不太一样,熔断其实是一个框架级的处理,每个微服务都需要(无层级之分),而降级一般需要对业务有层级之分(比如降级一般是从最外围服务开始)
7.3 Hystrix介绍
  • Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时,异常等,Hystrix能够保证在一个依赖出现问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。

  • 防雪崩利器,具备服务降级,服务熔断,服务限流,依赖隔离,实时监控(Hystrix Dashboard)

断路器作用

“断路器”本身是一种开关设置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方法返回一个服务预期的,可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方法无法处理的异常,这样就可以保证了服务调用方的线程不会被长时间,不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。

  • 断路器完全打开状态:一段时间内 达到一定的次数无法调用 并且多次监测没有恢复的迹象 断路器完全打开 那么下次请求就不会请求到该服务
  • 半开:短时间内 有恢复迹象 断路器会将部分请求发给该服务,正常调用时 断路器关闭
  • 关闭:当服务一直处于正常状态, 能正常调用

8. 配置管理工具

  • 在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。

  • 在Spring Cloud中,有分布式配置中心组件spring cloud config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。

  • 在spring cloud config 组件中,分两个角色,一是config server,二是config client。

9. 事件、消息总线

Spring Cloud Bus整合 Java的事件处理机制和消息中间件消息的发送和接受,主要由发送端、接收端和事件组成。针对不同的业务需求,可以设置不同的事件,发送端发送事件,接收端接受相应的事件,并进行相应的处理。

10. SpringCloud Alibaba

10.1 Nacos
10.1.1 Nacos介绍

Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。

Nacos 的关键特性包括:

  • 服务发现和服务健康监测
  • 动态配置服务
  • 动态 DNS 服务
  • 服务及其元数据管理
10.1.2 Nacos原理

Nacos注册中心分为server与client,server采用Java编写,为client提供注册发现服务与配置服务。而client可以用多语言实现,client与微服务嵌套在一起,nacos提供sdk和openApi,如果没有sdk也可以根据openApi手动写服务注册与发现和配置拉取的逻辑

服务注册: Nacos Client会通过发送REST请求的方式向Nacos Server注册自己的服务,提供自身的元数据,比如ip地址、端口等信息。Nacos Server接收到注册请求后,就会把这些元数据信息存储在一个双层的内存Map中。

服务心跳:在服务注册后,Nacos Client会维护一个定时心跳来持续通知Nacos Server,说明服务一直处于可用状态,防止被剔除。默认5s发送一次心跳。

服务同步:Nacos Server集群之间会互相同步服务实例,用来保证服务信息的一致性。

服务发现:服务消费者(Nacos Client)在调用服务提供者的服务时,会发送一个REST请求给NacosServer,获取上面注册的服务清单,并且缓存在Nacos Client本地,同时会在Nacos Client本地开启一个定时任务定时拉取服务端最新的注册表信息更新到本地缓存

服务健康检查: Nacos Server会开启一个定时任务用来检查注册服务实例的健康情况,对于超过15s没有收到客户端心跳的实例会将它的healthy属性置为false(客户端服务发现时不会发现),如果某个实例超过30秒没有收到心跳,直接剔除该实例(被剔除的实例如果恢复发送心跳则会重新注册)

10.1.3 Nacos优缺点

优点:

  1. 开箱即用,适用于dubbo,spring cloud
  2. AP模型,数据最终一致性
  3. 注册中心,配置中心二合一,提供控制台管理
  4. 纯国产,久经双十一考验

缺点:

  1. 刚刚开源不久,社区热度不够,依然存在bug
  2. 0.5.0 注册服务无鉴权认证机制,存在风险
10.2 Sentinel
10.2.1 Sentinel介绍

Sentinel是面向分布式服务框架的轻量级流量控制框架,主要以流量为切入点,从流量控制,熔断降级,系统负载保护等多个维度来维护系统的稳定性

Sentinel基本概念

  1. 资源:

    资源是Sentinel的关键概念,它可以是Java应用程序中的任何内容,例如由应用程序提供的服务,或由应用程序调用其他应用提供的服务,甚至可以是一段代码.

  2. 规则:

    围绕资源的实时状态设定的规则,可以包括流量控制规则,熔断降级规则,以及系统保护规则,所有规则都可以动态实时调整.

流量控制在网络传输中是一个常用的概念,它用于调整网络包的发送数据.

10.2.2 流量控制设计理念

流量控制有内下几个角度:

  1. 资源的调用关系,例如资源的调用链路,资源和资源之间的关系
  2. 运行指标,例如QPS,线程池,系统负载等
  3. 控制的效果,例如直接限流,冷启动,排队
10.2.3 熔断降级

Sentinel和Hystrix的原则一致,当调用链路中某个资源出现不稳定,例如,timeout,异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,避免影响到其他资源.

熔断降级设计理念

在限制的手段上,Sentinel和Hystrix采取了完全不一样的方法.

Hystrix通过线程池的方式,来对依赖(对应Sentinel中的资源)进行隔离.这样做的好处是资源和资源之间做到了最彻底的隔离.缺点是除了增加了线程切换的成本,还需要预先给各个资源做线程池大小的分配.

10.2.4 两种方式限流方式
  1. 通过并发线程数进行限制
    Sentinel通过限制资源并发线程的数量,来减少不稳定资源对其他资源的影响.这样不但没有线程切换的损耗,也不需要预先分配线程池大小.当某个资源出现不稳定的情况下,例如相应时间变长,对资源的直接影响就是会造成线程数的逐步堆积.当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝.堆积的线程完成任务后才开始继续接受请求.
  2. 通过响应时间对资源进行降级
    除了对并发线程数进行控制以外,Sentinel还可以通过响应时间来快速降级不稳定的资源.当依赖的资源出现响应时间过长后,所有对资源的访问都会被直接拒绝,直接过了指定的时间之后才重新恢复.

系统负载保护
Sentinel同时对系统的维度提供保护,防止雪崩.当系统负载较高的时候,如果还持续让请求进入,可能会导致系统崩溃,无法响应.在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其他机器上去.如果这个时候其它机器也处于一个边缘状态,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用.

Dubbo

1. Dubbo介绍

Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的RPC实现服务的输出和输入功能,可以和Spring框架无缝集成。简单地说,dubbo是一个基于Spring的RPC(远程过程调用)框架,能够实现服务的远程调用、服务的治理。最主要就是服务之间的治理

dubbo的核心服务

  1. 远程通讯: 提供对多种基于长连接的NIO框架抽象封装,包括多种线程模型,序列化,以及“请求-响应”模式的信息交换方式。

  2. 集群容错: 提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持。

  3. 自动发现: 基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。

2. Dubbo架构

节点角色说明

节点角色说明
Provider暴露服务的服务提供方
Consumer调用远程服务的服务消费方
Registry服务注册与发现的注册中心
Monitor统计服务的调用次数和调用时间的监控中心
Container服务运行容器

调用关系说明:

  1. 服务容器负责启动,加载,运行服务提供者。

  2. 服务提供者在启动时,向注册中心注册自己提供的服务。

  3. 服务消费者在启动时,向注册中心订阅自己所需的服务。

  4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。

  5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败, 再选另一台调用。

  6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

总结:

生产者启动时把接口注册到注册中心,消费者启动时从注册中心获取生产者的接口列表放到本地,当有请求过来时,从消费者的本地获取生产者的接口列表选择一个生产者使用底层的调用协议(http、rmi、netty(dubbo))去调用,dubbo同时还能对服务进行监控

3. Dubbo简单案例

案例取自官网,详细内容见官网:https://dubbo.apache.org/zh/docs/v2.7/user/quick-start/

3.1 服务提供者

定义服务接口

DemoService.java 1

package org.apache.dubbo.demo;

public interface DemoService {
String sayHello(String name);
}

  • 1
  • 2
  • 3
  • 4
  • 5

在服务提供方实现接口

DemoServiceImpl.java 2

package org.apache.dubbo.demo.provider;

import org.apache.dubbo.demo.DemoService;

public class DemoServiceImpl implements DemoService {
public String sayHello(String name) {
return "Hello " + name;
}
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

用 Spring 配置声明暴露服务

provider.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
    xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans-4.3.xsd        http://dubbo.apache.org/schema/dubbo        http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<span class="token comment">&lt;!-- 提供方应用信息,用于计算依赖关系 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">dubbo:</span>application</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>hello-world-app<span class="token punctuation">"</span></span>  <span class="token punctuation">/&gt;</span></span>

<span class="token comment">&lt;!-- 使用multicast广播注册中心暴露服务地址 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">dubbo:</span>registry</span> <span class="token attr-name">address</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>multicast://224.5.6.7:1234<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>

<span class="token comment">&lt;!-- 用dubbo协议在20880端口暴露服务 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">dubbo:</span>protocol</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>dubbo<span class="token punctuation">"</span></span> <span class="token attr-name">port</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>20880<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>

<span class="token comment">&lt;!-- 声明需要暴露的服务接口 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">dubbo:</span>service</span> <span class="token attr-name">interface</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>org.apache.dubbo.demo.DemoService<span class="token punctuation">"</span></span> <span class="token attr-name">ref</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>demoService<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>

<span class="token comment">&lt;!-- 和本地bean一样实现服务 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>bean</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>demoService<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>org.apache.dubbo.demo.provider.DemoServiceImpl<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>

</beans>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

加载 Spring 配置

Provider.java:

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Provider {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{ “META-INF/spring/dubbo-demo-provider.xml”});
context.start();
System.in.read(); // 按任意键退出
}
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
3.2 服务消费者

通过 Spring 配置引用远程服务

consumer.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
    xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans-4.3.xsd        http://dubbo.apache.org/schema/dubbo        http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<span class="token comment">&lt;!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">dubbo:</span>application</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>consumer-of-helloworld-app<span class="token punctuation">"</span></span>  <span class="token punctuation">/&gt;</span></span>

<span class="token comment">&lt;!-- 使用multicast广播注册中心暴露发现服务地址 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">dubbo:</span>registry</span> <span class="token attr-name">address</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>multicast://224.5.6.7:1234<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>

<span class="token comment">&lt;!-- 生成远程服务代理,可以和本地bean一样使用demoService --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">dubbo:</span>reference</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>demoService<span class="token punctuation">"</span></span> <span class="token attr-name">interface</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>org.apache.dubbo.demo.DemoService<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>

</beans>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

加载Spring配置,并调用远程服务

Consumer.java 3

import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.apache.dubbo.demo.DemoService;

public class Consumer {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] { “META-INF/spring/dubbo-demo-consumer.xml”});
context.start();
DemoService demoService = (DemoService)context.getBean(“demoService”); // 获取远程服务代理
String hello = demoService.sayHello(“world”); // 执行远程方法
System.out.println( hello ); // 显示调用结果
}
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

4. Dubbo配置

4.1 启动时检查

在启动时检查依赖的服务是否可用

Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check="true"

可以通过 check="false" 关闭检查,比如,测试时,有些服务不关心,或者出现了循环依赖,必须有一方先启动。

另外,如果你的 Spring 容器是懒加载的,或者通过 API 编程延迟引用服务,请关闭 check,否则服务临时不可用时,会抛出异常,拿到 null 引用,如果 check="false",总是会返回引用,当服务恢复时,能自动连上。

通过 spring 配置文件:
//关闭某个服务的启动时检查 (没有提供者时报错):
<dubbo:reference interface="com.foo.BarService" check="false" />

//关闭所有服务的启动时检查 (没有提供者时报错):
<dubbo:consumer check=“false” />

//关闭注册中心启动时检查 (注册订阅失败时报错):
<dubbo:registry check=“false” />

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
4.2 配置覆盖关系
  • 方法级优先,接口级次之,全局配置再次之。
  • 如果级别一样,则消费方优先,提供方次之。
4.3 重试次数

Dubbo 服务在尝试调用一次之后,如出现非业务异常(服务突然不可用、超时等),Dubbo 默认会进行额外的最多2次重试

//1.通过注解/xml进行固定配置
<dubbo:consumer retries="2"></dubbo:consumer>

//2.通过RpcContext进行运行时动态配置,优先级高于注解/xml进行的固定配置(两者都配置的情况下,以RpcContext配置为准). // dubbo服务调用前,通过RpcContext动态设置本次调用的重试次数

RpcContext rpcContext = RpcContext.getContext();
rpcContext.setAttachment(“retries”, 5);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
4.4 多版本

当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。

可以按照以下的步骤进行版本迁移:

  1. 在低压力时间段,先升级一半提供者为新版本
  2. 再将所有消费者升级为新版本
  3. 然后将剩下的一半提供者升级为新版本
//老版本服务提供者配置:

<dubbo:service interface=“com.foo.BarService” version=“1.0.0” />

//新版本服务提供者配置:

<dubbo:service interface=“com.foo.BarService” version=“2.0.0” />

//老版本服务消费者配置:

<dubbo:reference id=“barService” interface=“com.foo.BarService” version=“1.0.0” />

//新版本服务消费者配置:

<dubbo:reference id=“barService” interface=“com.foo.BarService” version=“2.0.0” />

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

5.Dubbo高可用

5.1 负载均衡

负载均衡策略

  1. Random LoadBalance

    • 随机,按权重设置随机概率。

    • 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。

  2. RoundRobin LoadBalance

    • 轮询,按公约后的权重设置轮询比率。
    • 存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
  3. LeastActive LoadBalance

    • 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
    • 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
  4. ConsistentHash LoadBalance

    • 一致性 Hash,相同参数的请求总是发到同一提供者。
    • 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
    • 算法参见:http://en.wikipedia.org/wiki/Consistent_hashing
    • 缺省只对第一个参数 Hash,如果要修改,请配置 <dubbo:parameter key="hash.arguments" value="0,1" />
    • 缺省用 160 份虚拟节点,如果要修改,请配置 <dubbo:parameter key="hash.nodes" value="320" />
配置:
//服务端服务级别
<dubbo:service interface="..." loadbalance="roundrobin" />

//客户端服务级别
<dubbo:reference interface=“…” loadbalance=“roundrobin” />

//服务端方法级别
<dubbo:service interface=“…”>
<dubbo:method name=“…” loadbalance=“roundrobin”/>
</dubbo:service>

//客户端方法级别
<dubbo:reference interface=“…”>
<dubbo:method name=“…” loadbalance=“roundrobin”/>
</dubbo:reference>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
5.2 服务降级

当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心交易正常运作或高效运作。

可以在控制台直接控制:

  • mock=force:return+null表示消费方对该服务的方法调用都直接返回nul值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。

  • 还可以改为mock=fail:return+null表示消费方对该服务的方法调用在失败后,再返回null值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。。

5.3 集群容错

集群容错模式:

  • Failfast Cluster

    快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。

  • Failsafe Cluster

    失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。

  • Failback Cluster

    失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。

  • Forking Cluster

    并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数。

  • Broadcast Cluster

    广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。

集群模式配置

按照以下示例在服务提供方和消费方配置集群模式

<dubbo:service cluster="failsafe" />

 
 
  • 1

<dubbo:reference cluster="failsafe" />

 
 
  • 1

整合Hystrix

和SpringCloud类似

6. Dubbo框架设计

图例说明:

  • 图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口。
  • 图中从下至上分为十层,各层均为单向依赖,右边的黑色箭头代表层之间的依赖关系,每一层都可以剥离上层被复用,其中,Service 和 Config 层为 API,其它各层均为 SPI。
  • 图中绿色小块的为扩展接口,蓝色小块为实现类,图中只显示用于关联各层的实现类。
  • 图中蓝色虚线为初始化过程,即启动时组装链,红色实线为方法调用过程,即运行时调时链,紫色三角箭头为继承,可以把子类看作父类的同一个节点,线上的文字为调用的方法。

详细内容参考官网:

https://dubbo.apache.org/zh/docs/v2.7/dev/design/

7. Dubbo原理

7.1 基本原理
  1. client一个线程调用远程接口,生成一个唯一的ID(比如一段随机字符串,UUID等),Dubbo是使用AtomicLong从0开始累计数字的
  2. 将打包的方法调用信息(如调用的接口名称,方法名称,参数值列表等),和处理结果的回调对象callback,全部封装在一起,组成一个对象object
  3. 向专门存放调用信息的全局ConcurrentHashMap里面put(ID, object)
  4. 将ID和打包的方法调用信息封装成一对象connRequest,使用IoSession.write(connRequest)异步发送出去
  5. 当前线程再使用callback的get()方法试图获取远程返回的结果,在get()内部,则使用synchronized获取回调对象callback的锁, 再先检测是否已经获取到结果,如果没有,然后调用callback的wait()方法,释放callback上的锁,让当前线程处于等待状态。
  6. 服务端接收到请求并处理后,将结果(此结果中包含了前面的ID,即回传)发送给客户端,客户端socket连接上专门监听消息的线程收到消息,分析结果,取到ID,再从前面的ConcurrentHashMap里面get(ID),从而找到callback,将方法调用结果设置到callback对象里。
  7. 监听线程接着使用synchronized获取回调对象callback的锁(因为前面调用过wait(),那个线程已释放callback的锁了),再notifyAll(),唤醒前面处于等待状态的线程继续执行(callback的get()方法继续执行就能拿到调用结果了),至此,整个过程结束。

当前线程怎么让它“暂停”,等结果回来后,再向后执行?

先生成一个对象obj,在一个全局map里put(ID,obj)存放起来,再用synchronized获取obj锁,再调用obj.wait()让当前线程处于等待状态,然后另一消息监听线程等到服 务端结果来了后,再map.get(ID)找到obj,再用synchronized获取obj锁,再调用obj.notifyAll()唤醒前面处于等待状态的线程。

正如前面所说,Socket通信是一个全双工的方式,如果有多个线程同时进行远程方法调用,这时建立在client server之间的socket连接上会有很多双方发送的消息传递,前后顺序也可能是乱七八糟的,server处理完结果后,将结果消息发送给client,client收到很多消息,怎么知道哪个消息结果是原先哪个线程调用的?

使用一个ID,让其唯一,然后传递给服务端,再服务端又回传回来,这样就知道结果是原先哪个线程的了。

img

下面我们用一个精简的图来说明最重要的两种Invoker:服务提供Invoker和服务消费Invoker:

img

7.2 服务暴露原理

处理过程:
IOC容器启动时会将@Service注解(或者xml中的dubbo:service标签)解析成ServiceBean,在ServiceBean对象创建完成的时候回调用afterPropertiesSet方法还会在IOC容器启动完成后调用onApplicationEvent,那么他们都做了什么呢?

  1. afterPropertiesSet 调用:setProvider/setModules/setProtocol,将配置的信息都保存起来

  2. onApplicationEvent方法 延迟/要暴露/还未暴露 则暴露provider服务

  3. doExport执行暴露

  4. doExportUrls执行暴露地址,加载注册中心的信息,加载注册中心的地址,若配置了多个协议的暴露方式(可以这样做),这里会遍历然后以多个协议方式暴露服务。

  5. doExportUrlForProtocol

  6. proxyFactory获取执行器,获取的invoker,通过接口和接口的实现来获取invoker。invoker就是接口、实现类、url的包装对象。继续对invoker进行wrap得到wrapperInvoker

  7. 暴露wrapperInvoker,先来到register注册中心进行暴露,再在dubbo来openServer,然后使用Netty作为底层来监听对应端口,然后将要注册服务的url地址和对应的执行器保存在注册表中

    //保存在一个HashMap中  注册地址和执行器
    public class ProviderConsumerRegTable {
        public static ConcurrentHashMap<String, Set<ProviderInvokerWrapper>> providerInvokers = new ConcurrentHashMap();
        public static ConcurrentHashMap<String, Set<ConsumerInvokerWrapper>> consumerInvokers = new ConcurrentHashMap();
    
       
       
    • 1
    • 2
    • 3
    • 4
  8. 对于(7)的好处,provider端口启动后远程调用某个url,通过该url查找到对应的Invoker进行调用

总结:

整个服务暴露的过程就是服务向注册中心注册的过程,除了基本的实现以外,Dubbo在该过程中还提供了ListenerFilter这两个扩展点方便开发者进行定制化的实现。

可参考博客:https://blog.csdn.net/nangeali/article/details/82633125

7.3 服务引用原理

引用流程:

1.ReferenceBean是远程引用服务的bean,是一个工厂bean,获得该bean需要调用工厂的getObject()方法,然后我们来get()和init()(初始化);

2.主要是ProxyFactory来创建对象,创建对象就是Protocol来引用远程服务(使用DubboProtocol和RegistryProtocol);

3.DubboProtocol负责和远程的服务器(eg:20880)来建起连接,创建一个客户端;

4.RegistryProtocol负责从注册中心订阅服务,并且把创建的invoker(客户端信息)保存到注册表中;

5.invoker的对象创建完了之后,来返回,相当于远程引用的service就是返回的代理对象;

6.核心就是:代理对象里面有和远程建立起连接的客户端,也有注册表里面每一个远程服务的url地址信息。那么如何远程调用服务:客户端和服务器建起连接,再来调用服务;

7.4 服务调用原理

  1. 调用某个接口的方法会调用之前生成的代理类,这个代理对象层层封装了各种invoker,invoker里面是我们真正要执行的功能方法

    • MockClusterInvoker类中的invoke方法又封装了FailoverClusterInvoker(集群容错)

    • AbstractClusterInvoker的invoke方法 (负载均衡)

  2. 然后会从 cluster 中经过路由的过滤、负载均衡机制选择一个 invoker 发起远程调用,此时会记录此请求和请求的 ID 等待服务端的响应。

  3. 服务端接受请求之后会通过参数找到之前暴露存储的 map,得到相应的 exporter ,然后最终调用真正的实现类,再组装好结果返回,这个响应会带上之前请求的 ID。

  4. 消费者收到这个响应之后会通过 ID 去找之前记录的请求,然后找到请求之后将响应塞到对应的 Future 中,唤醒等待的线程,最后消费者得到响应,一个流程完毕。

8. Dubbo SPI机制

8.1 Java SPI机制
  • SPI的全名为Service Provider Interface.java spi机制的思想: 系统里抽象的各个模块,往往有很多不同的实现方案,在面向的对象的设计里,一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。

  • Java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制(JDK内置的一种服务提供发现机制)。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。所以SPI的核心思想就是解耦

  • SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类

实现 SPI 需要遵循的标准:

  1. 需要在 classpath 下创建一个目录,该目录命名必须是:META-INF/service
  2. 在该目录下创建一个 properties 文件,该文件需要满足以下几个条件 :
    2.1 文件名必须是扩展的接口的全路径名称
    2.2 文件内部描述的是该扩展接口的所有实现类
    2.3 文件的编码格式是 UTF-8
  3. 通过 java.util.ServiceLoader 的加载机制来发现

SPI 的缺点:

  1. JDK 标准的 SPI 会一次性加载实例化扩展点的所有实现。如果在 META-INF/service 下的文件里面加了 N 个实现类,那么 JDK 启动的时候都会一次性全部加载。那么如果有的扩展点实现初始化很耗时或者如果有些实现类并没有用到, 那么会很浪费资源
  2. 如果扩展点加载失败,会导致调用方报错,而且这个错误很难定位到是这个原因
8.2 Dubbo SPI机制

Dubbo在原有的SPI基础上主要有以下的改变:

  1. 配置文件采用键值对配置的方式,使用起来更加灵活和简单
  2. 增强了原本SPI的功能,使得SPI具备ioc和aop的功能
  3. Dubbo不会一次性实例化扩展点所有实现,而是在有用到的时候去加载这些扩展组件。

总结

1. SpringCloud和Dubbo

SpringCloud和Dubbo都是现在主流的微服务架构

  • SpringCloud是Apache旗下Spring体系下的微服务解决方案
  • Dubbo是阿里系的分布式服务治理框架
1.1 从技术维度上
  1. 其实SpringCloud远远的超过Dubbo,Dubbo本身只是实现了服务治理,而SpringCloud现在已经有21个子项目以后还会更多。所以其实很多人都会说Dubbo和SpringCloud是不公平的对比。
  2. 但是由于RPC以及注册中心元数据等原因,在技术选型的时候我们只能二者选其一,所以我们常常为用他俩来对比。
  3. 服务的调用方式Dubbo使用的是RPC远程过程调用,而SpringCloud使用的是 Rest API,其实更符合微服务官方的定义。
  4. 服务的注册中心来看,Dubbo使用了第三方的ZooKeeper作为其底层的注册中心,实现服务的注册和发现,SpringCloud使用Spring Cloud Netflix Eureka实现注册中心,当然SpringCloud也可以使用ZooKeeper实现,但一般我们不会这样做。
  5. 服务网关,Dubbo并没有本身的实现,只能通过其他第三方技术的整合,而SpringCloud有Zuul路由网关,作为路由服务器,进行消费者的请求分发,SpringCloud还支持断路器,与git完美集成分布式配置文件支持版本控制,事务总线实现配置文件的更新与服务自动装配等等一系列的微服务架构要素。
1.2 从技术选型上讲
  • 目前国内的分布式系统选型主要还是Dubbo毕竟国产,而且国内工程师的技术熟练程度高,并且Dubbo在其他维度上的缺陷可以由其他第三方框架进行集成和弥补。
  • SpringCloud目前是国外比较流行,国内的市场也会慢慢的偏向SpringCloud,就连刘军作为Dubbo重启的负责人也发表过观点,Dubbo的发展方向是积极适应SpringCloud生态,并不是起冲突
1.3 Rest和RPC对比
  • 其实如果仔细阅读过微服务提出者马丁福勒的论文的话可以发现其定义的服务间通信机制就是Http Rest
  • RPC最主要的缺陷就是服务提供方和调用方式之间依赖太强,我们需要为每一个微服务进行接口的定义,并通过持续继承发布,需要严格的版本控制才不会出现服务提供和调用之间因为版本不同而产生的冲突。
  • 而REST是轻量级的接口,服务的提供和调用不存在代码之间的耦合,只是通过一个约定进行规范,但也有可能出现文档和接口不一致而导致的服务集成问题,但可以通过swagger工具整合,使代码和文档一体化得以解决,所以REST在分布式环境下比RPC更加灵活。这也是为什么当当网的DubboX在对Dubbo的增强中增加了对REST的支持的原因。
1.4 文档质量和社区活跃度
  • SpringCloud社区活跃度远高于Dubbo,毕竟由于梁飞团队的原因导致Dubbo停止更新迭代五年,而中小型公司无法承担技术开发的成本导致Dubbo社区严重低落,而SpringCloud异军突起,迅速占领了微服务的市场,背靠Spring混的风生水起。
  • Dubbo经过多年的积累,文档相当成熟,对于微服务的架构体系各个公司也有稳定的现状。

2. 微服务的优缺点

2.1 优点
  • 每一个服务足够内聚,代码容易理解
  • 开发效率提高,一个服务只做一件事
  • 微服务能够被小团队单独开发
  • 微服务是松耦合的,是有功能意义的服务
  • 可以用不同的语言开发,面向接口编程
  • 易于与第三方集成
  • 微服务只是业务逻辑的代码,不会和HTML,CSS或者其他界面组合
  • 可以灵活搭配,连接公共库/连接独立库
2.2 缺点
  • 分布式系统的复杂性
  • 多服务运维难度,随着服务的增加,运维的压力也在增大
  • 系统部署依赖
  • 服务间通信成本
  • 数据一致性问题
  • 系统集成测试
  • 性能监控问题

3. 你所知道的微服务技术栈

维度(SpringCloud)

  • 服务开发:SpringBoot Spring SpringMVC
  • 服务配置与管理:Netfix公司的Archaiusm ,阿里的Diamond
  • 服务注册与发现:Eureka Consul Zookeeper Nacos
  • 服务调用:Rest RPC gRPC
  • 服务熔断器:Hystrix Sentinel
  • 服务负载均衡:Ribbon Nginx
  • 服务接口调用:Fegin
  • 消息队列:Kafka RabbitMQ ActiveMQ RocketMQ
  • 服务配置中心管理:SpringCloud Config
  • 服务路由(API网关)Zuul Gateway
  • 事件消息总线:SpringCloud Bus
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值